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