1/*
2 * Copyright (C) 2005, 2006, 2007, 2012 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#include "config.h"
30#include "FormDataStreamCFNet.h"
31
32#include "BlobData.h"
33#include "FileSystem.h"
34#include "FormData.h"
35#include <sys/stat.h>
36#include <sys/types.h>
37#include <wtf/Assertions.h>
38#include <wtf/HashMap.h>
39#include <wtf/MainThread.h>
40#include <wtf/RetainPtr.h>
41#include <wtf/SchedulePair.h>
42#include <wtf/StdLibExtras.h>
43#include <wtf/Threading.h>
44
45#if PLATFORM(IOS)
46#include <MacErrors.h>
47#elif PLATFORM(MAC)
48#include <CoreServices/CoreServices.h>
49#endif
50
51#if PLATFORM(MAC)
52extern "C" void CFURLRequestSetHTTPRequestBody(CFMutableURLRequestRef mutableHTTPRequest, CFDataRef httpBody);
53extern "C" void CFURLRequestSetHTTPHeaderFieldValue(CFMutableURLRequestRef mutableHTTPRequest, CFStringRef httpHeaderField, CFStringRef httpHeaderFieldValue);
54extern "C" void CFURLRequestSetHTTPRequestBodyStream(CFMutableURLRequestRef req, CFReadStreamRef bodyStream);
55#elif PLATFORM(WIN)
56#include <CFNetwork/CFURLRequest.h>
57#endif
58
59typedef struct {
60    CFIndex version; /* == 1 */
61    void *(*create)(CFReadStreamRef stream, void *info);
62    void (*finalize)(CFReadStreamRef stream, void *info);
63    CFStringRef (*copyDescription)(CFReadStreamRef stream, void *info);
64    Boolean (*open)(CFReadStreamRef stream, CFStreamError *error, Boolean *openComplete, void *info);
65    Boolean (*openCompleted)(CFReadStreamRef stream, CFStreamError *error, void *info);
66    CFIndex (*read)(CFReadStreamRef stream, UInt8 *buffer, CFIndex bufferLength, CFStreamError *error, Boolean *atEOF, void *info);
67    const UInt8 *(*getBuffer)(CFReadStreamRef stream, CFIndex maxBytesToRead, CFIndex *numBytesRead, CFStreamError *error, Boolean *atEOF, void *info);
68    Boolean (*canRead)(CFReadStreamRef stream, void *info);
69    void (*close)(CFReadStreamRef stream, void *info);
70    CFTypeRef (*copyProperty)(CFReadStreamRef stream, CFStringRef propertyName, void *info);
71    Boolean (*setProperty)(CFReadStreamRef stream, CFStringRef propertyName, CFTypeRef propertyValue, void *info);
72    void (*requestEvents)(CFReadStreamRef stream, CFOptionFlags streamEvents, void *info);
73    void (*schedule)(CFReadStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode, void *info);
74    void (*unschedule)(CFReadStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode, void *info);
75} CFReadStreamCallBacksV1;
76
77#if PLATFORM(WIN)
78#define EXTERN extern "C" __declspec(dllimport)
79#else
80#define EXTERN extern "C"
81#endif
82
83EXTERN void CFReadStreamSignalEvent(CFReadStreamRef stream, CFStreamEventType event, const void *error);
84EXTERN CFReadStreamRef CFReadStreamCreate(CFAllocatorRef alloc, const void *callbacks, void *info);
85
86namespace WebCore {
87
88static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context);
89
90static CFStringRef formDataPointerPropertyName = CFSTR("WebKitFormDataPointer");
91
92CFStringRef formDataStreamLengthPropertyName()
93{
94    return CFSTR("WebKitFormDataStreamLength");
95}
96
97struct FormCreationContext {
98    RefPtr<FormData> formData;
99    unsigned long long streamLength;
100};
101
102struct FormStreamFields {
103    RefPtr<FormData> formData;
104    SchedulePairHashSet scheduledRunLoopPairs;
105    Vector<FormDataElement> remainingElements; // in reverse order
106    CFReadStreamRef currentStream;
107#if ENABLE(BLOB)
108    long long currentStreamRangeLength;
109#endif
110    char* currentData;
111    CFReadStreamRef formStream;
112    unsigned long long streamLength;
113    unsigned long long bytesSent;
114};
115
116static void closeCurrentStream(FormStreamFields* form)
117{
118    if (form->currentStream) {
119        CFReadStreamClose(form->currentStream);
120        CFReadStreamSetClient(form->currentStream, kCFStreamEventNone, 0, 0);
121        CFRelease(form->currentStream);
122        form->currentStream = 0;
123#if ENABLE(BLOB)
124        form->currentStreamRangeLength = BlobDataItem::toEndOfFile;
125#endif
126    }
127    if (form->currentData) {
128        fastFree(form->currentData);
129        form->currentData = 0;
130    }
131}
132
133// Return false if we cannot advance the stream. Currently the only possible failure is that the underlying file has been removed or changed since File.slice.
134static bool advanceCurrentStream(FormStreamFields* form)
135{
136    closeCurrentStream(form);
137
138    if (form->remainingElements.isEmpty())
139        return true;
140
141    // Create the new stream.
142    FormDataElement& nextInput = form->remainingElements.last();
143
144    if (nextInput.m_type == FormDataElement::data) {
145        size_t size = nextInput.m_data.size();
146        char* data = nextInput.m_data.releaseBuffer();
147        form->currentStream = CFReadStreamCreateWithBytesNoCopy(0, reinterpret_cast<const UInt8*>(data), size, kCFAllocatorNull);
148        form->currentData = data;
149    } else {
150#if ENABLE(BLOB)
151        // Check if the file has been changed or not if required.
152        if (isValidFileTime(nextInput.m_expectedFileModificationTime)) {
153            time_t fileModificationTime;
154            if (!getFileModificationTime(nextInput.m_filename, fileModificationTime) || fileModificationTime != static_cast<time_t>(nextInput.m_expectedFileModificationTime))
155                return false;
156        }
157#endif
158        const String& path = nextInput.m_shouldGenerateFile ? nextInput.m_generatedFilename : nextInput.m_filename;
159        form->currentStream = CFReadStreamCreateWithFile(0, pathAsURL(path).get());
160        if (!form->currentStream) {
161            // The file must have been removed or become unreadable.
162            return false;
163        }
164#if ENABLE(BLOB)
165        if (nextInput.m_fileStart > 0) {
166            RetainPtr<CFNumberRef> position = adoptCF(CFNumberCreate(0, kCFNumberLongLongType, &nextInput.m_fileStart));
167            CFReadStreamSetProperty(form->currentStream, kCFStreamPropertyFileCurrentOffset, position.get());
168        }
169        form->currentStreamRangeLength = nextInput.m_fileLength;
170#endif
171    }
172    form->remainingElements.removeLast();
173
174    // Set up the callback.
175    CFStreamClientContext context = { 0, form, 0, 0, 0 };
176    CFReadStreamSetClient(form->currentStream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered,
177        formEventCallback, &context);
178
179    // Schedule with the current set of run loops.
180    SchedulePairHashSet::iterator end = form->scheduledRunLoopPairs.end();
181    for (SchedulePairHashSet::iterator it = form->scheduledRunLoopPairs.begin(); it != end; ++it)
182        CFReadStreamScheduleWithRunLoop(form->currentStream, (*it)->runLoop(), (*it)->mode());
183
184    return true;
185}
186
187static bool openNextStream(FormStreamFields* form)
188{
189    // Skip over any streams we can't open.
190    if (!advanceCurrentStream(form))
191        return false;
192    while (form->currentStream && !CFReadStreamOpen(form->currentStream)) {
193        if (!advanceCurrentStream(form))
194            return false;
195    }
196    return true;
197}
198
199static void* formCreate(CFReadStreamRef stream, void* context)
200{
201    FormCreationContext* formContext = static_cast<FormCreationContext*>(context);
202
203    FormStreamFields* newInfo = new FormStreamFields;
204    newInfo->formData = formContext->formData.release();
205    newInfo->currentStream = 0;
206#if ENABLE(BLOB)
207    newInfo->currentStreamRangeLength = BlobDataItem::toEndOfFile;
208#endif
209    newInfo->currentData = 0;
210    newInfo->formStream = stream; // Don't retain. That would create a reference cycle.
211    newInfo->streamLength = formContext->streamLength;
212    newInfo->bytesSent = 0;
213
214    // Append in reverse order since we remove elements from the end.
215    size_t size = newInfo->formData->elements().size();
216    newInfo->remainingElements.reserveInitialCapacity(size);
217    for (size_t i = 0; i < size; ++i)
218        newInfo->remainingElements.append(newInfo->formData->elements()[size - i - 1]);
219
220    return newInfo;
221}
222
223static void formFinishFinalizationOnMainThread(void* context)
224{
225    OwnPtr<FormStreamFields> form = adoptPtr(static_cast<FormStreamFields*>(context));
226
227    closeCurrentStream(form.get());
228}
229
230static void formFinalize(CFReadStreamRef stream, void* context)
231{
232    FormStreamFields* form = static_cast<FormStreamFields*>(context);
233    ASSERT_UNUSED(stream, form->formStream == stream);
234
235    callOnMainThread(formFinishFinalizationOnMainThread, form);
236}
237
238static Boolean formOpen(CFReadStreamRef, CFStreamError* error, Boolean* openComplete, void* context)
239{
240    FormStreamFields* form = static_cast<FormStreamFields*>(context);
241
242    bool opened = openNextStream(form);
243
244    *openComplete = opened;
245    error->error = opened ? 0 :
246#if PLATFORM(WIN)
247        ENOENT;
248#else
249        fnfErr;
250#endif
251    return opened;
252}
253
254static CFIndex formRead(CFReadStreamRef, UInt8* buffer, CFIndex bufferLength, CFStreamError* error, Boolean* atEOF, void* context)
255{
256    FormStreamFields* form = static_cast<FormStreamFields*>(context);
257
258    while (form->currentStream) {
259        CFIndex bytesToRead = bufferLength;
260#if ENABLE(BLOB)
261        if (form->currentStreamRangeLength != BlobDataItem::toEndOfFile && form->currentStreamRangeLength < bytesToRead)
262            bytesToRead = static_cast<CFIndex>(form->currentStreamRangeLength);
263#endif
264        CFIndex bytesRead = CFReadStreamRead(form->currentStream, buffer, bytesToRead);
265        if (bytesRead < 0) {
266            *error = CFReadStreamGetError(form->currentStream);
267            return -1;
268        }
269        if (bytesRead > 0) {
270            error->error = 0;
271            *atEOF = FALSE;
272            form->bytesSent += bytesRead;
273#if ENABLE(BLOB)
274            if (form->currentStreamRangeLength != BlobDataItem::toEndOfFile)
275                form->currentStreamRangeLength -= bytesRead;
276#endif
277
278            return bytesRead;
279        }
280        openNextStream(form);
281    }
282
283    error->error = 0;
284    *atEOF = TRUE;
285    return 0;
286}
287
288static Boolean formCanRead(CFReadStreamRef stream, void* context)
289{
290    FormStreamFields* form = static_cast<FormStreamFields*>(context);
291
292    while (form->currentStream && CFReadStreamGetStatus(form->currentStream) == kCFStreamStatusAtEnd)
293        openNextStream(form);
294
295    if (!form->currentStream) {
296        CFReadStreamSignalEvent(stream, kCFStreamEventEndEncountered, 0);
297        return FALSE;
298    }
299    return CFReadStreamHasBytesAvailable(form->currentStream);
300}
301
302static void formClose(CFReadStreamRef, void* context)
303{
304    FormStreamFields* form = static_cast<FormStreamFields*>(context);
305
306    closeCurrentStream(form);
307}
308
309static CFTypeRef formCopyProperty(CFReadStreamRef, CFStringRef propertyName, void *context)
310{
311    FormStreamFields* form = static_cast<FormStreamFields*>(context);
312
313    if (kCFCompareEqualTo == CFStringCompare(propertyName, formDataPointerPropertyName, 0)) {
314        long formDataAsNumber = static_cast<long>(reinterpret_cast<intptr_t>(form->formData.get()));
315        return CFNumberCreate(0, kCFNumberLongType, &formDataAsNumber);
316    }
317
318    if (kCFCompareEqualTo == CFStringCompare(propertyName, formDataStreamLengthPropertyName(), 0))
319        return CFStringCreateWithFormat(0, 0, CFSTR("%llu"), form->streamLength);
320
321    return 0;
322}
323
324static void formSchedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context)
325{
326    FormStreamFields* form = static_cast<FormStreamFields*>(context);
327
328    if (form->currentStream)
329        CFReadStreamScheduleWithRunLoop(form->currentStream, runLoop, runLoopMode);
330    form->scheduledRunLoopPairs.add(SchedulePair::create(runLoop, runLoopMode));
331}
332
333static void formUnschedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context)
334{
335    FormStreamFields* form = static_cast<FormStreamFields*>(context);
336
337    if (form->currentStream)
338        CFReadStreamUnscheduleFromRunLoop(form->currentStream, runLoop, runLoopMode);
339    form->scheduledRunLoopPairs.remove(SchedulePair::create(runLoop, runLoopMode));
340}
341
342static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context)
343{
344    FormStreamFields* form = static_cast<FormStreamFields*>(context);
345
346    switch (type) {
347    case kCFStreamEventHasBytesAvailable:
348        CFReadStreamSignalEvent(form->formStream, kCFStreamEventHasBytesAvailable, 0);
349        break;
350    case kCFStreamEventErrorOccurred: {
351        CFStreamError readStreamError = CFReadStreamGetError(stream);
352        CFReadStreamSignalEvent(form->formStream, kCFStreamEventErrorOccurred, &readStreamError);
353        break;
354    }
355    case kCFStreamEventEndEncountered:
356        openNextStream(form);
357        if (!form->currentStream)
358            CFReadStreamSignalEvent(form->formStream, kCFStreamEventEndEncountered, 0);
359        break;
360    case kCFStreamEventNone:
361        LOG_ERROR("unexpected kCFStreamEventNone");
362        break;
363    case kCFStreamEventOpenCompleted:
364        LOG_ERROR("unexpected kCFStreamEventOpenCompleted");
365        break;
366    case kCFStreamEventCanAcceptBytes:
367        LOG_ERROR("unexpected kCFStreamEventCanAcceptBytes");
368        break;
369    }
370}
371
372void setHTTPBody(CFMutableURLRequestRef request, PassRefPtr<FormData> prpFormData)
373{
374    RefPtr<FormData> formData = prpFormData;
375
376    if (!formData)
377        return;
378
379    size_t count = formData->elements().size();
380
381    // Handle the common special case of one piece of form data, with no files.
382    if (count == 1 && !formData->alwaysStream()) {
383        const FormDataElement& element = formData->elements()[0];
384        if (element.m_type == FormDataElement::data) {
385            RetainPtr<CFDataRef> data = adoptCF(CFDataCreate(0, reinterpret_cast<const UInt8 *>(element.m_data.data()), element.m_data.size()));
386            CFURLRequestSetHTTPRequestBody(request, data.get());
387            return;
388        }
389    }
390
391#if ENABLE(BLOB)
392    formData = formData->resolveBlobReferences();
393    count = formData->elements().size();
394#endif
395
396    // Precompute the content length so NSURLConnection doesn't use chunked mode.
397    unsigned long long length = 0;
398    for (size_t i = 0; i < count; ++i) {
399        const FormDataElement& element = formData->elements()[i];
400        if (element.m_type == FormDataElement::data)
401            length += element.m_data.size();
402        else {
403#if ENABLE(BLOB)
404            // If we're sending the file range, use the existing range length for now. We will detect if the file has been changed right before we read the file and abort the operation if necessary.
405            if (element.m_fileLength != BlobDataItem::toEndOfFile) {
406                length += element.m_fileLength;
407                continue;
408            }
409#endif
410            long long fileSize;
411            if (getFileSize(element.m_shouldGenerateFile ? element.m_generatedFilename : element.m_filename, fileSize))
412                length += fileSize;
413        }
414    }
415
416    // Create and set the stream.
417
418    // Pass the length along with the formData so it does not have to be recomputed.
419    FormCreationContext formContext = { formData.release(), length };
420
421    CFReadStreamCallBacksV1 callBacks = { 1, formCreate, formFinalize, 0, formOpen, 0, formRead, 0, formCanRead, formClose, formCopyProperty, 0, 0, formSchedule, formUnschedule
422    };
423    RetainPtr<CFReadStreamRef> stream = adoptCF(CFReadStreamCreate(0, static_cast<const void*>(&callBacks), &formContext));
424
425    CFURLRequestSetHTTPRequestBodyStream(request, stream.get());
426}
427
428FormData* httpBodyFromStream(CFReadStreamRef stream)
429{
430    if (!stream)
431        return 0;
432
433    // Passing the pointer as property appears to be the only way to associate a stream with FormData.
434    // A new stream is always created in CFURLRequestCopyHTTPRequestBodyStream (or -[NSURLRequest HTTPBodyStream]),
435    // so a side HashMap wouldn't work.
436    // Even the stream's context pointer is different from the one we returned from formCreate().
437
438    RetainPtr<CFNumberRef> formDataPointerAsCFNumber = adoptCF(static_cast<CFNumberRef>(CFReadStreamCopyProperty(stream, formDataPointerPropertyName)));
439    if (!formDataPointerAsCFNumber)
440        return 0;
441
442    long formDataPointerAsNumber;
443    if (!CFNumberGetValue(formDataPointerAsCFNumber.get(), kCFNumberLongType, &formDataPointerAsNumber))
444        return 0;
445
446    return reinterpret_cast<FormData*>(static_cast<intptr_t>(formDataPointerAsNumber));
447}
448
449} // namespace WebCore
450