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/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"
23 PluginStream::PluginStream(
24 PluginInstance
* instance
,
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
),
35 seekable_stream_(false) {
36 memset(&stream_
, 0, sizeof(stream_
));
37 stream_
.url
= base::strdup(url
);
38 ResetTempFileHandle();
42 PluginStream::~PluginStream() {
43 // always close our temporary files.
45 free(const_cast<char*>(stream_
.url
));
48 bool PluginStream::Open(const std::string
& mime_type
,
49 const std::string
& headers
,
52 bool request_is_seekable
) {
54 NPP id
= instance_
->npp();
56 stream_
.lastmodified
= last_modified
;
58 stream_
.ndata
= id
->ndata
;
59 stream_
.notifyData
= notify_data_
;
60 if (!headers_
.empty())
61 stream_
.headers
= headers_
.c_str();
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;
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();
76 GURL
gurl(stream_
.url
);
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();
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
) {
95 if (requested_plugin_mode_
== NP_SEEK
) {
96 seekable_stream_
= true;
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) {
106 mime_type_
= char_mime_type
;
110 int PluginStream::Write(const char* buffer
, const int length
,
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.
120 if (WriteToFile(buffer
, length
) &&
121 WriteToPlugin(buffer
, length
, data_offset
)) {
128 bool PluginStream::WriteToFile(const char* buf
, size_t length
) {
129 // For ASFILEONLY, ASFILE, and SEEK modes, we need to write
131 if (TempFileIsValid() && RequestedPluginModeIsAsFile()) {
132 size_t totalBytesWritten
= 0, bytes
;
134 bytes
= WriteBytes(buf
, length
);
135 totalBytesWritten
+= bytes
;
136 } while (bytes
> 0U && totalBytesWritten
< length
);
138 if (totalBytesWritten
!= length
) {
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
) {
155 int written
= TryWriteToPlugin(buf
, length
, data_offset
);
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));
173 void PluginStream::OnDelayDelivery() {
174 // It is possible that the plugin stream may have closed before the task
179 int size
= static_cast<int>(delivery_data_
.size());
180 int written
= TryWriteToPlugin(&delivery_data_
.front(), size
, data_offset_
);
182 // Remove the data that we already wrote.
183 delivery_data_
.erase(delivery_data_
.begin(),
184 delivery_data_
.begin() + written
);
188 int PluginStream::TryWriteToPlugin(const char* buf
, const int length
,
189 const int data_offset
) {
193 data_offset_
= data_offset
;
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
;
201 if (bytes_to_write
== 0)
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
);
212 if (bytes_consumed
== 0) {
213 // The plugin couldn't take all of the data now.
217 // The plugin might report more that we gave it.
218 bytes_consumed
= std::min(bytes_consumed
, bytes_to_write
);
220 data_offset_
+= bytes_consumed
;
221 byte_offset
+= bytes_consumed
;
224 if (close_on_write_data_
)
230 bool PluginStream::Close(NPReason reason
) {
231 if (opened_
== true) {
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;
240 // Stop any pending data from being streamed
241 delivery_data_
.resize(0);
245 // If we have a temp file, be sure to close it.
246 // Also, allow the plugin to access it now.
247 if (TempFileIsValid()) {
249 if (reason
== NPRES_DONE
)
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
);
264 WebPluginResourceClient
* PluginStream::AsResourceClient() {
268 void PluginStream::Notify(NPReason reason
) {
269 if (notify_needed_
) {
270 instance_
->NPP_URLNotify(stream_
.url
, reason
, notify_data_
);
271 notify_needed_
= false;
275 bool PluginStream::RequestedPluginModeIsAsFile() const {
276 return (requested_plugin_mode_
== NP_ASFILE
||
277 requested_plugin_mode_
== NP_ASFILEONLY
);
280 } // namespace content