Reviewed by Darin.
[WebKit-https.git] / WebCore / kwq / KWQLoader.mm
1 /*
2  * Copyright (C) 2003 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 #import "KWQLoader.h"
27
28 #import "KWQExceptions.h"
29 #import "KWQKJobClasses.h"
30 #import "KWQLogging.h"
31 #import "KWQResourceLoader.h"
32 #import "KWQFoundationExtras.h"
33 #import "WebCoreBridge.h"
34 #import "khtml_part.h"
35 #import "loader.h"
36
37 #import <Foundation/NSURLResponse.h>
38
39 using khtml::Cache;
40 using khtml::CachedObject;
41 using khtml::CachedImage;
42 using khtml::DocLoader;
43 using khtml::Loader;
44 using khtml::Request;
45 using KIO::TransferJob;
46
47 bool KWQServeRequest(Loader *loader, Request *request, TransferJob *job)
48 {
49     LOG(Loading, "Serving request for base %s, url %s", 
50         request->m_docLoader->part()->baseURL().url().latin1(),
51         request->object->url().string().latin1());
52     
53     return KWQServeRequest(loader, request->m_docLoader, job);
54 }
55
56 @interface NSDictionary (WebCore_Extras)
57 - (id)_webcore_initWithHeaderString:(NSString *)string;
58 @end
59
60 @implementation NSDictionary (WebCore_Extras)
61 - (id)_webcore_initWithHeaderString:(NSString *)string
62 {
63     NSMutableDictionary *headers = [[NSMutableDictionary alloc] init];
64
65     NSArray *lines = [string componentsSeparatedByString:@"\r\n"];
66
67     NSEnumerator *e = [lines objectEnumerator];
68     NSString *line;
69
70     NSString *lastHeaderName = nil;
71
72     while ((line = (NSString *)[e nextObject]) != nil) {
73         if (([line characterAtIndex:0] == ' ' || [line characterAtIndex:0] == '\t')
74             && lastHeaderName != nil) {
75             // lines that start with space or tab continue the previous header value
76             NSString *oldVal = [headers objectForKey:lastHeaderName];
77             ASSERT(oldVal);
78             [headers setObject:[NSString stringWithFormat:@"%@ %@", oldVal, [line stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" \t"]]]
79                         forKey:lastHeaderName];
80             continue;
81         }
82
83         NSRange colonRange = [line rangeOfString:@":"];
84         if (colonRange.location != NSNotFound) {
85             // don't worry about case, assume lower levels will take care of it
86
87             NSString *headerName = [[line substringToIndex:colonRange.location] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" \t"]];
88             NSString *headerValue = [[line substringFromIndex:colonRange.location + 1] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" \t"]];
89             
90             NSString *oldVal = [headers objectForKey:headerName];
91             if (oldVal) {
92                 headerValue = [NSString stringWithFormat:@"%@, %@", oldVal, headerValue];
93             }
94
95             [headers setObject:headerValue forKey:headerName];
96             
97             lastHeaderName = headerName;
98         }
99     }
100
101     self = [self initWithDictionary:headers];
102     [headers release];
103     return self;
104 }
105
106 @end
107
108 bool KWQServeRequest(Loader *loader, DocLoader *docLoader, TransferJob *job)
109 {
110     KWQKHTMLPart *part = static_cast<KWQKHTMLPart *>(docLoader->part());
111     WebCoreBridge *bridge = part->bridge();
112
113     part->didTellBridgeAboutLoad(job->url().url());
114
115     KWQ_BLOCK_EXCEPTIONS;
116     KWQResourceLoader *resourceLoader = [[KWQResourceLoader alloc] initWithJob:job];
117
118     id <WebCoreResourceHandle> handle;
119
120     NSDictionary *headerDict = nil;
121     QString headerString = job->queryMetaData("customHTTPHeader");
122
123     if (!headerString.isEmpty()) {
124         headerDict = [[NSDictionary alloc] _webcore_initWithHeaderString:headerString.getNSString()];
125     }
126
127     if (job->method() == "POST") {
128         NSData *postData = [NSData dataWithBytesNoCopy:job->postData().data() length:job->postData().size() freeWhenDone:NO];
129         handle = [bridge startLoadingResource:resourceLoader withURL:job->url().getNSURL() customHeaders:headerDict postData:postData];
130     } else {
131         handle = [bridge startLoadingResource:resourceLoader withURL:job->url().getNSURL() customHeaders:headerDict];
132     }
133     [resourceLoader setHandle:handle];
134     [resourceLoader release];
135     return handle != nil;
136     KWQ_UNBLOCK_EXCEPTIONS;
137
138     return true;
139 }
140
141 static NSString *KWQHeaderStringFromDictionary(NSDictionary *headers, int statusCode)
142 {
143     NSMutableString *headerString = [[NSMutableString alloc] init];
144     [headerString appendString:[NSString stringWithFormat:@"HTTP/1.0 %d OK\n", statusCode]];
145     
146     NSEnumerator *e = [headers keyEnumerator];
147     NSString *key;
148     
149     bool first = true;
150     
151     while ((key = [e nextObject]) != nil) {
152         if (first) {
153             first = false;
154         } else {
155             [headerString appendString:@"\n"];
156         }
157         [headerString appendString:key];
158         [headerString appendString:@": "];
159         [headerString appendString:[headers objectForKey:key]];
160     }
161         
162     return headerString;
163 }
164
165 QByteArray KWQServeSynchronousRequest(Loader *loader, DocLoader *docLoader, TransferJob *job, KURL &finalURL, QString &responseHeaders)
166 {
167     KWQKHTMLPart *part = static_cast<KWQKHTMLPart *>(docLoader->part());
168     WebCoreBridge *bridge = part->bridge();
169
170     part->didTellBridgeAboutLoad(job->url().url());
171
172     KWQ_BLOCK_EXCEPTIONS;
173
174     NSDictionary *headerDict = nil;
175     QString headerString = job->queryMetaData("customHTTPHeader");
176
177     if (!headerString.isEmpty()) {
178         headerDict = [[NSDictionary alloc] _webcore_initWithHeaderString:headerString.getNSString()];
179     }
180
181     NSData *postData = nil;
182     
183
184     if (job->method() == "POST") {
185         postData = [NSData dataWithBytesNoCopy:job->postData().data() length:job->postData().size() freeWhenDone:NO];
186     }
187
188     NSURL *finalNSURL = nil;
189     NSDictionary *responseHeaderDict = nil;
190     int statusCode = 0;
191     NSData *resultData = [bridge syncLoadResourceWithURL:job->url().getNSURL() customHeaders:headerDict postData:postData finalURL:&finalNSURL responseHeaders:&responseHeaderDict statusCode:&statusCode];
192     
193     job->kill();
194
195     finalURL = finalNSURL;
196     responseHeaders = QString::fromNSString(KWQHeaderStringFromDictionary(responseHeaderDict, statusCode));
197
198     QByteArray results([resultData length]);
199
200     memcpy( results.data(), [resultData bytes], [resultData length] );
201
202     return results;
203
204     KWQ_UNBLOCK_EXCEPTIONS;
205
206     return QByteArray();
207 }
208
209 int KWQNumberOfPendingOrLoadingRequests(khtml::DocLoader *dl)
210 {
211     return Cache::loader()->numRequests(dl);
212 }
213
214 bool KWQCheckIfReloading(DocLoader *loader)
215 {
216     KWQ_BLOCK_EXCEPTIONS;
217     return [static_cast<KWQKHTMLPart *>(loader->part())->bridge() isReloading];
218     KWQ_UNBLOCK_EXCEPTIONS;
219
220     return false;
221 }
222
223 void KWQCheckCacheObjectStatus(DocLoader *loader, CachedObject *cachedObject)
224 {
225     // Return from the function for objects that we didn't load from the cache.
226     if (!cachedObject)
227         return;
228     switch (cachedObject->status()) {
229     case CachedObject::Persistent:
230     case CachedObject::Cached:
231     case CachedObject::Uncacheable:
232         break;
233     case CachedObject::NotCached:
234     case CachedObject::Unknown:
235     case CachedObject::New:
236     case CachedObject::Pending:
237         return;
238     }
239     
240     ASSERT(cachedObject->response());
241     
242     // Notify the caller that we "loaded".
243     KWQKHTMLPart *part = static_cast<KWQKHTMLPart *>(loader->part());
244
245     QString urlString = cachedObject->url().string();
246
247     if (!part->haveToldBridgeAboutLoad(urlString)) {
248         WebCoreBridge *bridge = part->bridge();
249         CachedImage *cachedImage = dynamic_cast<CachedImage *>(cachedObject);
250
251         KWQ_BLOCK_EXCEPTIONS;
252         [bridge objectLoadedFromCacheWithURL:KURL(cachedObject->url().string()).getNSURL()
253                 response:(id)cachedObject->response()
254                 size:cachedImage ? cachedImage->dataSize() : cachedObject->size()];
255         KWQ_UNBLOCK_EXCEPTIONS;
256
257         part->didTellBridgeAboutLoad(urlString);
258     }
259 }
260
261 void KWQRetainResponse(void *response)
262 {
263     // There's no way a retain can raise
264     KWQRetain((id)response);
265 }
266
267 void KWQReleaseResponse(void *response)
268 {
269     // A release could raise if it deallocs.
270     KWQ_BLOCK_EXCEPTIONS;
271     KWQRelease((id)response);
272     KWQ_UNBLOCK_EXCEPTIONS;
273 }
274
275 void *KWQResponseMIMEType(void *response)
276 {
277     KWQ_BLOCK_EXCEPTIONS;
278     return [(NSURLResponse *)response MIMEType];
279     KWQ_UNBLOCK_EXCEPTIONS;
280
281     return NULL;
282 }
283
284 void *KWQResponseTextEncodingName(void *response)
285 {
286     KWQ_BLOCK_EXCEPTIONS;
287     return [(NSURLResponse *)response textEncodingName];
288     KWQ_UNBLOCK_EXCEPTIONS;
289
290     return NULL;
291 }
292
293 void *KWQResponseHeaderString(void *response)
294 {
295     KWQ_BLOCK_EXCEPTIONS;
296     NSURLResponse *nsResponse = (NSURLResponse *)response;
297     if ([nsResponse isKindOfClass:[NSHTTPURLResponse class]]) {
298         NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)nsResponse;
299         NSDictionary *headers = [httpResponse allHeaderFields];
300
301         return KWQHeaderStringFromDictionary(headers, [httpResponse statusCode]);
302     }
303
304     KWQ_UNBLOCK_EXCEPTIONS;
305
306     return NULL;
307 }
308
309 time_t KWQCacheObjectExpiresTime(khtml::DocLoader *docLoader, void *response)
310 {
311     KWQ_BLOCK_EXCEPTIONS;
312     
313     KWQKHTMLPart *part = static_cast<KWQKHTMLPart *>(docLoader->part());
314     WebCoreBridge *bridge = part->bridge();
315     return [bridge expiresTimeForResponse:(NSURLResponse *)response];
316     
317     KWQ_UNBLOCK_EXCEPTIONS;
318     
319     return 0;
320 }
321
322 KWQLoader::KWQLoader(Loader *loader)
323     : _requestStarted(loader, SIGNAL(requestStarted(khtml::DocLoader *, khtml::CachedObject *)))
324     , _requestDone(loader, SIGNAL(requestDone(khtml::DocLoader *, khtml::CachedObject *)))
325     , _requestFailed(loader, SIGNAL(requestFailed(khtml::DocLoader *, khtml::CachedObject *)))
326 {
327 }