• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2005, 2006, 2008 Apple 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 "config.h"
32#import "FormDataStreamMac.h"
33
34#import "CString.h"
35#import "FileSystem.h"
36#import "FormData.h"
37#import "ResourceHandle.h"
38#import "ResourceHandleClient.h"
39#import "SchedulePair.h"
40#import "WebCoreSystemInterface.h"
41#import <sys/stat.h>
42#import <sys/types.h>
43#import <wtf/Assertions.h>
44#import <wtf/HashMap.h>
45#import <wtf/MainThread.h>
46#import <wtf/StdLibExtras.h>
47#import <wtf/Threading.h>
48
49namespace WebCore {
50
51typedef HashMap<CFReadStreamRef, RefPtr<FormData> > StreamFormDataMap;
52static StreamFormDataMap& getStreamFormDataMap()
53{
54    DEFINE_STATIC_LOCAL(StreamFormDataMap, streamFormDataMap, ());
55    return streamFormDataMap;
56}
57
58typedef HashMap<CFReadStreamRef, RefPtr<ResourceHandle> > StreamResourceHandleMap;
59static StreamResourceHandleMap& getStreamResourceHandleMap()
60{
61    DEFINE_STATIC_LOCAL(StreamResourceHandleMap, streamResourceHandleMap, ());
62    return streamResourceHandleMap;
63}
64
65void associateStreamWithResourceHandle(NSInputStream *stream, ResourceHandle* resourceHandle)
66{
67    ASSERT(isMainThread());
68
69    ASSERT(resourceHandle);
70
71    if (!stream)
72        return;
73
74    if (!getStreamFormDataMap().contains((CFReadStreamRef)stream))
75        return;
76
77    ASSERT(!getStreamResourceHandleMap().contains((CFReadStreamRef)stream));
78    getStreamResourceHandleMap().set((CFReadStreamRef)stream, resourceHandle);
79}
80
81void disassociateStreamWithResourceHandle(NSInputStream *stream)
82{
83    ASSERT(isMainThread());
84
85    if (!stream)
86        return;
87
88    getStreamResourceHandleMap().remove((CFReadStreamRef)stream);
89}
90
91struct DidSendDataCallbackData {
92    DidSendDataCallbackData(CFReadStreamRef stream_, unsigned long long bytesSent_, unsigned long long streamLength_)
93        : stream(stream_)
94        , bytesSent(bytesSent_)
95        , streamLength(streamLength_)
96    {
97    }
98
99    CFReadStreamRef stream;
100    unsigned long long bytesSent;
101    unsigned long long streamLength;
102};
103
104static void performDidSendDataCallback(void* context)
105{
106    ASSERT(isMainThread());
107
108    DidSendDataCallbackData* data = static_cast<DidSendDataCallbackData*>(context);
109    ResourceHandle* resourceHandle = getStreamResourceHandleMap().get(data->stream).get();
110
111    if (resourceHandle && resourceHandle->client())
112        resourceHandle->client()->didSendData(resourceHandle, data->bytesSent, data->streamLength);
113
114    delete data;
115}
116
117static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context);
118
119struct FormContext {
120    FormData* formData;
121    unsigned long long streamLength;
122};
123
124struct FormStreamFields {
125    SchedulePairHashSet scheduledRunLoopPairs;
126    Vector<FormDataElement> remainingElements; // in reverse order
127    CFReadStreamRef currentStream;
128    char* currentData;
129    CFReadStreamRef formStream;
130    unsigned long long streamLength;
131    unsigned long long bytesSent;
132};
133
134static void closeCurrentStream(FormStreamFields *form)
135{
136    if (form->currentStream) {
137        CFReadStreamClose(form->currentStream);
138        CFReadStreamSetClient(form->currentStream, kCFStreamEventNone, NULL, NULL);
139        CFRelease(form->currentStream);
140        form->currentStream = NULL;
141    }
142    if (form->currentData) {
143        fastFree(form->currentData);
144        form->currentData = 0;
145    }
146}
147
148static void advanceCurrentStream(FormStreamFields *form)
149{
150    closeCurrentStream(form);
151
152    if (form->remainingElements.isEmpty())
153        return;
154
155    // Create the new stream.
156    FormDataElement& nextInput = form->remainingElements.last();
157    if (nextInput.m_type == FormDataElement::data) {
158        size_t size = nextInput.m_data.size();
159        char* data = nextInput.m_data.releaseBuffer();
160        form->currentStream = CFReadStreamCreateWithBytesNoCopy(0, reinterpret_cast<const UInt8*>(data), size, kCFAllocatorNull);
161        form->currentData = data;
162    } else {
163        const String& path = nextInput.m_shouldGenerateFile ? nextInput.m_generatedFilename : nextInput.m_filename;
164        CFStringRef filename = path.createCFString();
165        CFURLRef fileURL = CFURLCreateWithFileSystemPath(0, filename, kCFURLPOSIXPathStyle, FALSE);
166        CFRelease(filename);
167        form->currentStream = CFReadStreamCreateWithFile(0, fileURL);
168        CFRelease(fileURL);
169    }
170    form->remainingElements.removeLast();
171
172    // Set up the callback.
173    CFStreamClientContext context = { 0, form, NULL, NULL, NULL };
174    CFReadStreamSetClient(form->currentStream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered,
175        formEventCallback, &context);
176
177    // Schedule with the current set of run loops.
178    SchedulePairHashSet::iterator end = form->scheduledRunLoopPairs.end();
179    for (SchedulePairHashSet::iterator it = form->scheduledRunLoopPairs.begin(); it != end; ++it)
180        CFReadStreamScheduleWithRunLoop(form->currentStream, (*it)->runLoop(), (*it)->mode());
181}
182
183static void openNextStream(FormStreamFields* form)
184{
185    // Skip over any streams we can't open.
186    // For some purposes we might want to return an error, but the current NSURLConnection
187    // can't really do anything useful with an error at this point, so this is better.
188    advanceCurrentStream(form);
189    while (form->currentStream && !CFReadStreamOpen(form->currentStream))
190        advanceCurrentStream(form);
191}
192
193static void* formCreate(CFReadStreamRef stream, void* context)
194{
195    FormContext* formContext = static_cast<FormContext*>(context);
196
197    FormStreamFields* newInfo = new FormStreamFields;
198    newInfo->currentStream = NULL;
199    newInfo->currentData = 0;
200    newInfo->formStream = stream; // Don't retain. That would create a reference cycle.
201    newInfo->streamLength = formContext->streamLength;
202    newInfo->bytesSent = 0;
203
204    FormData* formData = formContext->formData;
205
206    // Append in reverse order since we remove elements from the end.
207    size_t size = formData->elements().size();
208    newInfo->remainingElements.reserveInitialCapacity(size);
209    for (size_t i = 0; i < size; ++i)
210        newInfo->remainingElements.append(formData->elements()[size - i - 1]);
211
212    getStreamFormDataMap().set(stream, adoptRef(formData));
213
214    return newInfo;
215}
216
217static void formFinalize(CFReadStreamRef stream, void* context)
218{
219    FormStreamFields* form = static_cast<FormStreamFields*>(context);
220
221    getStreamFormDataMap().remove(stream);
222
223    closeCurrentStream(form);
224    delete form;
225}
226
227static Boolean formOpen(CFReadStreamRef, CFStreamError* error, Boolean* openComplete, void* context)
228{
229    FormStreamFields* form = static_cast<FormStreamFields*>(context);
230
231    openNextStream(form);
232
233    *openComplete = TRUE;
234    error->error = 0;
235    return TRUE;
236}
237
238static CFIndex formRead(CFReadStreamRef stream, UInt8* buffer, CFIndex bufferLength, CFStreamError* error, Boolean* atEOF, void* context)
239{
240    FormStreamFields* form = static_cast<FormStreamFields*>(context);
241
242    while (form->currentStream) {
243        CFIndex bytesRead = CFReadStreamRead(form->currentStream, buffer, bufferLength);
244        if (bytesRead < 0) {
245            *error = CFReadStreamGetError(form->currentStream);
246            return -1;
247        }
248        if (bytesRead > 0) {
249            error->error = 0;
250            *atEOF = FALSE;
251            form->bytesSent += bytesRead;
252
253            if (!ResourceHandle::didSendBodyDataDelegateExists()) {
254                // FIXME: Figure out how to only do this when a ResourceHandleClient is available.
255                DidSendDataCallbackData* data = new DidSendDataCallbackData(stream, form->bytesSent, form->streamLength);
256                callOnMainThread(performDidSendDataCallback, data);
257            }
258
259            return bytesRead;
260        }
261        openNextStream(form);
262    }
263
264    error->error = 0;
265    *atEOF = TRUE;
266    return 0;
267}
268
269static Boolean formCanRead(CFReadStreamRef stream, void* context)
270{
271    FormStreamFields* form = static_cast<FormStreamFields*>(context);
272
273    while (form->currentStream && CFReadStreamGetStatus(form->currentStream) == kCFStreamStatusAtEnd) {
274        openNextStream(form);
275    }
276    if (!form->currentStream) {
277        wkSignalCFReadStreamEnd(stream);
278        return FALSE;
279    }
280    return CFReadStreamHasBytesAvailable(form->currentStream);
281}
282
283static void formClose(CFReadStreamRef, void* context)
284{
285    FormStreamFields* form = static_cast<FormStreamFields*>(context);
286
287    closeCurrentStream(form);
288}
289
290static void formSchedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context)
291{
292    FormStreamFields* form = static_cast<FormStreamFields*>(context);
293
294    if (form->currentStream)
295        CFReadStreamScheduleWithRunLoop(form->currentStream, runLoop, runLoopMode);
296    form->scheduledRunLoopPairs.add(SchedulePair::create(runLoop, runLoopMode));
297}
298
299static void formUnschedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context)
300{
301    FormStreamFields* form = static_cast<FormStreamFields*>(context);
302
303    if (form->currentStream)
304        CFReadStreamUnscheduleFromRunLoop(form->currentStream, runLoop, runLoopMode);
305    form->scheduledRunLoopPairs.remove(SchedulePair::create(runLoop, runLoopMode));
306}
307
308static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context)
309{
310    FormStreamFields* form = static_cast<FormStreamFields*>(context);
311
312    switch (type) {
313    case kCFStreamEventHasBytesAvailable:
314        wkSignalCFReadStreamHasBytes(form->formStream);
315        break;
316    case kCFStreamEventErrorOccurred: {
317        CFStreamError readStreamError = CFReadStreamGetError(stream);
318        wkSignalCFReadStreamError(form->formStream, &readStreamError);
319        break;
320    }
321    case kCFStreamEventEndEncountered:
322        openNextStream(form);
323        if (!form->currentStream) {
324            wkSignalCFReadStreamEnd(form->formStream);
325        }
326        break;
327    case kCFStreamEventNone:
328        LOG_ERROR("unexpected kCFStreamEventNone");
329        break;
330    case kCFStreamEventOpenCompleted:
331        LOG_ERROR("unexpected kCFStreamEventOpenCompleted");
332        break;
333    case kCFStreamEventCanAcceptBytes:
334        LOG_ERROR("unexpected kCFStreamEventCanAcceptBytes");
335        break;
336    }
337}
338
339void setHTTPBody(NSMutableURLRequest *request, PassRefPtr<FormData> formData)
340{
341    if (!formData)
342        return;
343
344    size_t count = formData->elements().size();
345
346    // Handle the common special case of one piece of form data, with no files.
347    if (count == 1 && !formData->alwaysStream()) {
348        const FormDataElement& element = formData->elements()[0];
349        if (element.m_type == FormDataElement::data) {
350            NSData *data = [[NSData alloc] initWithBytes:element.m_data.data() length:element.m_data.size()];
351            [request setHTTPBody:data];
352            [data release];
353            return;
354        }
355    }
356
357    // Precompute the content length so NSURLConnection doesn't use chunked mode.
358    long long length = 0;
359    for (size_t i = 0; i < count; ++i) {
360        const FormDataElement& element = formData->elements()[i];
361        if (element.m_type == FormDataElement::data)
362            length += element.m_data.size();
363        else {
364            long long fileSize;
365            if (getFileSize(element.m_shouldGenerateFile ? element.m_generatedFilename : element.m_filename, fileSize))
366                length += fileSize;
367        }
368    }
369
370    // Set the length.
371    [request setValue:[NSString stringWithFormat:@"%lld", length] forHTTPHeaderField:@"Content-Length"];
372
373    // Create and set the stream.
374
375    // Pass the length along with the formData so it does not have to be recomputed.
376    FormContext formContext = { formData.releaseRef(), length };
377
378    CFReadStreamRef stream = wkCreateCustomCFReadStream(formCreate, formFinalize,
379        formOpen, formRead, formCanRead, formClose, formSchedule, formUnschedule,
380        &formContext);
381    [request setHTTPBodyStream:(NSInputStream *)stream];
382    CFRelease(stream);
383}
384
385FormData* httpBodyFromStream(NSInputStream* stream)
386{
387    return getStreamFormDataMap().get((CFReadStreamRef)stream).get();
388}
389
390} // namespace WebCore
391