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/StringBuilder.h>
40#include <wtf/text/WTFString.h>
41
42// We use -2 here because some plugins like to return -1 to indicate error
43// and this way we won't clash with them.
44static const int WebReasonNone = -2;
45
46using std::max;
47using std::min;
48
49namespace WebCore {
50
51typedef HashMap<NPStream*, NPP> StreamMap;
52static StreamMap& streams()
53{
54    static StreamMap staticStreams;
55    return staticStreams;
56}
57
58PluginStream::PluginStream(PluginStreamClient* client, Frame* frame, const ResourceRequest& resourceRequest, bool sendNotification, void* notifyData, const NPPluginFuncs* pluginFuncs, NPP instance, const PluginQuirkSet& quirks)
59    : m_resourceRequest(resourceRequest)
60    , m_client(client)
61    , m_frame(frame)
62    , m_notifyData(notifyData)
63    , m_sendNotification(sendNotification)
64    , m_streamState(StreamBeforeStarted)
65    , m_loadManually(false)
66    , m_delayDeliveryTimer(this, &PluginStream::delayDeliveryTimerFired)
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        StringBuilder stringBuilder;
143        String separator = ASCIILiteral(": ");
144
145        String statusLine = "HTTP " + String::number(m_resourceResponse.httpStatusCode()) + " OK\n";
146        stringBuilder.append(statusLine);
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->key);
151            stringBuilder.append(separator);
152            stringBuilder.append(it->value);
153            stringBuilder.append('\n');
154        }
155
156        m_headers = stringBuilder.toString().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_UNUSED(timer, 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            m_delayDeliveryTimer.startOneShot(0);
343            break;
344        } else {
345            deliveryBytes = min(deliveryBytes, totalBytes - totalBytesDelivered);
346            int32_t dataLength = deliveryBytes;
347            char* data = m_deliveryData->data() + totalBytesDelivered;
348
349            // Write the data
350            deliveryBytes = m_pluginFuncs->write(m_instance, &m_stream, m_offset, dataLength, (void*)data);
351            if (deliveryBytes < 0) {
352                LOG_PLUGIN_NET_ERROR();
353                if (m_loader)
354                    m_loader->setDefersLoading(false);
355                cancelAndDestroyStream(NPRES_NETWORK_ERR);
356                return;
357            }
358            deliveryBytes = min(deliveryBytes, dataLength);
359            m_offset += deliveryBytes;
360            totalBytesDelivered += deliveryBytes;
361        }
362    }
363    if (m_loader)
364        m_loader->setDefersLoading(false);
365
366    if (totalBytesDelivered > 0) {
367        if (totalBytesDelivered < totalBytes) {
368            int remainingBytes = totalBytes - totalBytesDelivered;
369            memmove(m_deliveryData->data(), m_deliveryData->data() + totalBytesDelivered, remainingBytes);
370            m_deliveryData->resize(remainingBytes);
371        } else {
372            m_deliveryData->resize(0);
373            if (m_reason != WebReasonNone)
374                destroyStream();
375        }
376    }
377}
378
379void PluginStream::sendJavaScriptStream(const KURL& requestURL, const CString& resultString)
380{
381    didReceiveResponse(0, ResourceResponse(requestURL, "text/plain", resultString.length(), "", ""));
382
383    if (m_streamState == StreamStopped)
384        return;
385
386    if (!resultString.isNull()) {
387        didReceiveData(0, resultString.data(), resultString.length());
388        if (m_streamState == StreamStopped)
389            return;
390    }
391
392    m_loader = 0;
393
394    destroyStream(resultString.isNull() ? NPRES_NETWORK_ERR : NPRES_DONE);
395}
396
397void PluginStream::didReceiveResponse(NetscapePlugInStreamLoader* loader, const ResourceResponse& response)
398{
399    ASSERT_UNUSED(loader, loader == m_loader);
400    ASSERT(m_streamState == StreamBeforeStarted);
401
402    m_resourceResponse = response;
403
404    startStream();
405}
406
407void PluginStream::didReceiveData(NetscapePlugInStreamLoader* loader, const char* data, int length)
408{
409    ASSERT_UNUSED(loader, loader == m_loader);
410    ASSERT(m_streamState == StreamStarted);
411
412    // If the plug-in cancels the stream in deliverData it could be deleted,
413    // so protect it here.
414    RefPtr<PluginStream> protect(this);
415
416    if (m_transferMode != NP_ASFILEONLY) {
417        if (!m_deliveryData)
418            m_deliveryData = adoptPtr(new Vector<char>);
419
420        int oldSize = m_deliveryData->size();
421        m_deliveryData->resize(oldSize + length);
422        memcpy(m_deliveryData->data() + oldSize, data, length);
423
424        deliverData();
425    }
426
427    if (m_streamState != StreamStopped && isHandleValid(m_tempFileHandle)) {
428        int bytesWritten = writeToFile(m_tempFileHandle, data, length);
429        if (bytesWritten != length)
430            cancelAndDestroyStream(NPRES_NETWORK_ERR);
431    }
432}
433
434void PluginStream::didFail(NetscapePlugInStreamLoader* loader, const ResourceError&)
435{
436    ASSERT_UNUSED(loader, loader == m_loader);
437
438    LOG_PLUGIN_NET_ERROR();
439
440    // destroyStream can result in our being deleted
441    RefPtr<PluginStream> protect(this);
442
443    destroyStream(NPRES_NETWORK_ERR);
444
445    m_loader = 0;
446}
447
448void PluginStream::didFinishLoading(NetscapePlugInStreamLoader* loader)
449{
450    ASSERT_UNUSED(loader, loader == m_loader);
451    ASSERT(m_streamState == StreamStarted);
452
453    // destroyStream can result in our being deleted
454    RefPtr<PluginStream> protect(this);
455
456    destroyStream(NPRES_DONE);
457
458    m_loader = 0;
459}
460
461bool PluginStream::wantsAllStreams() const
462{
463    if (!m_pluginFuncs->getvalue)
464        return false;
465
466    void* result = 0;
467    if (m_pluginFuncs->getvalue(m_instance, NPPVpluginWantsAllNetworkStreams, &result) != NPERR_NO_ERROR)
468        return false;
469
470    return result != 0;
471}
472
473}
474