initial import
[vuplus_webkit] / Source / WebKit2 / WebProcess / Plugins / Netscape / NetscapePluginStream.cpp
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
37 NetscapePluginStream::NetscapePluginStream(PassRefPtr<NetscapePlugin> plugin, uint64_t streamID, const String& requestURLString, bool sendNotification, void* notificationData)
38     : m_plugin(plugin)
39     , m_streamID(streamID)
40     , m_requestURLString(requestURLString)
41     , m_sendNotification(sendNotification)
42     , m_notificationData(notificationData)
43     , m_npStream()
44     , m_transferMode(NP_NORMAL)
45     , m_offset(0)
46     , m_fileHandle(invalidPlatformFileHandle)
47     , m_isStarted(false)
48 #if !ASSERT_DISABLED
49     , m_urlNotifyHasBeenCalled(false)
50 #endif    
51     , m_deliveryDataTimer(RunLoop::main(), this, &NetscapePluginStream::deliverDataToPlugin)
52     , m_stopStreamWhenDoneDelivering(false)
53 {
54 }
55
56 NetscapePluginStream::~NetscapePluginStream()
57 {
58     ASSERT(!m_isStarted);
59     ASSERT(!m_sendNotification || m_urlNotifyHasBeenCalled);
60     ASSERT(m_fileHandle == invalidPlatformFileHandle);
61 }
62
63 void NetscapePluginStream::didReceiveResponse(const KURL& responseURL, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers)
64 {
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);
67
68     start(responseURL, streamLength, lastModifiedTime, mimeType, headers);
69 }
70
71 void NetscapePluginStream::didReceiveData(const char* bytes, int length)
72 {
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);
75
76     deliverData(bytes, length);
77 }
78
79 void NetscapePluginStream::didFinishLoading()
80 {
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);
83
84     stop(NPRES_DONE);
85 }
86
87 void NetscapePluginStream::didFail(bool wasCancelled)
88 {
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);
91
92     stop(wasCancelled ? NPRES_USER_BREAK : NPRES_NETWORK_ERR);
93 }
94     
95 void NetscapePluginStream::sendJavaScriptStream(const String& result)
96 {
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);
100
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);
105         return;
106     }
107
108     if (!start(m_requestURLString, resultCString.length(), 0, "text/plain", ""))
109         return;
110
111     deliverData(resultCString.data(), resultCString.length());
112     stop(NPRES_DONE);
113 }
114
115 NPError NetscapePluginStream::destroy(NPReason reason)
116 {
117     // It doesn't make sense to call NPN_DestroyStream on a stream that hasn't been started yet.
118     if (!m_isStarted)
119         return NPERR_GENERIC_ERROR;
120
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;
125
126     cancel();
127     stop(reason);
128     return NPERR_NO_ERROR;
129 }
130
131 static bool isSupportedTransferMode(uint16_t transferMode)
132 {
133     switch (transferMode) {
134     case NP_ASFILEONLY:
135     case NP_ASFILE:
136     case NP_NORMAL:
137         return true;
138     // FIXME: We don't support seekable streams.
139     case NP_SEEK:
140         return false;
141     }
142
143     ASSERT_NOT_REACHED();
144     return false;
145 }
146     
147 bool NetscapePluginStream::start(const String& responseURLString, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers)
148 {
149     m_responseURL = responseURLString.utf8();
150     m_mimeType = mimeType.utf8();
151     m_headers = headers.utf8();
152
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();
159
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.
163         cancel();
164         notifyAndDestroyStream(NPRES_NETWORK_ERR);
165         return false;
166     }
167
168     // We successfully started the stream.
169     m_isStarted = true;
170
171     if (!isSupportedTransferMode(m_transferMode)) {
172         // Cancel the load and stop the stream.
173         cancel();
174         stop(NPRES_NETWORK_ERR);
175         return false;
176     }
177
178     return true;
179 }
180
181 void NetscapePluginStream::deliverData(const char* bytes, int length)
182 {
183     ASSERT(m_isStarted);
184
185     if (m_transferMode != NP_ASFILEONLY) {
186         if (!m_deliveryData)
187             m_deliveryData = adoptPtr(new Vector<uint8_t>);
188
189         m_deliveryData->reserveCapacity(m_deliveryData->size() + length);
190         m_deliveryData->append(bytes, length);
191         
192         deliverDataToPlugin();
193     }
194
195     if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY)
196         deliverDataToFile(bytes, length);
197 }
198
199 void NetscapePluginStream::deliverDataToPlugin()
200 {
201     ASSERT(m_isStarted);
202
203     int32_t numBytesToDeliver = m_deliveryData->size();
204     int32_t numBytesDelivered = 0;
205
206     while (numBytesDelivered < numBytesToDeliver) {
207         int32_t numBytesPluginCanHandle = m_plugin->NPP_WriteReady(&m_npStream);
208         
209         // NPP_WriteReady could call NPN_DestroyStream and destroy the stream.
210         if (!m_isStarted)
211             return;
212
213         if (numBytesPluginCanHandle <= 0) {
214             // The plug-in can't handle more data, we'll send the rest later
215             m_deliveryDataTimer.startOneShot(0);
216             break;
217         }
218
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;
222
223         int32_t numBytesWritten = m_plugin->NPP_Write(&m_npStream, m_offset, dataLength, data);
224         if (numBytesWritten < 0) {
225             cancel();
226             stop(NPRES_NETWORK_ERR);
227             return;
228         }
229
230         // NPP_Write could call NPN_DestroyStream and destroy the stream.
231         if (!m_isStarted)
232             return;
233
234         numBytesWritten = min(numBytesWritten, dataLength);
235         m_offset += numBytesWritten;
236         numBytesDelivered += numBytesWritten;
237     }
238
239     // We didn't write anything.
240     if (!numBytesDelivered)
241         return;
242
243     if (numBytesDelivered < numBytesToDeliver) {
244         // Remove the bytes that we actually delivered.
245         m_deliveryData->remove(0, numBytesDelivered);
246     } else {
247         m_deliveryData->clear();
248
249         if (m_stopStreamWhenDoneDelivering)
250             stop(NPRES_DONE);
251     }
252 }
253
254 void NetscapePluginStream::deliverDataToFile(const char* bytes, int length)
255 {
256     if (m_fileHandle == invalidPlatformFileHandle && m_filePath.isNull()) {
257         // Create a temporary file.
258         m_filePath = openTemporaryFile("WebKitPluginStream", m_fileHandle);
259
260         // We failed to open the file, stop the stream.
261         if (m_fileHandle == invalidPlatformFileHandle) {
262             stop(NPRES_NETWORK_ERR);
263             return;
264         }
265     }
266
267     if (!length)
268         return;
269
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);
274
275         stop(NPRES_NETWORK_ERR);
276     }
277 }
278
279 void NetscapePluginStream::stop(NPReason reason)
280 {
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.
283     if (!m_isStarted) {
284         ASSERT(reason != NPRES_DONE);
285         notifyAndDestroyStream(reason);
286         return;
287     }
288
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());
292         
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;
297         return;
298     }
299
300     m_deliveryData = nullptr;
301     m_deliveryDataTimer.stop();
302
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);
309             
310             ASSERT(!m_filePath.isNull());
311             
312             m_plugin->NPP_StreamAsFile(&m_npStream, m_filePath.utf8().data());
313         } else {
314             // Just close the file.
315             if (m_fileHandle != invalidPlatformFileHandle)
316                 closeFile(m_fileHandle);
317         }
318
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();
325
326         // NPP_StreamAsFile could call NPN_DestroyStream and destroy the stream.
327         if (!m_isStarted)
328             return;
329     }
330
331     // Set m_isStarted to false before calling NPP_DestroyStream in case NPP_DestroyStream calls NPN_DestroyStream.
332     m_isStarted = false;
333
334     m_plugin->NPP_DestroyStream(&m_npStream, reason);
335
336     notifyAndDestroyStream(reason);
337 }
338
339 void NetscapePluginStream::cancel()
340 {
341     m_plugin->cancelStreamLoad(this);
342 }
343
344 void NetscapePluginStream::notifyAndDestroyStream(NPReason reason)
345 {
346     ASSERT(!m_isStarted);
347     ASSERT(!m_deliveryDataTimer.isActive());
348     ASSERT(!m_urlNotifyHasBeenCalled);
349     
350     if (m_sendNotification) {
351         m_plugin->NPP_URLNotify(m_requestURLString.utf8().data(), reason, m_notificationData);
352     
353 #if !ASSERT_DISABLED
354         m_urlNotifyHasBeenCalled = true;
355 #endif    
356     }
357
358     m_plugin->removePluginStream(this);
359 }
360
361 } // namespace WebKit