- more SPI removal, for font cache and CFStream
[WebKit-https.git] / WebKit / WebView.subproj / WebFormDataStream.m
1 /*
2  * Copyright (C) 2005 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  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 /* originally written by Becky Willrich, additional code by Darin Adler */
30
31 #import "WebFormDataStream.h"
32
33 #import <sys/types.h>
34 #import <sys/stat.h>
35
36 #import "WebAssertions.h"
37 #import "WebNSObjectExtras.h"
38 #import <WebKitSystemInterface.h>
39
40 #if !BUILDING_ON_PANTHER
41
42 static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void *context);
43
44 typedef struct {
45     CFMutableSetRef scheduledRunLoopPairs;
46     CFMutableArrayRef formDataArray;
47     CFReadStreamRef currentStream;
48     CFDataRef currentData;
49     CFReadStreamRef formStream;
50 } FormStreamFields;
51
52 typedef struct {
53     CFRunLoopRef runLoop;
54     CFStringRef mode;
55 } SchedulePair;
56
57 static const void *pairRetain(CFAllocatorRef alloc, const void *value)
58 {
59     const SchedulePair *pair = (const SchedulePair *)value;
60
61     SchedulePair *result = CFAllocatorAllocate(alloc, sizeof(SchedulePair), 0);
62     CFRetain(pair->runLoop);
63     result->runLoop = pair->runLoop;
64     result->mode = CFStringCreateCopy(alloc, pair->mode);
65     return result;
66 }
67
68 static void pairRelease(CFAllocatorRef alloc, const void *value)
69 {
70     const SchedulePair *pair = (const SchedulePair *)value;
71
72     CFRelease(pair->runLoop);
73     CFRelease(pair->mode);
74     CFAllocatorDeallocate(alloc, (void *)pair);
75 }
76
77 static Boolean pairEqual(const void *a, const void *b)
78 {
79     const SchedulePair *pairA = (const SchedulePair *)a;
80     const SchedulePair *pairB = (const SchedulePair *)b;
81
82     return pairA->runLoop == pairB->runLoop && CFEqual(pairA->mode, pairB->mode);
83 }
84
85 static CFHashCode pairHash(const void *value)
86 {
87     const SchedulePair *pair = (const SchedulePair *)value;
88
89     return ((CFHashCode)pair->runLoop) ^ CFHash(pair->mode);
90 }
91
92 static void closeCurrentStream(FormStreamFields *form)
93 {
94     if (form->currentStream) {
95         CFReadStreamClose(form->currentStream);
96         CFReadStreamSetClient(form->currentStream, kCFStreamEventNone, NULL, NULL);
97         CFRelease(form->currentStream);
98         form->currentStream = NULL;
99     }
100     if (form->currentData) {
101         CFRelease(form->currentData);
102         form->currentData = NULL;
103     }
104 }
105
106 static void scheduleWithPair(const void *value, void *context)
107 {
108     const SchedulePair *pair = (const SchedulePair *)value;
109     CFReadStreamRef stream = (CFReadStreamRef)context;
110
111     CFReadStreamScheduleWithRunLoop(stream, pair->runLoop, pair->mode);
112 }
113
114 static void advanceCurrentStream(FormStreamFields *form)
115 {
116     closeCurrentStream(form);
117
118     // Handle the case where we're at the end of the array.
119     if (CFArrayGetCount(form->formDataArray) == 0) {
120         return;
121     }
122
123     // Create the new stream.
124     CFAllocatorRef alloc = CFGetAllocator(form->formDataArray);
125     CFTypeRef nextInput = CFArrayGetValueAtIndex(form->formDataArray, 0);
126     if (CFGetTypeID(nextInput) == CFDataGetTypeID()) {
127         // nextInput is a CFData containing an absolute path
128         CFDataRef data = (CFDataRef)nextInput;
129         form->currentStream = CFReadStreamCreateWithBytesNoCopy(alloc, CFDataGetBytePtr(data), CFDataGetLength(data), kCFAllocatorNull);
130         form->currentData = data;
131         CFRetain(data);
132     } else {
133         // nextInput is a CFString containing an absolute path
134         CFStringRef path = (CFStringRef)nextInput;
135         CFURLRef fileURL = CFURLCreateWithFileSystemPath(alloc, path, kCFURLPOSIXPathStyle, FALSE);
136         form->currentStream = CFReadStreamCreateWithFile(alloc, fileURL);
137         CFRelease(fileURL);
138     }
139     CFArrayRemoveValueAtIndex(form->formDataArray, 0);
140
141     // Set up the callback.
142     CFStreamClientContext context = { 0, form, NULL, NULL, NULL };
143     CFReadStreamSetClient(form->currentStream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered,
144         formEventCallback, &context);
145
146     // Schedule with the current set of run loops.
147     CFSetApplyFunction(form->scheduledRunLoopPairs, scheduleWithPair, form->currentStream);
148 }
149
150 static void openNextStream(FormStreamFields *form)
151 {
152     // Skip over any streams we can't open.
153     // For some purposes we might want to return an error, but the current NSURLConnection
154     // can't really do anything useful with an error at this point, so this is better.
155     advanceCurrentStream(form);
156     while (form->currentStream && !CFReadStreamOpen(form->currentStream)) {
157         advanceCurrentStream(form);
158     }
159 }
160
161 static void *formCreate(CFReadStreamRef stream, void *context)
162 {
163     CFArrayRef formDataArray = (CFArrayRef)context;
164
165     CFSetCallBacks runLoopAndModeCallBacks = { 0, pairRetain, pairRelease, NULL, pairEqual, pairHash };
166
167     CFAllocatorRef alloc = CFGetAllocator(stream);
168     FormStreamFields *newInfo = CFAllocatorAllocate(alloc, sizeof(FormStreamFields), 0);
169     newInfo->scheduledRunLoopPairs = CFSetCreateMutable(alloc, 0, &runLoopAndModeCallBacks);
170     newInfo->formDataArray = CFArrayCreateMutableCopy(alloc, CFArrayGetCount(formDataArray), formDataArray);
171     newInfo->currentStream = NULL;
172     newInfo->currentData = NULL;
173     newInfo->formStream = stream; // Don't retain. That would create a reference cycle.
174     return newInfo;
175 }
176
177 static void formFinalize(CFReadStreamRef stream, void *context)
178 {
179     FormStreamFields *form = (FormStreamFields *)context;
180
181     closeCurrentStream(context);
182     CFRelease(form->scheduledRunLoopPairs);
183     CFRelease(form->formDataArray);
184     CFAllocatorDeallocate(CFGetAllocator(stream), context);
185 }
186
187 static Boolean formOpen(CFReadStreamRef stream, CFStreamError *error, Boolean *openComplete, void *context)
188 {
189     FormStreamFields *form = (FormStreamFields *)context;
190
191     openNextStream(form);
192
193     *openComplete = TRUE;
194     error->error = 0;
195     return TRUE;
196 }
197
198 static CFIndex formRead(CFReadStreamRef stream, UInt8 *buffer, CFIndex bufferLength, CFStreamError *error, Boolean *atEOF, void *context)
199 {
200     FormStreamFields *form = (FormStreamFields *)context;
201
202     while (form->currentStream) {
203         CFIndex bytesRead = CFReadStreamRead(form->currentStream, buffer, bufferLength);
204         if (bytesRead < 0) {
205             *error = CFReadStreamGetError(form->currentStream);
206             return -1;
207         }
208         if (bytesRead > 0) {
209             error->error = 0;
210             *atEOF = FALSE;
211             return bytesRead;
212         }
213         openNextStream(form);
214     }
215
216     error->error = 0;
217     *atEOF = TRUE;
218     return 0;
219 }
220
221 static Boolean formCanRead(CFReadStreamRef stream, void *context)
222 {
223     FormStreamFields *form = (FormStreamFields *)context;
224
225     while (form->currentStream && CFReadStreamGetStatus(form->currentStream) == kCFStreamStatusAtEnd) {
226         openNextStream(form);
227     }
228     if (!form->currentStream) {
229                 WKSignalCFReadStreamEnd(stream);
230         return FALSE;
231     }
232     return CFReadStreamHasBytesAvailable(form->currentStream);
233 }
234
235 static void formClose(CFReadStreamRef stream, void *context)
236 {
237     FormStreamFields *form = (FormStreamFields *)context;
238
239     closeCurrentStream(form);
240 }
241
242 static void formSchedule(CFReadStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode, void *context)
243 {
244     FormStreamFields *form = (FormStreamFields *)context;
245
246     if (form->currentStream) {
247         CFReadStreamScheduleWithRunLoop(form->currentStream, runLoop, runLoopMode);
248     }
249     SchedulePair pair = { runLoop, runLoopMode };
250     CFSetAddValue(form->scheduledRunLoopPairs, &pair);
251 }
252
253 static void formUnschedule(CFReadStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode, void *context)
254 {
255     FormStreamFields *form = (FormStreamFields *)context;
256
257     if (form->currentStream) {
258         CFReadStreamUnscheduleFromRunLoop(form->currentStream, runLoop, runLoopMode);
259     }
260     SchedulePair pair = { runLoop, runLoopMode };
261     CFSetRemoveValue(form->scheduledRunLoopPairs, &pair);
262 }
263
264 static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void *context)
265 {
266     FormStreamFields *form = (FormStreamFields *)context;
267
268     switch (type) {
269     case kCFStreamEventHasBytesAvailable:
270         WKSignalCFReadStreamHasBytes(form->formStream);
271         break;
272     case kCFStreamEventErrorOccurred: {
273         CFStreamError readStreamError = CFReadStreamGetError(stream);
274         WKSignalCFReadStreamError(form->formStream, &readStreamError);
275         break;
276     }
277     case kCFStreamEventEndEncountered:
278         openNextStream(form);
279         if (!form->currentStream) {
280             WKSignalCFReadStreamEnd(form->formStream);
281         }
282         break;
283     case kCFStreamEventNone:
284         ERROR("unexpected kCFStreamEventNone");
285         break;
286     case kCFStreamEventOpenCompleted:
287         ERROR("unexpected kCFStreamEventOpenCompleted");
288         break;
289     case kCFStreamEventCanAcceptBytes:
290         ERROR("unexpected kCFStreamEventCanAcceptBytes");
291         break;
292     }
293 }
294
295 #endif // !BUILDING_ON_PANTHER
296
297 void webSetHTTPBody(NSMutableURLRequest *request, NSArray *formData)
298 {
299     unsigned count = [formData count];
300
301     // Handle the common special case of one piece of form data, with no files.
302     if (count == 1) {
303         id d = [formData objectAtIndex:0];
304         if ([d isKindOfClass:[NSData class]]) {
305             [request setHTTPBody:(NSData *)d];
306             return;
307         }
308     }
309
310 #if !BUILDING_ON_PANTHER
311
312     // Precompute the content length so NSURLConnection doesn't use chunked mode.
313     long long length = 0;
314     unsigned i;
315     for (i = 0; i < count; ++i) {
316         id data = [formData objectAtIndex:i];
317         if ([data isKindOfClass:[NSData class]]) {
318             NSData *d = data;
319             length += [d length];
320         } else if ([data isKindOfClass:[NSString class]]) {
321             NSString *s = data;
322             struct stat sb;
323             int statResult = stat([s fileSystemRepresentation], &sb);
324             if (statResult == 0 && (sb.st_mode & S_IFMT) == S_IFREG) {
325                 length += sb.st_size;
326             }
327         } else {
328             ERROR("item in form data array is neither NSData nor NSString");
329             return;
330         }
331     }
332
333     // Set the length.
334     [request setValue:[NSString stringWithFormat:@"%lld", length] forHTTPHeaderField:@"Content-Length"];
335
336     // Create and set the stream.
337         CFReadStreamRef stream = WKCreateCustomCFReadStream(formCreate, formFinalize, formOpen, formRead, formCanRead, formClose, formSchedule, formUnschedule, formData);
338         [request setHTTPBodyStream:(NSInputStream *)stream];
339     CFRelease(stream);
340
341 #else
342
343     NSMutableData *allData = [[NSMutableData alloc] init];
344
345     unsigned i;
346     for (i = 0; i < count; ++i) {
347         id data = [formData objectAtIndex:i];
348         if ([data isKindOfClass:[NSData class]]) {
349             NSData *d = data;
350             [allData appendData:d];
351         } else if ([data isKindOfClass:[NSString class]]) {
352             NSString *s = data;
353             NSData *d = [[NSData alloc] initWithContentsOfFile:s];
354             if (d != nil) {
355                 [allData appendData:d];
356                 [d release];
357             }
358         } else {
359             ERROR("item in form data array is neither NSData nor NSString");
360             return;
361         }
362     }
363
364     [request setHTTPBody:allData];
365     
366     [allData release];
367
368 #endif
369 }