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