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 "extensions/browser/content_verifier.h" 6 7 #include <algorithm> 8 9 #include "base/files/file_path.h" 10 #include "base/stl_util.h" 11 #include "base/strings/string_util.h" 12 #include "content/public/browser/browser_thread.h" 13 #include "extensions/browser/content_hash_fetcher.h" 14 #include "extensions/browser/content_hash_reader.h" 15 #include "extensions/browser/content_verifier_delegate.h" 16 #include "extensions/browser/content_verifier_io_data.h" 17 #include "extensions/browser/extension_registry.h" 18 #include "extensions/common/constants.h" 19 #include "extensions/common/extension_l10n_util.h" 20 21 namespace extensions { 22 23 ContentVerifier::ContentVerifier(content::BrowserContext* context, 24 ContentVerifierDelegate* delegate) 25 : shutdown_(false), 26 context_(context), 27 delegate_(delegate), 28 fetcher_(new ContentHashFetcher( 29 context, 30 delegate, 31 base::Bind(&ContentVerifier::OnFetchComplete, this))), 32 observer_(this), 33 io_data_(new ContentVerifierIOData) { 34 } 35 36 ContentVerifier::~ContentVerifier() { 37 } 38 39 void ContentVerifier::Start() { 40 ExtensionRegistry* registry = ExtensionRegistry::Get(context_); 41 observer_.Add(registry); 42 } 43 44 void ContentVerifier::Shutdown() { 45 shutdown_ = true; 46 content::BrowserThread::PostTask( 47 content::BrowserThread::IO, 48 FROM_HERE, 49 base::Bind(&ContentVerifierIOData::Clear, io_data_)); 50 observer_.RemoveAll(); 51 fetcher_.reset(); 52 } 53 54 ContentVerifyJob* ContentVerifier::CreateJobFor( 55 const std::string& extension_id, 56 const base::FilePath& extension_root, 57 const base::FilePath& relative_path) { 58 DCHECK_CURRENTLY_ON(content::BrowserThread::IO); 59 60 const ContentVerifierIOData::ExtensionData* data = 61 io_data_->GetData(extension_id); 62 if (!data) 63 return NULL; 64 65 std::set<base::FilePath> paths; 66 paths.insert(relative_path); 67 if (!ShouldVerifyAnyPaths(extension_id, extension_root, paths)) 68 return NULL; 69 70 // TODO(asargent) - we can probably get some good performance wins by having 71 // a cache of ContentHashReader's that we hold onto past the end of each job. 72 return new ContentVerifyJob( 73 new ContentHashReader(extension_id, 74 data->version, 75 extension_root, 76 relative_path, 77 delegate_->PublicKey()), 78 base::Bind(&ContentVerifier::VerifyFailed, this, extension_id)); 79 } 80 81 void ContentVerifier::VerifyFailed(const std::string& extension_id, 82 ContentVerifyJob::FailureReason reason) { 83 if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) { 84 content::BrowserThread::PostTask( 85 content::BrowserThread::UI, 86 FROM_HERE, 87 base::Bind(&ContentVerifier::VerifyFailed, this, extension_id, reason)); 88 return; 89 } 90 if (shutdown_) 91 return; 92 93 VLOG(1) << "VerifyFailed " << extension_id << " reason:" << reason; 94 95 ExtensionRegistry* registry = ExtensionRegistry::Get(context_); 96 const Extension* extension = 97 registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING); 98 99 if (!extension) 100 return; 101 102 if (reason == ContentVerifyJob::MISSING_ALL_HASHES) { 103 // If we failed because there were no hashes yet for this extension, just 104 // request some. 105 fetcher_->DoFetch(extension, true /* force */); 106 } else { 107 delegate_->VerifyFailed(extension_id, reason); 108 } 109 } 110 111 static base::FilePath MakeImagePathRelative(const base::FilePath& path) { 112 if (path.ReferencesParent()) 113 return base::FilePath(); 114 115 std::vector<base::FilePath::StringType> parts; 116 path.GetComponents(&parts); 117 if (parts.empty()) 118 return base::FilePath(); 119 120 // Remove the first component if it is '.' or '/' or '//'. 121 const base::FilePath::StringType separators( 122 base::FilePath::kSeparators, base::FilePath::kSeparatorsLength); 123 if (!parts[0].empty() && 124 (parts[0] == base::FilePath::kCurrentDirectory || 125 parts[0].find_first_not_of(separators) == std::string::npos)) 126 parts.erase(parts.begin()); 127 128 // Note that elsewhere we always normalize path separators to '/' so this 129 // should work for all platforms. 130 return base::FilePath(JoinString(parts, '/')); 131 } 132 133 void ContentVerifier::OnExtensionLoaded( 134 content::BrowserContext* browser_context, 135 const Extension* extension) { 136 if (shutdown_) 137 return; 138 139 ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension); 140 if (mode != ContentVerifierDelegate::NONE) { 141 // The browser image paths from the extension may not be relative (eg 142 // they might have leading '/' or './'), so we strip those to make 143 // comparing to actual relative paths work later on. 144 std::set<base::FilePath> original_image_paths = 145 delegate_->GetBrowserImagePaths(extension); 146 147 scoped_ptr<std::set<base::FilePath>> image_paths( 148 new std::set<base::FilePath>); 149 for (const auto& path : original_image_paths) { 150 image_paths->insert(MakeImagePathRelative(path)); 151 } 152 153 scoped_ptr<ContentVerifierIOData::ExtensionData> data( 154 new ContentVerifierIOData::ExtensionData( 155 image_paths.Pass(), 156 extension->version() ? *extension->version() : base::Version())); 157 content::BrowserThread::PostTask(content::BrowserThread::IO, 158 FROM_HERE, 159 base::Bind(&ContentVerifierIOData::AddData, 160 io_data_, 161 extension->id(), 162 base::Passed(&data))); 163 fetcher_->ExtensionLoaded(extension); 164 } 165 } 166 167 void ContentVerifier::OnExtensionUnloaded( 168 content::BrowserContext* browser_context, 169 const Extension* extension, 170 UnloadedExtensionInfo::Reason reason) { 171 if (shutdown_) 172 return; 173 content::BrowserThread::PostTask( 174 content::BrowserThread::IO, 175 FROM_HERE, 176 base::Bind( 177 &ContentVerifierIOData::RemoveData, io_data_, extension->id())); 178 if (fetcher_) 179 fetcher_->ExtensionUnloaded(extension); 180 } 181 182 void ContentVerifier::OnFetchCompleteHelper(const std::string& extension_id, 183 bool shouldVerifyAnyPathsResult) { 184 if (shouldVerifyAnyPathsResult) 185 delegate_->VerifyFailed(extension_id, ContentVerifyJob::MISSING_ALL_HASHES); 186 } 187 188 void ContentVerifier::OnFetchComplete( 189 const std::string& extension_id, 190 bool success, 191 bool was_force_check, 192 const std::set<base::FilePath>& hash_mismatch_paths) { 193 if (shutdown_) 194 return; 195 196 VLOG(1) << "OnFetchComplete " << extension_id << " success:" << success; 197 198 ExtensionRegistry* registry = ExtensionRegistry::Get(context_); 199 const Extension* extension = 200 registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING); 201 if (!delegate_ || !extension) 202 return; 203 204 ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension); 205 if (was_force_check && !success && 206 mode == ContentVerifierDelegate::ENFORCE_STRICT) { 207 // We weren't able to get verified_contents.json or weren't able to compute 208 // hashes. 209 delegate_->VerifyFailed(extension_id, ContentVerifyJob::MISSING_ALL_HASHES); 210 } else { 211 content::BrowserThread::PostTaskAndReplyWithResult( 212 content::BrowserThread::IO, 213 FROM_HERE, 214 base::Bind(&ContentVerifier::ShouldVerifyAnyPaths, 215 this, 216 extension_id, 217 extension->path(), 218 hash_mismatch_paths), 219 base::Bind( 220 &ContentVerifier::OnFetchCompleteHelper, this, extension_id)); 221 } 222 } 223 224 bool ContentVerifier::ShouldVerifyAnyPaths( 225 const std::string& extension_id, 226 const base::FilePath& extension_root, 227 const std::set<base::FilePath>& relative_paths) { 228 DCHECK_CURRENTLY_ON(content::BrowserThread::IO); 229 const ContentVerifierIOData::ExtensionData* data = 230 io_data_->GetData(extension_id); 231 if (!data) 232 return false; 233 234 const std::set<base::FilePath>& browser_images = *(data->browser_image_paths); 235 236 base::FilePath locales_dir = extension_root.Append(kLocaleFolder); 237 scoped_ptr<std::set<std::string> > all_locales; 238 239 for (std::set<base::FilePath>::const_iterator i = relative_paths.begin(); 240 i != relative_paths.end(); 241 ++i) { 242 const base::FilePath& relative_path = *i; 243 244 if (relative_path == base::FilePath(kManifestFilename)) 245 continue; 246 247 if (ContainsKey(browser_images, relative_path)) 248 continue; 249 250 base::FilePath full_path = extension_root.Append(relative_path); 251 if (locales_dir.IsParent(full_path)) { 252 if (!all_locales) { 253 // TODO(asargent) - see if we can cache this list longer to avoid 254 // having to fetch it more than once for a given run of the 255 // browser. Maybe it can never change at runtime? (Or if it can, maybe 256 // there is an event we can listen for to know to drop our cache). 257 all_locales.reset(new std::set<std::string>); 258 extension_l10n_util::GetAllLocales(all_locales.get()); 259 } 260 261 // Since message catalogs get transcoded during installation, we want 262 // to skip those paths. 263 if (full_path.DirName().DirName() == locales_dir && 264 !extension_l10n_util::ShouldSkipValidation( 265 locales_dir, full_path.DirName(), *all_locales)) 266 continue; 267 } 268 return true; 269 } 270 return false; 271 } 272 273 } // namespace extensions 274