• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_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 
~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 #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 
sendJavaScriptStream(const KURL & requestURL,const CString & resultString)394 void 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 
didReceiveResponse(NetscapePlugInStreamLoader * loader,const ResourceResponse & response)412 void 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 
didReceiveData(NetscapePlugInStreamLoader * loader,const char * data,int length)422 void 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 
didFail(NetscapePlugInStreamLoader * loader,const ResourceError &)453 void 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 
didFinishLoading(NetscapePlugInStreamLoader * loader)467 void 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 
wantsAllStreams() const480 bool 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)
deliveryDelay() const493 int 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