--- /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;
+}
+
+}