Home | History | Annotate | Download | only in browser
      1 // Copyright (c) 2011 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_handler.h"
      6 
      7 #include <set>
      8 
      9 #include "base/logging.h"
     10 #include "base/message_loop.h"
     11 #include "base/string_util.h"
     12 #include "base/threading/thread.h"
     13 #include "build/build_config.h"
     14 #include "chrome/browser/browser_process_impl.h"
     15 #include "chrome/browser/platform_util.h"
     16 #include "chrome/browser/prefs/pref_service.h"
     17 #include "chrome/browser/prefs/scoped_user_pref_update.h"
     18 #include "chrome/common/pref_names.h"
     19 #include "googleurl/src/gurl.h"
     20 #include "net/base/escape.h"
     21 
     22 // Whether we accept requests for launching external protocols. This is set to
     23 // false every time an external protocol is requested, and set back to true on
     24 // each user gesture. This variable should only be accessed from the UI thread.
     25 static bool g_accept_requests = true;
     26 
     27 // static
     28 void ExternalProtocolHandler::PrepopulateDictionary(DictionaryValue* win_pref) {
     29   static bool is_warm = false;
     30   if (is_warm)
     31     return;
     32   is_warm = true;
     33 
     34   static const char* const denied_schemes[] = {
     35     "afp",
     36     "data",
     37     "disk",
     38     "disks",
     39     // ShellExecuting file:///C:/WINDOWS/system32/notepad.exe will simply
     40     // execute the file specified!  Hopefully we won't see any "file" schemes
     41     // because we think of file:// URLs as handled URLs, but better to be safe
     42     // than to let an attacker format the user's hard drive.
     43     "file",
     44     "hcp",
     45     "javascript",
     46     "ms-help",
     47     "nntp",
     48     "shell",
     49     "vbscript",
     50     // view-source is a special case in chrome. When it comes through an
     51     // iframe or a redirect, it looks like an external protocol, but we don't
     52     // want to shellexecute it.
     53     "view-source",
     54     "vnd.ms.radio",
     55   };
     56 
     57   static const char* const allowed_schemes[] = {
     58     "mailto",
     59     "news",
     60     "snews",
     61   };
     62 
     63   bool should_block;
     64   for (size_t i = 0; i < arraysize(denied_schemes); ++i) {
     65     if (!win_pref->GetBoolean(denied_schemes[i], &should_block)) {
     66       win_pref->SetBoolean(denied_schemes[i], true);
     67     }
     68   }
     69 
     70   for (size_t i = 0; i < arraysize(allowed_schemes); ++i) {
     71     if (!win_pref->GetBoolean(allowed_schemes[i], &should_block)) {
     72       win_pref->SetBoolean(allowed_schemes[i], false);
     73     }
     74   }
     75 }
     76 
     77 // static
     78 ExternalProtocolHandler::BlockState ExternalProtocolHandler::GetBlockState(
     79     const std::string& scheme) {
     80   // If we are being carpet bombed, block the request.
     81   if (!g_accept_requests)
     82     return BLOCK;
     83 
     84   if (scheme.length() == 1) {
     85     // We have a URL that looks something like:
     86     //   C:/WINDOWS/system32/notepad.exe
     87     // ShellExecuting this URL will cause the specified program to be executed.
     88     return BLOCK;
     89   }
     90 
     91   // Check the stored prefs.
     92   // TODO(pkasting): http://b/1119651 This kind of thing should go in the
     93   // preferences on the profile, not in the local state.
     94   PrefService* pref = g_browser_process->local_state();
     95   if (pref) {  // May be NULL during testing.
     96     DictionaryPrefUpdate update_excluded_schemas(pref, prefs::kExcludedSchemes);
     97 
     98     // Warm up the dictionary if needed.
     99     PrepopulateDictionary(update_excluded_schemas.Get());
    100 
    101     bool should_block;
    102     if (update_excluded_schemas->GetBoolean(scheme, &should_block))
    103       return should_block ? BLOCK : DONT_BLOCK;
    104   }
    105 
    106   return UNKNOWN;
    107 }
    108 
    109 // static
    110 void ExternalProtocolHandler::SetBlockState(const std::string& scheme,
    111                                             BlockState state) {
    112   // Set in the stored prefs.
    113   // TODO(pkasting): http://b/1119651 This kind of thing should go in the
    114   // preferences on the profile, not in the local state.
    115   PrefService* pref = g_browser_process->local_state();
    116   if (pref) {  // May be NULL during testing.
    117     DictionaryPrefUpdate update_excluded_schemas(pref, prefs::kExcludedSchemes);
    118 
    119     if (state == UNKNOWN) {
    120       update_excluded_schemas->Remove(scheme, NULL);
    121     } else {
    122       update_excluded_schemas->SetBoolean(scheme,
    123                                           state == BLOCK ? true : false);
    124     }
    125   }
    126 }
    127 
    128 // static
    129 void ExternalProtocolHandler::LaunchUrl(const GURL& url,
    130                                         int render_process_host_id,
    131                                         int tab_contents_id) {
    132   DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type());
    133 
    134   // Escape the input scheme to be sure that the command does not
    135   // have parameters unexpected by the external program.
    136   std::string escaped_url_string = EscapeExternalHandlerValue(url.spec());
    137   GURL escaped_url(escaped_url_string);
    138   BlockState block_state = GetBlockState(escaped_url.scheme());
    139   if (block_state == BLOCK)
    140     return;
    141 
    142   g_accept_requests = false;
    143 
    144   if (block_state == UNKNOWN) {
    145     // Ask the user if they want to allow the protocol. This will call
    146     // LaunchUrlWithoutSecurityCheck if the user decides to accept the protocol.
    147     RunExternalProtocolDialog(escaped_url,
    148                               render_process_host_id,
    149                               tab_contents_id);
    150     return;
    151   }
    152 
    153   LaunchUrlWithoutSecurityCheck(escaped_url);
    154 }
    155 
    156 // static
    157 void ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck(const GURL& url) {
    158 #if defined(OS_MACOSX)
    159   // This must run on the UI thread on OS X.
    160   platform_util::OpenExternal(url);
    161 #else
    162   // Otherwise put this work on the file thread. On Windows ShellExecute may
    163   // block for a significant amount of time, and it shouldn't hurt on Linux.
    164   MessageLoop* loop = g_browser_process->file_thread()->message_loop();
    165   if (loop == NULL) {
    166     return;
    167   }
    168 
    169   loop->PostTask(FROM_HERE,
    170       NewRunnableFunction(&platform_util::OpenExternal, url));
    171 #endif
    172 }
    173 
    174 // static
    175 void ExternalProtocolHandler::RegisterPrefs(PrefService* prefs) {
    176   prefs->RegisterDictionaryPref(prefs::kExcludedSchemes);
    177 }
    178 
    179 // static
    180 void ExternalProtocolHandler::PermitLaunchUrl() {
    181   DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type());
    182   g_accept_requests = true;
    183 }
    184