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