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