1 /* 2 * Copyright (C) 2005, 2006, 2008 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 /* originally written by Becky Willrich, additional code by Darin Adler */ 30 31 #import "config.h" 32 #import "FormDataStreamMac.h" 33 34 #if !USE(CFNETWORK) 35 36 #import "BlobRegistryImpl.h" 37 #import "FileSystem.h" 38 #import "FormData.h" 39 #import "ResourceHandle.h" 40 #import "ResourceHandleClient.h" 41 #import "SchedulePair.h" 42 #import "WebCoreSystemInterface.h" 43 #import <sys/stat.h> 44 #import <sys/types.h> 45 #import <wtf/Assertions.h> 46 #import <wtf/HashMap.h> 47 #import <wtf/MainThread.h> 48 #import <wtf/StdLibExtras.h> 49 #import <wtf/Threading.h> 50 51 #if PLATFORM(IOS) 52 #import <MacErrors.h> 53 #else 54 #import <CoreServices/CoreServices.h> 55 #endif 56 57 namespace WebCore { 58 59 typedef HashMap<CFReadStreamRef, RefPtr<FormData> > StreamFormDataMap; 60 static StreamFormDataMap& getStreamFormDataMap() 61 { 62 DEFINE_STATIC_LOCAL(StreamFormDataMap, streamFormDataMap, ()); 63 return streamFormDataMap; 64 } 65 66 typedef HashMap<CFReadStreamRef, RefPtr<ResourceHandle> > StreamResourceHandleMap; 67 static StreamResourceHandleMap& getStreamResourceHandleMap() 68 { 69 DEFINE_STATIC_LOCAL(StreamResourceHandleMap, streamResourceHandleMap, ()); 70 return streamResourceHandleMap; 71 } 72 73 void associateStreamWithResourceHandle(NSInputStream *stream, ResourceHandle* resourceHandle) 74 { 75 ASSERT(isMainThread()); 76 77 ASSERT(resourceHandle); 78 79 if (!stream) 80 return; 81 82 if (!getStreamFormDataMap().contains((CFReadStreamRef)stream)) 83 return; 84 85 ASSERT(!getStreamResourceHandleMap().contains((CFReadStreamRef)stream)); 86 getStreamResourceHandleMap().set((CFReadStreamRef)stream, resourceHandle); 87 } 88 89 void disassociateStreamWithResourceHandle(NSInputStream *stream) 90 { 91 ASSERT(isMainThread()); 92 93 if (!stream) 94 return; 95 96 getStreamResourceHandleMap().remove((CFReadStreamRef)stream); 97 } 98 99 struct DidSendDataCallbackData { 100 DidSendDataCallbackData(CFReadStreamRef stream_, unsigned long long bytesSent_, unsigned long long streamLength_) 101 : stream(stream_) 102 , bytesSent(bytesSent_) 103 , streamLength(streamLength_) 104 { 105 } 106 107 CFReadStreamRef stream; 108 unsigned long long bytesSent; 109 unsigned long long streamLength; 110 }; 111 112 static void performDidSendDataCallback(void* context) 113 { 114 ASSERT(isMainThread()); 115 116 DidSendDataCallbackData* data = static_cast<DidSendDataCallbackData*>(context); 117 ResourceHandle* resourceHandle = getStreamResourceHandleMap().get(data->stream).get(); 118 119 if (resourceHandle && resourceHandle->client()) 120 resourceHandle->client()->didSendData(resourceHandle, data->bytesSent, data->streamLength); 121 122 delete data; 123 } 124 125 static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context); 126 127 struct FormContext { 128 FormData* formData; 129 unsigned long long streamLength; 130 }; 131 132 struct FormStreamFields { 133 SchedulePairHashSet scheduledRunLoopPairs; 134 Vector<FormDataElement> remainingElements; // in reverse order 135 CFReadStreamRef currentStream; 136 #if ENABLE(BLOB) 137 long long currentStreamRangeLength; 138 #endif 139 char* currentData; 140 CFReadStreamRef formStream; 141 unsigned long long streamLength; 142 unsigned long long bytesSent; 143 }; 144 145 static void closeCurrentStream(FormStreamFields *form) 146 { 147 if (form->currentStream) { 148 CFReadStreamClose(form->currentStream); 149 CFReadStreamSetClient(form->currentStream, kCFStreamEventNone, NULL, NULL); 150 CFRelease(form->currentStream); 151 form->currentStream = NULL; 152 #if ENABLE(BLOB) 153 form->currentStreamRangeLength = BlobDataItem::toEndOfFile; 154 #endif 155 } 156 if (form->currentData) { 157 fastFree(form->currentData); 158 form->currentData = 0; 159 } 160 } 161 162 // 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. 163 static bool advanceCurrentStream(FormStreamFields* form) 164 { 165 closeCurrentStream(form); 166 167 if (form->remainingElements.isEmpty()) 168 return true; 169 170 // Create the new stream. 171 FormDataElement& nextInput = form->remainingElements.last(); 172 173 if (nextInput.m_type == FormDataElement::data) { 174 size_t size = nextInput.m_data.size(); 175 char* data = nextInput.m_data.releaseBuffer(); 176 form->currentStream = CFReadStreamCreateWithBytesNoCopy(0, reinterpret_cast<const UInt8*>(data), size, kCFAllocatorNull); 177 form->currentData = data; 178 } else { 179 #if ENABLE(BLOB) 180 // Check if the file has been changed or not if required. 181 if (nextInput.m_expectedFileModificationTime != BlobDataItem::doNotCheckFileChange) { 182 time_t fileModificationTime; 183 if (!getFileModificationTime(nextInput.m_filename, fileModificationTime) || fileModificationTime != static_cast<time_t>(nextInput.m_expectedFileModificationTime)) 184 return false; 185 } 186 #endif 187 const String& path = nextInput.m_shouldGenerateFile ? nextInput.m_generatedFilename : nextInput.m_filename; 188 form->currentStream = CFReadStreamCreateWithFile(0, pathAsURL(path).get()); 189 if (!form->currentStream) { 190 // The file must have been removed or become unreadable. 191 return false; 192 } 193 #if ENABLE(BLOB) 194 if (nextInput.m_fileStart > 0) { 195 RetainPtr<CFNumberRef> position(AdoptCF, CFNumberCreate(0, kCFNumberLongLongType, &nextInput.m_fileStart)); 196 CFReadStreamSetProperty(form->currentStream, kCFStreamPropertyFileCurrentOffset, position.get()); 197 } 198 form->currentStreamRangeLength = nextInput.m_fileLength; 199 #endif 200 } 201 form->remainingElements.removeLast(); 202 203 // Set up the callback. 204 CFStreamClientContext context = { 0, form, NULL, NULL, NULL }; 205 CFReadStreamSetClient(form->currentStream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, 206 formEventCallback, &context); 207 208 // Schedule with the current set of run loops. 209 SchedulePairHashSet::iterator end = form->scheduledRunLoopPairs.end(); 210 for (SchedulePairHashSet::iterator it = form->scheduledRunLoopPairs.begin(); it != end; ++it) 211 CFReadStreamScheduleWithRunLoop(form->currentStream, (*it)->runLoop(), (*it)->mode()); 212 213 return true; 214 } 215 216 static bool openNextStream(FormStreamFields* form) 217 { 218 // Skip over any streams we can't open. 219 if (!advanceCurrentStream(form)) 220 return false; 221 while (form->currentStream && !CFReadStreamOpen(form->currentStream)) { 222 if (!advanceCurrentStream(form)) 223 return false; 224 } 225 return true; 226 } 227 228 static void* formCreate(CFReadStreamRef stream, void* context) 229 { 230 FormContext* formContext = static_cast<FormContext*>(context); 231 232 FormStreamFields* newInfo = new FormStreamFields; 233 newInfo->currentStream = NULL; 234 #if ENABLE(BLOB) 235 newInfo->currentStreamRangeLength = BlobDataItem::toEndOfFile; 236 #endif 237 newInfo->currentData = 0; 238 newInfo->formStream = stream; // Don't retain. That would create a reference cycle. 239 newInfo->streamLength = formContext->streamLength; 240 newInfo->bytesSent = 0; 241 242 FormData* formData = formContext->formData; 243 244 // Append in reverse order since we remove elements from the end. 245 size_t size = formData->elements().size(); 246 newInfo->remainingElements.reserveInitialCapacity(size); 247 for (size_t i = 0; i < size; ++i) 248 newInfo->remainingElements.append(formData->elements()[size - i - 1]); 249 250 getStreamFormDataMap().set(stream, adoptRef(formData)); 251 252 return newInfo; 253 } 254 255 static void formFinalize(CFReadStreamRef stream, void* context) 256 { 257 FormStreamFields* form = static_cast<FormStreamFields*>(context); 258 259 getStreamFormDataMap().remove(stream); 260 261 closeCurrentStream(form); 262 delete form; 263 } 264 265 static Boolean formOpen(CFReadStreamRef, CFStreamError* error, Boolean* openComplete, void* context) 266 { 267 FormStreamFields* form = static_cast<FormStreamFields*>(context); 268 269 bool opened = openNextStream(form); 270 271 *openComplete = opened; 272 error->error = opened ? 0 : fnfErr; 273 return opened; 274 } 275 276 static CFIndex formRead(CFReadStreamRef stream, UInt8* buffer, CFIndex bufferLength, CFStreamError* error, Boolean* atEOF, void* context) 277 { 278 FormStreamFields* form = static_cast<FormStreamFields*>(context); 279 280 while (form->currentStream) { 281 CFIndex bytesToRead = bufferLength; 282 #if ENABLE(BLOB) 283 if (form->currentStreamRangeLength != BlobDataItem::toEndOfFile && form->currentStreamRangeLength < bytesToRead) 284 bytesToRead = static_cast<CFIndex>(form->currentStreamRangeLength); 285 #endif 286 CFIndex bytesRead = CFReadStreamRead(form->currentStream, buffer, bytesToRead); 287 if (bytesRead < 0) { 288 *error = CFReadStreamGetError(form->currentStream); 289 return -1; 290 } 291 if (bytesRead > 0) { 292 error->error = 0; 293 *atEOF = FALSE; 294 form->bytesSent += bytesRead; 295 #if ENABLE(BLOB) 296 if (form->currentStreamRangeLength != BlobDataItem::toEndOfFile) 297 form->currentStreamRangeLength -= bytesRead; 298 #endif 299 300 if (!ResourceHandle::didSendBodyDataDelegateExists()) { 301 // FIXME: Figure out how to only do this when a ResourceHandleClient is available. 302 DidSendDataCallbackData* data = new DidSendDataCallbackData(stream, form->bytesSent, form->streamLength); 303 callOnMainThread(performDidSendDataCallback, data); 304 } 305 306 return bytesRead; 307 } 308 openNextStream(form); 309 } 310 311 error->error = 0; 312 *atEOF = TRUE; 313 return 0; 314 } 315 316 static Boolean formCanRead(CFReadStreamRef stream, void* context) 317 { 318 FormStreamFields* form = static_cast<FormStreamFields*>(context); 319 320 while (form->currentStream && CFReadStreamGetStatus(form->currentStream) == kCFStreamStatusAtEnd) { 321 openNextStream(form); 322 } 323 if (!form->currentStream) { 324 wkSignalCFReadStreamEnd(stream); 325 return FALSE; 326 } 327 return CFReadStreamHasBytesAvailable(form->currentStream); 328 } 329 330 static void formClose(CFReadStreamRef, void* context) 331 { 332 FormStreamFields* form = static_cast<FormStreamFields*>(context); 333 334 closeCurrentStream(form); 335 } 336 337 static void formSchedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context) 338 { 339 FormStreamFields* form = static_cast<FormStreamFields*>(context); 340 341 if (form->currentStream) 342 CFReadStreamScheduleWithRunLoop(form->currentStream, runLoop, runLoopMode); 343 form->scheduledRunLoopPairs.add(SchedulePair::create(runLoop, runLoopMode)); 344 } 345 346 static void formUnschedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context) 347 { 348 FormStreamFields* form = static_cast<FormStreamFields*>(context); 349 350 if (form->currentStream) 351 CFReadStreamUnscheduleFromRunLoop(form->currentStream, runLoop, runLoopMode); 352 form->scheduledRunLoopPairs.remove(SchedulePair::create(runLoop, runLoopMode)); 353 } 354 355 static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context) 356 { 357 FormStreamFields* form = static_cast<FormStreamFields*>(context); 358 359 switch (type) { 360 case kCFStreamEventHasBytesAvailable: 361 wkSignalCFReadStreamHasBytes(form->formStream); 362 break; 363 case kCFStreamEventErrorOccurred: { 364 CFStreamError readStreamError = CFReadStreamGetError(stream); 365 wkSignalCFReadStreamError(form->formStream, &readStreamError); 366 break; 367 } 368 case kCFStreamEventEndEncountered: 369 openNextStream(form); 370 if (!form->currentStream) { 371 wkSignalCFReadStreamEnd(form->formStream); 372 } 373 break; 374 case kCFStreamEventNone: 375 LOG_ERROR("unexpected kCFStreamEventNone"); 376 break; 377 case kCFStreamEventOpenCompleted: 378 LOG_ERROR("unexpected kCFStreamEventOpenCompleted"); 379 break; 380 case kCFStreamEventCanAcceptBytes: 381 LOG_ERROR("unexpected kCFStreamEventCanAcceptBytes"); 382 break; 383 } 384 } 385 386 void setHTTPBody(NSMutableURLRequest *request, PassRefPtr<FormData> formData) 387 { 388 if (!formData) 389 return; 390 391 size_t count = formData->elements().size(); 392 393 // Handle the common special case of one piece of form data, with no files. 394 if (count == 1 && !formData->alwaysStream()) { 395 const FormDataElement& element = formData->elements()[0]; 396 if (element.m_type == FormDataElement::data) { 397 NSData *data = [[NSData alloc] initWithBytes:element.m_data.data() length:element.m_data.size()]; 398 [request setHTTPBody:data]; 399 [data release]; 400 return; 401 } 402 } 403 404 #if ENABLE(BLOB) 405 // Check if there is a blob in the form data. 406 bool hasBlob = false; 407 for (size_t i = 0; i < count; ++i) { 408 const FormDataElement& element = formData->elements()[i]; 409 if (element.m_type == FormDataElement::encodedBlob) { 410 hasBlob = true; 411 break; 412 } 413 } 414 415 // If yes, we have to resolve all the blob references and regenerate the form data with only data and file types. 416 if (hasBlob) { 417 RefPtr<FormData> newFormData = FormData::create(); 418 newFormData->setAlwaysStream(formData->alwaysStream()); 419 newFormData->setIdentifier(formData->identifier()); 420 for (size_t i = 0; i < count; ++i) { 421 const FormDataElement& element = formData->elements()[i]; 422 if (element.m_type == FormDataElement::data) 423 newFormData->appendData(element.m_data.data(), element.m_data.size()); 424 else if (element.m_type == FormDataElement::encodedFile) 425 newFormData->appendFile(element.m_filename, element.m_shouldGenerateFile); 426 else { 427 ASSERT(element.m_type == FormDataElement::encodedBlob); 428 RefPtr<BlobStorageData> blobData = static_cast<BlobRegistryImpl&>(blobRegistry()).getBlobDataFromURL(KURL(ParsedURLString, element.m_blobURL)); 429 if (blobData) { 430 for (size_t j = 0; j < blobData->items().size(); ++j) { 431 const BlobDataItem& blobItem = blobData->items()[j]; 432 if (blobItem.type == BlobDataItem::Data) { 433 newFormData->appendData(blobItem.data->data() + static_cast<int>(blobItem.offset), static_cast<int>(blobItem.length)); 434 } else { 435 ASSERT(blobItem.type == BlobDataItem::File); 436 newFormData->appendFileRange(blobItem.path, blobItem.offset, blobItem.length, blobItem.expectedModificationTime); 437 } 438 } 439 } 440 } 441 } 442 formData = newFormData; 443 count = formData->elements().size(); 444 } 445 #endif 446 447 // Precompute the content length so NSURLConnection doesn't use chunked mode. 448 long long length = 0; 449 for (size_t i = 0; i < count; ++i) { 450 const FormDataElement& element = formData->elements()[i]; 451 if (element.m_type == FormDataElement::data) 452 length += element.m_data.size(); 453 else { 454 #if ENABLE(BLOB) 455 // 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. 456 if (element.m_fileLength != BlobDataItem::toEndOfFile) { 457 length += element.m_fileLength; 458 continue; 459 } 460 #endif 461 long long fileSize; 462 if (getFileSize(element.m_shouldGenerateFile ? element.m_generatedFilename : element.m_filename, fileSize)) 463 length += fileSize; 464 } 465 } 466 467 // Set the length. 468 [request setValue:[NSString stringWithFormat:@"%lld", length] forHTTPHeaderField:@"Content-Length"]; 469 470 // Create and set the stream. 471 472 // Pass the length along with the formData so it does not have to be recomputed. 473 FormContext formContext = { formData.releaseRef(), length }; 474 475 RetainPtr<CFReadStreamRef> stream(AdoptCF, wkCreateCustomCFReadStream(formCreate, formFinalize, 476 formOpen, formRead, formCanRead, formClose, formSchedule, formUnschedule, 477 &formContext)); 478 [request setHTTPBodyStream:(NSInputStream *)stream.get()]; 479 } 480 481 FormData* httpBodyFromStream(NSInputStream* stream) 482 { 483 return getStreamFormDataMap().get((CFReadStreamRef)stream).get(); 484 } 485 486 } // namespace WebCore 487 488 #endif // !USE(CFNETWORK) 489