| // Copyright (c) 2010 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 <algorithm> |
| |
| #include "webkit/appcache/view_appcache_internals_job.h" |
| |
| #include "base/logging.h" |
| #include "base/format_macros.h" |
| #include "base/utf_string_conversions.h" |
| #include "base/i18n/time_formatting.h" |
| #include "base/string_util.h" |
| #include "net/base/escape.h" |
| #include "net/url_request/url_request.h" |
| #include "webkit/appcache/appcache_policy.h" |
| #include "webkit/appcache/appcache_service.h" |
| |
| namespace { |
| |
| const char kErrorMessage[] = "Error in retrieving Application Caches."; |
| const char kEmptyAppCachesMessage[] = "No available Application Caches."; |
| const char kRemoveAppCache[] = "Remove this AppCache"; |
| 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[] = |
| "<b><i><font color=\"FF0000\">" |
| "This Application Cache is disabled by policy.</font></i></b><br/>"; |
| |
| void StartHTML(std::string* out) { |
| DCHECK(out); |
| out->append( |
| "<!DOCTYPE HTML>" |
| "<html><title>AppCache Internals</title>" |
| "<style>" |
| "body { font-family: sans-serif; font-size: 0.8em; }\n" |
| "tt, code, pre { font-family: WebKitHack, monospace; }\n" |
| ".subsection_body { margin: 10px 0 10px 2em; }\n" |
| ".subsection_title { font-weight: bold; }\n" |
| "</style>" |
| "<script>\n" |
| // Unfortunately we can't do XHR from chrome://appcache-internals |
| // because the chrome:// protocol restricts access. |
| // |
| // So instead, we will send commands by doing a form |
| // submission (which as a side effect will reload the page). |
| "function RemoveCommand(command) {\n" |
| " document.getElementById('cmd').value = command;\n" |
| " document.getElementById('cmdsender').submit();\n" |
| "}\n" |
| "</script>\n" |
| "</head><body>" |
| "<form action='' method=GET id=cmdsender>" |
| "<input type='hidden' id=cmd name='remove'>" |
| "</form>"); |
| } |
| |
| void EndHTML(std::string* out) { |
| DCHECK(out); |
| out->append("</body></html>"); |
| } |
| |
| // Appends an input button to |data| with text |title| that sends the command |
| // string |command| back to the browser, and then refreshes the page. |
| void DrawCommandButton(const std::string& title, |
| const std::string& command, |
| std::string* data) { |
| base::StringAppendF(data, "<input type=\"button\" value=\"%s\" " |
| "onclick=\"RemoveCommand('%s')\" />", |
| title.c_str(), |
| command.c_str()); |
| } |
| |
| void AddLiTag(const std::string& element_title, |
| const std::string& element_data, std::string* out) { |
| DCHECK(out); |
| out->append("<li>"); |
| out->append(element_title); |
| out->append(element_data); |
| out->append("</li>"); |
| } |
| |
| void WrapInHREF(const std::string& in, std::string* out) { |
| out->append("<a href='"); |
| out->append(in); |
| out->append("'>"); |
| out->append(in); |
| out->append("</a><br/>"); |
| } |
| |
| void AddHTMLFromAppCacheToOutput( |
| const appcache::AppCacheService& appcache_service, |
| const appcache::AppCacheInfoVector& appcaches, std::string* out) { |
| for (std::vector<appcache::AppCacheInfo>::const_iterator info = |
| appcaches.begin(); |
| info != appcaches.end(); ++info) { |
| out->append("<p>"); |
| std::string anchored_manifest; |
| WrapInHREF(info->manifest_url.spec(), &anchored_manifest); |
| out->append(kManifest); |
| out->append(anchored_manifest); |
| if (!appcache_service.appcache_policy()->CanLoadAppCache( |
| info->manifest_url)) { |
| out->append(kFormattedDisabledAppCacheMsg); |
| } |
| out->append("<br/>"); |
| DrawCommandButton(kRemoveAppCache, info->manifest_url.spec(), out); |
| out->append("<ul>"); |
| |
| AddLiTag(kSize, |
| UTF16ToUTF8(FormatBytes( |
| info->size, GetByteDisplayUnits(info->size), true)), |
| out); |
| AddLiTag(kCreationTime, |
| UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info->creation_time)), |
| out); |
| AddLiTag(kLastAccessTime, |
| UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info->last_access_time)), |
| out); |
| AddLiTag(kLastUpdateTime, |
| UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info->last_update_time)), |
| out); |
| |
| out->append("</ul></p></br>"); |
| } |
| } |
| |
| std::string GetAppCacheManifestToRemove(const std::string& query) { |
| if (!StartsWithASCII(query, "remove=", true)) { |
| // Not a recognized format. |
| return std::string(); |
| } |
| std::string manifest_str = query.substr(strlen("remove=")); |
| return UnescapeURLComponent( |
| manifest_str, UnescapeRule::NORMAL | UnescapeRule::URL_SPECIAL_CHARS); |
| } |
| |
| struct ManifestURLComparator { |
| public: |
| bool operator() ( |
| const appcache::AppCacheInfo& lhs, |
| const appcache::AppCacheInfo& rhs) const { |
| return (lhs.manifest_url.spec() < rhs.manifest_url.spec()); |
| } |
| } manifest_url_comparator; |
| |
| } // namespace |
| |
| namespace appcache { |
| |
| ViewAppCacheInternalsJob::ViewAppCacheInternalsJob( |
| net::URLRequest* request, |
| AppCacheService* service) |
| : net::URLRequestSimpleJob(request), |
| appcache_service_(service) { |
| } |
| |
| ViewAppCacheInternalsJob::~ViewAppCacheInternalsJob() { |
| // Cancel callback if job is destroyed before callback is called. |
| if (appcache_done_callback_) |
| appcache_done_callback_.release()->Cancel(); |
| } |
| |
| void ViewAppCacheInternalsJob::GetAppCacheInfoAsync() { |
| info_collection_ = new AppCacheInfoCollection; |
| appcache_done_callback_ = |
| new net::CancelableCompletionCallback<ViewAppCacheInternalsJob>( |
| this, &ViewAppCacheInternalsJob::AppCacheDone); |
| appcache_service_->GetAllAppCacheInfo( |
| info_collection_, appcache_done_callback_); |
| } |
| |
| void ViewAppCacheInternalsJob::RemoveAppCacheInfoAsync( |
| const std::string& manifest_url_spec) { |
| appcache_done_callback_ = |
| new net::CancelableCompletionCallback<ViewAppCacheInternalsJob>( |
| this, &ViewAppCacheInternalsJob::AppCacheDone); |
| |
| GURL manifest(manifest_url_spec); |
| appcache_service_->DeleteAppCacheGroup( |
| manifest, appcache_done_callback_); |
| } |
| |
| void ViewAppCacheInternalsJob::Start() { |
| if (!request_) |
| return; |
| |
| // Handle any remove appcache request, then redirect back to |
| // the same URL stripped of query parameters. The redirect happens as part |
| // of IsRedirectResponse(). |
| if (request_->url().has_query()) { |
| std::string remove_appcache_manifest( |
| GetAppCacheManifestToRemove(request_->url().query())); |
| |
| // Empty manifests are dealt with by the deleter. |
| RemoveAppCacheInfoAsync(remove_appcache_manifest); |
| } else { |
| GetAppCacheInfoAsync(); |
| } |
| } |
| |
| bool ViewAppCacheInternalsJob::IsRedirectResponse( |
| GURL* location, int* http_status_code) { |
| if (request_->url().has_query()) { |
| // Strip the query parameters. |
| GURL::Replacements replacements; |
| replacements.ClearQuery(); |
| *location = request_->url().ReplaceComponents(replacements); |
| *http_status_code = 307; |
| return true; |
| } |
| return false; |
| } |
| |
| void ViewAppCacheInternalsJob::GenerateHTMLAppCacheInfo( |
| std::string* out) const { |
| typedef std::map<GURL, AppCacheInfoVector> InfoByOrigin; |
| AppCacheInfoVector appcaches; |
| for (InfoByOrigin::const_iterator origin = |
| info_collection_->infos_by_origin.begin(); |
| origin != info_collection_->infos_by_origin.end(); ++origin) { |
| for (AppCacheInfoVector::const_iterator info = |
| origin->second.begin(); info != origin->second.end(); ++info) |
| appcaches.push_back(*info); |
| } |
| |
| std::sort(appcaches.begin(), appcaches.end(), manifest_url_comparator); |
| |
| AddHTMLFromAppCacheToOutput(*appcache_service_, appcaches, out); |
| } |
| |
| void ViewAppCacheInternalsJob::AppCacheDone(int rv) { |
| appcache_done_callback_ = NULL; |
| if (rv != net::OK) |
| info_collection_ = NULL; |
| StartAsync(); |
| } |
| |
| bool ViewAppCacheInternalsJob::GetData(std::string* mime_type, |
| std::string* charset, |
| std::string* data) const { |
| mime_type->assign("text/html"); |
| charset->assign("UTF-8"); |
| |
| data->clear(); |
| StartHTML(data); |
| if (!info_collection_.get()) |
| data->append(kErrorMessage); |
| else if (info_collection_->infos_by_origin.empty()) |
| data->append(kEmptyAppCachesMessage); |
| else |
| GenerateHTMLAppCacheInfo(data); |
| EndHTML(data); |
| return true; |
| } |
| |
| } // namespace appcache |