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