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