/* * Copyright (C) 2005, 2006, 2007 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* originally written by Becky Willrich, additional code by Darin Adler */ #include "config.h" #include "FormDataStreamCFNet.h" #include "CString.h" #include "FileSystem.h" #include "FormData.h" #include #include #include #include #include #include #include #define USE_V1_CFSTREAM_CALLBACKS #ifdef USE_V1_CFSTREAM_CALLBACKS typedef CFReadStreamCallBacksV1 WCReadStreamCallBacks; #else typedef CFReadStreamCallBacks WCReadStreamCallBacks; #endif namespace WebCore { static HashMap >& getStreamFormDatas() { static HashMap > streamFormDatas; return streamFormDatas; } static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context); struct FormStreamFields { CFMutableSetRef scheduledRunLoopPairs; Vector remainingElements; // in reverse order CFReadStreamRef currentStream; char* currentData; CFReadStreamRef formStream; }; struct SchedulePair { CFRunLoopRef runLoop; CFStringRef mode; }; static const void* pairRetain(CFAllocatorRef alloc, const void* value) { const SchedulePair* pair = static_cast(value); SchedulePair* result = new SchedulePair; CFRetain(pair->runLoop); result->runLoop = pair->runLoop; result->mode = CFStringCreateCopy(alloc, pair->mode); return result; } static void pairRelease(CFAllocatorRef alloc, const void* value) { const SchedulePair* pair = static_cast(value); CFRelease(pair->runLoop); CFRelease(pair->mode); delete pair; } static Boolean pairEqual(const void* a, const void* b) { const SchedulePair* pairA = static_cast(a); const SchedulePair* pairB = static_cast(b); return pairA->runLoop == pairB->runLoop && CFEqual(pairA->mode, pairB->mode); } static CFHashCode pairHash(const void* value) { const SchedulePair* pair = static_cast(value); return (CFHashCode)pair->runLoop ^ CFHash(pair->mode); } static void closeCurrentStream(FormStreamFields *form) { if (form->currentStream) { CFReadStreamClose(form->currentStream); CFReadStreamSetClient(form->currentStream, kCFStreamEventNone, NULL, NULL); CFRelease(form->currentStream); form->currentStream = NULL; } if (form->currentData) { fastFree(form->currentData); form->currentData = 0; } } static void scheduleWithPair(const void* value, void* context) { const SchedulePair* pair = static_cast(value); CFReadStreamRef stream = (CFReadStreamRef)context; CFReadStreamScheduleWithRunLoop(stream, pair->runLoop, pair->mode); } static void advanceCurrentStream(FormStreamFields *form) { closeCurrentStream(form); if (form->remainingElements.isEmpty()) return; // Create the new stream. FormDataElement& nextInput = form->remainingElements.last(); if (nextInput.m_type == FormDataElement::data) { size_t size = nextInput.m_data.size(); char* data = nextInput.m_data.releaseBuffer(); form->currentStream = CFReadStreamCreateWithBytesNoCopy(0, reinterpret_cast(data), size, kCFAllocatorNull); form->currentData = data; } else { CFStringRef filename = nextInput.m_filename.createCFString(); #if PLATFORM(WIN) CFURLRef fileURL = CFURLCreateWithFileSystemPath(0, filename, kCFURLWindowsPathStyle, FALSE); #else CFURLRef fileURL = CFURLCreateWithFileSystemPath(0, filename, kCFURLPOSIXPathStyle, FALSE); #endif CFRelease(filename); form->currentStream = CFReadStreamCreateWithFile(0, fileURL); CFRelease(fileURL); } form->remainingElements.removeLast(); // Set up the callback. CFStreamClientContext context = { 0, form, NULL, NULL, NULL }; CFReadStreamSetClient(form->currentStream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, formEventCallback, &context); // Schedule with the current set of run loops. CFSetApplyFunction(form->scheduledRunLoopPairs, scheduleWithPair, form->currentStream); } static void openNextStream(FormStreamFields* form) { // Skip over any streams we can't open. // For some purposes we might want to return an error, but the current CFURLConnection // can't really do anything useful with an error at this point, so this is better. advanceCurrentStream(form); while (form->currentStream && !CFReadStreamOpen(form->currentStream)) advanceCurrentStream(form); } static void* formCreate(CFReadStreamRef stream, void* context) { FormData* formData = static_cast(context); CFSetCallBacks runLoopAndModeCallBacks = { 0, pairRetain, pairRelease, NULL, pairEqual, pairHash }; FormStreamFields* newInfo = new FormStreamFields; newInfo->scheduledRunLoopPairs = CFSetCreateMutable(0, 0, &runLoopAndModeCallBacks); newInfo->currentStream = NULL; newInfo->currentData = 0; newInfo->formStream = stream; // Don't retain. That would create a reference cycle. // Append in reverse order since we remove elements from the end. size_t size = formData->elements().size(); newInfo->remainingElements.reserveCapacity(size); for (size_t i = 0; i < size; ++i) newInfo->remainingElements.append(formData->elements()[size - i - 1]); getStreamFormDatas().set(stream, adoptRef(formData)); return newInfo; } static void formFinalize(CFReadStreamRef stream, void* context) { FormStreamFields* form = static_cast(context); getStreamFormDatas().remove(stream); closeCurrentStream(form); CFRelease(form->scheduledRunLoopPairs); delete form; } static Boolean formOpen(CFReadStreamRef stream, CFStreamError* error, Boolean* openComplete, void* context) { FormStreamFields* form = static_cast(context); openNextStream(form); *openComplete = TRUE; error->error = 0; return TRUE; } static CFIndex formRead(CFReadStreamRef stream, UInt8* buffer, CFIndex bufferLength, CFStreamError* error, Boolean* atEOF, void* context) { FormStreamFields* form = static_cast(context); while (form->currentStream) { CFIndex bytesRead = CFReadStreamRead(form->currentStream, buffer, bufferLength); if (bytesRead < 0) { *error = CFReadStreamGetError(form->currentStream); return -1; } if (bytesRead > 0) { error->error = 0; *atEOF = FALSE; return bytesRead; } openNextStream(form); } error->error = 0; *atEOF = TRUE; return 0; } static Boolean formCanRead(CFReadStreamRef stream, void* context) { FormStreamFields* form = static_cast(context); while (form->currentStream && CFReadStreamGetStatus(form->currentStream) == kCFStreamStatusAtEnd) { openNextStream(form); } if (!form->currentStream) { CFReadStreamSignalEvent(stream, kCFStreamEventEndEncountered, 0); return FALSE; } return CFReadStreamHasBytesAvailable(form->currentStream); } static void formClose(CFReadStreamRef stream, void* context) { FormStreamFields* form = static_cast(context); closeCurrentStream(form); } static void formSchedule(CFReadStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context) { FormStreamFields* form = static_cast(context); if (form->currentStream) CFReadStreamScheduleWithRunLoop(form->currentStream, runLoop, runLoopMode); SchedulePair pair = { runLoop, runLoopMode }; CFSetAddValue(form->scheduledRunLoopPairs, &pair); } static void formUnschedule(CFReadStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context) { FormStreamFields* form = static_cast(context); if (form->currentStream) CFReadStreamUnscheduleFromRunLoop(form->currentStream, runLoop, runLoopMode); SchedulePair pair = { runLoop, runLoopMode }; CFSetRemoveValue(form->scheduledRunLoopPairs, &pair); } static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context) { FormStreamFields* form = static_cast(context); switch (type) { case kCFStreamEventHasBytesAvailable: CFReadStreamSignalEvent(form->formStream, kCFStreamEventHasBytesAvailable, 0); break; case kCFStreamEventErrorOccurred: { CFStreamError readStreamError = CFReadStreamGetError(stream); CFReadStreamSignalEvent(form->formStream, kCFStreamEventErrorOccurred, &readStreamError); break; } case kCFStreamEventEndEncountered: openNextStream(form); if (!form->currentStream) CFReadStreamSignalEvent(form->formStream, kCFStreamEventEndEncountered, 0); break; case kCFStreamEventNone: LOG_ERROR("unexpected kCFStreamEventNone"); break; case kCFStreamEventOpenCompleted: LOG_ERROR("unexpected kCFStreamEventOpenCompleted"); break; case kCFStreamEventCanAcceptBytes: LOG_ERROR("unexpected kCFStreamEventCanAcceptBytes"); break; } } void setHTTPBody(CFMutableURLRequestRef request, PassRefPtr formData) { if (!formData) { if (wkCanAccessCFURLRequestHTTPBodyParts()) wkCFURLRequestSetHTTPRequestBodyParts(request, 0); return; } size_t count = formData->elements().size(); if (count == 0) return; // Handle the common special case of one piece of form data, with no files. if (count == 1) { const FormDataElement& element = formData->elements()[0]; if (element.m_type == FormDataElement::data) { CFDataRef data = CFDataCreate(0, reinterpret_cast(element.m_data.data()), element.m_data.size()); CFURLRequestSetHTTPRequestBody(request, data); CFRelease(data); return; } } if (wkCanAccessCFURLRequestHTTPBodyParts()) { RetainPtr array(AdoptCF, CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks)); for (size_t i = 0; i < count; ++i) { const FormDataElement& element = formData->elements()[i]; if (element.m_type == FormDataElement::data) { RetainPtr data(AdoptCF, CFDataCreate(0, reinterpret_cast(element.m_data.data()), element.m_data.size())); CFArrayAppendValue(array.get(), data.get()); } else { RetainPtr filename(AdoptCF, element.m_filename.createCFString()); CFArrayAppendValue(array.get(), filename.get()); } } wkCFURLRequestSetHTTPRequestBodyParts(request, array.get()); return; } // Precompute the content length so CFURLConnection doesn't use chunked mode. bool haveLength = true; long long length = 0; for (size_t i = 0; i < count; ++i) { const FormDataElement& element = formData->elements()[i]; if (element.m_type == FormDataElement::data) length += element.m_data.size(); else { long long size; if (getFileSize(element.m_filename, size)) length += size; else haveLength = false; } } if (haveLength) { CFStringRef lengthStr = CFStringCreateWithFormat(0, 0, CFSTR("%lld"), length); CFURLRequestSetHTTPHeaderFieldValue(request, CFSTR("Content-Length"), lengthStr); CFRelease(lengthStr); } static WCReadStreamCallBacks formDataStreamCallbacks = { 1, formCreate, formFinalize, 0, formOpen, 0, formRead, 0, formCanRead, formClose, 0, 0, 0, formSchedule, formUnschedule }; CFReadStreamRef stream = CFReadStreamCreate(0, (CFReadStreamCallBacks *)&formDataStreamCallbacks, formData.releaseRef()); CFURLRequestSetHTTPRequestBodyStream(request, stream); CFRelease(stream); } PassRefPtr httpBodyFromRequest(CFURLRequestRef request) { if (RetainPtr bodyData = CFURLRequestCopyHTTPRequestBody(request)) return FormData::create(CFDataGetBytePtr(bodyData.get()), CFDataGetLength(bodyData.get())); if (wkCanAccessCFURLRequestHTTPBodyParts()) { if (RetainPtr bodyParts = wkCFURLRequestCopyHTTPRequestBodyParts(request)) { RefPtr formData = FormData::create(); CFIndex count = CFArrayGetCount(bodyParts.get()); for (CFIndex i = 0; i < count; i++) { CFTypeRef bodyPart = CFArrayGetValueAtIndex(bodyParts.get(), i); CFTypeID typeID = CFGetTypeID(bodyPart); if (typeID == CFStringGetTypeID()) { String filename = (CFStringRef)bodyPart; formData->appendFile(filename); } else if (typeID == CFDataGetTypeID()) { CFDataRef data = (CFDataRef)bodyPart; formData->appendData(CFDataGetBytePtr(data), CFDataGetLength(data)); } else ASSERT_NOT_REACHED(); } return formData.release(); } } else { if (RetainPtr bodyStream = CFURLRequestCopyHTTPRequestBodyStream(request)) return getStreamFormDatas().get(bodyStream.get()); } // FIXME: what to do about arbitrary body streams? return 0; } }