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