Make ResourceLoader::willSendRequestInternal asynchronous
[WebKit-https.git] / Source / WebKitLegacy / win / Plugins / PluginStream.cpp
1 /*
2  * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
3  * Copyright (C) 2008 Collabora, Ltd. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
25  */
26
27 #include "config.h"
28 #include "PluginStream.h"
29
30 #include "DocumentLoader.h"
31 #include "Frame.h"
32 #include "FrameLoader.h"
33 #include "HTTPHeaderNames.h"
34 #include "PluginDebug.h"
35 #include "SharedBuffer.h"
36 #include "SubresourceLoader.h"
37 #include "WebResourceLoadScheduler.h"
38 #include <wtf/CompletionHandler.h>
39 #include <wtf/StringExtras.h>
40 #include <wtf/text/CString.h>
41 #include <wtf/text/StringBuilder.h>
42 #include <wtf/text/WTFString.h>
43
44 // We use -2 here because some plugins like to return -1 to indicate error
45 // and this way we won't clash with them.
46 static const int WebReasonNone = -2;
47
48 using std::max;
49 using std::min;
50
51 namespace WebCore {
52
53 typedef HashMap<NPStream*, NPP> StreamMap;
54 static StreamMap& streams()
55 {
56     static StreamMap staticStreams;
57     return staticStreams;
58 }
59
60 PluginStream::PluginStream(PluginStreamClient* client, Frame* frame, const ResourceRequest& resourceRequest, bool sendNotification, void* notifyData, const NPPluginFuncs* pluginFuncs, NPP instance, const PluginQuirkSet& quirks)
61     : m_resourceRequest(resourceRequest)
62     , m_client(client)
63     , m_frame(frame)
64     , m_notifyData(notifyData)
65     , m_sendNotification(sendNotification)
66     , m_streamState(StreamBeforeStarted)
67     , m_loadManually(false)
68     , m_delayDeliveryTimer(*this, &PluginStream::delayDeliveryTimerFired)
69     , m_tempFileHandle(FileSystem::invalidPlatformFileHandle)
70     , m_pluginFuncs(pluginFuncs)
71     , m_instance(instance)
72     , m_quirks(quirks)
73 {
74     ASSERT(m_instance);
75
76     m_stream.url = 0;
77     m_stream.ndata = 0;
78     m_stream.pdata = 0;
79     m_stream.end = 0;
80     m_stream.notifyData = 0;
81     m_stream.lastmodified = 0;
82     m_stream.headers = 0;
83
84     streams().add(&m_stream, m_instance);
85 }
86
87 PluginStream::~PluginStream()
88 {
89     ASSERT(m_streamState != StreamStarted);
90     ASSERT(!m_loader);
91
92     fastFree((char*)m_stream.url);
93
94     streams().remove(&m_stream);
95 }
96
97 void PluginStream::start()
98 {
99     ASSERT(!m_loadManually);
100     ASSERT(m_frame);
101     webResourceLoadScheduler().schedulePluginStreamLoad(*m_frame, *this, ResourceRequest(m_resourceRequest), [this, protectedThis = makeRef(*this)] (RefPtr<WebCore::NetscapePlugInStreamLoader>&& loader) {
102         m_loader = WTFMove(loader);
103     });
104 }
105
106 void PluginStream::stop()
107 {
108     m_streamState = StreamStopped;
109
110     if (m_loadManually) {
111         ASSERT(!m_loader);
112
113         DocumentLoader* documentLoader = m_frame->loader().activeDocumentLoader();
114         ASSERT(documentLoader);
115
116         if (documentLoader->isLoadingMainResource())
117             documentLoader->cancelMainResourceLoad(m_frame->loader().cancelledError(m_resourceRequest));
118
119         return;
120     }
121
122     if (m_loader) {
123         m_loader->cancel();
124         m_loader = nullptr;
125     }
126
127     m_client = 0;
128 }
129
130 static uint32_t lastModifiedDateMS(const ResourceResponse& response)
131 {
132     auto lastModified = response.lastModified();
133     if (!lastModified)
134         return 0;
135
136     return std::chrono::duration_cast<std::chrono::milliseconds>(lastModified.value().time_since_epoch()).count();
137 }
138
139 void PluginStream::startStream()
140 {
141     ASSERT(m_streamState == StreamBeforeStarted);
142
143     const URL& responseURL = m_resourceResponse.url();
144
145     // Some plugins (Flash) expect that javascript URLs are passed back decoded as this is the
146     // format used when requesting the URL.
147     if (protocolIsJavaScript(responseURL))
148         m_stream.url = fastStrDup(decodeURLEscapeSequences(responseURL.string()).utf8().data());
149     else
150         m_stream.url = fastStrDup(responseURL.string().utf8().data());
151
152     CString mimeTypeStr = m_resourceResponse.mimeType().utf8();
153
154     long long expectedContentLength = m_resourceResponse.expectedContentLength();
155
156     if (m_resourceResponse.isHTTP()) {
157         StringBuilder stringBuilder;
158         String separator = ASCIILiteral(": ");
159
160         String statusLine = "HTTP " + String::number(m_resourceResponse.httpStatusCode()) + " OK\n";
161         stringBuilder.append(statusLine);
162
163         HTTPHeaderMap::const_iterator end = m_resourceResponse.httpHeaderFields().end();
164         for (HTTPHeaderMap::const_iterator it = m_resourceResponse.httpHeaderFields().begin(); it != end; ++it) {
165             stringBuilder.append(it->key);
166             stringBuilder.append(separator);
167             stringBuilder.append(it->value);
168             stringBuilder.append('\n');
169         }
170
171         m_headers = stringBuilder.toString().utf8();
172
173         // If the content is encoded (most likely compressed), then don't send its length to the plugin,
174         // which is only interested in the decoded length, not yet known at the moment.
175         // <rdar://problem/4470599> tracks a request for -[NSURLResponse expectedContentLength] to incorporate this logic.
176         String contentEncoding = m_resourceResponse.httpHeaderField(HTTPHeaderName::ContentEncoding);
177         if (!contentEncoding.isNull() && contentEncoding != "identity")
178             expectedContentLength = -1;
179     }
180
181     m_stream.headers = m_headers.data();
182     m_stream.pdata = 0;
183     m_stream.ndata = this;
184     m_stream.end = max(expectedContentLength, 0LL);
185     m_stream.lastmodified = lastModifiedDateMS(m_resourceResponse);
186     m_stream.notifyData = m_notifyData;
187
188     m_transferMode = NP_NORMAL;
189     m_offset = 0;
190     m_reason = WebReasonNone;
191
192     // Protect the stream if destroystream is called from within the newstream handler
193     RefPtr<PluginStream> protect(this);
194
195     // calling into a plug-in could result in re-entrance if the plug-in yields
196     // control to the system (rdar://5744899). prevent this by deferring further
197     // loading while calling into the plug-in.
198     if (m_loader)
199         m_loader->setDefersLoading(true);
200     NPError npErr = m_pluginFuncs->newstream(m_instance, (NPMIMEType)mimeTypeStr.data(), &m_stream, false, &m_transferMode);
201     if (m_loader)
202         m_loader->setDefersLoading(false);
203     
204     // If the stream was destroyed in the call to newstream we return
205     if (m_reason != WebReasonNone)
206         return;
207         
208     if (npErr != NPERR_NO_ERROR) {
209         cancelAndDestroyStream(npErr);
210         return;
211     }
212
213     m_streamState = StreamStarted;
214
215     if (m_transferMode == NP_NORMAL)
216         return;
217
218     m_path = FileSystem::openTemporaryFile("WKP", m_tempFileHandle);
219
220     // Something went wrong, cancel loading the stream
221     if (!FileSystem::isHandleValid(m_tempFileHandle))
222         cancelAndDestroyStream(NPRES_NETWORK_ERR);
223 }
224
225 NPP PluginStream::ownerForStream(NPStream* stream)
226 {
227     return streams().get(stream);
228 }
229
230 void PluginStream::cancelAndDestroyStream(NPReason reason)
231 {
232     RefPtr<PluginStream> protect(this);
233
234     destroyStream(reason);
235     stop();
236 }
237
238 void PluginStream::destroyStream(NPReason reason)
239 {
240     m_reason = reason;
241     if (m_reason != NPRES_DONE) {
242         // Stop any pending data from being streamed
243         if (m_deliveryData)
244             m_deliveryData->resize(0);
245     } else if (m_deliveryData && m_deliveryData->size() > 0) {
246         // There is more data to be streamed, don't destroy the stream now.
247         return;
248     }
249     destroyStream();
250 }
251
252 void PluginStream::destroyStream()
253 {
254     if (m_streamState == StreamStopped)
255         return;
256
257     ASSERT(m_reason != WebReasonNone);
258     ASSERT(!m_deliveryData || m_deliveryData->size() == 0);
259
260     FileSystem::closeFile(m_tempFileHandle);
261
262     bool newStreamCalled = m_stream.ndata;
263
264     // Protect from destruction if:
265     //  NPN_DestroyStream is called from NPP_NewStream or
266     //  PluginStreamClient::streamDidFinishLoading() removes the last reference
267     RefPtr<PluginStream> protect(this);
268
269     if (newStreamCalled) {
270         if (m_reason == NPRES_DONE && (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY)) {
271             ASSERT(!m_path.isNull());
272
273             if (m_loader)
274                 m_loader->setDefersLoading(true);
275             m_pluginFuncs->asfile(m_instance, &m_stream, m_path.utf8().data());
276             if (m_loader)
277                 m_loader->setDefersLoading(false);
278         }
279
280         if (m_streamState != StreamBeforeStarted) {
281             if (m_loader)
282                 m_loader->setDefersLoading(true);
283
284             NPError npErr = m_pluginFuncs->destroystream(m_instance, &m_stream, m_reason);
285
286             if (m_loader)
287                 m_loader->setDefersLoading(false);
288
289             LOG_NPERROR(npErr);
290         }
291
292         m_stream.ndata = 0;
293     }
294
295     if (m_sendNotification) {
296         // Flash 9 can dereference null if we call NPP_URLNotify without first calling NPP_NewStream
297         // for requests made with NPN_PostURLNotify; see <rdar://5588807>
298         if (m_loader)
299             m_loader->setDefersLoading(true);
300         if (!newStreamCalled && m_quirks.contains(PluginQuirkFlashURLNotifyBug) &&
301             equalLettersIgnoringASCIICase(m_resourceRequest.httpMethod(), "post")) {
302             m_transferMode = NP_NORMAL;
303             m_stream.url = "";
304             m_stream.notifyData = m_notifyData;
305
306             static char emptyMimeType[] = "";
307             m_pluginFuncs->newstream(m_instance, emptyMimeType, &m_stream, false, &m_transferMode);
308             m_pluginFuncs->destroystream(m_instance, &m_stream, m_reason);
309
310             // in successful requests, the URL is dynamically allocated and freed in our
311             // destructor, so reset it to 0
312             m_stream.url = 0;
313         }
314         m_pluginFuncs->urlnotify(m_instance, m_resourceRequest.url().string().utf8().data(), m_reason, m_notifyData);
315         if (m_loader)
316             m_loader->setDefersLoading(false);
317     }
318
319     m_streamState = StreamStopped;
320
321     if (!m_loadManually && m_client)
322         m_client->streamDidFinishLoading(this);
323
324     if (!m_path.isNull())
325         FileSystem::deleteFile(m_path);
326 }
327
328 void PluginStream::delayDeliveryTimerFired()
329 {
330     deliverData();
331 }
332
333 void PluginStream::deliverData()
334 {
335     ASSERT(m_deliveryData);
336     
337     if (m_streamState == StreamStopped)
338         // FIXME: We should cancel our job in the SubresourceLoader on error so we don't reach this case
339         return;
340
341     ASSERT(m_streamState != StreamBeforeStarted);
342
343     if (!m_stream.ndata || m_deliveryData->size() == 0)
344         return;
345
346     int32_t totalBytes = m_deliveryData->size();
347     int32_t totalBytesDelivered = 0;
348
349     if (m_loader)
350         m_loader->setDefersLoading(true);
351     while (totalBytesDelivered < totalBytes) {
352         int32_t deliveryBytes = m_pluginFuncs->writeready(m_instance, &m_stream);
353
354         if (deliveryBytes <= 0) {
355             m_delayDeliveryTimer.startOneShot(0_s);
356             break;
357         } else {
358             deliveryBytes = min(deliveryBytes, totalBytes - totalBytesDelivered);
359             int32_t dataLength = deliveryBytes;
360             char* data = m_deliveryData->data() + totalBytesDelivered;
361
362             // Write the data
363             deliveryBytes = m_pluginFuncs->write(m_instance, &m_stream, m_offset, dataLength, (void*)data);
364             if (deliveryBytes < 0) {
365                 LOG_PLUGIN_NET_ERROR();
366                 if (m_loader)
367                     m_loader->setDefersLoading(false);
368                 cancelAndDestroyStream(NPRES_NETWORK_ERR);
369                 return;
370             }
371             deliveryBytes = min(deliveryBytes, dataLength);
372             m_offset += deliveryBytes;
373             totalBytesDelivered += deliveryBytes;
374         }
375     }
376     if (m_loader)
377         m_loader->setDefersLoading(false);
378
379     if (totalBytesDelivered > 0) {
380         if (totalBytesDelivered < totalBytes) {
381             int remainingBytes = totalBytes - totalBytesDelivered;
382             memmove(m_deliveryData->data(), m_deliveryData->data() + totalBytesDelivered, remainingBytes);
383             m_deliveryData->resize(remainingBytes);
384         } else {
385             m_deliveryData->resize(0);
386             if (m_reason != WebReasonNone)
387                 destroyStream();
388         }
389     } 
390 }
391
392 void PluginStream::sendJavaScriptStream(const URL& requestURL, const CString& resultString)
393 {
394     didReceiveResponse(0, ResourceResponse(requestURL, "text/plain", resultString.length(), ""));
395
396     if (m_streamState == StreamStopped)
397         return;
398
399     if (!resultString.isNull()) {
400         didReceiveData(0, resultString.data(), resultString.length());
401         if (m_streamState == StreamStopped)
402             return;
403     }
404
405     m_loader = nullptr;
406
407     destroyStream(resultString.isNull() ? NPRES_NETWORK_ERR : NPRES_DONE);
408 }
409
410 void PluginStream::willSendRequest(NetscapePlugInStreamLoader*, ResourceRequest&& request, const ResourceResponse&, CompletionHandler<void(WebCore::ResourceRequest&&)>&& callback)
411 {
412     // FIXME: We should notify the plug-in with NPP_URLRedirectNotify here.
413     callback(WTFMove(request));
414 }
415
416 void PluginStream::didReceiveResponse(NetscapePlugInStreamLoader* loader, const ResourceResponse& response)
417 {
418     ASSERT_UNUSED(loader, loader == m_loader);
419     ASSERT(m_streamState == StreamBeforeStarted);
420
421     m_resourceResponse = response;
422
423     startStream();
424 }
425
426 void PluginStream::didReceiveData(NetscapePlugInStreamLoader* loader, const char* data, int length)
427 {
428     ASSERT_UNUSED(loader, loader == m_loader);
429     ASSERT(m_streamState == StreamStarted);
430
431     // If the plug-in cancels the stream in deliverData it could be deleted, 
432     // so protect it here.
433     RefPtr<PluginStream> protect(this);
434
435     if (m_transferMode != NP_ASFILEONLY) {
436         if (!m_deliveryData)
437             m_deliveryData = std::make_unique<Vector<char>>();
438
439         int oldSize = m_deliveryData->size();
440         m_deliveryData->resize(oldSize + length);
441         memcpy(m_deliveryData->data() + oldSize, data, length);
442
443         deliverData();
444     }
445
446     if (m_streamState != StreamStopped && FileSystem::isHandleValid(m_tempFileHandle)) {
447         int bytesWritten = FileSystem::writeToFile(m_tempFileHandle, data, length);
448         if (bytesWritten != length)
449             cancelAndDestroyStream(NPRES_NETWORK_ERR);
450     }
451 }
452
453 void PluginStream::didFail(NetscapePlugInStreamLoader* loader, const ResourceError&)
454 {
455     ASSERT_UNUSED(loader, loader == m_loader);
456
457     LOG_PLUGIN_NET_ERROR();
458
459     // destroyStream can result in our being deleted
460     RefPtr<PluginStream> protect(this);
461
462     destroyStream(NPRES_NETWORK_ERR);
463
464     m_loader = nullptr;
465 }
466
467 void PluginStream::didFinishLoading(NetscapePlugInStreamLoader* loader)
468 {
469     ASSERT_UNUSED(loader, loader == m_loader);
470     ASSERT(m_streamState == StreamStarted);
471
472     // destroyStream can result in our being deleted
473     RefPtr<PluginStream> protect(this);
474
475     destroyStream(NPRES_DONE);
476
477     m_loader = nullptr;
478 }
479
480 bool PluginStream::wantsAllStreams() const
481 {
482     if (!m_pluginFuncs->getvalue)
483         return false;
484
485     void* result = nullptr;
486     if (m_pluginFuncs->getvalue(m_instance, NPPVpluginWantsAllNetworkStreams, &result) != NPERR_NO_ERROR)
487         return false;
488
489     return !!result;
490 }
491
492 }