Home | History | Annotate | Download | only in cocoa
      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 #import "chrome/browser/ui/cocoa/external_protocol_dialog.h"
      6 
      7 #include "base/message_loop/message_loop.h"
      8 #include "base/metrics/histogram.h"
      9 #include "base/strings/string_util.h"
     10 #include "base/strings/sys_string_conversions.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "chrome/browser/external_protocol/external_protocol_handler.h"
     13 #include "grit/chromium_strings.h"
     14 #include "grit/generated_resources.h"
     15 #include "ui/base/l10n/l10n_util_mac.h"
     16 #include "ui/gfx/text_elider.h"
     17 
     18 ///////////////////////////////////////////////////////////////////////////////
     19 // ExternalProtocolHandler
     20 
     21 // static
     22 void ExternalProtocolHandler::RunExternalProtocolDialog(
     23     const GURL& url, int render_process_host_id, int routing_id) {
     24   [[ExternalProtocolDialogController alloc] initWithGURL:&url
     25                                      renderProcessHostId:render_process_host_id
     26                                                routingId:routing_id];
     27 }
     28 
     29 ///////////////////////////////////////////////////////////////////////////////
     30 // ExternalProtocolDialogController
     31 
     32 @interface ExternalProtocolDialogController(Private)
     33 - (void)alertEnded:(NSAlert *)alert
     34         returnCode:(int)returnCode
     35        contextInfo:(void*)contextInfo;
     36 - (base::string16)appNameForProtocol;
     37 @end
     38 
     39 @implementation ExternalProtocolDialogController
     40 - (id)initWithGURL:(const GURL*)url
     41     renderProcessHostId:(int)renderProcessHostId
     42     routingId:(int)routingId {
     43   DCHECK(base::MessageLoopForUI::IsCurrent());
     44 
     45   if (!(self = [super init]))
     46     return nil;
     47 
     48   url_ = *url;
     49   render_process_host_id_ = renderProcessHostId;
     50   routing_id_ = routingId;
     51   creation_time_ = base::Time::Now();
     52 
     53   base::string16 appName = [self appNameForProtocol];
     54   if (appName.length() == 0) {
     55     // No registered apps for this protocol; give up and go home.
     56     [self autorelease];
     57     return nil;
     58   }
     59 
     60   alert_ = [[NSAlert alloc] init];
     61 
     62   [alert_ setMessageText:
     63       l10n_util::GetNSStringWithFixup(IDS_EXTERNAL_PROTOCOL_TITLE)];
     64 
     65   NSButton* allowButton = [alert_ addButtonWithTitle:
     66       l10n_util::GetNSStringWithFixup(IDS_EXTERNAL_PROTOCOL_OK_BUTTON_TEXT)];
     67   [allowButton setKeyEquivalent:@""];  // disallow as default
     68   [alert_ addButtonWithTitle:
     69       l10n_util::GetNSStringWithFixup(
     70         IDS_EXTERNAL_PROTOCOL_CANCEL_BUTTON_TEXT)];
     71 
     72   const int kMaxUrlWithoutSchemeSize = 256;
     73   base::string16 elided_url_without_scheme;
     74   gfx::ElideString(base::ASCIIToUTF16(url_.possibly_invalid_spec()),
     75                   kMaxUrlWithoutSchemeSize, &elided_url_without_scheme);
     76 
     77   NSString* urlString = l10n_util::GetNSStringFWithFixup(
     78       IDS_EXTERNAL_PROTOCOL_INFORMATION,
     79       base::ASCIIToUTF16(url_.scheme() + ":"),
     80       elided_url_without_scheme);
     81   NSString* appString = l10n_util::GetNSStringFWithFixup(
     82       IDS_EXTERNAL_PROTOCOL_APPLICATION_TO_LAUNCH,
     83       appName);
     84   NSString* warningString =
     85       l10n_util::GetNSStringWithFixup(IDS_EXTERNAL_PROTOCOL_WARNING);
     86   NSString* informativeText =
     87       [NSString stringWithFormat:@"%@\n\n%@\n\n%@",
     88                                  urlString,
     89                                  appString,
     90                                  warningString];
     91 
     92   [alert_ setInformativeText:informativeText];
     93 
     94   [alert_ setShowsSuppressionButton:YES];
     95   [[alert_ suppressionButton] setTitle:
     96       l10n_util::GetNSStringWithFixup(IDS_EXTERNAL_PROTOCOL_CHECKBOX_TEXT)];
     97 
     98   [alert_ beginSheetModalForWindow:nil  // nil here makes it app-modal
     99                      modalDelegate:self
    100                     didEndSelector:@selector(alertEnded:returnCode:contextInfo:)
    101                        contextInfo:nil];
    102 
    103   return self;
    104 }
    105 
    106 - (void)dealloc {
    107   [alert_ release];
    108 
    109   [super dealloc];
    110 }
    111 
    112 - (void)alertEnded:(NSAlert *)alert
    113         returnCode:(int)returnCode
    114        contextInfo:(void*)contextInfo {
    115   ExternalProtocolHandler::BlockState blockState =
    116       ExternalProtocolHandler::UNKNOWN;
    117   switch (returnCode) {
    118     case NSAlertFirstButtonReturn:
    119       blockState = ExternalProtocolHandler::DONT_BLOCK;
    120       break;
    121     case NSAlertSecondButtonReturn:
    122       blockState = ExternalProtocolHandler::BLOCK;
    123       break;
    124     default:
    125       NOTREACHED();
    126   }
    127 
    128   // Set the "don't warn me again" info.
    129   if ([[alert_ suppressionButton] state] == NSOnState)
    130     ExternalProtocolHandler::SetBlockState(url_.scheme(), blockState);
    131 
    132   if (blockState == ExternalProtocolHandler::DONT_BLOCK) {
    133     UMA_HISTOGRAM_LONG_TIMES("clickjacking.launch_url",
    134                              base::Time::Now() - creation_time_);
    135 
    136     ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck(
    137         url_, render_process_host_id_, routing_id_);
    138   }
    139 
    140   [self autorelease];
    141 }
    142 
    143 - (base::string16)appNameForProtocol {
    144   NSURL* url = [NSURL URLWithString:
    145       base::SysUTF8ToNSString(url_.possibly_invalid_spec())];
    146   CFURLRef openingApp = NULL;
    147   OSStatus status = LSGetApplicationForURL((CFURLRef)url,
    148                                            kLSRolesAll,
    149                                            NULL,
    150                                            &openingApp);
    151   if (status != noErr) {
    152     // likely kLSApplicationNotFoundErr
    153     return base::string16();
    154   }
    155   NSString* appPath = [(NSURL*)openingApp path];
    156   CFRelease(openingApp);  // NOT A BUG; LSGetApplicationForURL retains for us
    157   NSString* appDisplayName =
    158       [[NSFileManager defaultManager] displayNameAtPath:appPath];
    159 
    160   return base::SysNSStringToUTF16(appDisplayName);
    161 }
    162 
    163 @end
    164