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.
45static const int WebReasonNone = -2;
46
47using std::max;
48using std::min;
49
50namespace WebCore {
51
52typedef HashMap<NPStream*, NPP> StreamMap;
53static StreamMap& streams()
54{
55    static StreamMap staticStreams;
56    return staticStreams;
57}
58
59PluginStream::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
86PluginStream::~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
96void PluginStream::start()
97{
98    ASSERT(!m_loadManually);
99    m_loader = resourceLoadScheduler()->schedulePluginStreamLoad(m_frame, this, m_resourceRequest);
100}
101
102void 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
126static 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
135void 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
221NPP PluginStream::ownerForStream(NPStream* stream)
222{
223    return streams().get(stream);
224}
225
226void PluginStream::cancelAndDestroyStream(NPReason reason)
227{
228    RefPtr<PluginStream> protect(this);
229
230    destroyStream(reason);
231    stop();
232}
233
234void 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
248void 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
324void PluginStream::delayDeliveryTimerFired(Timer<PluginStream>* timer)
325{
326    ASSERT_UNUSED(timer, timer == &m_delayDeliveryTimer);
327
328    deliverData();
329}
330
331void 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
390void 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
408void 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
418void 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
445void 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
459void 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
472bool 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}
485