Upstream version 9.37.195.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / external_protocol / external_protocol_handler.cc
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/external_protocol/external_protocol_handler.h"
6
7 #include <set>
8
9 #include "base/bind.h"
10 #include "base/logging.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/prefs/pref_registry_simple.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/prefs/scoped_user_pref_update.h"
15 #include "base/strings/string_util.h"
16 #include "base/threading/thread.h"
17 #include "build/build_config.h"
18 #include "chrome/browser/browser_process.h"
19 #include "chrome/browser/platform_util.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/tab_contents/tab_util.h"
22 #include "chrome/common/pref_names.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/web_contents.h"
25 #include "net/base/escape.h"
26 #include "url/gurl.h"
27
28 using content::BrowserThread;
29
30 // Whether we accept requests for launching external protocols. This is set to
31 // false every time an external protocol is requested, and set back to true on
32 // each user gesture. This variable should only be accessed from the UI thread.
33 static bool g_accept_requests = true;
34
35 namespace {
36
37 // Functions enabling unit testing. Using a NULL delegate will use the default
38 // behavior; if a delegate is provided it will be used instead.
39 ShellIntegration::DefaultProtocolClientWorker* CreateShellWorker(
40     ShellIntegration::DefaultWebClientObserver* observer,
41     const std::string& protocol,
42     ExternalProtocolHandler::Delegate* delegate) {
43   if (!delegate)
44     return new ShellIntegration::DefaultProtocolClientWorker(observer,
45                                                              protocol);
46
47   return delegate->CreateShellWorker(observer, protocol);
48 }
49
50 ExternalProtocolHandler::BlockState GetBlockStateWithDelegate(
51     const std::string& scheme,
52     ExternalProtocolHandler::Delegate* delegate) {
53   if (!delegate)
54     return ExternalProtocolHandler::GetBlockState(scheme);
55
56   return delegate->GetBlockState(scheme);
57 }
58
59 void RunExternalProtocolDialogWithDelegate(
60     const GURL& url,
61     int render_process_host_id,
62     int routing_id,
63     ExternalProtocolHandler::Delegate* delegate) {
64   if (!delegate) {
65     ExternalProtocolHandler::RunExternalProtocolDialog(url,
66                                                        render_process_host_id,
67                                                        routing_id);
68   } else {
69     delegate->RunExternalProtocolDialog(url, render_process_host_id,
70                                         routing_id);
71   }
72 }
73
74 void LaunchUrlWithoutSecurityCheckWithDelegate(
75     const GURL& url,
76     int render_process_host_id,
77     int tab_contents_id,
78     ExternalProtocolHandler::Delegate* delegate) {
79   if (!delegate) {
80     ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck(
81         url, render_process_host_id, tab_contents_id);
82   } else {
83     delegate->LaunchUrlWithoutSecurityCheck(url);
84   }
85 }
86
87 // When we are about to launch a URL with the default OS level application,
88 // we check if that external application will be us. If it is we just ignore
89 // the request.
90 class ExternalDefaultProtocolObserver
91     : public ShellIntegration::DefaultWebClientObserver {
92  public:
93   ExternalDefaultProtocolObserver(const GURL& escaped_url,
94                                   int render_process_host_id,
95                                   int tab_contents_id,
96                                   bool prompt_user,
97                                   ExternalProtocolHandler::Delegate* delegate)
98       : delegate_(delegate),
99         escaped_url_(escaped_url),
100         render_process_host_id_(render_process_host_id),
101         tab_contents_id_(tab_contents_id),
102         prompt_user_(prompt_user) {}
103
104   virtual void SetDefaultWebClientUIState(
105       ShellIntegration::DefaultWebClientUIState state) OVERRIDE {
106     DCHECK(base::MessageLoopForUI::IsCurrent());
107
108     // If we are still working out if we're the default, or we've found
109     // out we definately are the default, we end here.
110     if (state == ShellIntegration::STATE_PROCESSING) {
111       return;
112     }
113
114     if (delegate_)
115       delegate_->FinishedProcessingCheck();
116
117     if (state == ShellIntegration::STATE_IS_DEFAULT) {
118       if (delegate_)
119         delegate_->BlockRequest();
120       return;
121     }
122
123     // If we get here, either we are not the default or we cannot work out
124     // what the default is, so we proceed.
125     if (prompt_user_) {
126       // Ask the user if they want to allow the protocol. This will call
127       // LaunchUrlWithoutSecurityCheck if the user decides to accept the
128       // protocol.
129       RunExternalProtocolDialogWithDelegate(escaped_url_,
130           render_process_host_id_, tab_contents_id_, delegate_);
131       return;
132     }
133
134     LaunchUrlWithoutSecurityCheckWithDelegate(
135         escaped_url_, render_process_host_id_, tab_contents_id_, delegate_);
136   }
137
138   virtual bool IsOwnedByWorker() OVERRIDE { return true; }
139
140  private:
141   ExternalProtocolHandler::Delegate* delegate_;
142   GURL escaped_url_;
143   int render_process_host_id_;
144   int tab_contents_id_;
145   bool prompt_user_;
146 };
147
148 }  // namespace
149
150 // static
151 void ExternalProtocolHandler::PrepopulateDictionary(
152     base::DictionaryValue* win_pref) {
153   static bool is_warm = false;
154   if (is_warm)
155     return;
156   is_warm = true;
157
158   static const char* const denied_schemes[] = {
159     "afp",
160     "data",
161     "disk",
162     "disks",
163     // ShellExecuting file:///C:/WINDOWS/system32/notepad.exe will simply
164     // execute the file specified!  Hopefully we won't see any "file" schemes
165     // because we think of file:// URLs as handled URLs, but better to be safe
166     // than to let an attacker format the user's hard drive.
167     "file",
168     "hcp",
169     "javascript",
170     "ms-help",
171     "nntp",
172     "shell",
173     "vbscript",
174     // view-source is a special case in chrome. When it comes through an
175     // iframe or a redirect, it looks like an external protocol, but we don't
176     // want to shellexecute it.
177     "view-source",
178     "vnd.ms.radio",
179   };
180
181   static const char* const allowed_schemes[] = {
182     "mailto",
183     "news",
184     "snews",
185 #if defined(OS_WIN)
186     "ms-windows-store",
187 #endif
188   };
189
190   bool should_block;
191   for (size_t i = 0; i < arraysize(denied_schemes); ++i) {
192     if (!win_pref->GetBoolean(denied_schemes[i], &should_block)) {
193       win_pref->SetBoolean(denied_schemes[i], true);
194     }
195   }
196
197   for (size_t i = 0; i < arraysize(allowed_schemes); ++i) {
198     if (!win_pref->GetBoolean(allowed_schemes[i], &should_block)) {
199       win_pref->SetBoolean(allowed_schemes[i], false);
200     }
201   }
202 }
203
204 // static
205 ExternalProtocolHandler::BlockState ExternalProtocolHandler::GetBlockState(
206     const std::string& scheme) {
207   // If we are being carpet bombed, block the request.
208   if (!g_accept_requests)
209     return BLOCK;
210
211   if (scheme.length() == 1) {
212     // We have a URL that looks something like:
213     //   C:/WINDOWS/system32/notepad.exe
214     // ShellExecuting this URL will cause the specified program to be executed.
215     return BLOCK;
216   }
217
218   // Check the stored prefs.
219   // TODO(pkasting): http://b/1119651 This kind of thing should go in the
220   // preferences on the profile, not in the local state.
221   PrefService* pref = g_browser_process->local_state();
222   if (pref) {  // May be NULL during testing.
223     DictionaryPrefUpdate update_excluded_schemas(pref, prefs::kExcludedSchemes);
224
225     // Warm up the dictionary if needed.
226     PrepopulateDictionary(update_excluded_schemas.Get());
227
228     bool should_block;
229     if (update_excluded_schemas->GetBoolean(scheme, &should_block))
230       return should_block ? BLOCK : DONT_BLOCK;
231   }
232
233   return UNKNOWN;
234 }
235
236 // static
237 void ExternalProtocolHandler::SetBlockState(const std::string& scheme,
238                                             BlockState state) {
239   // Set in the stored prefs.
240   // TODO(pkasting): http://b/1119651 This kind of thing should go in the
241   // preferences on the profile, not in the local state.
242   PrefService* pref = g_browser_process->local_state();
243   if (pref) {  // May be NULL during testing.
244     DictionaryPrefUpdate update_excluded_schemas(pref, prefs::kExcludedSchemes);
245
246     if (state == UNKNOWN) {
247       update_excluded_schemas->Remove(scheme, NULL);
248     } else {
249       update_excluded_schemas->SetBoolean(scheme, (state == BLOCK));
250     }
251   }
252 }
253
254 // static
255 void ExternalProtocolHandler::LaunchUrlWithDelegate(const GURL& url,
256                                                     int render_process_host_id,
257                                                     int tab_contents_id,
258                                                     Delegate* delegate) {
259   DCHECK(base::MessageLoopForUI::IsCurrent());
260
261   // Escape the input scheme to be sure that the command does not
262   // have parameters unexpected by the external program.
263   std::string escaped_url_string = net::EscapeExternalHandlerValue(url.spec());
264   GURL escaped_url(escaped_url_string);
265   BlockState block_state =
266       GetBlockStateWithDelegate(escaped_url.scheme(), delegate);
267   if (block_state == BLOCK) {
268     if (delegate)
269       delegate->BlockRequest();
270     return;
271   }
272
273   g_accept_requests = false;
274
275   // The worker creates tasks with references to itself and puts them into
276   // message loops. When no tasks are left it will delete the observer and
277   // eventually be deleted itself.
278   ShellIntegration::DefaultWebClientObserver* observer =
279       new ExternalDefaultProtocolObserver(url,
280                                           render_process_host_id,
281                                           tab_contents_id,
282                                           block_state == UNKNOWN,
283                                           delegate);
284   scoped_refptr<ShellIntegration::DefaultProtocolClientWorker> worker =
285       CreateShellWorker(observer, escaped_url.scheme(), delegate);
286
287   // Start the check process running. This will send tasks to the FILE thread
288   // and when the answer is known will send the result back to the observer on
289   // the UI thread.
290   worker->StartCheckIsDefault();
291 }
292
293 // static
294 void ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck(
295     const GURL& url,
296     int render_process_host_id,
297     int tab_contents_id) {
298   content::WebContents* web_contents = tab_util::GetWebContentsByID(
299       render_process_host_id, tab_contents_id);
300   if (!web_contents)
301     return;
302
303   platform_util::OpenExternal(
304       Profile::FromBrowserContext(web_contents->GetBrowserContext()), url);
305 }
306
307 // static
308 void ExternalProtocolHandler::RegisterPrefs(PrefRegistrySimple* registry) {
309   registry->RegisterDictionaryPref(prefs::kExcludedSchemes);
310 }
311
312 // static
313 void ExternalProtocolHandler::PermitLaunchUrl() {
314   DCHECK(base::MessageLoopForUI::IsCurrent());
315   g_accept_requests = true;
316 }