Home | History | Annotate | Download | only in plugins
      1 /*
      2  * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
      3  * Copyright (C) 2008 Collabora, Ltd. All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      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  *
     14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     25  */
     26 
     27 #include "config.h"
     28 #include "PluginStream.h"
     29 
     30 #include "CString.h"
     31 #include "DocumentLoader.h"
     32 #include "Frame.h"
     33 #include "FrameLoader.h"
     34 #include "PluginDebug.h"
     35 #include "SharedBuffer.h"
     36 #include "SubresourceLoader.h"
     37 #include <StringExtras.h>
     38 
     39 // We use -2 here because some plugins like to return -1 to indicate error
     40 // and this way we won't clash with them.
     41 static const int WebReasonNone = -2;
     42 
     43 using std::max;
     44 using std::min;
     45 
     46 namespace WebCore {
     47 
     48 typedef HashMap<NPStream*, NPP> StreamMap;
     49 static StreamMap& streams()
     50 {
     51     static StreamMap staticStreams;
     52     return staticStreams;
     53 }
     54 
     55 PluginStream::PluginStream(PluginStreamClient* client, Frame* frame, const ResourceRequest& resourceRequest, bool sendNotification, void* notifyData, const NPPluginFuncs* pluginFuncs, NPP instance, const PluginQuirkSet& quirks)
     56     : m_resourceRequest(resourceRequest)
     57     , m_client(client)
     58     , m_frame(frame)
     59     , m_notifyData(notifyData)
     60     , m_sendNotification(sendNotification)
     61     , m_streamState(StreamBeforeStarted)
     62     , m_loadManually(false)
     63     , m_delayDeliveryTimer(this, &PluginStream::delayDeliveryTimerFired)
     64     , m_deliveryData(0)
     65     , m_tempFileHandle(invalidPlatformFileHandle)
     66     , m_pluginFuncs(pluginFuncs)
     67     , m_instance(instance)
     68     , m_quirks(quirks)
     69 {
     70     ASSERT(m_instance);
     71 
     72     m_stream.url = 0;
     73     m_stream.ndata = 0;
     74     m_stream.pdata = 0;
     75     m_stream.end = 0;
     76     m_stream.notifyData = 0;
     77     m_stream.lastmodified = 0;
     78 
     79     streams().add(&m_stream, m_instance);
     80 }
     81 
     82 PluginStream::~PluginStream()
     83 {
     84     ASSERT(m_streamState != StreamStarted);
     85     ASSERT(!m_loader);
     86 
     87     fastFree((char*)m_stream.url);
     88 
     89     streams().remove(&m_stream);
     90 }
     91 
     92 void PluginStream::start()
     93 {
     94     ASSERT(!m_loadManually);
     95 
     96     m_loader = NetscapePlugInStreamLoader::create(m_frame, this);
     97 
     98     m_loader->setShouldBufferData(false);
     99     m_loader->documentLoader()->addPlugInStreamLoader(m_loader.get());
    100     m_loader->load(m_resourceRequest);
    101 }
    102 
    103 void PluginStream::stop()
    104 {
    105     m_streamState = StreamStopped;
    106 
    107     if (m_loadManually) {
    108         ASSERT(!m_loader);
    109 
    110         DocumentLoader* documentLoader = m_frame->loader()->activeDocumentLoader();
    111         ASSERT(documentLoader);
    112 
    113         if (documentLoader->isLoadingMainResource())
    114             documentLoader->cancelMainResourceLoad(m_frame->loader()->cancelledError(m_resourceRequest));
    115 
    116         return;
    117     }
    118 
    119     if (m_loader) {
    120         m_loader->cancel();
    121         m_loader = 0;
    122     }
    123 
    124     m_client = 0;
    125 }
    126 
    127 void PluginStream::startStream()
    128 {
    129     ASSERT(m_streamState == StreamBeforeStarted);
    130 
    131     const KURL& responseURL = m_resourceResponse.url();
    132 
    133     // Some plugins (Flash) expect that javascript URLs are passed back decoded as this is the
    134     // format used when requesting the URL.
    135     if (protocolIsJavaScript(responseURL))
    136         m_stream.url = fastStrDup(decodeURLEscapeSequences(responseURL.string()).utf8().data());
    137     else
    138         m_stream.url = fastStrDup(responseURL.string().utf8().data());
    139 
    140     CString mimeTypeStr = m_resourceResponse.mimeType().utf8();
    141 
    142     long long expectedContentLength = m_resourceResponse.expectedContentLength();
    143 
    144     if (m_resourceResponse.isHTTP()) {
    145         Vector<UChar> stringBuilder;
    146         String separator(": ");
    147 
    148         String statusLine = String::format("HTTP %d OK\n", m_resourceResponse.httpStatusCode());
    149 
    150         stringBuilder.append(statusLine.characters(), statusLine.length());
    151 
    152         HTTPHeaderMap::const_iterator end = m_resourceResponse.httpHeaderFields().end();
    153         for (HTTPHeaderMap::const_iterator it = m_resourceResponse.httpHeaderFields().begin(); it != end; ++it) {
    154             stringBuilder.append(it->first.characters(), it->first.length());
    155             stringBuilder.append(separator.characters(), separator.length());
    156             stringBuilder.append(it->second.characters(), it->second.length());
    157             stringBuilder.append('\n');
    158         }
    159 
    160         m_headers = String::adopt(stringBuilder).utf8();
    161 
    162         // If the content is encoded (most likely compressed), then don't send its length to the plugin,
    163         // which is only interested in the decoded length, not yet known at the moment.
    164         // <rdar://problem/4470599> tracks a request for -[NSURLResponse expectedContentLength] to incorporate this logic.
    165         String contentEncoding = m_resourceResponse.httpHeaderField("Content-Encoding");
    166         if (!contentEncoding.isNull() && contentEncoding != "identity")
    167             expectedContentLength = -1;
    168     }
    169 
    170     m_stream.headers = m_headers.data();
    171     m_stream.pdata = 0;
    172     m_stream.ndata = this;
    173     m_stream.end = max(expectedContentLength, 0LL);
    174     m_stream.lastmodified = m_resourceResponse.lastModifiedDate();
    175     m_stream.notifyData = m_notifyData;
    176 
    177     m_transferMode = NP_NORMAL;
    178     m_offset = 0;
    179     m_reason = WebReasonNone;
    180 
    181     // Protect the stream if destroystream is called from within the newstream handler
    182     RefPtr<PluginStream> protect(this);
    183 
    184     // calling into a plug-in could result in re-entrance if the plug-in yields
    185     // control to the system (rdar://5744899). prevent this by deferring further
    186     // loading while calling into the plug-in.
    187     if (m_loader)
    188         m_loader->setDefersLoading(true);
    189     NPError npErr = m_pluginFuncs->newstream(m_instance, (NPMIMEType)mimeTypeStr.data(), &m_stream, false, &m_transferMode);
    190     if (m_loader)
    191         m_loader->setDefersLoading(false);
    192 
    193     // If the stream was destroyed in the call to newstream we return
    194     if (m_reason != WebReasonNone)
    195         return;
    196 
    197     if (npErr != NPERR_NO_ERROR) {
    198         cancelAndDestroyStream(npErr);
    199         return;
    200     }
    201 
    202     m_streamState = StreamStarted;
    203 
    204     if (m_transferMode == NP_NORMAL)
    205         return;
    206 
    207     m_path = openTemporaryFile("WKP", m_tempFileHandle);
    208 
    209     // Something went wrong, cancel loading the stream
    210     if (!isHandleValid(m_tempFileHandle))
    211         cancelAndDestroyStream(NPRES_NETWORK_ERR);
    212 }
    213 
    214 NPP PluginStream::ownerForStream(NPStream* stream)
    215 {
    216     return streams().get(stream);
    217 }
    218 
    219 void PluginStream::cancelAndDestroyStream(NPReason reason)
    220 {
    221     RefPtr<PluginStream> protect(this);
    222 
    223     destroyStream(reason);
    224     stop();
    225 }
    226 
    227 void PluginStream::destroyStream(NPReason reason)
    228 {
    229     m_reason = reason;
    230     if (m_reason != NPRES_DONE) {
    231         // Stop any pending data from being streamed
    232         if (m_deliveryData)
    233             m_deliveryData->resize(0);
    234     } else if (m_deliveryData && m_deliveryData->size() > 0) {
    235         // There is more data to be streamed, don't destroy the stream now.
    236         return;
    237     }
    238     destroyStream();
    239 }
    240 
    241 void PluginStream::destroyStream()
    242 {
    243     if (m_streamState == StreamStopped)
    244         return;
    245 
    246     ASSERT(m_reason != WebReasonNone);
    247     ASSERT(!m_deliveryData || m_deliveryData->size() == 0);
    248 
    249     closeFile(m_tempFileHandle);
    250 
    251     bool newStreamCalled = m_stream.ndata;
    252 
    253     // Protect from destruction if:
    254     //  NPN_DestroyStream is called from NPP_NewStream or
    255     //  PluginStreamClient::streamDidFinishLoading() removes the last reference
    256     RefPtr<PluginStream> protect(this);
    257 
    258     if (newStreamCalled) {
    259         if (m_reason == NPRES_DONE && (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY)) {
    260             ASSERT(!m_path.isNull());
    261 
    262             if (m_loader)
    263                 m_loader->setDefersLoading(true);
    264             m_pluginFuncs->asfile(m_instance, &m_stream, m_path.data());
    265             if (m_loader)
    266                 m_loader->setDefersLoading(false);
    267         }
    268 
    269         if (m_streamState != StreamBeforeStarted) {
    270             if (m_loader)
    271                 m_loader->setDefersLoading(true);
    272 
    273             NPError npErr = m_pluginFuncs->destroystream(m_instance, &m_stream, m_reason);
    274 
    275             if (m_loader)
    276                 m_loader->setDefersLoading(false);
    277 
    278             LOG_NPERROR(npErr);
    279         }
    280 
    281         m_stream.ndata = 0;
    282     }
    283 
    284     if (m_sendNotification) {
    285         // Flash 9 can dereference null if we call NPP_URLNotify without first calling NPP_NewStream
    286         // for requests made with NPN_PostURLNotify; see <rdar://5588807>
    287         if (m_loader)
    288             m_loader->setDefersLoading(true);
    289         if (!newStreamCalled && m_quirks.contains(PluginQuirkFlashURLNotifyBug) &&
    290             equalIgnoringCase(m_resourceRequest.httpMethod(), "POST")) {
    291             m_transferMode = NP_NORMAL;
    292             m_stream.url = "";
    293             m_stream.notifyData = m_notifyData;
    294 
    295             static char emptyMimeType[] = "";
    296             m_pluginFuncs->newstream(m_instance, emptyMimeType, &m_stream, false, &m_transferMode);
    297             m_pluginFuncs->destroystream(m_instance, &m_stream, m_reason);
    298 
    299             // in successful requests, the URL is dynamically allocated and freed in our
    300             // destructor, so reset it to 0
    301             m_stream.url = 0;
    302         }
    303         m_pluginFuncs->urlnotify(m_instance, m_resourceRequest.url().string().utf8().data(), m_reason, m_notifyData);
    304         if (m_loader)
    305             m_loader->setDefersLoading(false);
    306     }
    307 
    308     m_streamState = StreamStopped;
    309 
    310     if (!m_loadManually && m_client)
    311         m_client->streamDidFinishLoading(this);
    312 
    313     if (!m_path.isNull()) {
    314         String tempFilePath = String::fromUTF8(m_path.data());
    315         deleteFile(tempFilePath);
    316     }
    317 }
    318 
    319 void PluginStream::delayDeliveryTimerFired(Timer<PluginStream>* timer)
    320 {
    321     ASSERT(timer == &m_delayDeliveryTimer);
    322 
    323     deliverData();
    324 }
    325 
    326 void PluginStream::deliverData()
    327 {
    328     ASSERT(m_deliveryData);
    329 
    330     if (m_streamState == StreamStopped)
    331         // FIXME: We should cancel our job in the SubresourceLoader on error so we don't reach this case
    332         return;
    333 
    334     ASSERT(m_streamState != StreamBeforeStarted);
    335 
    336     if (!m_stream.ndata || m_deliveryData->size() == 0)
    337         return;
    338 
    339     int32 totalBytes = m_deliveryData->size();
    340     int32 totalBytesDelivered = 0;
    341 
    342     if (m_loader)
    343         m_loader->setDefersLoading(true);
    344     while (totalBytesDelivered < totalBytes) {
    345         int32 deliveryBytes = m_pluginFuncs->writeready(m_instance, &m_stream);
    346 
    347         if (deliveryBytes <= 0) {
    348 #if PLATFORM(ANDROID)
    349 // TODO: This needs to be upstreamed.
    350             if (m_loader)
    351                 m_loader->pauseLoad(true);
    352 
    353             // ask the plugin for a delay value.
    354             int delay = deliveryDelay();
    355             m_delayDeliveryTimer.startOneShot(delay * 0.001);
    356 #else
    357             m_delayDeliveryTimer.startOneShot(0);
    358 #endif
    359             break;
    360         } else {
    361             deliveryBytes = min(deliveryBytes, totalBytes - totalBytesDelivered);
    362             int32 dataLength = deliveryBytes;
    363             char* data = m_deliveryData->data() + totalBytesDelivered;
    364 
    365             // Write the data
    366             deliveryBytes = m_pluginFuncs->write(m_instance, &m_stream, m_offset, dataLength, (void*)data);
    367             if (deliveryBytes < 0) {
    368                 LOG_PLUGIN_NET_ERROR();
    369                 cancelAndDestroyStream(NPRES_NETWORK_ERR);
    370                 return;
    371             }
    372             deliveryBytes = min(deliveryBytes, dataLength);
    373             m_offset += deliveryBytes;
    374             totalBytesDelivered += deliveryBytes;
    375         }
    376     }
    377     if (m_loader)
    378         m_loader->setDefersLoading(false);
    379 
    380     if (totalBytesDelivered > 0) {
    381         if (totalBytesDelivered < totalBytes) {
    382             int remainingBytes = totalBytes - totalBytesDelivered;
    383             memmove(m_deliveryData->data(), m_deliveryData->data() + totalBytesDelivered, remainingBytes);
    384             m_deliveryData->resize(remainingBytes);
    385         } else {
    386 #if PLATFORM(ANDROID)
    387 //TODO: This needs to be upstreamed to WebKit.
    388             if (m_loader)
    389                 m_loader->pauseLoad(false);
    390 #endif
    391             m_deliveryData->resize(0);
    392             if (m_reason != WebReasonNone)
    393                 destroyStream();
    394         }
    395     }
    396 }
    397 
    398 void PluginStream::sendJavaScriptStream(const KURL& requestURL, const CString& resultString)
    399 {
    400     didReceiveResponse(0, ResourceResponse(requestURL, "text/plain", resultString.length(), "", ""));
    401 
    402     if (m_streamState == StreamStopped)
    403         return;
    404 
    405     if (!resultString.isNull()) {
    406         didReceiveData(0, resultString.data(), resultString.length());
    407         if (m_streamState == StreamStopped)
    408             return;
    409     }
    410 
    411     m_loader = 0;
    412 
    413     destroyStream(resultString.isNull() ? NPRES_NETWORK_ERR : NPRES_DONE);
    414 }
    415 
    416 void PluginStream::didReceiveResponse(NetscapePlugInStreamLoader* loader, const ResourceResponse& response)
    417 {
    418     ASSERT(loader == m_loader);
    419     ASSERT(m_streamState == StreamBeforeStarted);
    420 
    421     m_resourceResponse = response;
    422 
    423     startStream();
    424 }
    425 
    426 void PluginStream::didReceiveData(NetscapePlugInStreamLoader* loader, const char* data, int length)
    427 {
    428     ASSERT(loader == m_loader);
    429     ASSERT(length > 0);
    430     ASSERT(m_streamState == StreamStarted);
    431 
    432     // If the plug-in cancels the stream in deliverData it could be deleted,
    433     // so protect it here.
    434     RefPtr<PluginStream> protect(this);
    435 
    436     if (m_transferMode != NP_ASFILEONLY) {
    437         if (!m_deliveryData)
    438             m_deliveryData.set(new Vector<char>);
    439 
    440         int oldSize = m_deliveryData->size();
    441         m_deliveryData->resize(oldSize + length);
    442         memcpy(m_deliveryData->data() + oldSize, data, length);
    443 
    444 #if PLATFORM(ANDROID)
    445 //TODO: This needs to be upstreamed to WebKit.
    446         if (!m_delayDeliveryTimer.isActive())
    447 #endif
    448         deliverData();
    449     }
    450 
    451     if (m_streamState != StreamStopped && isHandleValid(m_tempFileHandle)) {
    452         int bytesWritten = writeToFile(m_tempFileHandle, data, length);
    453         if (bytesWritten != length)
    454             cancelAndDestroyStream(NPRES_NETWORK_ERR);
    455     }
    456 }
    457 
    458 void PluginStream::didFail(NetscapePlugInStreamLoader* loader, const ResourceError&)
    459 {
    460     ASSERT(loader == m_loader);
    461 
    462     LOG_PLUGIN_NET_ERROR();
    463 
    464     // destroyStream can result in our being deleted
    465     RefPtr<PluginStream> protect(this);
    466 
    467     destroyStream(NPRES_NETWORK_ERR);
    468 
    469     m_loader = 0;
    470 }
    471 
    472 void PluginStream::didFinishLoading(NetscapePlugInStreamLoader* loader)
    473 {
    474     ASSERT(loader == m_loader);
    475     ASSERT(m_streamState == StreamStarted);
    476 
    477     // destroyStream can result in our being deleted
    478     RefPtr<PluginStream> protect(this);
    479 
    480     destroyStream(NPRES_DONE);
    481 
    482     m_loader = 0;
    483 }
    484 
    485 bool PluginStream::wantsAllStreams() const
    486 {
    487     if (!m_pluginFuncs->getvalue)
    488         return false;
    489 
    490     void* result = 0;
    491     if (m_pluginFuncs->getvalue(m_instance, NPPVpluginWantsAllNetworkStreams, &result) != NPERR_NO_ERROR)
    492         return false;
    493 
    494     return result != 0;
    495 }
    496 
    497 #if PLATFORM(ANDROID)
    498 int PluginStream::deliveryDelay() const
    499 {
    500     if (!m_pluginFuncs->getvalue)
    501         return 0;
    502 
    503     int delay = 0;
    504     if (m_pluginFuncs->getvalue(m_instance, NPPDataDeliveryDelayMs, &delay) != NPERR_NO_ERROR)
    505         return 0;
    506 
    507     return delay;
    508 }
    509 #endif
    510 
    511 }
    512