|  | // 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 "net/url_request/view_cache_helper.h" | 
|  |  | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/bind_helpers.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "net/base/escape.h" | 
|  | #include "net/base/io_buffer.h" | 
|  | #include "net/base/net_errors.h" | 
|  | #include "net/base/request_priority.h" | 
|  | #include "net/disk_cache/disk_cache.h" | 
|  | #include "net/http/http_cache.h" | 
|  | #include "net/http/http_response_headers.h" | 
|  | #include "net/http/http_response_info.h" | 
|  | #include "net/url_request/url_request_context.h" | 
|  |  | 
|  | #define VIEW_CACHE_HEAD                                 \ | 
|  | "<html><meta charset=\"utf-8\">"                      \ | 
|  | "<meta http-equiv=\"Content-Security-Policy\" "       \ | 
|  | "  content=\"object-src 'none'; script-src 'none'\">" \ | 
|  | "<body><table>" | 
|  |  | 
|  | #define VIEW_CACHE_TAIL \ | 
|  | "</table></body></html>" | 
|  |  | 
|  | namespace net { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | std::string FormatEntryInfo(disk_cache::Entry* entry, | 
|  | const std::string& url_prefix) { | 
|  | std::string key = entry->GetKey(); | 
|  | GURL url = GURL(url_prefix + key); | 
|  | std::string row = | 
|  | "<tr><td><a href=\"" + url.spec() + "\">" + EscapeForHTML(key) + | 
|  | "</a></td></tr>"; | 
|  | return row; | 
|  | } | 
|  |  | 
|  | }  // namespace. | 
|  |  | 
|  | ViewCacheHelper::ViewCacheHelper() | 
|  | : context_(NULL), | 
|  | disk_cache_(NULL), | 
|  | entry_(NULL), | 
|  | buf_len_(0), | 
|  | index_(0), | 
|  | data_(NULL), | 
|  | next_state_(STATE_NONE), | 
|  | weak_factory_(this) { | 
|  | } | 
|  |  | 
|  | ViewCacheHelper::~ViewCacheHelper() { | 
|  | if (entry_) | 
|  | entry_->Close(); | 
|  | } | 
|  |  | 
|  | int ViewCacheHelper::GetEntryInfoHTML(const std::string& key, | 
|  | const URLRequestContext* context, | 
|  | std::string* out, | 
|  | CompletionOnceCallback callback) { | 
|  | return GetInfoHTML(key, context, std::string(), out, std::move(callback)); | 
|  | } | 
|  |  | 
|  | int ViewCacheHelper::GetContentsHTML(const URLRequestContext* context, | 
|  | const std::string& url_prefix, | 
|  | std::string* out, | 
|  | CompletionOnceCallback callback) { | 
|  | return GetInfoHTML(std::string(), context, url_prefix, out, | 
|  | std::move(callback)); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void ViewCacheHelper::HexDump(const char *buf, size_t buf_len, | 
|  | std::string* result) { | 
|  | const size_t kMaxRows = 16; | 
|  | int offset = 0; | 
|  |  | 
|  | const unsigned char *p; | 
|  | while (buf_len) { | 
|  | base::StringAppendF(result, "%08x: ", offset); | 
|  | offset += kMaxRows; | 
|  |  | 
|  | p = (const unsigned char *) buf; | 
|  |  | 
|  | size_t i; | 
|  | size_t row_max = std::min(kMaxRows, buf_len); | 
|  |  | 
|  | // print hex codes: | 
|  | for (i = 0; i < row_max; ++i) | 
|  | base::StringAppendF(result, "%02x ", *p++); | 
|  | for (i = row_max; i < kMaxRows; ++i) | 
|  | result->append("   "); | 
|  | result->append(" "); | 
|  |  | 
|  | // print ASCII glyphs if possible: | 
|  | p = (const unsigned char *) buf; | 
|  | for (i = 0; i < row_max; ++i, ++p) { | 
|  | if (*p < 0x7F && *p > 0x1F) { | 
|  | AppendEscapedCharForHTML(*p, result); | 
|  | } else { | 
|  | result->push_back('.'); | 
|  | } | 
|  | } | 
|  |  | 
|  | result->push_back('\n'); | 
|  |  | 
|  | buf += row_max; | 
|  | buf_len -= row_max; | 
|  | } | 
|  | } | 
|  |  | 
|  | //----------------------------------------------------------------------------- | 
|  |  | 
|  | int ViewCacheHelper::GetInfoHTML(const std::string& key, | 
|  | const URLRequestContext* context, | 
|  | const std::string& url_prefix, | 
|  | std::string* out, | 
|  | CompletionOnceCallback callback) { | 
|  | DCHECK(callback_.is_null()); | 
|  | DCHECK(context); | 
|  | key_ = key; | 
|  | context_ = context; | 
|  | url_prefix_ = url_prefix; | 
|  | data_ = out; | 
|  | next_state_ = STATE_GET_BACKEND; | 
|  | int rv = DoLoop(OK); | 
|  |  | 
|  | if (rv == ERR_IO_PENDING) | 
|  | callback_ = std::move(callback); | 
|  |  | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | void ViewCacheHelper::DoCallback(int rv) { | 
|  | DCHECK_NE(ERR_IO_PENDING, rv); | 
|  | DCHECK(!callback_.is_null()); | 
|  |  | 
|  | std::move(callback_).Run(rv); | 
|  | } | 
|  |  | 
|  | void ViewCacheHelper::HandleResult(int rv) { | 
|  | DCHECK_NE(ERR_IO_PENDING, rv); | 
|  | DCHECK_NE(ERR_FAILED, rv); | 
|  | context_ = NULL; | 
|  | if (!callback_.is_null()) | 
|  | DoCallback(rv); | 
|  | } | 
|  |  | 
|  | int ViewCacheHelper::DoLoop(int result) { | 
|  | DCHECK(next_state_ != STATE_NONE); | 
|  |  | 
|  | int rv = result; | 
|  | do { | 
|  | State state = next_state_; | 
|  | next_state_ = STATE_NONE; | 
|  | switch (state) { | 
|  | case STATE_GET_BACKEND: | 
|  | DCHECK_EQ(OK, rv); | 
|  | rv = DoGetBackend(); | 
|  | break; | 
|  | case STATE_GET_BACKEND_COMPLETE: | 
|  | rv = DoGetBackendComplete(rv); | 
|  | break; | 
|  | case STATE_OPEN_NEXT_ENTRY: | 
|  | DCHECK_EQ(OK, rv); | 
|  | rv = DoOpenNextEntry(); | 
|  | break; | 
|  | case STATE_OPEN_NEXT_ENTRY_COMPLETE: | 
|  | rv = DoOpenNextEntryComplete(rv); | 
|  | break; | 
|  | case STATE_OPEN_ENTRY: | 
|  | DCHECK_EQ(OK, rv); | 
|  | rv = DoOpenEntry(); | 
|  | break; | 
|  | case STATE_OPEN_ENTRY_COMPLETE: | 
|  | rv = DoOpenEntryComplete(rv); | 
|  | break; | 
|  | case STATE_READ_RESPONSE: | 
|  | DCHECK_EQ(OK, rv); | 
|  | rv = DoReadResponse(); | 
|  | break; | 
|  | case STATE_READ_RESPONSE_COMPLETE: | 
|  | rv = DoReadResponseComplete(rv); | 
|  | break; | 
|  | case STATE_READ_DATA: | 
|  | DCHECK_EQ(OK, rv); | 
|  | rv = DoReadData(); | 
|  | break; | 
|  | case STATE_READ_DATA_COMPLETE: | 
|  | rv = DoReadDataComplete(rv); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | NOTREACHED() << "bad state"; | 
|  | rv = ERR_FAILED; | 
|  | break; | 
|  | } | 
|  | } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); | 
|  |  | 
|  | if (rv != ERR_IO_PENDING) | 
|  | HandleResult(rv); | 
|  |  | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | int ViewCacheHelper::DoGetBackend() { | 
|  | next_state_ = STATE_GET_BACKEND_COMPLETE; | 
|  |  | 
|  | if (!context_->http_transaction_factory()) | 
|  | return ERR_FAILED; | 
|  |  | 
|  | HttpCache* http_cache = context_->http_transaction_factory()->GetCache(); | 
|  | if (!http_cache) | 
|  | return ERR_FAILED; | 
|  |  | 
|  | return http_cache->GetBackend( | 
|  | &disk_cache_, | 
|  | base::BindOnce(&ViewCacheHelper::OnIOComplete, base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | int ViewCacheHelper::DoGetBackendComplete(int result) { | 
|  | if (result == ERR_FAILED) { | 
|  | data_->append("no disk cache"); | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | DCHECK_EQ(OK, result); | 
|  | if (key_.empty()) { | 
|  | data_->assign(VIEW_CACHE_HEAD); | 
|  | DCHECK(!iter_); | 
|  | next_state_ = STATE_OPEN_NEXT_ENTRY; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | next_state_ = STATE_OPEN_ENTRY; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | int ViewCacheHelper::DoOpenNextEntry() { | 
|  | next_state_ = STATE_OPEN_NEXT_ENTRY_COMPLETE; | 
|  | if (!iter_) | 
|  | iter_ = disk_cache_->CreateIterator(); | 
|  | return | 
|  | iter_->OpenNextEntry(&entry_, base::Bind(&ViewCacheHelper::OnIOComplete, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | int ViewCacheHelper::DoOpenNextEntryComplete(int result) { | 
|  | if (result == ERR_FAILED) { | 
|  | data_->append(VIEW_CACHE_TAIL); | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | DCHECK_EQ(OK, result); | 
|  | data_->append(FormatEntryInfo(entry_, url_prefix_)); | 
|  | entry_->Close(); | 
|  | entry_ = NULL; | 
|  |  | 
|  | next_state_ = STATE_OPEN_NEXT_ENTRY; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | int ViewCacheHelper::DoOpenEntry() { | 
|  | next_state_ = STATE_OPEN_ENTRY_COMPLETE; | 
|  | return disk_cache_->OpenEntry( | 
|  | key_, net::HIGHEST, &entry_, | 
|  | base::Bind(&ViewCacheHelper::OnIOComplete, base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | int ViewCacheHelper::DoOpenEntryComplete(int result) { | 
|  | if (result == ERR_FAILED) { | 
|  | data_->append("no matching cache entry for: " + EscapeForHTML(key_)); | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | data_->assign(VIEW_CACHE_HEAD); | 
|  | data_->append(EscapeForHTML(entry_->GetKey())); | 
|  | next_state_ = STATE_READ_RESPONSE; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | int ViewCacheHelper::DoReadResponse() { | 
|  | next_state_ = STATE_READ_RESPONSE_COMPLETE; | 
|  | buf_len_ = entry_->GetDataSize(0); | 
|  | if (!buf_len_) | 
|  | return buf_len_; | 
|  |  | 
|  | buf_ = base::MakeRefCounted<IOBuffer>(buf_len_); | 
|  | return entry_->ReadData( | 
|  | 0, | 
|  | 0, | 
|  | buf_.get(), | 
|  | buf_len_, | 
|  | base::Bind(&ViewCacheHelper::OnIOComplete, weak_factory_.GetWeakPtr())); | 
|  | } | 
|  |  | 
|  | int ViewCacheHelper::DoReadResponseComplete(int result) { | 
|  | if (result && result == buf_len_) { | 
|  | HttpResponseInfo response; | 
|  | bool truncated; | 
|  | if (HttpCache::ParseResponseInfo( | 
|  | buf_->data(), buf_len_, &response, &truncated) && | 
|  | response.headers.get()) { | 
|  | if (truncated) | 
|  | data_->append("<pre>RESPONSE_INFO_TRUNCATED</pre>"); | 
|  |  | 
|  | data_->append("<hr><pre>"); | 
|  | data_->append(EscapeForHTML(response.headers->GetStatusLine())); | 
|  | data_->push_back('\n'); | 
|  |  | 
|  | size_t iter = 0; | 
|  | std::string name, value; | 
|  | while (response.headers->EnumerateHeaderLines(&iter, &name, &value)) { | 
|  | data_->append(EscapeForHTML(name)); | 
|  | data_->append(": "); | 
|  | data_->append(EscapeForHTML(value)); | 
|  | data_->push_back('\n'); | 
|  | } | 
|  | data_->append("</pre>"); | 
|  | } | 
|  | } | 
|  |  | 
|  | index_ = 0; | 
|  | next_state_ = STATE_READ_DATA; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | int ViewCacheHelper::DoReadData() { | 
|  | data_->append("<hr><pre>"); | 
|  |  | 
|  | next_state_ = STATE_READ_DATA_COMPLETE; | 
|  | buf_len_ = entry_->GetDataSize(index_); | 
|  | if (!buf_len_) | 
|  | return buf_len_; | 
|  |  | 
|  | buf_ = base::MakeRefCounted<IOBuffer>(buf_len_); | 
|  | return entry_->ReadData( | 
|  | index_, | 
|  | 0, | 
|  | buf_.get(), | 
|  | buf_len_, | 
|  | base::Bind(&ViewCacheHelper::OnIOComplete, weak_factory_.GetWeakPtr())); | 
|  | } | 
|  |  | 
|  | int ViewCacheHelper::DoReadDataComplete(int result) { | 
|  | if (result && result == buf_len_) { | 
|  | HexDump(buf_->data(), buf_len_, data_); | 
|  | } | 
|  | data_->append("</pre>"); | 
|  | index_++; | 
|  | if (index_ < HttpCache::kNumCacheEntryDataIndices) { | 
|  | next_state_ = STATE_READ_DATA; | 
|  | } else { | 
|  | data_->append(VIEW_CACHE_TAIL); | 
|  | entry_->Close(); | 
|  | entry_ = NULL; | 
|  | } | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | void ViewCacheHelper::OnIOComplete(int result) { | 
|  | DoLoop(result); | 
|  | } | 
|  |  | 
|  | }  // namespace net. |