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"
34 #import "FileSystem.h"
36 #import "ResourceHandle.h"
37 #import "ResourceHandleClient.h"
38 #import "SchedulePair.h"
39 #import "WebCoreSystemInterface.h"
42 #import <wtf/Assertions.h>
43 #import <wtf/HashMap.h>
44 #import <wtf/MainThread.h>
45 #import <wtf/StdLibExtras.h>
46 #import <wtf/Threading.h>
50 typedef HashMap<CFReadStreamRef, RefPtr<FormData> > StreamFormDataMap;
51 static StreamFormDataMap& getStreamFormDataMap()
53 DEFINE_STATIC_LOCAL(StreamFormDataMap, streamFormDataMap, ());
54 return streamFormDataMap;
57 typedef HashMap<CFReadStreamRef, RefPtr<ResourceHandle> > StreamResourceHandleMap;
58 static StreamResourceHandleMap& getStreamResourceHandleMap()
60 DEFINE_STATIC_LOCAL(StreamResourceHandleMap, streamResourceHandleMap, ());
61 return streamResourceHandleMap;
64 void associateStreamWithResourceHandle(NSInputStream *stream, ResourceHandle* resourceHandle)
66 ASSERT(isMainThread());
68 ASSERT(resourceHandle);
73 if (!getStreamFormDataMap().contains((CFReadStreamRef)stream))
76 ASSERT(!getStreamResourceHandleMap().contains((CFReadStreamRef)stream));
77 getStreamResourceHandleMap().set((CFReadStreamRef)stream, resourceHandle);
80 void disassociateStreamWithResourceHandle(NSInputStream *stream)
82 ASSERT(isMainThread());
87 getStreamResourceHandleMap().remove((CFReadStreamRef)stream);
90 struct DidSendDataCallbackData {
91 DidSendDataCallbackData(CFReadStreamRef stream_, unsigned long long bytesSent_, unsigned long long streamLength_)
93 , bytesSent(bytesSent_)
94 , streamLength(streamLength_)
98 CFReadStreamRef stream;
99 unsigned long long bytesSent;
100 unsigned long long streamLength;
103 static void performDidSendDataCallback(void* context)
105 ASSERT(isMainThread());
107 DidSendDataCallbackData* data = static_cast<DidSendDataCallbackData*>(context);
108 ResourceHandle* resourceHandle = getStreamResourceHandleMap().get(data->stream).get();
110 if (resourceHandle && resourceHandle->client())
111 resourceHandle->client()->didSendData(resourceHandle, data->bytesSent, data->streamLength);
116 static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context);
120 unsigned long long streamLength;
123 struct FormStreamFields {
124 SchedulePairHashSet scheduledRunLoopPairs;
125 Vector<FormDataElement> remainingElements; // in reverse order
126 CFReadStreamRef currentStream;
127 #if ENABLE(BLOB_SLICE)
128 long long currentStreamRangeLength;
131 CFReadStreamRef formStream;
132 unsigned long long streamLength;
133 unsigned long long bytesSent;
136 static void closeCurrentStream(FormStreamFields *form)
138 if (form->currentStream) {
139 CFReadStreamClose(form->currentStream);
140 CFReadStreamSetClient(form->currentStream, kCFStreamEventNone, NULL, NULL);
141 CFRelease(form->currentStream);
142 form->currentStream = NULL;
143 #if ENABLE(BLOB_SLICE)
144 form->currentStreamRangeLength = FormDataElement::toEndOfFile;
147 if (form->currentData) {
148 fastFree(form->currentData);
149 form->currentData = 0;
153 // 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.
154 static bool advanceCurrentStream(FormStreamFields* form)
156 closeCurrentStream(form);
158 if (form->remainingElements.isEmpty())
161 // Create the new stream.
162 FormDataElement& nextInput = form->remainingElements.last();
164 if (nextInput.m_type == FormDataElement::data) {
165 size_t size = nextInput.m_data.size();
166 char* data = nextInput.m_data.releaseBuffer();
167 form->currentStream = CFReadStreamCreateWithBytesNoCopy(0, reinterpret_cast<const UInt8*>(data), size, kCFAllocatorNull);
168 form->currentData = data;
170 #if ENABLE(BLOB_SLICE)
171 // Check if the file has been changed or not if required.
172 if (nextInput.m_expectedFileModificationTime != FormDataElement::doNotCheckFileChange) {
173 time_t fileModificationTime;
174 if (!getFileModificationTime(nextInput.m_filename, fileModificationTime) || fileModificationTime != static_cast<time_t>(nextInput.m_expectedFileModificationTime))
178 const String& path = nextInput.m_shouldGenerateFile ? nextInput.m_generatedFilename : nextInput.m_filename;
179 RetainPtr<CFStringRef> filename(AdoptCF, path.createCFString());
180 RetainPtr<CFURLRef> fileURL(AdoptCF, CFURLCreateWithFileSystemPath(0, filename.get(), kCFURLPOSIXPathStyle, FALSE));
181 form->currentStream = CFReadStreamCreateWithFile(0, fileURL.get());
182 if (!form->currentStream) {
183 // The file must have been removed or become unreadable.
186 #if ENABLE(BLOB_SLICE)
187 if (nextInput.m_fileStart > 0) {
188 CFNumberRef position = CFNumberCreate(0, kCFNumberLongLongType, &nextInput.m_fileStart);
189 CFReadStreamSetProperty(form->currentStream, kCFStreamPropertyFileCurrentOffset, position);
191 form->currentStreamRangeLength = nextInput.m_fileLength;
194 form->remainingElements.removeLast();
196 // Set up the callback.
197 CFStreamClientContext context = { 0, form, NULL, NULL, NULL };
198 CFReadStreamSetClient(form->currentStream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered,
199 formEventCallback, &context);
201 // Schedule with the current set of run loops.
202 SchedulePairHashSet::iterator end = form->scheduledRunLoopPairs.end();
203 for (SchedulePairHashSet::iterator it = form->scheduledRunLoopPairs.begin(); it != end; ++it)
204 CFReadStreamScheduleWithRunLoop(form->currentStream, (*it)->runLoop(), (*it)->mode());
209 static bool openNextStream(FormStreamFields* form)
211 // Skip over any streams we can't open.
212 if (!advanceCurrentStream(form))
214 while (form->currentStream && !CFReadStreamOpen(form->currentStream)) {
215 if (!advanceCurrentStream(form))
221 static void* formCreate(CFReadStreamRef stream, void* context)
223 FormContext* formContext = static_cast<FormContext*>(context);
225 FormStreamFields* newInfo = new FormStreamFields;
226 newInfo->currentStream = NULL;
227 #if ENABLE(BLOB_SLICE)
228 newInfo->currentStreamRangeLength = FormDataElement::toEndOfFile;
230 newInfo->currentData = 0;
231 newInfo->formStream = stream; // Don't retain. That would create a reference cycle.
232 newInfo->streamLength = formContext->streamLength;
233 newInfo->bytesSent = 0;
235 FormData* formData = formContext->formData;
237 // Append in reverse order since we remove elements from the end.
238 size_t size = formData->elements().size();
239 newInfo->remainingElements.reserveInitialCapacity(size);
240 for (size_t i = 0; i < size; ++i)
241 newInfo->remainingElements.append(formData->elements()[size - i - 1]);
243 getStreamFormDataMap().set(stream, adoptRef(formData));
248 static void formFinalize(CFReadStreamRef stream, void* context)
250 FormStreamFields* form = static_cast<FormStreamFields*>(context);
252 getStreamFormDataMap().remove(stream);
254 closeCurrentStream(form);
258 static Boolean formOpen(CFReadStreamRef, CFStreamError* error, Boolean* openComplete, void* context)
260 FormStreamFields* form = static_cast<FormStreamFields*>(context);
262 bool opened = openNextStream(form);
264 *openComplete = opened;
265 error->error = opened ? 0 : fnfErr;
269 static CFIndex formRead(CFReadStreamRef stream, UInt8* buffer, CFIndex bufferLength, CFStreamError* error, Boolean* atEOF, void* context)
271 FormStreamFields* form = static_cast<FormStreamFields*>(context);
273 while (form->currentStream) {
274 CFIndex bytesToRead = bufferLength;
275 #if ENABLE(BLOB_SLICE)
276 if (form->currentStreamRangeLength != FormDataElement::toEndOfFile && form->currentStreamRangeLength < bytesToRead)
277 bytesToRead = static_cast<CFIndex>(form->currentStreamRangeLength);
279 CFIndex bytesRead = CFReadStreamRead(form->currentStream, buffer, bytesToRead);
281 *error = CFReadStreamGetError(form->currentStream);
287 form->bytesSent += bytesRead;
288 #if ENABLE(BLOB_SLICE)
289 if (form->currentStreamRangeLength != FormDataElement::toEndOfFile)
290 form->currentStreamRangeLength -= bytesRead;
293 if (!ResourceHandle::didSendBodyDataDelegateExists()) {
294 // FIXME: Figure out how to only do this when a ResourceHandleClient is available.
295 DidSendDataCallbackData* data = new DidSendDataCallbackData(stream, form->bytesSent, form->streamLength);
296 callOnMainThread(performDidSendDataCallback, data);
301 openNextStream(form);
309 static Boolean formCanRead(CFReadStreamRef stream, void* context)
311 FormStreamFields* form = static_cast<FormStreamFields*>(context);
313 while (form->currentStream && CFReadStreamGetStatus(form->currentStream) == kCFStreamStatusAtEnd) {
314 openNextStream(form);
316 if (!form->currentStream) {
317 wkSignalCFReadStreamEnd(stream);
320 return CFReadStreamHasBytesAvailable(form->currentStream);
323 static void formClose(CFReadStreamRef, void* context)
325 FormStreamFields* form = static_cast<FormStreamFields*>(context);
327 closeCurrentStream(form);
330 static void formSchedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context)
332 FormStreamFields* form = static_cast<FormStreamFields*>(context);
334 if (form->currentStream)
335 CFReadStreamScheduleWithRunLoop(form->currentStream, runLoop, runLoopMode);
336 form->scheduledRunLoopPairs.add(SchedulePair::create(runLoop, runLoopMode));
339 static void formUnschedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context)
341 FormStreamFields* form = static_cast<FormStreamFields*>(context);
343 if (form->currentStream)
344 CFReadStreamUnscheduleFromRunLoop(form->currentStream, runLoop, runLoopMode);
345 form->scheduledRunLoopPairs.remove(SchedulePair::create(runLoop, runLoopMode));
348 static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context)
350 FormStreamFields* form = static_cast<FormStreamFields*>(context);
353 case kCFStreamEventHasBytesAvailable:
354 wkSignalCFReadStreamHasBytes(form->formStream);
356 case kCFStreamEventErrorOccurred: {
357 CFStreamError readStreamError = CFReadStreamGetError(stream);
358 wkSignalCFReadStreamError(form->formStream, &readStreamError);
361 case kCFStreamEventEndEncountered:
362 openNextStream(form);
363 if (!form->currentStream) {
364 wkSignalCFReadStreamEnd(form->formStream);
367 case kCFStreamEventNone:
368 LOG_ERROR("unexpected kCFStreamEventNone");
370 case kCFStreamEventOpenCompleted:
371 LOG_ERROR("unexpected kCFStreamEventOpenCompleted");
373 case kCFStreamEventCanAcceptBytes:
374 LOG_ERROR("unexpected kCFStreamEventCanAcceptBytes");
379 void setHTTPBody(NSMutableURLRequest *request, PassRefPtr<FormData> formData)
384 size_t count = formData->elements().size();
386 // Handle the common special case of one piece of form data, with no files.
387 if (count == 1 && !formData->alwaysStream()) {
388 const FormDataElement& element = formData->elements()[0];
389 if (element.m_type == FormDataElement::data) {
390 NSData *data = [[NSData alloc] initWithBytes:element.m_data.data() length:element.m_data.size()];
391 [request setHTTPBody:data];
397 // Precompute the content length so NSURLConnection doesn't use chunked mode.
398 long long length = 0;
399 for (size_t i = 0; i < count; ++i) {
400 const FormDataElement& element = formData->elements()[i];
401 if (element.m_type == FormDataElement::data)
402 length += element.m_data.size();
404 #if ENABLE(BLOB_SLICE)
405 // 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.
406 if (element.m_fileLength != FormDataElement::toEndOfFile) {
407 length += element.m_fileLength;
412 if (getFileSize(element.m_shouldGenerateFile ? element.m_generatedFilename : element.m_filename, fileSize))
418 [request setValue:[NSString stringWithFormat:@"%lld", length] forHTTPHeaderField:@"Content-Length"];
420 // Create and set the stream.
422 // Pass the length along with the formData so it does not have to be recomputed.
423 FormContext formContext = { formData.releaseRef(), length };
425 RetainPtr<CFReadStreamRef> stream(AdoptCF, wkCreateCustomCFReadStream(formCreate, formFinalize,
426 formOpen, formRead, formCanRead, formClose, formSchedule, formUnschedule,
428 [request setHTTPBodyStream:(NSInputStream *)stream.get()];
431 FormData* httpBodyFromStream(NSInputStream* stream)
433 return getStreamFormDataMap().get((CFReadStreamRef)stream).get();
436 } // namespace WebCore