1/* 2 * Copyright (C) 2005, 2006, 2007, 2012 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#include "config.h" 30#include "FormDataStreamCFNet.h" 31 32#include "BlobData.h" 33#include "FileSystem.h" 34#include "FormData.h" 35#include <sys/stat.h> 36#include <sys/types.h> 37#include <wtf/Assertions.h> 38#include <wtf/HashMap.h> 39#include <wtf/MainThread.h> 40#include <wtf/RetainPtr.h> 41#include <wtf/SchedulePair.h> 42#include <wtf/StdLibExtras.h> 43#include <wtf/Threading.h> 44 45#if PLATFORM(IOS) 46#include <MacErrors.h> 47#elif PLATFORM(MAC) 48#include <CoreServices/CoreServices.h> 49#endif 50 51#if PLATFORM(MAC) 52extern "C" void CFURLRequestSetHTTPRequestBody(CFMutableURLRequestRef mutableHTTPRequest, CFDataRef httpBody); 53extern "C" void CFURLRequestSetHTTPHeaderFieldValue(CFMutableURLRequestRef mutableHTTPRequest, CFStringRef httpHeaderField, CFStringRef httpHeaderFieldValue); 54extern "C" void CFURLRequestSetHTTPRequestBodyStream(CFMutableURLRequestRef req, CFReadStreamRef bodyStream); 55#elif PLATFORM(WIN) 56#include <CFNetwork/CFURLRequest.h> 57#endif 58 59typedef struct { 60 CFIndex version; /* == 1 */ 61 void *(*create)(CFReadStreamRef stream, void *info); 62 void (*finalize)(CFReadStreamRef stream, void *info); 63 CFStringRef (*copyDescription)(CFReadStreamRef stream, void *info); 64 Boolean (*open)(CFReadStreamRef stream, CFStreamError *error, Boolean *openComplete, void *info); 65 Boolean (*openCompleted)(CFReadStreamRef stream, CFStreamError *error, void *info); 66 CFIndex (*read)(CFReadStreamRef stream, UInt8 *buffer, CFIndex bufferLength, CFStreamError *error, Boolean *atEOF, void *info); 67 const UInt8 *(*getBuffer)(CFReadStreamRef stream, CFIndex maxBytesToRead, CFIndex *numBytesRead, CFStreamError *error, Boolean *atEOF, void *info); 68 Boolean (*canRead)(CFReadStreamRef stream, void *info); 69 void (*close)(CFReadStreamRef stream, void *info); 70 CFTypeRef (*copyProperty)(CFReadStreamRef stream, CFStringRef propertyName, void *info); 71 Boolean (*setProperty)(CFReadStreamRef stream, CFStringRef propertyName, CFTypeRef propertyValue, void *info); 72 void (*requestEvents)(CFReadStreamRef stream, CFOptionFlags streamEvents, void *info); 73 void (*schedule)(CFReadStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode, void *info); 74 void (*unschedule)(CFReadStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode, void *info); 75} CFReadStreamCallBacksV1; 76 77#if PLATFORM(WIN) 78#define EXTERN extern "C" __declspec(dllimport) 79#else 80#define EXTERN extern "C" 81#endif 82 83EXTERN void CFReadStreamSignalEvent(CFReadStreamRef stream, CFStreamEventType event, const void *error); 84EXTERN CFReadStreamRef CFReadStreamCreate(CFAllocatorRef alloc, const void *callbacks, void *info); 85 86namespace WebCore { 87 88static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context); 89 90static CFStringRef formDataPointerPropertyName = CFSTR("WebKitFormDataPointer"); 91 92CFStringRef formDataStreamLengthPropertyName() 93{ 94 return CFSTR("WebKitFormDataStreamLength"); 95} 96 97struct FormCreationContext { 98 RefPtr<FormData> formData; 99 unsigned long long streamLength; 100}; 101 102struct FormStreamFields { 103 RefPtr<FormData> formData; 104 SchedulePairHashSet scheduledRunLoopPairs; 105 Vector<FormDataElement> remainingElements; // in reverse order 106 CFReadStreamRef currentStream; 107#if ENABLE(BLOB) 108 long long currentStreamRangeLength; 109#endif 110 char* currentData; 111 CFReadStreamRef formStream; 112 unsigned long long streamLength; 113 unsigned long long bytesSent; 114}; 115 116static void closeCurrentStream(FormStreamFields* form) 117{ 118 if (form->currentStream) { 119 CFReadStreamClose(form->currentStream); 120 CFReadStreamSetClient(form->currentStream, kCFStreamEventNone, 0, 0); 121 CFRelease(form->currentStream); 122 form->currentStream = 0; 123#if ENABLE(BLOB) 124 form->currentStreamRangeLength = BlobDataItem::toEndOfFile; 125#endif 126 } 127 if (form->currentData) { 128 fastFree(form->currentData); 129 form->currentData = 0; 130 } 131} 132 133// 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. 134static bool advanceCurrentStream(FormStreamFields* form) 135{ 136 closeCurrentStream(form); 137 138 if (form->remainingElements.isEmpty()) 139 return true; 140 141 // Create the new stream. 142 FormDataElement& nextInput = form->remainingElements.last(); 143 144 if (nextInput.m_type == FormDataElement::data) { 145 size_t size = nextInput.m_data.size(); 146 char* data = nextInput.m_data.releaseBuffer(); 147 form->currentStream = CFReadStreamCreateWithBytesNoCopy(0, reinterpret_cast<const UInt8*>(data), size, kCFAllocatorNull); 148 form->currentData = data; 149 } else { 150#if ENABLE(BLOB) 151 // Check if the file has been changed or not if required. 152 if (isValidFileTime(nextInput.m_expectedFileModificationTime)) { 153 time_t fileModificationTime; 154 if (!getFileModificationTime(nextInput.m_filename, fileModificationTime) || fileModificationTime != static_cast<time_t>(nextInput.m_expectedFileModificationTime)) 155 return false; 156 } 157#endif 158 const String& path = nextInput.m_shouldGenerateFile ? nextInput.m_generatedFilename : nextInput.m_filename; 159 form->currentStream = CFReadStreamCreateWithFile(0, pathAsURL(path).get()); 160 if (!form->currentStream) { 161 // The file must have been removed or become unreadable. 162 return false; 163 } 164#if ENABLE(BLOB) 165 if (nextInput.m_fileStart > 0) { 166 RetainPtr<CFNumberRef> position = adoptCF(CFNumberCreate(0, kCFNumberLongLongType, &nextInput.m_fileStart)); 167 CFReadStreamSetProperty(form->currentStream, kCFStreamPropertyFileCurrentOffset, position.get()); 168 } 169 form->currentStreamRangeLength = nextInput.m_fileLength; 170#endif 171 } 172 form->remainingElements.removeLast(); 173 174 // Set up the callback. 175 CFStreamClientContext context = { 0, form, 0, 0, 0 }; 176 CFReadStreamSetClient(form->currentStream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, 177 formEventCallback, &context); 178 179 // Schedule with the current set of run loops. 180 SchedulePairHashSet::iterator end = form->scheduledRunLoopPairs.end(); 181 for (SchedulePairHashSet::iterator it = form->scheduledRunLoopPairs.begin(); it != end; ++it) 182 CFReadStreamScheduleWithRunLoop(form->currentStream, (*it)->runLoop(), (*it)->mode()); 183 184 return true; 185} 186 187static bool openNextStream(FormStreamFields* form) 188{ 189 // Skip over any streams we can't open. 190 if (!advanceCurrentStream(form)) 191 return false; 192 while (form->currentStream && !CFReadStreamOpen(form->currentStream)) { 193 if (!advanceCurrentStream(form)) 194 return false; 195 } 196 return true; 197} 198 199static void* formCreate(CFReadStreamRef stream, void* context) 200{ 201 FormCreationContext* formContext = static_cast<FormCreationContext*>(context); 202 203 FormStreamFields* newInfo = new FormStreamFields; 204 newInfo->formData = formContext->formData.release(); 205 newInfo->currentStream = 0; 206#if ENABLE(BLOB) 207 newInfo->currentStreamRangeLength = BlobDataItem::toEndOfFile; 208#endif 209 newInfo->currentData = 0; 210 newInfo->formStream = stream; // Don't retain. That would create a reference cycle. 211 newInfo->streamLength = formContext->streamLength; 212 newInfo->bytesSent = 0; 213 214 // Append in reverse order since we remove elements from the end. 215 size_t size = newInfo->formData->elements().size(); 216 newInfo->remainingElements.reserveInitialCapacity(size); 217 for (size_t i = 0; i < size; ++i) 218 newInfo->remainingElements.append(newInfo->formData->elements()[size - i - 1]); 219 220 return newInfo; 221} 222 223static void formFinishFinalizationOnMainThread(void* context) 224{ 225 OwnPtr<FormStreamFields> form = adoptPtr(static_cast<FormStreamFields*>(context)); 226 227 closeCurrentStream(form.get()); 228} 229 230static void formFinalize(CFReadStreamRef stream, void* context) 231{ 232 FormStreamFields* form = static_cast<FormStreamFields*>(context); 233 ASSERT_UNUSED(stream, form->formStream == stream); 234 235 callOnMainThread(formFinishFinalizationOnMainThread, form); 236} 237 238static Boolean formOpen(CFReadStreamRef, CFStreamError* error, Boolean* openComplete, void* context) 239{ 240 FormStreamFields* form = static_cast<FormStreamFields*>(context); 241 242 bool opened = openNextStream(form); 243 244 *openComplete = opened; 245 error->error = opened ? 0 : 246#if PLATFORM(WIN) 247 ENOENT; 248#else 249 fnfErr; 250#endif 251 return opened; 252} 253 254static CFIndex formRead(CFReadStreamRef, UInt8* buffer, CFIndex bufferLength, CFStreamError* error, Boolean* atEOF, void* context) 255{ 256 FormStreamFields* form = static_cast<FormStreamFields*>(context); 257 258 while (form->currentStream) { 259 CFIndex bytesToRead = bufferLength; 260#if ENABLE(BLOB) 261 if (form->currentStreamRangeLength != BlobDataItem::toEndOfFile && form->currentStreamRangeLength < bytesToRead) 262 bytesToRead = static_cast<CFIndex>(form->currentStreamRangeLength); 263#endif 264 CFIndex bytesRead = CFReadStreamRead(form->currentStream, buffer, bytesToRead); 265 if (bytesRead < 0) { 266 *error = CFReadStreamGetError(form->currentStream); 267 return -1; 268 } 269 if (bytesRead > 0) { 270 error->error = 0; 271 *atEOF = FALSE; 272 form->bytesSent += bytesRead; 273#if ENABLE(BLOB) 274 if (form->currentStreamRangeLength != BlobDataItem::toEndOfFile) 275 form->currentStreamRangeLength -= bytesRead; 276#endif 277 278 return bytesRead; 279 } 280 openNextStream(form); 281 } 282 283 error->error = 0; 284 *atEOF = TRUE; 285 return 0; 286} 287 288static Boolean formCanRead(CFReadStreamRef stream, void* context) 289{ 290 FormStreamFields* form = static_cast<FormStreamFields*>(context); 291 292 while (form->currentStream && CFReadStreamGetStatus(form->currentStream) == kCFStreamStatusAtEnd) 293 openNextStream(form); 294 295 if (!form->currentStream) { 296 CFReadStreamSignalEvent(stream, kCFStreamEventEndEncountered, 0); 297 return FALSE; 298 } 299 return CFReadStreamHasBytesAvailable(form->currentStream); 300} 301 302static void formClose(CFReadStreamRef, void* context) 303{ 304 FormStreamFields* form = static_cast<FormStreamFields*>(context); 305 306 closeCurrentStream(form); 307} 308 309static CFTypeRef formCopyProperty(CFReadStreamRef, CFStringRef propertyName, void *context) 310{ 311 FormStreamFields* form = static_cast<FormStreamFields*>(context); 312 313 if (kCFCompareEqualTo == CFStringCompare(propertyName, formDataPointerPropertyName, 0)) { 314 long formDataAsNumber = static_cast<long>(reinterpret_cast<intptr_t>(form->formData.get())); 315 return CFNumberCreate(0, kCFNumberLongType, &formDataAsNumber); 316 } 317 318 if (kCFCompareEqualTo == CFStringCompare(propertyName, formDataStreamLengthPropertyName(), 0)) 319 return CFStringCreateWithFormat(0, 0, CFSTR("%llu"), form->streamLength); 320 321 return 0; 322} 323 324static void formSchedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context) 325{ 326 FormStreamFields* form = static_cast<FormStreamFields*>(context); 327 328 if (form->currentStream) 329 CFReadStreamScheduleWithRunLoop(form->currentStream, runLoop, runLoopMode); 330 form->scheduledRunLoopPairs.add(SchedulePair::create(runLoop, runLoopMode)); 331} 332 333static void formUnschedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context) 334{ 335 FormStreamFields* form = static_cast<FormStreamFields*>(context); 336 337 if (form->currentStream) 338 CFReadStreamUnscheduleFromRunLoop(form->currentStream, runLoop, runLoopMode); 339 form->scheduledRunLoopPairs.remove(SchedulePair::create(runLoop, runLoopMode)); 340} 341 342static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context) 343{ 344 FormStreamFields* form = static_cast<FormStreamFields*>(context); 345 346 switch (type) { 347 case kCFStreamEventHasBytesAvailable: 348 CFReadStreamSignalEvent(form->formStream, kCFStreamEventHasBytesAvailable, 0); 349 break; 350 case kCFStreamEventErrorOccurred: { 351 CFStreamError readStreamError = CFReadStreamGetError(stream); 352 CFReadStreamSignalEvent(form->formStream, kCFStreamEventErrorOccurred, &readStreamError); 353 break; 354 } 355 case kCFStreamEventEndEncountered: 356 openNextStream(form); 357 if (!form->currentStream) 358 CFReadStreamSignalEvent(form->formStream, kCFStreamEventEndEncountered, 0); 359 break; 360 case kCFStreamEventNone: 361 LOG_ERROR("unexpected kCFStreamEventNone"); 362 break; 363 case kCFStreamEventOpenCompleted: 364 LOG_ERROR("unexpected kCFStreamEventOpenCompleted"); 365 break; 366 case kCFStreamEventCanAcceptBytes: 367 LOG_ERROR("unexpected kCFStreamEventCanAcceptBytes"); 368 break; 369 } 370} 371 372void setHTTPBody(CFMutableURLRequestRef request, PassRefPtr<FormData> prpFormData) 373{ 374 RefPtr<FormData> formData = prpFormData; 375 376 if (!formData) 377 return; 378 379 size_t count = formData->elements().size(); 380 381 // Handle the common special case of one piece of form data, with no files. 382 if (count == 1 && !formData->alwaysStream()) { 383 const FormDataElement& element = formData->elements()[0]; 384 if (element.m_type == FormDataElement::data) { 385 RetainPtr<CFDataRef> data = adoptCF(CFDataCreate(0, reinterpret_cast<const UInt8 *>(element.m_data.data()), element.m_data.size())); 386 CFURLRequestSetHTTPRequestBody(request, data.get()); 387 return; 388 } 389 } 390 391#if ENABLE(BLOB) 392 formData = formData->resolveBlobReferences(); 393 count = formData->elements().size(); 394#endif 395 396 // Precompute the content length so NSURLConnection doesn't use chunked mode. 397 unsigned long long length = 0; 398 for (size_t i = 0; i < count; ++i) { 399 const FormDataElement& element = formData->elements()[i]; 400 if (element.m_type == FormDataElement::data) 401 length += element.m_data.size(); 402 else { 403#if ENABLE(BLOB) 404 // 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. 405 if (element.m_fileLength != BlobDataItem::toEndOfFile) { 406 length += element.m_fileLength; 407 continue; 408 } 409#endif 410 long long fileSize; 411 if (getFileSize(element.m_shouldGenerateFile ? element.m_generatedFilename : element.m_filename, fileSize)) 412 length += fileSize; 413 } 414 } 415 416 // Create and set the stream. 417 418 // Pass the length along with the formData so it does not have to be recomputed. 419 FormCreationContext formContext = { formData.release(), length }; 420 421 CFReadStreamCallBacksV1 callBacks = { 1, formCreate, formFinalize, 0, formOpen, 0, formRead, 0, formCanRead, formClose, formCopyProperty, 0, 0, formSchedule, formUnschedule 422 }; 423 RetainPtr<CFReadStreamRef> stream = adoptCF(CFReadStreamCreate(0, static_cast<const void*>(&callBacks), &formContext)); 424 425 CFURLRequestSetHTTPRequestBodyStream(request, stream.get()); 426} 427 428FormData* httpBodyFromStream(CFReadStreamRef stream) 429{ 430 if (!stream) 431 return 0; 432 433 // Passing the pointer as property appears to be the only way to associate a stream with FormData. 434 // A new stream is always created in CFURLRequestCopyHTTPRequestBodyStream (or -[NSURLRequest HTTPBodyStream]), 435 // so a side HashMap wouldn't work. 436 // Even the stream's context pointer is different from the one we returned from formCreate(). 437 438 RetainPtr<CFNumberRef> formDataPointerAsCFNumber = adoptCF(static_cast<CFNumberRef>(CFReadStreamCopyProperty(stream, formDataPointerPropertyName))); 439 if (!formDataPointerAsCFNumber) 440 return 0; 441 442 long formDataPointerAsNumber; 443 if (!CFNumberGetValue(formDataPointerAsCFNumber.get(), kCFNumberLongType, &formDataPointerAsNumber)) 444 return 0; 445 446 return reinterpret_cast<FormData*>(static_cast<intptr_t>(formDataPointerAsNumber)); 447} 448 449} // namespace WebCore 450