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 #include "NetscapePlugin.h" 30 #include <utility> 31 32 using namespace WebCore; 33 using namespace std; 34 35 namespace WebKit { 36 37 NetscapePluginStream::NetscapePluginStream(PassRefPtr<NetscapePlugin> plugin, uint64_t streamID, bool sendNotification, void* notificationData) 38 : m_plugin(plugin) 39 , m_streamID(streamID) 40 , m_sendNotification(sendNotification) 41 , m_notificationData(notificationData) 42 , m_npStream() 43 , m_transferMode(NP_NORMAL) 44 , m_offset(0) 45 , m_fileHandle(invalidPlatformFileHandle) 46 , m_isStarted(false) 47 #if !ASSERT_DISABLED 48 , m_urlNotifyHasBeenCalled(false) 49 #endif 50 , m_deliveryDataTimer(RunLoop::main(), this, &NetscapePluginStream::deliverDataToPlugin) 51 , m_stopStreamWhenDoneDelivering(false) 52 { 53 } 54 55 NetscapePluginStream::~NetscapePluginStream() 56 { 57 ASSERT(!m_isStarted); 58 ASSERT(!m_sendNotification || m_urlNotifyHasBeenCalled); 59 ASSERT(m_fileHandle == invalidPlatformFileHandle); 60 } 61 62 void NetscapePluginStream::didReceiveResponse(const KURL& responseURL, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers) 63 { 64 // Starting the stream could cause the plug-in stream to go away so we keep a reference to it here. 65 RefPtr<NetscapePluginStream> protect(this); 66 67 start(responseURL, streamLength, lastModifiedTime, mimeType, headers); 68 } 69 70 void NetscapePluginStream::didReceiveData(const char* bytes, int length) 71 { 72 // Delivering the data could cause the plug-in stream to go away so we keep a reference to it here. 73 RefPtr<NetscapePluginStream> protect(this); 74 75 deliverData(bytes, length); 76 } 77 78 void NetscapePluginStream::didFinishLoading() 79 { 80 // Stopping the stream could cause the plug-in stream to go away so we keep a reference to it here. 81 RefPtr<NetscapePluginStream> protect(this); 82 83 stop(NPRES_DONE); 84 } 85 86 void NetscapePluginStream::didFail(bool wasCancelled) 87 { 88 // Stopping the stream could cause the plug-in stream to go away so we keep a reference to it here. 89 RefPtr<NetscapePluginStream> protect(this); 90 91 stop(wasCancelled ? NPRES_USER_BREAK : NPRES_NETWORK_ERR); 92 } 93 94 void NetscapePluginStream::sendJavaScriptStream(const String& requestURLString, const String& result) 95 { 96 // starting the stream or delivering the data to it might cause the plug-in stream to go away, so we keep 97 // a reference to it here. 98 RefPtr<NetscapePluginStream> protect(this); 99 100 CString resultCString = requestURLString.utf8(); 101 if (resultCString.isNull()) { 102 // There was an error evaluating the JavaScript, call NPP_URLNotify if needed and then destroy the stream. 103 notifyAndDestroyStream(NPRES_NETWORK_ERR); 104 return; 105 } 106 107 if (!start(requestURLString, resultCString.length(), 0, "text/plain", "")) 108 return; 109 110 deliverData(resultCString.data(), resultCString.length()); 111 stop(NPRES_DONE); 112 } 113 114 NPError NetscapePluginStream::destroy(NPReason reason) 115 { 116 // It doesn't make sense to call NPN_DestroyStream on a stream that hasn't been started yet. 117 if (!m_isStarted) 118 return NPERR_GENERIC_ERROR; 119 120 // It isn't really valid for a plug-in to call NPN_DestroyStream with NPRES_DONE. 121 // (At least not for browser initiated streams, and we don't support plug-in initiated streams). 122 if (reason == NPRES_DONE) 123 return NPERR_INVALID_PARAM; 124 125 cancel(); 126 stop(reason); 127 return NPERR_NO_ERROR; 128 } 129 130 static bool isSupportedTransferMode(uint16_t transferMode) 131 { 132 switch (transferMode) { 133 case NP_ASFILEONLY: 134 case NP_ASFILE: 135 case NP_NORMAL: 136 return true; 137 // FIXME: We don't support seekable streams. 138 case NP_SEEK: 139 return false; 140 } 141 142 ASSERT_NOT_REACHED(); 143 return false; 144 } 145 146 bool NetscapePluginStream::start(const String& responseURLString, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers) 147 { 148 m_responseURL = responseURLString.utf8(); 149 m_mimeType = mimeType.utf8(); 150 m_headers = headers.utf8(); 151 152 m_npStream.ndata = this; 153 m_npStream.url = m_responseURL.data(); 154 m_npStream.end = streamLength; 155 m_npStream.lastmodified = lastModifiedTime; 156 m_npStream.notifyData = m_notificationData; 157 m_npStream.headers = m_headers.length() == 0 ? 0 : m_headers.data(); 158 159 NPError error = m_plugin->NPP_NewStream(const_cast<char*>(m_mimeType.data()), &m_npStream, false, &m_transferMode); 160 if (error != NPERR_NO_ERROR) { 161 // We failed to start the stream, cancel the load and destroy it. 162 cancel(); 163 notifyAndDestroyStream(NPRES_NETWORK_ERR); 164 return false; 165 } 166 167 // We successfully started the stream. 168 m_isStarted = true; 169 170 if (!isSupportedTransferMode(m_transferMode)) { 171 // Cancel the load and stop the stream. 172 cancel(); 173 stop(NPRES_NETWORK_ERR); 174 return false; 175 } 176 177 return true; 178 } 179 180 void NetscapePluginStream::deliverData(const char* bytes, int length) 181 { 182 ASSERT(m_isStarted); 183 184 if (m_transferMode != NP_ASFILEONLY) { 185 if (!m_deliveryData) 186 m_deliveryData.set(new Vector<uint8_t>); 187 188 m_deliveryData->reserveCapacity(m_deliveryData->size() + length); 189 m_deliveryData->append(bytes, length); 190 191 deliverDataToPlugin(); 192 } 193 194 if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY) 195 deliverDataToFile(bytes, length); 196 } 197 198 void NetscapePluginStream::deliverDataToPlugin() 199 { 200 ASSERT(m_isStarted); 201 202 int32_t numBytesToDeliver = m_deliveryData->size(); 203 int32_t numBytesDelivered = 0; 204 205 while (numBytesDelivered < numBytesToDeliver) { 206 int32_t numBytesPluginCanHandle = m_plugin->NPP_WriteReady(&m_npStream); 207 208 // NPP_WriteReady could call NPN_DestroyStream and destroy the stream. 209 if (!m_isStarted) 210 return; 211 212 if (numBytesPluginCanHandle <= 0) { 213 // The plug-in can't handle more data, we'll send the rest later 214 m_deliveryDataTimer.startOneShot(0); 215 break; 216 } 217 218 // Figure out how much data to send to the plug-in. 219 int32_t dataLength = min(numBytesPluginCanHandle, numBytesToDeliver - numBytesDelivered); 220 uint8_t* data = m_deliveryData->data() + numBytesDelivered; 221 222 int32_t numBytesWritten = m_plugin->NPP_Write(&m_npStream, m_offset, dataLength, data); 223 if (numBytesWritten < 0) { 224 cancel(); 225 stop(NPRES_NETWORK_ERR); 226 return; 227 } 228 229 // NPP_Write could call NPN_DestroyStream and destroy the stream. 230 if (!m_isStarted) 231 return; 232 233 numBytesWritten = min(numBytesWritten, dataLength); 234 m_offset += numBytesWritten; 235 numBytesDelivered += numBytesWritten; 236 } 237 238 // We didn't write anything. 239 if (!numBytesDelivered) 240 return; 241 242 if (numBytesDelivered < numBytesToDeliver) { 243 // Remove the bytes that we actually delivered. 244 m_deliveryData->remove(0, numBytesDelivered); 245 } else { 246 m_deliveryData->clear(); 247 248 if (m_stopStreamWhenDoneDelivering) 249 stop(NPRES_DONE); 250 } 251 } 252 253 void NetscapePluginStream::deliverDataToFile(const char* bytes, int length) 254 { 255 if (m_fileHandle == invalidPlatformFileHandle && m_filePath.isNull()) { 256 // Create a temporary file. 257 m_filePath = openTemporaryFile("WebKitPluginStream", m_fileHandle); 258 259 // We failed to open the file, stop the stream. 260 if (m_fileHandle == invalidPlatformFileHandle) { 261 stop(NPRES_NETWORK_ERR); 262 return; 263 } 264 } 265 266 if (!length) 267 return; 268 269 int byteCount = writeToFile(m_fileHandle, bytes, length); 270 if (byteCount != length) { 271 // This happens only rarely, when we are out of disk space or have a disk I/O error. 272 closeFile(m_fileHandle); 273 274 stop(NPRES_NETWORK_ERR); 275 } 276 } 277 278 void NetscapePluginStream::stop(NPReason reason) 279 { 280 // The stream was stopped before it got a chance to start. This can happen if a stream is cancelled by 281 // WebKit before it received a response. 282 if (!m_isStarted) 283 return; 284 285 if (reason == NPRES_DONE && m_deliveryData && !m_deliveryData->isEmpty()) { 286 // There is still data left that the plug-in hasn't been able to consume yet. 287 ASSERT(m_deliveryDataTimer.isActive()); 288 289 // Set m_stopStreamWhenDoneDelivering to true so that the next time the delivery timer fires 290 // and calls deliverDataToPlugin the stream will be closed if all the remaining data was 291 // successfully delivered. 292 m_stopStreamWhenDoneDelivering = true; 293 return; 294 } 295 296 m_deliveryData = 0; 297 m_deliveryDataTimer.stop(); 298 299 if (m_transferMode == NP_ASFILE || m_transferMode == NP_ASFILEONLY) { 300 if (reason == NPRES_DONE) { 301 // Ensure that the file is created. 302 deliverDataToFile(0, 0); 303 if (m_fileHandle != invalidPlatformFileHandle) 304 closeFile(m_fileHandle); 305 306 ASSERT(!m_filePath.isNull()); 307 308 m_plugin->NPP_StreamAsFile(&m_npStream, m_filePath.utf8().data()); 309 } else { 310 // Just close the file. 311 if (m_fileHandle != invalidPlatformFileHandle) 312 closeFile(m_fileHandle); 313 } 314 315 // Delete the file after calling NPP_StreamAsFile(), instead of in the destructor. It should be OK 316 // to delete the file here -- NPP_StreamAsFile() is always called immediately before NPP_DestroyStream() 317 // (the stream destruction function), so there can be no expectation that a plugin will read the stream 318 // file asynchronously after NPP_StreamAsFile() is called. 319 deleteFile(m_filePath); 320 m_filePath = String(); 321 322 // NPP_StreamAsFile could call NPN_DestroyStream and destroy the stream. 323 if (!m_isStarted) 324 return; 325 } 326 327 // Set m_isStarted to false before calling NPP_DestroyStream in case NPP_DestroyStream calls NPN_DestroyStream. 328 m_isStarted = false; 329 330 m_plugin->NPP_DestroyStream(&m_npStream, reason); 331 332 notifyAndDestroyStream(reason); 333 } 334 335 void NetscapePluginStream::cancel() 336 { 337 m_plugin->cancelStreamLoad(this); 338 } 339 340 void NetscapePluginStream::notifyAndDestroyStream(NPReason reason) 341 { 342 ASSERT(!m_isStarted); 343 ASSERT(!m_deliveryDataTimer.isActive()); 344 ASSERT(!m_urlNotifyHasBeenCalled); 345 346 if (m_sendNotification) { 347 m_plugin->NPP_URLNotify(m_responseURL.data(), reason, m_notificationData); 348 349 #if !ASSERT_DISABLED 350 m_urlNotifyHasBeenCalled = true; 351 #endif 352 } 353 354 m_plugin->removePluginStream(this); 355 } 356 357 } // namespace WebKit 358