blob: c2416a1fa4871db9fee3ec0fa3b27697e9eba46a [file] [log] [blame]
/*
* 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 <CFNetwork/CFURLRequestPriv.h>
#include <CoreFoundation/CFStreamAbstract.h>
#include <WebKitSystemInterface/WebKitSystemInterface.h>
#include <sys/types.h>
#include <wtf/Assertions.h>
#include <wtf/HashMap.h>
#include <wtf/RetainPtr.h>
#define USE_V1_CFSTREAM_CALLBACKS
#ifdef USE_V1_CFSTREAM_CALLBACKS
typedef CFReadStreamCallBacksV1 WCReadStreamCallBacks;
#else
typedef CFReadStreamCallBacks WCReadStreamCallBacks;
#endif
namespace WebCore {
static HashMap<CFReadStreamRef, RefPtr<FormData> >& getStreamFormDatas()
{
static HashMap<CFReadStreamRef, RefPtr<FormData> > streamFormDatas;
return streamFormDatas;
}
static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context);
struct FormStreamFields {
CFMutableSetRef scheduledRunLoopPairs;
Vector<FormDataElement> 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<const SchedulePair*>(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<const SchedulePair*>(value);
CFRelease(pair->runLoop);
CFRelease(pair->mode);
delete pair;
}
static Boolean pairEqual(const void* a, const void* b)
{
const SchedulePair* pairA = static_cast<const SchedulePair*>(a);
const SchedulePair* pairB = static_cast<const SchedulePair*>(b);
return pairA->runLoop == pairB->runLoop && CFEqual(pairA->mode, pairB->mode);
}
static CFHashCode pairHash(const void* value)
{
const SchedulePair* pair = static_cast<const SchedulePair*>(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<const SchedulePair*>(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<const UInt8*>(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<FormData*>(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<FormStreamFields*>(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<FormStreamFields*>(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<FormStreamFields*>(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<FormStreamFields*>(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<FormStreamFields*>(context);
closeCurrentStream(form);
}
static void formSchedule(CFReadStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context)
{
FormStreamFields* form = static_cast<FormStreamFields*>(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<FormStreamFields*>(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<FormStreamFields*>(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> 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<const UInt8 *>(element.m_data.data()), element.m_data.size());
CFURLRequestSetHTTPRequestBody(request, data);
CFRelease(data);
return;
}
}
if (wkCanAccessCFURLRequestHTTPBodyParts()) {
RetainPtr<CFMutableArrayRef> 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<CFDataRef> data(AdoptCF, CFDataCreate(0, reinterpret_cast<const UInt8*>(element.m_data.data()), element.m_data.size()));
CFArrayAppendValue(array.get(), data.get());
} else {
RetainPtr<CFStringRef> 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<FormData> httpBodyFromRequest(CFURLRequestRef request)
{
if (RetainPtr<CFDataRef> bodyData = CFURLRequestCopyHTTPRequestBody(request))
return new FormData(CFDataGetBytePtr(bodyData.get()), CFDataGetLength(bodyData.get()));
if (wkCanAccessCFURLRequestHTTPBodyParts()) {
if (RetainPtr<CFArrayRef> bodyParts = wkCFURLRequestCopyHTTPRequestBodyParts(request)) {
RefPtr<FormData> formData = new FormData();
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<CFReadStreamRef> bodyStream = CFURLRequestCopyHTTPRequestBodyStream(request))
return getStreamFormDatas().get(bodyStream.get());
}
// FIXME: what to do about arbitrary body streams?
return 0;
}
}