1/* 2 * Copyright (C) 2010 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "config.h" 27#include "NetscapePluginStream.h" 28 29#if ENABLE(NETSCAPE_PLUGIN_API) 30 31#include "NetscapePlugin.h" 32#include <utility> 33#include <wtf/Vector.h> 34 35using namespace WebCore; 36 37namespace WebKit { 38 39NetscapePluginStream::NetscapePluginStream(PassRefPtr<NetscapePlugin> plugin, uint64_t streamID, const String& requestURLString, bool sendNotification, void* notificationData) 40 : m_plugin(plugin) 41 , m_streamID(streamID) 42 , m_requestURLString(requestURLString) 43 , m_sendNotification(sendNotification) 44 , m_notificationData(notificationData) 45 , m_npStream() 46 , m_transferMode(NP_NORMAL) 47 , m_offset(0) 48 , m_fileHandle(invalidPlatformFileHandle) 49 , m_isStarted(false) 50#if !ASSERT_DISABLED 51 , m_urlNotifyHasBeenCalled(false) 52#endif 53 , m_deliveryDataTimer(RunLoop::main(), this, &NetscapePluginStream::deliverDataToPlugin) 54 , m_stopStreamWhenDoneDelivering(false) 55{ 56} 57 58NetscapePluginStream::~NetscapePluginStream() 59{ 60 ASSERT(!m_isStarted); 61 ASSERT(!m_sendNotification || m_urlNotifyHasBeenCalled); 62 ASSERT(m_fileHandle == invalidPlatformFileHandle); 63} 64 65void NetscapePluginStream::didReceiveResponse(const URL& responseURL, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers) 66{ 67 // Starting the stream could cause the plug-in stream to go away so we keep a reference to it here. 68 Ref<NetscapePluginStream> protect(*this); 69 70 start(responseURL, streamLength, lastModifiedTime, mimeType, headers); 71} 72 73void NetscapePluginStream::didReceiveData(const char* bytes, int length) 74{ 75 // Delivering the data could cause the plug-in stream to go away so we keep a reference to it here. 76 Ref<NetscapePluginStream> protect(*this); 77 78 deliverData(bytes, length); 79} 80 81void NetscapePluginStream::didFinishLoading() 82{ 83 // Stopping the stream could cause the plug-in stream to go away so we keep a reference to it here. 84 Ref<NetscapePluginStream> protect(*this); 85 86 stop(NPRES_DONE); 87} 88 89void NetscapePluginStream::didFail(bool wasCancelled) 90{ 91 // Stopping the stream could cause the plug-in stream to go away so we keep a reference to it here. 92 Ref<NetscapePluginStream> protect(*this); 93 94 stop(wasCancelled ? NPRES_USER_BREAK : NPRES_NETWORK_ERR); 95} 96 97void NetscapePluginStream::sendJavaScriptStream(const String& result) 98{ 99 // starting the stream or delivering the data to it might cause the plug-in stream to go away, so we keep 100 // a reference to it here. 101 Ref<NetscapePluginStream> protect(*this); 102 103 CString resultCString = result.utf8(); 104 if (resultCString.isNull()) { 105 // There was an error evaluating the JavaScript, call NPP_URLNotify if needed and then destroy the stream. 106 notifyAndDestroyStream(NPRES_NETWORK_ERR); 107 return; 108 } 109 110 if (!start(m_requestURLString, resultCString.length(), 0, "text/plain", "")) 111 return; 112 113 deliverData(resultCString.data(), resultCString.length()); 114 stop(NPRES_DONE); 115} 116 117NPError NetscapePluginStream::destroy(NPReason reason) 118{ 119 // It doesn't make sense to call NPN_DestroyStream on a stream that hasn't been started yet. 120 if (!m_isStarted) 121 return NPERR_GENERIC_ERROR; 122 123 // It isn't really valid for a plug-in to call NPN_DestroyStream with NPRES_DONE. 124 // (At least not for browser initiated streams, and we don't support plug-in initiated streams). 125 if (reason == NPRES_DONE) 126 return NPERR_INVALID_PARAM; 127 128 cancel(); 129 stop(reason); 130 return NPERR_NO_ERROR; 131} 132 133static bool isSupportedTransferMode(uint16_t transferMode) 134{ 135 switch (transferMode) { 136 case NP_ASFILEONLY: 137 case NP_ASFILE: 138 case NP_NORMAL: 139 return true; 140 // FIXME: We don't support seekable streams. 141 case NP_SEEK: 142 return false; 143 } 144 145 ASSERT_NOT_REACHED(); 146 return false; 147} 148 149bool NetscapePluginStream::start(const String& responseURLString, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers) 150{ 151 m_responseURL = responseURLString.utf8(); 152 m_mimeType = mimeType.utf8(); 153 m_headers = headers.utf8(); 154 155 m_npStream.ndata = this; 156 m_npStream.url = m_responseURL.data(); 157 m_npStream.end = streamLength; 158 m_npStream.lastmodified = lastModifiedTime; 159 m_npStream.notifyData = m_notificationData; 160 m_npStream.headers = m_headers.length() == 0 ? 0 : m_headers.data(); 161 162 NPError error = m_plugin->NPP_NewStream(const_cast<char*>(m_mimeType.data()), &m_npStream, false, &m_transferMode); 163 if (error != NPERR_NO_ERROR) { 164 // We failed to start the stream, cancel the load and destroy it. 165 cancel(); 166 notifyAndDestroyStream(NPRES_NETWORK_ERR); 167 return false; 168 } 169 170 // We successfully started the stream. 171 m_isStarted = true; 172 173 if (!isSupportedTransferMode(m_transferMode)) { 174 // Cancel the load and stop the stream. 175 cancel(); 176 stop(NPRES_NETWORK_ERR); 177 return false; 178 } 179 180 return true; 181} 182 183void NetscapePluginStream::deliverData(const char* bytes, int length) 184{ 185 ASSERT(m_isStarted); 186 187 if (m_transferMode != NP_ASFILEONLY) { 188 if (!m_deliveryData) 189 m_deliveryData = std::make_unique<Vector<uint8_t>>(); 190 191 m_deliveryData->reserveCapacity(m_deliveryData->size() + length); 192 m_deliveryData->append(bytes, length); 193 194 deliverDataToPlugin(); 195 } 196 197 if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY) 198 deliverDataToFile(bytes, length); 199} 200 201void NetscapePluginStream::deliverDataToPlugin() 202{ 203 ASSERT(m_isStarted); 204 205 int32_t numBytesToDeliver = m_deliveryData->size(); 206 int32_t numBytesDelivered = 0; 207 208 while (numBytesDelivered < numBytesToDeliver) { 209 int32_t numBytesPluginCanHandle = m_plugin->NPP_WriteReady(&m_npStream); 210 211 // NPP_WriteReady could call NPN_DestroyStream and destroy the stream. 212 if (!m_isStarted) 213 return; 214 215 if (numBytesPluginCanHandle <= 0) { 216 // The plug-in can't handle more data, we'll send the rest later 217 m_deliveryDataTimer.startOneShot(0); 218 break; 219 } 220 221 // Figure out how much data to send to the plug-in. 222 int32_t dataLength = std::min(numBytesPluginCanHandle, numBytesToDeliver - numBytesDelivered); 223 uint8_t* data = m_deliveryData->data() + numBytesDelivered; 224 225 int32_t numBytesWritten = m_plugin->NPP_Write(&m_npStream, m_offset, dataLength, data); 226 if (numBytesWritten < 0) { 227 cancel(); 228 stop(NPRES_NETWORK_ERR); 229 return; 230 } 231 232 // NPP_Write could call NPN_DestroyStream and destroy the stream. 233 if (!m_isStarted) 234 return; 235 236 numBytesWritten = std::min(numBytesWritten, dataLength); 237 m_offset += numBytesWritten; 238 numBytesDelivered += numBytesWritten; 239 } 240 241 // We didn't write anything. 242 if (!numBytesDelivered) 243 return; 244 245 if (numBytesDelivered < numBytesToDeliver) { 246 // Remove the bytes that we actually delivered. 247 m_deliveryData->remove(0, numBytesDelivered); 248 } else { 249 m_deliveryData->clear(); 250 251 if (m_stopStreamWhenDoneDelivering) 252 stop(NPRES_DONE); 253 } 254} 255 256void NetscapePluginStream::deliverDataToFile(const char* bytes, int length) 257{ 258 if (m_fileHandle == invalidPlatformFileHandle && m_filePath.isNull()) { 259 // Create a temporary file. 260 m_filePath = openTemporaryFile("WebKitPluginStream", m_fileHandle); 261 262 // We failed to open the file, stop the stream. 263 if (m_fileHandle == invalidPlatformFileHandle) { 264 stop(NPRES_NETWORK_ERR); 265 return; 266 } 267 } 268 269 if (!length) 270 return; 271 272 int byteCount = writeToFile(m_fileHandle, bytes, length); 273 if (byteCount != length) { 274 // This happens only rarely, when we are out of disk space or have a disk I/O error. 275 closeFile(m_fileHandle); 276 277 stop(NPRES_NETWORK_ERR); 278 } 279} 280 281void NetscapePluginStream::stop(NPReason reason) 282{ 283 // The stream was stopped before it got a chance to start. This can happen if a stream is cancelled by 284 // WebKit before it received a response. 285 if (!m_isStarted) { 286 ASSERT(reason != NPRES_DONE); 287 notifyAndDestroyStream(reason); 288 return; 289 } 290 291 if (reason == NPRES_DONE && m_deliveryData && !m_deliveryData->isEmpty()) { 292 // There is still data left that the plug-in hasn't been able to consume yet. 293 ASSERT(m_deliveryDataTimer.isActive()); 294 295 // Set m_stopStreamWhenDoneDelivering to true so that the next time the delivery timer fires 296 // and calls deliverDataToPlugin the stream will be closed if all the remaining data was 297 // successfully delivered. 298 m_stopStreamWhenDoneDelivering = true; 299 return; 300 } 301 302 m_deliveryData = nullptr; 303 m_deliveryDataTimer.stop(); 304 305 if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY) { 306 if (reason == NPRES_DONE) { 307 // Ensure that the file is created. 308 deliverDataToFile(0, 0); 309 if (m_fileHandle != invalidPlatformFileHandle) 310 closeFile(m_fileHandle); 311 312 ASSERT(!m_filePath.isNull()); 313 314 m_plugin->NPP_StreamAsFile(&m_npStream, m_filePath.utf8().data()); 315 } else { 316 // Just close the file. 317 if (m_fileHandle != invalidPlatformFileHandle) 318 closeFile(m_fileHandle); 319 } 320 321 // Delete the file after calling NPP_StreamAsFile(), instead of in the destructor. It should be OK 322 // to delete the file here -- NPP_StreamAsFile() is always called immediately before NPP_DestroyStream() 323 // (the stream destruction function), so there can be no expectation that a plugin will read the stream 324 // file asynchronously after NPP_StreamAsFile() is called. 325 deleteFile(m_filePath); 326 m_filePath = String(); 327 328 // NPP_StreamAsFile could call NPN_DestroyStream and destroy the stream. 329 if (!m_isStarted) 330 return; 331 } 332 333 // Set m_isStarted to false before calling NPP_DestroyStream in case NPP_DestroyStream calls NPN_DestroyStream. 334 m_isStarted = false; 335 336 m_plugin->NPP_DestroyStream(&m_npStream, reason); 337 338 notifyAndDestroyStream(reason); 339} 340 341void NetscapePluginStream::cancel() 342{ 343 m_plugin->cancelStreamLoad(this); 344} 345 346void NetscapePluginStream::notifyAndDestroyStream(NPReason reason) 347{ 348 ASSERT(!m_isStarted); 349 ASSERT(!m_deliveryDataTimer.isActive()); 350 ASSERT(!m_urlNotifyHasBeenCalled); 351 352 if (m_sendNotification) { 353 m_plugin->NPP_URLNotify(m_requestURLString.utf8().data(), reason, m_notificationData); 354 355#if !ASSERT_DISABLED 356 m_urlNotifyHasBeenCalled = true; 357#endif 358 } 359 360 m_plugin->removePluginStream(this); 361} 362 363} // namespace WebKit 364 365#endif // ENABLE(NETSCAPE_PLUGIN_API) 366