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 | |
35 | namespace WebKit { |
36 | using namespace WebCore; |
37 | |
38 | NetscapePluginStream::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 | |
57 | NetscapePluginStream::~NetscapePluginStream() |
58 | { |
59 | ASSERT(!m_isStarted); |
60 | ASSERT(!m_sendNotification || m_urlNotifyHasBeenCalled); |
61 | ASSERT(m_fileHandle == FileSystem::invalidPlatformFileHandle); |
62 | } |
63 | |
64 | void 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 | |
72 | void NetscapePluginStream::didReceiveResponse(const URL& responseURL, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& ) |
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 | |
80 | void 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 | |
88 | void 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 | |
96 | void 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 | |
104 | void 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 | |
124 | NPError 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 | |
140 | static 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 | |
156 | bool NetscapePluginStream::start(const String& responseURLString, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& ) |
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 | |
190 | void 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 | |
208 | void 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 | |
263 | void 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 | |
288 | void 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 | |
348 | void NetscapePluginStream::setURL(const String& newURLString) |
349 | { |
350 | m_requestURLString = newURLString; |
351 | } |
352 | |
353 | void NetscapePluginStream::cancel() |
354 | { |
355 | m_plugin->cancelStreamLoad(this); |
356 | } |
357 | |
358 | void 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 | |