PluginStream.cpp revision cad810f21b803229eb11403f9209855525a25d57
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 <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. 43static const int WebReasonNone = -2; 44 45using std::max; 46using std::min; 47 48namespace WebCore { 49 50typedef HashMap<NPStream*, NPP> StreamMap; 51static StreamMap& streams() 52{ 53 static StreamMap staticStreams; 54 return staticStreams; 55} 56 57PluginStream::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 84PluginStream::~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 94void PluginStream::start() 95{ 96 ASSERT(!m_loadManually); 97 m_loader = resourceLoadScheduler()->schedulePluginStreamLoad(m_frame, this, m_resourceRequest); 98} 99 100void 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 124void 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 210NPP PluginStream::ownerForStream(NPStream* stream) 211{ 212 return streams().get(stream); 213} 214 215void PluginStream::cancelAndDestroyStream(NPReason reason) 216{ 217 RefPtr<PluginStream> protect(this); 218 219 destroyStream(reason); 220 stop(); 221} 222 223void 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 237void 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.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 String tempFilePath = String::fromUTF8(m_path.data()); 311 deleteFile(tempFilePath); 312 } 313} 314 315void PluginStream::delayDeliveryTimerFired(Timer<PluginStream>* timer) 316{ 317 ASSERT(timer == &m_delayDeliveryTimer); 318 319 deliverData(); 320} 321 322void PluginStream::deliverData() 323{ 324 ASSERT(m_deliveryData); 325 326 if (m_streamState == StreamStopped) 327 // FIXME: We should cancel our job in the SubresourceLoader on error so we don't reach this case 328 return; 329 330 ASSERT(m_streamState != StreamBeforeStarted); 331 332 if (!m_stream.ndata || m_deliveryData->size() == 0) 333 return; 334 335 int32_t totalBytes = m_deliveryData->size(); 336 int32_t totalBytesDelivered = 0; 337 338 if (m_loader) 339 m_loader->setDefersLoading(true); 340 while (totalBytesDelivered < totalBytes) { 341 int32_t deliveryBytes = m_pluginFuncs->writeready(m_instance, &m_stream); 342 343 if (deliveryBytes <= 0) { 344#if PLATFORM(ANDROID) 345// TODO: This needs to be upstreamed. 346 if (m_loader) 347 m_loader->pauseLoad(true); 348 349 // ask the plugin for a delay value. 350 int delay = deliveryDelay(); 351 m_delayDeliveryTimer.startOneShot(delay * 0.001); 352#else 353 m_delayDeliveryTimer.startOneShot(0); 354#endif 355 break; 356 } else { 357 deliveryBytes = min(deliveryBytes, totalBytes - totalBytesDelivered); 358 int32_t dataLength = deliveryBytes; 359 char* data = m_deliveryData->data() + totalBytesDelivered; 360 361 // Write the data 362 deliveryBytes = m_pluginFuncs->write(m_instance, &m_stream, m_offset, dataLength, (void*)data); 363 if (deliveryBytes < 0) { 364 LOG_PLUGIN_NET_ERROR(); 365 if (m_loader) 366 m_loader->setDefersLoading(false); 367 cancelAndDestroyStream(NPRES_NETWORK_ERR); 368 return; 369 } 370 deliveryBytes = min(deliveryBytes, dataLength); 371 m_offset += deliveryBytes; 372 totalBytesDelivered += deliveryBytes; 373 } 374 } 375 if (m_loader) 376 m_loader->setDefersLoading(false); 377 378 if (totalBytesDelivered > 0) { 379 if (totalBytesDelivered < totalBytes) { 380 int remainingBytes = totalBytes - totalBytesDelivered; 381 memmove(m_deliveryData->data(), m_deliveryData->data() + totalBytesDelivered, remainingBytes); 382 m_deliveryData->resize(remainingBytes); 383 } else { 384#if PLATFORM(ANDROID) 385//TODO: This needs to be upstreamed to WebKit. 386 if (m_loader) 387 m_loader->pauseLoad(false); 388#endif 389 m_deliveryData->resize(0); 390 if (m_reason != WebReasonNone) 391 destroyStream(); 392 } 393 } 394} 395 396void PluginStream::sendJavaScriptStream(const KURL& requestURL, const CString& resultString) 397{ 398 didReceiveResponse(0, ResourceResponse(requestURL, "text/plain", resultString.length(), "", "")); 399 400 if (m_streamState == StreamStopped) 401 return; 402 403 if (!resultString.isNull()) { 404 didReceiveData(0, resultString.data(), resultString.length()); 405 if (m_streamState == StreamStopped) 406 return; 407 } 408 409 m_loader = 0; 410 411 destroyStream(resultString.isNull() ? NPRES_NETWORK_ERR : NPRES_DONE); 412} 413 414void PluginStream::didReceiveResponse(NetscapePlugInStreamLoader* loader, const ResourceResponse& response) 415{ 416 ASSERT(loader == m_loader); 417 ASSERT(m_streamState == StreamBeforeStarted); 418 419 m_resourceResponse = response; 420 421 startStream(); 422} 423 424void PluginStream::didReceiveData(NetscapePlugInStreamLoader* loader, const char* data, int length) 425{ 426 ASSERT(loader == m_loader); 427 ASSERT(m_streamState == StreamStarted); 428 429 // If the plug-in cancels the stream in deliverData it could be deleted, 430 // so protect it here. 431 RefPtr<PluginStream> protect(this); 432 433 if (m_transferMode != NP_ASFILEONLY) { 434 if (!m_deliveryData) 435 m_deliveryData.set(new Vector<char>); 436 437 int oldSize = m_deliveryData->size(); 438 m_deliveryData->resize(oldSize + length); 439 memcpy(m_deliveryData->data() + oldSize, data, length); 440 441#if PLATFORM(ANDROID) 442//TODO: This needs to be upstreamed to WebKit. 443 if (!m_delayDeliveryTimer.isActive()) 444#endif 445 deliverData(); 446 } 447 448 if (m_streamState != StreamStopped && isHandleValid(m_tempFileHandle)) { 449 int bytesWritten = writeToFile(m_tempFileHandle, data, length); 450 if (bytesWritten != length) 451 cancelAndDestroyStream(NPRES_NETWORK_ERR); 452 } 453} 454 455void PluginStream::didFail(NetscapePlugInStreamLoader* loader, const ResourceError&) 456{ 457 ASSERT(loader == m_loader); 458 459 LOG_PLUGIN_NET_ERROR(); 460 461 // destroyStream can result in our being deleted 462 RefPtr<PluginStream> protect(this); 463 464 destroyStream(NPRES_NETWORK_ERR); 465 466 m_loader = 0; 467} 468 469void PluginStream::didFinishLoading(NetscapePlugInStreamLoader* loader) 470{ 471 ASSERT(loader == m_loader); 472 ASSERT(m_streamState == StreamStarted); 473 474 // destroyStream can result in our being deleted 475 RefPtr<PluginStream> protect(this); 476 477 destroyStream(NPRES_DONE); 478 479 m_loader = 0; 480} 481 482bool PluginStream::wantsAllStreams() const 483{ 484 if (!m_pluginFuncs->getvalue) 485 return false; 486 487 void* result = 0; 488 if (m_pluginFuncs->getvalue(m_instance, NPPVpluginWantsAllNetworkStreams, &result) != NPERR_NO_ERROR) 489 return false; 490 491 return result != 0; 492} 493 494#if PLATFORM(ANDROID) 495int PluginStream::deliveryDelay() const 496{ 497 if (!m_pluginFuncs->getvalue) 498 return 0; 499 500 int delay = 0; 501 if (m_pluginFuncs->getvalue(m_instance, NPPDataDeliveryDelayMs, &delay) != NPERR_NO_ERROR) 502 return 0; 503 504 return delay; 505} 506#endif 507 508} 509