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
21namespace content {
22
23PluginStream::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
42PluginStream::~PluginStream() {
43  // always close our temporary files.
44  CloseTempFile();
45  free(const_cast<char*>(stream_.url));
46}
47
48bool 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
110int 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
128bool 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
146bool 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
173void 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
188int 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
230bool 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
264WebPluginResourceClient* PluginStream::AsResourceClient() {
265  return NULL;
266}
267
268void PluginStream::Notify(NPReason reason) {
269  if (notify_needed_) {
270    instance_->NPP_URLNotify(stream_.url, reason, notify_data_);
271    notify_needed_ = false;
272  }
273}
274
275bool PluginStream::RequestedPluginModeIsAsFile() const {
276  return (requested_plugin_mode_ == NP_ASFILE ||
277          requested_plugin_mode_ == NP_ASFILEONLY);
278}
279
280}  // namespace content
281