| // Copyright (c) 2012 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 <windows.h> |
| #include <lmcons.h> |
| #include <shellapi.h> |
| #include <shlobj.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <strsafe.h> |
| #include <userenv.h> |
| #include <winspool.h> |
| |
| #include "base/at_exit.h" |
| #include "base/command_line.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/path_service.h" |
| #include "base/process/launch.h" |
| #include "base/process/process.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/string_util.h" |
| #include "base/win/registry.h" |
| #include "base/win/scoped_handle.h" |
| #include "base/win/windows_version.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/installer/launcher_support/chrome_launcher_support.h" |
| #include "cloud_print/common/win/cloud_print_utils.h" |
| #include "cloud_print/virtual_driver/win/port_monitor/spooler_win.h" |
| #include "cloud_print/virtual_driver/win/virtual_driver_consts.h" |
| #include "cloud_print/virtual_driver/win/virtual_driver_helpers.h" |
| |
| namespace cloud_print { |
| |
| namespace { |
| |
| const wchar_t kIePath[] = L"Internet Explorer\\iexplore.exe"; |
| |
| const char kChromeInstallUrl[] = |
| "https://google.com/cloudprint/learn/chrome.html"; |
| |
| const wchar_t kCloudPrintRegKey[] = L"Software\\Google\\CloudPrint"; |
| |
| const wchar_t kXpsMimeType[] = L"application/vnd.ms-xpsdocument"; |
| |
| const wchar_t kAppDataDir[] = L"Google\\Cloud Printer"; |
| |
| const wchar_t kDocumentPathPlaceHolder[] = L"%%Document_Path%%"; |
| |
| const wchar_t kDocumentTypePlaceHolder[] = L"%%Document_Type%%"; |
| |
| const wchar_t kJobTitlePlaceHolder[] = L"%%Job_Title%%"; |
| |
| struct MonitorData { |
| std::unique_ptr<base::AtExitManager> at_exit_manager; |
| }; |
| |
| struct PortData { |
| PortData() : job_id(0), printer_handle(NULL), file(0) {} |
| ~PortData() { Close(); } |
| void Close() { |
| if (printer_handle) { |
| ClosePrinter(printer_handle); |
| printer_handle = NULL; |
| } |
| if (file) { |
| base::CloseFile(file); |
| file = NULL; |
| } |
| } |
| DWORD job_id; |
| HANDLE printer_handle; |
| FILE* file; |
| base::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}; |
| |
| base::FilePath GetLocalAppDataLow() { |
| wchar_t system_buffer[MAX_PATH]; |
| if (FAILED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, |
| system_buffer))) |
| return base::FilePath(); |
| return base::FilePath(system_buffer).DirName().AppendASCII("LocalLow"); |
| } |
| |
| base::FilePath GetAppDataDir() { |
| base::FilePath file_path; |
| if (base::win::GetVersion() >= base::win::VERSION_VISTA) |
| file_path = GetLocalAppDataLow(); |
| else |
| PathService::Get(base::DIR_LOCAL_APP_DATA, &file_path); |
| if (file_path.empty()) { |
| LOG(ERROR) << "Can't get app data dir"; |
| } |
| return file_path.Append(kAppDataDir); |
| } |
| |
| // Delete files which where not deleted by chrome. |
| void DeleteLeakedFiles(const base::FilePath& dir) { |
| base::Time delete_before = base::Time::Now() - base::TimeDelta::FromDays(1); |
| base::FileEnumerator enumerator(dir, false, base::FileEnumerator::FILES); |
| for (base::FilePath file_path = enumerator.Next(); !file_path.empty(); |
| file_path = enumerator.Next()) { |
| if (enumerator.GetInfo().GetLastModifiedTime() < delete_before) |
| base::DeleteFile(file_path, false); |
| } |
| } |
| |
| // 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, base::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; |
| } |
| std::unique_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 base::string16& caption) { |
| if (hwnd != NULL && IsWindow(hwnd)) { |
| DisplayWindowsMessage(hwnd, CO_E_NOT_SUPPORTED, cloud_print::kPortName); |
| } |
| } |
| |
| // Gets the primary token for the user that submitted the print job. |
| bool GetUserToken(HANDLE* primary_token) { |
| 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, primary_token)) { |
| LOG(ERROR) << "Unable to get primary thread token."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool LaunchCommandAsUser(const base::CommandLine& command) { |
| HANDLE token = NULL; |
| if (!GetUserToken(&token)) { |
| LOG(ERROR) << "Unable to get user token."; |
| return false; |
| } |
| base::win::ScopedHandle primary_token_scoped(token); |
| base::LaunchOptions options; |
| options.as_user = primary_token_scoped.Get(); |
| base::LaunchProcess(command, options); |
| return true; |
| } |
| |
| // Escape the command line argument as necessary per Microsoft rules. |
| // See QuoteForCommandLineToArgvW in base/command_line.cc |
| base::string16 EscapeCommandLineArg(const base::string16& arg) { |
| base::string16 quotable_chars(L" \\\""); |
| if (arg.find_first_of(quotable_chars) == base::string16::npos) { |
| // No quoting necessary. |
| return arg; |
| } |
| |
| base::string16 out; |
| out.push_back(L'"'); |
| for (size_t i = 0; i < arg.size(); ++i) { |
| if (arg[i] == '\\') { |
| // Find the extent of this run of backslashes. |
| size_t start = i, end = start + 1; |
| for (; end < arg.size() && arg[end] == '\\'; ++end) { |
| } |
| size_t backslash_count = end - start; |
| |
| // Backslashes are escapes only if the run is followed by a double quote. |
| // Since we also will end the string with a double quote, we escape for |
| // either a double quote or the end of the string. |
| if (end == arg.size() || arg[end] == '"') { |
| // To quote, we need to output 2x as many backslashes. |
| backslash_count *= 2; |
| } |
| for (size_t j = 0; j < backslash_count; ++j) |
| out.push_back('\\'); |
| |
| // Advance i to one before the end to balance i++ in loop. |
| i = end - 1; |
| } else if (arg[i] == '"') { |
| out.push_back('\\'); |
| out.push_back('"'); |
| } else { |
| out.push_back(arg[i]); |
| } |
| } |
| out.push_back('"'); |
| |
| return out; |
| } |
| |
| // Launch the print command as specified in the cloud print registry. |
| bool LaunchPrintCommandFromTemplate(const base::string16& command_template, |
| const base::FilePath& xps_path, |
| const base::string16& job_title) { |
| base::string16 command_string(command_template); |
| // Substitude the place holder with the document path wrapped in quotes. |
| base::ReplaceFirstSubstringAfterOffset( |
| &command_string, 0, kDocumentPathPlaceHolder, |
| EscapeCommandLineArg(xps_path.value())); |
| // Substitude the place holder with the document type wrapped in quotes. |
| base::ReplaceFirstSubstringAfterOffset( |
| &command_string, 0, kDocumentTypePlaceHolder, kXpsMimeType); |
| // Substitude the place holder with the job title wrapped in quotes. |
| base::ReplaceFirstSubstringAfterOffset(&command_string, 0, |
| kJobTitlePlaceHolder, |
| EscapeCommandLineArg(job_title)); |
| |
| base::CommandLine command = base::CommandLine::FromString(command_string); |
| |
| return LaunchCommandAsUser(command); |
| } |
| |
| // Launches a page to allow the user to download chrome. |
| // TODO(abodenha@chromium.org) Point to a custom page explaining what's wrong |
| // rather than the generic chrome download page. See |
| // http://code.google.com/p/chromium/issues/detail?id=112019 |
| void LaunchChromeDownloadPage() { |
| if (kIsUnittest) |
| return; |
| HANDLE token = NULL; |
| if (!GetUserToken(&token)) { |
| LOG(ERROR) << "Unable to get user token."; |
| return; |
| } |
| base::win::ScopedHandle token_scoped(token); |
| |
| // Consider using the shell to invoke the default browser instead of hardcoded |
| // reference to IE which might not be available on the system. |
| base::FilePath ie_path; |
| PathService::Get(base::DIR_PROGRAM_FILESX86, &ie_path); |
| ie_path = ie_path.Append(kIePath); |
| base::CommandLine command_line(ie_path); |
| command_line.AppendArg(kChromeInstallUrl); |
| |
| base::LaunchOptions options; |
| options.as_user = token_scoped.Get(); |
| base::LaunchProcess(command_line, options); |
| } |
| |
| // Returns false if the print job is being run in a context |
| // that shouldn't be launching Chrome. |
| bool ValidateCurrentUser() { |
| HANDLE token = NULL; |
| if (!GetUserToken(&token)) { |
| // If we can't get the token we're probably not impersonating |
| // the user, so validation should fail. |
| return false; |
| } |
| base::win::ScopedHandle token_scoped(token); |
| |
| if (base::win::GetVersion() >= base::win::VERSION_VISTA) { |
| DWORD session_id = 0; |
| DWORD dummy; |
| if (!GetTokenInformation(token_scoped.Get(), TokenSessionId, |
| reinterpret_cast<void*>(&session_id), |
| sizeof(DWORD), &dummy)) { |
| return false; |
| } |
| if (session_id == 0) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } // namespace |
| |
| base::string16 ReadStringFromRegistry(HKEY root, const wchar_t* path_name) { |
| base::win::RegKey gcp_key(root, kCloudPrintRegKey, KEY_READ); |
| base::string16 data; |
| gcp_key.ReadValue(path_name, &data); |
| return data; |
| } |
| |
| base::string16 ReadStringFromAnyRegistry(const wchar_t* path_name) { |
| base::string16 result = ReadStringFromRegistry(HKEY_CURRENT_USER, path_name); |
| if (!result.empty()) |
| return result; |
| return ReadStringFromRegistry(HKEY_LOCAL_MACHINE, path_name); |
| } |
| |
| base::FilePath GetChromeExePath() { |
| base::string16 value = ReadStringFromAnyRegistry(kChromeExePathRegValue); |
| if (!value.empty() && base::PathExists(base::FilePath(value))) |
| return base::FilePath(value); |
| return chrome_launcher_support::GetAnyChromePath(false /* is_sxs */); |
| } |
| |
| base::FilePath GetChromeProfilePath() { |
| base::string16 value = ReadStringFromAnyRegistry(kChromeProfilePathRegValue); |
| if (!value.empty() && base::DirectoryExists(base::FilePath(value))) |
| return base::FilePath(value); |
| return base::FilePath(); |
| } |
| |
| // Launches the Cloud Print dialog in Chrome. |
| bool LaunchChromePrintDialog(const base::FilePath& xps_path, |
| const base::string16& job_title) { |
| base::FilePath chrome_path = GetChromeExePath(); |
| if (chrome_path.empty()) { |
| LOG(ERROR) << "Unable to get chrome exe path."; |
| LaunchChromeDownloadPage(); |
| return false; |
| } |
| |
| base::CommandLine command_line(chrome_path); |
| |
| base::FilePath chrome_profile = GetChromeProfilePath(); |
| if (!chrome_profile.empty()) |
| command_line.AppendSwitchPath(switches::kUserDataDir, chrome_profile); |
| |
| command_line.AppendSwitchPath(switches::kCloudPrintFile, xps_path); |
| command_line.AppendSwitchNative(switches::kCloudPrintFileType, kXpsMimeType); |
| command_line.AppendSwitchNative(switches::kCloudPrintJobTitle, job_title); |
| |
| return LaunchCommandAsUser(command_line); |
| } |
| |
| base::string16 GetPrintCommandTemplate() { |
| return ReadStringFromAnyRegistry(kPrintCommandRegValue); |
| } |
| |
| // Launches the print command. This will either launch Chrome to display the |
| // Cloud Print dialog or another exe as specified in the cloud print registry. |
| // xps_path references a file to print. |
| // job_title is the title to be used for the resulting print job. |
| bool LaunchPrintCommand(const base::FilePath& xps_path, |
| const base::string16& job_title) { |
| base::string16 command_template = GetPrintCommandTemplate(); |
| if (!command_template.empty()) { |
| return LaunchPrintCommandFromTemplate(command_template, xps_path, |
| job_title); |
| } else { |
| return LaunchChromePrintDialog(xps_path, job_title); |
| } |
| } |
| |
| BOOL WINAPI Monitor2EnumPorts(HANDLE, |
| wchar_t*, |
| DWORD level, |
| BYTE* ports, |
| DWORD ports_size, |
| DWORD* needed_bytes, |
| DWORD* returned) { |
| 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 += static_cast<DWORD>(cloud_print::kPortNameSize); |
| 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 - cloud_print::kPortNameSize); |
| if (level == 1) { |
| PORT_INFO_1* port_info = reinterpret_cast<PORT_INFO_1*>(ports); |
| port_info->pName = string_target; |
| StringCbCopy(port_info->pName, cloud_print::kPortNameSize, |
| cloud_print::kPortName); |
| } else { |
| PORT_INFO_2* port_info = reinterpret_cast<PORT_INFO_2*>(ports); |
| port_info->pPortName = string_target; |
| StringCbCopy(port_info->pPortName, cloud_print::kPortNameSize, |
| cloud_print::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) { |
| if (handle == NULL) { |
| LOG(ERROR) << "handle should not be NULL."; |
| SetLastError(ERROR_INVALID_PARAMETER); |
| return FALSE; |
| } |
| *handle = new PortData(); |
| return TRUE; |
| } |
| |
| BOOL WINAPI Monitor2StartDocPort(HANDLE port_handle, |
| wchar_t* printer_name, |
| DWORD job_id, |
| DWORD, |
| BYTE*) { |
| SetGoogleUpdateUsage(kGoogleUpdateProductId); |
| 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; |
| } |
| base::FilePath& file_path = port_data->file_path; |
| base::FilePath app_data_dir = GetAppDataDir(); |
| if (app_data_dir.empty()) |
| return FALSE; |
| DeleteLeakedFiles(app_data_dir); |
| if (!base::CreateDirectory(app_data_dir) || |
| !base::CreateTemporaryFileInDir(app_data_dir, &file_path)) { |
| LOG(ERROR) << "Can't create temporary file in " << app_data_dir.value(); |
| return FALSE; |
| } |
| port_data->file = base::OpenFile(file_path, "wb+"); |
| if (port_data->file == NULL) { |
| LOG(ERROR) << "Error opening file " << file_path.value() << "."; |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| BOOL WINAPI Monitor2WritePort(HANDLE port_handle, |
| BYTE* buffer, |
| DWORD buffer_size, |
| DWORD* bytes_written) { |
| 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(ERROR) << "Read is not supported."; |
| *read_bytes = 0; |
| SetLastError(ERROR_NOT_SUPPORTED); |
| return FALSE; |
| } |
| |
| BOOL WINAPI Monitor2EndDocPort(HANDLE port_handle) { |
| 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) { |
| base::CloseFile(port_data->file); |
| port_data->file = NULL; |
| bool delete_file = true; |
| int64_t file_size = 0; |
| base::GetFileSize(port_data->file_path, &file_size); |
| if (file_size > 0) { |
| base::string16 job_title; |
| if (port_data->printer_handle != NULL) { |
| GetJobTitle(port_data->printer_handle, port_data->job_id, &job_title); |
| } |
| if (LaunchPrintCommand(port_data->file_path, job_title)) { |
| delete_file = false; |
| } |
| } |
| if (delete_file) |
| base::DeleteFile(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); |
| } |
| port_data->Close(); |
| // 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) { |
| if (port_handle == NULL) { |
| LOG(ERROR) << "port_handle should not be NULL."; |
| SetLastError(ERROR_INVALID_PARAMETER); |
| return FALSE; |
| } |
| delete reinterpret_cast<PortData*>(port_handle); |
| return TRUE; |
| } |
| |
| VOID WINAPI Monitor2Shutdown(HANDLE monitor_handle) { |
| if (monitor_handle != NULL) { |
| delete reinterpret_cast<MonitorData*>(monitor_handle); |
| } |
| } |
| |
| BOOL WINAPI Monitor2XcvOpenPort(HANDLE, |
| const wchar_t*, |
| ACCESS_MASK granted_access, |
| HANDLE* handle) { |
| if (handle == NULL) { |
| LOG(ERROR) << "handle should not be NULL."; |
| SetLastError(ERROR_INVALID_PARAMETER); |
| return FALSE; |
| } |
| XcvUiData* xcv_data = new XcvUiData(); |
| xcv_data->granted_access = granted_access; |
| *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) { |
| 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) { |
| DWORD dll_path_len = 0; |
| base::FilePath dll_path(cloud_print::GetPortMonitorDllName()); |
| dll_path_len = static_cast<DWORD>(dll_path.value().length()); |
| if (output_data_bytes_needed != NULL) { |
| *output_data_bytes_needed = dll_path_len; |
| } |
| if (output_data_bytes < dll_path_len) { |
| return ERROR_INSUFFICIENT_BUFFER; |
| } else { |
| ret_val = StringCbCopy(reinterpret_cast<wchar_t*>(output_data), |
| output_data_bytes, dll_path.value().c_str()); |
| } |
| } else { |
| return ERROR_INVALID_PARAMETER; |
| } |
| return ret_val; |
| } |
| |
| BOOL WINAPI Monitor2XcvClosePort(HANDLE handle) { |
| delete reinterpret_cast<XcvUiData*>(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) { |
| if (handle == NULL) { |
| SetLastError(ERROR_INVALID_PARAMETER); |
| return NULL; |
| } |
| cloud_print::MonitorData* monitor_data = new cloud_print::MonitorData; |
| *handle = monitor_data; |
| if (!cloud_print::kIsUnittest) { |
| // Unit tests set up their own AtExitManager |
| monitor_data->at_exit_manager.reset(new base::AtExitManager()); |
| // Single spooler.exe handles verbose users. |
| PathService::DisableCache(); |
| } |
| return &cloud_print::g_monitor_2; |
| } |
| |
| MONITORUI* WINAPI InitializePrintMonitorUI(void) { |
| return &cloud_print::g_monitor_ui; |
| } |