1 // Copyright (c) 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/browser_plugin/browser_plugin_guest_manager.h" 6 7 #include "content/browser/browser_plugin/browser_plugin_guest.h" 8 #include "content/browser/browser_plugin/browser_plugin_host_factory.h" 9 #include "content/browser/renderer_host/render_view_host_impl.h" 10 #include "content/browser/web_contents/web_contents_impl.h" 11 #include "content/common/browser_plugin/browser_plugin_constants.h" 12 #include "content/common/browser_plugin/browser_plugin_messages.h" 13 #include "content/common/content_export.h" 14 #include "content/public/browser/user_metrics.h" 15 #include "content/public/common/result_codes.h" 16 #include "content/public/common/url_constants.h" 17 #include "content/public/common/url_utils.h" 18 #include "net/base/escape.h" 19 20 namespace content { 21 22 // static 23 BrowserPluginHostFactory* BrowserPluginGuestManager::factory_ = NULL; 24 25 BrowserPluginGuestManager::BrowserPluginGuestManager() 26 : next_instance_id_(browser_plugin::kInstanceIDNone) { 27 } 28 29 BrowserPluginGuestManager::~BrowserPluginGuestManager() { 30 } 31 32 // static 33 BrowserPluginGuestManager* BrowserPluginGuestManager::Create() { 34 if (factory_) 35 return factory_->CreateBrowserPluginGuestManager(); 36 return new BrowserPluginGuestManager(); 37 } 38 39 BrowserPluginGuest* BrowserPluginGuestManager::CreateGuest( 40 SiteInstance* embedder_site_instance, 41 int instance_id, 42 const BrowserPluginHostMsg_Attach_Params& params, 43 scoped_ptr<base::DictionaryValue> extra_params) { 44 RenderProcessHost* embedder_process_host = 45 embedder_site_instance->GetProcess(); 46 // Validate that the partition id coming from the renderer is valid UTF-8, 47 // since we depend on this in other parts of the code, such as FilePath 48 // creation. If the validation fails, treat it as a bad message and kill the 49 // renderer process. 50 if (!IsStringUTF8(params.storage_partition_id)) { 51 content::RecordAction(UserMetricsAction("BadMessageTerminate_BPGM")); 52 base::KillProcess( 53 embedder_process_host->GetHandle(), 54 content::RESULT_CODE_KILLED_BAD_MESSAGE, false); 55 return NULL; 56 } 57 58 // We usually require BrowserPlugins to be hosted by a storage isolated 59 // extension. We treat WebUI pages as a special case if they host the 60 // BrowserPlugin in a component extension iframe. In that case, we use the 61 // iframe's URL to determine the extension. 62 const GURL& embedder_site_url = embedder_site_instance->GetSiteURL(); 63 GURL validated_frame_url(params.embedder_frame_url); 64 RenderViewHost::FilterURL( 65 embedder_process_host, false, &validated_frame_url); 66 const std::string& host = content::HasWebUIScheme(embedder_site_url) ? 67 validated_frame_url.host() : embedder_site_url.host(); 68 69 std::string url_encoded_partition = net::EscapeQueryParamValue( 70 params.storage_partition_id, false); 71 // The SiteInstance of a given webview tag is based on the fact that it's 72 // a guest process in addition to which platform application the tag 73 // belongs to and what storage partition is in use, rather than the URL 74 // that the tag is being navigated to. 75 GURL guest_site(base::StringPrintf("%s://%s/%s?%s", 76 kGuestScheme, 77 host.c_str(), 78 params.persist_storage ? "persist" : "", 79 url_encoded_partition.c_str())); 80 81 // If we already have a webview tag in the same app using the same storage 82 // partition, we should use the same SiteInstance so the existing tag and 83 // the new tag can script each other. 84 SiteInstance* guest_site_instance = GetGuestSiteInstance(guest_site); 85 if (!guest_site_instance) { 86 // Create the SiteInstance in a new BrowsingInstance, which will ensure 87 // that webview tags are also not allowed to send messages across 88 // different partitions. 89 guest_site_instance = SiteInstance::CreateForURL( 90 embedder_site_instance->GetBrowserContext(), guest_site); 91 } 92 93 return WebContentsImpl::CreateGuest( 94 embedder_site_instance->GetBrowserContext(), 95 guest_site_instance, 96 instance_id, 97 extra_params.Pass()); 98 } 99 100 BrowserPluginGuest* BrowserPluginGuestManager::GetGuestByInstanceID( 101 int instance_id, 102 int embedder_render_process_id) const { 103 if (!CanEmbedderAccessInstanceIDMaybeKill(embedder_render_process_id, 104 instance_id)) { 105 return NULL; 106 } 107 GuestInstanceMap::const_iterator it = 108 guest_web_contents_by_instance_id_.find(instance_id); 109 if (it == guest_web_contents_by_instance_id_.end()) 110 return NULL; 111 return static_cast<WebContentsImpl*>(it->second)->GetBrowserPluginGuest(); 112 } 113 114 void BrowserPluginGuestManager::AddGuest(int instance_id, 115 WebContentsImpl* guest_web_contents) { 116 DCHECK(guest_web_contents_by_instance_id_.find(instance_id) == 117 guest_web_contents_by_instance_id_.end()); 118 guest_web_contents_by_instance_id_[instance_id] = guest_web_contents; 119 } 120 121 void BrowserPluginGuestManager::RemoveGuest(int instance_id) { 122 DCHECK(guest_web_contents_by_instance_id_.find(instance_id) != 123 guest_web_contents_by_instance_id_.end()); 124 guest_web_contents_by_instance_id_.erase(instance_id); 125 } 126 127 bool BrowserPluginGuestManager::CanEmbedderAccessInstanceIDMaybeKill( 128 int embedder_render_process_id, 129 int instance_id) const { 130 if (!CanEmbedderAccessInstanceID(embedder_render_process_id, instance_id)) { 131 // The embedder process is trying to access a guest it does not own. 132 content::RecordAction(UserMetricsAction("BadMessageTerminate_BPGM")); 133 base::KillProcess( 134 RenderProcessHost::FromID(embedder_render_process_id)->GetHandle(), 135 content::RESULT_CODE_KILLED_BAD_MESSAGE, false); 136 return false; 137 } 138 return true; 139 } 140 141 void BrowserPluginGuestManager::OnMessageReceived(const IPC::Message& message, 142 int render_process_id) { 143 if (BrowserPluginGuest::ShouldForwardToBrowserPluginGuest(message)) { 144 int instance_id = 0; 145 // All allowed messages must have instance_id as their first parameter. 146 PickleIterator iter(message); 147 bool success = iter.ReadInt(&instance_id); 148 DCHECK(success); 149 BrowserPluginGuest* guest = 150 GetGuestByInstanceID(instance_id, render_process_id); 151 if (guest && guest->OnMessageReceivedFromEmbedder(message)) 152 return; 153 } 154 IPC_BEGIN_MESSAGE_MAP(BrowserPluginGuestManager, message) 155 IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_BuffersSwappedACK, 156 OnUnhandledSwapBuffersACK) 157 IPC_END_MESSAGE_MAP() 158 } 159 160 // static 161 bool BrowserPluginGuestManager::CanEmbedderAccessGuest( 162 int embedder_render_process_id, 163 BrowserPluginGuest* guest) { 164 // The embedder can access the guest if it has not been attached and its 165 // opener's embedder lives in the same process as the given embedder. 166 if (!guest->attached()) { 167 if (!guest->opener()) 168 return false; 169 170 return embedder_render_process_id == 171 guest->opener()->embedder_web_contents()->GetRenderProcessHost()-> 172 GetID(); 173 } 174 175 return embedder_render_process_id == 176 guest->embedder_web_contents()->GetRenderProcessHost()->GetID(); 177 } 178 179 bool BrowserPluginGuestManager::CanEmbedderAccessInstanceID( 180 int embedder_render_process_id, 181 int instance_id) const { 182 // The embedder is trying to access a guest with a negative or zero 183 // instance ID. 184 if (instance_id <= browser_plugin::kInstanceIDNone) 185 return false; 186 187 // The embedder is trying to access an instance ID that has not yet been 188 // allocated by BrowserPluginGuestManager. This could cause instance ID 189 // collisions in the future, and potentially give one embedder access to a 190 // guest it does not own. 191 if (instance_id > next_instance_id_) 192 return false; 193 194 GuestInstanceMap::const_iterator it = 195 guest_web_contents_by_instance_id_.find(instance_id); 196 if (it == guest_web_contents_by_instance_id_.end()) 197 return true; 198 BrowserPluginGuest* guest = 199 static_cast<WebContentsImpl*>(it->second)->GetBrowserPluginGuest(); 200 201 return CanEmbedderAccessGuest(embedder_render_process_id, guest); 202 } 203 204 SiteInstance* BrowserPluginGuestManager::GetGuestSiteInstance( 205 const GURL& guest_site) { 206 for (GuestInstanceMap::const_iterator it = 207 guest_web_contents_by_instance_id_.begin(); 208 it != guest_web_contents_by_instance_id_.end(); ++it) { 209 if (it->second->GetSiteInstance()->GetSiteURL() == guest_site) 210 return it->second->GetSiteInstance(); 211 } 212 return NULL; 213 } 214 215 // We only get here during teardown if we have one last buffer pending, 216 // otherwise the ACK is handled by the guest. 217 void BrowserPluginGuestManager::OnUnhandledSwapBuffersACK( 218 int instance_id, 219 int route_id, 220 int gpu_host_id, 221 const std::string& mailbox_name, 222 uint32 sync_point) { 223 BrowserPluginGuest::AcknowledgeBufferPresent(route_id, 224 gpu_host_id, 225 mailbox_name, 226 sync_point); 227 } 228 229 bool BrowserPluginGuestManager::ForEachGuest( 230 WebContentsImpl* embedder_web_contents, const GuestCallback& callback) { 231 for (GuestInstanceMap::iterator it = 232 guest_web_contents_by_instance_id_.begin(); 233 it != guest_web_contents_by_instance_id_.end(); ++it) { 234 BrowserPluginGuest* guest = it->second->GetBrowserPluginGuest(); 235 if (embedder_web_contents != guest->embedder_web_contents()) 236 continue; 237 238 if (callback.Run(guest)) 239 return true; 240 } 241 return false; 242 } 243 244 } // namespace content 245