2009-03-11 Darin Adler <darin@apple.com>
[WebKit-https.git] / WebCore / platform / network / cf / ResourceHandleCFNet.cpp
1 /*
2  * Copyright (C) 2004, 2005, 2006, 2007 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 #include "config.h"
27
28 #include "ResourceHandle.h"
29 #include "ResourceHandleClient.h"
30 #include "ResourceHandleInternal.h"
31
32 #include "AuthenticationCF.h"
33 #include "AuthenticationChallenge.h"
34 #include "CookieStorageWin.h"
35 #include "CString.h"
36 #include "DocLoader.h"
37 #include "Frame.h"
38 #include "FrameLoader.h"
39 #include "Logging.h"
40 #include "NotImplemented.h"
41 #include "ResourceError.h"
42 #include "ResourceResponse.h"
43
44 #include <wtf/HashMap.h>
45 #include <wtf/Threading.h>
46
47 #include <sys/types.h>
48 #include <sys/stat.h>
49 #include <process.h> // for _beginthread()
50
51 #include <CFNetwork/CFNetwork.h>
52 #include <WebKitSystemInterface/WebKitSystemInterface.h>
53
54 namespace WebCore {
55
56 static HashSet<String>& allowsAnyHTTPSCertificateHosts()
57 {
58     static HashSet<String> hosts;
59
60     return hosts;
61 }
62
63 static HashMap<String, RetainPtr<CFDataRef> >& clientCerts()
64 {
65     static HashMap<String, RetainPtr<CFDataRef> > certs;
66     return certs;
67 }
68
69 CFURLRequestRef willSendRequest(CFURLConnectionRef conn, CFURLRequestRef cfRequest, CFURLResponseRef cfRedirectResponse, const void* clientInfo)
70 {
71     ResourceHandle* handle = (ResourceHandle*)clientInfo;
72
73     if (!cfRedirectResponse) {
74         CFRetain(cfRequest);
75         return cfRequest;
76     }
77
78     LOG(Network, "CFNet - willSendRequest(conn=%p, handle=%p) (%s)", conn, handle, handle->request().url().string().utf8().data());
79
80     ResourceRequest request(cfRequest);
81     if (handle->client())
82         handle->client()->willSendRequest(handle, request, cfRedirectResponse);
83
84     cfRequest = request.cfURLRequest();
85
86     CFRetain(cfRequest);
87     return cfRequest;
88 }
89
90 void didReceiveResponse(CFURLConnectionRef conn, CFURLResponseRef cfResponse, const void* clientInfo) 
91 {
92     ResourceHandle* handle = (ResourceHandle*)clientInfo;
93
94     LOG(Network, "CFNet - didReceiveResponse(conn=%p, handle=%p) (%s)", conn, handle, handle->request().url().string().utf8().data());
95
96     if (handle->client())
97         handle->client()->didReceiveResponse(handle, cfResponse);
98 }
99
100 void didReceiveData(CFURLConnectionRef conn, CFDataRef data, CFIndex originalLength, const void* clientInfo) 
101 {
102     ResourceHandle* handle = (ResourceHandle*)clientInfo;
103     const UInt8* bytes = CFDataGetBytePtr(data);
104     CFIndex length = CFDataGetLength(data);
105
106     LOG(Network, "CFNet - didReceiveData(conn=%p, handle=%p, bytes=%d) (%s)", conn, handle, length, handle->request().url().string().utf8().data());
107
108     if (handle->client())
109         handle->client()->didReceiveData(handle, (const char*)bytes, length, originalLength);
110 }
111
112 static void didSendBodyData(CFURLConnectionRef conn, CFIndex bytesWritten, CFIndex totalBytesWritten, CFIndex totalBytesExpectedToWrite, const void *clientInfo)
113 {
114     ResourceHandle* handle = (ResourceHandle*)clientInfo;
115     if (!handle || !handle->client())
116         return;
117     handle->client()->didSendData(handle, totalBytesWritten, totalBytesExpectedToWrite);
118 }
119
120 static Boolean shouldUseCredentialStorageCallback(CFURLConnectionRef conn, const void* clientInfo)
121 {
122     ResourceHandle* handle = const_cast<ResourceHandle*>(static_cast<const ResourceHandle*>(clientInfo));
123
124     LOG(Network, "CFNet - shouldUseCredentialStorage(conn=%p, handle=%p) (%s)", conn, handle, handle->request().url().string().utf8().data());
125
126     if (!handle)
127         return false;
128
129     return handle->shouldUseCredentialStorage();
130 }
131
132 void didFinishLoading(CFURLConnectionRef conn, const void* clientInfo) 
133 {
134     ResourceHandle* handle = (ResourceHandle*)clientInfo;
135
136     LOG(Network, "CFNet - didFinishLoading(conn=%p, handle=%p) (%s)", conn, handle, handle->request().url().string().utf8().data());
137
138     if (handle->client())
139         handle->client()->didFinishLoading(handle);
140 }
141
142 void didFail(CFURLConnectionRef conn, CFErrorRef error, const void* clientInfo) 
143 {
144     ResourceHandle* handle = (ResourceHandle*)clientInfo;
145
146     LOG(Network, "CFNet - didFail(conn=%p, handle=%p, error = %p) (%s)", conn, handle, error, handle->request().url().string().utf8().data());
147
148     if (handle->client())
149         handle->client()->didFail(handle, ResourceError(error));
150 }
151
152 CFCachedURLResponseRef willCacheResponse(CFURLConnectionRef conn, CFCachedURLResponseRef cachedResponse, const void* clientInfo) 
153 {
154     ResourceHandle* handle = (ResourceHandle*)clientInfo;
155
156     CacheStoragePolicy policy = static_cast<CacheStoragePolicy>(CFCachedURLResponseGetStoragePolicy(cachedResponse));
157
158     if (handle->client())
159         handle->client()->willCacheResponse(handle, policy);
160
161     if (static_cast<CFURLCacheStoragePolicy>(policy) != CFCachedURLResponseGetStoragePolicy(cachedResponse))
162         cachedResponse = CFCachedURLResponseCreateWithUserInfo(kCFAllocatorDefault, 
163                                                                CFCachedURLResponseGetWrappedResponse(cachedResponse),
164                                                                CFCachedURLResponseGetReceiverData(cachedResponse),
165                                                                CFCachedURLResponseGetUserInfo(cachedResponse), 
166                                                                static_cast<CFURLCacheStoragePolicy>(policy));
167     CFRetain(cachedResponse);
168
169     return cachedResponse;
170 }
171
172 void didReceiveChallenge(CFURLConnectionRef conn, CFURLAuthChallengeRef challenge, const void* clientInfo)
173 {
174     ResourceHandle* handle = (ResourceHandle*)clientInfo;
175     ASSERT(handle);
176     LOG(Network, "CFNet - didReceiveChallenge(conn=%p, handle=%p (%s)", conn, handle, handle->request().url().string().utf8().data());
177
178     handle->didReceiveAuthenticationChallenge(AuthenticationChallenge(challenge, handle));
179 }
180
181 void addHeadersFromHashMap(CFMutableURLRequestRef request, const HTTPHeaderMap& requestHeaders) 
182 {
183     if (!requestHeaders.size())
184         return;
185
186     HTTPHeaderMap::const_iterator end = requestHeaders.end();
187     for (HTTPHeaderMap::const_iterator it = requestHeaders.begin(); it != end; ++it) {
188         CFStringRef key = it->first.createCFString();
189         CFStringRef value = it->second.createCFString();
190         CFURLRequestSetHTTPHeaderFieldValue(request, key, value);
191         CFRelease(key);
192         CFRelease(value);
193     }
194 }
195
196 ResourceHandleInternal::~ResourceHandleInternal()
197 {
198     if (m_connection) {
199         LOG(Network, "CFNet - Cancelling connection %p (%s)", m_connection, m_request.url().string().utf8().data());
200         CFURLConnectionCancel(m_connection.get());
201     }
202 }
203
204 ResourceHandle::~ResourceHandle()
205 {
206     LOG(Network, "CFNet - Destroying job %p (%s)", this, d->m_request.url().string().utf8().data());
207 }
208
209 CFArrayRef arrayFromFormData(const FormData& d)
210 {
211     size_t size = d.elements().size();
212     CFMutableArrayRef a = CFArrayCreateMutable(0, d.elements().size(), &kCFTypeArrayCallBacks);
213     for (size_t i = 0; i < size; ++i) {
214         const FormDataElement& e = d.elements()[i];
215         if (e.m_type == FormDataElement::data) {
216             CFDataRef data = CFDataCreate(0, (const UInt8*)e.m_data.data(), e.m_data.size());
217             CFArrayAppendValue(a, data);
218             CFRelease(data);
219         } else {
220             ASSERT(e.m_type == FormDataElement::encodedFile);
221             CFStringRef filename = e.m_filename.createCFString();
222             CFArrayAppendValue(a, filename);
223             CFRelease(filename);
224         }
225     }
226     return a;
227 }
228
229 void emptyPerform(void* unused) 
230 {
231 }
232
233 static CFRunLoopRef loaderRL = 0;
234 void* runLoaderThread(void *unused)
235 {
236     loaderRL = CFRunLoopGetCurrent();
237
238     // Must add a source to the run loop to prevent CFRunLoopRun() from exiting
239     CFRunLoopSourceContext ctxt = {0, (void *)1 /*must be non-NULL*/, 0, 0, 0, 0, 0, 0, 0, emptyPerform};
240     CFRunLoopSourceRef bogusSource = CFRunLoopSourceCreate(0, 0, &ctxt);
241     CFRunLoopAddSource(loaderRL, bogusSource,kCFRunLoopDefaultMode);
242
243     CFRunLoopRun();
244
245     return 0;
246 }
247
248 CFRunLoopRef ResourceHandle::loaderRunLoop()
249 {
250     if (!loaderRL) {
251         createThread(runLoaderThread, 0, "WebCore: CFNetwork Loader");
252         while (loaderRL == 0) {
253             // FIXME: sleep 10? that can't be right...
254             Sleep(10);
255         }
256     }
257     return loaderRL;
258 }
259
260 static CFURLRequestRef makeFinalRequest(const ResourceRequest& request, bool shouldContentSniff)
261 {
262     CFMutableURLRequestRef newRequest = CFURLRequestCreateMutableCopy(kCFAllocatorDefault, request.cfURLRequest());
263     
264     if (!shouldContentSniff)
265         wkSetCFURLRequestShouldContentSniff(newRequest, false);
266
267     RetainPtr<CFMutableDictionaryRef> sslProps;
268
269     if (allowsAnyHTTPSCertificateHosts().contains(request.url().host().lower())) {
270         sslProps.adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
271         CFDictionaryAddValue(sslProps.get(), kCFStreamSSLAllowsAnyRoot, kCFBooleanTrue);
272         CFDictionaryAddValue(sslProps.get(), kCFStreamSSLAllowsExpiredRoots, kCFBooleanTrue);
273     }
274
275     HashMap<String, RetainPtr<CFDataRef> >::iterator clientCert = clientCerts().find(request.url().host().lower());
276     if (clientCert != clientCerts().end()) {
277         if (!sslProps)
278             sslProps.adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
279         wkSetClientCertificateInSSLProperties(sslProps.get(), (clientCert->second).get());
280     }
281
282     if (sslProps)
283         CFURLRequestSetSSLProperties(newRequest, sslProps.get());
284
285     if (CFHTTPCookieStorageRef cookieStorage = currentCookieStorage()) {
286         CFURLRequestSetHTTPCookieStorage(newRequest, cookieStorage);
287         CFURLRequestSetHTTPCookieStorageAcceptPolicy(newRequest, CFHTTPCookieStorageGetCookieAcceptPolicy(cookieStorage));
288     }
289
290     return newRequest;
291 }
292
293 bool ResourceHandle::start(Frame* frame)
294 {
295     // If we are no longer attached to a Page, this must be an attempted load from an
296     // onUnload handler, so let's just block it.
297     if (!frame->page())
298         return false;
299
300     RetainPtr<CFURLRequestRef> request(AdoptCF, makeFinalRequest(d->m_request, d->m_shouldContentSniff));
301
302     CFURLConnectionClient_V3 client = { 3, this, 0, 0, 0, willSendRequest, didReceiveResponse, didReceiveData, NULL, didFinishLoading, didFail, willCacheResponse, didReceiveChallenge, didSendBodyData, shouldUseCredentialStorageCallback, 0};
303
304     d->m_connection.adoptCF(CFURLConnectionCreate(0, request.get(), reinterpret_cast<CFURLConnectionClient*>(&client)));
305
306     CFURLConnectionScheduleWithCurrentMessageQueue(d->m_connection.get());
307     CFURLConnectionScheduleDownloadWithRunLoop(d->m_connection.get(), loaderRunLoop(), kCFRunLoopDefaultMode);
308     CFURLConnectionStart(d->m_connection.get());
309
310     LOG(Network, "CFNet - Starting URL %s (handle=%p, conn=%p)", d->m_request.url().string().utf8().data(), this, d->m_connection);
311
312     return true;
313 }
314
315 void ResourceHandle::cancel()
316 {
317     if (d->m_connection) {
318         CFURLConnectionCancel(d->m_connection.get());
319         d->m_connection = 0;
320     }
321 }
322
323 PassRefPtr<SharedBuffer> ResourceHandle::bufferedData()
324 {
325     ASSERT_NOT_REACHED();
326     return 0;
327 }
328
329 bool ResourceHandle::supportsBufferedData()
330 {
331     return false;
332 }
333
334 bool ResourceHandle::shouldUseCredentialStorage()
335 {
336     LOG(Network, "CFNet - shouldUseCredentialStorage()");
337     if (client())
338         return client()->shouldUseCredentialStorage(this);
339
340     return false;
341 }
342
343 void ResourceHandle::didReceiveAuthenticationChallenge(const AuthenticationChallenge& challenge)
344 {
345     LOG(Network, "CFNet - didReceiveAuthenticationChallenge()");
346     ASSERT(!d->m_currentCFChallenge);
347     ASSERT(d->m_currentWebChallenge.isNull());
348     // Since CFURLConnection networking relies on keeping a reference to the original CFURLAuthChallengeRef,
349     // we make sure that is actually present
350     ASSERT(challenge.cfURLAuthChallengeRef());
351         
352     d->m_currentCFChallenge = challenge.cfURLAuthChallengeRef();
353     d->m_currentWebChallenge = AuthenticationChallenge(d->m_currentCFChallenge, this);
354     
355     if (client())
356         client()->didReceiveAuthenticationChallenge(this, d->m_currentWebChallenge);
357 }
358
359 void ResourceHandle::receivedCredential(const AuthenticationChallenge& challenge, const Credential& credential)
360 {
361     LOG(Network, "CFNet - receivedCredential()");
362     ASSERT(!challenge.isNull());
363     ASSERT(challenge.cfURLAuthChallengeRef());
364     if (challenge != d->m_currentWebChallenge)
365         return;
366
367     CFURLCredentialRef cfCredential = createCF(credential);
368     CFURLConnectionUseCredential(d->m_connection.get(), cfCredential, challenge.cfURLAuthChallengeRef());
369     CFRelease(cfCredential);
370
371     clearAuthentication();
372 }
373
374 void ResourceHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge& challenge)
375 {
376     LOG(Network, "CFNet - receivedRequestToContinueWithoutCredential()");
377     ASSERT(!challenge.isNull());
378     ASSERT(challenge.cfURLAuthChallengeRef());
379     if (challenge != d->m_currentWebChallenge)
380         return;
381
382     CFURLConnectionUseCredential(d->m_connection.get(), 0, challenge.cfURLAuthChallengeRef());
383
384     clearAuthentication();
385 }
386
387 void ResourceHandle::receivedCancellation(const AuthenticationChallenge& challenge)
388 {
389     LOG(Network, "CFNet - receivedCancellation()");
390     if (challenge != d->m_currentWebChallenge)
391         return;
392
393     if (client())
394         client()->receivedCancellation(this, challenge);
395 }
396
397 CFURLConnectionRef ResourceHandle::connection() const
398 {
399     return d->m_connection.get();
400 }
401
402 CFURLConnectionRef ResourceHandle::releaseConnectionForDownload()
403 {
404     LOG(Network, "CFNet - Job %p releasing connection %p for download", this, d->m_connection.get());
405     return d->m_connection.releaseRef();
406 }
407
408 void ResourceHandle::loadResourceSynchronously(const ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector<char>& vector, Frame*)
409 {
410     ASSERT(!request.isEmpty());
411     CFURLResponseRef cfResponse = 0;
412     CFErrorRef cfError = 0;
413     RetainPtr<CFURLRequestRef> cfRequest(AdoptCF, makeFinalRequest(request, true));
414
415     CFDataRef data = CFURLConnectionSendSynchronousRequest(cfRequest.get(), &cfResponse, &cfError, request.timeoutInterval());
416
417     if (cfError) {
418         error = cfError;
419         CFRelease(cfError);
420
421         response = ResourceResponse(request.url(), String(), 0, String(), String());
422         response.setHTTPStatusCode(404);
423     } else {
424         response = cfResponse;
425         if (cfResponse)
426             CFRelease(cfResponse);
427     }
428
429     if (data) {
430         ASSERT(vector.isEmpty());
431         vector.append(CFDataGetBytePtr(data), CFDataGetLength(data));
432         CFRelease(data);
433     }
434 }
435
436 void ResourceHandle::setHostAllowsAnyHTTPSCertificate(const String& host)
437 {
438     allowsAnyHTTPSCertificateHosts().add(host.lower());
439 }
440
441 void ResourceHandle::setClientCertificate(const String& host, CFDataRef cert)
442 {
443     clientCerts().set(host.lower(), cert);
444 }
445
446 void ResourceHandle::setDefersLoading(bool defers)
447 {
448     if (!d->m_connection)
449         return;
450
451     if (defers)
452         CFURLConnectionHalt(d->m_connection.get());
453     else
454         CFURLConnectionResume(d->m_connection.get());
455 }
456
457 bool ResourceHandle::loadsBlocked()
458 {
459     return false;
460 }
461
462 bool ResourceHandle::willLoadFromCache(ResourceRequest& request)
463 {
464     request.setCachePolicy(ReturnCacheDataDontLoad);
465     
466     CFURLResponseRef cfResponse = 0;
467     CFErrorRef cfError = 0;
468     RetainPtr<CFURLRequestRef> cfRequest(AdoptCF, makeFinalRequest(request, true));
469     RetainPtr<CFDataRef> data(AdoptCF, CFURLConnectionSendSynchronousRequest(cfRequest.get(), &cfResponse, &cfError, request.timeoutInterval()));
470     bool cached = cfResponse && !cfError;
471
472     if (cfError)
473         CFRelease(cfError);
474     if (cfResponse)
475         CFRelease(cfResponse);
476
477     return cached;
478 }
479
480 } // namespace WebCore