Home | History | Annotate | Download | only in Netscape
      1 /*
      2  * Copyright (C) 2010 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  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
     14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
     15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
     17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
     23  * THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "NetscapePluginStream.h"
     28 
     29 #include "NetscapePlugin.h"
     30 #include <utility>
     31 
     32 using namespace WebCore;
     33 using namespace std;
     34 
     35 namespace WebKit {
     36 
     37 NetscapePluginStream::NetscapePluginStream(PassRefPtr<NetscapePlugin> plugin, uint64_t streamID, bool sendNotification, void* notificationData)
     38     : m_plugin(plugin)
     39     , m_streamID(streamID)
     40     , m_sendNotification(sendNotification)
     41     , m_notificationData(notificationData)
     42     , m_npStream()
     43     , m_transferMode(NP_NORMAL)
     44     , m_offset(0)
     45     , m_fileHandle(invalidPlatformFileHandle)
     46     , m_isStarted(false)
     47 #if !ASSERT_DISABLED
     48     , m_urlNotifyHasBeenCalled(false)
     49 #endif
     50     , m_deliveryDataTimer(RunLoop::main(), this, &NetscapePluginStream::deliverDataToPlugin)
     51     , m_stopStreamWhenDoneDelivering(false)
     52 {
     53 }
     54 
     55 NetscapePluginStream::~NetscapePluginStream()
     56 {
     57     ASSERT(!m_isStarted);
     58     ASSERT(!m_sendNotification || m_urlNotifyHasBeenCalled);
     59     ASSERT(m_fileHandle == invalidPlatformFileHandle);
     60 }
     61 
     62 void NetscapePluginStream::didReceiveResponse(const KURL& responseURL, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers)
     63 {
     64     // Starting the stream could cause the plug-in stream to go away so we keep a reference to it here.
     65     RefPtr<NetscapePluginStream> protect(this);
     66 
     67     start(responseURL, streamLength, lastModifiedTime, mimeType, headers);
     68 }
     69 
     70 void NetscapePluginStream::didReceiveData(const char* bytes, int length)
     71 {
     72     // Delivering the data could cause the plug-in stream to go away so we keep a reference to it here.
     73     RefPtr<NetscapePluginStream> protect(this);
     74 
     75     deliverData(bytes, length);
     76 }
     77 
     78 void NetscapePluginStream::didFinishLoading()
     79 {
     80     // Stopping the stream could cause the plug-in stream to go away so we keep a reference to it here.
     81     RefPtr<NetscapePluginStream> protect(this);
     82 
     83     stop(NPRES_DONE);
     84 }
     85 
     86 void NetscapePluginStream::didFail(bool wasCancelled)
     87 {
     88     // Stopping the stream could cause the plug-in stream to go away so we keep a reference to it here.
     89     RefPtr<NetscapePluginStream> protect(this);
     90 
     91     stop(wasCancelled ? NPRES_USER_BREAK : NPRES_NETWORK_ERR);
     92 }
     93 
     94 void NetscapePluginStream::sendJavaScriptStream(const String& requestURLString, const String& result)
     95 {
     96     // starting the stream or delivering the data to it might cause the plug-in stream to go away, so we keep
     97     // a reference to it here.
     98     RefPtr<NetscapePluginStream> protect(this);
     99 
    100     CString resultCString = requestURLString.utf8();
    101     if (resultCString.isNull()) {
    102         // There was an error evaluating the JavaScript, call NPP_URLNotify if needed and then destroy the stream.
    103         notifyAndDestroyStream(NPRES_NETWORK_ERR);
    104         return;
    105     }
    106 
    107     if (!start(requestURLString, resultCString.length(), 0, "text/plain", ""))
    108         return;
    109 
    110     deliverData(resultCString.data(), resultCString.length());
    111     stop(NPRES_DONE);
    112 }
    113 
    114 NPError NetscapePluginStream::destroy(NPReason reason)
    115 {
    116     // It doesn't make sense to call NPN_DestroyStream on a stream that hasn't been started yet.
    117     if (!m_isStarted)
    118         return NPERR_GENERIC_ERROR;
    119 
    120     // It isn't really valid for a plug-in to call NPN_DestroyStream with NPRES_DONE.
    121     // (At least not for browser initiated streams, and we don't support plug-in initiated streams).
    122     if (reason == NPRES_DONE)
    123         return NPERR_INVALID_PARAM;
    124 
    125     cancel();
    126     stop(reason);
    127     return NPERR_NO_ERROR;
    128 }
    129 
    130 static bool isSupportedTransferMode(uint16_t transferMode)
    131 {
    132     switch (transferMode) {
    133     case NP_ASFILEONLY:
    134     case NP_ASFILE:
    135     case NP_NORMAL:
    136         return true;
    137     // FIXME: We don't support seekable streams.
    138     case NP_SEEK:
    139         return false;
    140     }
    141 
    142     ASSERT_NOT_REACHED();
    143     return false;
    144 }
    145 
    146 bool NetscapePluginStream::start(const String& responseURLString, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers)
    147 {
    148     m_responseURL = responseURLString.utf8();
    149     m_mimeType = mimeType.utf8();
    150     m_headers = headers.utf8();
    151 
    152     m_npStream.ndata = this;
    153     m_npStream.url = m_responseURL.data();
    154     m_npStream.end = streamLength;
    155     m_npStream.lastmodified = lastModifiedTime;
    156     m_npStream.notifyData = m_notificationData;
    157     m_npStream.headers = m_headers.length() == 0 ? 0 : m_headers.data();
    158 
    159     NPError error = m_plugin->NPP_NewStream(const_cast<char*>(m_mimeType.data()), &m_npStream, false, &m_transferMode);
    160     if (error != NPERR_NO_ERROR) {
    161         // We failed to start the stream, cancel the load and destroy it.
    162         cancel();
    163         notifyAndDestroyStream(NPRES_NETWORK_ERR);
    164         return false;
    165     }
    166 
    167     // We successfully started the stream.
    168     m_isStarted = true;
    169 
    170     if (!isSupportedTransferMode(m_transferMode)) {
    171         // Cancel the load and stop the stream.
    172         cancel();
    173         stop(NPRES_NETWORK_ERR);
    174         return false;
    175     }
    176 
    177     return true;
    178 }
    179 
    180 void NetscapePluginStream::deliverData(const char* bytes, int length)
    181 {
    182     ASSERT(m_isStarted);
    183 
    184     if (m_transferMode != NP_ASFILEONLY) {
    185         if (!m_deliveryData)
    186             m_deliveryData.set(new Vector<uint8_t>);
    187 
    188         m_deliveryData->reserveCapacity(m_deliveryData->size() + length);
    189         m_deliveryData->append(bytes, length);
    190 
    191         deliverDataToPlugin();
    192     }
    193 
    194     if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY)
    195         deliverDataToFile(bytes, length);
    196 }
    197 
    198 void NetscapePluginStream::deliverDataToPlugin()
    199 {
    200     ASSERT(m_isStarted);
    201 
    202     int32_t numBytesToDeliver = m_deliveryData->size();
    203     int32_t numBytesDelivered = 0;
    204 
    205     while (numBytesDelivered < numBytesToDeliver) {
    206         int32_t numBytesPluginCanHandle = m_plugin->NPP_WriteReady(&m_npStream);
    207 
    208         // NPP_WriteReady could call NPN_DestroyStream and destroy the stream.
    209         if (!m_isStarted)
    210             return;
    211 
    212         if (numBytesPluginCanHandle <= 0) {
    213             // The plug-in can't handle more data, we'll send the rest later
    214             m_deliveryDataTimer.startOneShot(0);
    215             break;
    216         }
    217 
    218         // Figure out how much data to send to the plug-in.
    219         int32_t dataLength = min(numBytesPluginCanHandle, numBytesToDeliver - numBytesDelivered);
    220         uint8_t* data = m_deliveryData->data() + numBytesDelivered;
    221 
    222         int32_t numBytesWritten = m_plugin->NPP_Write(&m_npStream, m_offset, dataLength, data);
    223         if (numBytesWritten < 0) {
    224             cancel();
    225             stop(NPRES_NETWORK_ERR);
    226             return;
    227         }
    228 
    229         // NPP_Write could call NPN_DestroyStream and destroy the stream.
    230         if (!m_isStarted)
    231             return;
    232 
    233         numBytesWritten = min(numBytesWritten, dataLength);
    234         m_offset += numBytesWritten;
    235         numBytesDelivered += numBytesWritten;
    236     }
    237 
    238     // We didn't write anything.
    239     if (!numBytesDelivered)
    240         return;
    241 
    242     if (numBytesDelivered < numBytesToDeliver) {
    243         // Remove the bytes that we actually delivered.
    244         m_deliveryData->remove(0, numBytesDelivered);
    245     } else {
    246         m_deliveryData->clear();
    247 
    248         if (m_stopStreamWhenDoneDelivering)
    249             stop(NPRES_DONE);
    250     }
    251 }
    252 
    253 void NetscapePluginStream::deliverDataToFile(const char* bytes, int length)
    254 {
    255     if (m_fileHandle == invalidPlatformFileHandle && m_filePath.isNull()) {
    256         // Create a temporary file.
    257         m_filePath = openTemporaryFile("WebKitPluginStream", m_fileHandle);
    258 
    259         // We failed to open the file, stop the stream.
    260         if (m_fileHandle == invalidPlatformFileHandle) {
    261             stop(NPRES_NETWORK_ERR);
    262             return;
    263         }
    264     }
    265 
    266     if (!length)
    267         return;
    268 
    269     int byteCount = writeToFile(m_fileHandle, bytes, length);
    270     if (byteCount != length) {
    271         // This happens only rarely, when we are out of disk space or have a disk I/O error.
    272         closeFile(m_fileHandle);
    273 
    274         stop(NPRES_NETWORK_ERR);
    275     }
    276 }
    277 
    278 void NetscapePluginStream::stop(NPReason reason)
    279 {
    280     // The stream was stopped before it got a chance to start. This can happen if a stream is cancelled by
    281     // WebKit before it received a response.
    282     if (!m_isStarted)
    283         return;
    284 
    285     if (reason == NPRES_DONE && m_deliveryData && !m_deliveryData->isEmpty()) {
    286         // There is still data left that the plug-in hasn't been able to consume yet.
    287         ASSERT(m_deliveryDataTimer.isActive());
    288 
    289         // Set m_stopStreamWhenDoneDelivering to true so that the next time the delivery timer fires
    290         // and calls deliverDataToPlugin the stream will be closed if all the remaining data was
    291         // successfully delivered.
    292         m_stopStreamWhenDoneDelivering = true;
    293         return;
    294     }
    295 
    296     m_deliveryData = 0;
    297     m_deliveryDataTimer.stop();
    298 
    299     if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY) {
    300         if (reason == NPRES_DONE) {
    301             // Ensure that the file is created.
    302             deliverDataToFile(0, 0);
    303             if (m_fileHandle != invalidPlatformFileHandle)
    304                 closeFile(m_fileHandle);
    305 
    306             ASSERT(!m_filePath.isNull());
    307 
    308             m_plugin->NPP_StreamAsFile(&m_npStream, m_filePath.utf8().data());
    309         } else {
    310             // Just close the file.
    311             if (m_fileHandle != invalidPlatformFileHandle)
    312                 closeFile(m_fileHandle);
    313         }
    314 
    315         // Delete the file after calling NPP_StreamAsFile(), instead of in the destructor.  It should be OK
    316         // to delete the file here -- NPP_StreamAsFile() is always called immediately before NPP_DestroyStream()
    317         // (the stream destruction function), so there can be no expectation that a plugin will read the stream
    318         // file asynchronously after NPP_StreamAsFile() is called.
    319         deleteFile(m_filePath);
    320         m_filePath = String();
    321 
    322         // NPP_StreamAsFile could call NPN_DestroyStream and destroy the stream.
    323         if (!m_isStarted)
    324             return;
    325     }
    326 
    327     // Set m_isStarted to false before calling NPP_DestroyStream in case NPP_DestroyStream calls NPN_DestroyStream.
    328     m_isStarted = false;
    329 
    330     m_plugin->NPP_DestroyStream(&m_npStream, reason);
    331 
    332     notifyAndDestroyStream(reason);
    333 }
    334 
    335 void NetscapePluginStream::cancel()
    336 {
    337     m_plugin->cancelStreamLoad(this);
    338 }
    339 
    340 void NetscapePluginStream::notifyAndDestroyStream(NPReason reason)
    341 {
    342     ASSERT(!m_isStarted);
    343     ASSERT(!m_deliveryDataTimer.isActive());
    344     ASSERT(!m_urlNotifyHasBeenCalled);
    345 
    346     if (m_sendNotification) {
    347         m_plugin->NPP_URLNotify(m_responseURL.data(), reason, m_notificationData);
    348 
    349 #if !ASSERT_DISABLED
    350         m_urlNotifyHasBeenCalled = true;
    351 #endif
    352     }
    353 
    354     m_plugin->removePluginStream(this);
    355 }
    356 
    357 } // namespace WebKit
    358