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_EQ(base::MessageLoop::TYPE_UI, base::MessageLoop::current()->type());
    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(DictionaryValue* win_pref) {
    152   static bool is_warm = false;
    153   if (is_warm)
    154     return;
    155   is_warm = true;
    156 
    157   static const char* const denied_schemes[] = {
    158     "afp",
    159     "data",
    160     "disk",
    161     "disks",
    162     // ShellExecuting file:///C:/WINDOWS/system32/notepad.exe will simply
    163     // execute the file specified!  Hopefully we won't see any "file" schemes
    164     // because we think of file:// URLs as handled URLs, but better to be safe
    165     // than to let an attacker format the user's hard drive.
    166     "file",
    167     "hcp",
    168     "javascript",
    169     "ms-help",
    170     "nntp",
    171     "shell",
    172     "vbscript",
    173     // view-source is a special case in chrome. When it comes through an
    174     // iframe or a redirect, it looks like an external protocol, but we don't
    175     // want to shellexecute it.
    176     "view-source",
    177     "vnd.ms.radio",
    178   };
    179 
    180   static const char* const allowed_schemes[] = {
    181     "mailto",
    182     "news",
    183     "snews",
    184 #if defined(OS_WIN)
    185     "ms-windows-store",
    186 #endif
    187   };
    188 
    189   bool should_block;
    190   for (size_t i = 0; i < arraysize(denied_schemes); ++i) {
    191     if (!win_pref->GetBoolean(denied_schemes[i], &should_block)) {
    192       win_pref->SetBoolean(denied_schemes[i], true);
    193     }
    194   }
    195 
    196   for (size_t i = 0; i < arraysize(allowed_schemes); ++i) {
    197     if (!win_pref->GetBoolean(allowed_schemes[i], &should_block)) {
    198       win_pref->SetBoolean(allowed_schemes[i], false);
    199     }
    200   }
    201 }
    202 
    203 // static
    204 ExternalProtocolHandler::BlockState ExternalProtocolHandler::GetBlockState(
    205     const std::string& scheme) {
    206   // If we are being carpet bombed, block the request.
    207   if (!g_accept_requests)
    208     return BLOCK;
    209 
    210   if (scheme.length() == 1) {
    211     // We have a URL that looks something like:
    212     //   C:/WINDOWS/system32/notepad.exe
    213     // ShellExecuting this URL will cause the specified program to be executed.
    214     return BLOCK;
    215   }
    216 
    217   // Check the stored prefs.
    218   // TODO(pkasting): http://b/1119651 This kind of thing should go in the
    219   // preferences on the profile, not in the local state.
    220   PrefService* pref = g_browser_process->local_state();
    221   if (pref) {  // May be NULL during testing.
    222     DictionaryPrefUpdate update_excluded_schemas(pref, prefs::kExcludedSchemes);
    223 
    224     // Warm up the dictionary if needed.
    225     PrepopulateDictionary(update_excluded_schemas.Get());
    226 
    227     bool should_block;
    228     if (update_excluded_schemas->GetBoolean(scheme, &should_block))
    229       return should_block ? BLOCK : DONT_BLOCK;
    230   }
    231 
    232   return UNKNOWN;
    233 }
    234 
    235 // static
    236 void ExternalProtocolHandler::SetBlockState(const std::string& scheme,
    237                                             BlockState state) {
    238   // Set in the stored prefs.
    239   // TODO(pkasting): http://b/1119651 This kind of thing should go in the
    240   // preferences on the profile, not in the local state.
    241   PrefService* pref = g_browser_process->local_state();
    242   if (pref) {  // May be NULL during testing.
    243     DictionaryPrefUpdate update_excluded_schemas(pref, prefs::kExcludedSchemes);
    244 
    245     if (state == UNKNOWN) {
    246       update_excluded_schemas->Remove(scheme, NULL);
    247     } else {
    248       update_excluded_schemas->SetBoolean(scheme, (state == BLOCK));
    249     }
    250   }
    251 }
    252 
    253 // static
    254 void ExternalProtocolHandler::LaunchUrlWithDelegate(const GURL& url,
    255                                                     int render_process_host_id,
    256                                                     int tab_contents_id,
    257                                                     Delegate* delegate) {
    258   DCHECK_EQ(base::MessageLoop::TYPE_UI, base::MessageLoop::current()->type());
    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   if (block_state == BLOCK) {
    267     if (delegate)
    268       delegate->BlockRequest();
    269     return;
    270   }
    271 
    272   g_accept_requests = false;
    273 
    274   // The worker creates tasks with references to itself and puts them into
    275   // message loops. When no tasks are left it will delete the observer and
    276   // eventually be deleted itself.
    277   ShellIntegration::DefaultWebClientObserver* observer =
    278       new ExternalDefaultProtocolObserver(url,
    279                                           render_process_host_id,
    280                                           tab_contents_id,
    281                                           block_state == UNKNOWN,
    282                                           delegate);
    283   scoped_refptr<ShellIntegration::DefaultProtocolClientWorker> worker =
    284       CreateShellWorker(observer, escaped_url.scheme(), delegate);
    285 
    286   // Start the check process running. This will send tasks to the FILE thread
    287   // and when the answer is known will send the result back to the observer on
    288   // the UI thread.
    289   worker->StartCheckIsDefault();
    290 }
    291 
    292 // static
    293 void ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck(
    294     const GURL& url,
    295     int render_process_host_id,
    296     int tab_contents_id) {
    297   content::WebContents* web_contents = tab_util::GetWebContentsByID(
    298       render_process_host_id, tab_contents_id);
    299   if (!web_contents)
    300     return;
    301 
    302   platform_util::OpenExternal(
    303       Profile::FromBrowserContext(web_contents->GetBrowserContext()), url);
    304 }
    305 
    306 // static
    307 void ExternalProtocolHandler::RegisterPrefs(PrefRegistrySimple* registry) {
    308   registry->RegisterDictionaryPref(prefs::kExcludedSchemes);
    309 }
    310 
    311 // static
    312 void ExternalProtocolHandler::PermitLaunchUrl() {
    313   DCHECK_EQ(base::MessageLoop::TYPE_UI, base::MessageLoop::current()->type());
    314   g_accept_requests = true;
    315 }
    316