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