// Copyright (c) 2012 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 #include #include "webkit/appcache/view_appcache_internals_job.h" #include "base/base64.h" #include "base/bind.h" #include "base/format_macros.h" #include "base/i18n/time_formatting.h" #include "base/logging.h" #include "base/memory/weak_ptr.h" #include "base/string_number_conversions.h" #include "base/string_util.h" #include "base/stringprintf.h" #include "base/utf_string_conversions.h" #include "net/base/escape.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/http/http_response_headers.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_simple_job.h" #include "net/url_request/view_cache_helper.h" #include "webkit/appcache/appcache.h" #include "webkit/appcache/appcache_group.h" #include "webkit/appcache/appcache_policy.h" #include "webkit/appcache/appcache_response.h" #include "webkit/appcache/appcache_service.h" namespace appcache { namespace { const char kErrorMessage[] = "Error in retrieving Application Caches."; const char kEmptyAppCachesMessage[] = "No available Application Caches."; const char kManifestNotFoundMessage[] = "Manifest not found."; const char kManifest[] = "Manifest: "; const char kSize[] = "Size: "; const char kCreationTime[] = "Creation Time: "; const char kLastAccessTime[] = "Last Access Time: "; const char kLastUpdateTime[] = "Last Update Time: "; const char kFormattedDisabledAppCacheMsg[] = "" "This Application Cache is disabled by policy.
"; const char kRemoveCacheLabel[] = "Remove"; const char kViewCacheLabel[] = "View Entries"; const char kRemoveCacheCommand[] = "remove-cache"; const char kViewCacheCommand[] = "view-cache"; const char kViewEntryCommand[] = "view-entry"; void EmitPageStart(std::string* out) { out->append( "\n" "AppCache Internals\n" "\n" "\n" "\n"); } void EmitPageEnd(std::string* out) { out->append("\n"); } void EmitListItem(const std::string& label, const std::string& data, std::string* out) { out->append("
  • "); out->append(net::EscapeForHTML(label)); out->append(net::EscapeForHTML(data)); out->append("
  • \n"); } void EmitAnchor(const std::string& url, const std::string& text, std::string* out) { out->append("append(net::EscapeForHTML(url)); out->append("\">"); out->append(net::EscapeForHTML(text)); out->append(""); } void EmitCommandAnchor(const char* label, const GURL& base_url, const char* command, const char* param, std::string* out) { std::string query(command); query.push_back('='); query.append(param); GURL::Replacements replacements; replacements.SetQuery(query.data(), url_parse::Component(0, query.length())); GURL command_url = base_url.ReplaceComponents(replacements); EmitAnchor(command_url.spec(), label, out); } void EmitAppCacheInfo(const GURL& base_url, AppCacheService* service, const AppCacheInfo* info, std::string* out) { std::string manifest_url_base64; base::Base64Encode(info->manifest_url.spec(), &manifest_url_base64); out->append("\n

    "); out->append(kManifest); EmitAnchor(info->manifest_url.spec(), info->manifest_url.spec(), out); out->append("
    \n"); if (!service->appcache_policy()->CanLoadAppCache( info->manifest_url, info->manifest_url)) { out->append(kFormattedDisabledAppCacheMsg); } out->append("\n
    \n"); EmitCommandAnchor(kRemoveCacheLabel, base_url, kRemoveCacheCommand, manifest_url_base64.c_str(), out); out->append("  "); EmitCommandAnchor(kViewCacheLabel, base_url, kViewCacheCommand, manifest_url_base64.c_str(), out); out->append("\n
    \n"); out->append("


    \n"); } void EmitAppCacheInfoVector( const GURL& base_url, AppCacheService* service, const AppCacheInfoVector& appcaches, std::string* out) { for (std::vector::const_iterator info = appcaches.begin(); info != appcaches.end(); ++info) { EmitAppCacheInfo(base_url, service, &(*info), out); } } void EmitTableData(const std::string& data, bool align_right, bool bold, std::string* out) { if (align_right) out->append(""); else out->append(""); if (bold) out->append(""); out->append(data); if (bold) out->append(""); out->append(""); } std::string FormFlagsString(const AppCacheResourceInfo& info) { std::string str; if (info.is_manifest) str.append("Manifest, "); if (info.is_master) str.append("Master, "); if (info.is_intercept) str.append("Intercept, "); if (info.is_fallback) str.append("Fallback, "); if (info.is_explicit) str.append("Explicit, "); if (info.is_foreign) str.append("Foreign, "); return str; } std::string FormViewEntryAnchor(const GURL& base_url, const GURL& manifest_url, const GURL& entry_url, int64 response_id, int64 group_id) { std::string manifest_url_base64; std::string entry_url_base64; std::string response_id_string; std::string group_id_string; base::Base64Encode(manifest_url.spec(), &manifest_url_base64); base::Base64Encode(entry_url.spec(), &entry_url_base64); response_id_string = base::Int64ToString(response_id); group_id_string = base::Int64ToString(group_id); std::string query(kViewEntryCommand); query.push_back('='); query.append(manifest_url_base64); query.push_back('|'); query.append(entry_url_base64); query.push_back('|'); query.append(response_id_string); query.push_back('|'); query.append(group_id_string); GURL::Replacements replacements; replacements.SetQuery(query.data(), url_parse::Component(0, query.length())); GURL view_entry_url = base_url.ReplaceComponents(replacements); std::string anchor; EmitAnchor(view_entry_url.spec(), entry_url.spec(), &anchor); return anchor; } void EmitAppCacheResourceInfoVector( const GURL& base_url, const GURL& manifest_url, const AppCacheResourceInfoVector& resource_infos, int64 group_id, std::string* out) { out->append("\n"); out->append(""); EmitTableData("Flags", false, true, out); EmitTableData("URL", false, true, out); EmitTableData("Size (headers and data)", true, true, out); out->append("\n"); for (AppCacheResourceInfoVector::const_iterator iter = resource_infos.begin(); iter != resource_infos.end(); ++iter) { out->append(""); EmitTableData(FormFlagsString(*iter), false, false, out); EmitTableData(FormViewEntryAnchor(base_url, manifest_url, iter->url, iter->response_id, group_id), false, false, out); EmitTableData(UTF16ToUTF8(FormatBytesUnlocalized(iter->size)), true, false, out); out->append("\n"); } out->append("
    \n"); } void EmitResponseHeaders(net::HttpResponseHeaders* headers, std::string* out) { out->append("
    ");
      out->append(net::EscapeForHTML(headers->GetStatusLine()));
      out->push_back('\n');
    
      void* iter = NULL;
      std::string name, value;
      while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
        out->append(net::EscapeForHTML(name));
        out->append(": ");
        out->append(net::EscapeForHTML(value));
        out->push_back('\n');
      }
      out->append("
    "); } void EmitHexDump(const char *buf, size_t buf_len, size_t total_len, std::string* out) { out->append("
    ");
      base::StringAppendF(out, "Showing %d of %d bytes\n\n",
                          static_cast(buf_len), static_cast(total_len));
      net::ViewCacheHelper::HexDump(buf, buf_len, out);
      if (buf_len < total_len)
        out->append("\nNote: data is truncated...");
      out->append("
    "); } GURL DecodeBase64URL(const std::string& base64) { std::string url; base::Base64Decode(base64, &url); return GURL(url); } bool ParseQuery(const std::string& query, std::string* command, std::string* value) { size_t position = query.find("="); if (position == std::string::npos) return false; *command = query.substr(0, position); *value = query.substr(position + 1); return !command->empty() && !value->empty(); } bool SortByManifestUrl(const AppCacheInfo& lhs, const AppCacheInfo& rhs) { return lhs.manifest_url.spec() < rhs.manifest_url.spec(); } bool SortByResourceUrl(const AppCacheResourceInfo& lhs, const AppCacheResourceInfo& rhs) { return lhs.url.spec() < rhs.url.spec(); } GURL ClearQuery(const GURL& url) { GURL::Replacements replacements; replacements.ClearQuery(); return url.ReplaceComponents(replacements); } // Simple base class for the job subclasses defined here. class BaseInternalsJob : public net::URLRequestSimpleJob { protected: BaseInternalsJob(net::URLRequest* request, net::NetworkDelegate* network_delegate, AppCacheService* service) : URLRequestSimpleJob(request, network_delegate), appcache_service_(service) {} virtual ~BaseInternalsJob() {} AppCacheService* appcache_service_; }; // Job that lists all appcaches in the system. class MainPageJob : public BaseInternalsJob { public: MainPageJob(net::URLRequest* request, net::NetworkDelegate* network_delegate, AppCacheService* service) : BaseInternalsJob(request, network_delegate, service), ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { } virtual void Start() OVERRIDE { DCHECK(request_); info_collection_ = new AppCacheInfoCollection; appcache_service_->GetAllAppCacheInfo( info_collection_, base::Bind(&MainPageJob::OnGotInfoComplete, weak_factory_.GetWeakPtr())); } // Produces a page containing the listing virtual int GetData(std::string* mime_type, std::string* charset, std::string* out, const net::CompletionCallback& callback) const OVERRIDE { mime_type->assign("text/html"); charset->assign("UTF-8"); out->clear(); EmitPageStart(out); if (!info_collection_.get()) { out->append(kErrorMessage); } else if (info_collection_->infos_by_origin.empty()) { out->append(kEmptyAppCachesMessage); } else { typedef std::map InfoByOrigin; AppCacheInfoVector appcaches; for (InfoByOrigin::const_iterator origin = info_collection_->infos_by_origin.begin(); origin != info_collection_->infos_by_origin.end(); ++origin) { appcaches.insert(appcaches.end(), origin->second.begin(), origin->second.end()); } std::sort(appcaches.begin(), appcaches.end(), SortByManifestUrl); GURL base_url = ClearQuery(request_->url()); EmitAppCacheInfoVector(base_url, appcache_service_, appcaches, out); } EmitPageEnd(out); return net::OK; } private: virtual ~MainPageJob() {} void OnGotInfoComplete(int rv) { if (rv != net::OK) info_collection_ = NULL; StartAsync(); } base::WeakPtrFactory weak_factory_; scoped_refptr info_collection_; DISALLOW_COPY_AND_ASSIGN(MainPageJob); }; // Job that redirects back to the main appcache internals page. class RedirectToMainPageJob : public BaseInternalsJob { public: RedirectToMainPageJob(net::URLRequest* request, net::NetworkDelegate* network_delegate, AppCacheService* service) : BaseInternalsJob(request, network_delegate, service) {} virtual int GetData(std::string* mime_type, std::string* charset, std::string* data, const net::CompletionCallback& callback) const OVERRIDE { return net::OK; // IsRedirectResponse induces a redirect. } virtual bool IsRedirectResponse(GURL* location, int* http_status_code) OVERRIDE { *location = ClearQuery(request_->url()); *http_status_code = 307; return true; } protected: virtual ~RedirectToMainPageJob() {} }; // Job that removes an appcache and then redirects back to the main page. class RemoveAppCacheJob : public RedirectToMainPageJob { public: RemoveAppCacheJob( net::URLRequest* request, net::NetworkDelegate* network_delegate, AppCacheService* service, const GURL& manifest_url) : RedirectToMainPageJob(request, network_delegate, service), manifest_url_(manifest_url), ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { } virtual void Start() OVERRIDE { DCHECK(request_); appcache_service_->DeleteAppCacheGroup( manifest_url_,base::Bind(&RemoveAppCacheJob::OnDeleteAppCacheComplete, weak_factory_.GetWeakPtr())); } private: virtual ~RemoveAppCacheJob() {} void OnDeleteAppCacheComplete(int rv) { StartAsync(); // Causes the base class to redirect. } GURL manifest_url_; base::WeakPtrFactory weak_factory_; }; // Job shows the details of a particular manifest url. class ViewAppCacheJob : public BaseInternalsJob, public AppCacheStorage::Delegate { public: ViewAppCacheJob( net::URLRequest* request, net::NetworkDelegate* network_delegate, AppCacheService* service, const GURL& manifest_url) : BaseInternalsJob(request, network_delegate, service), manifest_url_(manifest_url) {} virtual void Start() OVERRIDE { DCHECK(request_); appcache_service_->storage()->LoadOrCreateGroup(manifest_url_, this); } // Produces a page containing the entries listing. virtual int GetData(std::string* mime_type, std::string* charset, std::string* out, const net::CompletionCallback& callback) const OVERRIDE { mime_type->assign("text/html"); charset->assign("UTF-8"); out->clear(); EmitPageStart(out); if (appcache_info_.manifest_url.is_empty()) { out->append(kManifestNotFoundMessage); } else { GURL base_url = ClearQuery(request_->url()); EmitAppCacheInfo(base_url, appcache_service_, &appcache_info_, out); EmitAppCacheResourceInfoVector(base_url, manifest_url_, resource_infos_, appcache_info_.group_id, out); } EmitPageEnd(out); return net::OK; } private: virtual ~ViewAppCacheJob() { appcache_service_->storage()->CancelDelegateCallbacks(this); } // AppCacheStorage::Delegate override virtual void OnGroupLoaded( AppCacheGroup* group, const GURL& manifest_url) OVERRIDE { DCHECK_EQ(manifest_url_, manifest_url); if (group && group->newest_complete_cache()) { appcache_info_.manifest_url = manifest_url; appcache_info_.group_id = group->group_id(); appcache_info_.size = group->newest_complete_cache()->cache_size(); appcache_info_.creation_time = group->creation_time(); appcache_info_.last_update_time = group->newest_complete_cache()->update_time(); appcache_info_.last_access_time = base::Time::Now(); group->newest_complete_cache()->ToResourceInfoVector(&resource_infos_); std::sort(resource_infos_.begin(), resource_infos_.end(), SortByResourceUrl); } StartAsync(); } GURL manifest_url_; AppCacheInfo appcache_info_; AppCacheResourceInfoVector resource_infos_; DISALLOW_COPY_AND_ASSIGN(ViewAppCacheJob); }; // Job that shows the details of a particular cached resource. class ViewEntryJob : public BaseInternalsJob, public AppCacheStorage::Delegate { public: ViewEntryJob( net::URLRequest* request, net::NetworkDelegate* network_delegate, AppCacheService* service, const GURL& manifest_url, const GURL& entry_url, int64 response_id, int64 group_id) : BaseInternalsJob(request, network_delegate, service), manifest_url_(manifest_url), entry_url_(entry_url), response_id_(response_id), group_id_(group_id), amount_read_(0) { } virtual void Start() OVERRIDE { DCHECK(request_); appcache_service_->storage()->LoadResponseInfo( manifest_url_, group_id_, response_id_, this); } // Produces a page containing the response headers and data. virtual int GetData(std::string* mime_type, std::string* charset, std::string* out, const net::CompletionCallback& callback) const OVERRIDE { mime_type->assign("text/html"); charset->assign("UTF-8"); out->clear(); EmitPageStart(out); EmitAnchor(entry_url_.spec(), entry_url_.spec(), out); out->append("
    \n"); if (response_info_) { if (response_info_->http_response_info()) EmitResponseHeaders(response_info_->http_response_info()->headers, out); else out->append("Failed to read response headers.
    "); if (response_data_) { EmitHexDump(response_data_->data(), amount_read_, response_info_->response_data_size(), out); } else { out->append("Failed to read response data.
    "); } } else { out->append("Failed to read response headers and data.
    "); } EmitPageEnd(out); return net::OK; } private: virtual ~ViewEntryJob() { appcache_service_->storage()->CancelDelegateCallbacks(this); } virtual void OnResponseInfoLoaded( AppCacheResponseInfo* response_info, int64 response_id) OVERRIDE { if (!response_info) { StartAsync(); return; } response_info_ = response_info; // Read the response data, truncating if its too large. const int64 kLimit = 100 * 1000; int64 amount_to_read = std::min(kLimit, response_info->response_data_size()); response_data_ = new net::IOBuffer(amount_to_read); reader_.reset(appcache_service_->storage()->CreateResponseReader( manifest_url_, group_id_, response_id_)); reader_->ReadData( response_data_, amount_to_read, base::Bind(&ViewEntryJob::OnReadComplete, base::Unretained(this))); } void OnReadComplete(int result) { reader_.reset(); amount_read_ = result; if (result < 0) response_data_ = NULL; StartAsync(); } GURL manifest_url_; GURL entry_url_; int64 response_id_; int64 group_id_; scoped_refptr response_info_; scoped_refptr response_data_; int amount_read_; scoped_ptr reader_; }; } // namespace net::URLRequestJob* ViewAppCacheInternalsJobFactory::CreateJobForRequest( net::URLRequest* request, net::NetworkDelegate* network_delegate, AppCacheService* service) { if (!request->url().has_query()) return new MainPageJob(request, network_delegate, service); std::string command; std::string param; ParseQuery(request->url().query(), &command, ¶m); if (command == kRemoveCacheCommand) return new RemoveAppCacheJob(request, network_delegate, service, DecodeBase64URL(param)); if (command == kViewCacheCommand) return new ViewAppCacheJob(request, network_delegate, service, DecodeBase64URL(param)); std::vector tokens; int64 response_id; int64 group_id; if (command == kViewEntryCommand && Tokenize(param, "|", &tokens) == 4u && base::StringToInt64(tokens[2], &response_id) && base::StringToInt64(tokens[3], &group_id)) { return new ViewEntryJob(request, network_delegate, service, DecodeBase64URL(tokens[0]), // manifest url DecodeBase64URL(tokens[1]), // entry url response_id, group_id); } return new RedirectToMainPageJob(request, network_delegate, service); } } // namespace appcache