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.
5 // TODO : Support NP_ASFILEONLY mode
6 // TODO : Support NP_SEEK mode
7 // TODO : Support SEEKABLE=true in NewStream
9 #include "content/child/npapi/plugin_stream.h"
13 #include "base/bind.h"
14 #include "base/location.h"
15 #include "base/single_thread_task_runner.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/thread_task_runner_handle.h"
19 #include "content/child/npapi/plugin_instance.h"
20 #include "net/base/mime_util.h"
25 PluginStream::PluginStream(
26 PluginInstance
* instance
,
30 : instance_(instance
),
31 notify_needed_(need_notify
),
32 notify_data_(notify_data
),
33 close_on_write_data_(false),
34 requested_plugin_mode_(NP_NORMAL
),
37 seekable_stream_(false) {
38 memset(&stream_
, 0, sizeof(stream_
));
39 stream_
.url
= base::strdup(url
);
40 ResetTempFileHandle();
44 PluginStream::~PluginStream() {
45 // always close our temporary files.
47 free(const_cast<char*>(stream_
.url
));
50 bool PluginStream::Open(const std::string
& mime_type
,
51 const std::string
& headers
,
54 bool request_is_seekable
) {
56 NPP id
= instance_
->npp();
58 stream_
.lastmodified
= last_modified
;
60 stream_
.ndata
= id
->ndata
;
61 stream_
.notifyData
= notify_data_
;
62 if (!headers_
.empty())
63 stream_
.headers
= headers_
.c_str();
65 bool seekable_stream
= false;
66 if (request_is_seekable
) {
67 std::string headers_lc
= base::StringToLowerASCII(headers
);
68 if (headers_lc
.find("accept-ranges: bytes") != std::string::npos
) {
69 seekable_stream
= true;
73 const char* char_mime_type
= "application/x-unknown-content-type";
74 std::string temp_mime_type
;
75 if (!mime_type
.empty()) {
76 char_mime_type
= mime_type
.c_str();
78 GURL
gurl(stream_
.url
);
80 base::FilePath path
= base::FilePath::FromUTF8Unsafe(gurl
.path());
81 if (net::GetMimeTypeFromFile(path
, &temp_mime_type
))
82 char_mime_type
= temp_mime_type
.c_str();
85 // Silverlight expects a valid mime type
86 DCHECK_NE(0U, strlen(char_mime_type
));
87 NPError err
= instance_
->NPP_NewStream((NPMIMEType
)char_mime_type
,
88 &stream_
, seekable_stream
,
89 &requested_plugin_mode_
);
90 if (err
!= NPERR_NO_ERROR
) {
97 if (requested_plugin_mode_
== NP_SEEK
) {
98 seekable_stream_
= true;
100 // If the plugin has requested certain modes, then we need a copy
101 // of this file on disk. Open it and save it as we go.
102 if (RequestedPluginModeIsAsFile()) {
103 if (OpenTempFile() == false) {
108 mime_type_
= char_mime_type
;
112 int PluginStream::Write(const char* buffer
, const int length
,
114 // There may be two streams to write to - the plugin and the file.
115 // It is unclear what to do if we cannot write to both. The rules of
116 // this function are that the plugin must consume at least as many
117 // bytes as returned by the WriteReady call. So, we will attempt to
118 // write that many to both streams. If we can't write that many bytes
119 // to each stream, we'll return failure.
122 if (WriteToFile(buffer
, length
) &&
123 WriteToPlugin(buffer
, length
, data_offset
)) {
130 bool PluginStream::WriteToFile(const char* buf
, size_t length
) {
131 // For ASFILEONLY, ASFILE, and SEEK modes, we need to write
133 if (TempFileIsValid() && RequestedPluginModeIsAsFile()) {
134 size_t totalBytesWritten
= 0, bytes
;
136 bytes
= WriteBytes(buf
, length
);
137 totalBytesWritten
+= bytes
;
138 } while (bytes
> 0U && totalBytesWritten
< length
);
140 if (totalBytesWritten
!= length
) {
148 bool PluginStream::WriteToPlugin(const char* buf
, const int length
,
149 const int data_offset
) {
150 // For NORMAL and ASFILE modes, we send the data to the plugin now
151 if (requested_plugin_mode_
!= NP_NORMAL
&&
152 requested_plugin_mode_
!= NP_ASFILE
&&
153 requested_plugin_mode_
!= NP_SEEK
) {
157 int written
= TryWriteToPlugin(buf
, length
, data_offset
);
161 if (written
< length
) {
162 // Buffer the remaining data.
163 size_t remaining
= length
- written
;
164 size_t previous_size
= delivery_data_
.size();
165 delivery_data_
.resize(previous_size
+ remaining
);
166 data_offset_
= data_offset
;
167 memcpy(&delivery_data_
[previous_size
], buf
+ written
, remaining
);
168 base::ThreadTaskRunnerHandle::Get()->PostTask(
169 FROM_HERE
, base::Bind(&PluginStream::OnDelayDelivery
, this));
175 void PluginStream::OnDelayDelivery() {
176 // It is possible that the plugin stream may have closed before the task
181 int size
= static_cast<int>(delivery_data_
.size());
182 int written
= TryWriteToPlugin(&delivery_data_
.front(), size
, data_offset_
);
184 // Remove the data that we already wrote.
185 delivery_data_
.erase(delivery_data_
.begin(),
186 delivery_data_
.begin() + written
);
190 int PluginStream::TryWriteToPlugin(const char* buf
, const int length
,
191 const int data_offset
) {
195 data_offset_
= data_offset
;
197 while (byte_offset
< length
) {
198 int bytes_remaining
= length
- byte_offset
;
199 int bytes_to_write
= instance_
->NPP_WriteReady(&stream_
);
200 if (bytes_to_write
> bytes_remaining
)
201 bytes_to_write
= bytes_remaining
;
203 if (bytes_to_write
== 0)
206 int bytes_consumed
= instance_
->NPP_Write(
207 &stream_
, data_offset_
, bytes_to_write
,
208 const_cast<char*>(buf
+ byte_offset
));
209 if (bytes_consumed
< 0) {
210 // The plugin failed, which means that we need to close the stream.
211 Close(NPRES_NETWORK_ERR
);
214 if (bytes_consumed
== 0) {
215 // The plugin couldn't take all of the data now.
219 // The plugin might report more that we gave it.
220 bytes_consumed
= std::min(bytes_consumed
, bytes_to_write
);
222 data_offset_
+= bytes_consumed
;
223 byte_offset
+= bytes_consumed
;
226 if (close_on_write_data_
)
232 bool PluginStream::Close(NPReason reason
) {
233 if (opened_
== true) {
236 if (delivery_data_
.size()) {
237 if (reason
== NPRES_DONE
) {
238 // There is more data to be streamed, don't destroy the stream now.
239 close_on_write_data_
= true;
242 // Stop any pending data from being streamed
243 delivery_data_
.resize(0);
247 // If we have a temp file, be sure to close it.
248 // Also, allow the plugin to access it now.
249 if (TempFileIsValid()) {
251 if (reason
== NPRES_DONE
)
255 if (stream_
.ndata
!= NULL
) {
256 // Stream hasn't been closed yet.
257 NPError err
= instance_
->NPP_DestroyStream(&stream_
, reason
);
258 DCHECK(err
== NPERR_NO_ERROR
);
266 WebPluginResourceClient
* PluginStream::AsResourceClient() {
270 void PluginStream::Notify(NPReason reason
) {
271 if (notify_needed_
) {
272 instance_
->NPP_URLNotify(stream_
.url
, reason
, notify_data_
);
273 notify_needed_
= false;
277 bool PluginStream::RequestedPluginModeIsAsFile() const {
278 return (requested_plugin_mode_
== NP_ASFILE
||
279 requested_plugin_mode_
== NP_ASFILEONLY
);
282 } // namespace content