1/*
2 * Copyright (C) 2010 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
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.
12 *
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.
24 */
25
26#include "config.h"
27#include "NetscapePluginStream.h"
28
29#if ENABLE(NETSCAPE_PLUGIN_API)
30
31#include "NetscapePlugin.h"
32#include <utility>
33#include <wtf/Vector.h>
34
35namespace WebKit {
36using namespace WebCore;
37
38NetscapePluginStream::NetscapePluginStream(Ref<NetscapePlugin>&& plugin, uint64_t streamID, const String& requestURLString, bool sendNotification, void* notificationData)
39 : m_plugin(WTFMove(plugin))
40 , m_streamID(streamID)
41 , m_requestURLString(requestURLString)
42 , m_sendNotification(sendNotification)
43 , m_notificationData(notificationData)
44 , m_npStream()
45 , m_transferMode(NP_NORMAL)
46 , m_offset(0)
47 , m_fileHandle(FileSystem::invalidPlatformFileHandle)
48 , m_isStarted(false)
49#if !ASSERT_DISABLED
50 , m_urlNotifyHasBeenCalled(false)
51#endif
52 , m_deliveryDataTimer(RunLoop::main(), this, &NetscapePluginStream::deliverDataToPlugin)
53 , m_stopStreamWhenDoneDelivering(false)
54{
55}
56
57NetscapePluginStream::~NetscapePluginStream()
58{
59 ASSERT(!m_isStarted);
60 ASSERT(!m_sendNotification || m_urlNotifyHasBeenCalled);
61 ASSERT(m_fileHandle == FileSystem::invalidPlatformFileHandle);
62}
63
64void NetscapePluginStream::willSendRequest(const URL& requestURL, const URL& redirectResponseURL, int redirectResponseStatus)
65{
66 Ref<NetscapePluginStream> protect(*this);
67
68 if (redirectResponseStatus >= 300 && redirectResponseStatus < 400)
69 m_plugin->registerRedirect(this, requestURL, redirectResponseStatus, m_notificationData);
70}
71
72void NetscapePluginStream::didReceiveResponse(const URL& responseURL, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers)
73{
74 // Starting the stream could cause the plug-in stream to go away so we keep a reference to it here.
75 Ref<NetscapePluginStream> protect(*this);
76
77 start(responseURL, streamLength, lastModifiedTime, mimeType, headers);
78}
79
80void NetscapePluginStream::didReceiveData(const char* bytes, int length)
81{
82 // Delivering the data could cause the plug-in stream to go away so we keep a reference to it here.
83 Ref<NetscapePluginStream> protect(*this);
84
85 deliverData(bytes, length);
86}
87
88void NetscapePluginStream::didFinishLoading()
89{
90 // Stopping the stream could cause the plug-in stream to go away so we keep a reference to it here.
91 Ref<NetscapePluginStream> protect(*this);
92
93 stop(NPRES_DONE);
94}
95
96void NetscapePluginStream::didFail(bool wasCancelled)
97{
98 // Stopping the stream could cause the plug-in stream to go away so we keep a reference to it here.
99 Ref<NetscapePluginStream> protect(*this);
100
101 stop(wasCancelled ? NPRES_USER_BREAK : NPRES_NETWORK_ERR);
102}
103
104void NetscapePluginStream::sendJavaScriptStream(const String& result)
105{
106 // starting the stream or delivering the data to it might cause the plug-in stream to go away, so we keep
107 // a reference to it here.
108 Ref<NetscapePluginStream> protect(*this);
109
110 CString resultCString = result.utf8();
111 if (resultCString.isNull()) {
112 // There was an error evaluating the JavaScript, call NPP_URLNotify if needed and then destroy the stream.
113 notifyAndDestroyStream(NPRES_NETWORK_ERR);
114 return;
115 }
116
117 if (!start(m_requestURLString, resultCString.length(), 0, "text/plain", ""))
118 return;
119
120 deliverData(resultCString.data(), resultCString.length());
121 stop(NPRES_DONE);
122}
123
124NPError NetscapePluginStream::destroy(NPReason reason)
125{
126 // It doesn't make sense to call NPN_DestroyStream on a stream that hasn't been started yet.
127 if (!m_isStarted)
128 return NPERR_GENERIC_ERROR;
129
130 // It isn't really valid for a plug-in to call NPN_DestroyStream with NPRES_DONE.
131 // (At least not for browser initiated streams, and we don't support plug-in initiated streams).
132 if (reason == NPRES_DONE)
133 return NPERR_INVALID_PARAM;
134
135 cancel();
136 stop(reason);
137 return NPERR_NO_ERROR;
138}
139
140static bool isSupportedTransferMode(uint16_t transferMode)
141{
142 switch (transferMode) {
143 case NP_ASFILEONLY:
144 case NP_ASFILE:
145 case NP_NORMAL:
146 return true;
147 // FIXME: We don't support seekable streams.
148 case NP_SEEK:
149 return false;
150 }
151
152 ASSERT_NOT_REACHED();
153 return false;
154}
155
156bool NetscapePluginStream::start(const String& responseURLString, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers)
157{
158 m_responseURL = responseURLString.utf8();
159 m_mimeType = mimeType.utf8();
160 m_headers = headers.utf8();
161
162 m_npStream.ndata = this;
163 m_npStream.url = m_responseURL.data();
164 m_npStream.end = streamLength;
165 m_npStream.lastmodified = lastModifiedTime;
166 m_npStream.notifyData = m_notificationData;
167 m_npStream.headers = m_headers.length() == 0 ? 0 : m_headers.data();
168
169 NPError error = m_plugin->NPP_NewStream(const_cast<char*>(m_mimeType.data()), &m_npStream, false, &m_transferMode);
170 if (error != NPERR_NO_ERROR) {
171 // We failed to start the stream, cancel the load and destroy it.
172 cancel();
173 notifyAndDestroyStream(NPRES_NETWORK_ERR);
174 return false;
175 }
176
177 // We successfully started the stream.
178 m_isStarted = true;
179
180 if (!isSupportedTransferMode(m_transferMode)) {
181 // Cancel the load and stop the stream.
182 cancel();
183 stop(NPRES_NETWORK_ERR);
184 return false;
185 }
186
187 return true;
188}
189
190void NetscapePluginStream::deliverData(const char* bytes, int length)
191{
192 ASSERT(m_isStarted);
193
194 if (m_transferMode != NP_ASFILEONLY) {
195 if (!m_deliveryData)
196 m_deliveryData = std::make_unique<Vector<uint8_t>>();
197
198 m_deliveryData->reserveCapacity(m_deliveryData->size() + length);
199 m_deliveryData->append(bytes, length);
200
201 deliverDataToPlugin();
202 }
203
204 if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY)
205 deliverDataToFile(bytes, length);
206}
207
208void NetscapePluginStream::deliverDataToPlugin()
209{
210 ASSERT(m_isStarted);
211
212 int32_t numBytesToDeliver = m_deliveryData->size();
213 int32_t numBytesDelivered = 0;
214
215 while (numBytesDelivered < numBytesToDeliver) {
216 int32_t numBytesPluginCanHandle = m_plugin->NPP_WriteReady(&m_npStream);
217
218 // NPP_WriteReady could call NPN_DestroyStream and destroy the stream.
219 if (!m_isStarted)
220 return;
221
222 if (numBytesPluginCanHandle <= 0) {
223 // The plug-in can't handle more data, we'll send the rest later
224 m_deliveryDataTimer.startOneShot(0_s);
225 break;
226 }
227
228 // Figure out how much data to send to the plug-in.
229 int32_t dataLength = std::min(numBytesPluginCanHandle, numBytesToDeliver - numBytesDelivered);
230 uint8_t* data = m_deliveryData->data() + numBytesDelivered;
231
232 int32_t numBytesWritten = m_plugin->NPP_Write(&m_npStream, m_offset, dataLength, data);
233 if (numBytesWritten < 0) {
234 cancel();
235 stop(NPRES_NETWORK_ERR);
236 return;
237 }
238
239 // NPP_Write could call NPN_DestroyStream and destroy the stream.
240 if (!m_isStarted)
241 return;
242
243 numBytesWritten = std::min(numBytesWritten, dataLength);
244 m_offset += numBytesWritten;
245 numBytesDelivered += numBytesWritten;
246 }
247
248 // We didn't write anything.
249 if (!numBytesDelivered)
250 return;
251
252 if (numBytesDelivered < numBytesToDeliver) {
253 // Remove the bytes that we actually delivered.
254 m_deliveryData->remove(0, numBytesDelivered);
255 } else {
256 m_deliveryData->clear();
257
258 if (m_stopStreamWhenDoneDelivering)
259 stop(NPRES_DONE);
260 }
261}
262
263void NetscapePluginStream::deliverDataToFile(const char* bytes, int length)
264{
265 if (m_fileHandle == FileSystem::invalidPlatformFileHandle && m_filePath.isNull()) {
266 // Create a temporary file.
267 m_filePath = FileSystem::openTemporaryFile("WebKitPluginStream", m_fileHandle);
268
269 // We failed to open the file, stop the stream.
270 if (m_fileHandle == FileSystem::invalidPlatformFileHandle) {
271 stop(NPRES_NETWORK_ERR);
272 return;
273 }
274 }
275
276 if (!length)
277 return;
278
279 int byteCount = FileSystem::writeToFile(m_fileHandle, bytes, length);
280 if (byteCount != length) {
281 // This happens only rarely, when we are out of disk space or have a disk I/O error.
282 FileSystem::closeFile(m_fileHandle);
283
284 stop(NPRES_NETWORK_ERR);
285 }
286}
287
288void NetscapePluginStream::stop(NPReason reason)
289{
290 // The stream was stopped before it got a chance to start. This can happen if a stream is cancelled by
291 // WebKit before it received a response.
292 if (!m_isStarted) {
293 ASSERT(reason != NPRES_DONE);
294 notifyAndDestroyStream(reason);
295 return;
296 }
297
298 if (reason == NPRES_DONE && m_deliveryData && !m_deliveryData->isEmpty()) {
299 // There is still data left that the plug-in hasn't been able to consume yet.
300 ASSERT(m_deliveryDataTimer.isActive());
301
302 // Set m_stopStreamWhenDoneDelivering to true so that the next time the delivery timer fires
303 // and calls deliverDataToPlugin the stream will be closed if all the remaining data was
304 // successfully delivered.
305 m_stopStreamWhenDoneDelivering = true;
306 return;
307 }
308
309 m_deliveryData = nullptr;
310 m_deliveryDataTimer.stop();
311
312 if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY) {
313 if (reason == NPRES_DONE) {
314 // Ensure that the file is created.
315 deliverDataToFile(0, 0);
316 if (m_fileHandle != FileSystem::invalidPlatformFileHandle)
317 FileSystem::closeFile(m_fileHandle);
318
319 ASSERT(!m_filePath.isNull());
320
321 m_plugin->NPP_StreamAsFile(&m_npStream, m_filePath.utf8().data());
322 } else {
323 // Just close the file.
324 if (m_fileHandle != FileSystem::invalidPlatformFileHandle)
325 FileSystem::closeFile(m_fileHandle);
326 }
327
328 // Delete the file after calling NPP_StreamAsFile(), instead of in the destructor. It should be OK
329 // to delete the file here -- NPP_StreamAsFile() is always called immediately before NPP_DestroyStream()
330 // (the stream destruction function), so there can be no expectation that a plugin will read the stream
331 // file asynchronously after NPP_StreamAsFile() is called.
332 FileSystem::deleteFile(m_filePath);
333 m_filePath = String();
334
335 // NPP_StreamAsFile could call NPN_DestroyStream and destroy the stream.
336 if (!m_isStarted)
337 return;
338 }
339
340 // Set m_isStarted to false before calling NPP_DestroyStream in case NPP_DestroyStream calls NPN_DestroyStream.
341 m_isStarted = false;
342
343 m_plugin->NPP_DestroyStream(&m_npStream, reason);
344
345 notifyAndDestroyStream(reason);
346}
347
348void NetscapePluginStream::setURL(const String& newURLString)
349{
350 m_requestURLString = newURLString;
351}
352
353void NetscapePluginStream::cancel()
354{
355 m_plugin->cancelStreamLoad(this);
356}
357
358void NetscapePluginStream::notifyAndDestroyStream(NPReason reason)
359{
360 ASSERT(!m_isStarted);
361 ASSERT(!m_deliveryDataTimer.isActive());
362 ASSERT(!m_urlNotifyHasBeenCalled);
363
364 if (m_sendNotification) {
365 m_plugin->NPP_URLNotify(m_requestURLString.utf8().data(), reason, m_notificationData);
366
367#if !ASSERT_DISABLED
368 m_urlNotifyHasBeenCalled = true;
369#endif
370 }
371
372 m_plugin->removePluginStream(this);
373}
374
375} // namespace WebKit
376
377#endif // ENABLE(NETSCAPE_PLUGIN_API)
378