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