1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 // TODO : Support NP_ASFILEONLY mode 6 // TODO : Support NP_SEEK mode 7 // TODO : Support SEEKABLE=true in NewStream 8 9 #include "content/child/npapi/plugin_stream.h" 10 11 #include <algorithm> 12 13 #include "base/bind.h" 14 #include "base/message_loop/message_loop.h" 15 #include "base/strings/string_util.h" 16 #include "base/strings/utf_string_conversions.h" 17 #include "content/child/npapi/plugin_instance.h" 18 #include "net/base/mime_util.h" 19 #include "url/gurl.h" 20 21 namespace content { 22 23 PluginStream::PluginStream( 24 PluginInstance* instance, 25 const char* url, 26 bool need_notify, 27 void* notify_data) 28 : instance_(instance), 29 notify_needed_(need_notify), 30 notify_data_(notify_data), 31 close_on_write_data_(false), 32 requested_plugin_mode_(NP_NORMAL), 33 opened_(false), 34 data_offset_(0), 35 seekable_stream_(false) { 36 memset(&stream_, 0, sizeof(stream_)); 37 stream_.url = base::strdup(url); 38 ResetTempFileHandle(); 39 ResetTempFileName(); 40 } 41 42 PluginStream::~PluginStream() { 43 // always close our temporary files. 44 CloseTempFile(); 45 free(const_cast<char*>(stream_.url)); 46 } 47 48 bool PluginStream::Open(const std::string& mime_type, 49 const std::string& headers, 50 uint32 length, 51 uint32 last_modified, 52 bool request_is_seekable) { 53 headers_ = headers; 54 NPP id = instance_->npp(); 55 stream_.end = length; 56 stream_.lastmodified = last_modified; 57 stream_.pdata = 0; 58 stream_.ndata = id->ndata; 59 stream_.notifyData = notify_data_; 60 if (!headers_.empty()) 61 stream_.headers = headers_.c_str(); 62 63 bool seekable_stream = false; 64 if (request_is_seekable) { 65 std::string headers_lc = StringToLowerASCII(headers); 66 if (headers_lc.find("accept-ranges: bytes") != std::string::npos) { 67 seekable_stream = true; 68 } 69 } 70 71 const char* char_mime_type = "application/x-unknown-content-type"; 72 std::string temp_mime_type; 73 if (!mime_type.empty()) { 74 char_mime_type = mime_type.c_str(); 75 } else { 76 GURL gurl(stream_.url); 77 78 base::FilePath path = base::FilePath::FromUTF8Unsafe(gurl.path()); 79 if (net::GetMimeTypeFromFile(path, &temp_mime_type)) 80 char_mime_type = temp_mime_type.c_str(); 81 } 82 83 // Silverlight expects a valid mime type 84 DCHECK_NE(0U, strlen(char_mime_type)); 85 NPError err = instance_->NPP_NewStream((NPMIMEType)char_mime_type, 86 &stream_, seekable_stream, 87 &requested_plugin_mode_); 88 if (err != NPERR_NO_ERROR) { 89 Notify(err); 90 return false; 91 } 92 93 opened_ = true; 94 95 if (requested_plugin_mode_ == NP_SEEK) { 96 seekable_stream_ = true; 97 } 98 // If the plugin has requested certain modes, then we need a copy 99 // of this file on disk. Open it and save it as we go. 100 if (RequestedPluginModeIsAsFile()) { 101 if (OpenTempFile() == false) { 102 return false; 103 } 104 } 105 106 mime_type_ = char_mime_type; 107 return true; 108 } 109 110 int PluginStream::Write(const char* buffer, const int length, 111 int data_offset) { 112 // There may be two streams to write to - the plugin and the file. 113 // It is unclear what to do if we cannot write to both. The rules of 114 // this function are that the plugin must consume at least as many 115 // bytes as returned by the WriteReady call. So, we will attempt to 116 // write that many to both streams. If we can't write that many bytes 117 // to each stream, we'll return failure. 118 119 DCHECK(opened_); 120 if (WriteToFile(buffer, length) && 121 WriteToPlugin(buffer, length, data_offset)) { 122 return length; 123 } 124 125 return -1; 126 } 127 128 bool PluginStream::WriteToFile(const char* buf, size_t length) { 129 // For ASFILEONLY, ASFILE, and SEEK modes, we need to write 130 // to the disk 131 if (TempFileIsValid() && RequestedPluginModeIsAsFile()) { 132 size_t totalBytesWritten = 0, bytes; 133 do { 134 bytes = WriteBytes(buf, length); 135 totalBytesWritten += bytes; 136 } while (bytes > 0U && totalBytesWritten < length); 137 138 if (totalBytesWritten != length) { 139 return false; 140 } 141 } 142 143 return true; 144 } 145 146 bool PluginStream::WriteToPlugin(const char* buf, const int length, 147 const int data_offset) { 148 // For NORMAL and ASFILE modes, we send the data to the plugin now 149 if (requested_plugin_mode_ != NP_NORMAL && 150 requested_plugin_mode_ != NP_ASFILE && 151 requested_plugin_mode_ != NP_SEEK) { 152 return true; 153 } 154 155 int written = TryWriteToPlugin(buf, length, data_offset); 156 if (written == -1) 157 return false; 158 159 if (written < length) { 160 // Buffer the remaining data. 161 size_t remaining = length - written; 162 size_t previous_size = delivery_data_.size(); 163 delivery_data_.resize(previous_size + remaining); 164 data_offset_ = data_offset; 165 memcpy(&delivery_data_[previous_size], buf + written, remaining); 166 base::MessageLoop::current()->PostTask( 167 FROM_HERE, base::Bind(&PluginStream::OnDelayDelivery, this)); 168 } 169 170 return true; 171 } 172 173 void PluginStream::OnDelayDelivery() { 174 // It is possible that the plugin stream may have closed before the task 175 // was hit. 176 if (!opened_) 177 return; 178 179 int size = static_cast<int>(delivery_data_.size()); 180 int written = TryWriteToPlugin(&delivery_data_.front(), size, data_offset_); 181 if (written > 0) { 182 // Remove the data that we already wrote. 183 delivery_data_.erase(delivery_data_.begin(), 184 delivery_data_.begin() + written); 185 } 186 } 187 188 int PluginStream::TryWriteToPlugin(const char* buf, const int length, 189 const int data_offset) { 190 int byte_offset = 0; 191 192 if (data_offset > 0) 193 data_offset_ = data_offset; 194 195 while (byte_offset < length) { 196 int bytes_remaining = length - byte_offset; 197 int bytes_to_write = instance_->NPP_WriteReady(&stream_); 198 if (bytes_to_write > bytes_remaining) 199 bytes_to_write = bytes_remaining; 200 201 if (bytes_to_write == 0) 202 return byte_offset; 203 204 int bytes_consumed = instance_->NPP_Write( 205 &stream_, data_offset_, bytes_to_write, 206 const_cast<char*>(buf + byte_offset)); 207 if (bytes_consumed < 0) { 208 // The plugin failed, which means that we need to close the stream. 209 Close(NPRES_NETWORK_ERR); 210 return -1; 211 } 212 if (bytes_consumed == 0) { 213 // The plugin couldn't take all of the data now. 214 return byte_offset; 215 } 216 217 // The plugin might report more that we gave it. 218 bytes_consumed = std::min(bytes_consumed, bytes_to_write); 219 220 data_offset_ += bytes_consumed; 221 byte_offset += bytes_consumed; 222 } 223 224 if (close_on_write_data_) 225 Close(NPRES_DONE); 226 227 return length; 228 } 229 230 bool PluginStream::Close(NPReason reason) { 231 if (opened_ == true) { 232 opened_ = false; 233 234 if (delivery_data_.size()) { 235 if (reason == NPRES_DONE) { 236 // There is more data to be streamed, don't destroy the stream now. 237 close_on_write_data_ = true; 238 return true; 239 } else { 240 // Stop any pending data from being streamed 241 delivery_data_.resize(0); 242 } 243 } 244 245 // If we have a temp file, be sure to close it. 246 // Also, allow the plugin to access it now. 247 if (TempFileIsValid()) { 248 CloseTempFile(); 249 if (reason == NPRES_DONE) 250 WriteAsFile(); 251 } 252 253 if (stream_.ndata != NULL) { 254 // Stream hasn't been closed yet. 255 NPError err = instance_->NPP_DestroyStream(&stream_, reason); 256 DCHECK(err == NPERR_NO_ERROR); 257 } 258 } 259 260 Notify(reason); 261 return true; 262 } 263 264 WebPluginResourceClient* PluginStream::AsResourceClient() { 265 return NULL; 266 } 267 268 void PluginStream::Notify(NPReason reason) { 269 if (notify_needed_) { 270 instance_->NPP_URLNotify(stream_.url, reason, notify_data_); 271 notify_needed_ = false; 272 } 273 } 274 275 bool PluginStream::RequestedPluginModeIsAsFile() const { 276 return (requested_plugin_mode_ == NP_ASFILE || 277 requested_plugin_mode_ == NP_ASFILEONLY); 278 } 279 280 } // namespace content 281