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