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 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(COCOA) 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 long long currentStreamRangeLength; 108 MallocPtr<char> currentData; 109 CFReadStreamRef formStream; 110 unsigned long long streamLength; 111 unsigned long long bytesSent; 112}; 113 114static void closeCurrentStream(FormStreamFields* form) 115{ 116 if (form->currentStream) { 117 CFReadStreamClose(form->currentStream); 118 CFReadStreamSetClient(form->currentStream, kCFStreamEventNone, 0, 0); 119 CFRelease(form->currentStream); 120 form->currentStream = 0; 121 form->currentStreamRangeLength = BlobDataItem::toEndOfFile; 122 } 123 124 form->currentData = nullptr; 125} 126 127// 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. 128static bool advanceCurrentStream(FormStreamFields* form) 129{ 130 closeCurrentStream(form); 131 132 if (form->remainingElements.isEmpty()) 133 return true; 134 135 // Create the new stream. 136 FormDataElement& nextInput = form->remainingElements.last(); 137 138 if (nextInput.m_type == FormDataElement::Type::Data) { 139 size_t size = nextInput.m_data.size(); 140 MallocPtr<char> data = nextInput.m_data.releaseBuffer(); 141 form->currentStream = CFReadStreamCreateWithBytesNoCopy(0, reinterpret_cast<const UInt8*>(data.get()), size, kCFAllocatorNull); 142 form->currentData = WTF::move(data); 143 } else { 144 // Check if the file has been changed or not if required. 145 if (isValidFileTime(nextInput.m_expectedFileModificationTime)) { 146 time_t fileModificationTime; 147 if (!getFileModificationTime(nextInput.m_filename, fileModificationTime) || fileModificationTime != static_cast<time_t>(nextInput.m_expectedFileModificationTime)) 148 return false; 149 } 150 const String& path = nextInput.m_shouldGenerateFile ? nextInput.m_generatedFilename : nextInput.m_filename; 151 form->currentStream = CFReadStreamCreateWithFile(0, pathAsURL(path).get()); 152 if (!form->currentStream) { 153 // The file must have been removed or become unreadable. 154 return false; 155 } 156 if (nextInput.m_fileStart > 0) { 157 RetainPtr<CFNumberRef> position = adoptCF(CFNumberCreate(0, kCFNumberLongLongType, &nextInput.m_fileStart)); 158 CFReadStreamSetProperty(form->currentStream, kCFStreamPropertyFileCurrentOffset, position.get()); 159 } 160 form->currentStreamRangeLength = nextInput.m_fileLength; 161 } 162 form->remainingElements.removeLast(); 163 164 // Set up the callback. 165 CFStreamClientContext context = { 0, form, 0, 0, 0 }; 166 CFReadStreamSetClient(form->currentStream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, 167 formEventCallback, &context); 168 169 // Schedule with the current set of run loops. 170 SchedulePairHashSet::iterator end = form->scheduledRunLoopPairs.end(); 171 for (SchedulePairHashSet::iterator it = form->scheduledRunLoopPairs.begin(); it != end; ++it) 172 CFReadStreamScheduleWithRunLoop(form->currentStream, (*it)->runLoop(), (*it)->mode()); 173 174 return true; 175} 176 177static bool openNextStream(FormStreamFields* form) 178{ 179 // Skip over any streams we can't open. 180 if (!advanceCurrentStream(form)) 181 return false; 182 while (form->currentStream && !CFReadStreamOpen(form->currentStream)) { 183 if (!advanceCurrentStream(form)) 184 return false; 185 } 186 return true; 187} 188 189static void* formCreate(CFReadStreamRef stream, void* context) 190{ 191 FormCreationContext* formContext = static_cast<FormCreationContext*>(context); 192 193 FormStreamFields* newInfo = new FormStreamFields; 194 newInfo->formData = formContext->formData.release(); 195 newInfo->currentStream = 0; 196 newInfo->currentStreamRangeLength = BlobDataItem::toEndOfFile; 197 newInfo->formStream = stream; // Don't retain. That would create a reference cycle. 198 newInfo->streamLength = formContext->streamLength; 199 newInfo->bytesSent = 0; 200 201 // Append in reverse order since we remove elements from the end. 202 size_t size = newInfo->formData->elements().size(); 203 newInfo->remainingElements.reserveInitialCapacity(size); 204 for (size_t i = 0; i < size; ++i) 205 newInfo->remainingElements.append(newInfo->formData->elements()[size - i - 1]); 206 207 return newInfo; 208} 209 210static void formFinishFinalizationOnMainThread(void* context) 211{ 212 OwnPtr<FormStreamFields> form = adoptPtr(static_cast<FormStreamFields*>(context)); 213 214 closeCurrentStream(form.get()); 215} 216 217static void formFinalize(CFReadStreamRef stream, void* context) 218{ 219 FormStreamFields* form = static_cast<FormStreamFields*>(context); 220 ASSERT_UNUSED(stream, form->formStream == stream); 221 222 callOnMainThread(formFinishFinalizationOnMainThread, form); 223} 224 225static Boolean formOpen(CFReadStreamRef, CFStreamError* error, Boolean* openComplete, void* context) 226{ 227 FormStreamFields* form = static_cast<FormStreamFields*>(context); 228 229 bool opened = openNextStream(form); 230 231 *openComplete = opened; 232 error->error = opened ? 0 : 233#if PLATFORM(WIN) 234 ENOENT; 235#else 236 fnfErr; 237#endif 238 return opened; 239} 240 241static CFIndex formRead(CFReadStreamRef, UInt8* buffer, CFIndex bufferLength, CFStreamError* error, Boolean* atEOF, void* context) 242{ 243 FormStreamFields* form = static_cast<FormStreamFields*>(context); 244 245 while (form->currentStream) { 246 CFIndex bytesToRead = bufferLength; 247 if (form->currentStreamRangeLength != BlobDataItem::toEndOfFile && form->currentStreamRangeLength < bytesToRead) 248 bytesToRead = static_cast<CFIndex>(form->currentStreamRangeLength); 249 CFIndex bytesRead = CFReadStreamRead(form->currentStream, buffer, bytesToRead); 250 if (bytesRead < 0) { 251 *error = CFReadStreamGetError(form->currentStream); 252 return -1; 253 } 254 if (bytesRead > 0) { 255 error->error = 0; 256 *atEOF = FALSE; 257 form->bytesSent += bytesRead; 258 if (form->currentStreamRangeLength != BlobDataItem::toEndOfFile) 259 form->currentStreamRangeLength -= bytesRead; 260 261 return bytesRead; 262 } 263 openNextStream(form); 264 } 265 266 error->error = 0; 267 *atEOF = TRUE; 268 return 0; 269} 270 271static Boolean formCanRead(CFReadStreamRef stream, void* context) 272{ 273 FormStreamFields* form = static_cast<FormStreamFields*>(context); 274 275 while (form->currentStream && CFReadStreamGetStatus(form->currentStream) == kCFStreamStatusAtEnd) 276 openNextStream(form); 277 278 if (!form->currentStream) { 279 CFReadStreamSignalEvent(stream, kCFStreamEventEndEncountered, 0); 280 return FALSE; 281 } 282 return CFReadStreamHasBytesAvailable(form->currentStream); 283} 284 285static void formClose(CFReadStreamRef, void* context) 286{ 287 FormStreamFields* form = static_cast<FormStreamFields*>(context); 288 289 closeCurrentStream(form); 290} 291 292static CFTypeRef formCopyProperty(CFReadStreamRef, CFStringRef propertyName, void *context) 293{ 294 FormStreamFields* form = static_cast<FormStreamFields*>(context); 295 296 if (kCFCompareEqualTo == CFStringCompare(propertyName, formDataPointerPropertyName, 0)) { 297 long formDataAsNumber = static_cast<long>(reinterpret_cast<intptr_t>(form->formData.get())); 298 return CFNumberCreate(0, kCFNumberLongType, &formDataAsNumber); 299 } 300 301 if (kCFCompareEqualTo == CFStringCompare(propertyName, formDataStreamLengthPropertyName(), 0)) 302 return CFStringCreateWithFormat(0, 0, CFSTR("%llu"), form->streamLength); 303 304 return 0; 305} 306 307static void formSchedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context) 308{ 309 FormStreamFields* form = static_cast<FormStreamFields*>(context); 310 311 if (form->currentStream) 312 CFReadStreamScheduleWithRunLoop(form->currentStream, runLoop, runLoopMode); 313 form->scheduledRunLoopPairs.add(SchedulePair::create(runLoop, runLoopMode)); 314} 315 316static void formUnschedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context) 317{ 318 FormStreamFields* form = static_cast<FormStreamFields*>(context); 319 320 if (form->currentStream) 321 CFReadStreamUnscheduleFromRunLoop(form->currentStream, runLoop, runLoopMode); 322 form->scheduledRunLoopPairs.remove(SchedulePair::create(runLoop, runLoopMode)); 323} 324 325static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context) 326{ 327 FormStreamFields* form = static_cast<FormStreamFields*>(context); 328 329 switch (type) { 330 case kCFStreamEventHasBytesAvailable: 331 CFReadStreamSignalEvent(form->formStream, kCFStreamEventHasBytesAvailable, 0); 332 break; 333 case kCFStreamEventErrorOccurred: { 334 CFStreamError readStreamError = CFReadStreamGetError(stream); 335 CFReadStreamSignalEvent(form->formStream, kCFStreamEventErrorOccurred, &readStreamError); 336 break; 337 } 338 case kCFStreamEventEndEncountered: 339 openNextStream(form); 340 if (!form->currentStream) 341 CFReadStreamSignalEvent(form->formStream, kCFStreamEventEndEncountered, 0); 342 break; 343 case kCFStreamEventNone: 344 LOG_ERROR("unexpected kCFStreamEventNone"); 345 break; 346 case kCFStreamEventOpenCompleted: 347 LOG_ERROR("unexpected kCFStreamEventOpenCompleted"); 348 break; 349 case kCFStreamEventCanAcceptBytes: 350 LOG_ERROR("unexpected kCFStreamEventCanAcceptBytes"); 351 break; 352 } 353} 354 355void setHTTPBody(CFMutableURLRequestRef request, PassRefPtr<FormData> prpFormData) 356{ 357 RefPtr<FormData> formData = prpFormData; 358 359 if (!formData) 360 return; 361 362 size_t count = formData->elements().size(); 363 364 // Handle the common special case of one piece of form data, with no files. 365 if (count == 1 && !formData->alwaysStream()) { 366 const FormDataElement& element = formData->elements()[0]; 367 if (element.m_type == FormDataElement::Type::Data) { 368 RetainPtr<CFDataRef> data = adoptCF(CFDataCreate(0, reinterpret_cast<const UInt8 *>(element.m_data.data()), element.m_data.size())); 369 CFURLRequestSetHTTPRequestBody(request, data.get()); 370 return; 371 } 372 } 373 374 formData = formData->resolveBlobReferences(); 375 count = formData->elements().size(); 376 377 // Precompute the content length so NSURLConnection doesn't use chunked mode. 378 unsigned long long length = 0; 379 for (size_t i = 0; i < count; ++i) { 380 const FormDataElement& element = formData->elements()[i]; 381 if (element.m_type == FormDataElement::Type::Data) 382 length += element.m_data.size(); 383 else { 384 // 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. 385 if (element.m_fileLength != BlobDataItem::toEndOfFile) { 386 length += element.m_fileLength; 387 continue; 388 } 389 long long fileSize; 390 if (getFileSize(element.m_shouldGenerateFile ? element.m_generatedFilename : element.m_filename, fileSize)) 391 length += fileSize; 392 } 393 } 394 395 // Create and set the stream. 396 397 // Pass the length along with the formData so it does not have to be recomputed. 398 FormCreationContext formContext = { formData.release(), length }; 399 400 CFReadStreamCallBacksV1 callBacks = { 1, formCreate, formFinalize, 0, formOpen, 0, formRead, 0, formCanRead, formClose, formCopyProperty, 0, 0, formSchedule, formUnschedule 401 }; 402 RetainPtr<CFReadStreamRef> stream = adoptCF(CFReadStreamCreate(0, static_cast<const void*>(&callBacks), &formContext)); 403 404 CFURLRequestSetHTTPRequestBodyStream(request, stream.get()); 405} 406 407FormData* httpBodyFromStream(CFReadStreamRef stream) 408{ 409 if (!stream) 410 return 0; 411 412 // Passing the pointer as property appears to be the only way to associate a stream with FormData. 413 // A new stream is always created in CFURLRequestCopyHTTPRequestBodyStream (or -[NSURLRequest HTTPBodyStream]), 414 // so a side HashMap wouldn't work. 415 // Even the stream's context pointer is different from the one we returned from formCreate(). 416 417 RetainPtr<CFNumberRef> formDataPointerAsCFNumber = adoptCF(static_cast<CFNumberRef>(CFReadStreamCopyProperty(stream, formDataPointerPropertyName))); 418 if (!formDataPointerAsCFNumber) 419 return 0; 420 421 long formDataPointerAsNumber; 422 if (!CFNumberGetValue(formDataPointerAsCFNumber.get(), kCFNumberLongType, &formDataPointerAsNumber)) 423 return 0; 424 425 return reinterpret_cast<FormData*>(static_cast<intptr_t>(formDataPointerAsNumber)); 426} 427 428} // namespace WebCore 429