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