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