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