2006-11-02 Anders Carlsson <acarlsson@apple.com>
[WebKit-https.git] / WebCore / platform / network / cf / ResourceHandleCFNet.cpp
1 /*
2  * Copyright (C) 2004, 2006 Apple Computer, 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 #if USE(CFNETWORK)
29
30 #include "DocLoader.h"
31 #include "Frame.h"
32 #include "ResourceHandle.h"
33 #include "ResourceHandleInternal.h"
34 #include "ResourceResponse.h"
35 #include "ResourceResponseCFNet.h"
36
37 #include <WTF/HashMap.h>
38
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <process.h> // for _beginthread()
42
43 #include <CFNetwork/CFNetwork.h>
44 #include <CFNetwork/CFNetworkPriv.h>
45
46 //#define LOG_RESOURCELOADER_EVENTS 1
47
48 namespace WebCore {
49
50 CFURLRequestRef willSendRequest(CFURLConnectionRef conn, CFURLRequestRef request, CFURLResponseRef redirectionResponse, const void* clientInfo) 
51 {
52     ResourceHandle* job = (ResourceHandle*)clientInfo;
53     CFURLRef url = CFURLRequestGetURL(request);
54     CFStringRef urlString = CFURLGetString(url);
55     const char *bytes = CFStringGetCStringPtr(urlString, kCFStringEncodingUTF8);
56     bool freeBytes = false;
57
58 #if defined(LOG_RESOURCELOADER_EVENTS)
59     CFStringRef str = CFStringCreateWithFormat(0, 0, CFSTR("willSendRequest(conn=%p, job = %p)\n"), conn, job);
60     CFShow(str);
61     CFRelease(str);
62 #endif
63
64     if (!bytes) {
65         CFIndex numBytes, urlLength = CFStringGetLength(urlString);
66         UInt8* newBytes;
67         CFStringGetBytes(urlString, CFRangeMake(0, urlLength), kCFStringEncodingUTF8, 0, FALSE, 0, 0, &numBytes);
68         newBytes = (UInt8*)malloc(numBytes + 1);
69         CFStringGetBytes(urlString, CFRangeMake(0, urlLength), kCFStringEncodingUTF8, 0, FALSE, newBytes, numBytes, &numBytes);
70         newBytes[numBytes] = 0;
71         freeBytes = true;
72         bytes = (char*)newBytes;
73     }
74     ASSERT(bytes);
75     KURL newURL(bytes);
76     if (!(newURL == job->url()))
77         job->client()->receivedRedirect(job, newURL);
78     if (freeBytes) 
79         free((void*)bytes);
80     return request;
81 }
82
83 void didReceiveResponse(CFURLConnectionRef conn, CFURLResponseRef cfResponse, const void* clientInfo) 
84 {
85     ResourceHandle* handle = (ResourceHandle*)clientInfo;
86
87 #if defined(LOG_RESOURCELOADER_EVENTS)
88     CFStringRef str = CFStringCreateWithFormat(0, 0, CFSTR("didReceiveResponse(conn=%p, job = %p)\n"), conn, handle);
89     CFShow(str);
90     CFRelease(str);
91 #endif
92
93     if (ResourceHandleClient* client = handle->client()) {
94       client->receivedResponse(handle, cfResponse);
95       ResourceResponse response;
96       getResourceResponse(response, cfResponse);
97       client->didReceiveResponse(handle, response);
98     }
99 }
100
101 void didReceiveData(CFURLConnectionRef conn, CFDataRef data, CFIndex originalLength, const void* clientInfo) 
102 {
103     ResourceHandle* job = (ResourceHandle*)clientInfo;
104     const UInt8* bytes = CFDataGetBytePtr(data);
105     CFIndex length = CFDataGetLength(data);
106
107 #if defined(LOG_RESOURCELOADER_EVENTS)
108     CFStringRef str = CFStringCreateWithFormat(0, 0, CFSTR("didReceiveData(conn=%p, job = %p, numBytes = %d)\n"), conn, job, length);
109     CFShow(str);
110     CFRelease(str);
111 #endif
112
113     job->client()->didReceiveData(job, (const char*)bytes, length);
114 }
115
116 void didFinishLoading(CFURLConnectionRef conn, const void* clientInfo) 
117 {
118     ResourceHandle* job = (ResourceHandle*)clientInfo;
119
120 #if defined(LOG_RESOURCELOADER_EVENTS)
121     CFStringRef str = CFStringCreateWithFormat(0, 0, CFSTR("didFinishLoading(conn=%p, job = %p)\n"), conn, job);
122     CFShow(str);
123     CFRelease(str);
124 #endif
125
126     job->client()->receivedAllData(job, 0);
127     job->client()->didFinishLoading(job);
128     job->kill();
129 }
130
131 void didFail(CFURLConnectionRef conn, CFStreamError error, const void* clientInfo) 
132 {
133     ResourceHandle* job = (ResourceHandle*)clientInfo;
134
135 #if defined(LOG_RESOURCELOADER_EVENTS)
136     CFStringRef str = CFStringCreateWithFormat(0, 0, CFSTR("didFail(conn=%p, job = %p, error = {%d, %d})\n"), conn, job, error.domain, error.error);
137     CFShow(str);
138     CFRelease(str);
139 #endif
140
141     job->setError(1);
142     job->client()->receivedAllData(job, 0);
143     job->client()->didFinishLoading(job);
144     job->kill();
145 }
146
147 CFCachedURLResponseRef willCacheResponse(CFURLConnectionRef conn, CFCachedURLResponseRef cachedResponse, const void* clientInfo) 
148 {
149     ResourceHandle* job = (ResourceHandle*)clientInfo;
150     return cachedResponse;
151 }
152
153 void didReceiveChallenge(CFURLConnectionRef conn, CFURLAuthChallengeRef challenge, const void* clientInfo)
154 {
155     ResourceHandle* job = (ResourceHandle*)clientInfo;
156
157     // Do nothing right now
158 }
159
160 void addHeadersFromHashMap(CFMutableURLRequestRef request, const HTTPHeaderMap& requestHeaders) 
161 {
162     if (!requestHeaders.size())
163         return;
164
165     HTTPHeaderMap::const_iterator end = requestHeaders.end();
166     for (HTTPHeaderMap::const_iterator it = requestHeaders.begin(); it != end; ++it) {
167         CFStringRef key = it->first.createCFString();
168         CFStringRef value = it->second.createCFString();
169         CFURLRequestSetHTTPHeaderFieldValue(request, key, value);
170         CFRelease(key);
171         CFRelease(value);
172     }
173 }
174
175 ResourceHandleInternal::~ResourceHandleInternal()
176 {
177     if (m_connection) {
178
179 #if defined(LOG_RESOURCELOADER_EVENTS)
180         CFStringRef str = CFStringCreateWithFormat(0, 0, CFSTR("Cancelling connection %p\n"), m_connection);
181         CFShow(str);
182         CFRelease(str);
183 #endif
184         CFURLConnectionCancel(m_connection);
185         CFRelease(m_connection);
186         m_connection = 0;
187     }
188 }
189
190 ResourceHandle::~ResourceHandle()
191 {
192 #if defined(LOG_RESOURCELOADER_EVENTS)
193     CFStringRef str = CFStringCreateWithFormat(0, 0, CFSTR("Destroying job %p\n"), this);
194     CFShow(str);
195     CFRelease(str);
196 #endif
197     delete d;
198 }
199
200 CFArrayRef arrayFromFormData(const FormData& d)
201 {
202     size_t size = d.elements().size();
203     CFMutableArrayRef a = CFArrayCreateMutable(0, d.elements().size(), &kCFTypeArrayCallBacks);
204     for (size_t i = 0; i < size; ++i) {
205         const FormDataElement& e = d.elements()[i];
206         if (e.m_type == FormDataElement::data) {
207             CFDataRef data = CFDataCreate(0, (const UInt8*)e.m_data.data(), e.m_data.size());
208             CFArrayAppendValue(a, data);
209             CFRelease(data);
210         } else {
211             ASSERT(e.m_type == FormDataElement::encodedFile);
212             CFStringRef filename = e.m_filename.createCFString();
213             CFArrayAppendValue(a, filename);
214             CFRelease(filename);
215         }
216     }
217     return a;
218 }
219
220 void emptyPerform(void* unused) 
221 {
222 }
223
224 static CFRunLoopRef loaderRL = 0;
225 void runLoaderThread(void *unused)
226 {
227     loaderRL = CFRunLoopGetCurrent();
228
229     // Must add a source to the run loop to prevent CFRunLoopRun() from exiting
230     CFRunLoopSourceContext ctxt = {0, (void *)1 /*must be non-NULL*/, 0, 0, 0, 0, 0, 0, 0, emptyPerform};
231     CFRunLoopSourceRef bogusSource = CFRunLoopSourceCreate(0, 0, &ctxt);
232     CFRunLoopAddSource(loaderRL, bogusSource,kCFRunLoopDefaultMode);
233
234     CFRunLoopRun();
235 }
236
237 bool ResourceHandle::start(DocLoader* docLoader)
238 {
239     CFURLRef url = d->m_request.url().createCFURL();
240
241     CFStringRef requestMethod = d->m_request.httpMethod().createCFString();
242     CFMutableURLRequestRef request = CFURLRequestCreateMutable(0, url, kCFURLRequestCachePolicyProtocolDefault, 30.0, 0);
243     Boolean isPost = CFStringCompare(requestMethod, CFSTR("POST"), kCFCompareCaseInsensitive);
244     CFRelease(requestMethod);
245     
246     CFStringRef userAgentString = docLoader->frame()->userAgent().createCFString();
247     CFURLRequestSetHTTPHeaderFieldValue(request, CFSTR("User-Agent"), userAgentString);
248     CFRelease(userAgentString);
249
250     ref();
251     d->m_loading = true;
252     addHeadersFromHashMap(request, d->m_request.httpHeaderFields());
253
254     String referrer = docLoader->frame()->referrer();
255     if (!referrer.isEmpty() && referrer.find("file:", 0, false) != 0) {
256         CFStringRef str = referrer.createCFString();
257         CFURLRequestSetHTTPHeaderFieldValue(request, CFSTR("Referer"),str);
258         CFRelease(str);
259     }
260     
261     CFReadStreamRef bodyStream = 0;
262     if (postData().elements().size() > 0) {
263         CFArrayRef formArray = arrayFromFormData(postData());
264         bool done = false;
265         CFIndex count = CFArrayGetCount(formArray);
266
267         if (count == 1) {
268             // Handle the common special case of one piece of form data, with no files.
269             CFTypeRef d = CFArrayGetValueAtIndex(formArray, 0);
270             if (CFGetTypeID(d) == CFDataGetTypeID()) {
271                 CFURLRequestSetHTTPRequestBody(request, (CFDataRef)d);
272                 done = true;
273             }
274         }
275
276         if (!done) {
277             // Precompute the content length so NSURLConnection doesn't use chunked mode.
278             long long length = 0;
279             unsigned i;
280             bool success = true;
281             for (i = 0; success && i < count; ++i) {
282                 CFTypeRef data = CFArrayGetValueAtIndex(formArray, i);
283                 CFIndex typeID = CFGetTypeID(data);
284                 if (typeID == CFDataGetTypeID()) {
285                     CFDataRef d = (CFDataRef)data;
286                     length += CFDataGetLength(d);
287                 } else {
288                     // data is a CFStringRef
289                     CFStringRef s = (CFStringRef)data;
290                     CFIndex bufLen = CFStringGetMaximumSizeOfFileSystemRepresentation(s);
291                     char* buf = (char*)malloc(bufLen);
292                     if (CFStringGetFileSystemRepresentation(s, buf, bufLen)) {
293                         struct _stat64i32 sb;
294                         int statResult = _stat(buf, &sb);
295                         if (statResult == 0 && (sb.st_mode & S_IFMT) == S_IFREG)
296                             length += sb.st_size;
297                         else
298                             success = false;
299                     } else {
300                         success = false;
301                     }
302                     free(buf);
303                 }
304             }
305             if (success) {
306                 CFStringRef lengthStr = CFStringCreateWithFormat(0, 0, CFSTR("%lld"), length);
307                 CFURLRequestSetHTTPHeaderFieldValue(request, CFSTR("Content-Length"), lengthStr);
308                 CFRelease(lengthStr);
309             }
310             bodyStream = CFReadStreamCreateWithFormArray(0, formArray);
311         }
312         CFRelease(formArray);
313     }
314
315     if (bodyStream)
316         CFURLRequestSetHTTPRequestBodyStream(request, bodyStream);
317     CFURLConnectionClient client = {0, this, 0, 0, 0, willSendRequest, didReceiveResponse, didReceiveData, NULL, didFinishLoading, didFail, willCacheResponse, didReceiveChallenge};
318     d->m_connection = CFURLConnectionCreate(0, request, &client);
319     CFRelease(request);
320
321     if (!loaderRL) {
322         _beginthread(runLoaderThread, 0, 0);
323         while (loaderRL == 0) {
324             Sleep(10);
325         }
326     }
327
328     CFURLConnectionScheduleWithCurrentMessageQueue(d->m_connection);
329     CFURLConnectionScheduleDownloadWithRunLoop(d->m_connection, loaderRL, kCFRunLoopDefaultMode);
330     CFURLConnectionStart(d->m_connection);
331
332 #if defined(LOG_RESOURCELOADER_EVENTS)
333     CFStringRef outStr = CFStringCreateWithFormat(0, 0, CFSTR("Starting URL %@ (job = %p, connection = %p)\n"), CFURLGetString(url), this, d->m_connection);
334     CFShow(outStr);
335     CFRelease(outStr);
336 #endif
337     CFRelease(url);
338     return true;
339 }
340
341 void ResourceHandle::cancel()
342 {
343     if (d->m_connection) {
344         CFURLConnectionCancel(d->m_connection);
345         CFRelease(d->m_connection);
346         d->m_connection = 0;
347     }
348
349     // Copied directly from ResourceHandleWin.cpp
350     setError(1);
351     d->m_client->receivedAllData(this, 0);
352     d->m_client->didFinishLoading(this);
353 }
354
355 } // namespace WebCore
356
357 #endif // USE(CFNETWORK)