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 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 = base::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