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