WebCore/plugins/PluginStream.cpp
changeset 0 4f2f89ce4247
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebCore/plugins/PluginStream.cpp	Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,480 @@
+/*
+ * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
+ * Copyright (C) 2008 Collabora, Ltd. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#include "config.h"
+#include "PluginStream.h"
+
+#include "DocumentLoader.h"
+#include "Frame.h"
+#include "FrameLoader.h"
+#include "PluginDebug.h"
+#include "SharedBuffer.h"
+#include "SubresourceLoader.h"
+#include <StringExtras.h>
+#include <wtf/text/CString.h>
+
+// We use -2 here because some plugins like to return -1 to indicate error
+// and this way we won't clash with them.
+static const int WebReasonNone = -2;
+
+using std::max;
+using std::min;
+
+namespace WebCore {
+
+typedef HashMap<NPStream*, NPP> StreamMap;
+static StreamMap& streams()
+{
+    static StreamMap staticStreams;
+    return staticStreams;
+}
+
+PluginStream::PluginStream(PluginStreamClient* client, Frame* frame, const ResourceRequest& resourceRequest, bool sendNotification, void* notifyData, const NPPluginFuncs* pluginFuncs, NPP instance, const PluginQuirkSet& quirks)
+    : m_resourceRequest(resourceRequest)
+    , m_client(client)
+    , m_frame(frame)
+    , m_notifyData(notifyData)
+    , m_sendNotification(sendNotification)
+    , m_streamState(StreamBeforeStarted)
+    , m_loadManually(false)
+    , m_delayDeliveryTimer(this, &PluginStream::delayDeliveryTimerFired)
+    , m_deliveryData(0)
+    , m_tempFileHandle(invalidPlatformFileHandle)
+    , m_pluginFuncs(pluginFuncs)
+    , m_instance(instance)
+    , m_quirks(quirks)
+{
+    ASSERT(m_instance);
+
+    m_stream.url = 0;
+    m_stream.ndata = 0;
+    m_stream.pdata = 0;
+    m_stream.end = 0;
+    m_stream.notifyData = 0;
+    m_stream.lastmodified = 0;
+
+    streams().add(&m_stream, m_instance);
+}
+
+PluginStream::~PluginStream()
+{
+    ASSERT(m_streamState != StreamStarted);
+    ASSERT(!m_loader);
+
+    fastFree((char*)m_stream.url);
+
+    streams().remove(&m_stream);
+}
+
+void PluginStream::start()
+{
+    ASSERT(!m_loadManually);
+
+    m_loader = NetscapePlugInStreamLoader::create(m_frame, this);
+
+    m_loader->setShouldBufferData(false);
+    m_loader->documentLoader()->addPlugInStreamLoader(m_loader.get());
+    m_loader->load(m_resourceRequest);
+}
+
+void PluginStream::stop()
+{
+    m_streamState = StreamStopped;
+
+    if (m_loadManually) {
+        ASSERT(!m_loader);
+
+        DocumentLoader* documentLoader = m_frame->loader()->activeDocumentLoader();
+        ASSERT(documentLoader);
+
+        if (documentLoader->isLoadingMainResource())
+            documentLoader->cancelMainResourceLoad(m_frame->loader()->cancelledError(m_resourceRequest));
+
+        return;
+    }
+
+    if (m_loader) {
+        m_loader->cancel();
+        m_loader = 0;
+    }
+
+    m_client = 0;
+}
+
+void PluginStream::startStream()
+{
+    ASSERT(m_streamState == StreamBeforeStarted);
+
+    const KURL& responseURL = m_resourceResponse.url();
+
+    // Some plugins (Flash) expect that javascript URLs are passed back decoded as this is the
+    // format used when requesting the URL.
+    if (protocolIsJavaScript(responseURL))
+        m_stream.url = fastStrDup(decodeURLEscapeSequences(responseURL.string()).utf8().data());
+    else
+        m_stream.url = fastStrDup(responseURL.string().utf8().data());
+
+    CString mimeTypeStr = m_resourceResponse.mimeType().utf8();
+
+    long long expectedContentLength = m_resourceResponse.expectedContentLength();
+
+    if (m_resourceResponse.isHTTP()) {
+        Vector<UChar> stringBuilder;
+        String separator(": ");
+
+        String statusLine = String::format("HTTP %d OK\n", m_resourceResponse.httpStatusCode());
+
+        stringBuilder.append(statusLine.characters(), statusLine.length());
+
+        HTTPHeaderMap::const_iterator end = m_resourceResponse.httpHeaderFields().end();
+        for (HTTPHeaderMap::const_iterator it = m_resourceResponse.httpHeaderFields().begin(); it != end; ++it) {
+            stringBuilder.append(it->first.characters(), it->first.length());
+            stringBuilder.append(separator.characters(), separator.length());
+            stringBuilder.append(it->second.characters(), it->second.length());
+            stringBuilder.append('\n');
+        }
+
+        m_headers = String::adopt(stringBuilder).utf8();
+
+        // If the content is encoded (most likely compressed), then don't send its length to the plugin,
+        // which is only interested in the decoded length, not yet known at the moment.
+        // <rdar://problem/4470599> tracks a request for -[NSURLResponse expectedContentLength] to incorporate this logic.
+        String contentEncoding = m_resourceResponse.httpHeaderField("Content-Encoding");
+        if (!contentEncoding.isNull() && contentEncoding != "identity")
+            expectedContentLength = -1;
+    }
+
+    m_stream.headers = m_headers.data();
+    m_stream.pdata = 0;
+    m_stream.ndata = this;
+    m_stream.end = max(expectedContentLength, 0LL);
+    m_stream.lastmodified = m_resourceResponse.lastModifiedDate();
+    m_stream.notifyData = m_notifyData;
+
+    m_transferMode = NP_NORMAL;
+    m_offset = 0;
+    m_reason = WebReasonNone;
+
+    // Protect the stream if destroystream is called from within the newstream handler
+    RefPtr<PluginStream> protect(this);
+
+    // calling into a plug-in could result in re-entrance if the plug-in yields
+    // control to the system (rdar://5744899). prevent this by deferring further
+    // loading while calling into the plug-in.
+    if (m_loader)
+        m_loader->setDefersLoading(true);
+    NPError npErr = m_pluginFuncs->newstream(m_instance, (NPMIMEType)mimeTypeStr.data(), &m_stream, false, &m_transferMode);
+    if (m_loader)
+        m_loader->setDefersLoading(false);
+    
+    // If the stream was destroyed in the call to newstream we return
+    if (m_reason != WebReasonNone)
+        return;
+        
+    if (npErr != NPERR_NO_ERROR) {
+        cancelAndDestroyStream(npErr);
+        return;
+    }
+
+    m_streamState = StreamStarted;
+
+    if (m_transferMode == NP_NORMAL)
+        return;
+
+    m_path = openTemporaryFile("WKP", m_tempFileHandle);
+
+    // Something went wrong, cancel loading the stream
+    if (!isHandleValid(m_tempFileHandle))
+        cancelAndDestroyStream(NPRES_NETWORK_ERR);
+}
+
+NPP PluginStream::ownerForStream(NPStream* stream)
+{
+    return streams().get(stream);
+}
+
+void PluginStream::cancelAndDestroyStream(NPReason reason)
+{
+    RefPtr<PluginStream> protect(this);
+
+    destroyStream(reason);
+    stop();
+}
+
+void PluginStream::destroyStream(NPReason reason)
+{
+    m_reason = reason;
+    if (m_reason != NPRES_DONE) {
+        // Stop any pending data from being streamed
+        if (m_deliveryData)
+            m_deliveryData->resize(0);
+    } else if (m_deliveryData && m_deliveryData->size() > 0) {
+        // There is more data to be streamed, don't destroy the stream now.
+        return;
+    }
+    destroyStream();
+}
+
+void PluginStream::destroyStream()
+{
+    if (m_streamState == StreamStopped)
+        return;
+
+    ASSERT(m_reason != WebReasonNone);
+    ASSERT(!m_deliveryData || m_deliveryData->size() == 0);
+
+    closeFile(m_tempFileHandle);
+
+    bool newStreamCalled = m_stream.ndata;
+
+    // Protect from destruction if:
+    //  NPN_DestroyStream is called from NPP_NewStream or
+    //  PluginStreamClient::streamDidFinishLoading() removes the last reference
+    RefPtr<PluginStream> protect(this);
+
+    if (newStreamCalled) {
+        if (m_reason == NPRES_DONE && (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY)) {
+            ASSERT(!m_path.isNull());
+
+            if (m_loader)
+                m_loader->setDefersLoading(true);
+            m_pluginFuncs->asfile(m_instance, &m_stream, m_path.data());
+            if (m_loader)
+                m_loader->setDefersLoading(false);
+        }
+
+        if (m_streamState != StreamBeforeStarted) {
+            if (m_loader)
+                m_loader->setDefersLoading(true);
+
+            NPError npErr = m_pluginFuncs->destroystream(m_instance, &m_stream, m_reason);
+
+            if (m_loader)
+                m_loader->setDefersLoading(false);
+
+            LOG_NPERROR(npErr);
+        }
+
+        m_stream.ndata = 0;
+    }
+
+    if (m_sendNotification) {
+        // Flash 9 can dereference null if we call NPP_URLNotify without first calling NPP_NewStream
+        // for requests made with NPN_PostURLNotify; see <rdar://5588807>
+        if (m_loader)
+            m_loader->setDefersLoading(true);
+        if (!newStreamCalled && m_quirks.contains(PluginQuirkFlashURLNotifyBug) &&
+            equalIgnoringCase(m_resourceRequest.httpMethod(), "POST")) {
+            m_transferMode = NP_NORMAL;
+            m_stream.url = "";
+            m_stream.notifyData = m_notifyData;
+
+            static char emptyMimeType[] = "";
+            m_pluginFuncs->newstream(m_instance, emptyMimeType, &m_stream, false, &m_transferMode);
+            m_pluginFuncs->destroystream(m_instance, &m_stream, m_reason);
+
+            // in successful requests, the URL is dynamically allocated and freed in our
+            // destructor, so reset it to 0
+            m_stream.url = 0;
+        }
+        m_pluginFuncs->urlnotify(m_instance, m_resourceRequest.url().string().utf8().data(), m_reason, m_notifyData);
+        if (m_loader)
+            m_loader->setDefersLoading(false);
+    }
+
+    m_streamState = StreamStopped;
+
+    if (!m_loadManually && m_client)
+        m_client->streamDidFinishLoading(this);
+
+    if (!m_path.isNull()) {
+        String tempFilePath = String::fromUTF8(m_path.data());
+        deleteFile(tempFilePath);
+    }
+}
+
+void PluginStream::delayDeliveryTimerFired(Timer<PluginStream>* timer)
+{
+    ASSERT(timer == &m_delayDeliveryTimer);
+
+    deliverData();
+}
+
+void PluginStream::deliverData()
+{
+    ASSERT(m_deliveryData);
+    
+    if (m_streamState == StreamStopped)
+        // FIXME: We should cancel our job in the SubresourceLoader on error so we don't reach this case
+        return;
+
+    ASSERT(m_streamState != StreamBeforeStarted);
+
+    if (!m_stream.ndata || m_deliveryData->size() == 0)
+        return;
+
+    int32_t totalBytes = m_deliveryData->size();
+    int32_t totalBytesDelivered = 0;
+
+    if (m_loader)
+        m_loader->setDefersLoading(true);
+    while (totalBytesDelivered < totalBytes) {
+        int32_t deliveryBytes = m_pluginFuncs->writeready(m_instance, &m_stream);
+
+        if (deliveryBytes <= 0) {
+            m_delayDeliveryTimer.startOneShot(0);
+            break;
+        } else {
+            deliveryBytes = min(deliveryBytes, totalBytes - totalBytesDelivered);
+            int32_t dataLength = deliveryBytes;
+            char* data = m_deliveryData->data() + totalBytesDelivered;
+
+            // Write the data
+            deliveryBytes = m_pluginFuncs->write(m_instance, &m_stream, m_offset, dataLength, (void*)data);
+            if (deliveryBytes < 0) {
+                LOG_PLUGIN_NET_ERROR();
+                if (m_loader)
+                    m_loader->setDefersLoading(false);
+                cancelAndDestroyStream(NPRES_NETWORK_ERR);
+                return;
+            }
+            deliveryBytes = min(deliveryBytes, dataLength);
+            m_offset += deliveryBytes;
+            totalBytesDelivered += deliveryBytes;
+        }
+    }
+    if (m_loader)
+        m_loader->setDefersLoading(false);
+
+    if (totalBytesDelivered > 0) {
+        if (totalBytesDelivered < totalBytes) {
+            int remainingBytes = totalBytes - totalBytesDelivered;
+            memmove(m_deliveryData->data(), m_deliveryData->data() + totalBytesDelivered, remainingBytes);
+            m_deliveryData->resize(remainingBytes);
+        } else {
+            m_deliveryData->resize(0);
+            if (m_reason != WebReasonNone)
+                destroyStream();
+        }
+    } 
+}
+
+void PluginStream::sendJavaScriptStream(const KURL& requestURL, const CString& resultString)
+{
+    didReceiveResponse(0, ResourceResponse(requestURL, "text/plain", resultString.length(), "", ""));
+
+    if (m_streamState == StreamStopped)
+        return;
+
+    if (!resultString.isNull()) {
+        didReceiveData(0, resultString.data(), resultString.length());
+        if (m_streamState == StreamStopped)
+            return;
+    }
+
+    m_loader = 0;
+
+    destroyStream(resultString.isNull() ? NPRES_NETWORK_ERR : NPRES_DONE);
+}
+
+void PluginStream::didReceiveResponse(NetscapePlugInStreamLoader* loader, const ResourceResponse& response)
+{
+    ASSERT(loader == m_loader);
+    ASSERT(m_streamState == StreamBeforeStarted);
+
+    m_resourceResponse = response;
+
+    startStream();
+}
+
+void PluginStream::didReceiveData(NetscapePlugInStreamLoader* loader, const char* data, int length)
+{
+    ASSERT(loader == m_loader);
+    ASSERT(length > 0);
+    ASSERT(m_streamState == StreamStarted);
+
+    // If the plug-in cancels the stream in deliverData it could be deleted, 
+    // so protect it here.
+    RefPtr<PluginStream> protect(this);
+
+    if (m_transferMode != NP_ASFILEONLY) {
+        if (!m_deliveryData)
+            m_deliveryData.set(new Vector<char>);
+
+        int oldSize = m_deliveryData->size();
+        m_deliveryData->resize(oldSize + length);
+        memcpy(m_deliveryData->data() + oldSize, data, length);
+
+        deliverData();
+    }
+
+    if (m_streamState != StreamStopped && isHandleValid(m_tempFileHandle)) {
+        int bytesWritten = writeToFile(m_tempFileHandle, data, length);
+        if (bytesWritten != length)
+            cancelAndDestroyStream(NPRES_NETWORK_ERR);
+    }
+}
+
+void PluginStream::didFail(NetscapePlugInStreamLoader* loader, const ResourceError&)
+{
+    ASSERT(loader == m_loader);
+
+    LOG_PLUGIN_NET_ERROR();
+
+    // destroyStream can result in our being deleted
+    RefPtr<PluginStream> protect(this);
+
+    destroyStream(NPRES_NETWORK_ERR);
+
+    m_loader = 0;
+}
+
+void PluginStream::didFinishLoading(NetscapePlugInStreamLoader* loader)
+{
+    ASSERT(loader == m_loader);
+    ASSERT(m_streamState == StreamStarted);
+
+    // destroyStream can result in our being deleted
+    RefPtr<PluginStream> protect(this);
+
+    destroyStream(NPRES_DONE);
+
+    m_loader = 0;
+}
+
+bool PluginStream::wantsAllStreams() const
+{
+    if (!m_pluginFuncs->getvalue)
+        return false;
+
+    void* result = 0;
+    if (m_pluginFuncs->getvalue(m_instance, NPPVpluginWantsAllNetworkStreams, &result) != NPERR_NO_ERROR)
+        return false;
+
+    return result != 0;
+}
+
+}