1 // Copyright 2013 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 #include "content/browser/renderer_host/pepper/pepper_file_system_browser_host.h"
6
7 #include "base/bind.h"
8 #include "base/callback.h"
9 #include "content/browser/renderer_host/pepper/pepper_file_io_host.h"
10 #include "content/browser/renderer_host/pepper/quota_reservation.h"
11 #include "content/public/browser/browser_ppapi_host.h"
12 #include "content/public/browser/browser_thread.h"
13 #include "content/public/browser/plugin_service.h"
14 #include "content/public/browser/render_process_host.h"
15 #include "content/public/browser/storage_partition.h"
16 #include "content/public/common/pepper_plugin_info.h"
17 #include "net/base/mime_util.h"
18 #include "ppapi/c/pp_errors.h"
19 #include "ppapi/host/dispatch_host_message.h"
20 #include "ppapi/host/ppapi_host.h"
21 #include "ppapi/proxy/ppapi_messages.h"
22 #include "ppapi/shared_impl/file_system_util.h"
23 #include "ppapi/shared_impl/file_type_conversion.h"
24 #include "webkit/browser/fileapi/file_system_operation_runner.h"
25 #include "webkit/browser/fileapi/isolated_context.h"
26 #include "webkit/browser/quota/quota_manager.h"
27 #include "webkit/common/fileapi/file_system_util.h"
28 #include "webkit/common/quota/quota_types.h"
29
30 namespace content {
31
32 namespace {
33
34 // This is the minimum amount of quota we reserve per file system.
35 const int64_t kMinimumQuotaReservationSize = 1024 * 1024; // 1 MB
36
37 scoped_refptr<fileapi::FileSystemContext>
GetFileSystemContextFromRenderId(int render_process_id)38 GetFileSystemContextFromRenderId(int render_process_id) {
39 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
40 RenderProcessHost* host = RenderProcessHost::FromID(render_process_id);
41 if (!host)
42 return NULL;
43 StoragePartition* storage_partition = host->GetStoragePartition();
44 if (!storage_partition)
45 return NULL;
46 return storage_partition->GetFileSystemContext();
47 }
48
49 } // namespace
50
QuotaRequest(int32_t amount_arg,const RequestQuotaCallback & callback_arg)51 PepperFileSystemBrowserHost::QuotaRequest::QuotaRequest(
52 int32_t amount_arg,
53 const RequestQuotaCallback& callback_arg)
54 : amount(amount_arg),
55 callback(callback_arg) {
56 }
57
~QuotaRequest()58 PepperFileSystemBrowserHost::QuotaRequest::~QuotaRequest() {
59 }
60
PepperFileSystemBrowserHost(BrowserPpapiHost * host,PP_Instance instance,PP_Resource resource,PP_FileSystemType type)61 PepperFileSystemBrowserHost::PepperFileSystemBrowserHost(BrowserPpapiHost* host,
62 PP_Instance instance,
63 PP_Resource resource,
64 PP_FileSystemType type)
65 : ResourceHost(host->GetPpapiHost(), instance, resource),
66 browser_ppapi_host_(host),
67 type_(type),
68 called_open_(false),
69 opened_(false),
70 file_system_context_(NULL),
71 reserved_quota_(0),
72 reserving_quota_(false),
73 weak_factory_(this) {
74 }
75
~PepperFileSystemBrowserHost()76 PepperFileSystemBrowserHost::~PepperFileSystemBrowserHost() {
77 // All FileRefs and FileIOs that reference us must have been destroyed. Cancel
78 // all pending file system operations.
79 if (file_system_operation_runner_)
80 file_system_operation_runner_->Shutdown();
81 }
82
OpenExisting(const GURL & root_url,const base::Closure & callback)83 void PepperFileSystemBrowserHost::OpenExisting(const GURL& root_url,
84 const base::Closure& callback) {
85 root_url_ = root_url;
86 int render_process_id = 0;
87 int unused;
88 if (!browser_ppapi_host_->GetRenderViewIDsForInstance(
89 pp_instance(), &render_process_id, &unused)) {
90 NOTREACHED();
91 }
92 called_open_ = true;
93 // Get the file system context asynchronously, and then complete the Open
94 // operation by calling |callback|.
95 BrowserThread::PostTaskAndReplyWithResult(
96 BrowserThread::UI,
97 FROM_HERE,
98 base::Bind(&GetFileSystemContextFromRenderId, render_process_id),
99 base::Bind(&PepperFileSystemBrowserHost::OpenExistingFileSystem,
100 weak_factory_.GetWeakPtr(), callback));
101 }
102
OnResourceMessageReceived(const IPC::Message & msg,ppapi::host::HostMessageContext * context)103 int32_t PepperFileSystemBrowserHost::OnResourceMessageReceived(
104 const IPC::Message& msg,
105 ppapi::host::HostMessageContext* context) {
106 IPC_BEGIN_MESSAGE_MAP(PepperFileSystemBrowserHost, msg)
107 PPAPI_DISPATCH_HOST_RESOURCE_CALL(
108 PpapiHostMsg_FileSystem_Open,
109 OnHostMsgOpen)
110 PPAPI_DISPATCH_HOST_RESOURCE_CALL(
111 PpapiHostMsg_FileSystem_InitIsolatedFileSystem,
112 OnHostMsgInitIsolatedFileSystem)
113 IPC_END_MESSAGE_MAP()
114 return PP_ERROR_FAILED;
115 }
116
IsFileSystemHost()117 bool PepperFileSystemBrowserHost::IsFileSystemHost() {
118 return true;
119 }
120
OpenQuotaFile(PepperFileIOHost * file_io_host,const fileapi::FileSystemURL & url,const OpenQuotaFileCallback & callback)121 void PepperFileSystemBrowserHost::OpenQuotaFile(
122 PepperFileIOHost* file_io_host,
123 const fileapi::FileSystemURL& url,
124 const OpenQuotaFileCallback& callback) {
125 int32_t id = file_io_host->pp_resource();
126 std::pair<FileMap::iterator, bool> insert_result =
127 files_.insert(std::make_pair(id, file_io_host));
128 if (insert_result.second) {
129 base::PostTaskAndReplyWithResult(
130 file_system_context_->default_file_task_runner(),
131 FROM_HERE,
132 base::Bind(&QuotaReservation::OpenFile,
133 quota_reservation_,
134 id,
135 url),
136 callback);
137 } else {
138 NOTREACHED();
139 }
140 }
141
CloseQuotaFile(PepperFileIOHost * file_io_host)142 void PepperFileSystemBrowserHost::CloseQuotaFile(
143 PepperFileIOHost* file_io_host) {
144 int32_t id = file_io_host->pp_resource();
145 int64_t max_written_offset = 0;
146 FileMap::iterator it = files_.find(id);
147 if (it != files_.end()) {
148 max_written_offset = file_io_host->max_written_offset();
149 files_.erase(it);
150 } else {
151 NOTREACHED();
152 return;
153 }
154
155 file_system_context_->default_file_task_runner()->PostTask(
156 FROM_HERE,
157 base::Bind(&QuotaReservation::CloseFile,
158 quota_reservation_,
159 id,
160 max_written_offset));
161 }
162
RequestQuota(int32_t amount,const RequestQuotaCallback & callback)163 int32_t PepperFileSystemBrowserHost::RequestQuota(
164 int32_t amount,
165 const RequestQuotaCallback& callback) {
166 DCHECK(amount >= 0);
167 if (!reserving_quota_ && reserved_quota_ >= amount) {
168 reserved_quota_ -= amount;
169 return amount;
170 }
171
172 // Queue up a pending quota request.
173 pending_quota_requests_.push(QuotaRequest(amount, callback));
174
175 // Reserve more quota if we haven't already.
176 if (!reserving_quota_)
177 ReserveQuota(amount);
178
179 return PP_OK_COMPLETIONPENDING;
180 }
181
OnHostMsgOpen(ppapi::host::HostMessageContext * context,int64_t)182 int32_t PepperFileSystemBrowserHost::OnHostMsgOpen(
183 ppapi::host::HostMessageContext* context,
184 int64_t /* unused */) {
185 // TODO(raymes): The file system size is now unused by FileSystemDispatcher.
186 // Figure out why. Why is the file system size signed?
187
188 // Not allow multiple opens.
189 if (called_open_)
190 return PP_ERROR_INPROGRESS;
191 called_open_ = true;
192
193 fileapi::FileSystemType file_system_type =
194 ppapi::PepperFileSystemTypeToFileSystemType(type_);
195 if (file_system_type == fileapi::kFileSystemTypeUnknown)
196 return PP_ERROR_FAILED;
197
198 int render_process_id = 0;
199 int unused;
200 if (!browser_ppapi_host_->GetRenderViewIDsForInstance(pp_instance(),
201 &render_process_id,
202 &unused)) {
203 return PP_ERROR_FAILED;
204 }
205
206 BrowserThread::PostTaskAndReplyWithResult(
207 BrowserThread::UI,
208 FROM_HERE,
209 base::Bind(&GetFileSystemContextFromRenderId, render_process_id),
210 base::Bind(&PepperFileSystemBrowserHost::OpenFileSystem,
211 weak_factory_.GetWeakPtr(),
212 context->MakeReplyMessageContext(),
213 file_system_type));
214 return PP_OK_COMPLETIONPENDING;
215 }
216
OpenExistingFileSystem(const base::Closure & callback,scoped_refptr<fileapi::FileSystemContext> file_system_context)217 void PepperFileSystemBrowserHost::OpenExistingFileSystem(
218 const base::Closure& callback,
219 scoped_refptr<fileapi::FileSystemContext> file_system_context) {
220 if (file_system_context.get()) {
221 opened_ = true;
222 } else {
223 // If there is no file system context, we log a warning and continue with an
224 // invalid resource (which will produce errors when used), since we have no
225 // way to communicate the error to the caller.
226 LOG(WARNING) << "Could not retrieve file system context.";
227 }
228 SetFileSystemContext(file_system_context);
229
230 if (ShouldCreateQuotaReservation())
231 CreateQuotaReservation(callback);
232 else
233 callback.Run();
234 }
235
OpenFileSystem(ppapi::host::ReplyMessageContext reply_context,fileapi::FileSystemType file_system_type,scoped_refptr<fileapi::FileSystemContext> file_system_context)236 void PepperFileSystemBrowserHost::OpenFileSystem(
237 ppapi::host::ReplyMessageContext reply_context,
238 fileapi::FileSystemType file_system_type,
239 scoped_refptr<fileapi::FileSystemContext> file_system_context) {
240 if (!file_system_context.get()) {
241 OpenFileSystemComplete(
242 reply_context, GURL(), std::string(), base::PLATFORM_FILE_ERROR_FAILED);
243 return;
244 }
245
246 SetFileSystemContext(file_system_context);
247
248 GURL origin = browser_ppapi_host_->GetDocumentURLForInstance(
249 pp_instance()).GetOrigin();
250 file_system_context_->OpenFileSystem(origin, file_system_type,
251 fileapi::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
252 base::Bind(&PepperFileSystemBrowserHost::OpenFileSystemComplete,
253 weak_factory_.GetWeakPtr(),
254 reply_context));
255 }
256
OpenFileSystemComplete(ppapi::host::ReplyMessageContext reply_context,const GURL & root,const std::string &,base::PlatformFileError error)257 void PepperFileSystemBrowserHost::OpenFileSystemComplete(
258 ppapi::host::ReplyMessageContext reply_context,
259 const GURL& root,
260 const std::string& /* unused */,
261 base::PlatformFileError error) {
262 int32 pp_error = ppapi::PlatformFileErrorToPepperError(error);
263 if (pp_error == PP_OK) {
264 opened_ = true;
265 root_url_ = root;
266
267 if (ShouldCreateQuotaReservation()) {
268 CreateQuotaReservation(
269 base::Bind(&PepperFileSystemBrowserHost::SendReplyForFileSystem,
270 weak_factory_.GetWeakPtr(),
271 reply_context,
272 static_cast<int32_t>(PP_OK)));
273 return;
274 }
275 }
276 SendReplyForFileSystem(reply_context, pp_error);
277 }
278
OpenIsolatedFileSystem(ppapi::host::ReplyMessageContext reply_context,const std::string & fsid,PP_IsolatedFileSystemType_Private type,scoped_refptr<fileapi::FileSystemContext> file_system_context)279 void PepperFileSystemBrowserHost::OpenIsolatedFileSystem(
280 ppapi::host::ReplyMessageContext reply_context,
281 const std::string& fsid,
282 PP_IsolatedFileSystemType_Private type,
283 scoped_refptr<fileapi::FileSystemContext> file_system_context) {
284 if (!file_system_context.get()) {
285 SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_FAILED);
286 return;
287 }
288 SetFileSystemContext(file_system_context);
289
290 root_url_ = GURL(fileapi::GetIsolatedFileSystemRootURIString(
291 browser_ppapi_host_->GetDocumentURLForInstance(pp_instance()).GetOrigin(),
292 fsid, ppapi::IsolatedFileSystemTypeToRootName(type)));
293 if (!root_url_.is_valid()) {
294 SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_FAILED);
295 return;
296 }
297
298 switch (type) {
299 case PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_CRX:
300 opened_ = true;
301 SendReplyForIsolatedFileSystem(reply_context, fsid, PP_OK);
302 return;
303 case PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_PLUGINPRIVATE:
304 OpenPluginPrivateFileSystem(reply_context, fsid, file_system_context_);
305 return;
306 default:
307 NOTREACHED();
308 SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_BADARGUMENT);
309 return;
310 }
311 }
312
OpenPluginPrivateFileSystem(ppapi::host::ReplyMessageContext reply_context,const std::string & fsid,scoped_refptr<fileapi::FileSystemContext> file_system_context)313 void PepperFileSystemBrowserHost::OpenPluginPrivateFileSystem(
314 ppapi::host::ReplyMessageContext reply_context,
315 const std::string& fsid,
316 scoped_refptr<fileapi::FileSystemContext> file_system_context) {
317 GURL origin = browser_ppapi_host_->GetDocumentURLForInstance(
318 pp_instance()).GetOrigin();
319 if (!origin.is_valid()) {
320 SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_FAILED);
321 return;
322 }
323
324 const std::string& plugin_id = GeneratePluginId(GetPluginMimeType());
325 if (plugin_id.empty()) {
326 SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_BADARGUMENT);
327 return;
328 }
329
330 file_system_context->OpenPluginPrivateFileSystem(
331 origin, fileapi::kFileSystemTypePluginPrivate, fsid, plugin_id,
332 fileapi::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
333 base::Bind(
334 &PepperFileSystemBrowserHost::OpenPluginPrivateFileSystemComplete,
335 weak_factory_.GetWeakPtr(), reply_context, fsid));
336 }
337
OpenPluginPrivateFileSystemComplete(ppapi::host::ReplyMessageContext reply_context,const std::string & fsid,base::PlatformFileError error)338 void PepperFileSystemBrowserHost::OpenPluginPrivateFileSystemComplete(
339 ppapi::host::ReplyMessageContext reply_context,
340 const std::string& fsid,
341 base::PlatformFileError error) {
342 int32 pp_error = ppapi::PlatformFileErrorToPepperError(error);
343 if (pp_error == PP_OK)
344 opened_ = true;
345 SendReplyForIsolatedFileSystem(reply_context, fsid, pp_error);
346 }
347
OnHostMsgInitIsolatedFileSystem(ppapi::host::HostMessageContext * context,const std::string & fsid,PP_IsolatedFileSystemType_Private type)348 int32_t PepperFileSystemBrowserHost::OnHostMsgInitIsolatedFileSystem(
349 ppapi::host::HostMessageContext* context,
350 const std::string& fsid,
351 PP_IsolatedFileSystemType_Private type) {
352 // Do not allow multiple opens.
353 if (called_open_)
354 return PP_ERROR_INPROGRESS;
355 called_open_ = true;
356
357 // Do a sanity check.
358 if (!fileapi::ValidateIsolatedFileSystemId(fsid))
359 return PP_ERROR_BADARGUMENT;
360
361 int render_process_id = 0;
362 int unused;
363 if (!browser_ppapi_host_->GetRenderViewIDsForInstance(pp_instance(),
364 &render_process_id,
365 &unused)) {
366 fileapi::IsolatedContext::GetInstance()->RevokeFileSystem(fsid);
367 return PP_ERROR_FAILED;
368 }
369
370 root_url_ = GURL(fileapi::GetIsolatedFileSystemRootURIString(
371 browser_ppapi_host_->GetDocumentURLForInstance(pp_instance()).GetOrigin(),
372 fsid, ppapi::IsolatedFileSystemTypeToRootName(type)));
373
374 BrowserThread::PostTaskAndReplyWithResult(
375 BrowserThread::UI,
376 FROM_HERE,
377 base::Bind(&GetFileSystemContextFromRenderId, render_process_id),
378 base::Bind(&PepperFileSystemBrowserHost::OpenIsolatedFileSystem,
379 weak_factory_.GetWeakPtr(),
380 context->MakeReplyMessageContext(), fsid, type));
381 return PP_OK_COMPLETIONPENDING;
382 }
383
SendReplyForFileSystem(ppapi::host::ReplyMessageContext reply_context,int32_t pp_error)384 void PepperFileSystemBrowserHost::SendReplyForFileSystem(
385 ppapi::host::ReplyMessageContext reply_context,
386 int32_t pp_error) {
387 reply_context.params.set_result(pp_error);
388 host()->SendReply(reply_context, PpapiPluginMsg_FileSystem_OpenReply());
389 }
390
SendReplyForIsolatedFileSystem(ppapi::host::ReplyMessageContext reply_context,const std::string & fsid,int32_t error)391 void PepperFileSystemBrowserHost::SendReplyForIsolatedFileSystem(
392 ppapi::host::ReplyMessageContext reply_context,
393 const std::string& fsid,
394 int32_t error) {
395 if (error != PP_OK)
396 fileapi::IsolatedContext::GetInstance()->RevokeFileSystem(fsid);
397 reply_context.params.set_result(error);
398 host()->SendReply(reply_context,
399 PpapiPluginMsg_FileSystem_InitIsolatedFileSystemReply());
400 }
401
SetFileSystemContext(scoped_refptr<fileapi::FileSystemContext> file_system_context)402 void PepperFileSystemBrowserHost::SetFileSystemContext(
403 scoped_refptr<fileapi::FileSystemContext> file_system_context) {
404 file_system_context_ = file_system_context;
405 if (type_ != PP_FILESYSTEMTYPE_EXTERNAL) {
406 file_system_operation_runner_ =
407 file_system_context_->CreateFileSystemOperationRunner();
408 }
409 }
410
ShouldCreateQuotaReservation() const411 bool PepperFileSystemBrowserHost::ShouldCreateQuotaReservation() const {
412 // Some file system types don't have quota.
413 if (!ppapi::FileSystemTypeHasQuota(type_))
414 return false;
415
416 // For file system types with quota, ome origins have unlimited storage.
417 quota::QuotaManagerProxy* quota_manager_proxy =
418 file_system_context_->quota_manager_proxy();
419 CHECK(quota_manager_proxy);
420 CHECK(quota_manager_proxy->quota_manager());
421 fileapi::FileSystemType file_system_type =
422 ppapi::PepperFileSystemTypeToFileSystemType(type_);
423 return !quota_manager_proxy->quota_manager()->IsStorageUnlimited(
424 root_url_.GetOrigin(),
425 fileapi::FileSystemTypeToQuotaStorageType(file_system_type));
426 }
427
CreateQuotaReservation(const base::Closure & callback)428 void PepperFileSystemBrowserHost::CreateQuotaReservation(
429 const base::Closure& callback) {
430 DCHECK(root_url_.is_valid());
431 base::PostTaskAndReplyWithResult(
432 file_system_context_->default_file_task_runner(),
433 FROM_HERE,
434 base::Bind(&QuotaReservation::Create,
435 file_system_context_,
436 root_url_.GetOrigin(),
437 ppapi::PepperFileSystemTypeToFileSystemType(type_)),
438 base::Bind(&PepperFileSystemBrowserHost::GotQuotaReservation,
439 weak_factory_.GetWeakPtr(),
440 callback));
441 }
442
GotQuotaReservation(const base::Closure & callback,scoped_refptr<QuotaReservation> quota_reservation)443 void PepperFileSystemBrowserHost::GotQuotaReservation(
444 const base::Closure& callback,
445 scoped_refptr<QuotaReservation> quota_reservation) {
446 quota_reservation_ = quota_reservation;
447 callback.Run();
448 }
449
ReserveQuota(int32_t amount)450 void PepperFileSystemBrowserHost::ReserveQuota(int32_t amount) {
451 DCHECK(!reserving_quota_);
452 reserving_quota_ = true;
453
454 // Get the max_written_offset for each open file.
455 QuotaReservation::OffsetMap max_written_offsets;
456 for (FileMap::iterator it = files_.begin(); it != files_.end(); ++ it) {
457 max_written_offsets.insert(
458 std::make_pair(it->first, it->second->max_written_offset()));
459 }
460
461 int64_t reservation_amount = std::max<int64_t>(kMinimumQuotaReservationSize,
462 amount);
463 file_system_context_->default_file_task_runner()->PostTask(
464 FROM_HERE,
465 base::Bind(&QuotaReservation::ReserveQuota,
466 quota_reservation_,
467 reservation_amount,
468 max_written_offsets,
469 base::Bind(&PepperFileSystemBrowserHost::GotReservedQuota,
470 weak_factory_.GetWeakPtr())));
471 }
472
GotReservedQuota(int64_t amount,const QuotaReservation::OffsetMap & max_written_offsets)473 void PepperFileSystemBrowserHost::GotReservedQuota(
474 int64_t amount,
475 const QuotaReservation::OffsetMap& max_written_offsets) {
476 DCHECK(reserving_quota_);
477 reserving_quota_ = false;
478 reserved_quota_ = amount;
479
480 // Update open files with their new base sizes. This won't write over any
481 // updates since the files are waiting for quota and can't write.
482 for (FileMap::iterator it = files_.begin(); it != files_.end(); ++ it) {
483 QuotaReservation::OffsetMap::const_iterator offset_it =
484 max_written_offsets.find(it->first);
485 if (offset_it != max_written_offsets.end())
486 it->second->set_max_written_offset(offset_it->second);
487 else
488 NOTREACHED();
489 }
490
491 DCHECK(!pending_quota_requests_.empty());
492 // If we can't grant the first request after refreshing reserved_quota_, then
493 // fail all pending quota requests to avoid an infinite refresh/fail loop.
494 bool fail_all = reserved_quota_ < pending_quota_requests_.front().amount;
495 while (!pending_quota_requests_.empty()) {
496 QuotaRequest& request = pending_quota_requests_.front();
497 if (fail_all) {
498 request.callback.Run(0);
499 pending_quota_requests_.pop();
500 } else if (reserved_quota_ >= request.amount) {
501 reserved_quota_ -= request.amount;
502 request.callback.Run(request.amount);
503 pending_quota_requests_.pop();
504 } else {
505 // Refresh the quota reservation for the first pending request that we
506 // can't satisfy.
507 ReserveQuota(request.amount);
508 break;
509 }
510 }
511 }
512
GetPluginMimeType() const513 std::string PepperFileSystemBrowserHost::GetPluginMimeType() const {
514 base::FilePath plugin_path = browser_ppapi_host_->GetPluginPath();
515 PepperPluginInfo* info =
516 PluginService::GetInstance()->GetRegisteredPpapiPluginInfo(plugin_path);
517 if (!info || info->mime_types.empty())
518 return std::string();
519 // Use the first element in |info->mime_types| even if several elements exist.
520 return info->mime_types[0].mime_type;
521 }
522
GeneratePluginId(const std::string & mime_type) const523 std::string PepperFileSystemBrowserHost::GeneratePluginId(
524 const std::string& mime_type) const {
525 // TODO(nhiroki): This function is very specialized for specific plugins (MIME
526 // types). If we bring this API to stable, we might have to make it more
527 // general.
528
529 if (!net::IsMimeType(mime_type))
530 return std::string();
531 std::string output = mime_type;
532
533 // Replace a slash used for type/subtype separator with an underscore.
534 // NOTE: This assumes there is only one slash in the MIME type.
535 ReplaceFirstSubstringAfterOffset(&output, 0, "/", "_");
536
537 // Verify |output| contains only alphabets, digits, or "._-".
538 for (std::string::const_iterator it = output.begin();
539 it != output.end(); ++it) {
540 if (!IsAsciiAlpha(*it) && !IsAsciiDigit(*it) &&
541 *it != '.' && *it != '_' && *it != '-') {
542 LOG(WARNING) << "Failed to generate a plugin id.";
543 return std::string();
544 }
545 }
546 return output;
547 }
548
549 } // namespace content
550