diff options
author | abodenha@chromium.org <abodenha@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-06 20:01:57 +0000 |
---|---|---|
committer | abodenha@chromium.org <abodenha@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-06 20:01:57 +0000 |
commit | e08f6acd3e52d8d454d2f47edaac85cf752b147c (patch) | |
tree | 090bb96f1402c3564024dc39e623fd95f8cc429b /cloud_print | |
parent | 399092645ed91a419c53310ad45ca2a68c9a74f5 (diff) | |
download | chromium_src-e08f6acd3e52d8d454d2f47edaac85cf752b147c.zip chromium_src-e08f6acd3e52d8d454d2f47edaac85cf752b147c.tar.gz chromium_src-e08f6acd3e52d8d454d2f47edaac85cf752b147c.tar.bz2 |
Initial CL for a simple port monitor for Windows to be used by the Cloud Print Virtual Print driver.
TEST=Everything should build and unit tests should pass.
Review URL: http://codereview.chromium.org/6778001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@80678 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'cloud_print')
6 files changed, 1108 insertions, 0 deletions
diff --git a/cloud_print/virtual_driver/virtual_driver.gyp b/cloud_print/virtual_driver/virtual_driver.gyp new file mode 100644 index 0000000..853e027 --- /dev/null +++ b/cloud_print/virtual_driver/virtual_driver.gyp @@ -0,0 +1,83 @@ +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'includes': [ + '../../build/common.gypi', + ], + 'target_defaults': { + 'variables': { + 'chromium_code': 1, + 'version_py_path': '../../chrome/tools/build/version.py', + 'version_path': 'VERSION', + }, + 'include_dirs': [ + '../..', + ], + 'libraries': [ + 'userenv.lib', + ], + 'sources': [ + 'win/port_monitor/port_monitor.cc', + 'win/port_monitor/port_monitor.h', + 'win/port_monitor/port_monitor.def', + ], + }, + 'conditions': [ + ['OS=="win"', { + 'targets' : [ + { + 'target_name': 'gcp_portmon', + 'type': 'loadable_module', + 'dependencies': [ + '../../base/base.gyp:base', + '../../chrome/chrome.gyp:common_constants', + ], + 'msvs_guid': 'ED3D7186-C94E-4D8B-A8E7-B7260F638F46', + }, + { + 'target_name': 'gcp_portmon64', + 'type': 'loadable_module', + 'defines': [ + '<@(nacl_win64_defines)', + ], + 'dependencies': [ + '../../base/base.gyp:base_nacl_win64', + '../../chrome/chrome.gyp:common_constants_win64', + ], + 'msvs_guid': '9BB292F4-6104-495A-B415-C3E314F46D6F', + 'configurations': { + 'Common_Base': { + 'msvs_target_platform': 'x64', + }, + }, + }, + { + 'target_name': 'virtual_driver_unittests', + 'type': 'executable', + 'msvs_guid': '97F82D29-58D8-4909-86C8-F2BBBCC4FEBF', + 'dependencies': [ + '../../base/base.gyp:base', + '../../chrome/chrome.gyp:common_constants', + '../../base/base.gyp:test_support_base', + '../../testing/gmock.gyp:gmock', + '../../testing/gtest.gyp:gtest', + ], + 'sources': [ + # Infrastructure files. + '../../base/test/run_all_unittests.cc', + 'win/port_monitor/port_monitor_unittest.cc' + ], + }, + ], + }, + ], + ] +} + +# Local Variables: +# tab-width:2 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=2 shiftwidth=2: diff --git a/cloud_print/virtual_driver/win/port_monitor/port_monitor.cc b/cloud_print/virtual_driver/win/port_monitor/port_monitor.cc new file mode 100644 index 0000000..5cd870c --- /dev/null +++ b/cloud_print/virtual_driver/win/port_monitor/port_monitor.cc @@ -0,0 +1,600 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cloud_print/virtual_driver/win/port_monitor/port_monitor.h" + +#include <lmcons.h> +#include <shlobj.h> +#include <strsafe.h> +#include <userenv.h> +#include <windows.h> +#include <winspool.h> + +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/process.h" +#include "base/process_util.h" +#include "base/string16.h" +#include "base/win/registry.h" +#include "base/win/scoped_handle.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_switches.h" +#include "cloud_print/virtual_driver/win/port_monitor/spooler_win.h" + +namespace cloud_print { + +#ifndef UNIT_TEST +const wchar_t kChromeExePath[] = L"google\\chrome\\application\\chrome.exe"; +const wchar_t kChromePathRegValue[] = L"PathToChromeExe"; +#endif + +const wchar_t kChromePathRegKey[] = L"Software\\Google\\CloudPrint"; + +namespace { +#ifdef _WIN64 +const wchar_t kPortMonitorDllName[] = L"gcp_portmon64.dll"; +#else +const wchar_t kPortMonitorDllName[] = L"gcp_portmon.dll"; +#endif + +const wchar_t kPortName[] = L"GCP:"; + +const wchar_t kXpsMimeType[] = L"application/vnd.ms-xpsdocument"; + +const size_t kMaxCommandLineLen = 0x7FFF; + +const size_t kMaxMessageLen = 100; + +struct MonitorData { + base::AtExitManager* at_exit_manager; +}; + +struct PortData { + DWORD job_id; + HANDLE printer_handle; + FILE* file; + FilePath* file_path; +}; + +typedef struct { + ACCESS_MASK granted_access; +} XcvUiData; + + +MONITORUI g_monitor_ui = { + sizeof(MONITORUI), + MonitorUiAddPortUi, + MonitorUiConfigureOrDeletePortUI, + MonitorUiConfigureOrDeletePortUI +}; + +MONITOR2 g_monitor_2 = { + sizeof(MONITOR2), + Monitor2EnumPorts, + Monitor2OpenPort, + NULL, // OpenPortEx is not supported. + Monitor2StartDocPort, + Monitor2WritePort, + Monitor2ReadPort, + Monitor2EndDocPort, + Monitor2ClosePort, + NULL, // AddPort is not supported. + NULL, // AddPortEx is not supported. + NULL, // ConfigurePort is not supported. + NULL, // DeletePort is not supported. + NULL, + NULL, // SetPortTimeOuts is not supported. + Monitor2XcvOpenPort, + Monitor2XcvDataPort, + Monitor2XcvClosePort, + Monitor2Shutdown +}; + +// Frees any objects referenced by port_data and sets pointers to NULL. +void CleanupPortData(PortData* port_data) { + delete port_data->file_path; + port_data->file_path = NULL; + if (port_data->printer_handle != NULL) { + ClosePrinter(port_data->printer_handle); + port_data->printer_handle = NULL; + } + if (port_data->file != NULL) { + file_util::CloseFile(port_data->file); + port_data->file = NULL; + } +} + +// Attempts to retrieve the title of the specified print job. +// On success returns TRUE and the first title_chars characters of the job title +// are copied into title. +// On failure returns FALSE and title is unmodified. +bool GetJobTitle(HANDLE printer_handle, + DWORD job_id, + string16 *title) { + DCHECK(printer_handle != NULL); + DCHECK(title != NULL); + DWORD bytes_needed = 0; + GetJob(printer_handle, job_id, 1, NULL, 0, &bytes_needed); + if (bytes_needed == 0) { + LOG(ERROR) << "Unable to get bytes needed for job info."; + return false; + } + scoped_ptr<BYTE> buffer(new BYTE[bytes_needed]); + if (!GetJob(printer_handle, + job_id, + 1, + buffer.get(), + bytes_needed, + &bytes_needed)) { + LOG(ERROR) << "Unable to get job info."; + return false; + } + JOB_INFO_1* job_info = reinterpret_cast<JOB_INFO_1*>(buffer.get()); + *title = job_info->pDocument; + return true; +} + +// Handler for the UI functions exported by the port monitor. +// Verifies that a valid parent Window exists and then just displays an +// error message to let the user know that there is no interactive +// configuration. +void HandlePortUi(HWND hwnd, const string16& caption) { + if (hwnd != NULL && IsWindow(hwnd)) { + wchar_t message_text[kMaxMessageLen + 1] = L""; + + ::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + CO_E_NOT_SUPPORTED, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + message_text, + kMaxMessageLen, + NULL); + ::MessageBox(hwnd, message_text, caption.c_str(), MB_OK); + } +} + +// Launches the Cloud Print dialog in Chrome. +// xps_path references a file to print. +// job_title is the title to be used for the resulting print job. +// process_handle is set to the handle of the resulting process. +bool LaunchPrintDialog(const string16& xps_path, + const string16& job_title, + base::ProcessHandle* process_handle) { + DCHECK(process_handle != NULL); + HANDLE token = NULL; + if (!OpenThreadToken(GetCurrentThread(), + TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY, + FALSE, + &token)) { + LOG(ERROR) << "Unable to get thread token."; + return false; + } + base::win::ScopedHandle token_scoped(token); + if (!DuplicateTokenEx(token, + TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY, + NULL, + SecurityImpersonation, + TokenPrimary, + &token)) { + LOG(ERROR) << "Unable to get primary thread token."; + return false; + } + base::win::ScopedHandle primary_token_scoped(token); + FilePath chrome_path; + if (!GetChromeExePath(&chrome_path)) { + LOG(ERROR) << "Unable to get chrome exe path."; + return false; + } + CommandLine command_line(chrome_path); + command_line.AppendSwitchPath(switches::kCloudPrintFile, + FilePath(xps_path)); + command_line.AppendSwitchNative(switches::kCloudPrintFileType, + kXpsMimeType); + command_line.AppendSwitchNative(switches::kCloudPrintJobTitle, + job_title); + base::LaunchAppAsUser(primary_token_scoped, + command_line.command_line_string(), + false, + process_handle); + return true; +} + +// Returns false if the print job is being run in a context +// that shouldn't be launching Chrome. +bool ValidateCurrentUser() { + wchar_t user_name[UNLEN + 1] = L""; + DWORD name_size = sizeof(user_name); + GetUserName(user_name, &name_size); + LOG(INFO) << "Username is " << user_name << "."; + // TODO(abodenha@chromium.org) Return false if running as session 0 or + // as local system. + return true; +} +} // namespace + +bool GetChromeExePath(FilePath* chrome_path) { + base::win::RegKey app_path_key(HKEY_CURRENT_USER, + kChromePathRegKey, + KEY_READ); + DCHECK(chrome_path != NULL); + std::wstring reg_data; + if (SUCCEEDED(app_path_key.ReadValue(kChromePathRegValue, + ®_data))) { + if (!reg_data.empty() && file_util::PathExists(FilePath(reg_data))) { + *chrome_path = FilePath(reg_data); + return true; + } + } + // First check %localappdata%\google\chrome\application\chrome.exe + FilePath path; + PathService::Get(base::DIR_LOCAL_APP_DATA, &path); + path = path.Append(kChromeExePath); + if (file_util::PathExists(path)) { + *chrome_path = FilePath(path.value()); + return true; + } + + // Chrome doesn't appear to be installed per user. + // Now check %programfiles(x86)%\google\chrome\application + // TODO(abodenha@chromium.org) Extend PathService::Get to be able to + // return the X86 program files path. At a minimum, use SHGetKnownFolderPath + // instead. + wchar_t system_buffer[MAX_PATH] = L""; + SHGetFolderPath(NULL, + CSIDL_PROGRAM_FILESX86, + NULL, + SHGFP_TYPE_CURRENT, + system_buffer); + path = FilePath(system_buffer); + path = path.Append(kChromeExePath); + if (file_util::PathExists(path)) { + *chrome_path = FilePath(path.value()); + return true; + } + LOG(WARNING) << kChromeExePath << " not found."; + return false; +} + +BOOL WINAPI Monitor2EnumPorts(HANDLE, + wchar_t*, + DWORD level, + BYTE* ports, + DWORD ports_size, + DWORD* needed_bytes, + DWORD* returned) { + LOG(INFO) << "Monitor2EnumPorts"; + + if (needed_bytes == NULL) { + LOG(ERROR) << "needed_bytes should not be NULL."; + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + if (level == 1) { + *needed_bytes = sizeof(PORT_INFO_1); + } else if (level == 2) { + *needed_bytes = sizeof(PORT_INFO_2); + } else { + LOG(ERROR) << "Level " << level << "is not supported."; + SetLastError(ERROR_INVALID_LEVEL); + return FALSE; + } + *needed_bytes += sizeof(kPortName); + if (ports_size < *needed_bytes) { + LOG(WARNING) << *needed_bytes << " bytes are required. Only " + << ports_size << " were allocated."; + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + if (ports == NULL) { + LOG(ERROR) << "ports should not be NULL."; + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + if (returned == NULL) { + LOG(ERROR) << "returned should not be NULL."; + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + // Windows expects any strings refernced by PORT_INFO_X structures to + // appear at the END of the buffer referenced by ports. Placing + // strings immediately after the PORT_INFO_X structure will cause + // EnumPorts to fail until the spooler is restarted. + // This is NOT mentioned in the documentation. + wchar_t* string_target = + reinterpret_cast<wchar_t*>(ports + ports_size - sizeof(kPortName)); + if (level == 1) { + PORT_INFO_1* port_info = reinterpret_cast<PORT_INFO_1*>(ports); + port_info->pName = string_target; + StringCbCopy(port_info->pName, sizeof(kPortName), kPortName); + } else { + PORT_INFO_2* port_info = reinterpret_cast<PORT_INFO_2*>(ports); + port_info->pPortName = string_target; + StringCbCopy(port_info->pPortName, sizeof(kPortName), kPortName); + port_info->pMonitorName = NULL; + port_info->pDescription = NULL; + port_info->fPortType = PORT_TYPE_WRITE; + port_info->Reserved = 0; + } + *returned = 1; + return TRUE; +} + +BOOL WINAPI Monitor2OpenPort(HANDLE, wchar_t*, HANDLE* handle) { + LOG(INFO) << "Monitor2OpenPort"; + + PortData* port_data = + reinterpret_cast<PortData*>(GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT, + sizeof(PortData))); + if (port_data == NULL) { + LOG(ERROR) << "Unable to allocate memory for internal structures."; + SetLastError(E_OUTOFMEMORY); + return FALSE; + } + if (handle == NULL) { + LOG(ERROR) << "handle should not be NULL."; + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + *handle = (HANDLE)port_data; + return TRUE; +} + +BOOL WINAPI Monitor2StartDocPort(HANDLE port_handle, + wchar_t* printer_name, + DWORD job_id, + DWORD, + BYTE*) { + LOG(INFO) << "Monitor2StartDocPort"; + if (port_handle == NULL) { + LOG(ERROR) << "port_handle should not be NULL."; + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + if (printer_name == NULL) { + LOG(ERROR) << "printer_name should not be NULL."; + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + if (!ValidateCurrentUser()) { + // TODO(abodenha@chromium.org) Abort the print job. + return FALSE; + } + PortData* port_data = reinterpret_cast<PortData*>(port_handle); + port_data->job_id = job_id; + if (!OpenPrinter(printer_name, &(port_data->printer_handle), NULL)) { + LOG(WARNING) << "Unable to open printer " << printer_name << "."; + // We can continue without a handle to the printer. + // It just means we can't get the job title or tell the spooler that + // the print job is complete. + // This is the normal flow during a unit test. + port_data->printer_handle = NULL; + } + FilePath app_data; + port_data->file_path = new FilePath(); + if (port_data->file_path == NULL) { + LOG(ERROR) << "Unable to allocate memory for internal structures."; + SetLastError(E_OUTOFMEMORY); + return FALSE; + } + bool result = PathService::Get(base::DIR_LOCAL_APP_DATA_LOW, &app_data); + file_util::CreateTemporaryFileInDir(app_data, port_data->file_path); + port_data->file = file_util::OpenFile(*(port_data->file_path), "wb+"); + if (port_data->file == NULL) { + LOG(ERROR) << "Error opening file " << port_data->file_path << "."; + return FALSE; + } + + return TRUE; +} + +BOOL WINAPI Monitor2WritePort(HANDLE port_handle, + BYTE* buffer, + DWORD buffer_size, + DWORD* bytes_written) { + LOG(INFO) << "Monitor2WritePort"; + PortData* port_data = reinterpret_cast<PortData*>(port_handle); + if (!ValidateCurrentUser()) { + // TODO(abodenha@chromium.org) Abort the print job. + return FALSE; + } + *bytes_written = + static_cast<DWORD>(fwrite(buffer, 1, buffer_size, port_data->file)); + if (*bytes_written > 0) { + return TRUE; + } else { + return FALSE; + } +} + +BOOL WINAPI Monitor2ReadPort(HANDLE, BYTE*, DWORD, DWORD* read_bytes) { + LOG(INFO) << "Monitor2ReadPort"; + LOG(ERROR) << "Read is not supported."; + *read_bytes = 0; + SetLastError(ERROR_NOT_SUPPORTED); + return FALSE; +} + +BOOL WINAPI Monitor2EndDocPort(HANDLE port_handle) { + LOG(INFO) << "Monitor2EndDocPort"; + HANDLE process_handle = NULL; + if (!ValidateCurrentUser()) { + // TODO(abodenha@chromium.org) Abort the print job. + return FALSE; + } + PortData* port_data = reinterpret_cast<PortData*>(port_handle); + if (port_data == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + if (port_data->file != NULL) { + file_util::CloseFile(port_data->file); + port_data->file = NULL; + string16 job_title; + if (port_data->printer_handle != NULL) { + GetJobTitle(port_data->printer_handle, + port_data->job_id, + &job_title); + } + LaunchPrintDialog(port_data->file_path->value().c_str(), + job_title, + &process_handle); + + // Wait for the print dialog process to exit and then delete the file. + // TODO(abodenha@chromium.org) Consider launching a thread to handle the + // deletion. + if (process_handle != NULL) { + WaitForSingleObject(process_handle, INFINITE); + } + file_util::Delete(*(port_data->file_path), false); + } + if (port_data->printer_handle != NULL) { + // Tell the spooler that the job is complete. + SetJob(port_data->printer_handle, + port_data->job_id, + 0, + NULL, + JOB_CONTROL_SENT_TO_PRINTER); + } + CleanupPortData(port_data); + // Return success even if we can't display the dialog. + // TODO(abodenha@chromium.org) Come up with a better way of handling + // this situation. + return TRUE; +} + +BOOL WINAPI Monitor2ClosePort(HANDLE port_handle) { + LOG(INFO) << "Monitor2ClosePort"; + if (port_handle == NULL) { + LOG(ERROR) << "port_handle should not be NULL."; + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + PortData* port_data = reinterpret_cast<PortData*>(port_handle); + CleanupPortData(port_data); + GlobalFree(port_handle); + return TRUE; +} + +VOID WINAPI Monitor2Shutdown(HANDLE monitor_handle) { + LOG(INFO) << "Monitor2Shutdown"; + if (monitor_handle != NULL) { + MonitorData* monitor_data = + reinterpret_cast<MonitorData*>(monitor_handle); + delete monitor_data->at_exit_manager; + GlobalFree(monitor_handle); + } +} + +BOOL WINAPI Monitor2XcvOpenPort(HANDLE, + const wchar_t*, + ACCESS_MASK granted_access, + HANDLE* handle) { + LOG(INFO) << "Monitor2XcvOpenPort"; + if (handle == NULL) { + LOG(ERROR) << "handle should not be NULL."; + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + XcvUiData* xcv_data; + xcv_data = reinterpret_cast<XcvUiData*>(GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT, + sizeof(XcvUiData))); + if (xcv_data == NULL) { + LOG(ERROR) << "Unable to allocate memory for internal structures."; + SetLastError(E_OUTOFMEMORY); + return FALSE; + } + xcv_data->granted_access = granted_access; + *handle = (HANDLE)xcv_data; + return TRUE; +} + +DWORD WINAPI Monitor2XcvDataPort(HANDLE xcv_handle, + const wchar_t* data_name, + BYTE*, + DWORD, + BYTE* output_data, + DWORD output_data_bytes, + DWORD* output_data_bytes_needed) { + LOG(INFO) << "Monitor2XcvDataPort"; + XcvUiData* xcv_data = reinterpret_cast<XcvUiData*>(xcv_handle); + DWORD ret_val = ERROR_SUCCESS; + if ((xcv_data->granted_access & SERVER_ACCESS_ADMINISTER) == 0) { + return ERROR_ACCESS_DENIED; + } + if (output_data == NULL || output_data_bytes == 0) { + return ERROR_INVALID_PARAMETER; + } + // We don't handle AddPort or DeletePort since we don't support + // dynamic creation of ports. + if (lstrcmp(L"MonitorUI", data_name) == 0) { + if (output_data_bytes_needed != NULL) { + *output_data_bytes_needed = sizeof(kPortMonitorDllName); + } + if (output_data_bytes < sizeof(kPortMonitorDllName)) { + return ERROR_INSUFFICIENT_BUFFER; + } else { + ret_val = StringCbCopy(reinterpret_cast<wchar_t*>(output_data), + output_data_bytes, + kPortMonitorDllName); + } + } else { + return ERROR_INVALID_PARAMETER; + } + return ret_val; +} + +BOOL WINAPI Monitor2XcvClosePort(HANDLE handle) { + GlobalFree(handle); + return TRUE; +} + +BOOL WINAPI MonitorUiAddPortUi(const wchar_t*, + HWND hwnd, + const wchar_t* monitor_name, + wchar_t**) { + HandlePortUi(hwnd, monitor_name); + return TRUE; +} + +BOOL WINAPI MonitorUiConfigureOrDeletePortUI(const wchar_t*, + HWND hwnd, + const wchar_t* port_name) { + HandlePortUi(hwnd, port_name); + return TRUE; +} + +} // namespace cloud_print + +MONITOR2* WINAPI InitializePrintMonitor2(MONITORINIT*, + HANDLE* handle) { + LOG(INFO) << "InitializePrintMonitor2"; + cloud_print::MonitorData* monitor_data = + reinterpret_cast<cloud_print::MonitorData*> + (GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT, sizeof(cloud_print::MonitorData))); + if (monitor_data == NULL) { + return NULL; + } + if (handle != NULL) { + *handle = (HANDLE)monitor_data; + #ifndef UNIT_TEST + // Unit tests set up their own AtExitManager + monitor_data->at_exit_manager = new base::AtExitManager(); + #endif + } else { + SetLastError(ERROR_INVALID_PARAMETER); + return NULL; + } + return &cloud_print::g_monitor_2; +} + +MONITORUI* WINAPI InitializePrintMonitorUI(void) { + LOG(INFO) << "InitializePrintMonitorUI"; + return &cloud_print::g_monitor_ui; +} + diff --git a/cloud_print/virtual_driver/win/port_monitor/port_monitor.def b/cloud_print/virtual_driver/win/port_monitor/port_monitor.def new file mode 100644 index 0000000..af6da8a --- /dev/null +++ b/cloud_print/virtual_driver/win/port_monitor/port_monitor.def @@ -0,0 +1,8 @@ +; Copyright (c) 2011 The Chromium Authors. All rights reserved. +; Use of this source code is governed by a BSD-style license that can be +; found in the LICENSE file. + +EXPORTS + InitializePrintMonitor2 + InitializePrintMonitorUI + diff --git a/cloud_print/virtual_driver/win/port_monitor/port_monitor.h b/cloud_print/virtual_driver/win/port_monitor/port_monitor.h new file mode 100644 index 0000000..5b3c1b9 --- /dev/null +++ b/cloud_print/virtual_driver/win/port_monitor/port_monitor.h @@ -0,0 +1,88 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CLOUD_PRINT_VIRTUAL_DRIVER_WIN_PORT_MONITOR_PORT_MONITOR_H_ +#define CLOUD_PRINT_VIRTUAL_DRIVER_WIN_PORT_MONITOR_PORT_MONITOR_H_ +#pragma once + +#include <windows.h> +#include <string> +#include "base/file_util.h" +#include "base/process.h" +#include "base/string16.h" + +namespace cloud_print { + +// Fills chrome_path with the path to be used for launching Chrome. +bool GetChromeExePath(FilePath* chrome_path); + +// Implementations for the function pointers in the MONITOR2 structure +// returned by InitializePrintMonitor2. The prototypes and behaviors +// are as described in the MONITOR2 documentation from Microsoft. + +BOOL WINAPI Monitor2EnumPorts(HANDLE, + wchar_t*, + DWORD level, + BYTE* ports, + DWORD ports_size, + DWORD* needed_bytes, + DWORD* returned); + +BOOL WINAPI Monitor2OpenPort(HANDLE monitor_data, wchar_t*, HANDLE* handle); + +BOOL WINAPI Monitor2StartDocPort(HANDLE port_handle, + wchar_t* printer_name, + DWORD job_id, + DWORD, + BYTE*); + +BOOL WINAPI Monitor2WritePort(HANDLE port, + BYTE* buffer, + DWORD buffer_size, + DWORD* bytes_written); + +BOOL WINAPI Monitor2ReadPort(HANDLE, BYTE*, DWORD, DWORD* bytes_read); + +BOOL WINAPI Monitor2EndDocPort(HANDLE port_handle); + +BOOL WINAPI Monitor2ClosePort(HANDLE port_handle); + +VOID WINAPI Monitor2Shutdown(HANDLE monitor_handle); + +BOOL WINAPI Monitor2XcvOpenPort(HANDLE monitor, + const wchar_t*, + ACCESS_MASK granted_access, + HANDLE* handle); + +DWORD WINAPI Monitor2XcvDataPort(HANDLE xcv_handle, + const wchar_t* data_name, + BYTE*, + DWORD, + BYTE* output_data, + DWORD output_data_bytes, + DWORD* output_data_bytes_needed); + +BOOL WINAPI Monitor2XcvClosePort(HANDLE handle); + +// Implementations for the function pointers in the MONITORUI structure +// returned by InitializePrintMonitorUI. The prototypes and behaviors +// are as described in the MONITORUI documentation from Microsoft. + +BOOL WINAPI MonitorUiAddPortUi(const wchar_t*, + HWND hwnd, + const wchar_t* monitor_name, + wchar_t**); + +BOOL WINAPI MonitorUiConfigureOrDeletePortUI(const wchar_t*, + HWND hwnd, + const wchar_t* port_name); + +extern const wchar_t kChromeExePath[]; +extern const wchar_t kChromePathRegKey[]; +extern const wchar_t kChromePathRegValue[]; + +} // namespace cloud_print + +#endif // CLOUD_PRINT_VIRTUAL_DRIVER_WIN_PORT_MONITOR_PORT_MONITOR_H_ + diff --git a/cloud_print/virtual_driver/win/port_monitor/port_monitor_unittest.cc b/cloud_print/virtual_driver/win/port_monitor/port_monitor_unittest.cc new file mode 100644 index 0000000..61f949c9 --- /dev/null +++ b/cloud_print/virtual_driver/win/port_monitor/port_monitor_unittest.cc @@ -0,0 +1,220 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cloud_print/virtual_driver/win/port_monitor/port_monitor.h" +#include <winspool.h> +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/string16.h" +#include "base/win/registry.h" +#include "cloud_print/virtual_driver/win/port_monitor/spooler_win.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace cloud_print { +const wchar_t kChromeExePath[] = L"google\\chrome\\application\\chrometest.exe"; +const wchar_t kAlternateChromeExePath[] = + L"google\\chrome\\application\\chrometestalternate.exe"; +const wchar_t kChromePathRegValue[] =L"PathToChromeTestExe"; + +class PortMonitorTest : public testing::Test { + public: + PortMonitorTest() {} + protected: + // Creates a registry entry pointing at a chrome + virtual void SetUpChromeExeRegistry() { + // Create a temporary chrome.exe location value. + base::win::RegKey key(HKEY_CURRENT_USER, + cloud_print::kChromePathRegKey, + KEY_ALL_ACCESS); + + FilePath path; + PathService::Get(base::DIR_LOCAL_APP_DATA, &path); + path = path.Append(kAlternateChromeExePath); + ASSERT_EQ(ERROR_SUCCESS, + key.WriteValue(cloud_print::kChromePathRegValue, + path.value().c_str())); + } + // Deletes the registry entry created in SetUpChromeExeRegistry + virtual void DeleteChromeExeRegistry() { + base::win::RegKey key(HKEY_CURRENT_USER, + cloud_print::kChromePathRegKey, + KEY_ALL_ACCESS); + key.DeleteValue(cloud_print::kChromePathRegValue); + } + + virtual void CreateTempChromeExeFiles() { + FilePath path; + PathService::Get(base::DIR_LOCAL_APP_DATA, &path); + FilePath main_path = path.Append(kChromeExePath); + ASSERT_TRUE(file_util::CreateDirectory(main_path)); + FilePath alternate_path = path.Append(kAlternateChromeExePath); + ASSERT_TRUE(file_util::CreateDirectory(alternate_path)); + } + + virtual void DeleteTempChromeExeFiles() { + FilePath path; + PathService::Get(base::DIR_LOCAL_APP_DATA, &path); + FilePath main_path = path.Append(kChromeExePath); + ASSERT_TRUE(file_util::Delete(main_path, true)); + PathService::Get(base::DIR_LOCAL_APP_DATA, &path); + FilePath alternate_path = path.Append(kAlternateChromeExePath); + ASSERT_TRUE(file_util::Delete(alternate_path, true)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(PortMonitorTest); +}; + +TEST_F(PortMonitorTest, GetChromeExePathTest) { + FilePath chrome_path; + SetUpChromeExeRegistry(); + CreateTempChromeExeFiles(); + EXPECT_TRUE(cloud_print::GetChromeExePath(&chrome_path)); + EXPECT_TRUE( + chrome_path.value().rfind(kAlternateChromeExePath) != std::string::npos); + DeleteChromeExeRegistry(); + chrome_path.clear(); + EXPECT_TRUE(cloud_print::GetChromeExePath(&chrome_path)); + EXPECT_TRUE(chrome_path.value().rfind(kChromeExePath) != std::string::npos); + EXPECT_TRUE(file_util::PathExists(FilePath(chrome_path))); + DeleteTempChromeExeFiles(); + EXPECT_FALSE(cloud_print::GetChromeExePath(&chrome_path)); +} + +TEST_F(PortMonitorTest, EnumPortsTest) { + DWORD needed_bytes = 0; + DWORD returned = 0; + EXPECT_FALSE(Monitor2EnumPorts(NULL, + NULL, + 1, + NULL, + 0, + &needed_bytes, + &returned)); + EXPECT_EQ(ERROR_INSUFFICIENT_BUFFER, GetLastError()); + EXPECT_NE(0u, needed_bytes); + EXPECT_EQ(0u, returned); + + BYTE* buffer = new BYTE[needed_bytes]; + ASSERT_TRUE(buffer != NULL); + EXPECT_TRUE(Monitor2EnumPorts(NULL, + NULL, + 1, + buffer, + needed_bytes, + &needed_bytes, + &returned)); + EXPECT_NE(0u, needed_bytes); + EXPECT_EQ(1u, returned); + PORT_INFO_1* port_info_1 = reinterpret_cast<PORT_INFO_1*>(buffer); + EXPECT_TRUE(port_info_1->pName != NULL); + delete[] buffer; + + returned = 0; + needed_bytes = 0; + EXPECT_FALSE(Monitor2EnumPorts(NULL, + NULL, + 2, + NULL, + 0, + &needed_bytes, + &returned)); + EXPECT_EQ(ERROR_INSUFFICIENT_BUFFER, GetLastError()); + EXPECT_NE(0u, needed_bytes); + EXPECT_EQ(0u, returned); + + buffer = new BYTE[needed_bytes]; + ASSERT_TRUE(buffer != NULL); + EXPECT_TRUE(Monitor2EnumPorts(NULL, + NULL, + 2, + buffer, + needed_bytes, + &needed_bytes, + &returned)); + EXPECT_NE(0u, needed_bytes); + EXPECT_EQ(1u, returned); + PORT_INFO_2* port_info_2 = reinterpret_cast<PORT_INFO_2*>(buffer); + EXPECT_TRUE(port_info_2->pPortName != NULL); + delete[] buffer; +} + +TEST_F(PortMonitorTest, FlowTest) { + const wchar_t kXcvDataItem[] = L"MonitorUI"; + MONITORINIT monitor_init = {0}; + HANDLE monitor_handle = NULL; + HANDLE port_handle = NULL; + HANDLE xcv_handle = NULL; + DWORD bytes_processed = 0; + DWORD bytes_needed = 0; + const size_t kBufferSize = 100; + BYTE buffer[kBufferSize] = {0}; + + // Initialize the print monitor + MONITOR2* monitor2 = InitializePrintMonitor2(&monitor_init, &monitor_handle); + EXPECT_TRUE(monitor2 != NULL); + EXPECT_TRUE(monitor_handle != NULL); + + // Test the XCV functions. Used for reporting the location of the + // UI portion of the port monitor. + EXPECT_TRUE(monitor2->pfnXcvOpenPort != NULL); + EXPECT_TRUE(monitor2->pfnXcvOpenPort(monitor_handle, NULL, 0, &xcv_handle)); + EXPECT_TRUE(xcv_handle != NULL); + EXPECT_TRUE(monitor2->pfnXcvDataPort != NULL); + EXPECT_EQ(ERROR_ACCESS_DENIED, + monitor2->pfnXcvDataPort(xcv_handle, + kXcvDataItem, + NULL, + 0, + buffer, + kBufferSize, + &bytes_needed)); + EXPECT_TRUE(monitor2->pfnXcvClosePort != NULL); + EXPECT_TRUE(monitor2->pfnXcvClosePort(xcv_handle)); + EXPECT_TRUE(monitor2->pfnXcvOpenPort(monitor_handle, + NULL, + SERVER_ACCESS_ADMINISTER, + &xcv_handle)); + EXPECT_TRUE(xcv_handle != NULL); + EXPECT_TRUE(monitor2->pfnXcvDataPort != NULL); + EXPECT_EQ(ERROR_SUCCESS, + monitor2->pfnXcvDataPort(xcv_handle, + kXcvDataItem, + NULL, + 0, + buffer, + kBufferSize, + &bytes_needed)); + EXPECT_TRUE(monitor2->pfnXcvClosePort != NULL); + EXPECT_TRUE(monitor2->pfnXcvClosePort(xcv_handle)); + + // Test opening the port and running a print job. + EXPECT_TRUE(monitor2->pfnOpenPort != NULL); + EXPECT_TRUE(monitor2->pfnOpenPort(monitor_handle, NULL, &port_handle)); + EXPECT_TRUE(port_handle != NULL); + EXPECT_TRUE(monitor2->pfnStartDocPort != NULL); + EXPECT_TRUE(monitor2->pfnStartDocPort(port_handle, L"", 0, 0, NULL)); + EXPECT_TRUE(monitor2->pfnWritePort != NULL); + EXPECT_TRUE(monitor2->pfnWritePort(port_handle, + buffer, + kBufferSize, + &bytes_processed)); + EXPECT_EQ(kBufferSize, bytes_processed); + EXPECT_TRUE(monitor2->pfnReadPort != NULL); + EXPECT_FALSE(monitor2->pfnReadPort(port_handle, + buffer, + sizeof(buffer), + &bytes_processed)); + EXPECT_EQ(0u, bytes_processed); + EXPECT_TRUE(monitor2->pfnEndDocPort != NULL); + EXPECT_TRUE(monitor2->pfnEndDocPort(port_handle)); + EXPECT_TRUE(monitor2->pfnClosePort != NULL); + EXPECT_TRUE(monitor2->pfnClosePort(port_handle)); + + // Shutdown the port monitor. + Monitor2Shutdown(monitor_handle); +} + +} // namespace cloud_print + diff --git a/cloud_print/virtual_driver/win/port_monitor/spooler_win.h b/cloud_print/virtual_driver/win/port_monitor/spooler_win.h new file mode 100644 index 0000000..ea45c9d --- /dev/null +++ b/cloud_print/virtual_driver/win/port_monitor/spooler_win.h @@ -0,0 +1,109 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CLOUD_PRINT_VIRTUAL_DRIVER_WIN_PORT_MONITOR_SPOOLER_WIN_H_ +#define CLOUD_PRINT_VIRTUAL_DRIVER_WIN_PORT_MONITOR_SPOOLER_WIN_H_ +#pragma once + +#include <windows.h> + +// Compatible structures and prototypes are also defined in the Windows DDK in +// winsplp.h. +#ifndef _WINSPLP_ + +typedef struct { + DWORD size; + BOOL (WINAPI *pfnEnumPorts)(HANDLE, + wchar_t*, + DWORD level, + BYTE* ports, + DWORD ports_size, + DWORD* needed_bytes, + DWORD* returned); + + BOOL (WINAPI *pfnOpenPort)(HANDLE monitor_data, wchar_t*, HANDLE* handle); + + void* pfnOpenPortEx; // Unused. + + BOOL (WINAPI *pfnStartDocPort)(HANDLE port_handle, + wchar_t* printer_name, + DWORD job_id, + DWORD, + BYTE*); + + BOOL (WINAPI *pfnWritePort)(HANDLE port, + BYTE* buffer, + DWORD buffer_size, + DWORD* bytes_written); + + BOOL (WINAPI *pfnReadPort)(HANDLE, BYTE*, DWORD, DWORD* bytes_read); + + BOOL (WINAPI *pfnEndDocPort)(HANDLE port_handle); + + BOOL (WINAPI *pfnClosePort)(HANDLE port_handle); + + void* pfnAddPort; // Unused. + + void* pfnAddPortEx; // Unused. + + void* pfnConfigurePort; // Unused. + + void* pfnDeletePort; // Unused. + + void* pfnGetPrinterDataFromPort; // Unused. + + void* pfnSetPortTimeOuts; // Unusued. + + BOOL (WINAPI *pfnXcvOpenPort)(HANDLE monitor, + const wchar_t*, + ACCESS_MASK granted_access, + HANDLE* handle); + + DWORD (WINAPI *pfnXcvDataPort)(HANDLE xcv_handle, + const wchar_t* data_name, + BYTE*, + DWORD, + BYTE* output_data, + DWORD output_data_bytes, + DWORD* output_data_bytes_needed); + + BOOL (WINAPI *pfnXcvClosePort)(HANDLE handle); + + VOID (WINAPI *pfnShutdown)(HANDLE monitor_handle); +} MONITOR2; + +typedef struct { + DWORD size; + + BOOL (WINAPI *pfnAddPortUI)(const wchar_t*, + HWND hwnd, + const wchar_t* monitor_name, + wchar_t**); + + BOOL (WINAPI *pfnConfigurePortUI)(const wchar_t*, + HWND hwnd, + const wchar_t* port_name); + + BOOL (WINAPI *pfnDeletePortUI)(const wchar_t*, + HWND hwnd, + const wchar_t* port_name); +} MONITORUI; + +typedef struct { + DWORD cbSize; + HANDLE hSpooler; + HKEY hckRegistryRoot; + void* pMonitorReg; // Unused + BOOL bLocal; + LPCWSTR pszServerName; +} MONITORINIT; + +MONITOR2* WINAPI InitializePrintMonitor2(MONITORINIT* monitor_init, + HANDLE* monitor_handle); + +MONITORUI* WINAPI InitializePrintMonitorUI(void); + +#endif // ifdef USE_WIN_DDK +#endif // CLOUD_PRINT_VIRTUAL_DRIVER_WIN_PORT_MONITOR_SPOOLER_WIN_H_ + |