8b8a7f7c5832621fe696fcc969ae740196cca3d9
[platform/framework/web/crosswalk.git] / src / content / child / npapi / plugin_stream.cc
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