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