plugin_data_remover_impl.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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#include "content/browser/plugin_data_remover_impl.h"
6
7#include <limits>
8
9#include "base/bind.h"
10#include "base/metrics/histogram.h"
11#include "base/sequenced_task_runner_helpers.h"
12#include "base/synchronization/waitable_event.h"
13#include "base/utf_string_conversions.h"
14#include "base/version.h"
15#include "content/browser/plugin_process_host.h"
16#include "content/browser/plugin_service_impl.h"
17#include "content/browser/renderer_host/pepper/pepper_flash_file_message_filter.h"
18#include "content/common/child_process_host_impl.h"
19#include "content/common/plugin_process_messages.h"
20#include "content/public/browser/browser_context.h"
21#include "content/public/browser/browser_thread.h"
22#include "content/public/common/pepper_plugin_info.h"
23#include "ppapi/proxy/ppapi_messages.h"
24#include "webkit/plugins/npapi/plugin_utils.h"
25#include "webkit/plugins/plugin_constants.h"
26
27namespace content {
28
29namespace {
30
31// The minimum Flash Player version that implements NPP_ClearSiteData.
32const char kMinFlashVersion[] = "10.3";
33const int64 kRemovalTimeoutMs = 10000;
34const uint64 kClearAllData = 0;
35
36}  // namespace
37
38// static
39PluginDataRemover* PluginDataRemover::Create(BrowserContext* browser_context) {
40  return new PluginDataRemoverImpl(browser_context);
41}
42
43// static
44void PluginDataRemover::GetSupportedPlugins(
45    std::vector<webkit::WebPluginInfo>* supported_plugins) {
46  bool allow_wildcard = false;
47  std::vector<webkit::WebPluginInfo> plugins;
48  PluginService::GetInstance()->GetPluginInfoArray(
49      GURL(), kFlashPluginSwfMimeType, allow_wildcard, &plugins, NULL);
50  Version min_version(kMinFlashVersion);
51  for (std::vector<webkit::WebPluginInfo>::iterator it = plugins.begin();
52       it != plugins.end(); ++it) {
53    Version version;
54    webkit::npapi::CreateVersionFromString(it->version, &version);
55    if (version.IsValid() && min_version.CompareTo(version) == -1)
56      supported_plugins->push_back(*it);
57  }
58}
59
60class PluginDataRemoverImpl::Context
61    : public PluginProcessHost::Client,
62      public PpapiPluginProcessHost::BrokerClient,
63      public IPC::Listener,
64      public base::RefCountedThreadSafe<Context,
65                                        BrowserThread::DeleteOnIOThread> {
66 public:
67  Context(base::Time begin_time, BrowserContext* browser_context)
68      : event_(new base::WaitableEvent(true, false)),
69        begin_time_(begin_time),
70        is_removing_(false),
71        browser_context_path_(browser_context->GetPath()),
72        resource_context_(browser_context->GetResourceContext()),
73        channel_(NULL) {
74    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
75  }
76
77  void Init(const std::string& mime_type) {
78    BrowserThread::PostTask(
79        BrowserThread::IO,
80        FROM_HERE,
81        base::Bind(&Context::InitOnIOThread, this, mime_type));
82    BrowserThread::PostDelayedTask(
83        BrowserThread::IO,
84        FROM_HERE,
85        base::Bind(&Context::OnTimeout, this),
86        base::TimeDelta::FromMilliseconds(kRemovalTimeoutMs));
87  }
88
89  void InitOnIOThread(const std::string& mime_type) {
90    PluginServiceImpl* plugin_service = PluginServiceImpl::GetInstance();
91
92    // Get the plugin file path.
93    std::vector<webkit::WebPluginInfo> plugins;
94    plugin_service->GetPluginInfoArray(
95        GURL(), mime_type, false, &plugins, NULL);
96    base::FilePath plugin_path;
97    if (!plugins.empty())  // May be empty for some tests.
98      plugin_path = plugins[0].path;
99
100    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
101    remove_start_time_ = base::Time::Now();
102    is_removing_ = true;
103    // Balanced in On[Ppapi]ChannelOpened or OnError. Exactly one them will
104    // eventually be called, so we need to keep this object around until then.
105    AddRef();
106
107    PepperPluginInfo* pepper_info =
108        plugin_service->GetRegisteredPpapiPluginInfo(plugin_path);
109    if (pepper_info) {
110      plugin_name_ = pepper_info->name;
111      // Use the broker since we run this function outside the sandbox.
112      plugin_service->OpenChannelToPpapiBroker(0, plugin_path, this);
113    } else {
114      plugin_service->OpenChannelToNpapiPlugin(
115          0, 0, GURL(), GURL(), mime_type, this);
116    }
117  }
118
119  // Called when a timeout happens in order not to block the client
120  // indefinitely.
121  void OnTimeout() {
122    LOG_IF(ERROR, is_removing_) << "Timed out";
123    SignalDone();
124  }
125
126  // PluginProcessHost::Client methods.
127  virtual int ID() OVERRIDE {
128    // Generate a unique identifier for this PluginProcessHostClient.
129    return ChildProcessHostImpl::GenerateChildProcessUniqueId();
130  }
131
132  virtual bool OffTheRecord() OVERRIDE {
133    return false;
134  }
135
136  virtual ResourceContext* GetResourceContext() OVERRIDE {
137    return resource_context_;
138  }
139
140  virtual void SetPluginInfo(const webkit::WebPluginInfo& info) OVERRIDE {}
141
142  virtual void OnFoundPluginProcessHost(PluginProcessHost* host) OVERRIDE {}
143
144  virtual void OnSentPluginChannelRequest() OVERRIDE {}
145
146  virtual void OnChannelOpened(const IPC::ChannelHandle& handle) OVERRIDE {
147    ConnectToChannel(handle, false);
148    // Balancing the AddRef call.
149    Release();
150  }
151
152  virtual void OnError() OVERRIDE {
153    LOG(ERROR) << "Couldn't open plugin channel";
154    SignalDone();
155    // Balancing the AddRef call.
156    Release();
157  }
158
159  // PpapiPluginProcessHost::BrokerClient implementation.
160  virtual void GetPpapiChannelInfo(base::ProcessHandle* renderer_handle,
161                                   int* renderer_id) OVERRIDE {
162    *renderer_handle = base::kNullProcessHandle;
163    *renderer_id = 0;
164  }
165
166  virtual void OnPpapiChannelOpened(
167      const IPC::ChannelHandle& channel_handle,
168      base::ProcessId  /* peer_pid */,
169      int /* child_id */) OVERRIDE {
170    if (!channel_handle.name.empty())
171      ConnectToChannel(channel_handle, true);
172
173    // Balancing the AddRef call.
174    Release();
175  }
176
177  // IPC::Listener methods.
178  virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
179    IPC_BEGIN_MESSAGE_MAP(Context, message)
180      IPC_MESSAGE_HANDLER(PluginProcessHostMsg_ClearSiteDataResult,
181                          OnClearSiteDataResult)
182      IPC_MESSAGE_HANDLER(PpapiHostMsg_ClearSiteDataResult,
183                          OnPpapiClearSiteDataResult)
184      IPC_MESSAGE_UNHANDLED_ERROR()
185    IPC_END_MESSAGE_MAP()
186
187    return true;
188  }
189
190  virtual void OnChannelError() OVERRIDE {
191    if (is_removing_) {
192      NOTREACHED() << "Channel error";
193      SignalDone();
194    }
195  }
196
197  base::WaitableEvent* event() { return event_.get(); }
198
199 private:
200  friend struct BrowserThread::DeleteOnThread<BrowserThread::IO>;
201  friend class base::DeleteHelper<Context>;
202  virtual ~Context() {}
203
204  IPC::Message* CreatePpapiClearSiteDataMsg(uint64 max_age) {
205    base::FilePath profile_path =
206        PepperFlashFileMessageFilter::GetDataDirName(browser_context_path_);
207    // TODO(vtl): This "duplicates" logic in webkit/plugins/ppapi/file_path.cc
208    // (which prepends the plugin name to the relative part of the path
209    // instead, with the absolute, profile-dependent part being enforced by
210    // the browser).
211#if defined(OS_WIN)
212    base::FilePath plugin_data_path =
213        profile_path.Append(base::FilePath(UTF8ToUTF16(plugin_name_)));
214#else
215    base::FilePath plugin_data_path =
216        profile_path.Append(base::FilePath(plugin_name_));
217#endif  // defined(OS_WIN)
218    return new PpapiMsg_ClearSiteData(0u, plugin_data_path, std::string(),
219                                      kClearAllData, max_age);
220  }
221
222  // Connects the client side of a newly opened plug-in channel.
223  void ConnectToChannel(const IPC::ChannelHandle& handle, bool is_ppapi) {
224    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
225
226    // If we timed out, don't bother connecting.
227    if (!is_removing_)
228      return;
229
230    DCHECK(!channel_.get());
231    channel_.reset(new IPC::Channel(handle, IPC::Channel::MODE_CLIENT, this));
232    if (!channel_->Connect()) {
233      NOTREACHED() << "Couldn't connect to plugin";
234      SignalDone();
235      return;
236    }
237
238    uint64 max_age = begin_time_.is_null() ?
239        std::numeric_limits<uint64>::max() :
240        (base::Time::Now() - begin_time_).InSeconds();
241
242    IPC::Message* msg;
243    if (is_ppapi) {
244      msg = CreatePpapiClearSiteDataMsg(max_age);
245    } else {
246      msg = new PluginProcessMsg_ClearSiteData(
247          std::string(), kClearAllData, max_age);
248    }
249    if (!channel_->Send(msg)) {
250      NOTREACHED() << "Couldn't send ClearSiteData message";
251      SignalDone();
252      return;
253    }
254  }
255
256  // Handles the PpapiHostMsg_ClearSiteDataResult message by delegating to the
257  // PluginProcessHostMsg_ClearSiteDataResult handler.
258  void OnPpapiClearSiteDataResult(uint32 request_id, bool success) {
259    DCHECK_EQ(0u, request_id);
260    OnClearSiteDataResult(success);
261  }
262
263  // Handles the PluginProcessHostMsg_ClearSiteDataResult message.
264  void OnClearSiteDataResult(bool success) {
265    LOG_IF(ERROR, !success) << "ClearSiteData returned error";
266    UMA_HISTOGRAM_TIMES("ClearPluginData.time",
267                        base::Time::Now() - remove_start_time_);
268    SignalDone();
269  }
270
271  // Signals that we are finished with removing data (successful or not). This
272  // method is safe to call multiple times.
273  void SignalDone() {
274    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
275    if (!is_removing_)
276      return;
277    is_removing_ = false;
278    event_->Signal();
279  }
280
281  scoped_ptr<base::WaitableEvent> event_;
282  // The point in time when we start removing data.
283  base::Time remove_start_time_;
284  // The point in time from which on we remove data.
285  base::Time begin_time_;
286  bool is_removing_;
287
288  // Path for the current profile. Must be retrieved on the UI thread from the
289  // browser context when we start so we can use it later on the I/O thread.
290  base::FilePath browser_context_path_;
291
292  // The resource context for the profile. Use only on the I/O thread.
293  ResourceContext* resource_context_;
294
295  // The name of the plugin. Use only on the I/O thread.
296  std::string plugin_name_;
297
298  // The channel is NULL until we have opened a connection to the plug-in
299  // process.
300  scoped_ptr<IPC::Channel> channel_;
301};
302
303
304PluginDataRemoverImpl::PluginDataRemoverImpl(BrowserContext* browser_context)
305    : mime_type_(kFlashPluginSwfMimeType),
306      browser_context_(browser_context) {
307}
308
309PluginDataRemoverImpl::~PluginDataRemoverImpl() {
310}
311
312base::WaitableEvent* PluginDataRemoverImpl::StartRemoving(
313    base::Time begin_time) {
314  DCHECK(!context_.get());
315  context_ = new Context(begin_time, browser_context_);
316  context_->Init(mime_type_);
317  return context_->event();
318}
319
320}  // namespace content
321