| /* |
| * Copyright (C) 2010 Apple Inc. 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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 "NetscapePluginStream.h" |
| |
| #include "NetscapePlugin.h" |
| #include <utility> |
| |
| using namespace WebCore; |
| using namespace std; |
| |
| namespace WebKit { |
| |
| NetscapePluginStream::NetscapePluginStream(PassRefPtr<NetscapePlugin> plugin, uint64_t streamID, bool sendNotification, void* notificationData) |
| : m_plugin(plugin) |
| , m_streamID(streamID) |
| , m_sendNotification(sendNotification) |
| , m_notificationData(notificationData) |
| , m_npStream() |
| , m_transferMode(NP_NORMAL) |
| , m_offset(0) |
| , m_fileHandle(invalidPlatformFileHandle) |
| , m_isStarted(false) |
| #if !ASSERT_DISABLED |
| , m_urlNotifyHasBeenCalled(false) |
| #endif |
| , m_deliveryDataTimer(RunLoop::main(), this, &NetscapePluginStream::deliverDataToPlugin) |
| , m_stopStreamWhenDoneDelivering(false) |
| { |
| } |
| |
| NetscapePluginStream::~NetscapePluginStream() |
| { |
| ASSERT(!m_isStarted); |
| ASSERT(!m_sendNotification || m_urlNotifyHasBeenCalled); |
| ASSERT(m_fileHandle == invalidPlatformFileHandle); |
| } |
| |
| void NetscapePluginStream::didReceiveResponse(const KURL& responseURL, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers) |
| { |
| // Starting the stream could cause the plug-in stream to go away so we keep a reference to it here. |
| RefPtr<NetscapePluginStream> protect(this); |
| |
| start(responseURL, streamLength, lastModifiedTime, mimeType, headers); |
| } |
| |
| void NetscapePluginStream::didReceiveData(const char* bytes, int length) |
| { |
| // Delivering the data could cause the plug-in stream to go away so we keep a reference to it here. |
| RefPtr<NetscapePluginStream> protect(this); |
| |
| deliverData(bytes, length); |
| } |
| |
| void NetscapePluginStream::didFinishLoading() |
| { |
| // Stopping the stream could cause the plug-in stream to go away so we keep a reference to it here. |
| RefPtr<NetscapePluginStream> protect(this); |
| |
| stop(NPRES_DONE); |
| } |
| |
| void NetscapePluginStream::didFail(bool wasCancelled) |
| { |
| // Stopping the stream could cause the plug-in stream to go away so we keep a reference to it here. |
| RefPtr<NetscapePluginStream> protect(this); |
| |
| stop(wasCancelled ? NPRES_USER_BREAK : NPRES_NETWORK_ERR); |
| } |
| |
| void NetscapePluginStream::sendJavaScriptStream(const String& requestURLString, const String& result) |
| { |
| // starting the stream or delivering the data to it might cause the plug-in stream to go away, so we keep |
| // a reference to it here. |
| RefPtr<NetscapePluginStream> protect(this); |
| |
| CString resultCString = requestURLString.utf8(); |
| if (resultCString.isNull()) { |
| // There was an error evaluating the JavaScript, call NPP_URLNotify if needed and then destroy the stream. |
| notifyAndDestroyStream(NPRES_NETWORK_ERR); |
| return; |
| } |
| |
| if (!start(requestURLString, resultCString.length(), 0, "text/plain", "")) |
| return; |
| |
| deliverData(resultCString.data(), resultCString.length()); |
| stop(NPRES_DONE); |
| } |
| |
| NPError NetscapePluginStream::destroy(NPReason reason) |
| { |
| // It doesn't make sense to call NPN_DestroyStream on a stream that hasn't been started yet. |
| if (!m_isStarted) |
| return NPERR_GENERIC_ERROR; |
| |
| // It isn't really valid for a plug-in to call NPN_DestroyStream with NPRES_DONE. |
| // (At least not for browser initiated streams, and we don't support plug-in initiated streams). |
| if (reason == NPRES_DONE) |
| return NPERR_INVALID_PARAM; |
| |
| cancel(); |
| stop(reason); |
| return NPERR_NO_ERROR; |
| } |
| |
| static bool isSupportedTransferMode(uint16_t transferMode) |
| { |
| switch (transferMode) { |
| case NP_ASFILEONLY: |
| case NP_ASFILE: |
| case NP_NORMAL: |
| return true; |
| // FIXME: We don't support seekable streams. |
| case NP_SEEK: |
| return false; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| |
| bool NetscapePluginStream::start(const String& responseURLString, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers) |
| { |
| m_responseURL = responseURLString.utf8(); |
| m_mimeType = mimeType.utf8(); |
| m_headers = headers.utf8(); |
| |
| m_npStream.ndata = this; |
| m_npStream.url = m_responseURL.data(); |
| m_npStream.end = streamLength; |
| m_npStream.lastmodified = lastModifiedTime; |
| m_npStream.notifyData = m_notificationData; |
| m_npStream.headers = m_headers.length() == 0 ? 0 : m_headers.data(); |
| |
| NPError error = m_plugin->NPP_NewStream(const_cast<char*>(m_mimeType.data()), &m_npStream, false, &m_transferMode); |
| if (error != NPERR_NO_ERROR) { |
| // We failed to start the stream, cancel the load and destroy it. |
| cancel(); |
| notifyAndDestroyStream(NPRES_NETWORK_ERR); |
| return false; |
| } |
| |
| // We successfully started the stream. |
| m_isStarted = true; |
| |
| if (!isSupportedTransferMode(m_transferMode)) { |
| // Cancel the load and stop the stream. |
| cancel(); |
| stop(NPRES_NETWORK_ERR); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void NetscapePluginStream::deliverData(const char* bytes, int length) |
| { |
| ASSERT(m_isStarted); |
| |
| if (m_transferMode != NP_ASFILEONLY) { |
| if (!m_deliveryData) |
| m_deliveryData.set(new Vector<uint8_t>); |
| |
| m_deliveryData->reserveCapacity(m_deliveryData->size() + length); |
| m_deliveryData->append(bytes, length); |
| |
| deliverDataToPlugin(); |
| } |
| |
| if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY) |
| deliverDataToFile(bytes, length); |
| } |
| |
| void NetscapePluginStream::deliverDataToPlugin() |
| { |
| ASSERT(m_isStarted); |
| |
| int32_t numBytesToDeliver = m_deliveryData->size(); |
| int32_t numBytesDelivered = 0; |
| |
| while (numBytesDelivered < numBytesToDeliver) { |
| int32_t numBytesPluginCanHandle = m_plugin->NPP_WriteReady(&m_npStream); |
| |
| // NPP_WriteReady could call NPN_DestroyStream and destroy the stream. |
| if (!m_isStarted) |
| return; |
| |
| if (numBytesPluginCanHandle <= 0) { |
| // The plug-in can't handle more data, we'll send the rest later |
| m_deliveryDataTimer.startOneShot(0); |
| break; |
| } |
| |
| // Figure out how much data to send to the plug-in. |
| int32_t dataLength = min(numBytesPluginCanHandle, numBytesToDeliver - numBytesDelivered); |
| uint8_t* data = m_deliveryData->data() + numBytesDelivered; |
| |
| int32_t numBytesWritten = m_plugin->NPP_Write(&m_npStream, m_offset, dataLength, data); |
| if (numBytesWritten < 0) { |
| stop(NPRES_NETWORK_ERR); |
| return; |
| } |
| |
| // NPP_Write could call NPN_DestroyStream and destroy the stream. |
| if (!m_isStarted) |
| return; |
| |
| numBytesWritten = min(numBytesWritten, dataLength); |
| m_offset += numBytesWritten; |
| numBytesDelivered += numBytesWritten; |
| } |
| |
| // We didn't write anything. |
| if (!numBytesDelivered) |
| return; |
| |
| if (numBytesDelivered < numBytesToDeliver) { |
| // Remove the bytes that we actually delivered. |
| m_deliveryData->remove(0, numBytesDelivered); |
| } else { |
| m_deliveryData->clear(); |
| |
| if (m_stopStreamWhenDoneDelivering) |
| stop(NPRES_DONE); |
| } |
| } |
| |
| void NetscapePluginStream::deliverDataToFile(const char* bytes, int length) |
| { |
| if (m_fileHandle == invalidPlatformFileHandle && m_filePath.isNull()) { |
| // Create a temporary file. |
| m_filePath = openTemporaryFile("WebKitPluginStream", m_fileHandle); |
| |
| // We failed to open the file, stop the stream. |
| if (m_fileHandle == invalidPlatformFileHandle) { |
| stop(NPRES_NETWORK_ERR); |
| return; |
| } |
| } |
| |
| if (!length) |
| return; |
| |
| int byteCount = writeToFile(m_fileHandle, bytes, length); |
| if (byteCount != length) { |
| // This happens only rarely, when we are out of disk space or have a disk I/O error. |
| closeFile(m_fileHandle); |
| |
| stop(NPRES_NETWORK_ERR); |
| } |
| } |
| |
| void NetscapePluginStream::stop(NPReason reason) |
| { |
| // The stream was stopped before it got a chance to start. This can happen if a stream is cancelled by |
| // WebKit before it received a response. |
| if (!m_isStarted) |
| return; |
| |
| if (reason == NPRES_DONE && m_deliveryData && !m_deliveryData->isEmpty()) { |
| // There is still data left that the plug-in hasn't been able to consume yet. |
| ASSERT(m_deliveryDataTimer.isActive()); |
| |
| // Set m_stopStreamWhenDoneDelivering to true so that the next time the delivery timer fires |
| // and calls deliverDataToPlugin the stream will be closed if all the remaining data was |
| // successfully delivered. |
| m_stopStreamWhenDoneDelivering = true; |
| return; |
| } |
| |
| m_deliveryData = 0; |
| m_deliveryDataTimer.stop(); |
| |
| if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY) { |
| if (reason == NPRES_DONE) { |
| // Ensure that the file is created. |
| deliverDataToFile(0, 0); |
| if (m_fileHandle != invalidPlatformFileHandle) |
| closeFile(m_fileHandle); |
| |
| ASSERT(!m_filePath.isNull()); |
| |
| m_plugin->NPP_StreamAsFile(&m_npStream, m_filePath.utf8().data()); |
| } else { |
| // Just close the file. |
| if (m_fileHandle != invalidPlatformFileHandle) |
| closeFile(m_fileHandle); |
| } |
| |
| // Delete the file after calling NPP_StreamAsFile(), instead of in the destructor. It should be OK |
| // to delete the file here -- NPP_StreamAsFile() is always called immediately before NPP_DestroyStream() |
| // (the stream destruction function), so there can be no expectation that a plugin will read the stream |
| // file asynchronously after NPP_StreamAsFile() is called. |
| deleteFile(m_filePath); |
| m_filePath = String(); |
| |
| // NPP_StreamAsFile could call NPN_DestroyStream and destroy the stream. |
| if (!m_isStarted) |
| return; |
| } |
| |
| // Set m_isStarted to false before calling NPP_DestroyStream in case NPP_DestroyStream calls NPN_DestroyStream. |
| m_isStarted = false; |
| |
| m_plugin->NPP_DestroyStream(&m_npStream, reason); |
| |
| notifyAndDestroyStream(reason); |
| } |
| |
| void NetscapePluginStream::cancel() |
| { |
| m_plugin->cancelStreamLoad(this); |
| } |
| |
| void NetscapePluginStream::notifyAndDestroyStream(NPReason reason) |
| { |
| ASSERT(!m_isStarted); |
| ASSERT(!m_deliveryDataTimer.isActive()); |
| ASSERT(!m_urlNotifyHasBeenCalled); |
| |
| if (m_sendNotification) { |
| m_plugin->NPP_URLNotify(m_responseURL.data(), reason, m_notificationData); |
| |
| #if !ASSERT_DISABLED |
| m_urlNotifyHasBeenCalled = true; |
| #endif |
| } |
| |
| m_plugin->removePluginStream(this); |
| } |
| |
| } // namespace WebKit |