2008-09-16 Anders Carlsson <andersca@apple.com>
[webkit/qt.git] / WebKit / mac / Plugins / WebBaseNetscapePluginStream.mm
blob3e276224a5f65e87535cf3a308ae6c7d5f45f3e2
1 /*
2  * Copyright (C) 2005, 2006, 2007 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  */
29 #if ENABLE(NETSCAPE_PLUGIN_API)
30 #import "WebBaseNetscapePluginStream.h"
32 #import "WebBaseNetscapePluginView.h"
33 #import "WebFrameInternal.h"
34 #import "WebKitErrorsPrivate.h"
35 #import "WebKitLogging.h"
36 #import "WebNSObjectExtras.h"
37 #import "WebNSURLExtras.h"
38 #import "WebNSURLRequestExtras.h"
39 #import "WebNetscapePluginPackage.h"
40 #import "WebNetscapePlugInStreamLoaderClient.h"
41 #import <Foundation/NSURLResponse.h>
42 #import <kjs/JSLock.h>
43 #import <WebCore/DocumentLoader.h>
44 #import <WebCore/Frame.h>
45 #import <WebCore/FrameLoader.h>
46 #import <WebCore/WebCoreObjCExtras.h>
47 #import <WebKitSystemInterface.h>
48 #import <wtf/HashMap.h>
50 using namespace WebCore;
52 #define WEB_REASON_NONE -1
54 static NSString *CarbonPathFromPOSIXPath(NSString *posixPath);
56 typedef HashMap<NPStream*, NPP> StreamMap;
57 static StreamMap& streams()
59     static StreamMap staticStreams;
60     return staticStreams;
63 @implementation WebBaseNetscapePluginStream
65 #ifndef BUILDING_ON_TIGER
66 + (void)initialize
68     WebCoreObjCFinalizeOnMainThread(self);
70 #endif
72 + (NPP)ownerForStream:(NPStream *)stream
74     return streams().get(stream);
77 + (NPReason)reasonForError:(NSError *)error
79     if (error == nil) {
80         return NPRES_DONE;
81     }
82     if ([[error domain] isEqualToString:NSURLErrorDomain] && [error code] == NSURLErrorCancelled) {
83         return NPRES_USER_BREAK;
84     }
85     return NPRES_NETWORK_ERR;
88 - (NSError *)_pluginCancelledConnectionError
90     return [[[NSError alloc] _initWithPluginErrorCode:WebKitErrorPlugInCancelledConnection
91                                            contentURL:responseURL != nil ? responseURL : requestURL
92                                         pluginPageURL:nil
93                                            pluginName:[[pluginView pluginPackage] name]
94                                              MIMEType:MIMEType] autorelease];
97 - (NSError *)errorForReason:(NPReason)theReason
99     if (theReason == NPRES_DONE) {
100         return nil;
101     }
102     if (theReason == NPRES_USER_BREAK) {
103         return [NSError _webKitErrorWithDomain:NSURLErrorDomain
104                                           code:NSURLErrorCancelled 
105                                            URL:responseURL != nil ? responseURL : requestURL];
106     }
107     return [self _pluginCancelledConnectionError];
110 - (id)initWithFrameLoader:(FrameLoader *)frameLoader
112     _frameLoader = frameLoader;
113     
114     return self;
117 - (id)initWithRequest:(NSURLRequest *)theRequest
118                plugin:(NPP)thePlugin
119            notifyData:(void *)theNotifyData 
120      sendNotification:(BOOL)flag
121 {   
122     WebBaseNetscapePluginView *view = (WebBaseNetscapePluginView *)thePlugin->ndata;
123     
124     if (!core([view webFrame])->loader()->canLoad([theRequest URL], String(), core([view webFrame])->document()))
125         return nil;
126     
127     if ([self initWithRequestURL:[theRequest URL]
128                           plugin:thePlugin
129                       notifyData:theNotifyData
130                 sendNotification:flag] == nil) {
131         return nil;
132     }
133     
134     // Temporarily set isTerminated to YES to avoid assertion failure in dealloc in case we are released in this method.
135     isTerminated = YES;
136     
137     request = [theRequest mutableCopy];
138     if (core([view webFrame])->loader()->shouldHideReferrer([theRequest URL], core([view webFrame])->loader()->outgoingReferrer()))
139         [(NSMutableURLRequest *)request _web_setHTTPReferrer:nil];
140     
141     _client = new WebNetscapePlugInStreamLoaderClient(self);
142     _loader = NetscapePlugInStreamLoader::create(core([view webFrame]), _client).releaseRef();
143     _loader->setShouldBufferData(false);
144     
145     isTerminated = NO;
146     
147     return self;
150 - (id)initWithRequestURL:(NSURL *)theRequestURL
151                   plugin:(NPP)thePlugin
152               notifyData:(void *)theNotifyData
153         sendNotification:(BOOL)flag
155     [super init];
157     // Temporarily set isTerminated to YES to avoid assertion failure in dealloc in case we are released in this method.
158     isTerminated = YES;
160     if (theRequestURL == nil || thePlugin == NULL) {
161         [self release];
162         return nil;
163     }
164     
165     [self setRequestURL:theRequestURL];
166     [self setPlugin:thePlugin];
167     notifyData = theNotifyData;
168     sendNotification = flag;
169     fileDescriptor = -1;
170     newStreamSuccessful = NO;
172     streams().add(&stream, thePlugin);
173     
174     isTerminated = NO;
175     
176     return self;
179 - (void)dealloc
181     ASSERT(!plugin);
182     ASSERT(isTerminated);
183     ASSERT(stream.ndata == nil);
185     // The stream file should have been deleted, and the path freed, in -_destroyStream
186     ASSERT(!path);
187     ASSERT(fileDescriptor == -1);
189     if (_loader)
190         _loader->deref();
191     delete _client;
192     [request release];
193     
194     [requestURL release];
195     [responseURL release];
196     [MIMEType release];
197     [pluginView release];
198     [deliveryData release];
199     
200     free((void *)stream.url);
201     free(path);
202     free(headers);
204     streams().remove(&stream);
206     [super dealloc];
209 - (void)finalize
211     ASSERT_MAIN_THREAD();
212     ASSERT(isTerminated);
213     ASSERT(stream.ndata == nil);
215     // The stream file should have been deleted, and the path freed, in -_destroyStream
216     ASSERT(!path);
217     ASSERT(fileDescriptor == -1);
219     if (_loader)
220         _loader->deref();
221     delete _client;
222     
223     free((void *)stream.url);
224     free(path);
225     free(headers);
227     streams().remove(&stream);
229     [super finalize];
232 - (uint16)transferMode
234     return transferMode;
237 - (NPP)plugin
239     return plugin;
242 - (void)setRequestURL:(NSURL *)theRequestURL
244     [theRequestURL retain];
245     [requestURL release];
246     requestURL = theRequestURL;
249 - (void)setResponseURL:(NSURL *)theResponseURL
251     [theResponseURL retain];
252     [responseURL release];
253     responseURL = theResponseURL;
256 - (void)setPlugin:(NPP)thePlugin
258     if (thePlugin) {
259         plugin = thePlugin;
260         pluginView = [(WebBaseNetscapePluginView *)plugin->ndata retain];
261         WebNetscapePluginPackage *pluginPackage = [pluginView pluginPackage];
262         
263         pluginFuncs = [pluginPackage pluginFuncs];
264     } else {
265         WebBaseNetscapePluginView *view = pluginView;
267         plugin = NULL;
268         pluginView = nil;
270         pluginFuncs = NULL;
272         [view disconnectStream:self];
273         [view release];
274     }
277 - (void)setMIMEType:(NSString *)theMIMEType
279     [theMIMEType retain];
280     [MIMEType release];
281     MIMEType = theMIMEType;
284 - (void)startStreamResponseURL:(NSURL *)URL
285          expectedContentLength:(long long)expectedContentLength
286               lastModifiedDate:(NSDate *)lastModifiedDate
287                       MIMEType:(NSString *)theMIMEType
288                        headers:(NSData *)theHeaders
290     ASSERT(!isTerminated);
291     
292     [self setResponseURL:URL];
293     [self setMIMEType:theMIMEType];
294     
295     free((void *)stream.url);
296     stream.url = strdup([responseURL _web_URLCString]);
298     stream.ndata = self;
299     stream.end = expectedContentLength > 0 ? (uint32)expectedContentLength : 0;
300     stream.lastmodified = (uint32)[lastModifiedDate timeIntervalSince1970];
301     stream.notifyData = notifyData;
303     if (theHeaders) {
304         unsigned len = [theHeaders length];
305         headers = (char*) malloc(len + 1);
306         [theHeaders getBytes:headers];
307         headers[len] = 0;
308         stream.headers = headers;
309     }
310     
311     transferMode = NP_NORMAL;
312     offset = 0;
313     reason = WEB_REASON_NONE;
314     // FIXME: If WebNetscapePluginStream called our initializer we wouldn't have to do this here.
315     fileDescriptor = -1;
317     // FIXME: Need a way to check if stream is seekable
319     WebBaseNetscapePluginView *pv = pluginView;
320     [pv willCallPlugInFunction];
321     NPError npErr = pluginFuncs->newstream(plugin, (char *)[MIMEType UTF8String], &stream, NO, &transferMode);
322     [pv didCallPlugInFunction];
323     LOG(Plugins, "NPP_NewStream URL=%@ MIME=%@ error=%d", responseURL, MIMEType, npErr);
325     if (npErr != NPERR_NO_ERROR) {
326         LOG_ERROR("NPP_NewStream failed with error: %d responseURL: %@", npErr, responseURL);
327         // Calling cancelLoadWithError: cancels the load, but doesn't call NPP_DestroyStream.
328         [self cancelLoadWithError:[self _pluginCancelledConnectionError]];
329         return;
330     }
332     newStreamSuccessful = YES;
334     switch (transferMode) {
335         case NP_NORMAL:
336             LOG(Plugins, "Stream type: NP_NORMAL");
337             break;
338         case NP_ASFILEONLY:
339             LOG(Plugins, "Stream type: NP_ASFILEONLY");
340             break;
341         case NP_ASFILE:
342             LOG(Plugins, "Stream type: NP_ASFILE");
343             break;
344         case NP_SEEK:
345             LOG_ERROR("Stream type: NP_SEEK not yet supported");
346             [self cancelLoadAndDestroyStreamWithError:[self _pluginCancelledConnectionError]];
347             break;
348         default:
349             LOG_ERROR("unknown stream type");
350     }
353 - (void)start
355     ASSERT(request);
356     ASSERT(!_frameLoader);
357     
358     _loader->documentLoader()->addPlugInStreamLoader(_loader);
359     _loader->load(request);    
362 - (void)stop
364     ASSERT(!_frameLoader);
365     
366     if (!_loader->isDone())
367         [self cancelLoadAndDestroyStreamWithError:_loader->cancelledError()];    
370 - (void)startStreamWithResponse:(NSURLResponse *)r
372     NSMutableData *theHeaders = nil;
373     long long expectedContentLength = [r expectedContentLength];
375     if ([r isKindOfClass:[NSHTTPURLResponse class]]) {
376         NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)r;
377         theHeaders = [NSMutableData dataWithCapacity:1024];
378         
379         // FIXME: it would be nice to be able to get the raw HTTP header block.
380         // This includes the HTTP version, the real status text,
381         // all headers in their original order and including duplicates,
382         // and all original bytes verbatim, rather than sent through Unicode translation.
383         // Unfortunately NSHTTPURLResponse doesn't provide access at that low a level.
384         
385         [theHeaders appendBytes:"HTTP " length:5];
386         char statusStr[10];
387         long statusCode = [httpResponse statusCode];
388         snprintf(statusStr, sizeof(statusStr), "%ld", statusCode);
389         [theHeaders appendBytes:statusStr length:strlen(statusStr)];
390         [theHeaders appendBytes:" OK\n" length:4];
392         // HACK: pass the headers through as UTF-8.
393         // This is not the intended behavior; we're supposed to pass original bytes verbatim.
394         // But we don't have the original bytes, we have NSStrings built by the URL loading system.
395         // It hopefully shouldn't matter, since RFC2616/RFC822 require ASCII-only headers,
396         // but surely someone out there is using non-ASCII characters, and hopefully UTF-8 is adequate here.
397         // It seems better than NSASCIIStringEncoding, which will lose information if non-ASCII is used.
399         NSDictionary *headerDict = [httpResponse allHeaderFields];
400         NSArray *keys = [[headerDict allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
401         NSEnumerator *i = [keys objectEnumerator];
402         NSString *k;
403         while ((k = [i nextObject]) != nil) {
404             NSString *v = [headerDict objectForKey:k];
405             [theHeaders appendData:[k dataUsingEncoding:NSUTF8StringEncoding]];
406             [theHeaders appendBytes:": " length:2];
407             [theHeaders appendData:[v dataUsingEncoding:NSUTF8StringEncoding]];
408             [theHeaders appendBytes:"\n" length:1];
409         }
411         // If the content is encoded (most likely compressed), then don't send its length to the plugin,
412         // which is only interested in the decoded length, not yet known at the moment.
413         // <rdar://problem/4470599> tracks a request for -[NSURLResponse expectedContentLength] to incorporate this logic.
414         NSString *contentEncoding = (NSString *)[[(NSHTTPURLResponse *)r allHeaderFields] objectForKey:@"Content-Encoding"];
415         if (contentEncoding && ![contentEncoding isEqualToString:@"identity"])
416             expectedContentLength = -1;
418         // startStreamResponseURL:... will null-terminate.
419     }
421     [self startStreamResponseURL:[r URL]
422            expectedContentLength:expectedContentLength
423                 lastModifiedDate:WKGetNSURLResponseLastModifiedDate(r)
424                         MIMEType:[r MIMEType]
425                          headers:theHeaders];
428 - (BOOL)wantsAllStreams
430     if (!pluginFuncs->getvalue)
431         return NO;
432     
433     void *value = 0;
434     NPError error;
435     WebBaseNetscapePluginView *pv = pluginView;
436     [pv willCallPlugInFunction];
437     {
438         JSC::JSLock::DropAllLocks dropAllLocks(false);
439         error = pluginFuncs->getvalue(plugin, NPPVpluginWantsAllNetworkStreams, &value);
440     }
441     [pv didCallPlugInFunction];
442     if (error != NPERR_NO_ERROR)
443         return NO;
444     
445     return value != 0;
448 - (void)_destroyStream
450     if (isTerminated)
451         return;
453     [self retain];
455     ASSERT(reason != WEB_REASON_NONE);
456     ASSERT([deliveryData length] == 0);
457     
458     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_deliverData) object:nil];
460     if (stream.ndata != nil) {
461         if (reason == NPRES_DONE && (transferMode == NP_ASFILE || transferMode == NP_ASFILEONLY)) {
462             ASSERT(fileDescriptor == -1);
463             ASSERT(path != NULL);
464             NSString *carbonPath = CarbonPathFromPOSIXPath(path);
465             ASSERT(carbonPath != NULL);
466             WebBaseNetscapePluginView *pv = pluginView;
467             [pv willCallPlugInFunction];
468             pluginFuncs->asfile(plugin, &stream, [carbonPath fileSystemRepresentation]);
469             [pv didCallPlugInFunction];
470             LOG(Plugins, "NPP_StreamAsFile responseURL=%@ path=%s", responseURL, carbonPath);
471         }
473         if (path) {
474             // Delete the file after calling NPP_StreamAsFile(), instead of in -dealloc/-finalize.  It should be OK
475             // to delete the file here -- NPP_StreamAsFile() is always called immediately before NPP_DestroyStream()
476             // (the stream destruction function), so there can be no expectation that a plugin will read the stream
477             // file asynchronously after NPP_StreamAsFile() is called.
478             unlink([path fileSystemRepresentation]);
479             [path release];
480             path = nil;
482             if (isTerminated)
483                 goto exit;
484         }
486         if (fileDescriptor != -1) {
487             // The file may still be open if we are destroying the stream before it completed loading.
488             close(fileDescriptor);
489             fileDescriptor = -1;
490         }
492         if (newStreamSuccessful) {
493             NPError npErr;
494             WebBaseNetscapePluginView *pv = pluginView;
495             [pv willCallPlugInFunction];
496             npErr = pluginFuncs->destroystream(plugin, &stream, reason);
497             [pv didCallPlugInFunction];
498             LOG(Plugins, "NPP_DestroyStream responseURL=%@ error=%d", responseURL, npErr);
499         }
501         free(headers);
502         headers = NULL;
503         stream.headers = NULL;
505         stream.ndata = nil;
507         if (isTerminated)
508             goto exit;
509     }
511     if (sendNotification) {
512         // NPP_URLNotify expects the request URL, not the response URL.
513         WebBaseNetscapePluginView *pv = pluginView;
514         [pv willCallPlugInFunction];
515         pluginFuncs->urlnotify(plugin, [requestURL _web_URLCString], reason, notifyData);
516         [pv didCallPlugInFunction];
517         LOG(Plugins, "NPP_URLNotify requestURL=%@ reason=%d", requestURL, reason);
518     }
520     isTerminated = YES;
522     [self setPlugin:NULL];
524 exit:
525     [self release];
528 - (void)_destroyStreamWithReason:(NPReason)theReason
530     reason = theReason;
531     if (reason != NPRES_DONE) {
532         // Stop any pending data from being streamed.
533         [deliveryData setLength:0];
534     } else if ([deliveryData length] > 0) {
535         // There is more data to be streamed, don't destroy the stream now.
536         return;
537     }
538     [self _destroyStream];
539     ASSERT(stream.ndata == nil);
542 - (void)cancelLoadWithError:(NSError *)error
544     if (_frameLoader) {
545         ASSERT(!_loader);
546         
547         DocumentLoader* documentLoader = _frameLoader->activeDocumentLoader();
548         ASSERT(documentLoader);
549         
550         if (documentLoader->isLoadingMainResource())
551             documentLoader->cancelMainResourceLoad(error);
552         return;
553     }
554     
555     if (!_loader->isDone())
556         _loader->cancel(error);
559 - (void)destroyStreamWithError:(NSError *)error
561     [self _destroyStreamWithReason:[[self class] reasonForError:error]];
564 - (void)cancelLoadAndDestroyStreamWithError:(NSError *)error
566     [self retain];
567     [self cancelLoadWithError:error];
568     [self destroyStreamWithError:error];
569     [self setPlugin:NULL];
570     [self release];
573 - (void)_deliverData
575     if (!stream.ndata || [deliveryData length] == 0)
576         return;
578     [self retain];
580     int32 totalBytes = [deliveryData length];
581     int32 totalBytesDelivered = 0;
583     while (totalBytesDelivered < totalBytes) {
584         WebBaseNetscapePluginView *pv = pluginView;
585         [pv willCallPlugInFunction];
586         int32 deliveryBytes = pluginFuncs->writeready(plugin, &stream);
587         [pv didCallPlugInFunction];
588         LOG(Plugins, "NPP_WriteReady responseURL=%@ bytes=%d", responseURL, deliveryBytes);
590         if (isTerminated)
591             goto exit;
593         if (deliveryBytes <= 0) {
594             // Plug-in can't receive anymore data right now. Send it later.
595             [self performSelector:@selector(_deliverData) withObject:nil afterDelay:0];
596             break;
597         } else {
598             deliveryBytes = MIN(deliveryBytes, totalBytes - totalBytesDelivered);
599             NSData *subdata = [deliveryData subdataWithRange:NSMakeRange(totalBytesDelivered, deliveryBytes)];
600             pv = pluginView;
601             [pv willCallPlugInFunction];
602             deliveryBytes = pluginFuncs->write(plugin, &stream, offset, [subdata length], (void *)[subdata bytes]);
603             [pv didCallPlugInFunction];
604             if (deliveryBytes < 0) {
605                 // Netscape documentation says that a negative result from NPP_Write means cancel the load.
606                 [self cancelLoadAndDestroyStreamWithError:[self _pluginCancelledConnectionError]];
607                 return;
608             }
609             deliveryBytes = MIN((unsigned)deliveryBytes, [subdata length]);
610             offset += deliveryBytes;
611             totalBytesDelivered += deliveryBytes;
612             LOG(Plugins, "NPP_Write responseURL=%@ bytes=%d total-delivered=%d/%d", responseURL, deliveryBytes, offset, stream.end);
613         }
614     }
616     if (totalBytesDelivered > 0) {
617         if (totalBytesDelivered < totalBytes) {
618             NSMutableData *newDeliveryData = [[NSMutableData alloc] initWithCapacity:totalBytes - totalBytesDelivered];
619             [newDeliveryData appendBytes:(char *)[deliveryData bytes] + totalBytesDelivered length:totalBytes - totalBytesDelivered];
620             [deliveryData release];
621             deliveryData = newDeliveryData;
622         } else {
623             [deliveryData setLength:0];
624             if (reason != WEB_REASON_NONE) {
625                 [self _destroyStream];
626             }
627         }
628     }
630 exit:
631     [self release];
634 - (void)_deliverDataToFile:(NSData *)data
636     if (fileDescriptor == -1 && !path) {
637         NSString *temporaryFileMask = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPlugInStreamXXXXXX"];
638         char *temporaryFileName = strdup([temporaryFileMask fileSystemRepresentation]);
639         fileDescriptor = mkstemp(temporaryFileName);
640         if (fileDescriptor == -1) {
641             LOG_ERROR("Can't create a temporary file.");
642             // This is not a network error, but the only error codes are "network error" and "user break".
643             [self _destroyStreamWithReason:NPRES_NETWORK_ERR];
644             free(temporaryFileName);
645             return;
646         }
648         path = [[NSString stringWithUTF8String:temporaryFileName] retain];
649         free(temporaryFileName);
650     }
652     int dataLength = [data length];
653     if (!dataLength)
654         return;
656     int byteCount = write(fileDescriptor, [data bytes], dataLength);
657     if (byteCount != dataLength) {
658         // This happens only rarely, when we are out of disk space or have a disk I/O error.
659         LOG_ERROR("error writing to temporary file, errno %d", errno);
660         close(fileDescriptor);
661         fileDescriptor = -1;
663         // This is not a network error, but the only error codes are "network error" and "user break".
664         [self _destroyStreamWithReason:NPRES_NETWORK_ERR];
665         [path release];
666         path = nil;
667     }
670 - (void)finishedLoading
672     if (!stream.ndata)
673         return;
675     if (transferMode == NP_ASFILE || transferMode == NP_ASFILEONLY) {
676         // Fake the delivery of an empty data to ensure that the file has been created
677         [self _deliverDataToFile:[NSData data]];
678         if (fileDescriptor != -1)
679             close(fileDescriptor);
680         fileDescriptor = -1;
681     }
683     [self _destroyStreamWithReason:NPRES_DONE];
686 - (void)receivedData:(NSData *)data
688     ASSERT([data length] > 0);
689     
690     if (transferMode != NP_ASFILEONLY) {
691         if (!deliveryData) {
692             deliveryData = [[NSMutableData alloc] initWithCapacity:[data length]];
693         }
694         [deliveryData appendData:data];
695         [self _deliverData];
696     }
697     if (transferMode == NP_ASFILE || transferMode == NP_ASFILEONLY)
698         [self _deliverDataToFile:data];
702 @end
704 static NSString *CarbonPathFromPOSIXPath(NSString *posixPath)
706     // Doesn't add a trailing colon for directories; this is a problem for paths to a volume,
707     // so this function would need to be revised if we ever wanted to call it with that.
709     CFURLRef url = (CFURLRef)[NSURL fileURLWithPath:posixPath];
710     if (!url)
711         return nil;
713     return WebCFAutorelease(CFURLCopyFileSystemPath(url, kCFURLHFSPathStyle));
716 #endif