// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO : Support NP_ASFILEONLY mode // TODO : Support NP_SEEK mode // TODO : Support SEEKABLE=true in NewStream #include "content/child/npapi/plugin_stream.h" #include <algorithm> #include "base/bind.h" #include "base/message_loop/message_loop.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "content/child/npapi/plugin_instance.h" #include "net/base/mime_util.h" #include "url/gurl.h" namespace content { PluginStream::PluginStream( PluginInstance* instance, const char* url, bool need_notify, void* notify_data) : instance_(instance), notify_needed_(need_notify), notify_data_(notify_data), close_on_write_data_(false), requested_plugin_mode_(NP_NORMAL), opened_(false), data_offset_(0), seekable_stream_(false) { memset(&stream_, 0, sizeof(stream_)); stream_.url = base::strdup(url); ResetTempFileHandle(); ResetTempFileName(); } PluginStream::~PluginStream() { // always close our temporary files. CloseTempFile(); free(const_cast<char*>(stream_.url)); } bool PluginStream::Open(const std::string& mime_type, const std::string& headers, uint32 length, uint32 last_modified, bool request_is_seekable) { headers_ = headers; NPP id = instance_->npp(); stream_.end = length; stream_.lastmodified = last_modified; stream_.pdata = 0; stream_.ndata = id->ndata; stream_.notifyData = notify_data_; if (!headers_.empty()) stream_.headers = headers_.c_str(); bool seekable_stream = false; if (request_is_seekable) { std::string headers_lc = base::StringToLowerASCII(headers); if (headers_lc.find("accept-ranges: bytes") != std::string::npos) { seekable_stream = true; } } const char* char_mime_type = "application/x-unknown-content-type"; std::string temp_mime_type; if (!mime_type.empty()) { char_mime_type = mime_type.c_str(); } else { GURL gurl(stream_.url); base::FilePath path = base::FilePath::FromUTF8Unsafe(gurl.path()); if (net::GetMimeTypeFromFile(path, &temp_mime_type)) char_mime_type = temp_mime_type.c_str(); } // Silverlight expects a valid mime type DCHECK_NE(0U, strlen(char_mime_type)); NPError err = instance_->NPP_NewStream((NPMIMEType)char_mime_type, &stream_, seekable_stream, &requested_plugin_mode_); if (err != NPERR_NO_ERROR) { Notify(err); return false; } opened_ = true; if (requested_plugin_mode_ == NP_SEEK) { seekable_stream_ = true; } // If the plugin has requested certain modes, then we need a copy // of this file on disk. Open it and save it as we go. if (RequestedPluginModeIsAsFile()) { if (OpenTempFile() == false) { return false; } } mime_type_ = char_mime_type; return true; } int PluginStream::Write(const char* buffer, const int length, int data_offset) { // There may be two streams to write to - the plugin and the file. // It is unclear what to do if we cannot write to both. The rules of // this function are that the plugin must consume at least as many // bytes as returned by the WriteReady call. So, we will attempt to // write that many to both streams. If we can't write that many bytes // to each stream, we'll return failure. DCHECK(opened_); if (WriteToFile(buffer, length) && WriteToPlugin(buffer, length, data_offset)) { return length; } return -1; } bool PluginStream::WriteToFile(const char* buf, size_t length) { // For ASFILEONLY, ASFILE, and SEEK modes, we need to write // to the disk if (TempFileIsValid() && RequestedPluginModeIsAsFile()) { size_t totalBytesWritten = 0, bytes; do { bytes = WriteBytes(buf, length); totalBytesWritten += bytes; } while (bytes > 0U && totalBytesWritten < length); if (totalBytesWritten != length) { return false; } } return true; } bool PluginStream::WriteToPlugin(const char* buf, const int length, const int data_offset) { // For NORMAL and ASFILE modes, we send the data to the plugin now if (requested_plugin_mode_ != NP_NORMAL && requested_plugin_mode_ != NP_ASFILE && requested_plugin_mode_ != NP_SEEK) { return true; } int written = TryWriteToPlugin(buf, length, data_offset); if (written == -1) return false; if (written < length) { // Buffer the remaining data. size_t remaining = length - written; size_t previous_size = delivery_data_.size(); delivery_data_.resize(previous_size + remaining); data_offset_ = data_offset; memcpy(&delivery_data_[previous_size], buf + written, remaining); base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&PluginStream::OnDelayDelivery, this)); } return true; } void PluginStream::OnDelayDelivery() { // It is possible that the plugin stream may have closed before the task // was hit. if (!opened_) return; int size = static_cast<int>(delivery_data_.size()); int written = TryWriteToPlugin(&delivery_data_.front(), size, data_offset_); if (written > 0) { // Remove the data that we already wrote. delivery_data_.erase(delivery_data_.begin(), delivery_data_.begin() + written); } } int PluginStream::TryWriteToPlugin(const char* buf, const int length, const int data_offset) { int byte_offset = 0; if (data_offset > 0) data_offset_ = data_offset; while (byte_offset < length) { int bytes_remaining = length - byte_offset; int bytes_to_write = instance_->NPP_WriteReady(&stream_); if (bytes_to_write > bytes_remaining) bytes_to_write = bytes_remaining; if (bytes_to_write == 0) return byte_offset; int bytes_consumed = instance_->NPP_Write( &stream_, data_offset_, bytes_to_write, const_cast<char*>(buf + byte_offset)); if (bytes_consumed < 0) { // The plugin failed, which means that we need to close the stream. Close(NPRES_NETWORK_ERR); return -1; } if (bytes_consumed == 0) { // The plugin couldn't take all of the data now. return byte_offset; } // The plugin might report more that we gave it. bytes_consumed = std::min(bytes_consumed, bytes_to_write); data_offset_ += bytes_consumed; byte_offset += bytes_consumed; } if (close_on_write_data_) Close(NPRES_DONE); return length; } bool PluginStream::Close(NPReason reason) { if (opened_ == true) { opened_ = false; if (delivery_data_.size()) { if (reason == NPRES_DONE) { // There is more data to be streamed, don't destroy the stream now. close_on_write_data_ = true; return true; } else { // Stop any pending data from being streamed delivery_data_.resize(0); } } // If we have a temp file, be sure to close it. // Also, allow the plugin to access it now. if (TempFileIsValid()) { CloseTempFile(); if (reason == NPRES_DONE) WriteAsFile(); } if (stream_.ndata != NULL) { // Stream hasn't been closed yet. NPError err = instance_->NPP_DestroyStream(&stream_, reason); DCHECK(err == NPERR_NO_ERROR); } } Notify(reason); return true; } WebPluginResourceClient* PluginStream::AsResourceClient() { return NULL; } void PluginStream::Notify(NPReason reason) { if (notify_needed_) { instance_->NPP_URLNotify(stream_.url, reason, notify_data_); notify_needed_ = false; } } bool PluginStream::RequestedPluginModeIsAsFile() const { return (requested_plugin_mode_ == NP_ASFILE || requested_plugin_mode_ == NP_ASFILEONLY); } } // namespace content