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