3da3e44b0c497a91e0271a7a155a14aad45f3fb7
[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 <CoreFoundation/CFStreamAbstract.h>
37
38 #import "WebAssertions.h"
39 #import "WebNSObjectExtras.h"
40
41 #if !BUILDING_ON_PANTHER
42
43 static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void *context);
44
45 typedef struct {
46     CFMutableSetRef scheduledRunLoopPairs;
47     CFMutableArrayRef formDataArray;
48     CFReadStreamRef currentStream;
49     CFDataRef currentData;
50     CFReadStreamRef formStream;
51 } FormStreamFields;
52
53 typedef struct {
54     CFRunLoopRef runLoop;
55     CFStringRef mode;
56 } SchedulePair;
57
58 static const void *pairRetain(CFAllocatorRef alloc, const void *value)
59 {
60     const SchedulePair *pair = (const SchedulePair *)value;
61
62     SchedulePair *result = CFAllocatorAllocate(alloc, sizeof(SchedulePair), 0);
63     CFRetain(pair->runLoop);
64     result->runLoop = pair->runLoop;
65     result->mode = CFStringCreateCopy(alloc, pair->mode);
66     return result;
67 }
68
69 static void pairRelease(CFAllocatorRef alloc, const void *value)
70 {
71     const SchedulePair *pair = (const SchedulePair *)value;
72
73     CFRelease(pair->runLoop);
74     CFRelease(pair->mode);
75     CFAllocatorDeallocate(alloc, (void *)pair);
76 }
77
78 static Boolean pairEqual(const void *a, const void *b)
79 {
80     const SchedulePair *pairA = (const SchedulePair *)a;
81     const SchedulePair *pairB = (const SchedulePair *)b;
82
83     return pairA->runLoop == pairB->runLoop && CFEqual(pairA->mode, pairB->mode);
84 }
85
86 static CFHashCode pairHash(const void *value)
87 {
88     const SchedulePair *pair = (const SchedulePair *)value;
89
90     return ((CFHashCode)pair->runLoop) ^ CFHash(pair->mode);
91 }
92
93 static void closeCurrentStream(FormStreamFields *form)
94 {
95     if (form->currentStream) {
96         CFReadStreamClose(form->currentStream);
97         CFReadStreamSetClient(form->currentStream, kCFStreamEventNone, NULL, NULL);
98         CFRelease(form->currentStream);
99         form->currentStream = NULL;
100     }
101     if (form->currentData) {
102         CFRelease(form->currentData);
103         form->currentData = NULL;
104     }
105 }
106
107 static void scheduleWithPair(const void *value, void *context)
108 {
109     const SchedulePair *pair = (const SchedulePair *)value;
110     CFReadStreamRef stream = (CFReadStreamRef)context;
111
112     CFReadStreamScheduleWithRunLoop(stream, pair->runLoop, pair->mode);
113 }
114
115 static void advanceCurrentStream(FormStreamFields *form)
116 {
117     closeCurrentStream(form);
118
119     // Handle the case where we're at the end of the array.
120     if (CFArrayGetCount(form->formDataArray) == 0) {
121         return;
122     }
123
124     // Create the new stream.
125     CFAllocatorRef alloc = CFGetAllocator(form->formDataArray);
126     CFTypeRef nextInput = CFArrayGetValueAtIndex(form->formDataArray, 0);
127     if (CFGetTypeID(nextInput) == CFDataGetTypeID()) {
128         // nextInput is a CFData containing an absolute path
129         CFDataRef data = (CFDataRef)nextInput;
130         form->currentStream = CFReadStreamCreateWithBytesNoCopy(alloc, CFDataGetBytePtr(data), CFDataGetLength(data), kCFAllocatorNull);
131         form->currentData = data;
132         CFRetain(data);
133     } else {
134         // nextInput is a CFString containing an absolute path
135         CFStringRef path = (CFStringRef)nextInput;
136         CFURLRef fileURL = CFURLCreateWithFileSystemPath(alloc, path, kCFURLPOSIXPathStyle, FALSE);
137         form->currentStream = CFReadStreamCreateWithFile(alloc, fileURL);
138         CFRelease(fileURL);
139     }
140     CFArrayRemoveValueAtIndex(form->formDataArray, 0);
141
142     // Set up the callback.
143     CFStreamClientContext context = { 0, form, NULL, NULL, NULL };
144     CFReadStreamSetClient(form->currentStream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered,
145         formEventCallback, &context);
146
147     // Schedule with the current set of run loops.
148     CFSetApplyFunction(form->scheduledRunLoopPairs, scheduleWithPair, form->currentStream);
149 }
150
151 static void openNextStream(FormStreamFields *form)
152 {
153     // Skip over any streams we can't open.
154     // For some purposes we might want to return an error, but the current NSURLConnection
155     // can't really do anything useful with an error at this point, so this is better.
156     advanceCurrentStream(form);
157     while (form->currentStream && !CFReadStreamOpen(form->currentStream)) {
158         advanceCurrentStream(form);
159     }
160 }
161
162 static void *formCreate(CFReadStreamRef stream, void *context)
163 {
164     CFArrayRef formDataArray = (CFArrayRef)context;
165
166     CFSetCallBacks runLoopAndModeCallBacks = { 0, pairRetain, pairRelease, NULL, pairEqual, pairHash };
167
168     CFAllocatorRef alloc = CFGetAllocator(stream);
169     FormStreamFields *newInfo = CFAllocatorAllocate(alloc, sizeof(FormStreamFields), 0);
170     newInfo->scheduledRunLoopPairs = CFSetCreateMutable(alloc, 0, &runLoopAndModeCallBacks);
171     newInfo->formDataArray = CFArrayCreateMutableCopy(alloc, CFArrayGetCount(formDataArray), formDataArray);
172     newInfo->currentStream = NULL;
173     newInfo->currentData = NULL;
174     newInfo->formStream = stream; // Don't retain. That would create a reference cycle.
175     return newInfo;
176 }
177
178 static void formFinalize(CFReadStreamRef stream, void *context)
179 {
180     FormStreamFields *form = (FormStreamFields *)context;
181
182     closeCurrentStream(context);
183     CFRelease(form->scheduledRunLoopPairs);
184     CFRelease(form->formDataArray);
185     CFAllocatorDeallocate(CFGetAllocator(stream), context);
186 }
187
188 static Boolean formOpen(CFReadStreamRef stream, CFStreamError *error, Boolean *openComplete, void *context)
189 {
190     FormStreamFields *form = (FormStreamFields *)context;
191
192     openNextStream(form);
193
194     *openComplete = TRUE;
195     error->error = 0;
196     return TRUE;
197 }
198
199 static CFIndex formRead(CFReadStreamRef stream, UInt8 *buffer, CFIndex bufferLength, CFStreamError *error, Boolean *atEOF, void *context)
200 {
201     FormStreamFields *form = (FormStreamFields *)context;
202
203     while (form->currentStream) {
204         CFIndex bytesRead = CFReadStreamRead(form->currentStream, buffer, bufferLength);
205         if (bytesRead < 0) {
206             *error = CFReadStreamGetError(form->currentStream);
207             return -1;
208         }
209         if (bytesRead > 0) {
210             error->error = 0;
211             *atEOF = FALSE;
212             return bytesRead;
213         }
214         openNextStream(form);
215     }
216
217     error->error = 0;
218     *atEOF = TRUE;
219     return 0;
220 }
221
222 static Boolean formCanRead(CFReadStreamRef stream, void *context)
223 {
224     FormStreamFields *form = (FormStreamFields *)context;
225
226     while (form->currentStream && CFReadStreamGetStatus(form->currentStream) == kCFStreamStatusAtEnd) {
227         openNextStream(form);
228     }
229     if (!form->currentStream) {
230         CFReadStreamSignalEvent(stream, kCFStreamEventEndEncountered, NULL);
231         return FALSE;
232     }
233     return CFReadStreamHasBytesAvailable(form->currentStream);
234 }
235
236 static void formClose(CFReadStreamRef stream, void *context)
237 {
238     FormStreamFields *form = (FormStreamFields *)context;
239
240     closeCurrentStream(form);
241 }
242
243 static void formSchedule(CFReadStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode, void *context)
244 {
245     FormStreamFields *form = (FormStreamFields *)context;
246
247     if (form->currentStream) {
248         CFReadStreamScheduleWithRunLoop(form->currentStream, runLoop, runLoopMode);
249     }
250     SchedulePair pair = { runLoop, runLoopMode };
251     CFSetAddValue(form->scheduledRunLoopPairs, &pair);
252 }
253
254 static void formUnschedule(CFReadStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode, void *context)
255 {
256     FormStreamFields *form = (FormStreamFields *)context;
257
258     if (form->currentStream) {
259         CFReadStreamUnscheduleFromRunLoop(form->currentStream, runLoop, runLoopMode);
260     }
261     SchedulePair pair = { runLoop, runLoopMode };
262     CFSetRemoveValue(form->scheduledRunLoopPairs, &pair);
263 }
264
265 static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void *context)
266 {
267     FormStreamFields *form = (FormStreamFields *)context;
268
269     switch (type) {
270     case kCFStreamEventHasBytesAvailable:
271         CFReadStreamSignalEvent(form->formStream, kCFStreamEventHasBytesAvailable, NULL);
272         break;
273     case kCFStreamEventErrorOccurred: {
274         CFStreamError readStreamError = CFReadStreamGetError(stream);
275         CFReadStreamSignalEvent(form->formStream, kCFStreamEventErrorOccurred, &readStreamError);
276         break;
277     }
278     case kCFStreamEventEndEncountered:
279         openNextStream(form);
280         if (!form->currentStream) {
281             CFReadStreamSignalEvent(form->formStream, kCFStreamEventEndEncountered, NULL);
282         }
283         break;
284     case kCFStreamEventNone:
285         ERROR("unexpected kCFStreamEventNone");
286         break;
287     case kCFStreamEventOpenCompleted:
288         ERROR("unexpected kCFStreamEventOpenCompleted");
289         break;
290     case kCFStreamEventCanAcceptBytes:
291         ERROR("unexpected kCFStreamEventCanAcceptBytes");
292         break;
293     }
294 }
295
296 #endif // !BUILDING_ON_PANTHER
297
298 void webSetHTTPBody(NSMutableURLRequest *request, NSArray *formData)
299 {
300     unsigned count = [formData count];
301
302     // Handle the common special case of one piece of form data, with no files.
303     if (count == 1) {
304         id d = [formData objectAtIndex:0];
305         if ([d isKindOfClass:[NSData class]]) {
306             [request setHTTPBody:(NSData *)d];
307             return;
308         }
309     }
310
311 #if !BUILDING_ON_PANTHER
312
313     // Precompute the content length so NSURLConnection doesn't use chunked mode.
314     long long length = 0;
315     unsigned i;
316     for (i = 0; i < count; ++i) {
317         id data = [formData objectAtIndex:i];
318         if ([data isKindOfClass:[NSData class]]) {
319             NSData *d = data;
320             length += [d length];
321         } else if ([data isKindOfClass:[NSString class]]) {
322             NSString *s = data;
323             struct stat sb;
324             int statResult = stat([s fileSystemRepresentation], &sb);
325             if (statResult == 0 && (sb.st_mode & S_IFMT) == S_IFREG) {
326                 length += sb.st_size;
327             }
328         } else {
329             ERROR("item in form data array is neither NSData nor NSString");
330             return;
331         }
332     }
333
334     // Set the length.
335     [request setValue:[NSString stringWithFormat:@"%lld", length] forHTTPHeaderField:@"Content-Length"];
336
337     // Create and set the stream.
338     CFReadStreamCallBacks callBacks = { 1, formCreate, formFinalize, NULL,
339         formOpen, NULL, formRead, NULL, formCanRead, formClose,
340         NULL, NULL, NULL, formSchedule, formUnschedule
341     };
342     CFReadStreamRef stream = CFReadStreamCreate(NULL, &callBacks, formData);
343     [request setHTTPBodyStream:(NSInputStream *)stream];
344     CFRelease(stream);
345
346 #else
347
348     NSMutableData *allData = [[NSMutableData alloc] init];
349
350     unsigned i;
351     for (i = 0; i < count; ++i) {
352         id data = [formData objectAtIndex:i];
353         if ([data isKindOfClass:[NSData class]]) {
354             NSData *d = data;
355             [allData appendData:d];
356         } else if ([data isKindOfClass:[NSString class]]) {
357             NSString *s = data;
358             NSData *d = [[NSData alloc] initWithContentsOfFile:s];
359             if (d != nil) {
360                 [allData appendData:d];
361                 [d release];
362             }
363         } else {
364             ERROR("item in form data array is neither NSData nor NSString");
365             return;
366         }
367     }
368
369     [request setHTTPBody:allData];
370     
371     [allData release];
372
373 #endif
374 }