2 * Copyright (C) 2010 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
27 #include "NetscapePluginStream.h"
29 #include "NetscapePlugin.h"
32 using namespace WebCore;
37 NetscapePluginStream::NetscapePluginStream(PassRefPtr<NetscapePlugin> plugin, uint64_t streamID, const String& requestURLString, bool sendNotification, void* notificationData)
39 , m_streamID(streamID)
40 , m_requestURLString(requestURLString)
41 , m_sendNotification(sendNotification)
42 , m_notificationData(notificationData)
44 , m_transferMode(NP_NORMAL)
46 , m_fileHandle(invalidPlatformFileHandle)
49 , m_urlNotifyHasBeenCalled(false)
51 , m_deliveryDataTimer(RunLoop::main(), this, &NetscapePluginStream::deliverDataToPlugin)
52 , m_stopStreamWhenDoneDelivering(false)
56 NetscapePluginStream::~NetscapePluginStream()
59 ASSERT(!m_sendNotification || m_urlNotifyHasBeenCalled);
60 ASSERT(m_fileHandle == invalidPlatformFileHandle);
63 void NetscapePluginStream::didReceiveResponse(const KURL& responseURL, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers)
65 // Starting the stream could cause the plug-in stream to go away so we keep a reference to it here.
66 RefPtr<NetscapePluginStream> protect(this);
68 start(responseURL, streamLength, lastModifiedTime, mimeType, headers);
71 void NetscapePluginStream::didReceiveData(const char* bytes, int length)
73 // Delivering the data could cause the plug-in stream to go away so we keep a reference to it here.
74 RefPtr<NetscapePluginStream> protect(this);
76 deliverData(bytes, length);
79 void NetscapePluginStream::didFinishLoading()
81 // Stopping the stream could cause the plug-in stream to go away so we keep a reference to it here.
82 RefPtr<NetscapePluginStream> protect(this);
87 void NetscapePluginStream::didFail(bool wasCancelled)
89 // Stopping the stream could cause the plug-in stream to go away so we keep a reference to it here.
90 RefPtr<NetscapePluginStream> protect(this);
92 stop(wasCancelled ? NPRES_USER_BREAK : NPRES_NETWORK_ERR);
95 void NetscapePluginStream::sendJavaScriptStream(const String& result)
97 // starting the stream or delivering the data to it might cause the plug-in stream to go away, so we keep
98 // a reference to it here.
99 RefPtr<NetscapePluginStream> protect(this);
101 CString resultCString = result.utf8();
102 if (resultCString.isNull()) {
103 // There was an error evaluating the JavaScript, call NPP_URLNotify if needed and then destroy the stream.
104 notifyAndDestroyStream(NPRES_NETWORK_ERR);
108 if (!start(m_requestURLString, resultCString.length(), 0, "text/plain", ""))
111 deliverData(resultCString.data(), resultCString.length());
115 NPError NetscapePluginStream::destroy(NPReason reason)
117 // It doesn't make sense to call NPN_DestroyStream on a stream that hasn't been started yet.
119 return NPERR_GENERIC_ERROR;
121 // It isn't really valid for a plug-in to call NPN_DestroyStream with NPRES_DONE.
122 // (At least not for browser initiated streams, and we don't support plug-in initiated streams).
123 if (reason == NPRES_DONE)
124 return NPERR_INVALID_PARAM;
128 return NPERR_NO_ERROR;
131 static bool isSupportedTransferMode(uint16_t transferMode)
133 switch (transferMode) {
138 // FIXME: We don't support seekable streams.
143 ASSERT_NOT_REACHED();
147 bool NetscapePluginStream::start(const String& responseURLString, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers)
149 m_responseURL = responseURLString.utf8();
150 m_mimeType = mimeType.utf8();
151 m_headers = headers.utf8();
153 m_npStream.ndata = this;
154 m_npStream.url = m_responseURL.data();
155 m_npStream.end = streamLength;
156 m_npStream.lastmodified = lastModifiedTime;
157 m_npStream.notifyData = m_notificationData;
158 m_npStream.headers = m_headers.length() == 0 ? 0 : m_headers.data();
160 NPError error = m_plugin->NPP_NewStream(const_cast<char*>(m_mimeType.data()), &m_npStream, false, &m_transferMode);
161 if (error != NPERR_NO_ERROR) {
162 // We failed to start the stream, cancel the load and destroy it.
164 notifyAndDestroyStream(NPRES_NETWORK_ERR);
168 // We successfully started the stream.
171 if (!isSupportedTransferMode(m_transferMode)) {
172 // Cancel the load and stop the stream.
174 stop(NPRES_NETWORK_ERR);
181 void NetscapePluginStream::deliverData(const char* bytes, int length)
185 if (m_transferMode != NP_ASFILEONLY) {
187 m_deliveryData = adoptPtr(new Vector<uint8_t>);
189 m_deliveryData->reserveCapacity(m_deliveryData->size() + length);
190 m_deliveryData->append(bytes, length);
192 deliverDataToPlugin();
195 if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY)
196 deliverDataToFile(bytes, length);
199 void NetscapePluginStream::deliverDataToPlugin()
203 int32_t numBytesToDeliver = m_deliveryData->size();
204 int32_t numBytesDelivered = 0;
206 while (numBytesDelivered < numBytesToDeliver) {
207 int32_t numBytesPluginCanHandle = m_plugin->NPP_WriteReady(&m_npStream);
209 // NPP_WriteReady could call NPN_DestroyStream and destroy the stream.
213 if (numBytesPluginCanHandle <= 0) {
214 // The plug-in can't handle more data, we'll send the rest later
215 m_deliveryDataTimer.startOneShot(0);
219 // Figure out how much data to send to the plug-in.
220 int32_t dataLength = min(numBytesPluginCanHandle, numBytesToDeliver - numBytesDelivered);
221 uint8_t* data = m_deliveryData->data() + numBytesDelivered;
223 int32_t numBytesWritten = m_plugin->NPP_Write(&m_npStream, m_offset, dataLength, data);
224 if (numBytesWritten < 0) {
226 stop(NPRES_NETWORK_ERR);
230 // NPP_Write could call NPN_DestroyStream and destroy the stream.
234 numBytesWritten = min(numBytesWritten, dataLength);
235 m_offset += numBytesWritten;
236 numBytesDelivered += numBytesWritten;
239 // We didn't write anything.
240 if (!numBytesDelivered)
243 if (numBytesDelivered < numBytesToDeliver) {
244 // Remove the bytes that we actually delivered.
245 m_deliveryData->remove(0, numBytesDelivered);
247 m_deliveryData->clear();
249 if (m_stopStreamWhenDoneDelivering)
254 void NetscapePluginStream::deliverDataToFile(const char* bytes, int length)
256 if (m_fileHandle == invalidPlatformFileHandle && m_filePath.isNull()) {
257 // Create a temporary file.
258 m_filePath = openTemporaryFile("WebKitPluginStream", m_fileHandle);
260 // We failed to open the file, stop the stream.
261 if (m_fileHandle == invalidPlatformFileHandle) {
262 stop(NPRES_NETWORK_ERR);
270 int byteCount = writeToFile(m_fileHandle, bytes, length);
271 if (byteCount != length) {
272 // This happens only rarely, when we are out of disk space or have a disk I/O error.
273 closeFile(m_fileHandle);
275 stop(NPRES_NETWORK_ERR);
279 void NetscapePluginStream::stop(NPReason reason)
281 // The stream was stopped before it got a chance to start. This can happen if a stream is cancelled by
282 // WebKit before it received a response.
284 ASSERT(reason != NPRES_DONE);
285 notifyAndDestroyStream(reason);
289 if (reason == NPRES_DONE && m_deliveryData && !m_deliveryData->isEmpty()) {
290 // There is still data left that the plug-in hasn't been able to consume yet.
291 ASSERT(m_deliveryDataTimer.isActive());
293 // Set m_stopStreamWhenDoneDelivering to true so that the next time the delivery timer fires
294 // and calls deliverDataToPlugin the stream will be closed if all the remaining data was
295 // successfully delivered.
296 m_stopStreamWhenDoneDelivering = true;
300 m_deliveryData = nullptr;
301 m_deliveryDataTimer.stop();
303 if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY) {
304 if (reason == NPRES_DONE) {
305 // Ensure that the file is created.
306 deliverDataToFile(0, 0);
307 if (m_fileHandle != invalidPlatformFileHandle)
308 closeFile(m_fileHandle);
310 ASSERT(!m_filePath.isNull());
312 m_plugin->NPP_StreamAsFile(&m_npStream, m_filePath.utf8().data());
314 // Just close the file.
315 if (m_fileHandle != invalidPlatformFileHandle)
316 closeFile(m_fileHandle);
319 // Delete the file after calling NPP_StreamAsFile(), instead of in the destructor. It should be OK
320 // to delete the file here -- NPP_StreamAsFile() is always called immediately before NPP_DestroyStream()
321 // (the stream destruction function), so there can be no expectation that a plugin will read the stream
322 // file asynchronously after NPP_StreamAsFile() is called.
323 deleteFile(m_filePath);
324 m_filePath = String();
326 // NPP_StreamAsFile could call NPN_DestroyStream and destroy the stream.
331 // Set m_isStarted to false before calling NPP_DestroyStream in case NPP_DestroyStream calls NPN_DestroyStream.
334 m_plugin->NPP_DestroyStream(&m_npStream, reason);
336 notifyAndDestroyStream(reason);
339 void NetscapePluginStream::cancel()
341 m_plugin->cancelStreamLoad(this);
344 void NetscapePluginStream::notifyAndDestroyStream(NPReason reason)
346 ASSERT(!m_isStarted);
347 ASSERT(!m_deliveryDataTimer.isActive());
348 ASSERT(!m_urlNotifyHasBeenCalled);
350 if (m_sendNotification) {
351 m_plugin->NPP_URLNotify(m_requestURLString.utf8().data(), reason, m_notificationData);
354 m_urlNotifyHasBeenCalled = true;
358 m_plugin->removePluginStream(this);
361 } // namespace WebKit