blob: 2049f37df5a0d26499123aacd6de341a9341c4fc [file] [log] [blame]
jrummellbb33a9732016-06-10 23:46:211// Copyright 2016 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/plugin_private_storage_helper.h"
6
7#include <stddef.h>
8#include <stdint.h>
9
10#include <memory>
11#include <set>
12#include <string>
13
14#include "base/bind.h"
15#include "base/compiler_specific.h"
16#include "base/files/file.h"
17#include "base/files/file_enumerator.h"
18#include "base/files/file_path.h"
19#include "base/location.h"
20#include "base/logging.h"
fdorayba121422016-12-23 19:51:4821#include "base/memory/ptr_util.h"
jrummellbb33a9732016-06-10 23:46:2122#include "base/stl_util.h"
23#include "base/strings/utf_string_conversions.h"
24#include "content/public/browser/browser_thread.h"
25#include "ppapi/shared_impl/ppapi_constants.h"
26#include "storage/browser/fileapi/async_file_util.h"
27#include "storage/browser/fileapi/async_file_util_adapter.h"
28#include "storage/browser/fileapi/file_system_context.h"
29#include "storage/browser/fileapi/isolated_context.h"
30#include "storage/browser/fileapi/obfuscated_file_util.h"
31#include "storage/common/fileapi/file_system_util.h"
32
33namespace content {
34
35namespace {
36
37std::string StringTypeToString(const base::FilePath::StringType& value) {
38#if defined(OS_POSIX)
39 return value;
40#elif defined(OS_WIN)
41 return base::WideToUTF8(value);
42#endif
43}
44
45// Helper for checking the plugin private data for a specified origin and
dschuyler11857732017-06-09 20:45:3346// plugin for the existence of any file that matches the time range specified.
jrummellbb33a9732016-06-10 23:46:2147// All of the operations in this class are done on the IO thread.
48//
49// This class keeps track of outstanding async requests it generates, and does
50// not call |callback_| until they all respond (and thus don't need to worry
51// about lifetime of |this| in the async requests). If the data for the origin
52// needs to be deleted, it needs to be done on the file task runner, so we
53// want to ensure that there are no pending requests that may prevent the
54// date from being deleted.
55class PluginPrivateDataByOriginChecker {
56 public:
57 PluginPrivateDataByOriginChecker(
58 storage::FileSystemContext* filesystem_context,
59 const GURL& origin,
60 const std::string& plugin_name,
61 const base::Time begin,
62 const base::Time end,
63 const base::Callback<void(bool, const GURL&)>& callback)
64 : filesystem_context_(filesystem_context),
65 origin_(origin),
66 plugin_name_(plugin_name),
67 begin_(begin),
68 end_(end),
69 callback_(callback) {
70 // Create the filesystem ID.
71 fsid_ = storage::IsolatedContext::GetInstance()
72 ->RegisterFileSystemForVirtualPath(
73 storage::kFileSystemTypePluginPrivate,
74 ppapi::kPluginPrivateRootName, base::FilePath());
75 }
76 ~PluginPrivateDataByOriginChecker() {}
77
78 // Checks the files contained in the plugin private filesystem for |origin_|
79 // and |plugin_name_| for any file whose last modified time is between
80 // |begin_| and |end_|. |callback_| is called when all actions are complete
81 // with true and the origin if any such file is found, false and empty GURL
82 // otherwise.
83 void CheckFilesOnIOThread();
84
85 private:
86 void OnFileSystemOpened(base::File::Error result);
87 void OnDirectoryRead(const std::string& root,
88 base::File::Error result,
tzika40b4402017-08-28 14:46:1289 storage::AsyncFileUtil::EntryList file_list,
jrummellbb33a9732016-06-10 23:46:2190 bool has_more);
91 void OnFileInfo(const std::string& file_name,
92 base::File::Error result,
93 const base::File::Info& file_info);
94
95 // Keeps track of the pending work. When |task_count_| goes to 0 then
96 // |callback_| is called and this helper object is destroyed.
97 void IncrementTaskCount();
98 void DecrementTaskCount();
99
100 // Not owned by this object. Caller is responsible for keeping the
101 // FileSystemContext alive until |callback_| is called.
102 storage::FileSystemContext* filesystem_context_;
103
104 const GURL origin_;
105 const std::string plugin_name_;
106 const base::Time begin_;
107 const base::Time end_;
108 const base::Callback<void(bool, const GURL&)> callback_;
109 std::string fsid_;
110 int task_count_ = 0;
111
112 // Keep track if the data for this origin needs to be deleted due to
113 // any file found that has last modified time between |begin_| and |end_|.
114 bool delete_this_origin_data_ = false;
jrummellbd3656d2017-01-12 19:17:29115
116 // Keep track if any files exist for this origin.
117 bool files_found_ = false;
jrummellbb33a9732016-06-10 23:46:21118};
119
120void PluginPrivateDataByOriginChecker::CheckFilesOnIOThread() {
121 DCHECK_CURRENTLY_ON(BrowserThread::IO);
122 DCHECK(storage::ValidateIsolatedFileSystemId(fsid_));
123
124 IncrementTaskCount();
125 filesystem_context_->OpenPluginPrivateFileSystem(
126 origin_, storage::kFileSystemTypePluginPrivate, fsid_, plugin_name_,
127 storage::OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
tzike2aca992017-09-05 08:50:54128 base::BindOnce(&PluginPrivateDataByOriginChecker::OnFileSystemOpened,
129 base::Unretained(this)));
jrummellbb33a9732016-06-10 23:46:21130}
131
132void PluginPrivateDataByOriginChecker::OnFileSystemOpened(
133 base::File::Error result) {
134 DCHECK_CURRENTLY_ON(BrowserThread::IO);
135 DVLOG(3) << "Opened filesystem for " << origin_ << ":" << plugin_name_
136 << ", result: " << result;
137
138 // If we can't open the directory, we can't delete files so simply return.
139 if (result != base::File::FILE_OK) {
140 DecrementTaskCount();
141 return;
142 }
143
144 storage::AsyncFileUtil* file_util = filesystem_context_->GetAsyncFileUtil(
145 storage::kFileSystemTypePluginPrivate);
146 std::string root = storage::GetIsolatedFileSystemRootURIString(
147 origin_, fsid_, ppapi::kPluginPrivateRootName);
148 std::unique_ptr<storage::FileSystemOperationContext> operation_context =
ricea641bb022016-09-02 02:51:09149 base::MakeUnique<storage::FileSystemOperationContext>(
150 filesystem_context_);
jrummellbb33a9732016-06-10 23:46:21151 file_util->ReadDirectory(
152 std::move(operation_context), filesystem_context_->CrackURL(GURL(root)),
tzika40b4402017-08-28 14:46:12153 base::BindRepeating(&PluginPrivateDataByOriginChecker::OnDirectoryRead,
154 base::Unretained(this), root));
jrummellbb33a9732016-06-10 23:46:21155}
156
157void PluginPrivateDataByOriginChecker::OnDirectoryRead(
158 const std::string& root,
159 base::File::Error result,
tzika40b4402017-08-28 14:46:12160 storage::AsyncFileUtil::EntryList file_list,
jrummellbb33a9732016-06-10 23:46:21161 bool has_more) {
162 DCHECK_CURRENTLY_ON(BrowserThread::IO);
pkastingf5279482016-07-27 02:18:20163 DVLOG(3) << __func__ << " result: " << result
jrummellbb33a9732016-06-10 23:46:21164 << ", #files: " << file_list.size();
165
166 // Quit if there is an error.
167 if (result != base::File::FILE_OK) {
168 DLOG(ERROR) << "Unable to read directory for " << origin_ << ":"
169 << plugin_name_;
170 DecrementTaskCount();
171 return;
172 }
173
jrummellbd3656d2017-01-12 19:17:29174 // If there are files found, keep track of it.
175 if (!file_list.empty())
176 files_found_ = true;
177
jrummellbb33a9732016-06-10 23:46:21178 // No error, process the files returned. No need to do this if we have
179 // already decided to delete all the data for this origin.
180 if (!delete_this_origin_data_) {
181 storage::AsyncFileUtil* file_util = filesystem_context_->GetAsyncFileUtil(
182 storage::kFileSystemTypePluginPrivate);
183 for (const auto& file : file_list) {
pkastingf5279482016-07-27 02:18:20184 DVLOG(3) << __func__ << " file: " << file.name;
jrummellbb33a9732016-06-10 23:46:21185 DCHECK(!file.is_directory); // Nested directories not implemented.
186
187 std::unique_ptr<storage::FileSystemOperationContext> operation_context =
ricea641bb022016-09-02 02:51:09188 base::MakeUnique<storage::FileSystemOperationContext>(
189 filesystem_context_);
jrummellbb33a9732016-06-10 23:46:21190 storage::FileSystemURL file_url = filesystem_context_->CrackURL(
191 GURL(root + StringTypeToString(file.name)));
192 IncrementTaskCount();
193 file_util->GetFileInfo(
194 std::move(operation_context), file_url,
195 storage::FileSystemOperation::GET_METADATA_FIELD_SIZE |
196 storage::FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED,
197 base::Bind(&PluginPrivateDataByOriginChecker::OnFileInfo,
198 base::Unretained(this), StringTypeToString(file.name)));
199 }
200 }
201
202 // If there are more files in this directory, wait for the next call.
203 if (has_more)
204 return;
205
206 DecrementTaskCount();
207}
208
209void PluginPrivateDataByOriginChecker::OnFileInfo(
210 const std::string& file_name,
211 base::File::Error result,
212 const base::File::Info& file_info) {
213 DCHECK_CURRENTLY_ON(BrowserThread::IO);
214
215 if (result == base::File::FILE_OK) {
pkastingf5279482016-07-27 02:18:20216 DVLOG(3) << __func__ << " name: " << file_name
jrummellbb33a9732016-06-10 23:46:21217 << ", size: " << file_info.size
218 << ", modified: " << file_info.last_modified;
219 if (file_info.last_modified >= begin_ && file_info.last_modified <= end_)
220 delete_this_origin_data_ = true;
221 }
222
223 DecrementTaskCount();
224}
225
226void PluginPrivateDataByOriginChecker::IncrementTaskCount() {
227 DCHECK_CURRENTLY_ON(BrowserThread::IO);
228 ++task_count_;
229}
230
231void PluginPrivateDataByOriginChecker::DecrementTaskCount() {
232 DCHECK_CURRENTLY_ON(BrowserThread::IO);
233 DCHECK_GT(task_count_, 0);
234 --task_count_;
235 if (task_count_)
236 return;
237
jrummellbd3656d2017-01-12 19:17:29238 // If no files exist for this origin, then we can safely delete it.
239 if (!files_found_)
240 delete_this_origin_data_ = true;
241
jrummellbb33a9732016-06-10 23:46:21242 // If there are no more tasks in progress, then run |callback_| on the
243 // proper thread.
244 filesystem_context_->default_file_task_runner()->PostTask(
tzike2aca992017-09-05 08:50:54245 FROM_HERE, base::BindOnce(callback_, delete_this_origin_data_, origin_));
jrummellbb33a9732016-06-10 23:46:21246 delete this;
247}
248
249// Helper for deleting the plugin private data.
250// All of the operations in this class are done on the file task runner.
251class PluginPrivateDataDeletionHelper {
252 public:
253 PluginPrivateDataDeletionHelper(
254 scoped_refptr<storage::FileSystemContext> filesystem_context,
255 const base::Time begin,
256 const base::Time end,
257 const base::Closure& callback)
258 : filesystem_context_(std::move(filesystem_context)),
259 begin_(begin),
260 end_(end),
261 callback_(callback) {}
262 ~PluginPrivateDataDeletionHelper() {}
263
264 void CheckOriginsOnFileTaskRunner(const std::set<GURL>& origins);
265
266 private:
267 // Keeps track of the pending work. When |task_count_| goes to 0 then
268 // |callback_| is called and this helper object is destroyed.
269 void IncrementTaskCount();
270 void DecrementTaskCount(bool delete_data_for_origin, const GURL& origin);
271
272 // Keep a reference to FileSystemContext until we are done with it.
273 scoped_refptr<storage::FileSystemContext> filesystem_context_;
274
275 const base::Time begin_;
276 const base::Time end_;
277 const base::Closure callback_;
278 int task_count_ = 0;
279};
280
281void PluginPrivateDataDeletionHelper::CheckOriginsOnFileTaskRunner(
282 const std::set<GURL>& origins) {
283 DCHECK(filesystem_context_->default_file_task_runner()
peary2229d97452017-05-12 01:55:19284 ->RunsTasksInCurrentSequence());
jrummellbb33a9732016-06-10 23:46:21285 IncrementTaskCount();
286
287 base::Callback<void(bool, const GURL&)> decrement_callback =
288 base::Bind(&PluginPrivateDataDeletionHelper::DecrementTaskCount,
289 base::Unretained(this));
290 storage::AsyncFileUtil* async_file_util =
291 filesystem_context_->GetAsyncFileUtil(
292 storage::kFileSystemTypePluginPrivate);
293 storage::ObfuscatedFileUtil* obfuscated_file_util =
294 static_cast<storage::ObfuscatedFileUtil*>(
295 static_cast<storage::AsyncFileUtilAdapter*>(async_file_util)
296 ->sync_file_util());
297 for (const auto& origin : origins) {
298 // Determine the available plugin private filesystem directories
299 // for this origin.
300 base::File::Error error;
301 base::FilePath path = obfuscated_file_util->GetDirectoryForOriginAndType(
302 origin, "", false, &error);
303 if (error != base::File::FILE_OK) {
304 DLOG(ERROR) << "Unable to read directory for " << origin;
305 continue;
306 }
307
308 // Currently the plugin private filesystem is only used by Encrypted
309 // Media Content Decryption Modules, which are treated as pepper plugins.
310 // Each CDM gets a directory based on the mimetype (e.g. plugin
311 // application/x-ppapi-widevine-cdm uses directory
312 // application_x-ppapi-widevine-cdm). Enumerate through the set of
313 // directories so that data from any CDM used by this origin is deleted.
314 base::FileEnumerator file_enumerator(path, false,
315 base::FileEnumerator::DIRECTORIES);
316 for (base::FilePath plugin_path = file_enumerator.Next();
317 !plugin_path.empty(); plugin_path = file_enumerator.Next()) {
318 IncrementTaskCount();
319 PluginPrivateDataByOriginChecker* helper =
320 new PluginPrivateDataByOriginChecker(
321 filesystem_context_.get(), origin.GetOrigin(),
322 plugin_path.BaseName().MaybeAsASCII(), begin_, end_,
323 decrement_callback);
324 BrowserThread::PostTask(
325 BrowserThread::IO, FROM_HERE,
tzike2aca992017-09-05 08:50:54326 base::BindOnce(
327 &PluginPrivateDataByOriginChecker::CheckFilesOnIOThread,
328 base::Unretained(helper)));
jrummellbb33a9732016-06-10 23:46:21329
330 // |helper| will delete itself when it is done.
331 }
332 }
333
334 // Cancels out the call to IncrementTaskCount() at the start of this method.
335 // If there are no origins specified then this will cause this helper to
336 // be destroyed.
337 DecrementTaskCount(false, GURL());
338}
339
340void PluginPrivateDataDeletionHelper::IncrementTaskCount() {
341 DCHECK(filesystem_context_->default_file_task_runner()
peary2229d97452017-05-12 01:55:19342 ->RunsTasksInCurrentSequence());
jrummellbb33a9732016-06-10 23:46:21343 ++task_count_;
344}
345
346void PluginPrivateDataDeletionHelper::DecrementTaskCount(
347 bool delete_data_for_origin,
348 const GURL& origin) {
349 DCHECK(filesystem_context_->default_file_task_runner()
peary2229d97452017-05-12 01:55:19350 ->RunsTasksInCurrentSequence());
jrummellbb33a9732016-06-10 23:46:21351
352 // Since the PluginPrivateDataByOriginChecker runs on the IO thread,
353 // delete all the data for |origin| if needed.
354 if (delete_data_for_origin) {
355 DCHECK(!origin.is_empty());
356 DVLOG(3) << "Deleting plugin data for " << origin;
357 storage::FileSystemBackend* backend =
358 filesystem_context_->GetFileSystemBackend(
359 storage::kFileSystemTypePluginPrivate);
360 storage::FileSystemQuotaUtil* quota_util = backend->GetQuotaUtil();
361 base::File::Error result = quota_util->DeleteOriginDataOnFileTaskRunner(
362 filesystem_context_.get(), nullptr, origin,
363 storage::kFileSystemTypePluginPrivate);
364 ALLOW_UNUSED_LOCAL(result);
365 DLOG_IF(ERROR, result != base::File::FILE_OK)
366 << "Unable to delete the plugin data for " << origin;
367 }
368
369 DCHECK_GT(task_count_, 0);
370 --task_count_;
371 if (task_count_)
372 return;
373
374 // If there are no more tasks in progress, run |callback_| and then
375 // this helper can be deleted.
376 callback_.Run();
377 delete this;
378}
379
380} // namespace
381
382void ClearPluginPrivateDataOnFileTaskRunner(
383 scoped_refptr<storage::FileSystemContext> filesystem_context,
384 const GURL& storage_origin,
385 const base::Time begin,
386 const base::Time end,
387 const base::Closure& callback) {
388 DCHECK(filesystem_context->default_file_task_runner()
peary2229d97452017-05-12 01:55:19389 ->RunsTasksInCurrentSequence());
jrummellbb33a9732016-06-10 23:46:21390 DVLOG(3) << "Clearing plugin data for origin: " << storage_origin;
391
392 storage::FileSystemBackend* backend =
393 filesystem_context->GetFileSystemBackend(
394 storage::kFileSystemTypePluginPrivate);
395 storage::FileSystemQuotaUtil* quota_util = backend->GetQuotaUtil();
396
397 // Determine the set of origins used.
398 std::set<GURL> origins;
399 quota_util->GetOriginsForTypeOnFileTaskRunner(
400 storage::kFileSystemTypePluginPrivate, &origins);
401
402 if (origins.empty()) {
403 // No origins, so nothing to do.
404 callback.Run();
405 return;
406 }
407
408 // If a specific origin is provided, then check that it is in the list
409 // returned and remove all the other origins.
410 if (!storage_origin.is_empty()) {
skyostil66bd67912016-08-12 12:33:11411 if (!base::ContainsKey(origins, storage_origin)) {
jrummellbb33a9732016-06-10 23:46:21412 // Nothing matches, so nothing to do.
413 callback.Run();
414 return;
415 }
416
417 // List should only contain the one value that matches.
418 origins.clear();
419 origins.insert(storage_origin);
420 }
421
422 PluginPrivateDataDeletionHelper* helper = new PluginPrivateDataDeletionHelper(
423 std::move(filesystem_context), begin, end, callback);
424 helper->CheckOriginsOnFileTaskRunner(origins);
425 // |helper| will delete itself when all origins have been checked.
426}
427
428} // namespace content