1/*
2 * Copyright (C) 2005, 2007, 2008, 2009 Apple Inc. All rights reserved.
3 * Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com)
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1.  Redistributions of source code must retain the above copyright
10 *     notice, this list of conditions and the following disclaimer.
11 * 2.  Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 *     its contributors may be used to endorse or promote products derived
16 *     from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#import "WebNSURLExtras.h"
31
32#import "WebKitNSStringExtras.h"
33#import "WebLocalizableStrings.h"
34#import "WebNSDataExtras.h"
35#import "WebSystemInterface.h"
36#import <Foundation/NSURLRequest.h>
37#import <WebCore/KURL.h>
38#import <WebCore/LoaderNSURLExtras.h>
39#import <WebCore/WebCoreNSURLExtras.h>
40#import <WebKitSystemInterface.h>
41#import <wtf/Assertions.h>
42#import <wtf/ObjcRuntimeExtras.h>
43#import <unicode/uchar.h>
44#import <unicode/uscript.h>
45
46using namespace WebCore;
47using namespace WTF;
48
49#define URL_BYTES_BUFFER_LENGTH 2048
50
51@implementation NSURL (WebNSURLExtras)
52
53+ (NSURL *)_web_URLWithUserTypedString:(NSString *)string relativeToURL:(NSURL *)URL
54{
55    return URLWithUserTypedString(string, URL);
56}
57
58+ (NSURL *)_web_URLWithUserTypedString:(NSString *)string
59{
60    return URLWithUserTypedString(string, nil);
61}
62
63+ (NSURL *)_web_URLWithDataAsString:(NSString *)string
64{
65    if (string == nil) {
66        return nil;
67    }
68    return [self _web_URLWithDataAsString:string relativeToURL:nil];
69}
70
71+ (NSURL *)_web_URLWithDataAsString:(NSString *)string relativeToURL:(NSURL *)baseURL
72{
73    if (string == nil) {
74        return nil;
75    }
76    string = [string _webkit_stringByTrimmingWhitespace];
77    NSData *data = [string dataUsingEncoding:NSISOLatin1StringEncoding];
78    return URLWithData(data, baseURL);
79}
80
81+ (NSURL *)_web_URLWithData:(NSData *)data
82{
83    return URLWithData(data, nil);
84}
85
86+ (NSURL *)_web_URLWithData:(NSData *)data relativeToURL:(NSURL *)baseURL
87{
88    return URLWithData(data, baseURL);
89}
90
91- (NSData *)_web_originalData
92{
93    return originalURLData(self);
94}
95
96- (NSString *)_web_originalDataAsString
97{
98    return [[[NSString alloc] initWithData:originalURLData(self) encoding:NSISOLatin1StringEncoding] autorelease];
99}
100
101- (NSString *)_web_userVisibleString
102{
103    return userVisibleString(self);
104}
105
106- (BOOL)_web_isEmpty
107{
108    if (!CFURLGetBaseURL((CFURLRef)self))
109        return CFURLGetBytes((CFURLRef)self, NULL, 0) == 0;
110    return [originalURLData(self) length] == 0;
111}
112
113- (const char *)_web_URLCString
114{
115    NSMutableData *data = [NSMutableData data];
116    [data appendData:originalURLData(self)];
117    [data appendBytes:"\0" length:1];
118    return (const char *)[data bytes];
119 }
120
121- (NSURL *)_webkit_canonicalize
122{
123    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:self];
124    Class concreteClass = WKNSURLProtocolClassForRequest(request);
125    if (!concreteClass) {
126        [request release];
127        return self;
128    }
129
130    // This applies NSURL's concept of canonicalization, but not KURL's concept. It would
131    // make sense to apply both, but when we tried that it caused a performance degradation
132    // (see 5315926). It might make sense to apply only the KURL concept and not the NSURL
133    // concept, but it's too risky to make that change for WebKit 3.0.
134    NSURLRequest *newRequest = [concreteClass canonicalRequestForRequest:request];
135    NSURL *newURL = [newRequest URL];
136    NSURL *result = [[newURL retain] autorelease];
137    [request release];
138
139    return result;
140}
141
142- (NSURL *)_web_URLByTruncatingOneCharacterBeforeComponent:(CFURLComponentType)component
143{
144    return URLByTruncatingOneCharacterBeforeComponent(self, component);
145}
146
147- (NSURL *)_webkit_URLByRemovingFragment
148{
149    return URLByTruncatingOneCharacterBeforeComponent(self, kCFURLComponentFragment);
150}
151
152- (NSURL *)_webkit_URLByRemovingResourceSpecifier
153{
154    return URLByTruncatingOneCharacterBeforeComponent(self, kCFURLComponentResourceSpecifier);
155}
156
157- (NSURL *)_web_URLByRemovingUserInfo
158{
159    return URLByRemovingUserInfo(self);
160}
161
162- (BOOL)_webkit_isJavaScriptURL
163{
164    return [[self _web_originalDataAsString] _webkit_isJavaScriptURL];
165}
166
167- (NSString *)_webkit_scriptIfJavaScriptURL
168{
169    return [[self absoluteString] _webkit_scriptIfJavaScriptURL];
170}
171
172- (BOOL)_webkit_isFileURL
173{
174    return [[self _web_originalDataAsString] _webkit_isFileURL];
175}
176
177- (BOOL)_webkit_isFTPDirectoryURL
178{
179    return [[self _web_originalDataAsString] _webkit_isFTPDirectoryURL];
180}
181
182- (BOOL)_webkit_shouldLoadAsEmptyDocument
183{
184    return [[self _web_originalDataAsString] _webkit_hasCaseInsensitivePrefix:@"about:"] || [self _web_isEmpty];
185}
186
187- (NSURL *)_web_URLWithLowercasedScheme
188{
189    CFRange range;
190    CFURLGetByteRangeForComponent((CFURLRef)self, kCFURLComponentScheme, &range);
191    if (range.location == kCFNotFound) {
192        return self;
193    }
194
195    UInt8 static_buffer[URL_BYTES_BUFFER_LENGTH];
196    UInt8 *buffer = static_buffer;
197    CFIndex bytesFilled = CFURLGetBytes((CFURLRef)self, buffer, URL_BYTES_BUFFER_LENGTH);
198    if (bytesFilled == -1) {
199        CFIndex bytesToAllocate = CFURLGetBytes((CFURLRef)self, NULL, 0);
200        buffer = static_cast<UInt8 *>(malloc(bytesToAllocate));
201        bytesFilled = CFURLGetBytes((CFURLRef)self, buffer, bytesToAllocate);
202        ASSERT(bytesFilled == bytesToAllocate);
203    }
204
205    int i;
206    BOOL changed = NO;
207    for (i = 0; i < range.length; ++i) {
208        char c = buffer[range.location + i];
209        char lower = toASCIILower(c);
210        if (c != lower) {
211            buffer[range.location + i] = lower;
212            changed = YES;
213        }
214    }
215
216    NSURL *result = changed
217        ? (NSURL *)HardAutorelease(CFURLCreateAbsoluteURLWithBytes(NULL, buffer, bytesFilled, kCFStringEncodingUTF8, nil, YES))
218        : (NSURL *)self;
219
220    if (buffer != static_buffer) {
221        free(buffer);
222    }
223
224    return result;
225}
226
227
228-(NSData *)_web_schemeSeparatorWithoutColon
229{
230    NSData *result = nil;
231    CFRange rangeWithSeparators;
232    CFRange range = CFURLGetByteRangeForComponent((CFURLRef)self, kCFURLComponentScheme, &rangeWithSeparators);
233    if (rangeWithSeparators.location != kCFNotFound) {
234        NSString *absoluteString = [self absoluteString];
235        NSRange separatorsRange = NSMakeRange(range.location + range.length + 1, rangeWithSeparators.length - range.length - 1);
236        if (separatorsRange.location + separatorsRange.length <= [absoluteString length]) {
237            NSString *slashes = [absoluteString substringWithRange:separatorsRange];
238            result = [slashes dataUsingEncoding:NSISOLatin1StringEncoding];
239        }
240    }
241    return result;
242}
243
244-(NSData *)_web_dataForURLComponentType:(CFURLComponentType)componentType
245{
246    return dataForURLComponentType(self, componentType);
247}
248
249-(NSData *)_web_schemeData
250{
251    return dataForURLComponentType(self, kCFURLComponentScheme);
252}
253
254-(NSData *)_web_hostData
255{
256    NSData *result = dataForURLComponentType(self, kCFURLComponentHost);
257    NSData *scheme = [self _web_schemeData];
258    // Take off localhost for file
259    if ([scheme _web_isCaseInsensitiveEqualToCString:"file"]) {
260        return ([result _web_isCaseInsensitiveEqualToCString:"localhost"]) ? nil : result;
261    }
262    return result;
263}
264
265- (NSString *)_web_hostString
266{
267    NSData *data = [self _web_hostData];
268    if (!data) {
269        data = [NSData data];
270    }
271    return [[[NSString alloc] initWithData:[self _web_hostData] encoding:NSUTF8StringEncoding] autorelease];
272}
273
274- (NSString *)_webkit_suggestedFilenameWithMIMEType:(NSString *)MIMEType
275{
276    return suggestedFilenameWithMIMEType(self, MIMEType);
277}
278
279- (NSURL *)_webkit_URLFromURLOrSchemelessFileURL
280{
281    if ([self scheme])
282        return self;
283
284    return [NSURL URLWithString:[@"file:" stringByAppendingString:[self absoluteString]]];
285}
286
287@end
288
289@implementation NSString (WebNSURLExtras)
290
291- (BOOL)_web_isUserVisibleURL
292{
293    return isUserVisibleURL(self);
294}
295
296- (BOOL)_webkit_isJavaScriptURL
297{
298    return [self _webkit_hasCaseInsensitivePrefix:@"javascript:"];
299}
300
301- (BOOL)_webkit_isFileURL
302{
303    return [self rangeOfString:@"file:" options:(NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound;
304}
305
306- (NSString *)_webkit_stringByReplacingValidPercentEscapes
307{
308    return decodeURLEscapeSequences(self);
309}
310
311- (NSString *)_webkit_scriptIfJavaScriptURL
312{
313    if (![self _webkit_isJavaScriptURL]) {
314        return nil;
315    }
316    return [[self substringFromIndex:11] _webkit_stringByReplacingValidPercentEscapes];
317}
318
319- (BOOL)_webkit_isFTPDirectoryURL
320{
321    int length = [self length];
322    if (length < 5) {  // 5 is length of "ftp:/"
323        return NO;
324    }
325    unichar lastChar = [self characterAtIndex:length - 1];
326    return lastChar == '/' && [self _webkit_hasCaseInsensitivePrefix:@"ftp:"];
327}
328
329- (BOOL)_web_hostNameNeedsDecodingWithRange:(NSRange)range
330{
331    return hostNameNeedsDecodingWithRange(self, range);
332}
333
334- (BOOL)_web_hostNameNeedsEncodingWithRange:(NSRange)range
335{
336    return hostNameNeedsEncodingWithRange(self, range);
337}
338
339- (NSString *)_web_decodeHostNameWithRange:(NSRange)range
340{
341    return decodeHostNameWithRange(self, range);
342}
343
344- (NSString *)_web_encodeHostNameWithRange:(NSRange)range
345{
346    return encodeHostNameWithRange(self, range);
347}
348
349- (NSString *)_web_decodeHostName
350{
351    return decodeHostName(self);
352}
353
354- (NSString *)_web_encodeHostName
355{
356    return encodeHostName(self);
357}
358
359-(NSRange)_webkit_rangeOfURLScheme
360{
361    NSRange colon = [self rangeOfString:@":"];
362    if (colon.location != NSNotFound && colon.location > 0) {
363        NSRange scheme = {0, colon.location};
364        static NSCharacterSet *InverseSchemeCharacterSet = nil;
365        if (!InverseSchemeCharacterSet) {
366            /*
367             This stuff is very expensive.  10-15 msec on a 2x1.2GHz.  If not cached it swamps
368             everything else when adding items to the autocomplete DB.  Makes me wonder if we
369             even need to enforce the character set here.
370            */
371            NSString *acceptableCharacters = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+.-";
372            InverseSchemeCharacterSet = [[[NSCharacterSet characterSetWithCharactersInString:acceptableCharacters] invertedSet] retain];
373        }
374        NSRange illegals = [self rangeOfCharacterFromSet:InverseSchemeCharacterSet options:0 range:scheme];
375        if (illegals.location == NSNotFound)
376            return scheme;
377    }
378    return NSMakeRange(NSNotFound, 0);
379}
380
381-(BOOL)_webkit_looksLikeAbsoluteURL
382{
383    // Trim whitespace because _web_URLWithString allows whitespace.
384    return [[self _webkit_stringByTrimmingWhitespace] _webkit_rangeOfURLScheme].location != NSNotFound;
385}
386
387- (NSString *)_webkit_URLFragment
388{
389    NSRange fragmentRange;
390
391    fragmentRange = [self rangeOfString:@"#" options:NSLiteralSearch];
392    if (fragmentRange.location == NSNotFound)
393        return nil;
394    return [self substringFromIndex:fragmentRange.location + 1];
395}
396
397@end
398