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 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(COCOA)
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    long long currentStreamRangeLength;
108    MallocPtr<char> currentData;
109    CFReadStreamRef formStream;
110    unsigned long long streamLength;
111    unsigned long long bytesSent;
112};
113
114static void closeCurrentStream(FormStreamFields* form)
115{
116    if (form->currentStream) {
117        CFReadStreamClose(form->currentStream);
118        CFReadStreamSetClient(form->currentStream, kCFStreamEventNone, 0, 0);
119        CFRelease(form->currentStream);
120        form->currentStream = 0;
121        form->currentStreamRangeLength = BlobDataItem::toEndOfFile;
122    }
123
124    form->currentData = nullptr;
125}
126
127// 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.
128static bool advanceCurrentStream(FormStreamFields* form)
129{
130    closeCurrentStream(form);
131
132    if (form->remainingElements.isEmpty())
133        return true;
134
135    // Create the new stream.
136    FormDataElement& nextInput = form->remainingElements.last();
137
138    if (nextInput.m_type == FormDataElement::Type::Data) {
139        size_t size = nextInput.m_data.size();
140        MallocPtr<char> data = nextInput.m_data.releaseBuffer();
141        form->currentStream = CFReadStreamCreateWithBytesNoCopy(0, reinterpret_cast<const UInt8*>(data.get()), size, kCFAllocatorNull);
142        form->currentData = WTF::move(data);
143    } else {
144        // Check if the file has been changed or not if required.
145        if (isValidFileTime(nextInput.m_expectedFileModificationTime)) {
146            time_t fileModificationTime;
147            if (!getFileModificationTime(nextInput.m_filename, fileModificationTime) || fileModificationTime != static_cast<time_t>(nextInput.m_expectedFileModificationTime))
148                return false;
149        }
150        const String& path = nextInput.m_shouldGenerateFile ? nextInput.m_generatedFilename : nextInput.m_filename;
151        form->currentStream = CFReadStreamCreateWithFile(0, pathAsURL(path).get());
152        if (!form->currentStream) {
153            // The file must have been removed or become unreadable.
154            return false;
155        }
156        if (nextInput.m_fileStart > 0) {
157            RetainPtr<CFNumberRef> position = adoptCF(CFNumberCreate(0, kCFNumberLongLongType, &nextInput.m_fileStart));
158            CFReadStreamSetProperty(form->currentStream, kCFStreamPropertyFileCurrentOffset, position.get());
159        }
160        form->currentStreamRangeLength = nextInput.m_fileLength;
161    }
162    form->remainingElements.removeLast();
163
164    // Set up the callback.
165    CFStreamClientContext context = { 0, form, 0, 0, 0 };
166    CFReadStreamSetClient(form->currentStream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered,
167        formEventCallback, &context);
168
169    // Schedule with the current set of run loops.
170    SchedulePairHashSet::iterator end = form->scheduledRunLoopPairs.end();
171    for (SchedulePairHashSet::iterator it = form->scheduledRunLoopPairs.begin(); it != end; ++it)
172        CFReadStreamScheduleWithRunLoop(form->currentStream, (*it)->runLoop(), (*it)->mode());
173
174    return true;
175}
176
177static bool openNextStream(FormStreamFields* form)
178{
179    // Skip over any streams we can't open.
180    if (!advanceCurrentStream(form))
181        return false;
182    while (form->currentStream && !CFReadStreamOpen(form->currentStream)) {
183        if (!advanceCurrentStream(form))
184            return false;
185    }
186    return true;
187}
188
189static void* formCreate(CFReadStreamRef stream, void* context)
190{
191    FormCreationContext* formContext = static_cast<FormCreationContext*>(context);
192
193    FormStreamFields* newInfo = new FormStreamFields;
194    newInfo->formData = formContext->formData.release();
195    newInfo->currentStream = 0;
196    newInfo->currentStreamRangeLength = BlobDataItem::toEndOfFile;
197    newInfo->formStream = stream; // Don't retain. That would create a reference cycle.
198    newInfo->streamLength = formContext->streamLength;
199    newInfo->bytesSent = 0;
200
201    // Append in reverse order since we remove elements from the end.
202    size_t size = newInfo->formData->elements().size();
203    newInfo->remainingElements.reserveInitialCapacity(size);
204    for (size_t i = 0; i < size; ++i)
205        newInfo->remainingElements.append(newInfo->formData->elements()[size - i - 1]);
206
207    return newInfo;
208}
209
210static void formFinishFinalizationOnMainThread(void* context)
211{
212    OwnPtr<FormStreamFields> form = adoptPtr(static_cast<FormStreamFields*>(context));
213
214    closeCurrentStream(form.get());
215}
216
217static void formFinalize(CFReadStreamRef stream, void* context)
218{
219    FormStreamFields* form = static_cast<FormStreamFields*>(context);
220    ASSERT_UNUSED(stream, form->formStream == stream);
221
222    callOnMainThread(formFinishFinalizationOnMainThread, form);
223}
224
225static Boolean formOpen(CFReadStreamRef, CFStreamError* error, Boolean* openComplete, void* context)
226{
227    FormStreamFields* form = static_cast<FormStreamFields*>(context);
228
229    bool opened = openNextStream(form);
230
231    *openComplete = opened;
232    error->error = opened ? 0 :
233#if PLATFORM(WIN)
234        ENOENT;
235#else
236        fnfErr;
237#endif
238    return opened;
239}
240
241static CFIndex formRead(CFReadStreamRef, UInt8* buffer, CFIndex bufferLength, CFStreamError* error, Boolean* atEOF, void* context)
242{
243    FormStreamFields* form = static_cast<FormStreamFields*>(context);
244
245    while (form->currentStream) {
246        CFIndex bytesToRead = bufferLength;
247        if (form->currentStreamRangeLength != BlobDataItem::toEndOfFile && form->currentStreamRangeLength < bytesToRead)
248            bytesToRead = static_cast<CFIndex>(form->currentStreamRangeLength);
249        CFIndex bytesRead = CFReadStreamRead(form->currentStream, buffer, bytesToRead);
250        if (bytesRead < 0) {
251            *error = CFReadStreamGetError(form->currentStream);
252            return -1;
253        }
254        if (bytesRead > 0) {
255            error->error = 0;
256            *atEOF = FALSE;
257            form->bytesSent += bytesRead;
258            if (form->currentStreamRangeLength != BlobDataItem::toEndOfFile)
259                form->currentStreamRangeLength -= bytesRead;
260
261            return bytesRead;
262        }
263        openNextStream(form);
264    }
265
266    error->error = 0;
267    *atEOF = TRUE;
268    return 0;
269}
270
271static Boolean formCanRead(CFReadStreamRef stream, void* context)
272{
273    FormStreamFields* form = static_cast<FormStreamFields*>(context);
274
275    while (form->currentStream && CFReadStreamGetStatus(form->currentStream) == kCFStreamStatusAtEnd)
276        openNextStream(form);
277
278    if (!form->currentStream) {
279        CFReadStreamSignalEvent(stream, kCFStreamEventEndEncountered, 0);
280        return FALSE;
281    }
282    return CFReadStreamHasBytesAvailable(form->currentStream);
283}
284
285static void formClose(CFReadStreamRef, void* context)
286{
287    FormStreamFields* form = static_cast<FormStreamFields*>(context);
288
289    closeCurrentStream(form);
290}
291
292static CFTypeRef formCopyProperty(CFReadStreamRef, CFStringRef propertyName, void *context)
293{
294    FormStreamFields* form = static_cast<FormStreamFields*>(context);
295
296    if (kCFCompareEqualTo == CFStringCompare(propertyName, formDataPointerPropertyName, 0)) {
297        long formDataAsNumber = static_cast<long>(reinterpret_cast<intptr_t>(form->formData.get()));
298        return CFNumberCreate(0, kCFNumberLongType, &formDataAsNumber);
299    }
300
301    if (kCFCompareEqualTo == CFStringCompare(propertyName, formDataStreamLengthPropertyName(), 0))
302        return CFStringCreateWithFormat(0, 0, CFSTR("%llu"), form->streamLength);
303
304    return 0;
305}
306
307static void formSchedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context)
308{
309    FormStreamFields* form = static_cast<FormStreamFields*>(context);
310
311    if (form->currentStream)
312        CFReadStreamScheduleWithRunLoop(form->currentStream, runLoop, runLoopMode);
313    form->scheduledRunLoopPairs.add(SchedulePair::create(runLoop, runLoopMode));
314}
315
316static void formUnschedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context)
317{
318    FormStreamFields* form = static_cast<FormStreamFields*>(context);
319
320    if (form->currentStream)
321        CFReadStreamUnscheduleFromRunLoop(form->currentStream, runLoop, runLoopMode);
322    form->scheduledRunLoopPairs.remove(SchedulePair::create(runLoop, runLoopMode));
323}
324
325static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context)
326{
327    FormStreamFields* form = static_cast<FormStreamFields*>(context);
328
329    switch (type) {
330    case kCFStreamEventHasBytesAvailable:
331        CFReadStreamSignalEvent(form->formStream, kCFStreamEventHasBytesAvailable, 0);
332        break;
333    case kCFStreamEventErrorOccurred: {
334        CFStreamError readStreamError = CFReadStreamGetError(stream);
335        CFReadStreamSignalEvent(form->formStream, kCFStreamEventErrorOccurred, &readStreamError);
336        break;
337    }
338    case kCFStreamEventEndEncountered:
339        openNextStream(form);
340        if (!form->currentStream)
341            CFReadStreamSignalEvent(form->formStream, kCFStreamEventEndEncountered, 0);
342        break;
343    case kCFStreamEventNone:
344        LOG_ERROR("unexpected kCFStreamEventNone");
345        break;
346    case kCFStreamEventOpenCompleted:
347        LOG_ERROR("unexpected kCFStreamEventOpenCompleted");
348        break;
349    case kCFStreamEventCanAcceptBytes:
350        LOG_ERROR("unexpected kCFStreamEventCanAcceptBytes");
351        break;
352    }
353}
354
355void setHTTPBody(CFMutableURLRequestRef request, PassRefPtr<FormData> prpFormData)
356{
357    RefPtr<FormData> formData = prpFormData;
358
359    if (!formData)
360        return;
361
362    size_t count = formData->elements().size();
363
364    // Handle the common special case of one piece of form data, with no files.
365    if (count == 1 && !formData->alwaysStream()) {
366        const FormDataElement& element = formData->elements()[0];
367        if (element.m_type == FormDataElement::Type::Data) {
368            RetainPtr<CFDataRef> data = adoptCF(CFDataCreate(0, reinterpret_cast<const UInt8 *>(element.m_data.data()), element.m_data.size()));
369            CFURLRequestSetHTTPRequestBody(request, data.get());
370            return;
371        }
372    }
373
374    formData = formData->resolveBlobReferences();
375    count = formData->elements().size();
376
377    // Precompute the content length so NSURLConnection doesn't use chunked mode.
378    unsigned long long length = 0;
379    for (size_t i = 0; i < count; ++i) {
380        const FormDataElement& element = formData->elements()[i];
381        if (element.m_type == FormDataElement::Type::Data)
382            length += element.m_data.size();
383        else {
384            // 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.
385            if (element.m_fileLength != BlobDataItem::toEndOfFile) {
386                length += element.m_fileLength;
387                continue;
388            }
389            long long fileSize;
390            if (getFileSize(element.m_shouldGenerateFile ? element.m_generatedFilename : element.m_filename, fileSize))
391                length += fileSize;
392        }
393    }
394
395    // Create and set the stream.
396
397    // Pass the length along with the formData so it does not have to be recomputed.
398    FormCreationContext formContext = { formData.release(), length };
399
400    CFReadStreamCallBacksV1 callBacks = { 1, formCreate, formFinalize, 0, formOpen, 0, formRead, 0, formCanRead, formClose, formCopyProperty, 0, 0, formSchedule, formUnschedule
401    };
402    RetainPtr<CFReadStreamRef> stream = adoptCF(CFReadStreamCreate(0, static_cast<const void*>(&callBacks), &formContext));
403
404    CFURLRequestSetHTTPRequestBodyStream(request, stream.get());
405}
406
407FormData* httpBodyFromStream(CFReadStreamRef stream)
408{
409    if (!stream)
410        return 0;
411
412    // Passing the pointer as property appears to be the only way to associate a stream with FormData.
413    // A new stream is always created in CFURLRequestCopyHTTPRequestBodyStream (or -[NSURLRequest HTTPBodyStream]),
414    // so a side HashMap wouldn't work.
415    // Even the stream's context pointer is different from the one we returned from formCreate().
416
417    RetainPtr<CFNumberRef> formDataPointerAsCFNumber = adoptCF(static_cast<CFNumberRef>(CFReadStreamCopyProperty(stream, formDataPointerPropertyName)));
418    if (!formDataPointerAsCFNumber)
419        return 0;
420
421    long formDataPointerAsNumber;
422    if (!CFNumberGetValue(formDataPointerAsCFNumber.get(), kCFNumberLongType, &formDataPointerAsNumber))
423        return 0;
424
425    return reinterpret_cast<FormData*>(static_cast<intptr_t>(formDataPointerAsNumber));
426}
427
428} // namespace WebCore
429