Home | History | Annotate | Download | only in Panels
      1 /*
      2  * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  *
      8  * 1.  Redistributions of source code must retain the above copyright
      9  *     notice, this list of conditions and the following disclaimer.
     10  * 2.  Redistributions in binary form must reproduce the above copyright
     11  *     notice, this list of conditions and the following disclaimer in the
     12  *     documentation and/or other materials provided with the distribution.
     13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     14  *     its contributors may be used to endorse or promote products derived
     15  *     from this software without specific prior written permission.
     16  *
     17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 #import <WebKit/WebAuthenticationPanel.h>
     30 
     31 #import "WebLocalizableStringsInternal.h"
     32 #import <Foundation/NSURLAuthenticationChallenge.h>
     33 #import <Foundation/NSURLProtectionSpace.h>
     34 #import <Foundation/NSURLCredential.h>
     35 #import <WebKit/WebKitNSStringExtras.h>
     36 #import <WebKit/WebNSURLExtras.h>
     37 #import <wtf/Assertions.h>
     38 
     39 #import <WebKit/WebNSControlExtras.h>
     40 
     41 #define WebAuthenticationPanelNibName @"WebAuthenticationPanel"
     42 
     43 @implementation WebAuthenticationPanel
     44 
     45 -(id)initWithCallback:(id)cb selector:(SEL)sel
     46 {
     47     self = [self init];
     48     if (self != nil) {
     49         callback = [cb retain];
     50         selector = sel;
     51     }
     52     return self;
     53 }
     54 
     55 
     56 - (void)dealloc
     57 {
     58     [panel release];
     59 
     60     [callback release];
     61 
     62     [super dealloc];
     63 }
     64 
     65 // IB actions
     66 
     67 - (IBAction)cancel:(id)sender
     68 {
     69     // This is required because the body of this method is going to
     70     // remove all of the panel's remaining refs, which can cause a
     71     // crash later when finishing button hit tracking.  So we make
     72     // sure it lives on a bit longer.
     73     [[panel retain] autorelease];
     74 
     75     // This is required as a workaround for AppKit issue 4118422
     76     [[self retain] autorelease];
     77 
     78     [panel close];
     79     if (usingSheet) {
     80         [[NSApplication sharedApplication] endSheet:panel returnCode:1];
     81     } else {
     82         [[NSApplication sharedApplication] stopModalWithCode:1];
     83     }
     84 }
     85 
     86 - (IBAction)logIn:(id)sender
     87 {
     88     // This is required because the body of this method is going to
     89     // remove all of the panel's remaining refs, which can cause a
     90     // crash later when finishing button hit tracking.  So we make
     91     // sure it lives on a bit longer.
     92     [[panel retain] autorelease];
     93 
     94     [panel close];
     95     if (usingSheet) {
     96         [[NSApplication sharedApplication] endSheet:panel returnCode:0];
     97     } else {
     98         [[NSApplication sharedApplication] stopModalWithCode:0];
     99     }
    100 }
    101 
    102 - (BOOL)loadNib
    103 {
    104     if (!nibLoaded) {
    105         if ([NSBundle loadNibNamed:WebAuthenticationPanelNibName owner:self]) {
    106             nibLoaded = YES;
    107             [imageView setImage:[NSImage imageNamed:@"NSApplicationIcon"]];
    108         } else {
    109             LOG_ERROR("couldn't load nib named '%@'", WebAuthenticationPanelNibName);
    110             return FALSE;
    111         }
    112     }
    113     return TRUE;
    114 }
    115 
    116 // Methods related to displaying the panel
    117 
    118 -(void)setUpForChallenge:(NSURLAuthenticationChallenge *)chall
    119 {
    120     [self loadNib];
    121 
    122     NSURLProtectionSpace *space = [chall protectionSpace];
    123 
    124     NSString *host;
    125     if ([space port] == 0) {
    126         host = [[space host] _web_decodeHostName];
    127     } else {
    128         host = [NSString stringWithFormat:@"%@:%u", [[space host] _web_decodeHostName], [space port]];
    129     }
    130 
    131     NSString *realm = [space realm];
    132     if (!realm)
    133         realm = @"";
    134     NSString *message;
    135 
    136     // Consider the realm name to be "simple" if it does not contain any whitespace or newline characters.
    137     // If the realm name is determined to be complex, we will use a slightly different sheet layout, designed
    138     // to keep a malicious realm name from spoofing the wording in the sheet text.
    139     BOOL realmNameIsSimple = [realm rangeOfCharacterFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].location == NSNotFound;
    140 
    141     if ([chall previousFailureCount] == 0) {
    142         if ([space isProxy]) {
    143             message = [NSString stringWithFormat:UI_STRING_INTERNAL("To view this page, you must log in to the %@ proxy server %@.",
    144                                                            "prompt string in authentication panel"),
    145                 [space proxyType], host];
    146         } else {
    147             if (realmNameIsSimple)
    148                 message = [NSString stringWithFormat:UI_STRING_INTERNAL("To view this page, you must log in to area %@ on %@.",
    149                                                                "prompt string in authentication panel"), realm, host];
    150             else
    151                 message = [NSString stringWithFormat:UI_STRING_INTERNAL("To view this page, you must log in to this area on %@:",
    152                                                                "prompt string in authentication panel"), host];
    153         }
    154     } else {
    155         if ([space isProxy]) {
    156             message = [NSString stringWithFormat:UI_STRING_INTERNAL("The user name or password you entered for the %@ proxy server %@ was incorrect. Make sure youre entering them correctly, and then try again.",
    157                                                            "prompt string in authentication panel"),
    158                 [space proxyType], host];
    159         } else {
    160             if (realmNameIsSimple)
    161                 message = [NSString stringWithFormat:UI_STRING_INTERNAL("The user name or password you entered for area %@ on %@ was incorrect. Make sure youre entering them correctly, and then try again.",
    162                                                                "prompt string in authentication panel"), realm, host];
    163             else
    164                 message = [NSString stringWithFormat:UI_STRING_INTERNAL("The user name or password you entered for this area on %@ was incorrect. Make sure youre entering them correctly, and then try again.",
    165                                                                "prompt string in authentication panel"), host];
    166         }
    167     }
    168 
    169     if (![space isProxy] && !realmNameIsSimple) {
    170         [separateRealmLabel setHidden:NO];
    171         [separateRealmLabel setStringValue:realm];
    172         [separateRealmLabel setAutoresizingMask:NSViewMinYMargin];
    173         [separateRealmLabel sizeToFitAndAdjustWindowHeight];
    174         [separateRealmLabel setAutoresizingMask:NSViewMaxYMargin];
    175     } else {
    176         // In the proxy or "simple" realm name case, we need to hide the 'separateRealmLabel'
    177         // and move the rest of the contents up appropriately to fill the space.
    178         NSRect mainLabelFrame = [mainLabel frame];
    179         NSRect realmFrame = [separateRealmLabel frame];
    180         NSRect smallLabelFrame = [smallLabel frame];
    181 
    182         // Find the distance between the 'smallLabel' and the label above it, initially the 'separateRealmLabel'.
    183         // Then, find the current distance between 'smallLabel' and 'mainLabel'.  The difference between
    184         // these two is how much shorter the panel needs to be after hiding the 'separateRealmLabel'.
    185         CGFloat smallLabelMargin = NSMinY(realmFrame) - NSMaxY(smallLabelFrame);
    186         CGFloat smallLabelToMainLabel = NSMinY(mainLabelFrame) - NSMaxY(smallLabelFrame);
    187         CGFloat deltaMargin = smallLabelToMainLabel - smallLabelMargin;
    188 
    189         [separateRealmLabel setHidden:YES];
    190         NSRect windowFrame = [panel frame];
    191         windowFrame.size.height -= deltaMargin;
    192         [panel setFrame:windowFrame display:NO];
    193     }
    194 
    195     [mainLabel setStringValue:message];
    196     [mainLabel sizeToFitAndAdjustWindowHeight];
    197 
    198     if ([space receivesCredentialSecurely] || [[space protocol] _webkit_isCaseInsensitiveEqualToString:@"https"]) {
    199         [smallLabel setStringValue:
    200             UI_STRING_INTERNAL("Your login information will be sent securely.",
    201                 "message in authentication panel")];
    202     } else {
    203         // Use this scary-sounding phrase only when using basic auth with non-https servers. In this case the password
    204         // could be sniffed by intercepting the network traffic.
    205         [smallLabel setStringValue:
    206             UI_STRING_INTERNAL("Your password will be sent unencrypted.",
    207                 "message in authentication panel")];
    208     }
    209 
    210     if ([[chall proposedCredential] user] != nil) {
    211         [username setStringValue:[[chall proposedCredential] user]];
    212         [panel setInitialFirstResponder:password];
    213     } else {
    214         [username setStringValue:@""];
    215         [password setStringValue:@""];
    216         [panel setInitialFirstResponder:username];
    217     }
    218 }
    219 
    220 - (void)runAsModalDialogWithChallenge:(NSURLAuthenticationChallenge *)chall
    221 {
    222     [self setUpForChallenge:chall];
    223 
    224     usingSheet = FALSE;
    225     [chall retain];
    226     NSURLCredential *credential = nil;
    227 
    228     if ([[NSApplication sharedApplication] runModalForWindow:panel] == 0) {
    229         credential = [[NSURLCredential alloc] initWithUser:[username stringValue] password:[password stringValue] persistence:([remember state] == NSOnState) ? NSURLCredentialPersistencePermanent : NSURLCredentialPersistenceForSession];
    230     }
    231 
    232     [callback performSelector:selector withObject:chall withObject:credential];
    233     [credential release];
    234     [chall release];
    235 }
    236 
    237 - (void)runAsSheetOnWindow:(NSWindow *)window withChallenge:(NSURLAuthenticationChallenge *)chall
    238 {
    239     ASSERT(!usingSheet);
    240 
    241     [self setUpForChallenge:chall];
    242 
    243     usingSheet = TRUE;
    244     challenge = [chall retain];
    245 
    246     [[NSApplication sharedApplication] beginSheet:panel modalForWindow:window modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:NULL];
    247 }
    248 
    249 - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void  *)contextInfo
    250 {
    251     NSURLCredential *credential = nil;
    252     NSURLAuthenticationChallenge *chall;
    253 
    254     ASSERT(usingSheet);
    255     ASSERT(challenge != nil);
    256 
    257     if (returnCode == 0) {
    258         credential = [[NSURLCredential alloc] initWithUser:[username stringValue] password:[password stringValue] persistence:([remember state] == NSOnState) ? NSURLCredentialPersistencePermanent : NSURLCredentialPersistenceForSession];
    259     }
    260 
    261     // We take this tricky approach to nilling out and releasing the challenge
    262     // because the callback below might remove our last ref.
    263     chall = challenge;
    264     challenge = nil;
    265     [callback performSelector:selector withObject:chall withObject:credential];
    266     [credential release];
    267     [chall release];
    268 }
    269 
    270 @end
    271 
    272 @implementation NonBlockingPanel
    273 
    274 - (BOOL)_blocksActionWhenModal:(SEL)theAction
    275 {
    276     // This override of a private AppKit method allows the user to quit when a login dialog
    277     // is onscreen, which is nice in general but in particular prevents pathological cases
    278     // like 3744583 from requiring a Force Quit.
    279     //
    280     // It would be nice to allow closing the individual window as well as quitting the app when
    281     // a login sheet is up, but this _blocksActionWhenModal: mechanism doesn't support that.
    282     // This override matches those in NSOpenPanel and NSToolbarConfigPanel.
    283     if (theAction == @selector(terminate:)) {
    284         return NO;
    285     }
    286     return YES;
    287 }
    288 
    289 @end
    290