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.
43 static const int WebReasonNone = -2;
44 
45 using std::max;
46 using std::min;
47 
48 namespace WebCore {
49 
50 typedef HashMap<NPStream*, NPP> StreamMap;
streams()51 static StreamMap& streams()
52 {
53     static StreamMap staticStreams;
54     return staticStreams;
55 }
56 
PluginStream(PluginStreamClient * client,Frame * frame,const ResourceRequest & resourceRequest,bool sendNotification,void * notifyData,const NPPluginFuncs * pluginFuncs,NPP instance,const PluginQuirkSet & quirks)57 PluginStream::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_tempFileHandle(invalidPlatformFileHandle)
67     , m_pluginFuncs(pluginFuncs)
68     , m_instance(instance)
69     , m_quirks(quirks)
70 {
71     ASSERT(m_instance);
72 
73     m_stream.url = 0;
74     m_stream.ndata = 0;
75     m_stream.pdata = 0;
76     m_stream.end = 0;
77     m_stream.notifyData = 0;
78     m_stream.lastmodified = 0;
79     m_stream.headers = 0;
80 
81     streams().add(&m_stream, m_instance);
82 }
83 
~PluginStream()84 PluginStream::~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 
start()94 void PluginStream::start()
95 {
96     ASSERT(!m_loadManually);
97     m_loader = resourceLoadScheduler()->schedulePluginStreamLoad(m_frame, this, m_resourceRequest);
98 }
99 
stop()100 void 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 
startStream()124 void 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 
ownerForStream(NPStream * stream)210 NPP PluginStream::ownerForStream(NPStream* stream)
211 {
212     return streams().get(stream);
213 }
214 
cancelAndDestroyStream(NPReason reason)215 void PluginStream::cancelAndDestroyStream(NPReason reason)
216 {
217     RefPtr<PluginStream> protect(this);
218 
219     destroyStream(reason);
220     stop();
221 }
222 
destroyStream(NPReason reason)223 void 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 
destroyStream()237 void 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 
delayDeliveryTimerFired(Timer<PluginStream> * timer)313 void PluginStream::delayDeliveryTimerFired(Timer<PluginStream>* timer)
314 {
315     ASSERT(timer == &m_delayDeliveryTimer);
316 
317     deliverData();
318 }
319 
deliverData()320 void 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 
sendJavaScriptStream(const KURL & requestURL,const CString & resultString)379 void 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 
didReceiveResponse(NetscapePlugInStreamLoader * loader,const ResourceResponse & response)397 void PluginStream::didReceiveResponse(NetscapePlugInStreamLoader* loader, const ResourceResponse& response)
398 {
399     ASSERT(loader == m_loader);
400     ASSERT(m_streamState == StreamBeforeStarted);
401 
402     m_resourceResponse = response;
403 
404     startStream();
405 }
406 
didReceiveData(NetscapePlugInStreamLoader * loader,const char * data,int length)407 void PluginStream::didReceiveData(NetscapePlugInStreamLoader* loader, const char* data, int length)
408 {
409     ASSERT(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 
didFail(NetscapePlugInStreamLoader * loader,const ResourceError &)434 void PluginStream::didFail(NetscapePlugInStreamLoader* loader, const ResourceError&)
435 {
436     ASSERT(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 
didFinishLoading(NetscapePlugInStreamLoader * loader)448 void PluginStream::didFinishLoading(NetscapePlugInStreamLoader* loader)
449 {
450     ASSERT(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 
wantsAllStreams() const461 bool 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