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