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 26 namespace content { 27 28 namespace { 29 30 // The minimum Flash Player version that implements NPP_ClearSiteData. 31 const char kMinFlashVersion[] = "10.3"; 32 const int64 kRemovalTimeoutMs = 10000; 33 const uint64 kClearAllData = 0; 34 35 } // namespace 36 37 // static 38 PluginDataRemover* PluginDataRemover::Create(BrowserContext* browser_context) { 39 return new PluginDataRemoverImpl(browser_context); 40 } 41 42 // static 43 void 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 59 class 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(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_.reset(new IPC::Channel(handle, IPC::Channel::MODE_CLIENT, 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 302 PluginDataRemoverImpl::PluginDataRemoverImpl(BrowserContext* browser_context) 303 : mime_type_(kFlashPluginSwfMimeType), 304 browser_context_(browser_context) { 305 } 306 307 PluginDataRemoverImpl::~PluginDataRemoverImpl() { 308 } 309 310 base::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