WebNSStringExtrasIOS.m   [plain text]


/*
 * Copyright (C) 2010 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#if PLATFORM(IOS)

#import "WebNSStringExtrasIOS.h"
#import "WebNSURLExtras.h"
#import <WebCore/NSStringSPI.h>

@implementation NSString (WebNSStringExtrasIOS)

- (NSArray *)_web_possibleURLsForForUserTypedString:(BOOL)isPrefix
{
    NSMutableArray *result = [NSMutableArray array];
    
    NSMutableString *workString = [NSMutableString stringWithString:self];
    
    // Trim whitespace from ends (likely cut & paste artifact)
    CFStringTrimWhitespace((CFMutableStringRef)workString);
    
    // Strip out newlines and carriage returns (likely cut & paste artifact)
    CFStringFindAndReplace((CFMutableStringRef)workString, CFSTR("\n"), CFSTR(""), CFRangeMake (0, [workString length]), 0);
    CFStringFindAndReplace((CFMutableStringRef)workString, CFSTR("\r"), CFSTR(""), CFRangeMake (0, [workString length]), 0);
    
    if ([workString length] > 0) {
        
        // Looks like an absolute path or a ~-path
        if ([workString characterAtIndex:0] == '/' || 
            [workString characterAtIndex:0] == '~') {
            // - treat it as a pathname
            NSURL *URL = [NSURL fileURLWithPath:[workString _web_stringByExpandingTildeInPath]];
            if (URL != nil) {
                [result addObject:URL];
            }
        } else {
            NSRange firstColonRange = [workString rangeOfString:@":"];
            NSRange firstSlashRange = [workString rangeOfString:@"/"];
            NSRange firstDotRange = [workString rangeOfString:@"."];
            NSRange firstAtRange = [workString rangeOfString:@"@"];
            
            BOOL URLHasScheme = NO;
            if (firstColonRange.location != NSNotFound && (firstSlashRange.location == NSNotFound || firstSlashRange.location >= firstColonRange.location)) {
                // Do a "sanity check" on the scheme, if it is not one of the "well-known"
                // schemes we support, do some work to add some possibilities.
                // This is so typed strings like "localhost:8080" do not get
                // misinterpreted as a URL with the localhost: scheme.
                // Perhaps this could be improved to be a dynamic list of schemes
                // based on loaded protocol handlers, but I do not see the need for
                // the extra complexity.
                if ([workString _web_hasCaseInsensitivePrefix:@"http:"] ||
                    [workString _web_hasCaseInsensitivePrefix:@"https:"] ||
                    [workString _web_hasCaseInsensitivePrefix:@"file:"] ||
                    [workString _web_hasCaseInsensitivePrefix:@"ftp:"] ||
                    [workString _web_hasCaseInsensitivePrefix:@"javascript:"] ||
                    [workString _web_hasCaseInsensitivePrefix:@"about:"] ||
                    [workString _web_hasCaseInsensitivePrefix:@"radar:"] ||
                    [workString _web_hasCaseInsensitivePrefix:@"rdar:"]) {
                    URLHasScheme = YES;
                }
                else if ([workString length] > firstColonRange.location + 1) {
                    // If the first character following the first colon is not a number
                    // guess that this is not is a host:port combination, and that
                    // the string preceding the colon is a scheme we don't know about.
                    unichar c = [workString characterAtIndex:firstColonRange.location + 1];
                    if (c < '0' || c > '9') {
                        URLHasScheme = YES;
                    }
                } else {
                    // If there is no character following the colon, then assume the
                    // string preceding the colon is a scheme.
                    URLHasScheme = YES;
                }
            }
            
            if (URLHasScheme) {
                // - use it unchanged
                NSURL *URL = [NSURL _web_URLWithUserTypedString:workString];
                if (URL != nil) {
                    [result addObject:URL];
                }
            } else {
                NSURL *URL;
                
                // apparent hostname contains a dot and starts with "www." 
                if ([[workString lowercaseString] hasPrefix:@"www."]) {
                    // - just prepend "http://"
                    [workString insertString:@"http://" atIndex:0];
                    URL = [NSURL _web_URLWithUserTypedString:workString];
                    if (URL != nil) {
                        [result addObject:URL];
                    }
                    
                // apparent hostname contains a dot and starts with "ftp."
                } else if ([[workString lowercaseString] hasPrefix:@"ftp."]) {
                    // - just prepend "ftp://"
                    [workString insertString:@"ftp://" atIndex:0];
                    URL = [NSURL _web_URLWithUserTypedString:workString];
                    if (URL != nil) {
                        [result addObject:URL];
                    }
                    
                // apparent hostname contains "@"
                } else if (firstAtRange.location != NSNotFound && firstAtRange.location < firstSlashRange.location) {
                    // - justprepend "http://"
                    [workString insertString:@"http://" atIndex:0];
                    URL = [NSURL _web_URLWithUserTypedString:workString];
                    if (URL != nil) {
                        [result addObject:URL];
                    }
                    
                // apparent hostname contains a dot and doesn't start with anything special
                } else if (firstDotRange.location != NSNotFound && firstDotRange.location < firstSlashRange.location) {
                    // - try prepending "http://"
                    [workString insertString:@"http://" atIndex:0];
                    URL = [NSURL _web_URLWithUserTypedString:workString];
                    if (URL != nil) {
                        [result addObject:URL];
                    }
                    
                    // - try prepending "http://www."
                    [workString insertString:@"www." atIndex:strlen("http://")];
                    URL = [NSURL _web_URLWithUserTypedString:workString];
                    if (URL != nil) {
                        [result addObject:URL];
                    }
                    
                // apparent hostname doesn't contain a dot but is equal to "localhost"
                } else if ([[workString lowercaseString] isEqualToString:@"localhost"] ||
                           [[workString lowercaseString] hasPrefix:@"localhost/"]) {
                    // - just prepend "http://"
                    [workString insertString:@"http://" atIndex:0];
                    URL = [NSURL _web_URLWithUserTypedString:workString];
                    if (URL != nil) {
                        [result addObject:URL];
                    }
                    
                // apparent hostname doesn't contain a dot and is nothing special
                } else {
                    // - try prepending "http://"
                    [workString insertString:@"http://" atIndex:0];
                    URL = [NSURL _web_URLWithUserTypedString:workString];
                    if (URL != nil) {
                        [result addObject:URL];
                    }
                    
                    // - try prepending "http://www." and appending .com to the hostname, 
                    // but account for a port number if there is one
                    NSRange secondColonRange = [workString rangeOfString:@":" options:NSLiteralSearch range:NSMakeRange(5, [workString length] - 5)]; // 5 is length of http:
                    unsigned endOfHostnameOrPort = (secondColonRange.location != NSNotFound) ?
                        secondColonRange.location :
                        (firstSlashRange.location != NSNotFound) ?
                        strlen("http://") + firstSlashRange.location : 
                        [workString length];  
                    
                    if (!(isPrefix && endOfHostnameOrPort == [workString length])) {
                        [workString insertString:@".com" atIndex:endOfHostnameOrPort];
                    }
                    [workString insertString:@"www." atIndex:strlen("http://")];
                    URL = [NSURL _web_URLWithUserTypedString:workString];
                    if (URL != nil) {
                        [result addObject:URL];
                    }
                }
            }
        }
    }
    
    return result;
}


- (NSArray *)_web_possibleURLPrefixesForUserTypedString
{
    return [self _web_possibleURLsForForUserTypedString:YES];
}

- (NSArray *)_web_possibleURLsForUserTypedString
{
    return [self _web_possibleURLsForForUserTypedString:NO];
}

- (NSURL *)_web_bestURLForUserTypedString
{
    NSArray *URLs = [self _web_possibleURLsForUserTypedString];
    if ([URLs count] == 0) {
        return nil;
    }
    return [[URLs objectAtIndex:0] _webkit_canonicalize];
}

@end

#endif // PLATFORM(IOS)