Document::securityOrigin() should return a reference.
[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 "WebResourceLoadScheduler.h"
41 #import <Foundation/NSURLResponse.h>
42 #import <WebCore/CFNetworkSPI.h>
43 #import <WebCore/CommonVM.h>
44 #import <WebCore/Document.h>
45 #import <WebCore/DocumentLoader.h>
46 #import <WebCore/Frame.h>
47 #import <WebCore/FrameLoader.h>
48 #import <WebCore/JSDOMWindowBase.h>
49 #import <WebCore/LoaderStrategy.h>
50 #import <WebCore/PlatformStrategies.h>
51 #import <WebCore/SecurityOrigin.h>
52 #import <WebCore/SecurityPolicy.h>
53 #import <WebCore/WebCoreObjCExtras.h>
54 #import <WebCore/WebCoreURLResponse.h>
55 #import <WebKitSystemInterface.h>
56 #import <runtime/JSLock.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     m_loader = webResourceLoadScheduler().schedulePluginStreamLoad(*core([m_pluginView.get() webFrame]), *this, m_request.get());
295 }
296
297 void WebNetscapePluginStream::stop()
298 {
299     ASSERT(!m_frameLoader);
300     
301     if (!m_loader->isDone())
302         cancelLoadAndDestroyStreamWithError(m_loader->cancelledError());
303 }
304
305 void WebNetscapePluginStream::willSendRequest(NetscapePlugInStreamLoader*, ResourceRequest&& request, const ResourceResponse&, std::function<void (WebCore::ResourceRequest&&)>&& callback)
306 {
307     // FIXME: We should notify the plug-in with NPP_URLRedirectNotify here.
308     callback(WTFMove(request));
309 }
310
311 void WebNetscapePluginStream::didReceiveResponse(NetscapePlugInStreamLoader*, const ResourceResponse& response)
312 {
313     NSURLResponse *r = response.nsURLResponse();
314     
315     NSMutableData *theHeaders = nil;
316     long long expectedContentLength = [r expectedContentLength];
317     
318     if ([r isKindOfClass:[NSHTTPURLResponse class]]) {
319         NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)r;
320         theHeaders = [NSMutableData dataWithCapacity:1024];
321         
322         // FIXME: it would be nice to be able to get the raw HTTP header block.
323         // This includes the HTTP version, the real status text,
324         // all headers in their original order and including duplicates,
325         // and all original bytes verbatim, rather than sent through Unicode translation.
326         // Unfortunately NSHTTPURLResponse doesn't provide access at that low a level.
327         
328         [theHeaders appendBytes:"HTTP " length:5];
329         char statusStr[10];
330         long statusCode = [httpResponse statusCode];
331         snprintf(statusStr, sizeof(statusStr), "%ld", statusCode);
332         [theHeaders appendBytes:statusStr length:strlen(statusStr)];
333         [theHeaders appendBytes:" OK\n" length:4];
334         
335         // HACK: pass the headers through as UTF-8.
336         // This is not the intended behavior; we're supposed to pass original bytes verbatim.
337         // But we don't have the original bytes, we have NSStrings built by the URL loading system.
338         // It hopefully shouldn't matter, since RFC2616/RFC822 require ASCII-only headers,
339         // but surely someone out there is using non-ASCII characters, and hopefully UTF-8 is adequate here.
340         // It seems better than NSASCIIStringEncoding, which will lose information if non-ASCII is used.
341         
342         NSDictionary *headerDict = [httpResponse allHeaderFields];
343         NSArray *keys = [[headerDict allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
344         NSEnumerator *i = [keys objectEnumerator];
345         NSString *k;
346         while ((k = [i nextObject]) != nil) {
347             NSString *v = [headerDict objectForKey:k];
348             [theHeaders appendData:[k dataUsingEncoding:NSUTF8StringEncoding]];
349             [theHeaders appendBytes:": " length:2];
350             [theHeaders appendData:[v dataUsingEncoding:NSUTF8StringEncoding]];
351             [theHeaders appendBytes:"\n" length:1];
352         }
353         
354         // If the content is encoded (most likely compressed), then don't send its length to the plugin,
355         // which is only interested in the decoded length, not yet known at the moment.
356         // <rdar://problem/4470599> tracks a request for -[NSURLResponse expectedContentLength] to incorporate this logic.
357         NSString *contentEncoding = (NSString *)[[(NSHTTPURLResponse *)r allHeaderFields] objectForKey:@"Content-Encoding"];
358         if (contentEncoding && ![contentEncoding isEqualToString:@"identity"])
359             expectedContentLength = -1;
360         
361         // startStreamResponseURL:... will null-terminate.
362     }
363     
364     startStream([r URL], expectedContentLength, [r _lastModifiedDate], response.mimeType(), theHeaders);
365 }
366
367 void WebNetscapePluginStream::startStreamWithResponse(NSURLResponse *response)
368 {
369     didReceiveResponse(0, response);
370 }
371
372 bool WebNetscapePluginStream::wantsAllStreams() const
373 {
374     if (!m_pluginFuncs->getvalue)
375         return false;
376     
377     void *value = 0;
378     NPError error;
379     {
380         PluginStopDeferrer deferrer(m_pluginView.get());
381         JSC::JSLock::DropAllLocks dropAllLocks(commonVM());
382         error = m_pluginFuncs->getvalue(m_plugin, NPPVpluginWantsAllNetworkStreams, &value);
383     }
384     if (error != NPERR_NO_ERROR)
385         return false;
386     
387     return value;
388 }
389
390 void WebNetscapePluginStream::destroyStream()
391 {
392     if (m_isTerminated)
393         return;
394
395     Ref<WebNetscapePluginStream> protect(*this);
396
397     ASSERT(m_reason != WEB_REASON_NONE);
398     ASSERT([m_deliveryData.get() length] == 0);
399     
400     m_deliverDataTimer.stop();
401
402     if (m_stream.ndata) {
403         if (m_reason == NPRES_DONE && (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY)) {
404             ASSERT(m_fileDescriptor == -1);
405             ASSERT(m_path);
406
407             PluginStopDeferrer deferrer(m_pluginView.get());
408             m_pluginFuncs->asfile(m_plugin, &m_stream, [m_path.get() fileSystemRepresentation]);
409             LOG(Plugins, "NPP_StreamAsFile responseURL=%@ path=%s", m_responseURL.get(), m_path.get());
410         }
411
412         if (m_path) {
413             // Delete the file after calling NPP_StreamAsFile(), instead of in -dealloc/-finalize.  It should be OK
414             // to delete the file here -- NPP_StreamAsFile() is always called immediately before NPP_DestroyStream()
415             // (the stream destruction function), so there can be no expectation that a plugin will read the stream
416             // file asynchronously after NPP_StreamAsFile() is called.
417             unlink([m_path.get() fileSystemRepresentation]);
418             m_path = 0;
419
420             if (m_isTerminated)
421                 return;
422         }
423
424         if (m_fileDescriptor != -1) {
425             // The file may still be open if we are destroying the stream before it completed loading.
426             close(m_fileDescriptor);
427             m_fileDescriptor = -1;
428         }
429
430         if (m_newStreamSuccessful) {
431             PluginStopDeferrer deferrer(m_pluginView.get());
432 #if !LOG_DISABLED
433             NPError npErr = 
434 #endif
435             m_pluginFuncs->destroystream(m_plugin, &m_stream, m_reason);
436             LOG(Plugins, "NPP_DestroyStream responseURL=%@ error=%d", m_responseURL.get(), npErr);
437         }
438
439         free(m_headers);
440         m_headers = NULL;
441         m_stream.headers = NULL;
442
443         m_stream.ndata = 0;
444
445         if (m_isTerminated)
446             return;
447     }
448
449     if (m_sendNotification) {
450         // NPP_URLNotify expects the request URL, not the response URL.
451         PluginStopDeferrer deferrer(m_pluginView.get());
452         m_pluginFuncs->urlnotify(m_plugin, m_requestURL.string().utf8().data(), m_reason, m_notifyData);
453         LOG(Plugins, "NPP_URLNotify requestURL=%@ reason=%d", (NSURL *)m_requestURL, m_reason);
454     }
455
456     m_isTerminated = true;
457
458     setPlugin(0);
459 }
460
461 void WebNetscapePluginStream::destroyStreamWithReason(NPReason reason)
462 {
463     m_reason = reason;
464     if (m_reason != NPRES_DONE) {
465         // Stop any pending data from being streamed.
466         [m_deliveryData.get() setLength:0];
467     } else if ([m_deliveryData.get() length] > 0) {
468         // There is more data to be streamed, don't destroy the stream now.
469         return;
470     }
471
472     Ref<WebNetscapePluginStream> protect(*this);
473     destroyStream();
474     ASSERT(!m_stream.ndata);
475 }
476
477 void WebNetscapePluginStream::cancelLoadWithError(NSError *error)
478 {
479     if (m_frameLoader) {
480         ASSERT(!m_loader);
481         
482         DocumentLoader* documentLoader = m_frameLoader->activeDocumentLoader();
483         ASSERT(documentLoader);
484         
485         if (documentLoader->isLoadingMainResource())
486             documentLoader->cancelMainResourceLoad(error);
487         return;
488     }
489     
490     if (!m_loader->isDone())
491         m_loader->cancel(error);
492 }
493
494 void WebNetscapePluginStream::destroyStreamWithError(NSError *error)
495 {
496     destroyStreamWithReason(reasonForError(error));
497 }
498
499 void WebNetscapePluginStream::didFail(WebCore::NetscapePlugInStreamLoader*, const WebCore::ResourceError& error)
500 {
501     destroyStreamWithError(error);
502 }
503
504 void WebNetscapePluginStream::cancelLoadAndDestroyStreamWithError(NSError *error)
505 {
506     Ref<WebNetscapePluginStream> protect(*this);
507     cancelLoadWithError(error);
508     destroyStreamWithError(error);
509     setPlugin(0);
510 }    
511
512 void WebNetscapePluginStream::deliverData()
513 {
514     if (!m_stream.ndata || [m_deliveryData.get() length] == 0)
515         return;
516
517     Ref<WebNetscapePluginStream> protect(*this);
518
519     int32_t totalBytes = [m_deliveryData.get() length];
520     int32_t totalBytesDelivered = 0;
521
522     while (totalBytesDelivered < totalBytes) {
523         PluginStopDeferrer deferrer(m_pluginView.get());
524         int32_t deliveryBytes = m_pluginFuncs->writeready(m_plugin, &m_stream);
525         LOG(Plugins, "NPP_WriteReady responseURL=%@ bytes=%d", m_responseURL.get(), deliveryBytes);
526
527         if (m_isTerminated)
528             return;
529
530         if (deliveryBytes <= 0) {
531             // Plug-in can't receive anymore data right now. Send it later.
532             if (!m_deliverDataTimer.isActive())
533                 m_deliverDataTimer.startOneShot(0);
534             break;
535         } else {
536             deliveryBytes = std::min(deliveryBytes, totalBytes - totalBytesDelivered);
537             NSData *subdata = [m_deliveryData.get() subdataWithRange:NSMakeRange(totalBytesDelivered, deliveryBytes)];
538             PluginStopDeferrer deferrer(m_pluginView.get());
539             deliveryBytes = m_pluginFuncs->write(m_plugin, &m_stream, m_offset, [subdata length], (void *)[subdata bytes]);
540             if (deliveryBytes < 0) {
541                 // Netscape documentation says that a negative result from NPP_Write means cancel the load.
542                 cancelLoadAndDestroyStreamWithError(pluginCancelledConnectionError());
543                 return;
544             }
545             deliveryBytes = std::min<int32_t>(deliveryBytes, [subdata length]);
546             m_offset += deliveryBytes;
547             totalBytesDelivered += deliveryBytes;
548             LOG(Plugins, "NPP_Write responseURL=%@ bytes=%d total-delivered=%d/%d", m_responseURL.get(), deliveryBytes, m_offset, m_stream.end);
549         }
550     }
551
552     if (totalBytesDelivered > 0) {
553         if (totalBytesDelivered < totalBytes) {
554             NSMutableData *newDeliveryData = [[NSMutableData alloc] initWithCapacity:totalBytes - totalBytesDelivered];
555             [newDeliveryData appendBytes:(char *)[m_deliveryData.get() bytes] + totalBytesDelivered length:totalBytes - totalBytesDelivered];
556             
557             m_deliveryData = adoptNS(newDeliveryData);
558         } else {
559             [m_deliveryData.get() setLength:0];
560             if (m_reason != WEB_REASON_NONE) 
561                 destroyStream();
562         }
563     }
564 }
565
566 void WebNetscapePluginStream::deliverDataToFile(NSData *data)
567 {
568     if (m_fileDescriptor == -1 && !m_path) {
569         NSString *temporaryFileMask = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPlugInStreamXXXXXX"];
570         char *temporaryFileName = strdup([temporaryFileMask fileSystemRepresentation]);
571         m_fileDescriptor = mkstemp(temporaryFileName);
572         if (m_fileDescriptor == -1) {
573             LOG_ERROR("Can't create a temporary file.");
574             // This is not a network error, but the only error codes are "network error" and "user break".
575             destroyStreamWithReason(NPRES_NETWORK_ERR);
576             free(temporaryFileName);
577             return;
578         }
579
580         m_path = [NSString stringWithUTF8String:temporaryFileName];
581         free(temporaryFileName);
582     }
583
584     int dataLength = [data length];
585     if (!dataLength)
586         return;
587
588     int byteCount = write(m_fileDescriptor, [data bytes], dataLength);
589     if (byteCount != dataLength) {
590         // This happens only rarely, when we are out of disk space or have a disk I/O error.
591         LOG_ERROR("error writing to temporary file, errno %d", errno);
592         close(m_fileDescriptor);
593         m_fileDescriptor = -1;
594
595         // This is not a network error, but the only error codes are "network error" and "user break".
596         destroyStreamWithReason(NPRES_NETWORK_ERR);
597         m_path = 0;
598     }
599 }
600
601 void WebNetscapePluginStream::didFinishLoading(NetscapePlugInStreamLoader*)
602 {
603     if (!m_stream.ndata)
604         return;
605     
606     if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY) {
607         // Fake the delivery of an empty data to ensure that the file has been created
608         deliverDataToFile([NSData data]);
609         if (m_fileDescriptor != -1)
610             close(m_fileDescriptor);
611         m_fileDescriptor = -1;
612     }
613     
614     destroyStreamWithReason(NPRES_DONE);
615 }
616
617 void WebNetscapePluginStream::didReceiveData(NetscapePlugInStreamLoader*, const char* bytes, int length)
618 {
619     NSData *data = [[NSData alloc] initWithBytesNoCopy:(void*)bytes length:length freeWhenDone:NO];
620
621     ASSERT([data length] > 0);
622     
623     if (m_transferMode != NP_ASFILEONLY) {
624         if (!m_deliveryData)
625             m_deliveryData = adoptNS([[NSMutableData alloc] initWithCapacity:[data length]]);
626         [m_deliveryData.get() appendData:data];
627         deliverData();
628     }
629     if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY)
630         deliverDataToFile(data);
631     
632     [data release];
633 }
634
635 #endif