Home | History | Annotate | Download | only in external_protocol
      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 }
    317