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