blob: 9d40823c93a874380a1c27eb7604adfab33c5efc [file] [log] [blame]
initial.commit09911bf2008-07-26 23:55:291// Copyright 2008, Google Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8// * Redistributions of source code must retain the above copyright
9// notice, this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above
11// copyright notice, this list of conditions and the following disclaimer
12// in the documentation and/or other materials provided with the
13// distribution.
14// * Neither the name of Google Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30#include "chrome/browser/external_protocol_handler.h"
31
32#include <windows.h>
33#include <shellapi.h>
34#include <set>
35
36#include "base/logging.h"
37#include "base/message_loop.h"
38#include "base/registry.h"
39#include "base/scoped_ptr.h"
40#include "chrome/browser/browser.h"
41#include "chrome/browser/browser_process_impl.h"
42#include "chrome/browser/external_protocol_dialog.h"
43#include "chrome/common/pref_service.h"
44#include "chrome/common/pref_names.h"
45#include "net/base/escape.h"
46
47// static
48void ExternalProtocolHandler::PrepopulateDictionary(DictionaryValue* win_pref) {
49 static bool is_warm = false;
50 if (is_warm)
51 return;
52 is_warm = true;
53
54 static const wchar_t* const denied_schemes[] = {
55 L"afp",
56 L"data",
57 L"disk",
58 L"disks",
59 // ShellExecuting file:///C:/WINDOWS/system32/notepad.exe will simply
60 // execute the file specified! Hopefully we won't see any "file" schemes
61 // because we think of file:// URLs as handled URLs, but better to be safe
62 // than to let an attacker format the user's hard drive.
63 L"file",
64 L"hcp",
65 L"javascript",
66 L"ms-help",
67 L"nntp",
68 L"shell",
69 L"vbscript",
70 // view-source is a special case in chrome. When it comes through an
71 // iframe or a redirect, it looks like an external protocol, but we don't
72 // want to shellexecute it.
73 L"view-source",
74 L"vnd.ms.radio",
75 };
76
77 static const wchar_t* const allowed_schemes[] = {
78 L"mailto",
79 L"news",
80 L"snews",
81 };
82
83 bool should_block;
84 for (int i = 0; i < arraysize(denied_schemes); ++i) {
85 if (!win_pref->GetBoolean(denied_schemes[i], &should_block)) {
86 win_pref->SetBoolean(denied_schemes[i], true);
87 }
88 }
89
90 for (int i = 0; i < arraysize(allowed_schemes); ++i) {
91 if (!win_pref->GetBoolean(allowed_schemes[i], &should_block)) {
92 win_pref->SetBoolean(allowed_schemes[i], false);
93 }
94 }
95}
96
97// static
98ExternalProtocolHandler::BlockState ExternalProtocolHandler::GetBlockState(
99 const std::wstring& scheme) {
100 if (scheme.length() == 1) {
101 // We have a URL that looks something like:
102 // C:/WINDOWS/system32/notepad.exe
103 // ShellExecuting this URL will cause the specified program to be executed.
104 return BLOCK;
105 }
106
107 // Check the stored prefs.
108 // TODO(pkasting): http://b/119651 This kind of thing should go in the
109 // preferences on the profile, not in the local state.
110 PrefService* pref = g_browser_process->local_state();
111 if (pref) { // May be NULL during testing.
112 DictionaryValue* win_pref =
113 pref->GetMutableDictionary(prefs::kExcludedSchemes);
114 CHECK(win_pref);
115
116 // Warm up the dictionary if needed.
117 PrepopulateDictionary(win_pref);
118
119 bool should_block;
120 if (win_pref->GetBoolean(scheme, &should_block))
121 return should_block ? BLOCK : DONT_BLOCK;
122 }
123
124 return UNKNOWN;
125}
126
127// static
128void ExternalProtocolHandler::LaunchUrl(const GURL& url,
129 int render_process_host_id,
130 int tab_contents_id) {
131 // Escape the input scheme to be sure that the command does not
132 // have parameters unexpected by the external program.
133 std::string escaped_url_string = EscapeExternalHandlerValue(url.spec());
134 GURL escaped_url(escaped_url_string);
135 BlockState block_state = GetBlockState(ASCIIToWide(escaped_url.scheme()));
136 if (block_state == BLOCK)
137 return;
138
139 if (block_state == UNKNOWN) {
140 // Ask the user if they want to allow the protocol. This will call
141 // LaunchUrlWithoutSecurityCheck if the user decides to accept the protocol.
142 ExternalProtocolDialog::RunExternalProtocolDialog(escaped_url,
143 render_process_host_id,
144 tab_contents_id);
145 return;
146 }
147
148 MessageLoop* io_loop = g_browser_process->io_thread()->message_loop();
149 if (io_loop == NULL) {
150 return;
151 }
152
153 // Otherwise the protocol is white-listed, so go ahead and launch.
154 io_loop->PostTask(FROM_HERE,
155 NewRunnableFunction(
156 &ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck,
157 escaped_url));
158}
159
160// static
161void ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck(const GURL& url) {
162 DCHECK(g_browser_process->io_thread()->message_loop() ==
163 MessageLoop::current());
164 // Quote the input scheme to be sure that the command does not have
165 // parameters unexpected by the external program. This url should already
166 // have been escaped.
167 std::string escaped_url = url.spec();
168 escaped_url.insert(0, "\"");
169 escaped_url += "\"";
170
171 // According to Mozilla in uriloader/exthandler/win/nsOSHelperAppService.cpp:
172 // "Some versions of windows (Win2k before SP3, Win XP before SP1) crash in
173 // ShellExecute on long URLs (bug 161357 on bugzilla.mozilla.org). IE 5 and 6
174 // support URLS of 2083 chars in length, 2K is safe."
175 const int kMaxUrlLength = 2048;
176 if (escaped_url.length() > kMaxUrlLength) {
177 NOTREACHED();
178 return;
179 }
180
181 RegKey key;
182 std::wstring registry_path = ASCIIToWide(url.scheme()) +
183 L"\\shell\\open\\command";
184 key.Open(HKEY_CLASSES_ROOT, registry_path.c_str());
185 if (key.Valid()) {
186 DWORD size = 0;
187 key.ReadValue(NULL, NULL, &size);
188 if (size <= 2) {
189 // ShellExecute crashes the process when the command is empty.
190 // We check for "2" because it always returns the trailing NULL.
191 // TODO(nsylvain): we should also add a dialog to warn on errors. See
192 // bug 1136923.
193 return;
194 }
195 }
196
197 if (reinterpret_cast<ULONG_PTR>(ShellExecuteA(NULL, "open",
198 escaped_url.c_str(), NULL, NULL,
199 SW_SHOWNORMAL)) <= 32) {
200 // We fail to execute the call. We could display a message to the user.
201 // TODO(nsylvain): we should also add a dialog to warn on errors. See
202 // bug 1136923.
203 return;
204 }
205}
206
207// static
208void ExternalProtocolHandler::RegisterPrefs(PrefService* prefs) {
209 prefs->RegisterDictionaryPref(prefs::kExcludedSchemes);
210}