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 */
28
29#if ENABLE(NETSCAPE_PLUGIN_API)
30#import "WebNetscapePluginStream.h"
31
32#import "WebNetscapePluginView.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 <Foundation/NSURLResponse.h>
41#import <runtime/JSLock.h>
42#import <WebCore/DocumentLoader.h>
43#import <WebCore/Frame.h>
44#import <WebCore/FrameLoader.h>
45#import <WebCore/ResourceLoadScheduler.h>
46#import <WebCore/SecurityOrigin.h>
47#import <WebCore/WebCoreObjCExtras.h>
48#import <WebCore/WebCoreURLResponse.h>
49#import <WebKitSystemInterface.h>
50#import <wtf/HashMap.h>
51#import <wtf/StdLibExtras.h>
52
53using namespace WebCore;
54using namespace std;
55
56#define WEB_REASON_NONE -1
57
58static NSString *CarbonPathFromPOSIXPath(NSString *posixPath);
59
60class PluginStopDeferrer {
61public:
62    PluginStopDeferrer(WebNetscapePluginView* pluginView)
63        : m_pluginView(pluginView)
64    {
65        ASSERT(m_pluginView);
66
67        [m_pluginView.get() willCallPlugInFunction];
68    }
69
70    ~PluginStopDeferrer()
71    {
72        ASSERT(m_pluginView);
73        [m_pluginView.get() didCallPlugInFunction];
74    }
75
76private:
77    RetainPtr<WebNetscapePluginView> m_pluginView;
78};
79
80typedef HashMap<NPStream*, NPP> StreamMap;
81static StreamMap& streams()
82{
83    DEFINE_STATIC_LOCAL(StreamMap, staticStreams, ());
84    return staticStreams;
85}
86
87NPP WebNetscapePluginStream::ownerForStream(NPStream *stream)
88{
89    return streams().get(stream);
90}
91
92NPReason WebNetscapePluginStream::reasonForError(NSError *error)
93{
94    if (!error)
95        return NPRES_DONE;
96
97    if ([[error domain] isEqualToString:NSURLErrorDomain] && [error code] == NSURLErrorCancelled)
98        return NPRES_USER_BREAK;
99
100    return NPRES_NETWORK_ERR;
101}
102
103NSError *WebNetscapePluginStream::pluginCancelledConnectionError() const
104{
105    return [[[NSError alloc] _initWithPluginErrorCode:WebKitErrorPlugInCancelledConnection
106                                           contentURL:m_responseURL ? m_responseURL.get() : (NSURL *)m_requestURL
107                                        pluginPageURL:nil
108                                           pluginName:[[m_pluginView.get() pluginPackage] pluginInfo].name
109                                             MIMEType:(NSString *)String::fromUTF8(m_mimeType.data(), m_mimeType.length())] autorelease];
110}
111
112NSError *WebNetscapePluginStream::errorForReason(NPReason reason) const
113{
114    if (reason == NPRES_DONE)
115        return nil;
116
117    if (reason == NPRES_USER_BREAK)
118        return [NSError _webKitErrorWithDomain:NSURLErrorDomain
119                                          code:NSURLErrorCancelled
120                                           URL:m_responseURL ? m_responseURL.get() : (NSURL *)m_requestURL];
121
122    return pluginCancelledConnectionError();
123}
124
125WebNetscapePluginStream::WebNetscapePluginStream(FrameLoader* frameLoader)
126    : m_plugin(0)
127    , m_transferMode(0)
128    , m_offset(0)
129    , m_fileDescriptor(-1)
130    , m_sendNotification(false)
131    , m_notifyData(0)
132    , m_headers(0)
133    , m_reason(NPRES_BASE)
134    , m_isTerminated(false)
135    , m_newStreamSuccessful(false)
136    , m_frameLoader(frameLoader)
137    , m_pluginFuncs(0)
138    , m_deliverDataTimer(this, &WebNetscapePluginStream::deliverDataTimerFired)
139{
140    memset(&m_stream, 0, sizeof(NPStream));
141}
142
143WebNetscapePluginStream::WebNetscapePluginStream(NSURLRequest *request, NPP plugin, bool sendNotification, void* notifyData)
144    : m_requestURL([request URL])
145    , m_plugin(0)
146    , m_transferMode(0)
147    , m_offset(0)
148    , m_fileDescriptor(-1)
149    , m_sendNotification(sendNotification)
150    , m_notifyData(notifyData)
151    , m_headers(0)
152    , m_reason(NPRES_BASE)
153    , m_isTerminated(false)
154    , m_newStreamSuccessful(false)
155    , m_frameLoader(0)
156    , m_request(AdoptNS, [request mutableCopy])
157    , m_pluginFuncs(0)
158    , m_deliverDataTimer(this, &WebNetscapePluginStream::deliverDataTimerFired)
159{
160    memset(&m_stream, 0, sizeof(NPStream));
161
162    WebNetscapePluginView *view = (WebNetscapePluginView *)plugin->ndata;
163
164    // This check has already been done by the plug-in view.
165    ASSERT(core([view webFrame])->document()->securityOrigin()->canDisplay([request URL]));
166
167    ASSERT([request URL]);
168    ASSERT(plugin);
169
170    setPlugin(plugin);
171
172    streams().add(&m_stream, plugin);
173
174    if (SecurityOrigin::shouldHideReferrer([request URL], core([view webFrame])->loader()->outgoingReferrer()))
175        [m_request.get() _web_setHTTPReferrer:nil];
176}
177
178WebNetscapePluginStream::~WebNetscapePluginStream()
179{
180    ASSERT(!m_plugin);
181    ASSERT(m_isTerminated);
182    ASSERT(!m_stream.ndata);
183
184    // The stream file should have been deleted, and the path freed, in -_destroyStream
185    ASSERT(!m_path);
186    ASSERT(m_fileDescriptor == -1);
187
188    free((void *)m_stream.url);
189    free(m_headers);
190
191    streams().remove(&m_stream);
192}
193
194void WebNetscapePluginStream::setPlugin(NPP plugin)
195{
196    if (plugin) {
197        m_plugin = plugin;
198        m_pluginView = static_cast<WebNetscapePluginView *>(m_plugin->ndata);
199
200        WebNetscapePluginPackage *pluginPackage = [m_pluginView.get() pluginPackage];
201
202        m_pluginFuncs = [pluginPackage pluginFuncs];
203    } else {
204        WebNetscapePluginView *view = m_pluginView.get();
205        m_plugin = 0;
206        m_pluginFuncs = 0;
207
208        [view disconnectStream:this];
209        m_pluginView = 0;
210    }
211}
212
213void WebNetscapePluginStream::startStream(NSURL *url, long long expectedContentLength, NSDate *lastModifiedDate, const String& mimeType, NSData *headers)
214{
215    ASSERT(!m_isTerminated);
216
217    m_responseURL = url;
218    m_mimeType = mimeType.utf8();
219
220    free((void *)m_stream.url);
221    m_stream.url = strdup([m_responseURL.get() _web_URLCString]);
222
223    m_stream.ndata = this;
224    m_stream.end = expectedContentLength > 0 ? (uint32_t)expectedContentLength : 0;
225    m_stream.lastmodified = (uint32_t)[lastModifiedDate timeIntervalSince1970];
226    m_stream.notifyData = m_notifyData;
227
228    if (headers) {
229        unsigned len = [headers length];
230        m_headers = (char*) malloc(len + 1);
231        [headers getBytes:m_headers];
232        m_headers[len] = 0;
233        m_stream.headers = m_headers;
234    }
235
236    m_transferMode = NP_NORMAL;
237    m_offset = 0;
238    m_reason = WEB_REASON_NONE;
239    // FIXME: If WebNetscapePluginStream called our initializer we wouldn't have to do this here.
240    m_fileDescriptor = -1;
241
242    // FIXME: Need a way to check if stream is seekable
243
244    NPError npErr;
245    {
246        PluginStopDeferrer deferrer(m_pluginView.get());
247        npErr = m_pluginFuncs->newstream(m_plugin, m_mimeType.mutableData(), &m_stream, NO, &m_transferMode);
248    }
249
250    LOG(Plugins, "NPP_NewStream URL=%@ MIME=%s error=%d", m_responseURL.get(), m_mimeType.data(), npErr);
251
252    if (npErr != NPERR_NO_ERROR) {
253        LOG_ERROR("NPP_NewStream failed with error: %d responseURL: %@", npErr, m_responseURL.get());
254        // Calling cancelLoadWithError: cancels the load, but doesn't call NPP_DestroyStream.
255        cancelLoadWithError(pluginCancelledConnectionError());
256        return;
257    }
258
259    m_newStreamSuccessful = true;
260
261    switch (m_transferMode) {
262        case NP_NORMAL:
263            LOG(Plugins, "Stream type: NP_NORMAL");
264            break;
265        case NP_ASFILEONLY:
266            LOG(Plugins, "Stream type: NP_ASFILEONLY");
267            break;
268        case NP_ASFILE:
269            LOG(Plugins, "Stream type: NP_ASFILE");
270            break;
271        case NP_SEEK:
272            LOG_ERROR("Stream type: NP_SEEK not yet supported");
273            cancelLoadAndDestroyStreamWithError(pluginCancelledConnectionError());
274            break;
275        default:
276            LOG_ERROR("unknown stream type");
277    }
278}
279
280void WebNetscapePluginStream::start()
281{
282    ASSERT(m_request);
283    ASSERT(!m_frameLoader);
284    ASSERT(!m_loader);
285
286    m_loader = resourceLoadScheduler()->schedulePluginStreamLoad(core([m_pluginView.get() webFrame]), this, m_request.get());
287}
288
289void WebNetscapePluginStream::stop()
290{
291    ASSERT(!m_frameLoader);
292
293    if (!m_loader->isDone())
294        cancelLoadAndDestroyStreamWithError(m_loader->cancelledError());
295}
296
297void WebNetscapePluginStream::didReceiveResponse(NetscapePlugInStreamLoader*, const ResourceResponse& response)
298{
299    NSURLResponse *r = response.nsURLResponse();
300
301    NSMutableData *theHeaders = nil;
302    long long expectedContentLength = [r expectedContentLength];
303
304    if ([r isKindOfClass:[NSHTTPURLResponse class]]) {
305        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)r;
306        theHeaders = [NSMutableData dataWithCapacity:1024];
307
308        // FIXME: it would be nice to be able to get the raw HTTP header block.
309        // This includes the HTTP version, the real status text,
310        // all headers in their original order and including duplicates,
311        // and all original bytes verbatim, rather than sent through Unicode translation.
312        // Unfortunately NSHTTPURLResponse doesn't provide access at that low a level.
313
314        [theHeaders appendBytes:"HTTP " length:5];
315        char statusStr[10];
316        long statusCode = [httpResponse statusCode];
317        snprintf(statusStr, sizeof(statusStr), "%ld", statusCode);
318        [theHeaders appendBytes:statusStr length:strlen(statusStr)];
319        [theHeaders appendBytes:" OK\n" length:4];
320
321        // HACK: pass the headers through as UTF-8.
322        // This is not the intended behavior; we're supposed to pass original bytes verbatim.
323        // But we don't have the original bytes, we have NSStrings built by the URL loading system.
324        // It hopefully shouldn't matter, since RFC2616/RFC822 require ASCII-only headers,
325        // but surely someone out there is using non-ASCII characters, and hopefully UTF-8 is adequate here.
326        // It seems better than NSASCIIStringEncoding, which will lose information if non-ASCII is used.
327
328        NSDictionary *headerDict = [httpResponse allHeaderFields];
329        NSArray *keys = [[headerDict allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
330        NSEnumerator *i = [keys objectEnumerator];
331        NSString *k;
332        while ((k = [i nextObject]) != nil) {
333            NSString *v = [headerDict objectForKey:k];
334            [theHeaders appendData:[k dataUsingEncoding:NSUTF8StringEncoding]];
335            [theHeaders appendBytes:": " length:2];
336            [theHeaders appendData:[v dataUsingEncoding:NSUTF8StringEncoding]];
337            [theHeaders appendBytes:"\n" length:1];
338        }
339
340        // If the content is encoded (most likely compressed), then don't send its length to the plugin,
341        // which is only interested in the decoded length, not yet known at the moment.
342        // <rdar://problem/4470599> tracks a request for -[NSURLResponse expectedContentLength] to incorporate this logic.
343        NSString *contentEncoding = (NSString *)[[(NSHTTPURLResponse *)r allHeaderFields] objectForKey:@"Content-Encoding"];
344        if (contentEncoding && ![contentEncoding isEqualToString:@"identity"])
345            expectedContentLength = -1;
346
347        // startStreamResponseURL:... will null-terminate.
348    }
349
350    startStream([r URL], expectedContentLength, WKGetNSURLResponseLastModifiedDate(r), response.mimeType(), theHeaders);
351}
352
353void WebNetscapePluginStream::startStreamWithResponse(NSURLResponse *response)
354{
355    didReceiveResponse(0, response);
356}
357
358bool WebNetscapePluginStream::wantsAllStreams() const
359{
360    if (!m_pluginFuncs->getvalue)
361        return false;
362
363    void *value = 0;
364    NPError error;
365    {
366        PluginStopDeferrer deferrer(m_pluginView.get());
367        JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly);
368        error = m_pluginFuncs->getvalue(m_plugin, NPPVpluginWantsAllNetworkStreams, &value);
369    }
370    if (error != NPERR_NO_ERROR)
371        return false;
372
373    return value;
374}
375
376void WebNetscapePluginStream::destroyStream()
377{
378    if (m_isTerminated)
379        return;
380
381    RefPtr<WebNetscapePluginStream> protect(this);
382
383    ASSERT(m_reason != WEB_REASON_NONE);
384    ASSERT([m_deliveryData.get() length] == 0);
385
386    m_deliverDataTimer.stop();
387
388    if (m_stream.ndata) {
389        if (m_reason == NPRES_DONE && (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY)) {
390            ASSERT(m_fileDescriptor == -1);
391            ASSERT(m_path);
392            NSString *carbonPath = CarbonPathFromPOSIXPath(m_path.get());
393            ASSERT(carbonPath != NULL);
394
395            PluginStopDeferrer deferrer(m_pluginView.get());
396            m_pluginFuncs->asfile(m_plugin, &m_stream, [carbonPath fileSystemRepresentation]);
397            LOG(Plugins, "NPP_StreamAsFile responseURL=%@ path=%s", m_responseURL.get(), carbonPath);
398        }
399
400        if (m_path) {
401            // Delete the file after calling NPP_StreamAsFile(), instead of in -dealloc/-finalize.  It should be OK
402            // to delete the file here -- NPP_StreamAsFile() is always called immediately before NPP_DestroyStream()
403            // (the stream destruction function), so there can be no expectation that a plugin will read the stream
404            // file asynchronously after NPP_StreamAsFile() is called.
405            unlink([m_path.get() fileSystemRepresentation]);
406            m_path = 0;
407
408            if (m_isTerminated)
409                return;
410        }
411
412        if (m_fileDescriptor != -1) {
413            // The file may still be open if we are destroying the stream before it completed loading.
414            close(m_fileDescriptor);
415            m_fileDescriptor = -1;
416        }
417
418        if (m_newStreamSuccessful) {
419            PluginStopDeferrer deferrer(m_pluginView.get());
420#if !LOG_DISABLED
421            NPError npErr =
422#endif
423            m_pluginFuncs->destroystream(m_plugin, &m_stream, m_reason);
424            LOG(Plugins, "NPP_DestroyStream responseURL=%@ error=%d", m_responseURL.get(), npErr);
425        }
426
427        free(m_headers);
428        m_headers = NULL;
429        m_stream.headers = NULL;
430
431        m_stream.ndata = 0;
432
433        if (m_isTerminated)
434            return;
435    }
436
437    if (m_sendNotification) {
438        // NPP_URLNotify expects the request URL, not the response URL.
439        PluginStopDeferrer deferrer(m_pluginView.get());
440        m_pluginFuncs->urlnotify(m_plugin, m_requestURL.string().utf8().data(), m_reason, m_notifyData);
441        LOG(Plugins, "NPP_URLNotify requestURL=%@ reason=%d", (NSURL *)m_requestURL, m_reason);
442    }
443
444    m_isTerminated = true;
445
446    setPlugin(0);
447}
448
449void WebNetscapePluginStream::destroyStreamWithReason(NPReason reason)
450{
451    m_reason = reason;
452    if (m_reason != NPRES_DONE) {
453        // Stop any pending data from being streamed.
454        [m_deliveryData.get() setLength:0];
455    } else if ([m_deliveryData.get() length] > 0) {
456        // There is more data to be streamed, don't destroy the stream now.
457        return;
458    }
459
460    RefPtr<WebNetscapePluginStream> protect(this);
461    destroyStream();
462    ASSERT(!m_stream.ndata);
463}
464
465void WebNetscapePluginStream::cancelLoadWithError(NSError *error)
466{
467    if (m_frameLoader) {
468        ASSERT(!m_loader);
469
470        DocumentLoader* documentLoader = m_frameLoader->activeDocumentLoader();
471        ASSERT(documentLoader);
472
473        if (documentLoader->isLoadingMainResource())
474            documentLoader->cancelMainResourceLoad(error);
475        return;
476    }
477
478    if (!m_loader->isDone())
479        m_loader->cancel(error);
480}
481
482void WebNetscapePluginStream::destroyStreamWithError(NSError *error)
483{
484    destroyStreamWithReason(reasonForError(error));
485}
486
487void WebNetscapePluginStream::didFail(WebCore::NetscapePlugInStreamLoader*, const WebCore::ResourceError& error)
488{
489    destroyStreamWithError(error);
490}
491
492void WebNetscapePluginStream::cancelLoadAndDestroyStreamWithError(NSError *error)
493{
494    RefPtr<WebNetscapePluginStream> protect(this);
495    cancelLoadWithError(error);
496    destroyStreamWithError(error);
497    setPlugin(0);
498}
499
500void WebNetscapePluginStream::deliverData()
501{
502    if (!m_stream.ndata || [m_deliveryData.get() length] == 0)
503        return;
504
505    RefPtr<WebNetscapePluginStream> protect(this);
506
507    int32_t totalBytes = [m_deliveryData.get() length];
508    int32_t totalBytesDelivered = 0;
509
510    while (totalBytesDelivered < totalBytes) {
511        PluginStopDeferrer deferrer(m_pluginView.get());
512        int32_t deliveryBytes = m_pluginFuncs->writeready(m_plugin, &m_stream);
513        LOG(Plugins, "NPP_WriteReady responseURL=%@ bytes=%d", m_responseURL.get(), deliveryBytes);
514
515        if (m_isTerminated)
516            return;
517
518        if (deliveryBytes <= 0) {
519            // Plug-in can't receive anymore data right now. Send it later.
520            if (!m_deliverDataTimer.isActive())
521                m_deliverDataTimer.startOneShot(0);
522            break;
523        } else {
524            deliveryBytes = min(deliveryBytes, totalBytes - totalBytesDelivered);
525            NSData *subdata = [m_deliveryData.get() subdataWithRange:NSMakeRange(totalBytesDelivered, deliveryBytes)];
526            PluginStopDeferrer deferrer(m_pluginView.get());
527            deliveryBytes = m_pluginFuncs->write(m_plugin, &m_stream, m_offset, [subdata length], (void *)[subdata bytes]);
528            if (deliveryBytes < 0) {
529                // Netscape documentation says that a negative result from NPP_Write means cancel the load.
530                cancelLoadAndDestroyStreamWithError(pluginCancelledConnectionError());
531                return;
532            }
533            deliveryBytes = min<int32_t>(deliveryBytes, [subdata length]);
534            m_offset += deliveryBytes;
535            totalBytesDelivered += deliveryBytes;
536            LOG(Plugins, "NPP_Write responseURL=%@ bytes=%d total-delivered=%d/%d", m_responseURL.get(), deliveryBytes, m_offset, m_stream.end);
537        }
538    }
539
540    if (totalBytesDelivered > 0) {
541        if (totalBytesDelivered < totalBytes) {
542            NSMutableData *newDeliveryData = [[NSMutableData alloc] initWithCapacity:totalBytes - totalBytesDelivered];
543            [newDeliveryData appendBytes:(char *)[m_deliveryData.get() bytes] + totalBytesDelivered length:totalBytes - totalBytesDelivered];
544
545            m_deliveryData.adoptNS(newDeliveryData);
546        } else {
547            [m_deliveryData.get() setLength:0];
548            if (m_reason != WEB_REASON_NONE)
549                destroyStream();
550        }
551    }
552}
553
554void WebNetscapePluginStream::deliverDataTimerFired(WebCore::Timer<WebNetscapePluginStream>* timer)
555{
556    deliverData();
557}
558
559void WebNetscapePluginStream::deliverDataToFile(NSData *data)
560{
561    if (m_fileDescriptor == -1 && !m_path) {
562        NSString *temporaryFileMask = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPlugInStreamXXXXXX"];
563        char *temporaryFileName = strdup([temporaryFileMask fileSystemRepresentation]);
564        m_fileDescriptor = mkstemp(temporaryFileName);
565        if (m_fileDescriptor == -1) {
566            LOG_ERROR("Can't create a temporary file.");
567            // This is not a network error, but the only error codes are "network error" and "user break".
568            destroyStreamWithReason(NPRES_NETWORK_ERR);
569            free(temporaryFileName);
570            return;
571        }
572
573        m_path.adoptNS([[NSString stringWithUTF8String:temporaryFileName] retain]);
574        free(temporaryFileName);
575    }
576
577    int dataLength = [data length];
578    if (!dataLength)
579        return;
580
581    int byteCount = write(m_fileDescriptor, [data bytes], dataLength);
582    if (byteCount != dataLength) {
583        // This happens only rarely, when we are out of disk space or have a disk I/O error.
584        LOG_ERROR("error writing to temporary file, errno %d", errno);
585        close(m_fileDescriptor);
586        m_fileDescriptor = -1;
587
588        // This is not a network error, but the only error codes are "network error" and "user break".
589        destroyStreamWithReason(NPRES_NETWORK_ERR);
590        m_path = 0;
591    }
592}
593
594void WebNetscapePluginStream::didFinishLoading(NetscapePlugInStreamLoader*)
595{
596    if (!m_stream.ndata)
597        return;
598
599    if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY) {
600        // Fake the delivery of an empty data to ensure that the file has been created
601        deliverDataToFile([NSData data]);
602        if (m_fileDescriptor != -1)
603            close(m_fileDescriptor);
604        m_fileDescriptor = -1;
605    }
606
607    destroyStreamWithReason(NPRES_DONE);
608}
609
610void WebNetscapePluginStream::didReceiveData(NetscapePlugInStreamLoader*, const char* bytes, int length)
611{
612    NSData *data = [[NSData alloc] initWithBytesNoCopy:(void*)bytes length:length freeWhenDone:NO];
613
614    ASSERT([data length] > 0);
615
616    if (m_transferMode != NP_ASFILEONLY) {
617        if (!m_deliveryData)
618            m_deliveryData.adoptNS([[NSMutableData alloc] initWithCapacity:[data length]]);
619        [m_deliveryData.get() appendData:data];
620        deliverData();
621    }
622    if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY)
623        deliverDataToFile(data);
624
625    [data release];
626}
627
628static NSString *CarbonPathFromPOSIXPath(NSString *posixPath)
629{
630    // Doesn't add a trailing colon for directories; this is a problem for paths to a volume,
631    // so this function would need to be revised if we ever wanted to call it with that.
632
633    CFURLRef url = (CFURLRef)[NSURL fileURLWithPath:posixPath];
634    if (!url)
635        return nil;
636
637    return WebCFAutorelease(CFURLCopyFileSystemPath(url, kCFURLHFSPathStyle));
638}
639
640#endif
641