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#if !USE(CFNETWORK)
35
36#import "BlobRegistryImpl.h"
37#import "FileSystem.h"
38#import "FormData.h"
39#import "ResourceHandle.h"
40#import "ResourceHandleClient.h"
41#import "SchedulePair.h"
42#import "WebCoreSystemInterface.h"
43#import <sys/stat.h>
44#import <sys/types.h>
45#import <wtf/Assertions.h>
46#import <wtf/HashMap.h>
47#import <wtf/MainThread.h>
48#import <wtf/StdLibExtras.h>
49#import <wtf/Threading.h>
50
51#if PLATFORM(IOS)
52#import <MacErrors.h>
53#else
54#import <CoreServices/CoreServices.h>
55#endif
56
57namespace WebCore {
58
59typedef HashMap<CFReadStreamRef, RefPtr<FormData> > StreamFormDataMap;
60static StreamFormDataMap& getStreamFormDataMap()
61{
62    DEFINE_STATIC_LOCAL(StreamFormDataMap, streamFormDataMap, ());
63    return streamFormDataMap;
64}
65
66typedef HashMap<CFReadStreamRef, RefPtr<ResourceHandle> > StreamResourceHandleMap;
67static StreamResourceHandleMap& getStreamResourceHandleMap()
68{
69    DEFINE_STATIC_LOCAL(StreamResourceHandleMap, streamResourceHandleMap, ());
70    return streamResourceHandleMap;
71}
72
73void associateStreamWithResourceHandle(NSInputStream *stream, ResourceHandle* resourceHandle)
74{
75    ASSERT(isMainThread());
76
77    ASSERT(resourceHandle);
78
79    if (!stream)
80        return;
81
82    if (!getStreamFormDataMap().contains((CFReadStreamRef)stream))
83        return;
84
85    ASSERT(!getStreamResourceHandleMap().contains((CFReadStreamRef)stream));
86    getStreamResourceHandleMap().set((CFReadStreamRef)stream, resourceHandle);
87}
88
89void disassociateStreamWithResourceHandle(NSInputStream *stream)
90{
91    ASSERT(isMainThread());
92
93    if (!stream)
94        return;
95
96    getStreamResourceHandleMap().remove((CFReadStreamRef)stream);
97}
98
99struct DidSendDataCallbackData {
100    DidSendDataCallbackData(CFReadStreamRef stream_, unsigned long long bytesSent_, unsigned long long streamLength_)
101        : stream(stream_)
102        , bytesSent(bytesSent_)
103        , streamLength(streamLength_)
104    {
105    }
106
107    CFReadStreamRef stream;
108    unsigned long long bytesSent;
109    unsigned long long streamLength;
110};
111
112static void performDidSendDataCallback(void* context)
113{
114    ASSERT(isMainThread());
115
116    DidSendDataCallbackData* data = static_cast<DidSendDataCallbackData*>(context);
117    ResourceHandle* resourceHandle = getStreamResourceHandleMap().get(data->stream).get();
118
119    if (resourceHandle && resourceHandle->client())
120        resourceHandle->client()->didSendData(resourceHandle, data->bytesSent, data->streamLength);
121
122    delete data;
123}
124
125static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context);
126
127struct FormContext {
128    FormData* formData;
129    unsigned long long streamLength;
130};
131
132struct FormStreamFields {
133    SchedulePairHashSet scheduledRunLoopPairs;
134    Vector<FormDataElement> remainingElements; // in reverse order
135    CFReadStreamRef currentStream;
136#if ENABLE(BLOB)
137    long long currentStreamRangeLength;
138#endif
139    char* currentData;
140    CFReadStreamRef formStream;
141    unsigned long long streamLength;
142    unsigned long long bytesSent;
143};
144
145static void closeCurrentStream(FormStreamFields *form)
146{
147    if (form->currentStream) {
148        CFReadStreamClose(form->currentStream);
149        CFReadStreamSetClient(form->currentStream, kCFStreamEventNone, NULL, NULL);
150        CFRelease(form->currentStream);
151        form->currentStream = NULL;
152#if ENABLE(BLOB)
153        form->currentStreamRangeLength = BlobDataItem::toEndOfFile;
154#endif
155    }
156    if (form->currentData) {
157        fastFree(form->currentData);
158        form->currentData = 0;
159    }
160}
161
162// 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.
163static bool advanceCurrentStream(FormStreamFields* form)
164{
165    closeCurrentStream(form);
166
167    if (form->remainingElements.isEmpty())
168        return true;
169
170    // Create the new stream.
171    FormDataElement& nextInput = form->remainingElements.last();
172
173    if (nextInput.m_type == FormDataElement::data) {
174        size_t size = nextInput.m_data.size();
175        char* data = nextInput.m_data.releaseBuffer();
176        form->currentStream = CFReadStreamCreateWithBytesNoCopy(0, reinterpret_cast<const UInt8*>(data), size, kCFAllocatorNull);
177        form->currentData = data;
178    } else {
179#if ENABLE(BLOB)
180        // Check if the file has been changed or not if required.
181        if (nextInput.m_expectedFileModificationTime != BlobDataItem::doNotCheckFileChange) {
182            time_t fileModificationTime;
183            if (!getFileModificationTime(nextInput.m_filename, fileModificationTime) || fileModificationTime != static_cast<time_t>(nextInput.m_expectedFileModificationTime))
184                return false;
185        }
186#endif
187        const String& path = nextInput.m_shouldGenerateFile ? nextInput.m_generatedFilename : nextInput.m_filename;
188        form->currentStream = CFReadStreamCreateWithFile(0, pathAsURL(path).get());
189        if (!form->currentStream) {
190            // The file must have been removed or become unreadable.
191            return false;
192        }
193#if ENABLE(BLOB)
194        if (nextInput.m_fileStart > 0) {
195            RetainPtr<CFNumberRef> position(AdoptCF, CFNumberCreate(0, kCFNumberLongLongType, &nextInput.m_fileStart));
196            CFReadStreamSetProperty(form->currentStream, kCFStreamPropertyFileCurrentOffset, position.get());
197        }
198        form->currentStreamRangeLength = nextInput.m_fileLength;
199#endif
200    }
201    form->remainingElements.removeLast();
202
203    // Set up the callback.
204    CFStreamClientContext context = { 0, form, NULL, NULL, NULL };
205    CFReadStreamSetClient(form->currentStream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered,
206        formEventCallback, &context);
207
208    // Schedule with the current set of run loops.
209    SchedulePairHashSet::iterator end = form->scheduledRunLoopPairs.end();
210    for (SchedulePairHashSet::iterator it = form->scheduledRunLoopPairs.begin(); it != end; ++it)
211        CFReadStreamScheduleWithRunLoop(form->currentStream, (*it)->runLoop(), (*it)->mode());
212
213    return true;
214}
215
216static bool openNextStream(FormStreamFields* form)
217{
218    // Skip over any streams we can't open.
219    if (!advanceCurrentStream(form))
220        return false;
221    while (form->currentStream && !CFReadStreamOpen(form->currentStream)) {
222        if (!advanceCurrentStream(form))
223            return false;
224    }
225    return true;
226}
227
228static void* formCreate(CFReadStreamRef stream, void* context)
229{
230    FormContext* formContext = static_cast<FormContext*>(context);
231
232    FormStreamFields* newInfo = new FormStreamFields;
233    newInfo->currentStream = NULL;
234#if ENABLE(BLOB)
235    newInfo->currentStreamRangeLength = BlobDataItem::toEndOfFile;
236#endif
237    newInfo->currentData = 0;
238    newInfo->formStream = stream; // Don't retain. That would create a reference cycle.
239    newInfo->streamLength = formContext->streamLength;
240    newInfo->bytesSent = 0;
241
242    FormData* formData = formContext->formData;
243
244    // Append in reverse order since we remove elements from the end.
245    size_t size = formData->elements().size();
246    newInfo->remainingElements.reserveInitialCapacity(size);
247    for (size_t i = 0; i < size; ++i)
248        newInfo->remainingElements.append(formData->elements()[size - i - 1]);
249
250    getStreamFormDataMap().set(stream, adoptRef(formData));
251
252    return newInfo;
253}
254
255static void formFinalize(CFReadStreamRef stream, void* context)
256{
257    FormStreamFields* form = static_cast<FormStreamFields*>(context);
258
259    getStreamFormDataMap().remove(stream);
260
261    closeCurrentStream(form);
262    delete form;
263}
264
265static Boolean formOpen(CFReadStreamRef, CFStreamError* error, Boolean* openComplete, void* context)
266{
267    FormStreamFields* form = static_cast<FormStreamFields*>(context);
268
269    bool opened = openNextStream(form);
270
271    *openComplete = opened;
272    error->error = opened ? 0 : fnfErr;
273    return opened;
274}
275
276static CFIndex formRead(CFReadStreamRef stream, UInt8* buffer, CFIndex bufferLength, CFStreamError* error, Boolean* atEOF, void* context)
277{
278    FormStreamFields* form = static_cast<FormStreamFields*>(context);
279
280    while (form->currentStream) {
281        CFIndex bytesToRead = bufferLength;
282#if ENABLE(BLOB)
283        if (form->currentStreamRangeLength != BlobDataItem::toEndOfFile && form->currentStreamRangeLength < bytesToRead)
284            bytesToRead = static_cast<CFIndex>(form->currentStreamRangeLength);
285#endif
286        CFIndex bytesRead = CFReadStreamRead(form->currentStream, buffer, bytesToRead);
287        if (bytesRead < 0) {
288            *error = CFReadStreamGetError(form->currentStream);
289            return -1;
290        }
291        if (bytesRead > 0) {
292            error->error = 0;
293            *atEOF = FALSE;
294            form->bytesSent += bytesRead;
295#if ENABLE(BLOB)
296            if (form->currentStreamRangeLength != BlobDataItem::toEndOfFile)
297                form->currentStreamRangeLength -= bytesRead;
298#endif
299
300            if (!ResourceHandle::didSendBodyDataDelegateExists()) {
301                // FIXME: Figure out how to only do this when a ResourceHandleClient is available.
302                DidSendDataCallbackData* data = new DidSendDataCallbackData(stream, form->bytesSent, form->streamLength);
303                callOnMainThread(performDidSendDataCallback, data);
304            }
305
306            return bytesRead;
307        }
308        openNextStream(form);
309    }
310
311    error->error = 0;
312    *atEOF = TRUE;
313    return 0;
314}
315
316static Boolean formCanRead(CFReadStreamRef stream, void* context)
317{
318    FormStreamFields* form = static_cast<FormStreamFields*>(context);
319
320    while (form->currentStream && CFReadStreamGetStatus(form->currentStream) == kCFStreamStatusAtEnd) {
321        openNextStream(form);
322    }
323    if (!form->currentStream) {
324        wkSignalCFReadStreamEnd(stream);
325        return FALSE;
326    }
327    return CFReadStreamHasBytesAvailable(form->currentStream);
328}
329
330static void formClose(CFReadStreamRef, void* context)
331{
332    FormStreamFields* form = static_cast<FormStreamFields*>(context);
333
334    closeCurrentStream(form);
335}
336
337static void formSchedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context)
338{
339    FormStreamFields* form = static_cast<FormStreamFields*>(context);
340
341    if (form->currentStream)
342        CFReadStreamScheduleWithRunLoop(form->currentStream, runLoop, runLoopMode);
343    form->scheduledRunLoopPairs.add(SchedulePair::create(runLoop, runLoopMode));
344}
345
346static void formUnschedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context)
347{
348    FormStreamFields* form = static_cast<FormStreamFields*>(context);
349
350    if (form->currentStream)
351        CFReadStreamUnscheduleFromRunLoop(form->currentStream, runLoop, runLoopMode);
352    form->scheduledRunLoopPairs.remove(SchedulePair::create(runLoop, runLoopMode));
353}
354
355static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context)
356{
357    FormStreamFields* form = static_cast<FormStreamFields*>(context);
358
359    switch (type) {
360    case kCFStreamEventHasBytesAvailable:
361        wkSignalCFReadStreamHasBytes(form->formStream);
362        break;
363    case kCFStreamEventErrorOccurred: {
364        CFStreamError readStreamError = CFReadStreamGetError(stream);
365        wkSignalCFReadStreamError(form->formStream, &readStreamError);
366        break;
367    }
368    case kCFStreamEventEndEncountered:
369        openNextStream(form);
370        if (!form->currentStream) {
371            wkSignalCFReadStreamEnd(form->formStream);
372        }
373        break;
374    case kCFStreamEventNone:
375        LOG_ERROR("unexpected kCFStreamEventNone");
376        break;
377    case kCFStreamEventOpenCompleted:
378        LOG_ERROR("unexpected kCFStreamEventOpenCompleted");
379        break;
380    case kCFStreamEventCanAcceptBytes:
381        LOG_ERROR("unexpected kCFStreamEventCanAcceptBytes");
382        break;
383    }
384}
385
386void setHTTPBody(NSMutableURLRequest *request, PassRefPtr<FormData> formData)
387{
388    if (!formData)
389        return;
390
391    size_t count = formData->elements().size();
392
393    // Handle the common special case of one piece of form data, with no files.
394    if (count == 1 && !formData->alwaysStream()) {
395        const FormDataElement& element = formData->elements()[0];
396        if (element.m_type == FormDataElement::data) {
397            NSData *data = [[NSData alloc] initWithBytes:element.m_data.data() length:element.m_data.size()];
398            [request setHTTPBody:data];
399            [data release];
400            return;
401        }
402    }
403
404#if ENABLE(BLOB)
405    // Check if there is a blob in the form data.
406    bool hasBlob = false;
407    for (size_t i = 0; i < count; ++i) {
408        const FormDataElement& element = formData->elements()[i];
409        if (element.m_type == FormDataElement::encodedBlob) {
410            hasBlob = true;
411            break;
412        }
413    }
414
415    // If yes, we have to resolve all the blob references and regenerate the form data with only data and file types.
416    if (hasBlob) {
417        RefPtr<FormData> newFormData = FormData::create();
418        newFormData->setAlwaysStream(formData->alwaysStream());
419        newFormData->setIdentifier(formData->identifier());
420        for (size_t i = 0; i < count; ++i) {
421            const FormDataElement& element = formData->elements()[i];
422            if (element.m_type == FormDataElement::data)
423                newFormData->appendData(element.m_data.data(), element.m_data.size());
424            else if (element.m_type == FormDataElement::encodedFile)
425                newFormData->appendFile(element.m_filename, element.m_shouldGenerateFile);
426            else {
427                ASSERT(element.m_type == FormDataElement::encodedBlob);
428                RefPtr<BlobStorageData> blobData = static_cast<BlobRegistryImpl&>(blobRegistry()).getBlobDataFromURL(KURL(ParsedURLString, element.m_blobURL));
429                if (blobData) {
430                    for (size_t j = 0; j < blobData->items().size(); ++j) {
431                        const BlobDataItem& blobItem = blobData->items()[j];
432                        if (blobItem.type == BlobDataItem::Data) {
433                            newFormData->appendData(blobItem.data->data() + static_cast<int>(blobItem.offset), static_cast<int>(blobItem.length));
434                        } else {
435                            ASSERT(blobItem.type == BlobDataItem::File);
436                            newFormData->appendFileRange(blobItem.path, blobItem.offset, blobItem.length, blobItem.expectedModificationTime);
437                        }
438                    }
439                }
440            }
441        }
442        formData = newFormData;
443        count = formData->elements().size();
444    }
445#endif
446
447    // Precompute the content length so NSURLConnection doesn't use chunked mode.
448    long long length = 0;
449    for (size_t i = 0; i < count; ++i) {
450        const FormDataElement& element = formData->elements()[i];
451        if (element.m_type == FormDataElement::data)
452            length += element.m_data.size();
453        else {
454#if ENABLE(BLOB)
455            // 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.
456            if (element.m_fileLength != BlobDataItem::toEndOfFile) {
457                length += element.m_fileLength;
458                continue;
459            }
460#endif
461            long long fileSize;
462            if (getFileSize(element.m_shouldGenerateFile ? element.m_generatedFilename : element.m_filename, fileSize))
463                length += fileSize;
464        }
465    }
466
467    // Set the length.
468    [request setValue:[NSString stringWithFormat:@"%lld", length] forHTTPHeaderField:@"Content-Length"];
469
470    // Create and set the stream.
471
472    // Pass the length along with the formData so it does not have to be recomputed.
473    FormContext formContext = { formData.releaseRef(), length };
474
475    RetainPtr<CFReadStreamRef> stream(AdoptCF, wkCreateCustomCFReadStream(formCreate, formFinalize,
476        formOpen, formRead, formCanRead, formClose, formSchedule, formUnschedule,
477        &formContext));
478    [request setHTTPBodyStream:(NSInputStream *)stream.get()];
479}
480
481FormData* httpBodyFromStream(NSInputStream* stream)
482{
483    return getStreamFormDataMap().get((CFReadStreamRef)stream).get();
484}
485
486} // namespace WebCore
487
488#endif // !USE(CFNETWORK)
489