// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/appcache/appcache_internals_ui.h" #include #include "base/bind.h" #include "base/logging.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/platform_thread.h" #include "base/values.h" #include "content/browser/appcache/appcache.h" #include "content/browser/appcache/appcache_response.h" #include "content/browser/storage_partition_impl.h" #include "content/grit/content_resources.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/web_ui.h" #include "content/public/browser/web_ui_data_source.h" #include "content/public/common/url_constants.h" #include "net/base/escape.h" #include "net/http/http_response_headers.h" #include "net/url_request/view_cache_helper.h" namespace content { namespace { const char kRequestGetAllAppCacheInfo[] = "getAllAppCache"; const char kRequestDeleteAppCache[] = "deleteAppCache"; const char kRequestGetAppCacheDetails[] = "getAppCacheDetails"; const char kRequestGetFileDetails[] = "getFileDetails"; const char kFunctionOnAllAppCacheInfoReady[] = "appcache.onAllAppCacheInfoReady"; const char kFunctionOnAppCacheInfoDeleted[] = "appcache.onAppCacheInfoDeleted"; const char kFunctionOnAppCacheDetailsReady[] = "appcache.onAppCacheDetailsReady"; const char kFunctionOnFileDetailsReady[] = "appcache.onFileDetailsReady"; const char kFunctionOnFileDetailsFailed[] = "appcache.onFileDetailsFailed"; int64_t ToInt64(const std::string& str) { int64_t i = 0; base::StringToInt64(str.c_str(), &i); return i; } bool SortByResourceUrl(const AppCacheResourceInfo& lhs, const AppCacheResourceInfo& rhs) { return lhs.url.spec() < rhs.url.spec(); } std::unique_ptr GetDictionaryValueForResponseEnquiry( const content::AppCacheInternalsUI::Proxy::ResponseEnquiry& response_enquiry) { std::unique_ptr dict_value( new base::DictionaryValue()); dict_value->SetString("manifestURL", response_enquiry.manifest_url); dict_value->SetString("groupId", base::Int64ToString(response_enquiry.group_id)); dict_value->SetString("responseId", base::Int64ToString(response_enquiry.response_id)); return dict_value; } std::unique_ptr GetDictionaryValueForAppCacheInfo( const content::AppCacheInfo& appcache_info) { std::unique_ptr dict_value( new base::DictionaryValue()); dict_value->SetString("manifestURL", appcache_info.manifest_url.spec()); dict_value->SetDouble("creationTime", appcache_info.creation_time.ToJsTime()); dict_value->SetDouble("lastUpdateTime", appcache_info.last_update_time.ToJsTime()); dict_value->SetDouble("lastAccessTime", appcache_info.last_access_time.ToJsTime()); dict_value->SetString( "size", base::UTF16ToUTF8(base::FormatBytesUnlocalized(appcache_info.size))); dict_value->SetString("groupId", base::Int64ToString(appcache_info.group_id)); return dict_value; } std::unique_ptr GetListValueForAppCacheInfoVector( const AppCacheInfoVector& appcache_info_vector) { std::unique_ptr list(new base::ListValue()); for (const AppCacheInfo& info : appcache_info_vector) list->Append(GetDictionaryValueForAppCacheInfo(info)); return list; } std::unique_ptr GetListValueFromAppCacheInfoCollection( AppCacheInfoCollection* appcache_collection) { std::unique_ptr list(new base::ListValue()); for (const auto& key_value : appcache_collection->infos_by_origin) { base::DictionaryValue* dict = new base::DictionaryValue; // Use GURL::spec() to keep consistency with previous version dict->SetString("originURL", key_value.first.GetURL().spec()); dict->Set("manifests", GetListValueForAppCacheInfoVector(key_value.second)); list->Append(std::unique_ptr(dict)); } return list; } std::unique_ptr GetDictionaryValueForAppCacheResourceInfo( const AppCacheResourceInfo& resource_info) { std::unique_ptr dict(new base::DictionaryValue); dict->SetString("url", resource_info.url.spec()); dict->SetString( "size", base::UTF16ToUTF8(base::FormatBytesUnlocalized(resource_info.size))); dict->SetString("responseId", base::Int64ToString(resource_info.response_id)); dict->SetBoolean("isExplicit", resource_info.is_explicit); dict->SetBoolean("isManifest", resource_info.is_manifest); dict->SetBoolean("isMaster", resource_info.is_master); dict->SetBoolean("isFallback", resource_info.is_fallback); dict->SetBoolean("isIntercept", resource_info.is_intercept); dict->SetBoolean("isForeign", resource_info.is_foreign); return dict; } std::unique_ptr GetListValueForAppCacheResourceInfoVector( AppCacheResourceInfoVector* resource_info_vector) { std::unique_ptr list(new base::ListValue); for (const AppCacheResourceInfo& res_info : *resource_info_vector) list->Append(GetDictionaryValueForAppCacheResourceInfo(res_info)); return list; } } // namespace AppCacheInternalsUI::Proxy::Proxy( base::WeakPtr appcache_internals_ui, const base::FilePath& partition_path) : appcache_internals_ui_(appcache_internals_ui), partition_path_(partition_path) {} void AppCacheInternalsUI::Proxy::Initialize( const scoped_refptr& chrome_appcache_service) { if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::BindOnce(&Proxy::Initialize, this, chrome_appcache_service)); return; } appcache_service_ = chrome_appcache_service->AsWeakPtr(); shutdown_called_ = false; preparing_response_ = false; } AppCacheInternalsUI::Proxy::~Proxy() { DCHECK(shutdown_called_); } void AppCacheInternalsUI::Proxy::Shutdown() { if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::BindOnce(&Proxy::Shutdown, this)); return; } shutdown_called_ = true; if (appcache_service_) { appcache_service_->storage()->CancelDelegateCallbacks(this); appcache_service_.reset(); response_enquiries_.clear(); } } void AppCacheInternalsUI::Proxy::RequestAllAppCacheInfo() { if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::BindOnce(&Proxy::RequestAllAppCacheInfo, this)); return; } if (appcache_service_) { scoped_refptr collection( new AppCacheInfoCollection()); appcache_service_->GetAllAppCacheInfo( collection.get(), base::BindOnce(&Proxy::OnAllAppCacheInfoReady, this, collection)); } } void AppCacheInternalsUI::Proxy::OnAllAppCacheInfoReady( scoped_refptr collection, int net_result_code) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::BindOnce(&AppCacheInternalsUI::OnAllAppCacheInfoReady, appcache_internals_ui_, collection, partition_path_)); } void AppCacheInternalsUI::Proxy::DeleteAppCache( const std::string& manifest_url) { if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::BindOnce(&Proxy::DeleteAppCache, this, manifest_url)); return; } if (appcache_service_) { appcache_service_->DeleteAppCacheGroup( GURL(manifest_url), base::Bind(&Proxy::OnAppCacheInfoDeleted, this, manifest_url)); } } void AppCacheInternalsUI::Proxy::OnAppCacheInfoDeleted( const std::string& manifest_url, int net_result_code) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::BindOnce(&AppCacheInternalsUI::OnAppCacheInfoDeleted, appcache_internals_ui_, partition_path_, manifest_url, net_result_code == net::OK)); } void AppCacheInternalsUI::Proxy::RequestAppCacheDetails( const std::string& manifest_url) { if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::BindOnce(&Proxy::RequestAppCacheDetails, this, manifest_url)); return; } if (appcache_service_) appcache_service_->storage()->LoadOrCreateGroup(GURL(manifest_url), this); } void AppCacheInternalsUI::Proxy::OnGroupLoaded(AppCacheGroup* appcache_group, const GURL& manifest_gurl) { std::unique_ptr resource_info_vector; if (appcache_group && appcache_group->newest_complete_cache()) { resource_info_vector.reset(new AppCacheResourceInfoVector); appcache_group->newest_complete_cache()->ToResourceInfoVector( resource_info_vector.get()); std::sort(resource_info_vector->begin(), resource_info_vector->end(), SortByResourceUrl); } BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::BindOnce(&AppCacheInternalsUI::OnAppCacheDetailsReady, appcache_internals_ui_, partition_path_, manifest_gurl.spec(), std::move(resource_info_vector))); } void AppCacheInternalsUI::Proxy::RequestFileDetails( const ResponseEnquiry& response_enquiry) { if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::BindOnce(&Proxy::RequestFileDetails, this, response_enquiry)); return; } DCHECK(!shutdown_called_); response_enquiries_.push_back(response_enquiry); HandleFileDetailsRequest(); } void AppCacheInternalsUI::Proxy::HandleFileDetailsRequest() { if (preparing_response_ || response_enquiries_.empty() || !appcache_service_) return; preparing_response_ = true; appcache_service_->storage()->LoadResponseInfo( GURL(response_enquiries_.front().manifest_url), response_enquiries_.front().response_id, this); } void AppCacheInternalsUI::Proxy::OnResponseInfoLoaded( AppCacheResponseInfo* response, int64_t response_id) { if (shutdown_called_) return; if (!appcache_service_) return; ResponseEnquiry response_enquiry = response_enquiries_.front(); response_enquiries_.pop_front(); if (response) { scoped_refptr response_info = response; const int64_t kLimit = 100 * 1000; int64_t amount_to_read = std::min(kLimit, response_info->response_data_size()); scoped_refptr response_data( new net::IOBuffer(base::checked_cast(amount_to_read))); std::unique_ptr reader( appcache_service_->storage()->CreateResponseReader( GURL(response_enquiry.manifest_url), response_enquiry.response_id)); reader->ReadData(response_data.get(), amount_to_read, base::BindOnce(&Proxy::OnResponseDataReadComplete, this, response_enquiry, response_info, std::move(reader), response_data)); } else { OnResponseDataReadComplete(response_enquiry, nullptr, nullptr, nullptr, -1); } } void AppCacheInternalsUI::Proxy::OnResponseDataReadComplete( const ResponseEnquiry& response_enquiry, scoped_refptr response_info, std::unique_ptr reader, scoped_refptr response_data, int net_result_code) { if (shutdown_called_) return; if (!response_info || net_result_code < 0) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::BindOnce(&AppCacheInternalsUI::OnFileDetailsFailed, appcache_internals_ui_, response_enquiry, net_result_code)); } else { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::BindOnce(&AppCacheInternalsUI::OnFileDetailsReady, appcache_internals_ui_, response_enquiry, response_info, response_data, net_result_code)); } preparing_response_ = false; HandleFileDetailsRequest(); } AppCacheInternalsUI::AppCacheInternalsUI(WebUI* web_ui) : WebUIController(web_ui), weak_ptr_factory_(this) { web_ui->RegisterMessageCallback( kRequestGetAllAppCacheInfo, base::BindRepeating(&AppCacheInternalsUI::GetAllAppCache, AsWeakPtr())); web_ui->RegisterMessageCallback( kRequestDeleteAppCache, base::BindRepeating(&AppCacheInternalsUI::DeleteAppCache, AsWeakPtr())); web_ui->RegisterMessageCallback( kRequestGetAppCacheDetails, base::BindRepeating(&AppCacheInternalsUI::GetAppCacheDetails, AsWeakPtr())); web_ui->RegisterMessageCallback( kRequestGetFileDetails, base::BindRepeating(&AppCacheInternalsUI::GetFileDetails, AsWeakPtr())); WebUIDataSource* source = WebUIDataSource::Create(kChromeUIAppCacheInternalsHost); source->SetJsonPath("strings.js"); source->AddResourcePath("appcache_internals.js", IDR_APPCACHE_INTERNALS_JS); source->AddResourcePath("appcache_internals.css", IDR_APPCACHE_INTERNALS_CSS); source->SetDefaultResource(IDR_APPCACHE_INTERNALS_HTML); source->UseGzip(); WebUIDataSource::Add(browser_context(), source); BrowserContext::StoragePartitionCallback callback = base::Bind(&AppCacheInternalsUI::CreateProxyForPartition, AsWeakPtr()); BrowserContext::ForEachStoragePartition(browser_context(), callback); } AppCacheInternalsUI::~AppCacheInternalsUI() { for (auto& proxy : appcache_proxies_) proxy->Shutdown(); } void AppCacheInternalsUI::CreateProxyForPartition( StoragePartition* storage_partition) { scoped_refptr proxy = new Proxy(weak_ptr_factory_.GetWeakPtr(), storage_partition->GetPath()); proxy->Initialize(static_cast(storage_partition) ->GetAppCacheService()); appcache_proxies_.push_back(proxy); } void AppCacheInternalsUI::GetAllAppCache(const base::ListValue* args) { DCHECK_CURRENTLY_ON(BrowserThread::UI); for (scoped_refptr& proxy : appcache_proxies_) proxy->RequestAllAppCacheInfo(); } void AppCacheInternalsUI::DeleteAppCache(const base::ListValue* args) { DCHECK_CURRENTLY_ON(BrowserThread::UI); std::string manifest_url, partition_path; args->GetString(0, &partition_path); args->GetString(1, &manifest_url); Proxy* proxy = GetProxyForPartitionPath(base::FilePath::FromUTF8Unsafe(partition_path)); if (proxy) proxy->DeleteAppCache(manifest_url); } void AppCacheInternalsUI::GetAppCacheDetails(const base::ListValue* args) { std::string manifest_url, partition_path; args->GetString(0, &partition_path); args->GetString(1, &manifest_url); Proxy* proxy = GetProxyForPartitionPath(base::FilePath::FromUTF8Unsafe(partition_path)); if (proxy) proxy->RequestAppCacheDetails(manifest_url); } void AppCacheInternalsUI::GetFileDetails(const base::ListValue* args) { std::string manifest_url, partition_path, group_id_str, response_id_str; args->GetString(0, &partition_path); args->GetString(1, &manifest_url); args->GetString(2, &group_id_str); args->GetString(3, &response_id_str); Proxy* proxy = GetProxyForPartitionPath(base::FilePath::FromUTF8Unsafe(partition_path)); if (proxy) proxy->RequestFileDetails( {manifest_url, ToInt64(group_id_str), ToInt64(response_id_str)}); } void AppCacheInternalsUI::OnAllAppCacheInfoReady( scoped_refptr collection, const base::FilePath& partition_path) { std::string incognito_path_prefix; if (browser_context()->IsOffTheRecord()) incognito_path_prefix = "Incognito "; web_ui()->CallJavascriptFunctionUnsafe( kFunctionOnAllAppCacheInfoReady, base::Value(incognito_path_prefix + partition_path.AsUTF8Unsafe()), *GetListValueFromAppCacheInfoCollection(collection.get())); } void AppCacheInternalsUI::OnAppCacheInfoDeleted( const base::FilePath& partition_path, const std::string& manifest_url, bool deleted) { web_ui()->CallJavascriptFunctionUnsafe( kFunctionOnAppCacheInfoDeleted, base::Value(partition_path.AsUTF8Unsafe()), base::Value(manifest_url), base::Value(deleted)); } void AppCacheInternalsUI::OnAppCacheDetailsReady( const base::FilePath& partition_path, const std::string& manifest_url, std::unique_ptr resource_info_vector) { if (resource_info_vector) { web_ui()->CallJavascriptFunctionUnsafe( kFunctionOnAppCacheDetailsReady, base::Value(manifest_url), base::Value(partition_path.AsUTF8Unsafe()), *GetListValueForAppCacheResourceInfoVector(resource_info_vector.get())); } else { web_ui()->CallJavascriptFunctionUnsafe( kFunctionOnAppCacheDetailsReady, base::Value(manifest_url), base::Value(partition_path.AsUTF8Unsafe())); } } void AppCacheInternalsUI::OnFileDetailsReady( const Proxy::ResponseEnquiry& response_enquiry, scoped_refptr response_info, scoped_refptr response_data, int data_length) { std::string headers; if (response_info->http_response_info()) { headers.append("
");
    headers.append(net::EscapeForHTML(
        response_info->http_response_info()->headers->GetStatusLine()));
    headers.push_back('\n');

    size_t iter = 0;
    std::string name, value;
    while (response_info->http_response_info()->headers->EnumerateHeaderLines(
        &iter, &name, &value)) {
      headers.append(net::EscapeForHTML(name));
      headers.append(": ");
      headers.append(net::EscapeForHTML(value));
      headers.push_back('\n');
    }
    headers.append("
"); } else { headers.append("Failed to read response headers.
"); } std::string hex_dump = base::StringPrintf( "
 Showing %d of %d bytes\n\n", static_cast(data_length),
      static_cast(response_info->response_data_size()));
  net::ViewCacheHelper::HexDump(response_data->data(), data_length, &hex_dump);
  if (data_length < response_info->response_data_size())
    hex_dump.append("\nNote: data is truncated...");
  hex_dump.append("
"); web_ui()->CallJavascriptFunctionUnsafe( kFunctionOnFileDetailsReady, *GetDictionaryValueForResponseEnquiry(response_enquiry), base::Value(headers), base::Value(hex_dump)); } void AppCacheInternalsUI::OnFileDetailsFailed( const Proxy::ResponseEnquiry& response_enquiry, int net_result_code) { web_ui()->CallJavascriptFunctionUnsafe( kFunctionOnFileDetailsFailed, *GetDictionaryValueForResponseEnquiry(response_enquiry), base::Value(net_result_code)); } AppCacheInternalsUI::Proxy* AppCacheInternalsUI::GetProxyForPartitionPath( const base::FilePath& partition_path) { for (const scoped_refptr& proxy : appcache_proxies_) { if (proxy->partition_path_ == partition_path) return proxy.get(); } NOTREACHED(); return nullptr; } } // namespace content