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