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