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#import "CString.h" 35#import "FileSystem.h" 36#import "FormData.h" 37#import "ResourceHandle.h" 38#import "ResourceHandleClient.h" 39#import "SchedulePair.h" 40#import "WebCoreSystemInterface.h" 41#import <sys/stat.h> 42#import <sys/types.h> 43#import <wtf/Assertions.h> 44#import <wtf/HashMap.h> 45#import <wtf/MainThread.h> 46#import <wtf/StdLibExtras.h> 47#import <wtf/Threading.h> 48 49namespace WebCore { 50 51typedef HashMap<CFReadStreamRef, RefPtr<FormData> > StreamFormDataMap; 52static StreamFormDataMap& getStreamFormDataMap() 53{ 54 DEFINE_STATIC_LOCAL(StreamFormDataMap, streamFormDataMap, ()); 55 return streamFormDataMap; 56} 57 58typedef HashMap<CFReadStreamRef, RefPtr<ResourceHandle> > StreamResourceHandleMap; 59static StreamResourceHandleMap& getStreamResourceHandleMap() 60{ 61 DEFINE_STATIC_LOCAL(StreamResourceHandleMap, streamResourceHandleMap, ()); 62 return streamResourceHandleMap; 63} 64 65void associateStreamWithResourceHandle(NSInputStream *stream, ResourceHandle* resourceHandle) 66{ 67 ASSERT(isMainThread()); 68 69 ASSERT(resourceHandle); 70 71 if (!stream) 72 return; 73 74 if (!getStreamFormDataMap().contains((CFReadStreamRef)stream)) 75 return; 76 77 ASSERT(!getStreamResourceHandleMap().contains((CFReadStreamRef)stream)); 78 getStreamResourceHandleMap().set((CFReadStreamRef)stream, resourceHandle); 79} 80 81void disassociateStreamWithResourceHandle(NSInputStream *stream) 82{ 83 ASSERT(isMainThread()); 84 85 if (!stream) 86 return; 87 88 getStreamResourceHandleMap().remove((CFReadStreamRef)stream); 89} 90 91struct DidSendDataCallbackData { 92 DidSendDataCallbackData(CFReadStreamRef stream_, unsigned long long bytesSent_, unsigned long long streamLength_) 93 : stream(stream_) 94 , bytesSent(bytesSent_) 95 , streamLength(streamLength_) 96 { 97 } 98 99 CFReadStreamRef stream; 100 unsigned long long bytesSent; 101 unsigned long long streamLength; 102}; 103 104static void performDidSendDataCallback(void* context) 105{ 106 ASSERT(isMainThread()); 107 108 DidSendDataCallbackData* data = static_cast<DidSendDataCallbackData*>(context); 109 ResourceHandle* resourceHandle = getStreamResourceHandleMap().get(data->stream).get(); 110 111 if (resourceHandle && resourceHandle->client()) 112 resourceHandle->client()->didSendData(resourceHandle, data->bytesSent, data->streamLength); 113 114 delete data; 115} 116 117static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context); 118 119struct FormContext { 120 FormData* formData; 121 unsigned long long streamLength; 122}; 123 124struct FormStreamFields { 125 SchedulePairHashSet scheduledRunLoopPairs; 126 Vector<FormDataElement> remainingElements; // in reverse order 127 CFReadStreamRef currentStream; 128 char* currentData; 129 CFReadStreamRef formStream; 130 unsigned long long streamLength; 131 unsigned long long bytesSent; 132}; 133 134static void closeCurrentStream(FormStreamFields *form) 135{ 136 if (form->currentStream) { 137 CFReadStreamClose(form->currentStream); 138 CFReadStreamSetClient(form->currentStream, kCFStreamEventNone, NULL, NULL); 139 CFRelease(form->currentStream); 140 form->currentStream = NULL; 141 } 142 if (form->currentData) { 143 fastFree(form->currentData); 144 form->currentData = 0; 145 } 146} 147 148static void advanceCurrentStream(FormStreamFields *form) 149{ 150 closeCurrentStream(form); 151 152 if (form->remainingElements.isEmpty()) 153 return; 154 155 // Create the new stream. 156 FormDataElement& nextInput = form->remainingElements.last(); 157 if (nextInput.m_type == FormDataElement::data) { 158 size_t size = nextInput.m_data.size(); 159 char* data = nextInput.m_data.releaseBuffer(); 160 form->currentStream = CFReadStreamCreateWithBytesNoCopy(0, reinterpret_cast<const UInt8*>(data), size, kCFAllocatorNull); 161 form->currentData = data; 162 } else { 163 const String& path = nextInput.m_shouldGenerateFile ? nextInput.m_generatedFilename : nextInput.m_filename; 164 CFStringRef filename = path.createCFString(); 165 CFURLRef fileURL = CFURLCreateWithFileSystemPath(0, filename, kCFURLPOSIXPathStyle, FALSE); 166 CFRelease(filename); 167 form->currentStream = CFReadStreamCreateWithFile(0, fileURL); 168 CFRelease(fileURL); 169 } 170 form->remainingElements.removeLast(); 171 172 // Set up the callback. 173 CFStreamClientContext context = { 0, form, NULL, NULL, NULL }; 174 CFReadStreamSetClient(form->currentStream, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, 175 formEventCallback, &context); 176 177 // Schedule with the current set of run loops. 178 SchedulePairHashSet::iterator end = form->scheduledRunLoopPairs.end(); 179 for (SchedulePairHashSet::iterator it = form->scheduledRunLoopPairs.begin(); it != end; ++it) 180 CFReadStreamScheduleWithRunLoop(form->currentStream, (*it)->runLoop(), (*it)->mode()); 181} 182 183static void openNextStream(FormStreamFields* form) 184{ 185 // Skip over any streams we can't open. 186 // For some purposes we might want to return an error, but the current NSURLConnection 187 // can't really do anything useful with an error at this point, so this is better. 188 advanceCurrentStream(form); 189 while (form->currentStream && !CFReadStreamOpen(form->currentStream)) 190 advanceCurrentStream(form); 191} 192 193static void* formCreate(CFReadStreamRef stream, void* context) 194{ 195 FormContext* formContext = static_cast<FormContext*>(context); 196 197 FormStreamFields* newInfo = new FormStreamFields; 198 newInfo->currentStream = NULL; 199 newInfo->currentData = 0; 200 newInfo->formStream = stream; // Don't retain. That would create a reference cycle. 201 newInfo->streamLength = formContext->streamLength; 202 newInfo->bytesSent = 0; 203 204 FormData* formData = formContext->formData; 205 206 // Append in reverse order since we remove elements from the end. 207 size_t size = formData->elements().size(); 208 newInfo->remainingElements.reserveInitialCapacity(size); 209 for (size_t i = 0; i < size; ++i) 210 newInfo->remainingElements.append(formData->elements()[size - i - 1]); 211 212 getStreamFormDataMap().set(stream, adoptRef(formData)); 213 214 return newInfo; 215} 216 217static void formFinalize(CFReadStreamRef stream, void* context) 218{ 219 FormStreamFields* form = static_cast<FormStreamFields*>(context); 220 221 getStreamFormDataMap().remove(stream); 222 223 closeCurrentStream(form); 224 delete form; 225} 226 227static Boolean formOpen(CFReadStreamRef, CFStreamError* error, Boolean* openComplete, void* context) 228{ 229 FormStreamFields* form = static_cast<FormStreamFields*>(context); 230 231 openNextStream(form); 232 233 *openComplete = TRUE; 234 error->error = 0; 235 return TRUE; 236} 237 238static CFIndex formRead(CFReadStreamRef stream, UInt8* buffer, CFIndex bufferLength, CFStreamError* error, Boolean* atEOF, void* context) 239{ 240 FormStreamFields* form = static_cast<FormStreamFields*>(context); 241 242 while (form->currentStream) { 243 CFIndex bytesRead = CFReadStreamRead(form->currentStream, buffer, bufferLength); 244 if (bytesRead < 0) { 245 *error = CFReadStreamGetError(form->currentStream); 246 return -1; 247 } 248 if (bytesRead > 0) { 249 error->error = 0; 250 *atEOF = FALSE; 251 form->bytesSent += bytesRead; 252 253 if (!ResourceHandle::didSendBodyDataDelegateExists()) { 254 // FIXME: Figure out how to only do this when a ResourceHandleClient is available. 255 DidSendDataCallbackData* data = new DidSendDataCallbackData(stream, form->bytesSent, form->streamLength); 256 callOnMainThread(performDidSendDataCallback, data); 257 } 258 259 return bytesRead; 260 } 261 openNextStream(form); 262 } 263 264 error->error = 0; 265 *atEOF = TRUE; 266 return 0; 267} 268 269static Boolean formCanRead(CFReadStreamRef stream, void* context) 270{ 271 FormStreamFields* form = static_cast<FormStreamFields*>(context); 272 273 while (form->currentStream && CFReadStreamGetStatus(form->currentStream) == kCFStreamStatusAtEnd) { 274 openNextStream(form); 275 } 276 if (!form->currentStream) { 277 wkSignalCFReadStreamEnd(stream); 278 return FALSE; 279 } 280 return CFReadStreamHasBytesAvailable(form->currentStream); 281} 282 283static void formClose(CFReadStreamRef, void* context) 284{ 285 FormStreamFields* form = static_cast<FormStreamFields*>(context); 286 287 closeCurrentStream(form); 288} 289 290static void formSchedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context) 291{ 292 FormStreamFields* form = static_cast<FormStreamFields*>(context); 293 294 if (form->currentStream) 295 CFReadStreamScheduleWithRunLoop(form->currentStream, runLoop, runLoopMode); 296 form->scheduledRunLoopPairs.add(SchedulePair::create(runLoop, runLoopMode)); 297} 298 299static void formUnschedule(CFReadStreamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode, void* context) 300{ 301 FormStreamFields* form = static_cast<FormStreamFields*>(context); 302 303 if (form->currentStream) 304 CFReadStreamUnscheduleFromRunLoop(form->currentStream, runLoop, runLoopMode); 305 form->scheduledRunLoopPairs.remove(SchedulePair::create(runLoop, runLoopMode)); 306} 307 308static void formEventCallback(CFReadStreamRef stream, CFStreamEventType type, void* context) 309{ 310 FormStreamFields* form = static_cast<FormStreamFields*>(context); 311 312 switch (type) { 313 case kCFStreamEventHasBytesAvailable: 314 wkSignalCFReadStreamHasBytes(form->formStream); 315 break; 316 case kCFStreamEventErrorOccurred: { 317 CFStreamError readStreamError = CFReadStreamGetError(stream); 318 wkSignalCFReadStreamError(form->formStream, &readStreamError); 319 break; 320 } 321 case kCFStreamEventEndEncountered: 322 openNextStream(form); 323 if (!form->currentStream) { 324 wkSignalCFReadStreamEnd(form->formStream); 325 } 326 break; 327 case kCFStreamEventNone: 328 LOG_ERROR("unexpected kCFStreamEventNone"); 329 break; 330 case kCFStreamEventOpenCompleted: 331 LOG_ERROR("unexpected kCFStreamEventOpenCompleted"); 332 break; 333 case kCFStreamEventCanAcceptBytes: 334 LOG_ERROR("unexpected kCFStreamEventCanAcceptBytes"); 335 break; 336 } 337} 338 339void setHTTPBody(NSMutableURLRequest *request, PassRefPtr<FormData> formData) 340{ 341 if (!formData) 342 return; 343 344 size_t count = formData->elements().size(); 345 346 // Handle the common special case of one piece of form data, with no files. 347 if (count == 1 && !formData->alwaysStream()) { 348 const FormDataElement& element = formData->elements()[0]; 349 if (element.m_type == FormDataElement::data) { 350 NSData *data = [[NSData alloc] initWithBytes:element.m_data.data() length:element.m_data.size()]; 351 [request setHTTPBody:data]; 352 [data release]; 353 return; 354 } 355 } 356 357 // Precompute the content length so NSURLConnection doesn't use chunked mode. 358 long long length = 0; 359 for (size_t i = 0; i < count; ++i) { 360 const FormDataElement& element = formData->elements()[i]; 361 if (element.m_type == FormDataElement::data) 362 length += element.m_data.size(); 363 else { 364 long long fileSize; 365 if (getFileSize(element.m_shouldGenerateFile ? element.m_generatedFilename : element.m_filename, fileSize)) 366 length += fileSize; 367 } 368 } 369 370 // Set the length. 371 [request setValue:[NSString stringWithFormat:@"%lld", length] forHTTPHeaderField:@"Content-Length"]; 372 373 // Create and set the stream. 374 375 // Pass the length along with the formData so it does not have to be recomputed. 376 FormContext formContext = { formData.releaseRef(), length }; 377 378 CFReadStreamRef stream = wkCreateCustomCFReadStream(formCreate, formFinalize, 379 formOpen, formRead, formCanRead, formClose, formSchedule, formUnschedule, 380 &formContext); 381 [request setHTTPBodyStream:(NSInputStream *)stream]; 382 CFRelease(stream); 383} 384 385FormData* httpBodyFromStream(NSInputStream* stream) 386{ 387 return getStreamFormDataMap().get((CFReadStreamRef)stream).get(); 388} 389 390} // namespace WebCore 391