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