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