Remove the Timer parameters from timer callbacks
[WebKit-https.git] / Source / WebKit / mac / Plugins / WebNetscapePluginStream.mm
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 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 "WebFrameInternal.h"
33 #import "WebKitErrorsPrivate.h"
34 #import "WebKitLogging.h"
35 #import "WebNSObjectExtras.h"
36 #import "WebNSURLExtras.h"
37 #import "WebNSURLRequestExtras.h"
38 #import "WebNetscapePluginPackage.h"
39 #import "WebNetscapePluginView.h"
40 #import <Foundation/NSURLResponse.h>
41 #import <WebCore/Document.h>
42 #import <WebCore/DocumentLoader.h>
43 #import <WebCore/Frame.h>
44 #import <WebCore/FrameLoader.h>
45 #import <WebCore/JSDOMWindowBase.h>
46 #import <WebCore/ResourceLoadScheduler.h>
47 #import <WebCore/SecurityOrigin.h>
48 #import <WebCore/SecurityPolicy.h>
49 #import <WebCore/WebCoreObjCExtras.h>
50 #import <WebCore/WebCoreURLResponse.h>
51 #import <WebKitSystemInterface.h>
52 #import <runtime/JSLock.h>
53 #import <wtf/HashMap.h>
54 #import <wtf/StdLibExtras.h>
55
56 using namespace WebCore;
57
58 #define WEB_REASON_NONE -1
59
60 class PluginStopDeferrer {
61 public:
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     
76 private:
77     RetainPtr<WebNetscapePluginView> m_pluginView;
78 };
79
80 typedef HashMap<NPStream*, NPP> StreamMap;
81 static StreamMap& streams()
82 {
83     DEPRECATED_DEFINE_STATIC_LOCAL(StreamMap, staticStreams, ());
84     return staticStreams;
85 }
86
87 NPP WebNetscapePluginStream::ownerForStream(NPStream *stream)
88 {
89     return streams().get(stream);
90 }
91
92 NPReason 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
103 NSError *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
112 NSError *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
125 WebNetscapePluginStream::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
143 WebNetscapePluginStream::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     String referrer = SecurityPolicy::generateReferrerHeader(core([view webFrame])->document()->referrerPolicy(), [request URL], core([view webFrame])->loader().outgoingReferrer());
175     if (referrer.isEmpty())
176         [m_request.get() _web_setHTTPReferrer:nil];
177     else
178         [m_request.get() _web_setHTTPReferrer:referrer];
179 }
180
181 WebNetscapePluginStream::~WebNetscapePluginStream()
182 {
183     ASSERT(!m_plugin);
184     ASSERT(m_isTerminated);
185     ASSERT(!m_stream.ndata);
186     
187     // The stream file should have been deleted, and the path freed, in -_destroyStream
188     ASSERT(!m_path);
189     ASSERT(m_fileDescriptor == -1);
190     
191     free((void *)m_stream.url);
192     free(m_headers);
193     
194     streams().remove(&m_stream);
195 }
196
197 void WebNetscapePluginStream::setPlugin(NPP plugin)
198 {
199     if (plugin) {
200         m_plugin = plugin;
201         m_pluginView = static_cast<WebNetscapePluginView *>(m_plugin->ndata);
202
203         WebNetscapePluginPackage *pluginPackage = [m_pluginView.get() pluginPackage];
204         
205         m_pluginFuncs = [pluginPackage pluginFuncs];
206     } else {
207         WebNetscapePluginView *view = m_pluginView.get();
208         m_plugin = 0;
209         m_pluginFuncs = 0;
210         
211         [view disconnectStream:this];
212         m_pluginView = 0;
213     }        
214 }
215
216 void WebNetscapePluginStream::startStream(NSURL *url, long long expectedContentLength, NSDate *lastModifiedDate, const String& mimeType, NSData *headers)
217 {
218     ASSERT(!m_isTerminated);
219     
220     m_responseURL = url;
221     m_mimeType = mimeType.utf8();
222     
223     free((void *)m_stream.url);
224     m_stream.url = strdup([m_responseURL.get() _web_URLCString]);
225
226     m_stream.ndata = this;
227     m_stream.end = expectedContentLength > 0 ? (uint32_t)expectedContentLength : 0;
228     m_stream.lastmodified = (uint32_t)[lastModifiedDate timeIntervalSince1970];
229     m_stream.notifyData = m_notifyData;
230
231     if (headers) {
232         unsigned len = [headers length];
233         m_headers = (char*) malloc(len + 1);
234         [headers getBytes:m_headers length:len];
235         m_headers[len] = 0;
236         m_stream.headers = m_headers;
237     }
238     
239     m_transferMode = NP_NORMAL;
240     m_offset = 0;
241     m_reason = WEB_REASON_NONE;
242     // FIXME: If WebNetscapePluginStream called our initializer we wouldn't have to do this here.
243     m_fileDescriptor = -1;
244
245     // FIXME: Need a way to check if stream is seekable
246
247     NPError npErr;
248     {
249         PluginStopDeferrer deferrer(m_pluginView.get());
250         npErr = m_pluginFuncs->newstream(m_plugin, m_mimeType.mutableData(), &m_stream, NO, &m_transferMode);
251     }
252
253     LOG(Plugins, "NPP_NewStream URL=%@ MIME=%s error=%d", m_responseURL.get(), m_mimeType.data(), npErr);
254
255     if (npErr != NPERR_NO_ERROR) {
256         LOG_ERROR("NPP_NewStream failed with error: %d responseURL: %@", npErr, m_responseURL.get());
257         // Calling cancelLoadWithError: cancels the load, but doesn't call NPP_DestroyStream.
258         cancelLoadWithError(pluginCancelledConnectionError());
259         return;
260     }
261
262     m_newStreamSuccessful = true;
263
264     switch (m_transferMode) {
265         case NP_NORMAL:
266             LOG(Plugins, "Stream type: NP_NORMAL");
267             break;
268         case NP_ASFILEONLY:
269             LOG(Plugins, "Stream type: NP_ASFILEONLY");
270             break;
271         case NP_ASFILE:
272             LOG(Plugins, "Stream type: NP_ASFILE");
273             break;
274         case NP_SEEK:
275             LOG_ERROR("Stream type: NP_SEEK not yet supported");
276             cancelLoadAndDestroyStreamWithError(pluginCancelledConnectionError());
277             break;
278         default:
279             LOG_ERROR("unknown stream type");
280     }
281 }
282
283 void WebNetscapePluginStream::start()
284 {
285     ASSERT(m_request);
286     ASSERT(!m_frameLoader);
287     ASSERT(!m_loader);
288     
289     m_loader = resourceLoadScheduler()->schedulePluginStreamLoad(core([m_pluginView.get() webFrame]), this, m_request.get());
290 }
291
292 void WebNetscapePluginStream::stop()
293 {
294     ASSERT(!m_frameLoader);
295     
296     if (!m_loader->isDone())
297         cancelLoadAndDestroyStreamWithError(m_loader->cancelledError());
298 }
299
300 void 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), response.mimeType(), theHeaders);
354 }
355
356 void WebNetscapePluginStream::startStreamWithResponse(NSURLResponse *response)
357 {
358     didReceiveResponse(0, response);
359 }
360
361 bool 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(JSDOMWindowBase::commonVM());
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
379 void WebNetscapePluginStream::destroyStream()
380 {
381     if (m_isTerminated)
382         return;
383
384     Ref<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
396             PluginStopDeferrer deferrer(m_pluginView.get());
397             m_pluginFuncs->asfile(m_plugin, &m_stream, [m_path.get() fileSystemRepresentation]);
398             LOG(Plugins, "NPP_StreamAsFile responseURL=%@ path=%s", m_responseURL.get(), m_path.get());
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.string().utf8().data(), m_reason, m_notifyData);
442         LOG(Plugins, "NPP_URLNotify requestURL=%@ reason=%d", (NSURL *)m_requestURL, m_reason);
443     }
444
445     m_isTerminated = true;
446
447     setPlugin(0);
448 }
449
450 void 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     Ref<WebNetscapePluginStream> protect(*this);
462     destroyStream();
463     ASSERT(!m_stream.ndata);
464 }
465
466 void 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
483 void WebNetscapePluginStream::destroyStreamWithError(NSError *error)
484 {
485     destroyStreamWithReason(reasonForError(error));
486 }
487
488 void WebNetscapePluginStream::didFail(WebCore::NetscapePlugInStreamLoader*, const WebCore::ResourceError& error)
489 {
490     destroyStreamWithError(error);
491 }
492
493 void WebNetscapePluginStream::cancelLoadAndDestroyStreamWithError(NSError *error)
494 {
495     Ref<WebNetscapePluginStream> protect(*this);
496     cancelLoadWithError(error);
497     destroyStreamWithError(error);
498     setPlugin(0);
499 }    
500
501 void WebNetscapePluginStream::deliverData()
502 {
503     if (!m_stream.ndata || [m_deliveryData.get() length] == 0)
504         return;
505
506     Ref<WebNetscapePluginStream> protect(*this);
507
508     int32_t totalBytes = [m_deliveryData.get() length];
509     int32_t totalBytesDelivered = 0;
510
511     while (totalBytesDelivered < totalBytes) {
512         PluginStopDeferrer deferrer(m_pluginView.get());
513         int32_t 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 = std::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 = std::min<int32_t>(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
555 void WebNetscapePluginStream::deliverDataTimerFired()
556 {
557     deliverData();
558 }
559
560 void 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 = [NSString stringWithUTF8String:temporaryFileName];
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
595 void 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
611 void 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
629 #endif