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. 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.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 313void PluginStream::delayDeliveryTimerFired(Timer<PluginStream>* timer) 314{ 315 ASSERT(timer == &m_delayDeliveryTimer); 316 317 deliverData(); 318} 319 320void 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 394void 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 412void 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 422void 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 453void 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 467void 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 480bool 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) 493int 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