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_FAMILY)

#import "WebNSStringExtrasIOS.h"
#import "WebNSURLExtras.h"
#import <pal/spi/cocoa/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 _webkit_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 _webkit_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 _webkit_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 _webkit_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 _webkit_URLWithUserTypedString:workString];
if (URL != nil) {
[result addObject:URL];
}

// - try prepending "http://www."
[workString insertString:@"www." atIndex:strlen("http://")];
URL = [NSURL _webkit_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 _webkit_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 _webkit_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 _webkit_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_FAMILY)