native_process_launcher_win.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/extensions/api/messaging/native_process_launcher.h"
6
7#include <windows.h>
8
9#include "base/command_line.h"
10#include "base/logging.h"
11#include "base/process_util.h"
12#include "base/strings/string16.h"
13#include "base/strings/string_number_conversions.h"
14#include "base/strings/stringprintf.h"
15#include "base/strings/utf_string_conversions.h"
16#include "base/win/registry.h"
17#include "base/win/scoped_handle.h"
18#include "chrome/browser/extensions/api/messaging/native_messaging_host_manifest.h"
19#include "crypto/random.h"
20
21namespace extensions {
22
23const wchar_t kNativeMessagingRegistryKey[] =
24    L"SOFTWARE\\Google\\Chrome\\NativeMessagingHosts";
25
26namespace {
27
28// Reads path to the native messaging host manifest from the registry. Returns
29// empty string if the path isn't found.
30string16 GetManifestPath(const string16& native_host_name, DWORD flags) {
31  base::win::RegKey key;
32  string16 result;
33
34  if (key.Open(HKEY_LOCAL_MACHINE, kNativeMessagingRegistryKey,
35               KEY_QUERY_VALUE | flags) != ERROR_SUCCESS ||
36      key.OpenKey(native_host_name.c_str(),
37                  KEY_QUERY_VALUE | flags) != ERROR_SUCCESS ||
38      key.ReadValue(NULL, &result) != ERROR_SUCCESS) {
39    return string16();
40  }
41
42  return result;
43}
44
45}  // namespace
46
47// static
48scoped_ptr<NativeMessagingHostManifest>
49NativeProcessLauncher::FindAndLoadManifest(
50    const std::string& native_host_name,
51    std::string* error_message) {
52  string16 native_host_name_wide = UTF8ToUTF16(native_host_name);
53
54  // First check 32-bit registry and then try 64-bit.
55  string16 manifest_path =
56      GetManifestPath(native_host_name_wide, KEY_WOW64_32KEY);
57  if (manifest_path.empty())
58    manifest_path = GetManifestPath(native_host_name_wide, KEY_WOW64_64KEY);
59
60  if (manifest_path.empty()) {
61    *error_message = "Native messaging host " + native_host_name +
62        " is not registered";
63    return scoped_ptr<NativeMessagingHostManifest>();
64  }
65
66  return NativeMessagingHostManifest::Load(
67      base::FilePath(manifest_path), error_message);
68}
69
70// static
71bool NativeProcessLauncher::LaunchNativeProcess(
72    const CommandLine& command_line,
73    base::PlatformFile* read_file,
74    base::PlatformFile* write_file) {
75  // Timeout for the IO pipes.
76  const DWORD kTimeoutMs = 5000;
77
78  // Windows will use default buffer size when 0 is passed to
79  // CreateNamedPipeW().
80  const DWORD kBufferSize = 0;
81
82  if (!command_line.GetProgram().IsAbsolute()) {
83    LOG(ERROR) << "Native Messaging host path must be absolute.";
84    return false;
85  }
86
87  uint64 pipe_name_token;
88  crypto::RandBytes(&pipe_name_token, sizeof(pipe_name_token));
89  string16 out_pipe_name = base::StringPrintf(
90      L"\\\\.\\pipe\\chrome.nativeMessaging.out.%llx", pipe_name_token);
91  string16 in_pipe_name = base::StringPrintf(
92      L"\\\\.\\pipe\\chrome.nativeMessaging.in.%llx", pipe_name_token);
93
94  // Create the pipes to read and write from.
95  base::win::ScopedHandle stdout_pipe(
96      CreateNamedPipeW(out_pipe_name.c_str(),
97                       PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED |
98                           FILE_FLAG_FIRST_PIPE_INSTANCE,
99                       PIPE_TYPE_BYTE, 1, kBufferSize, kBufferSize,
100                       kTimeoutMs, NULL));
101  if (!stdout_pipe.IsValid()) {
102    LOG(ERROR) << "Failed to create pipe " << out_pipe_name;
103    return false;
104  }
105
106  base::win::ScopedHandle stdin_pipe(
107      CreateNamedPipeW(in_pipe_name.c_str(),
108                       PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED |
109                           FILE_FLAG_FIRST_PIPE_INSTANCE,
110                       PIPE_TYPE_BYTE, 1, kBufferSize, kBufferSize,
111                       kTimeoutMs, NULL));
112  if (!stdin_pipe.IsValid()) {
113    LOG(ERROR) << "Failed to create pipe " << in_pipe_name;
114    return false;
115  }
116
117  DWORD comspec_length = ::GetEnvironmentVariable(L"COMSPEC", NULL, 0);
118  if (comspec_length == 0) {
119    LOG(ERROR) << "COMSPEC is not set";
120    return false;
121  }
122  scoped_ptr<wchar_t[]> comspec(new wchar_t[comspec_length]);
123  ::GetEnvironmentVariable(L"COMSPEC", comspec.get(), comspec_length);
124
125  string16 command_line_string = command_line.GetCommandLineString();
126
127  // 'start' command has a moronic syntax: if first argument is quoted then it
128  // interprets it as a command title. Host path may need to be in quotes, so
129  // we always need to specify the title as the first argument.
130  string16 command = base::StringPrintf(
131      L"%ls /c start \"Chrome Native Messaging Host\" /b "
132      L"%ls < %ls > %ls",
133      comspec.get(), command_line_string.c_str(),
134      in_pipe_name.c_str(), out_pipe_name.c_str());
135
136  base::LaunchOptions options;
137  options.start_hidden = true;
138  base::ProcessHandle cmd_handle;
139  if (!base::LaunchProcess(command.c_str(), options, &cmd_handle)) {
140    LOG(ERROR) << "Error launching process "
141               << command_line.GetProgram().MaybeAsASCII();
142    return false;
143  }
144
145  bool stdout_connected = ConnectNamedPipe(stdout_pipe.Get(), NULL) ?
146      TRUE : GetLastError() == ERROR_PIPE_CONNECTED;
147  bool stdin_connected = ConnectNamedPipe(stdin_pipe.Get(), NULL) ?
148      TRUE : GetLastError() == ERROR_PIPE_CONNECTED;
149  if (!stdout_connected || !stdin_connected) {
150    base::KillProcess(cmd_handle, 0, false);
151    base::CloseProcessHandle(cmd_handle);
152    LOG(ERROR) << "Failed to connect IO pipes when starting "
153               << command_line.GetProgram().MaybeAsASCII();
154    return false;
155  }
156
157  // Check that cmd.exe has completed with 0 exit code to make sure it was
158  // able to connect IO pipes.
159  int error_code;
160  if (!base::WaitForExitCodeWithTimeout(
161          cmd_handle, &error_code,
162          base::TimeDelta::FromMilliseconds(kTimeoutMs)) ||
163      error_code != 0) {
164    LOG(ERROR) << "cmd.exe did not exit cleanly";
165    base::KillProcess(cmd_handle, 0, false);
166    base::CloseProcessHandle(cmd_handle);
167    return false;
168  }
169
170  base::CloseProcessHandle(cmd_handle);
171
172  *read_file = stdout_pipe.Take();
173  *write_file = stdin_pipe.Take();
174
175  return true;
176}
177
178}  // namespace extensions
179