2 * Copyright (C) 2005, 2006, 2008 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
29 /* originally written by Becky Willrich, additional code by Darin Adler */
32 #import "FormDataStreamMac.h"
36 #import "BlobRegistryImpl.h"
37 #import "FileSystem.h"
39 #import "ResourceHandle.h"
40 #import "ResourceHandleClient.h"
41 #import "SchedulePair.h"
42 #import "WebCoreSystemInterface.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>
54 #import <CoreServices/CoreServices.h>
59 typedef HashMap<CFReadStreamRef, RefPtr<FormData> > StreamFormDataMap;
60 static StreamFormDataMap& getStreamFormDataMap()
62 DEFINE_STATIC_LOCAL(StreamFormDataMap, streamFormDataMap, ());
63 return streamFormDataMap;
66 typedef HashMap<CFReadStreamRef, RefPtr<ResourceHandle> > StreamResourceHandleMap;
67 static StreamResourceHandleMap& getStreamResourceHandleMap()
69 DEFINE_STATIC_LOCAL(StreamResourceHandleMap, streamResourceHandleMap, ());
70 return streamResourceHandleMap;
73 void associateStreamWithResourceHandle(NSInputStream *stream, ResourceHandle* resourceHandle)
75 ASSERT(isMainThread());
77 ASSERT(resourceHandle);
82 if (!getStreamFormDataMap().contains((CFReadStreamRef)stream))
85 ASSERT(!getStreamResourceHandleMap().contains((CFReadStreamRef)stream));
86 getStreamResourceHandleMap().set((CFReadStreamRef)stream, resourceHandle);
89 void disassociateStreamWithResourceHandle(NSInputStream *stream)
91 ASSERT(isMainThread());
96 getStreamResourceHandleMap().remove((CFReadStreamRef)stream);
99 struct DidSendDataCallbackData {
100 DidSendDataCallbackData(CFReadStreamRef stream_, unsigned long long bytesSent_, unsigned long long streamLength_)
102 , bytesSent(bytesSent_)
103 , streamLength(streamLength_)
107 CFReadStreamRef stream;
108 unsigned long long bytesSent;
109 unsigned long long streamLength;
112 static void performDidSendDataCallback(void* context)
114 ASSERT(isMainThread());
116 DidSendDataCallbackData* data = static_cast<DidSendDataCallbackData*>(context);
117 ResourceHandle* resourceHandle = getStreamResourceHandleMap().get(data->stream).get();
119 if (resourceHandle && resourceHandle->client())
120 resourceHandle->client()->didSendData(resourceHandle, data->bytesSent, data->streamLength);
125 static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context);
129 unsigned long long streamLength;
132 struct FormStreamFields {
133 SchedulePairHashSet scheduledRunLoopPairs;
134 Vector<FormDataElement> remainingElements; // in reverse order
135 CFReadStreamRef currentStream;
137 long long currentStreamRangeLength;
140 CFReadStreamRef formStream;
141 unsigned long long streamLength;
142 unsigned long long bytesSent;
145 static void closeCurrentStream(FormStreamFields *form)
147 if (form->currentStream) {
148 CFReadStreamClose(form->currentStream);
149 CFReadStreamSetClient(form->currentStream, kCFStreamEventNone, NULL, NULL);
150 CFRelease(form->currentStream);
151 form->currentStream = NULL;
153 form->currentStreamRangeLength = BlobDataItem::toEndOfFile;
156 if (form->currentData) {
157 fastFree(form->currentData);
158 form->currentData = 0;
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.
163 static bool advanceCurrentStream(FormStreamFields* form)
165 closeCurrentStream(form);
167 if (form->remainingElements.isEmpty())
170 // Create the new stream.
171 FormDataElement& nextInput = form->remainingElements.last();
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;
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))
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.
194 if (nextInput.m_fileStart > 0) {
195 CFNumberRef position = CFNumberCreate(0, kCFNumberLongLongType, &nextInput.m_fileStart);
196 CFReadStreamSetProperty(form->currentStream, kCFStreamPropertyFileCurrentOffset, position);
198 form->currentStreamRangeLength = nextInput.m_fileLength;
201 form->remainingElements.removeLast();
203 // Set up the callback.
204 CFStreamClientContext context = { 0, form, NULL, NULL, NULL };
205 CFReadStreamSetClient(form->currentStream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered,
206 formEventCallback, &context);
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());
216 static bool openNextStream(FormStreamFields* form)
218 // Skip over any streams we can't open.
219 if (!advanceCurrentStream(form))
221 while (form->currentStream && !CFReadStreamOpen(form->currentStream)) {
222 if (!advanceCurrentStream(form))
228 static void* formCreate(CFReadStreamRef stream, void* context)
230 FormContext* formContext = static_cast<FormContext*>(context);
232 FormStreamFields* newInfo = new FormStreamFields;
233 newInfo->currentStream = NULL;
235 newInfo->currentStreamRangeLength = BlobDataItem::toEndOfFile;
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;
242 FormData* formData = formContext->formData;
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]);
250 getStreamFormDataMap().set(stream, adoptRef(formData));
255 static void formFinalize(CFReadStreamRef stream, void* context)
257 FormStreamFields* form = static_cast<FormStreamFields*>(context);
259 getStreamFormDataMap().remove(stream);
261 closeCurrentStream(form);
265 static Boolean formOpen(CFReadStreamRef, CFStreamError* error, Boolean* openComplete, void* context)
267 FormStreamFields* form = static_cast<FormStreamFields*>(context);
269 bool opened = openNextStream(form);
271 *openComplete = opened;
272 error->error = opened ? 0 : fnfErr;
276 static CFIndex formRead(CFReadStreamRef stream, UInt8* buffer, CFIndex bufferLength, CFStreamError* error, Boolean* atEOF, void* context)
278 FormStreamFields* form = static_cast<FormStreamFields*>(context);
280 while (form->currentStream) {
281 CFIndex bytesToRead = bufferLength;
283 if (form->currentStreamRangeLength != BlobDataItem::toEndOfFile && form->currentStreamRangeLength < bytesToRead)
284 bytesToRead = static_cast<CFIndex>(form->currentStreamRangeLength);
286 CFIndex bytesRead = CFReadStreamRead(form->currentStream, buffer, bytesToRead);
288 *error = CFReadStreamGetError(form->currentStream);
294 form->bytesSent += bytesRead;
296 if (form->currentStreamRangeLength != BlobDataItem::toEndOfFile)
297 form->currentStreamRangeLength -= bytesRead;
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);
308 openNextStream(form);
316 static Boolean formCanRead(CFReadStreamRef stream, void* context)
318 FormStreamFields* form = static_cast<FormStreamFields*>(context);
320 while (form->currentStream && CFReadStreamGetStatus(form->currentStream) == kCFStreamStatusAtEnd) {
321 openNextStream(form);
323 if (!form->currentStream) {
324 wkSignalCFReadStreamEnd(stream);
327 return CFReadStreamHasBytesAvailable(form->currentStream);
330 static void formClose(CFReadStreamRef, void* context)
332 FormStreamFields* form = static_cast<FormStreamFields*>(context);
334 closeCurrentStream(form);
337 static void formSchedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context)
339 FormStreamFields* form = static_cast<FormStreamFields*>(context);
341 if (form->currentStream)
342 CFReadStreamScheduleWithRunLoop(form->currentStream, runLoop, runLoopMode);
343 form->scheduledRunLoopPairs.add(SchedulePair::create(runLoop, runLoopMode));
346 static void formUnschedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context)
348 FormStreamFields* form = static_cast<FormStreamFields*>(context);
350 if (form->currentStream)
351 CFReadStreamUnscheduleFromRunLoop(form->currentStream, runLoop, runLoopMode);
352 form->scheduledRunLoopPairs.remove(SchedulePair::create(runLoop, runLoopMode));
355 static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context)
357 FormStreamFields* form = static_cast<FormStreamFields*>(context);
360 case kCFStreamEventHasBytesAvailable:
361 wkSignalCFReadStreamHasBytes(form->formStream);
363 case kCFStreamEventErrorOccurred: {
364 CFStreamError readStreamError = CFReadStreamGetError(stream);
365 wkSignalCFReadStreamError(form->formStream, &readStreamError);
368 case kCFStreamEventEndEncountered:
369 openNextStream(form);
370 if (!form->currentStream) {
371 wkSignalCFReadStreamEnd(form->formStream);
374 case kCFStreamEventNone:
375 LOG_ERROR("unexpected kCFStreamEventNone");
377 case kCFStreamEventOpenCompleted:
378 LOG_ERROR("unexpected kCFStreamEventOpenCompleted");
380 case kCFStreamEventCanAcceptBytes:
381 LOG_ERROR("unexpected kCFStreamEventCanAcceptBytes");
386 void setHTTPBody(NSMutableURLRequest *request, PassRefPtr<FormData> formData)
391 size_t count = formData->elements().size();
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];
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) {
415 // If yes, we have to resolve all the blob references and regenerate the form data with only data and file types.
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);
427 ASSERT(element.m_type == FormDataElement::encodedBlob);
428 RefPtr<BlobStorageData> blobData = static_cast<BlobRegistryImpl&>(blobRegistry()).getBlobDataFromURL(KURL(ParsedURLString, element.m_blobURL));
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));
435 ASSERT(blobItem.type == BlobDataItem::File);
436 newFormData->appendFileRange(blobItem.path, blobItem.offset, blobItem.length, blobItem.expectedModificationTime);
442 formData = newFormData;
443 count = formData->elements().size();
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();
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;
462 if (getFileSize(element.m_shouldGenerateFile ? element.m_generatedFilename : element.m_filename, fileSize))
468 [request setValue:[NSString stringWithFormat:@"%lld", length] forHTTPHeaderField:@"Content-Length"];
470 // Create and set the stream.
472 // Pass the length along with the formData so it does not have to be recomputed.
473 FormContext formContext = { formData.releaseRef(), length };
475 RetainPtr<CFReadStreamRef> stream(AdoptCF, wkCreateCustomCFReadStream(formCreate, formFinalize,
476 formOpen, formRead, formCanRead, formClose, formSchedule, formUnschedule,
478 [request setHTTPBodyStream:(NSInputStream *)stream.get()];
481 FormData* httpBodyFromStream(NSInputStream* stream)
483 return getStreamFormDataMap().get((CFReadStreamRef)stream).get();
486 } // namespace WebCore
488 #endif // !USE(CFNETWORK)