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