• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2004, 2006, 2007, 2008, 2009, 2010 Apple 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 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27#import "ResourceHandleInternal.h"
28
29#if !USE(CFNETWORK)
30
31#import "AuthenticationChallenge.h"
32#import "AuthenticationMac.h"
33#import "Base64.h"
34#import "BlobRegistry.h"
35#import "BlockExceptions.h"
36#import "CookieStorage.h"
37#import "CredentialStorage.h"
38#import "CachedResourceLoader.h"
39#import "EmptyProtocolDefinitions.h"
40#import "FormDataStreamMac.h"
41#import "Frame.h"
42#import "FrameLoader.h"
43#import "Logging.h"
44#import "MIMETypeRegistry.h"
45#import "Page.h"
46#import "ResourceError.h"
47#import "ResourceResponse.h"
48#import "SchedulePair.h"
49#import "Settings.h"
50#import "SharedBuffer.h"
51#import "SubresourceLoader.h"
52#import "WebCoreSystemInterface.h"
53#import "WebCoreURLResponse.h"
54#import <wtf/text/CString.h>
55#import <wtf/UnusedParam.h>
56
57#ifdef BUILDING_ON_TIGER
58typedef int NSInteger;
59#endif
60
61using namespace WebCore;
62
63@interface WebCoreResourceHandleAsDelegate : NSObject <NSURLConnectionDelegate> {
64    ResourceHandle* m_handle;
65}
66- (id)initWithHandle:(ResourceHandle*)handle;
67- (void)detachHandle;
68@end
69
70// WebCoreNSURLConnectionDelegateProxy exists so that we can cast m_proxy to it in order
71// to disambiguate the argument type in the -setDelegate: call.  This avoids a spurious
72// warning that the compiler would otherwise emit.
73@interface WebCoreNSURLConnectionDelegateProxy : NSObject <NSURLConnectionDelegate>
74- (void)setDelegate:(id<NSURLConnectionDelegate>)delegate;
75@end
76
77@interface NSURLConnection (NSURLConnectionTigerPrivate)
78- (NSData *)_bufferedData;
79@end
80
81@interface NSURLConnection (Details)
82-(id)_initWithRequest:(NSURLRequest *)request delegate:(id)delegate usesCache:(BOOL)usesCacheFlag maxContentLength:(long long)maxContentLength startImmediately:(BOOL)startImmediately connectionProperties:(NSDictionary *)connectionProperties;
83@end
84
85@interface NSURLRequest (Details)
86- (id)_propertyForKey:(NSString *)key;
87@end
88
89#ifndef BUILDING_ON_TIGER
90
91class WebCoreSynchronousLoaderClient : public ResourceHandleClient {
92public:
93    static PassOwnPtr<WebCoreSynchronousLoaderClient> create()
94    {
95        return adoptPtr(new WebCoreSynchronousLoaderClient);
96    }
97
98    virtual ~WebCoreSynchronousLoaderClient();
99
100    void setAllowStoredCredentials(bool allow) { m_allowStoredCredentials = allow; }
101    NSURLResponse *response() { return m_response; }
102    NSMutableData *data() { return m_data; }
103    NSError *error() { return m_error; }
104    bool isDone() { return m_isDone; }
105
106private:
107    WebCoreSynchronousLoaderClient()
108        : m_allowStoredCredentials(false)
109        , m_response(0)
110        , m_data(0)
111        , m_error(0)
112        , m_isDone(false)
113    {
114    }
115
116    virtual void willSendRequest(ResourceHandle*, ResourceRequest&, const ResourceResponse& /*redirectResponse*/);
117    virtual bool shouldUseCredentialStorage(ResourceHandle*);
118    virtual void didReceiveAuthenticationChallenge(ResourceHandle*, const AuthenticationChallenge&);
119    virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&);
120    virtual void didReceiveData(ResourceHandle*, const char*, int, int /*encodedDataLength*/);
121    virtual void didFinishLoading(ResourceHandle*, double /*finishTime*/);
122    virtual void didFail(ResourceHandle*, const ResourceError&);
123#if USE(PROTECTION_SPACE_AUTH_CALLBACK)
124    virtual bool canAuthenticateAgainstProtectionSpace(ResourceHandle*, const ProtectionSpace&);
125#endif
126
127    bool m_allowStoredCredentials;
128    NSURLResponse *m_response;
129    NSMutableData *m_data;
130    NSError *m_error;
131    bool m_isDone;
132};
133
134static NSString *WebCoreSynchronousLoaderRunLoopMode = @"WebCoreSynchronousLoaderRunLoopMode";
135
136#endif
137
138namespace WebCore {
139
140#ifdef BUILDING_ON_TIGER
141static unsigned inNSURLConnectionCallback;
142#endif
143
144#ifndef NDEBUG
145static bool isInitializingConnection;
146#endif
147
148class CallbackGuard {
149public:
150    CallbackGuard()
151    {
152#ifdef BUILDING_ON_TIGER
153        ++inNSURLConnectionCallback;
154#endif
155    }
156    ~CallbackGuard()
157    {
158#ifdef BUILDING_ON_TIGER
159        ASSERT(inNSURLConnectionCallback > 0);
160        --inNSURLConnectionCallback;
161#endif
162    }
163};
164
165#ifndef BUILDING_ON_TIGER
166static String encodeBasicAuthorization(const String& user, const String& password)
167{
168    return base64Encode((user + ":" + password).utf8());
169}
170#endif
171
172ResourceHandleInternal::~ResourceHandleInternal()
173{
174}
175
176ResourceHandle::~ResourceHandle()
177{
178    releaseDelegate();
179    d->m_currentWebChallenge.setAuthenticationClient(0);
180
181    LOG(Network, "Handle %p destroyed", this);
182}
183
184static const double MaxFoundationVersionWithoutdidSendBodyDataDelegate = 677.21;
185bool ResourceHandle::didSendBodyDataDelegateExists()
186{
187    return NSFoundationVersionNumber > MaxFoundationVersionWithoutdidSendBodyDataDelegate;
188}
189
190static bool shouldRelaxThirdPartyCookiePolicy(const KURL& url)
191{
192    // If a URL already has cookies, then we'll relax the 3rd party cookie policy and accept new cookies.
193
194    NSHTTPCookieStorage *sharedStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
195
196    NSHTTPCookieAcceptPolicy cookieAcceptPolicy;
197#if USE(CFURLSTORAGESESSIONS)
198    CFHTTPCookieStorageRef cfPrivateBrowsingStorage = privateBrowsingCookieStorage().get();
199    if (cfPrivateBrowsingStorage)
200        cookieAcceptPolicy =  wkGetHTTPCookieAcceptPolicy(cfPrivateBrowsingStorage);
201    else
202#endif
203        cookieAcceptPolicy = [sharedStorage cookieAcceptPolicy];
204
205    if (cookieAcceptPolicy != NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain)
206        return false;
207
208    NSArray *cookies;
209#if USE(CFURLSTORAGESESSIONS)
210    if (cfPrivateBrowsingStorage)
211        cookies = wkHTTPCookiesForURL(cfPrivateBrowsingStorage, url);
212    else
213#endif
214        cookies = [sharedStorage cookiesForURL:url];
215
216    return [cookies count];
217}
218
219void ResourceHandle::createNSURLConnection(id delegate, bool shouldUseCredentialStorage, bool shouldContentSniff)
220{
221    // Credentials for ftp can only be passed in URL, the connection:didReceiveAuthenticationChallenge: delegate call won't be made.
222    if ((!d->m_user.isEmpty() || !d->m_pass.isEmpty())
223#ifndef BUILDING_ON_TIGER
224     && !firstRequest().url().protocolInHTTPFamily() // On Tiger, always pass credentials in URL, so that they get stored even if the request gets cancelled right away.
225#endif
226    ) {
227        KURL urlWithCredentials(firstRequest().url());
228        urlWithCredentials.setUser(d->m_user);
229        urlWithCredentials.setPass(d->m_pass);
230        firstRequest().setURL(urlWithCredentials);
231    }
232
233    if (shouldRelaxThirdPartyCookiePolicy(firstRequest().url()))
234        firstRequest().setFirstPartyForCookies(firstRequest().url());
235
236#if !defined(BUILDING_ON_TIGER)
237    if (shouldUseCredentialStorage && firstRequest().url().protocolInHTTPFamily()) {
238        if (d->m_user.isEmpty() && d->m_pass.isEmpty()) {
239            // <rdar://problem/7174050> - For URLs that match the paths of those previously challenged for HTTP Basic authentication,
240            // try and reuse the credential preemptively, as allowed by RFC 2617.
241            d->m_initialCredential = CredentialStorage::get(firstRequest().url());
242        } else {
243            // If there is already a protection space known for the URL, update stored credentials before sending a request.
244            // This makes it possible to implement logout by sending an XMLHttpRequest with known incorrect credentials, and aborting it immediately
245            // (so that an authentication dialog doesn't pop up).
246            CredentialStorage::set(Credential(d->m_user, d->m_pass, CredentialPersistenceNone), firstRequest().url());
247        }
248    }
249
250    if (!d->m_initialCredential.isEmpty()) {
251        // FIXME: Support Digest authentication, and Proxy-Authorization.
252        String authHeader = "Basic " + encodeBasicAuthorization(d->m_initialCredential.user(), d->m_initialCredential.password());
253        firstRequest().addHTTPHeaderField("Authorization", authHeader);
254    }
255
256    NSURLRequest *nsRequest = firstRequest().nsURLRequest();
257    if (!shouldContentSniff) {
258        NSMutableURLRequest *mutableRequest = [[nsRequest copy] autorelease];
259        wkSetNSURLRequestShouldContentSniff(mutableRequest, NO);
260        nsRequest = mutableRequest;
261    }
262
263#if !defined(BUILDING_ON_LEOPARD)
264    ASSERT([NSURLConnection instancesRespondToSelector:@selector(_initWithRequest:delegate:usesCache:maxContentLength:startImmediately:connectionProperties:)]);
265    static bool supportsSettingConnectionProperties = true;
266#else
267    static bool supportsSettingConnectionProperties = [NSURLConnection instancesRespondToSelector:@selector(_initWithRequest:delegate:usesCache:maxContentLength:startImmediately:connectionProperties:)];
268#endif
269
270#if USE(CFURLSTORAGESESSIONS)
271    if (CFURLStorageSessionRef storageSession = privateBrowsingStorageSession())
272        nsRequest = [wkCopyRequestWithStorageSession(storageSession, nsRequest) autorelease];
273#endif
274
275    if (supportsSettingConnectionProperties) {
276        NSDictionary *sessionID = shouldUseCredentialStorage ? [NSDictionary dictionary] : [NSDictionary dictionaryWithObject:@"WebKitPrivateSession" forKey:@"_kCFURLConnectionSessionID"];
277        NSDictionary *propertyDictionary = [NSDictionary dictionaryWithObject:sessionID forKey:@"kCFURLConnectionSocketStreamProperties"];
278        d->m_connection.adoptNS([[NSURLConnection alloc] _initWithRequest:nsRequest delegate:delegate usesCache:YES maxContentLength:0 startImmediately:NO connectionProperties:propertyDictionary]);
279        return;
280    }
281
282    d->m_connection.adoptNS([[NSURLConnection alloc] initWithRequest:nsRequest delegate:delegate startImmediately:NO]);
283    return;
284
285#else
286    // Building on Tiger. Don't use WebCore credential storage, don't try to disable content sniffing.
287    UNUSED_PARAM(shouldUseCredentialStorage);
288    UNUSED_PARAM(shouldContentSniff);
289    d->m_connection.adoptNS([[NSURLConnection alloc] initWithRequest:firstRequest().nsURLRequest() delegate:delegate]);
290#endif
291}
292
293bool ResourceHandle::start(NetworkingContext* context)
294{
295    if (!context)
296        return false;
297
298    BEGIN_BLOCK_OBJC_EXCEPTIONS;
299
300    // If NetworkingContext is invalid then we are no longer attached to a Page,
301    // this must be an attempted load from an unload event handler, so let's just block it.
302    if (!context->isValid())
303        return false;
304
305#ifndef NDEBUG
306    isInitializingConnection = YES;
307#endif
308
309    ASSERT(!d->m_proxy);
310    d->m_proxy.adoptNS(wkCreateNSURLConnectionDelegateProxy());
311    [static_cast<WebCoreNSURLConnectionDelegateProxy*>(d->m_proxy.get()) setDelegate:ResourceHandle::delegate()];
312
313    bool shouldUseCredentialStorage = !client() || client()->shouldUseCredentialStorage(this);
314
315    if (!ResourceHandle::didSendBodyDataDelegateExists())
316        associateStreamWithResourceHandle([firstRequest().nsURLRequest() HTTPBodyStream], this);
317
318#ifdef BUILDING_ON_TIGER
319    // A conditional request sent by WebCore (e.g. to update appcache) can be for a resource that is not cacheable by NSURLConnection,
320    // which can get confused and fail to load it in this case.
321    if (firstRequest().isConditional())
322        firstRequest().setCachePolicy(ReloadIgnoringCacheData);
323#endif
324
325    d->m_needsSiteSpecificQuirks = context->needsSiteSpecificQuirks();
326
327    createNSURLConnection(
328        d->m_proxy.get(),
329        shouldUseCredentialStorage,
330        d->m_shouldContentSniff || context->localFileContentSniffingEnabled());
331
332#ifndef BUILDING_ON_TIGER
333    bool scheduled = false;
334    if (SchedulePairHashSet* scheduledPairs = context->scheduledRunLoopPairs()) {
335        SchedulePairHashSet::iterator end = scheduledPairs->end();
336        for (SchedulePairHashSet::iterator it = scheduledPairs->begin(); it != end; ++it) {
337            if (NSRunLoop *runLoop = (*it)->nsRunLoop()) {
338                [connection() scheduleInRunLoop:runLoop forMode:(NSString *)(*it)->mode()];
339                scheduled = true;
340            }
341        }
342    }
343
344    // Start the connection if we did schedule with at least one runloop.
345    // We can't start the connection until we have one runloop scheduled.
346    if (scheduled)
347        [connection() start];
348    else
349        d->m_startWhenScheduled = true;
350#endif
351
352#ifndef NDEBUG
353    isInitializingConnection = NO;
354#endif
355
356    LOG(Network, "Handle %p starting connection %p for %@", this, connection(), firstRequest().nsURLRequest());
357
358    if (d->m_connection) {
359        if (d->m_defersLoading)
360            wkSetNSURLConnectionDefersCallbacks(connection(), YES);
361
362        return true;
363    }
364
365    END_BLOCK_OBJC_EXCEPTIONS;
366
367    return false;
368}
369
370void ResourceHandle::cancel()
371{
372    LOG(Network, "Handle %p cancel connection %p", this, d->m_connection.get());
373
374    // Leaks were seen on HTTP tests without this; can be removed once <rdar://problem/6886937> is fixed.
375    if (d->m_currentMacChallenge)
376        [[d->m_currentMacChallenge sender] cancelAuthenticationChallenge:d->m_currentMacChallenge];
377
378    if (!ResourceHandle::didSendBodyDataDelegateExists())
379        disassociateStreamWithResourceHandle([firstRequest().nsURLRequest() HTTPBodyStream]);
380    [d->m_connection.get() cancel];
381}
382
383void ResourceHandle::platformSetDefersLoading(bool defers)
384{
385    if (d->m_connection)
386        wkSetNSURLConnectionDefersCallbacks(d->m_connection.get(), defers);
387}
388
389void ResourceHandle::schedule(SchedulePair* pair)
390{
391#ifndef BUILDING_ON_TIGER
392    NSRunLoop *runLoop = pair->nsRunLoop();
393    if (!runLoop)
394        return;
395    [d->m_connection.get() scheduleInRunLoop:runLoop forMode:(NSString *)pair->mode()];
396    if (d->m_startWhenScheduled) {
397        [d->m_connection.get() start];
398        d->m_startWhenScheduled = false;
399    }
400#else
401    UNUSED_PARAM(pair);
402#endif
403}
404
405void ResourceHandle::unschedule(SchedulePair* pair)
406{
407#ifndef BUILDING_ON_TIGER
408    if (NSRunLoop *runLoop = pair->nsRunLoop())
409        [d->m_connection.get() unscheduleFromRunLoop:runLoop forMode:(NSString *)pair->mode()];
410#else
411    UNUSED_PARAM(pair);
412#endif
413}
414
415WebCoreResourceHandleAsDelegate *ResourceHandle::delegate()
416{
417    if (!d->m_delegate) {
418        WebCoreResourceHandleAsDelegate *delegate = [[WebCoreResourceHandleAsDelegate alloc] initWithHandle:this];
419        d->m_delegate = delegate;
420        [delegate release];
421    }
422    return d->m_delegate.get();
423}
424
425void ResourceHandle::releaseDelegate()
426{
427    if (!d->m_delegate)
428        return;
429    if (d->m_proxy)
430        [d->m_proxy.get() setDelegate:nil];
431    [d->m_delegate.get() detachHandle];
432    d->m_delegate = nil;
433}
434
435bool ResourceHandle::supportsBufferedData()
436{
437    static bool supportsBufferedData = [NSURLConnection instancesRespondToSelector:@selector(_bufferedData)];
438    return supportsBufferedData;
439}
440
441PassRefPtr<SharedBuffer> ResourceHandle::bufferedData()
442{
443    if (ResourceHandle::supportsBufferedData())
444        return SharedBuffer::wrapNSData([d->m_connection.get() _bufferedData]);
445
446    return 0;
447}
448
449id ResourceHandle::releaseProxy()
450{
451    id proxy = [[d->m_proxy.get() retain] autorelease];
452    d->m_proxy = nil;
453    [proxy setDelegate:nil];
454    return proxy;
455}
456
457NSURLConnection *ResourceHandle::connection() const
458{
459    return d->m_connection.get();
460}
461
462bool ResourceHandle::loadsBlocked()
463{
464#ifndef BUILDING_ON_TIGER
465    return false;
466#else
467    // On Tiger, if we're in an NSURLConnection callback, that blocks all other NSURLConnection callbacks.
468    // On Leopard and newer, it blocks only callbacks on that same NSURLConnection object, which is not
469    // a problem in practice.
470    return inNSURLConnectionCallback != 0;
471#endif
472}
473
474bool ResourceHandle::willLoadFromCache(ResourceRequest& request, Frame*)
475{
476#ifndef BUILDING_ON_TIGER
477    request.setCachePolicy(ReturnCacheDataDontLoad);
478    NSURLResponse *nsURLResponse = nil;
479    BEGIN_BLOCK_OBJC_EXCEPTIONS;
480
481   [NSURLConnection sendSynchronousRequest:request.nsURLRequest() returningResponse:&nsURLResponse error:nil];
482
483    END_BLOCK_OBJC_EXCEPTIONS;
484
485    return nsURLResponse;
486#else
487    // <rdar://problem/6803217> - Re-enable after <rdar://problem/6786454> is resolved.
488    UNUSED_PARAM(request);
489    return false;
490#endif
491}
492
493void ResourceHandle::loadResourceSynchronously(NetworkingContext* context, const ResourceRequest& request, StoredCredentials storedCredentials, ResourceError& error, ResourceResponse& response, Vector<char>& data)
494{
495    LOG(Network, "ResourceHandle::loadResourceSynchronously:%@ allowStoredCredentials:%u", request.nsURLRequest(), storedCredentials);
496
497#if ENABLE(BLOB)
498    if (request.url().protocolIs("blob"))
499        if (blobRegistry().loadResourceSynchronously(request, error, response, data))
500            return;
501#endif
502
503    NSError *nsError = nil;
504    NSURLResponse *nsURLResponse = nil;
505    NSData *result = nil;
506
507    ASSERT(!request.isEmpty());
508
509#ifndef BUILDING_ON_TIGER
510    OwnPtr<WebCoreSynchronousLoaderClient> client = WebCoreSynchronousLoaderClient::create();
511    client->setAllowStoredCredentials(storedCredentials == AllowStoredCredentials);
512
513    RefPtr<ResourceHandle> handle = adoptRef(new ResourceHandle(request, client.get(), false /*defersLoading*/, true /*shouldContentSniff*/));
514
515    if (context && handle->d->m_scheduledFailureType != NoFailure) {
516        error = context->blockedError(request);
517        return;
518    }
519
520    handle->createNSURLConnection(
521        handle->delegate(), // A synchronous request cannot turn into a download, so there is no need to proxy the delegate.
522        storedCredentials == AllowStoredCredentials,
523        handle->shouldContentSniff() || (context && context->localFileContentSniffingEnabled()));
524
525    [handle->connection() scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:WebCoreSynchronousLoaderRunLoopMode];
526    [handle->connection() start];
527
528    while (!client->isDone())
529        [[NSRunLoop currentRunLoop] runMode:WebCoreSynchronousLoaderRunLoopMode beforeDate:[NSDate distantFuture]];
530
531    result = client->data();
532    nsURLResponse = client->response();
533    nsError = client->error();
534
535    [handle->connection() cancel];
536
537#else
538    UNUSED_PARAM(storedCredentials);
539    UNUSED_PARAM(context);
540    NSURLRequest *firstRequest = request.nsURLRequest();
541
542    if (shouldRelaxThirdPartyCookiePolicy([firstRequest URL])) {
543        NSMutableURLRequest *mutableRequest = [[firstRequest mutableCopy] autorelease];
544        [mutableRequest setMainDocumentURL:[mutableRequest URL]];
545        firstRequest = mutableRequest;
546    }
547
548    BEGIN_BLOCK_OBJC_EXCEPTIONS;
549    result = [NSURLConnection sendSynchronousRequest:firstRequest returningResponse:&nsURLResponse error:&nsError];
550    END_BLOCK_OBJC_EXCEPTIONS;
551#endif
552
553    if (!nsError)
554        response = nsURLResponse;
555    else {
556        response = ResourceResponse(request.url(), String(), 0, String(), String());
557        if ([nsError domain] == NSURLErrorDomain)
558            switch ([nsError code]) {
559                case NSURLErrorUserCancelledAuthentication:
560                    // FIXME: we should really return the actual HTTP response, but sendSynchronousRequest doesn't provide us with one.
561                    response.setHTTPStatusCode(401);
562                    break;
563                default:
564                    response.setHTTPStatusCode([nsError code]);
565            }
566        else
567            response.setHTTPStatusCode(404);
568    }
569
570    data.resize([result length]);
571    memcpy(data.data(), [result bytes], [result length]);
572
573    error = nsError;
574}
575
576void ResourceHandle::willSendRequest(ResourceRequest& request, const ResourceResponse& redirectResponse)
577{
578    const KURL& url = request.url();
579    d->m_user = url.user();
580    d->m_pass = url.pass();
581    d->m_lastHTTPMethod = request.httpMethod();
582    request.removeCredentials();
583    if (!protocolHostAndPortAreEqual(request.url(), redirectResponse.url()))
584        request.clearHTTPAuthorization();
585
586#if USE(CFURLSTORAGESESSIONS)
587    if (CFURLStorageSessionRef storageSession = privateBrowsingStorageSession())
588        request.setStorageSession(storageSession);
589#endif
590
591    client()->willSendRequest(this, request, redirectResponse);
592}
593
594bool ResourceHandle::shouldUseCredentialStorage()
595{
596    if (client())
597        return client()->shouldUseCredentialStorage(this);
598
599    return false;
600}
601
602void ResourceHandle::didReceiveAuthenticationChallenge(const AuthenticationChallenge& challenge)
603{
604    ASSERT(!d->m_currentMacChallenge);
605    ASSERT(d->m_currentWebChallenge.isNull());
606    // Since NSURLConnection networking relies on keeping a reference to the original NSURLAuthenticationChallenge,
607    // we make sure that is actually present
608    ASSERT(challenge.nsURLAuthenticationChallenge());
609
610    if (!d->m_user.isNull() && !d->m_pass.isNull()) {
611        NSURLCredential *credential = [[NSURLCredential alloc] initWithUser:d->m_user
612                                                                   password:d->m_pass
613                                                                persistence:NSURLCredentialPersistenceForSession];
614        d->m_currentMacChallenge = challenge.nsURLAuthenticationChallenge();
615        d->m_currentWebChallenge = challenge;
616        receivedCredential(challenge, core(credential));
617        [credential release];
618        // FIXME: Per the specification, the user shouldn't be asked for credentials if there were incorrect ones provided explicitly.
619        d->m_user = String();
620        d->m_pass = String();
621        return;
622    }
623
624#ifndef BUILDING_ON_TIGER
625    if (!client() || client()->shouldUseCredentialStorage(this)) {
626        if (!d->m_initialCredential.isEmpty() || challenge.previousFailureCount()) {
627            // The stored credential wasn't accepted, stop using it.
628            // There is a race condition here, since a different credential might have already been stored by another ResourceHandle,
629            // but the observable effect should be very minor, if any.
630            CredentialStorage::remove(challenge.protectionSpace());
631        }
632
633        if (!challenge.previousFailureCount()) {
634            Credential credential = CredentialStorage::get(challenge.protectionSpace());
635            if (!credential.isEmpty() && credential != d->m_initialCredential) {
636                ASSERT(credential.persistence() == CredentialPersistenceNone);
637                if (challenge.failureResponse().httpStatusCode() == 401) {
638                    // Store the credential back, possibly adding it as a default for this directory.
639                    CredentialStorage::set(credential, challenge.protectionSpace(), firstRequest().url());
640                }
641                [challenge.sender() useCredential:mac(credential) forAuthenticationChallenge:mac(challenge)];
642                return;
643            }
644        }
645    }
646#endif
647
648    d->m_currentMacChallenge = challenge.nsURLAuthenticationChallenge();
649    d->m_currentWebChallenge = core(d->m_currentMacChallenge);
650    d->m_currentWebChallenge.setAuthenticationClient(this);
651
652    if (client())
653        client()->didReceiveAuthenticationChallenge(this, d->m_currentWebChallenge);
654}
655
656void ResourceHandle::didCancelAuthenticationChallenge(const AuthenticationChallenge& challenge)
657{
658    ASSERT(d->m_currentMacChallenge);
659    ASSERT(d->m_currentMacChallenge == challenge.nsURLAuthenticationChallenge());
660    ASSERT(!d->m_currentWebChallenge.isNull());
661
662    if (client())
663        client()->didCancelAuthenticationChallenge(this, challenge);
664}
665
666#if USE(PROTECTION_SPACE_AUTH_CALLBACK)
667bool ResourceHandle::canAuthenticateAgainstProtectionSpace(const ProtectionSpace& protectionSpace)
668{
669    if (client())
670        return client()->canAuthenticateAgainstProtectionSpace(this, protectionSpace);
671
672    return false;
673}
674#endif
675
676void ResourceHandle::receivedCredential(const AuthenticationChallenge& challenge, const Credential& credential)
677{
678    ASSERT(!challenge.isNull());
679    if (challenge != d->m_currentWebChallenge)
680        return;
681
682    // FIXME: Support empty credentials. Currently, an empty credential cannot be stored in WebCore credential storage, as that's empty value for its map.
683    if (credential.isEmpty()) {
684        receivedRequestToContinueWithoutCredential(challenge);
685        return;
686    }
687
688#ifdef BUILDING_ON_TIGER
689    if (credential.persistence() == CredentialPersistenceNone) {
690        // NSURLCredentialPersistenceNone doesn't work on Tiger, so we have to use session persistence.
691        Credential webCredential(credential.user(), credential.password(), CredentialPersistenceForSession);
692        [[d->m_currentMacChallenge sender] useCredential:mac(webCredential) forAuthenticationChallenge:d->m_currentMacChallenge];
693    } else
694#else
695    if (credential.persistence() == CredentialPersistenceForSession && (!d->m_needsSiteSpecificQuirks || ![[[mac(challenge) protectionSpace] host] isEqualToString:@"gallery.me.com"])) {
696        // Manage per-session credentials internally, because once NSURLCredentialPersistenceForSession is used, there is no way
697        // to ignore it for a particular request (short of removing it altogether).
698        // <rdar://problem/6867598> gallery.me.com is temporarily whitelisted, so that QuickTime plug-in could see the credentials.
699        Credential webCredential(credential, CredentialPersistenceNone);
700        KURL urlToStore;
701        if (challenge.failureResponse().httpStatusCode() == 401)
702            urlToStore = firstRequest().url();
703        CredentialStorage::set(webCredential, core([d->m_currentMacChallenge protectionSpace]), urlToStore);
704        [[d->m_currentMacChallenge sender] useCredential:mac(webCredential) forAuthenticationChallenge:d->m_currentMacChallenge];
705    } else
706#endif
707        [[d->m_currentMacChallenge sender] useCredential:mac(credential) forAuthenticationChallenge:d->m_currentMacChallenge];
708
709    clearAuthentication();
710}
711
712void ResourceHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge& challenge)
713{
714    ASSERT(!challenge.isNull());
715    if (challenge != d->m_currentWebChallenge)
716        return;
717
718    [[d->m_currentMacChallenge sender] continueWithoutCredentialForAuthenticationChallenge:d->m_currentMacChallenge];
719
720    clearAuthentication();
721}
722
723void ResourceHandle::receivedCancellation(const AuthenticationChallenge& challenge)
724{
725    if (challenge != d->m_currentWebChallenge)
726        return;
727
728    if (client())
729        client()->receivedCancellation(this, challenge);
730}
731
732#if USE(CFURLSTORAGESESSIONS)
733
734RetainPtr<CFURLStorageSessionRef> ResourceHandle::createPrivateBrowsingStorageSession(CFStringRef identifier)
735{
736    return RetainPtr<CFURLStorageSessionRef>(AdoptCF, wkCreatePrivateStorageSession(identifier));
737}
738
739String ResourceHandle::privateBrowsingStorageSessionIdentifierDefaultBase()
740{
741    return String([[NSBundle mainBundle] bundleIdentifier]);
742}
743
744#endif
745
746} // namespace WebCore
747
748@implementation WebCoreResourceHandleAsDelegate
749
750- (id)initWithHandle:(ResourceHandle*)handle
751{
752    self = [self init];
753    if (!self)
754        return nil;
755    m_handle = handle;
756    return self;
757}
758
759- (void)detachHandle
760{
761    m_handle = 0;
762}
763
764- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse
765{
766    UNUSED_PARAM(connection);
767
768    // the willSendRequest call may cancel this load, in which case self could be deallocated
769    RetainPtr<WebCoreResourceHandleAsDelegate> protect(self);
770
771    if (!m_handle || !m_handle->client())
772        return nil;
773
774    // See <rdar://problem/5380697> .  This is a workaround for a behavior change in CFNetwork where willSendRequest gets called more often.
775    if (!redirectResponse)
776        return newRequest;
777
778#if !LOG_DISABLED
779    if ([redirectResponse isKindOfClass:[NSHTTPURLResponse class]])
780        LOG(Network, "Handle %p delegate connection:%p willSendRequest:%@ redirectResponse:%d, Location:<%@>", m_handle, connection, [newRequest description], static_cast<int>([(id)redirectResponse statusCode]), [[(id)redirectResponse allHeaderFields] objectForKey:@"Location"]);
781    else
782        LOG(Network, "Handle %p delegate connection:%p willSendRequest:%@ redirectResponse:non-HTTP", m_handle, connection, [newRequest description]);
783#endif
784
785    if ([redirectResponse isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)redirectResponse statusCode] == 307) {
786        String lastHTTPMethod = m_handle->lastHTTPMethod();
787        if (!equalIgnoringCase(lastHTTPMethod, String([newRequest HTTPMethod]))) {
788            NSMutableURLRequest *mutableRequest = [newRequest mutableCopy];
789            [mutableRequest setHTTPMethod:lastHTTPMethod];
790
791            FormData* body = m_handle->firstRequest().httpBody();
792            if (!equalIgnoringCase(lastHTTPMethod, "GET") && body && !body->isEmpty())
793                WebCore::setHTTPBody(mutableRequest, body);
794
795            String originalContentType = m_handle->firstRequest().httpContentType();
796            if (!originalContentType.isEmpty())
797                [mutableRequest setValue:originalContentType forHTTPHeaderField:@"Content-Type"];
798
799            newRequest = [mutableRequest autorelease];
800        }
801    }
802
803    CallbackGuard guard;
804    ResourceRequest request = newRequest;
805
806    // Should not set Referer after a redirect from a secure resource to non-secure one.
807    if (!request.url().protocolIs("https") && protocolIs(request.httpReferrer(), "https"))
808        request.clearHTTPReferrer();
809
810    m_handle->willSendRequest(request, redirectResponse);
811
812    if (!ResourceHandle::didSendBodyDataDelegateExists()) {
813        // The client may change the request's body stream, in which case we have to re-associate
814        // the handle with the new stream so upload progress callbacks continue to work correctly.
815        NSInputStream* oldBodyStream = [newRequest HTTPBodyStream];
816        NSInputStream* newBodyStream = [request.nsURLRequest() HTTPBodyStream];
817        if (oldBodyStream != newBodyStream) {
818            disassociateStreamWithResourceHandle(oldBodyStream);
819            associateStreamWithResourceHandle(newBodyStream, m_handle);
820        }
821    }
822
823    return request.nsURLRequest();
824}
825
826- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
827{
828    UNUSED_PARAM(connection);
829
830    LOG(Network, "Handle %p delegate connectionShouldUseCredentialStorage:%p", m_handle, connection);
831
832    if (!m_handle)
833        return NO;
834
835    CallbackGuard guard;
836    return m_handle->shouldUseCredentialStorage();
837}
838
839- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
840{
841    UNUSED_PARAM(connection);
842
843    LOG(Network, "Handle %p delegate connection:%p didReceiveAuthenticationChallenge:%p", m_handle, connection, challenge);
844
845    if (!m_handle)
846        return;
847    CallbackGuard guard;
848    m_handle->didReceiveAuthenticationChallenge(core(challenge));
849}
850
851- (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
852{
853    UNUSED_PARAM(connection);
854
855    LOG(Network, "Handle %p delegate connection:%p didCancelAuthenticationChallenge:%p", m_handle, connection, challenge);
856
857    if (!m_handle)
858        return;
859    CallbackGuard guard;
860    m_handle->didCancelAuthenticationChallenge(core(challenge));
861}
862
863#if USE(PROTECTION_SPACE_AUTH_CALLBACK)
864- (BOOL)connection:(NSURLConnection *)unusedConnection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
865{
866    UNUSED_PARAM(unusedConnection);
867
868    if (!m_handle)
869        return NO;
870
871    CallbackGuard guard;
872    return m_handle->canAuthenticateAgainstProtectionSpace(core(protectionSpace));
873}
874#endif
875
876- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)r
877{
878    UNUSED_PARAM(connection);
879
880    LOG(Network, "Handle %p delegate connection:%p didReceiveResponse:%p (HTTP status %d, reported MIMEType '%s')", m_handle, connection, r, [r respondsToSelector:@selector(statusCode)] ? [(id)r statusCode] : 0, [[r MIMEType] UTF8String]);
881
882    if (!m_handle || !m_handle->client())
883        return;
884    CallbackGuard guard;
885
886    // Avoid MIME type sniffing if the response comes back as 304 Not Modified.
887    int statusCode = [r respondsToSelector:@selector(statusCode)] ? [(id)r statusCode] : 0;
888    if (statusCode != 304)
889        adjustMIMETypeIfNecessary([r _CFURLResponse]);
890
891    if ([m_handle->firstRequest().nsURLRequest() _propertyForKey:@"ForceHTMLMIMEType"])
892        [r _setMIMEType:@"text/html"];
893
894#if ENABLE(WML)
895    const KURL& url = [r URL];
896    if (url.isLocalFile()) {
897        // FIXME: Workaround for <rdar://problem/6917571>: The WML file extension ".wml" is not mapped to
898        // the right MIME type, work around that CFNetwork problem, to unbreak WML support for local files.
899        const String& path = url.path();
900
901        DEFINE_STATIC_LOCAL(const String, wmlExt, (".wml"));
902        if (path.endsWith(wmlExt, false)) {
903            static NSString* defaultMIMETypeString = [(NSString*) defaultMIMEType() retain];
904            if ([[r MIMEType] isEqualToString:defaultMIMETypeString])
905                [r _setMIMEType:@"text/vnd.wap.wml"];
906        }
907    }
908#endif
909
910    m_handle->client()->didReceiveResponse(m_handle, r);
911}
912
913#if HAVE(CFNETWORK_DATA_ARRAY_CALLBACK)
914- (void)connection:(NSURLConnection *)connection didReceiveDataArray:(NSArray *)dataArray
915{
916    UNUSED_PARAM(connection);
917    LOG(Network, "Handle %p delegate connection:%p didReceiveDataArray:%p arraySize:%d", m_handle, connection, dataArray, [dataArray count]);
918
919    if (!dataArray)
920        return;
921
922    if (!m_handle || !m_handle->client())
923        return;
924
925    if (m_handle->client()->supportsDataArray())
926        m_handle->client()->didReceiveDataArray(m_handle, reinterpret_cast<CFArrayRef>(dataArray));
927    else {
928        for (NSData *data in dataArray)
929            m_handle->client()->didReceiveData(m_handle, static_cast<const char*>([data bytes]), [data length], static_cast<int>([data length]));
930    }
931    return;
932}
933#endif
934
935- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived
936{
937    UNUSED_PARAM(connection);
938    UNUSED_PARAM(lengthReceived);
939
940    LOG(Network, "Handle %p delegate connection:%p didReceiveData:%p lengthReceived:%lld", m_handle, connection, data, lengthReceived);
941
942    if (!m_handle || !m_handle->client())
943        return;
944    // FIXME: If we get more than 2B bytes in a single chunk, this code won't do the right thing.
945    // However, with today's computers and networking speeds, this won't happen in practice.
946    // Could be an issue with a giant local file.
947    CallbackGuard guard;
948    // FIXME: https://bugs.webkit.org/show_bug.cgi?id=19793
949    // -1 means we do not provide any data about transfer size to inspector so it would use
950    // Content-Length headers or content size to show transfer size.
951    m_handle->client()->didReceiveData(m_handle, (const char*)[data bytes], [data length], -1);
952}
953
954- (void)connection:(NSURLConnection *)connection willStopBufferingData:(NSData *)data
955{
956    UNUSED_PARAM(connection);
957
958    LOG(Network, "Handle %p delegate connection:%p willStopBufferingData:%p", m_handle, connection, data);
959
960    if (!m_handle || !m_handle->client())
961        return;
962    // FIXME: If we get a resource with more than 2B bytes, this code won't do the right thing.
963    // However, with today's computers and networking speeds, this won't happen in practice.
964    // Could be an issue with a giant local file.
965    CallbackGuard guard;
966    m_handle->client()->willStopBufferingData(m_handle, (const char*)[data bytes], static_cast<int>([data length]));
967}
968
969- (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
970{
971    UNUSED_PARAM(connection);
972    UNUSED_PARAM(bytesWritten);
973
974    LOG(Network, "Handle %p delegate connection:%p didSendBodyData:%d totalBytesWritten:%d totalBytesExpectedToWrite:%d", m_handle, connection, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
975
976    if (!m_handle || !m_handle->client())
977        return;
978    CallbackGuard guard;
979    m_handle->client()->didSendData(m_handle, totalBytesWritten, totalBytesExpectedToWrite);
980}
981
982- (void)connectionDidFinishLoading:(NSURLConnection *)connection
983{
984    UNUSED_PARAM(connection);
985
986    LOG(Network, "Handle %p delegate connectionDidFinishLoading:%p", m_handle, connection);
987
988    if (!m_handle || !m_handle->client())
989        return;
990    CallbackGuard guard;
991
992    if (!ResourceHandle::didSendBodyDataDelegateExists())
993        disassociateStreamWithResourceHandle([m_handle->firstRequest().nsURLRequest() HTTPBodyStream]);
994
995    m_handle->client()->didFinishLoading(m_handle, 0);
996}
997
998- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
999{
1000    UNUSED_PARAM(connection);
1001
1002    LOG(Network, "Handle %p delegate connection:%p didFailWithError:%@", m_handle, connection, error);
1003
1004    if (!m_handle || !m_handle->client())
1005        return;
1006    CallbackGuard guard;
1007
1008    if (!ResourceHandle::didSendBodyDataDelegateExists())
1009        disassociateStreamWithResourceHandle([m_handle->firstRequest().nsURLRequest() HTTPBodyStream]);
1010
1011    m_handle->client()->didFail(m_handle, error);
1012}
1013
1014#ifdef BUILDING_ON_TIGER
1015- (void)_callConnectionWillCacheResponseWithInfo:(NSMutableDictionary *)info
1016{
1017    NSURLConnection *connection = [info objectForKey:@"connection"];
1018    NSCachedURLResponse *cachedResponse = [info objectForKey:@"cachedResponse"];
1019    NSCachedURLResponse *result = [self connection:connection willCacheResponse:cachedResponse];
1020    if (result)
1021        [info setObject:result forKey:@"result"];
1022}
1023#endif
1024
1025- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
1026{
1027    LOG(Network, "Handle %p delegate connection:%p willCacheResponse:%p", m_handle, connection, cachedResponse);
1028
1029#ifdef BUILDING_ON_TIGER
1030    // On Tiger CFURLConnection can sometimes call the connection:willCacheResponse: delegate method on
1031    // a secondary thread instead of the main thread. If this happens perform the work on the main thread.
1032    if (!pthread_main_np()) {
1033        NSMutableDictionary *info = [[NSMutableDictionary alloc] init];
1034        if (connection)
1035            [info setObject:connection forKey:@"connection"];
1036        if (cachedResponse)
1037            [info setObject:cachedResponse forKey:@"cachedResponse"];
1038
1039        // Include synchronous url connection's mode as an acceptable run loopmode
1040        // <rdar://problem/5511842>
1041        NSArray *modes = [[NSArray alloc] initWithObjects:(NSString *)kCFRunLoopCommonModes, @"NSSynchronousURLConnection_PrivateMode", nil];
1042        [self performSelectorOnMainThread:@selector(_callConnectionWillCacheResponseWithInfo:) withObject:info waitUntilDone:YES modes:modes];
1043        [modes release];
1044
1045        NSCachedURLResponse *result = [[info valueForKey:@"result"] retain];
1046        [info release];
1047
1048        return [result autorelease];
1049    }
1050#else
1051    UNUSED_PARAM(connection);
1052#endif
1053
1054#ifndef NDEBUG
1055    if (isInitializingConnection)
1056        LOG_ERROR("connection:willCacheResponse: was called inside of [NSURLConnection initWithRequest:delegate:] (4067625)");
1057#endif
1058
1059    if (!m_handle || !m_handle->client())
1060        return nil;
1061
1062    CallbackGuard guard;
1063
1064    NSCachedURLResponse *newResponse = m_handle->client()->willCacheResponse(m_handle, cachedResponse);
1065    if (newResponse != cachedResponse)
1066        return newResponse;
1067
1068    CacheStoragePolicy policy = static_cast<CacheStoragePolicy>([newResponse storagePolicy]);
1069
1070    m_handle->client()->willCacheResponse(m_handle, policy);
1071
1072    if (static_cast<NSURLCacheStoragePolicy>(policy) != [newResponse storagePolicy])
1073        newResponse = [[[NSCachedURLResponse alloc] initWithResponse:[newResponse response]
1074                                                                data:[newResponse data]
1075                                                            userInfo:[newResponse userInfo]
1076                                                       storagePolicy:static_cast<NSURLCacheStoragePolicy>(policy)] autorelease];
1077
1078    return newResponse;
1079}
1080
1081@end
1082
1083#ifndef BUILDING_ON_TIGER
1084
1085WebCoreSynchronousLoaderClient::~WebCoreSynchronousLoaderClient()
1086{
1087    [m_response release];
1088    [m_data release];
1089    [m_error release];
1090}
1091
1092void WebCoreSynchronousLoaderClient::willSendRequest(ResourceHandle* handle, ResourceRequest& request, const ResourceResponse& /*redirectResponse*/)
1093{
1094    // FIXME: This needs to be fixed to follow the redirect correctly even for cross-domain requests.
1095    if (!protocolHostAndPortAreEqual(handle->firstRequest().url(), request.url())) {
1096        ASSERT(!m_error);
1097        m_error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:NSURLErrorBadServerResponse userInfo:nil];
1098        m_isDone = true;
1099        request = 0;
1100        return;
1101    }
1102}
1103
1104bool WebCoreSynchronousLoaderClient::shouldUseCredentialStorage(ResourceHandle*)
1105{
1106    // FIXME: We should ask FrameLoaderClient whether using credential storage is globally forbidden.
1107    return m_allowStoredCredentials;
1108}
1109
1110#if USE(PROTECTION_SPACE_AUTH_CALLBACK)
1111bool WebCoreSynchronousLoaderClient::canAuthenticateAgainstProtectionSpace(ResourceHandle*, const ProtectionSpace&)
1112{
1113    // FIXME: We should ask FrameLoaderClient.
1114    return true;
1115}
1116#endif
1117
1118void WebCoreSynchronousLoaderClient::didReceiveAuthenticationChallenge(ResourceHandle*, const AuthenticationChallenge& challenge)
1119{
1120    // FIXME: The user should be asked for credentials, as in async case.
1121    [challenge.sender() continueWithoutCredentialForAuthenticationChallenge:challenge.nsURLAuthenticationChallenge()];
1122}
1123
1124void WebCoreSynchronousLoaderClient::didReceiveResponse(ResourceHandle*, const ResourceResponse& response)
1125{
1126    [m_response release];
1127    m_response = [response.nsURLResponse() copy];
1128}
1129
1130void WebCoreSynchronousLoaderClient::didReceiveData(ResourceHandle*, const char* data, int length, int /*encodedDataLength*/)
1131{
1132    if (!m_data)
1133        m_data = [[NSMutableData alloc] init];
1134    [m_data appendBytes:data length:length];
1135}
1136
1137void WebCoreSynchronousLoaderClient::didFinishLoading(ResourceHandle*, double)
1138{
1139    m_isDone = true;
1140}
1141
1142void WebCoreSynchronousLoaderClient::didFail(ResourceHandle*, const ResourceError& error)
1143{
1144    ASSERT(!m_error);
1145
1146    m_error = [error copy];
1147    m_isDone = true;
1148}
1149
1150#endif // BUILDING_ON_TIGER
1151
1152#endif // !USE(CFNETWORK)
1153