|  | // 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 "webkit/blob/view_blob_internals_job.h" | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/compiler_specific.h" | 
|  | #include "base/format_macros.h" | 
|  | #include "base/i18n/number_formatting.h" | 
|  | #include "base/i18n/time_formatting.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/message_loop.h" | 
|  | #include "base/string_util.h" | 
|  | #include "base/stringprintf.h" | 
|  | #include "base/utf_string_conversions.h" | 
|  | #include "net/base/escape.h" | 
|  | #include "net/base/net_errors.h" | 
|  | #include "net/url_request/url_request.h" | 
|  | #include "webkit/blob/blob_data.h" | 
|  | #include "webkit/blob/blob_storage_controller.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const char kEmptyBlobStorageMessage[] = "No available blob data."; | 
|  | const char kRemove[] = "Remove"; | 
|  | const char kContentType[] = "Content Type: "; | 
|  | const char kContentDisposition[] = "Content Disposition: "; | 
|  | const char kCount[] = "Count: "; | 
|  | const char kIndex[] = "Index: "; | 
|  | const char kType[] = "Type: "; | 
|  | const char kPath[] = "Path: "; | 
|  | const char kURL[] = "URL: "; | 
|  | const char kModificationTime[] = "Modification Time: "; | 
|  | const char kOffset[] = "Offset: "; | 
|  | const char kLength[] = "Length: "; | 
|  |  | 
|  | void StartHTML(std::string* out) { | 
|  | out->append( | 
|  | "<!DOCTYPE HTML>" | 
|  | "<html><title>Blob Storage Internals</title>" | 
|  | "<meta http-equiv=\"X-WebKit-CSP\"" | 
|  | "  content=\"object-src 'none'; script-src 'none'\">\n" | 
|  | "<style>\n" | 
|  | "body { font-family: sans-serif; font-size: 0.8em; }\n" | 
|  | "tt, code, pre { font-family: WebKitHack, monospace; }\n" | 
|  | "form { display: inline }\n" | 
|  | ".subsection_body { margin: 10px 0 10px 2em; }\n" | 
|  | ".subsection_title { font-weight: bold; }\n" | 
|  | "</style>\n" | 
|  | "</head><body>\n"); | 
|  | } | 
|  |  | 
|  | void EndHTML(std::string* out) { | 
|  | out->append("</body></html>"); | 
|  | } | 
|  |  | 
|  | void AddHTMLBoldText(const std::string& text, std::string* out) { | 
|  | out->append("<b>"); | 
|  | out->append(net::EscapeForHTML(text)); | 
|  | out->append("</b>"); | 
|  | } | 
|  |  | 
|  | void StartHTMLList(std::string* out) { | 
|  | out->append("<ul>"); | 
|  | } | 
|  |  | 
|  | void EndHTMLList(std::string* out) { | 
|  | out->append("</ul>"); | 
|  | } | 
|  |  | 
|  | void AddHTMLListItem(const std::string& element_title, | 
|  | const std::string& element_data, | 
|  | std::string* out) { | 
|  | out->append("<li>"); | 
|  | // No need to escape element_title since constant string is passed. | 
|  | out->append(element_title); | 
|  | out->append(net::EscapeForHTML(element_data)); | 
|  | out->append("</li>"); | 
|  | } | 
|  |  | 
|  | void AddHTMLButton(const std::string& title, | 
|  | const std::string& command, | 
|  | std::string* out) { | 
|  | // No need to escape title since constant string is passed. | 
|  | std::string escaped_command = net::EscapeForHTML(command.c_str()); | 
|  | base::StringAppendF(out, | 
|  | "<form action=\"\" method=\"GET\">\n" | 
|  | "<input type=\"hidden\" name=\"remove\" value=\"%s\">\n" | 
|  | "<input type=\"submit\" value=\"%s\">\n" | 
|  | "</form><br/>\n", | 
|  | escaped_command.c_str(), | 
|  | title.c_str()); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | namespace webkit_blob { | 
|  |  | 
|  | ViewBlobInternalsJob::ViewBlobInternalsJob( | 
|  | net::URLRequest* request, | 
|  | net::NetworkDelegate* network_delegate, | 
|  | BlobStorageController* blob_storage_controller) | 
|  | : net::URLRequestSimpleJob(request, network_delegate), | 
|  | blob_storage_controller_(blob_storage_controller), | 
|  | ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { | 
|  | } | 
|  |  | 
|  | ViewBlobInternalsJob::~ViewBlobInternalsJob() { | 
|  | } | 
|  |  | 
|  | void ViewBlobInternalsJob::Start() { | 
|  | MessageLoop::current()->PostTask( | 
|  | FROM_HERE, base::Bind(&ViewBlobInternalsJob::DoWorkAsync, | 
|  | weak_factory_.GetWeakPtr())); | 
|  | } | 
|  |  | 
|  | bool ViewBlobInternalsJob::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 ViewBlobInternalsJob::Kill() { | 
|  | net::URLRequestSimpleJob::Kill(); | 
|  | weak_factory_.InvalidateWeakPtrs(); | 
|  | } | 
|  |  | 
|  | void ViewBlobInternalsJob::DoWorkAsync() { | 
|  | if (request_->url().has_query() && | 
|  | StartsWithASCII(request_->url().query(), "remove=", true)) { | 
|  | std::string blob_url = request_->url().query().substr(strlen("remove=")); | 
|  | blob_url = net::UnescapeURLComponent(blob_url, | 
|  | net::UnescapeRule::NORMAL | net::UnescapeRule::URL_SPECIAL_CHARS); | 
|  | blob_storage_controller_->RemoveBlob(GURL(blob_url)); | 
|  | } | 
|  |  | 
|  | StartAsync(); | 
|  | } | 
|  |  | 
|  | int ViewBlobInternalsJob::GetData( | 
|  | std::string* mime_type, | 
|  | std::string* charset, | 
|  | std::string* data, | 
|  | const net::CompletionCallback& callback) const { | 
|  | mime_type->assign("text/html"); | 
|  | charset->assign("UTF-8"); | 
|  |  | 
|  | data->clear(); | 
|  | StartHTML(data); | 
|  | if (blob_storage_controller_->blob_map_.empty()) | 
|  | data->append(kEmptyBlobStorageMessage); | 
|  | else | 
|  | GenerateHTML(data); | 
|  | EndHTML(data); | 
|  | return net::OK; | 
|  | } | 
|  |  | 
|  | void ViewBlobInternalsJob::GenerateHTML(std::string* out) const { | 
|  | for (BlobStorageController::BlobMap::const_iterator iter = | 
|  | blob_storage_controller_->blob_map_.begin(); | 
|  | iter != blob_storage_controller_->blob_map_.end(); | 
|  | ++iter) { | 
|  | AddHTMLBoldText(iter->first, out); | 
|  | AddHTMLButton(kRemove, iter->first, out); | 
|  | GenerateHTMLForBlobData(*iter->second, out); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ViewBlobInternalsJob::GenerateHTMLForBlobData(const BlobData& blob_data, | 
|  | std::string* out) { | 
|  | StartHTMLList(out); | 
|  |  | 
|  | if (!blob_data.content_type().empty()) | 
|  | AddHTMLListItem(kContentType, blob_data.content_type(), out); | 
|  | if (!blob_data.content_disposition().empty()) | 
|  | AddHTMLListItem(kContentDisposition, blob_data.content_disposition(), out); | 
|  |  | 
|  | bool has_multi_items = blob_data.items().size() > 1; | 
|  | if (has_multi_items) { | 
|  | AddHTMLListItem(kCount, | 
|  | UTF16ToUTF8(base::FormatNumber(blob_data.items().size())), out); | 
|  | } | 
|  |  | 
|  | for (size_t i = 0; i < blob_data.items().size(); ++i) { | 
|  | if (has_multi_items) { | 
|  | AddHTMLListItem(kIndex, UTF16ToUTF8(base::FormatNumber(i)), out); | 
|  | StartHTMLList(out); | 
|  | } | 
|  | const BlobData::Item& item = blob_data.items().at(i); | 
|  |  | 
|  | switch (item.type()) { | 
|  | case BlobData::Item::TYPE_BYTES: | 
|  | AddHTMLListItem(kType, "data", out); | 
|  | break; | 
|  | case BlobData::Item::TYPE_FILE: | 
|  | AddHTMLListItem(kType, "file", out); | 
|  | AddHTMLListItem(kPath, | 
|  | net::EscapeForHTML(item.path().AsUTF8Unsafe()), | 
|  | out); | 
|  | if (!item.expected_modification_time().is_null()) { | 
|  | AddHTMLListItem(kModificationTime, UTF16ToUTF8( | 
|  | TimeFormatFriendlyDateAndTime(item.expected_modification_time())), | 
|  | out); | 
|  | } | 
|  | break; | 
|  | case BlobData::Item::TYPE_BLOB: | 
|  | AddHTMLListItem(kType, "blob", out); | 
|  | AddHTMLListItem(kURL, item.url().spec(), out); | 
|  | break; | 
|  | case BlobData::Item::TYPE_FILE_FILESYSTEM: | 
|  | AddHTMLListItem(kType, "filesystem", out); | 
|  | AddHTMLListItem(kURL, item.url().spec(), out); | 
|  | if (!item.expected_modification_time().is_null()) { | 
|  | AddHTMLListItem(kModificationTime, UTF16ToUTF8( | 
|  | TimeFormatFriendlyDateAndTime(item.expected_modification_time())), | 
|  | out); | 
|  | } | 
|  | break; | 
|  | case BlobData::Item::TYPE_UNKNOWN: | 
|  | NOTREACHED(); | 
|  | break; | 
|  | } | 
|  | if (item.offset()) { | 
|  | AddHTMLListItem(kOffset, UTF16ToUTF8(base::FormatNumber( | 
|  | static_cast<int64>(item.offset()))), out); | 
|  | } | 
|  | if (static_cast<int64>(item.length()) != -1) { | 
|  | AddHTMLListItem(kLength, UTF16ToUTF8(base::FormatNumber( | 
|  | static_cast<int64>(item.length()))), out); | 
|  | } | 
|  |  | 
|  | if (has_multi_items) | 
|  | EndHTMLList(out); | 
|  | } | 
|  |  | 
|  | EndHTMLList(out); | 
|  | } | 
|  |  | 
|  | }  // namespace webkit_blob |