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