PluginStream.cpp revision cad810f21b803229eb11403f9209855525a25d57
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 <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.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        String tempFilePath = String::fromUTF8(m_path.data());
311        deleteFile(tempFilePath);
312    }
313}
314
315void PluginStream::delayDeliveryTimerFired(Timer<PluginStream>* timer)
316{
317    ASSERT(timer == &m_delayDeliveryTimer);
318
319    deliverData();
320}
321
322void PluginStream::deliverData()
323{
324    ASSERT(m_deliveryData);
325
326    if (m_streamState == StreamStopped)
327        // FIXME: We should cancel our job in the SubresourceLoader on error so we don't reach this case
328        return;
329
330    ASSERT(m_streamState != StreamBeforeStarted);
331
332    if (!m_stream.ndata || m_deliveryData->size() == 0)
333        return;
334
335    int32_t totalBytes = m_deliveryData->size();
336    int32_t totalBytesDelivered = 0;
337
338    if (m_loader)
339        m_loader->setDefersLoading(true);
340    while (totalBytesDelivered < totalBytes) {
341        int32_t deliveryBytes = m_pluginFuncs->writeready(m_instance, &m_stream);
342
343        if (deliveryBytes <= 0) {
344#if PLATFORM(ANDROID)
345// TODO: This needs to be upstreamed.
346            if (m_loader)
347                m_loader->pauseLoad(true);
348
349            // ask the plugin for a delay value.
350            int delay = deliveryDelay();
351            m_delayDeliveryTimer.startOneShot(delay * 0.001);
352#else
353            m_delayDeliveryTimer.startOneShot(0);
354#endif
355            break;
356        } else {
357            deliveryBytes = min(deliveryBytes, totalBytes - totalBytesDelivered);
358            int32_t dataLength = deliveryBytes;
359            char* data = m_deliveryData->data() + totalBytesDelivered;
360
361            // Write the data
362            deliveryBytes = m_pluginFuncs->write(m_instance, &m_stream, m_offset, dataLength, (void*)data);
363            if (deliveryBytes < 0) {
364                LOG_PLUGIN_NET_ERROR();
365                if (m_loader)
366                    m_loader->setDefersLoading(false);
367                cancelAndDestroyStream(NPRES_NETWORK_ERR);
368                return;
369            }
370            deliveryBytes = min(deliveryBytes, dataLength);
371            m_offset += deliveryBytes;
372            totalBytesDelivered += deliveryBytes;
373        }
374    }
375    if (m_loader)
376        m_loader->setDefersLoading(false);
377
378    if (totalBytesDelivered > 0) {
379        if (totalBytesDelivered < totalBytes) {
380            int remainingBytes = totalBytes - totalBytesDelivered;
381            memmove(m_deliveryData->data(), m_deliveryData->data() + totalBytesDelivered, remainingBytes);
382            m_deliveryData->resize(remainingBytes);
383        } else {
384#if PLATFORM(ANDROID)
385//TODO: This needs to be upstreamed to WebKit.
386            if (m_loader)
387                m_loader->pauseLoad(false);
388#endif
389            m_deliveryData->resize(0);
390            if (m_reason != WebReasonNone)
391                destroyStream();
392        }
393    }
394}
395
396void PluginStream::sendJavaScriptStream(const KURL& requestURL, const CString& resultString)
397{
398    didReceiveResponse(0, ResourceResponse(requestURL, "text/plain", resultString.length(), "", ""));
399
400    if (m_streamState == StreamStopped)
401        return;
402
403    if (!resultString.isNull()) {
404        didReceiveData(0, resultString.data(), resultString.length());
405        if (m_streamState == StreamStopped)
406            return;
407    }
408
409    m_loader = 0;
410
411    destroyStream(resultString.isNull() ? NPRES_NETWORK_ERR : NPRES_DONE);
412}
413
414void PluginStream::didReceiveResponse(NetscapePlugInStreamLoader* loader, const ResourceResponse& response)
415{
416    ASSERT(loader == m_loader);
417    ASSERT(m_streamState == StreamBeforeStarted);
418
419    m_resourceResponse = response;
420
421    startStream();
422}
423
424void PluginStream::didReceiveData(NetscapePlugInStreamLoader* loader, const char* data, int length)
425{
426    ASSERT(loader == m_loader);
427    ASSERT(m_streamState == StreamStarted);
428
429    // If the plug-in cancels the stream in deliverData it could be deleted,
430    // so protect it here.
431    RefPtr<PluginStream> protect(this);
432
433    if (m_transferMode != NP_ASFILEONLY) {
434        if (!m_deliveryData)
435            m_deliveryData.set(new Vector<char>);
436
437        int oldSize = m_deliveryData->size();
438        m_deliveryData->resize(oldSize + length);
439        memcpy(m_deliveryData->data() + oldSize, data, length);
440
441#if PLATFORM(ANDROID)
442//TODO: This needs to be upstreamed to WebKit.
443        if (!m_delayDeliveryTimer.isActive())
444#endif
445        deliverData();
446    }
447
448    if (m_streamState != StreamStopped && isHandleValid(m_tempFileHandle)) {
449        int bytesWritten = writeToFile(m_tempFileHandle, data, length);
450        if (bytesWritten != length)
451            cancelAndDestroyStream(NPRES_NETWORK_ERR);
452    }
453}
454
455void PluginStream::didFail(NetscapePlugInStreamLoader* loader, const ResourceError&)
456{
457    ASSERT(loader == m_loader);
458
459    LOG_PLUGIN_NET_ERROR();
460
461    // destroyStream can result in our being deleted
462    RefPtr<PluginStream> protect(this);
463
464    destroyStream(NPRES_NETWORK_ERR);
465
466    m_loader = 0;
467}
468
469void PluginStream::didFinishLoading(NetscapePlugInStreamLoader* loader)
470{
471    ASSERT(loader == m_loader);
472    ASSERT(m_streamState == StreamStarted);
473
474    // destroyStream can result in our being deleted
475    RefPtr<PluginStream> protect(this);
476
477    destroyStream(NPRES_DONE);
478
479    m_loader = 0;
480}
481
482bool PluginStream::wantsAllStreams() const
483{
484    if (!m_pluginFuncs->getvalue)
485        return false;
486
487    void* result = 0;
488    if (m_pluginFuncs->getvalue(m_instance, NPPVpluginWantsAllNetworkStreams, &result) != NPERR_NO_ERROR)
489        return false;
490
491    return result != 0;
492}
493
494#if PLATFORM(ANDROID)
495int PluginStream::deliveryDelay() const
496{
497    if (!m_pluginFuncs->getvalue)
498        return 0;
499
500    int delay = 0;
501    if (m_pluginFuncs->getvalue(m_instance, NPPDataDeliveryDelayMs, &delay) != NPERR_NO_ERROR)
502        return 0;
503
504    return delay;
505}
506#endif
507
508}
509