1 // Copyright 2014 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 "chrome/browser/guest_view/guest_view_manager.h" 6 7 #include "base/strings/stringprintf.h" 8 #include "chrome/browser/extensions/extension_service.h" 9 #include "chrome/browser/guest_view/guest_view_base.h" 10 #include "chrome/browser/guest_view/guest_view_constants.h" 11 #include "chrome/browser/guest_view/guest_view_manager_factory.h" 12 #include "chrome/browser/guest_view/web_view/web_view_guest.h" 13 #include "chrome/browser/profiles/profile.h" 14 #include "content/public/browser/browser_context.h" 15 #include "content/public/browser/render_process_host.h" 16 #include "content/public/browser/user_metrics.h" 17 #include "content/public/browser/web_contents_observer.h" 18 #include "content/public/common/result_codes.h" 19 #include "content/public/common/url_constants.h" 20 #include "extensions/browser/extension_system.h" 21 #include "net/base/escape.h" 22 #include "url/gurl.h" 23 24 using content::BrowserContext; 25 using content::SiteInstance; 26 using content::WebContents; 27 28 // static 29 GuestViewManagerFactory* GuestViewManager::factory_ = NULL; 30 31 GuestViewManager::GuestViewManager(content::BrowserContext* context) 32 : current_instance_id_(0), last_instance_id_removed_(0), context_(context) { 33 } 34 35 GuestViewManager::~GuestViewManager() {} 36 37 // static. 38 GuestViewManager* GuestViewManager::FromBrowserContext( 39 BrowserContext* context) { 40 GuestViewManager* guest_manager = 41 static_cast<GuestViewManager*>(context->GetUserData( 42 guestview::kGuestViewManagerKeyName)); 43 if (!guest_manager) { 44 if (factory_) { 45 guest_manager = factory_->CreateGuestViewManager(context); 46 } else { 47 guest_manager = new GuestViewManager(context); 48 } 49 context->SetUserData(guestview::kGuestViewManagerKeyName, guest_manager); 50 } 51 return guest_manager; 52 } 53 54 content::WebContents* GuestViewManager::GetGuestByInstanceIDSafely( 55 int guest_instance_id, 56 int embedder_render_process_id) { 57 if (!CanEmbedderAccessInstanceIDMaybeKill(embedder_render_process_id, 58 guest_instance_id)) { 59 return NULL; 60 } 61 return GetGuestByInstanceID(guest_instance_id, embedder_render_process_id); 62 } 63 64 int GuestViewManager::GetNextInstanceID() { 65 return ++current_instance_id_; 66 } 67 68 content::WebContents* GuestViewManager::CreateGuest( 69 content::SiteInstance* embedder_site_instance, 70 int instance_id, 71 scoped_ptr<base::DictionaryValue> extra_params) { 72 std::string storage_partition_id; 73 bool persist_storage = false; 74 std::string storage_partition_string; 75 WebViewGuest::ParsePartitionParam( 76 extra_params.get(), &storage_partition_id, &persist_storage); 77 78 content::RenderProcessHost* embedder_process_host = 79 embedder_site_instance->GetProcess(); 80 // Validate that the partition id coming from the renderer is valid UTF-8, 81 // since we depend on this in other parts of the code, such as FilePath 82 // creation. If the validation fails, treat it as a bad message and kill the 83 // renderer process. 84 if (!base::IsStringUTF8(storage_partition_id)) { 85 content::RecordAction( 86 base::UserMetricsAction("BadMessageTerminate_BPGM")); 87 base::KillProcess( 88 embedder_process_host->GetHandle(), 89 content::RESULT_CODE_KILLED_BAD_MESSAGE, false); 90 return NULL; 91 } 92 93 const GURL& embedder_site_url = embedder_site_instance->GetSiteURL(); 94 const std::string& host = embedder_site_url.host(); 95 96 std::string url_encoded_partition = net::EscapeQueryParamValue( 97 storage_partition_id, false); 98 // The SiteInstance of a given webview tag is based on the fact that it's 99 // a guest process in addition to which platform application the tag 100 // belongs to and what storage partition is in use, rather than the URL 101 // that the tag is being navigated to. 102 GURL guest_site(base::StringPrintf("%s://%s/%s?%s", 103 content::kGuestScheme, 104 host.c_str(), 105 persist_storage ? "persist" : "", 106 url_encoded_partition.c_str())); 107 108 // If we already have a webview tag in the same app using the same storage 109 // partition, we should use the same SiteInstance so the existing tag and 110 // the new tag can script each other. 111 SiteInstance* guest_site_instance = GetGuestSiteInstance(guest_site); 112 if (!guest_site_instance) { 113 // Create the SiteInstance in a new BrowsingInstance, which will ensure 114 // that webview tags are also not allowed to send messages across 115 // different partitions. 116 guest_site_instance = SiteInstance::CreateForURL( 117 embedder_site_instance->GetBrowserContext(), guest_site); 118 } 119 WebContents::CreateParams create_params( 120 embedder_site_instance->GetBrowserContext(), 121 guest_site_instance); 122 create_params.guest_instance_id = instance_id; 123 create_params.guest_extra_params.reset(extra_params.release()); 124 return WebContents::Create(create_params); 125 } 126 127 void GuestViewManager::MaybeGetGuestByInstanceIDOrKill( 128 int guest_instance_id, 129 int embedder_render_process_id, 130 const GuestByInstanceIDCallback& callback) { 131 if (!CanEmbedderAccessInstanceIDMaybeKill(embedder_render_process_id, 132 guest_instance_id)) { 133 // If we kill the embedder, then don't bother calling back. 134 return; 135 } 136 content::WebContents* guest_web_contents = 137 GetGuestByInstanceID(guest_instance_id, embedder_render_process_id); 138 callback.Run(guest_web_contents); 139 } 140 141 SiteInstance* GuestViewManager::GetGuestSiteInstance( 142 const GURL& guest_site) { 143 for (GuestInstanceMap::const_iterator it = 144 guest_web_contents_by_instance_id_.begin(); 145 it != guest_web_contents_by_instance_id_.end(); ++it) { 146 if (it->second->GetSiteInstance()->GetSiteURL() == guest_site) 147 return it->second->GetSiteInstance(); 148 } 149 return NULL; 150 } 151 152 bool GuestViewManager::ForEachGuest(WebContents* embedder_web_contents, 153 const GuestCallback& callback) { 154 for (GuestInstanceMap::iterator it = 155 guest_web_contents_by_instance_id_.begin(); 156 it != guest_web_contents_by_instance_id_.end(); ++it) { 157 WebContents* guest = it->second; 158 GuestViewBase* guest_view = GuestViewBase::FromWebContents(guest); 159 if (embedder_web_contents != guest_view->embedder_web_contents()) 160 continue; 161 162 if (callback.Run(guest)) 163 return true; 164 } 165 return false; 166 } 167 168 void GuestViewManager::AddGuest(int guest_instance_id, 169 WebContents* guest_web_contents) { 170 CHECK(!ContainsKey(guest_web_contents_by_instance_id_, guest_instance_id)); 171 CHECK(CanUseGuestInstanceID(guest_instance_id)); 172 guest_web_contents_by_instance_id_[guest_instance_id] = guest_web_contents; 173 } 174 175 void GuestViewManager::RemoveGuest(int guest_instance_id) { 176 GuestInstanceMap::iterator it = 177 guest_web_contents_by_instance_id_.find(guest_instance_id); 178 DCHECK(it != guest_web_contents_by_instance_id_.end()); 179 guest_web_contents_by_instance_id_.erase(it); 180 181 // All the instance IDs that lie within [0, last_instance_id_removed_] 182 // are invalid. 183 // The remaining sparse invalid IDs are kept in |removed_instance_ids_| set. 184 // The following code compacts the set by incrementing 185 // |last_instance_id_removed_|. 186 if (guest_instance_id == last_instance_id_removed_ + 1) { 187 ++last_instance_id_removed_; 188 // Compact. 189 std::set<int>::iterator iter = removed_instance_ids_.begin(); 190 while (iter != removed_instance_ids_.end()) { 191 int instance_id = *iter; 192 // The sparse invalid IDs must not lie within 193 // [0, last_instance_id_removed_] 194 DCHECK(instance_id > last_instance_id_removed_); 195 if (instance_id != last_instance_id_removed_ + 1) 196 break; 197 ++last_instance_id_removed_; 198 removed_instance_ids_.erase(iter++); 199 } 200 } else { 201 removed_instance_ids_.insert(guest_instance_id); 202 } 203 } 204 205 content::WebContents* GuestViewManager::GetGuestByInstanceID( 206 int guest_instance_id, 207 int embedder_render_process_id) { 208 GuestInstanceMap::const_iterator it = 209 guest_web_contents_by_instance_id_.find(guest_instance_id); 210 if (it == guest_web_contents_by_instance_id_.end()) 211 return NULL; 212 return it->second; 213 } 214 215 bool GuestViewManager::CanEmbedderAccessInstanceIDMaybeKill( 216 int embedder_render_process_id, 217 int guest_instance_id) { 218 if (!CanEmbedderAccessInstanceID(embedder_render_process_id, 219 guest_instance_id)) { 220 // The embedder process is trying to access a guest it does not own. 221 content::RecordAction( 222 base::UserMetricsAction("BadMessageTerminate_BPGM")); 223 base::KillProcess( 224 content::RenderProcessHost::FromID(embedder_render_process_id)-> 225 GetHandle(), 226 content::RESULT_CODE_KILLED_BAD_MESSAGE, false); 227 return false; 228 } 229 return true; 230 } 231 232 bool GuestViewManager::CanUseGuestInstanceID(int guest_instance_id) { 233 if (guest_instance_id <= last_instance_id_removed_) 234 return false; 235 return !ContainsKey(removed_instance_ids_, guest_instance_id); 236 } 237 238 bool GuestViewManager::CanEmbedderAccessInstanceID( 239 int embedder_render_process_id, 240 int guest_instance_id) { 241 // The embedder is trying to access a guest with a negative or zero 242 // instance ID. 243 if (guest_instance_id <= guestview::kInstanceIDNone) 244 return false; 245 246 // The embedder is trying to access an instance ID that has not yet been 247 // allocated by GuestViewManager. This could cause instance ID 248 // collisions in the future, and potentially give one embedder access to a 249 // guest it does not own. 250 if (guest_instance_id > current_instance_id_) 251 return false; 252 253 GuestInstanceMap::const_iterator it = 254 guest_web_contents_by_instance_id_.find(guest_instance_id); 255 if (it == guest_web_contents_by_instance_id_.end()) 256 return true; 257 258 GuestViewBase* guest_view = GuestViewBase::FromWebContents(it->second); 259 if (!guest_view) 260 return false; 261 262 return CanEmbedderAccessGuest(embedder_render_process_id, guest_view); 263 } 264 265 bool GuestViewManager::CanEmbedderAccessGuest(int embedder_render_process_id, 266 GuestViewBase* guest) { 267 // The embedder can access the guest if it has not been attached and its 268 // opener's embedder lives in the same process as the given embedder. 269 if (!guest->attached()) { 270 if (!guest->GetOpener()) 271 return false; 272 273 return embedder_render_process_id == 274 guest->GetOpener()->embedder_web_contents()->GetRenderProcessHost()-> 275 GetID(); 276 } 277 278 return embedder_render_process_id == 279 guest->embedder_web_contents()->GetRenderProcessHost()->GetID(); 280 } 281