Home | History | Annotate | Download | only in appcache
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/browser/appcache/view_appcache_internals_job.h"
      6 
      7 #include <algorithm>
      8 #include <string>
      9 
     10 #include "base/base64.h"
     11 #include "base/bind.h"
     12 #include "base/format_macros.h"
     13 #include "base/i18n/time_formatting.h"
     14 #include "base/logging.h"
     15 #include "base/memory/weak_ptr.h"
     16 #include "base/strings/string_number_conversions.h"
     17 #include "base/strings/string_util.h"
     18 #include "base/strings/stringprintf.h"
     19 #include "base/strings/utf_string_conversions.h"
     20 #include "net/base/escape.h"
     21 #include "net/base/io_buffer.h"
     22 #include "net/base/net_errors.h"
     23 #include "net/http/http_response_headers.h"
     24 #include "net/url_request/url_request.h"
     25 #include "net/url_request/url_request_simple_job.h"
     26 #include "net/url_request/view_cache_helper.h"
     27 #include "webkit/browser/appcache/appcache.h"
     28 #include "webkit/browser/appcache/appcache_group.h"
     29 #include "webkit/browser/appcache/appcache_policy.h"
     30 #include "webkit/browser/appcache/appcache_response.h"
     31 #include "webkit/browser/appcache/appcache_service_impl.h"
     32 #include "webkit/browser/appcache/appcache_storage.h"
     33 
     34 using appcache::AppCacheGroup;
     35 using appcache::AppCacheInfo;
     36 using appcache::AppCacheInfoCollection;
     37 using appcache::AppCacheInfoVector;
     38 using appcache::AppCacheServiceImpl;
     39 using appcache::AppCacheStorage;
     40 using appcache::AppCacheStorageReference;
     41 using appcache::AppCacheResourceInfo;
     42 using appcache::AppCacheResourceInfoVector;
     43 using appcache::AppCacheResponseInfo;
     44 using appcache::AppCacheResponseReader;
     45 
     46 namespace content {
     47 namespace {
     48 
     49 const char kErrorMessage[] = "Error in retrieving Application Caches.";
     50 const char kEmptyAppCachesMessage[] = "No available Application Caches.";
     51 const char kManifestNotFoundMessage[] = "Manifest not found.";
     52 const char kManifest[] = "Manifest: ";
     53 const char kSize[] = "Size: ";
     54 const char kCreationTime[] = "Creation Time: ";
     55 const char kLastAccessTime[] = "Last Access Time: ";
     56 const char kLastUpdateTime[] = "Last Update Time: ";
     57 const char kFormattedDisabledAppCacheMsg[] =
     58     "<b><i><font color=\"FF0000\">"
     59     "This Application Cache is disabled by policy.</font></i></b><br/>";
     60 const char kRemoveCacheLabel[] = "Remove";
     61 const char kViewCacheLabel[] = "View Entries";
     62 const char kRemoveCacheCommand[] = "remove-cache";
     63 const char kViewCacheCommand[] = "view-cache";
     64 const char kViewEntryCommand[] = "view-entry";
     65 
     66 void EmitPageStart(std::string* out) {
     67   out->append(
     68       "<!DOCTYPE HTML>\n"
     69       "<html><title>AppCache Internals</title>\n"
     70       "<meta http-equiv=\"Content-Security-Policy\""
     71       "  content=\"object-src 'none'; script-src 'none'\">\n"
     72       "<style>\n"
     73       "body { font-family: sans-serif; font-size: 0.8em; }\n"
     74       "tt, code, pre { font-family: WebKitHack, monospace; }\n"
     75       "form { display: inline; }\n"
     76       ".subsection_body { margin: 10px 0 10px 2em; }\n"
     77       ".subsection_title { font-weight: bold; }\n"
     78       "</style>\n"
     79       "</head><body>\n");
     80 }
     81 
     82 void EmitPageEnd(std::string* out) {
     83   out->append("</body></html>\n");
     84 }
     85 
     86 void EmitListItem(const std::string& label,
     87                   const std::string& data,
     88                   std::string* out) {
     89   out->append("<li>");
     90   out->append(net::EscapeForHTML(label));
     91   out->append(net::EscapeForHTML(data));
     92   out->append("</li>\n");
     93 }
     94 
     95 void EmitAnchor(const std::string& url, const std::string& text,
     96                 std::string* out) {
     97   out->append("<a href=\"");
     98   out->append(net::EscapeForHTML(url));
     99   out->append("\">");
    100   out->append(net::EscapeForHTML(text));
    101   out->append("</a>");
    102 }
    103 
    104 void EmitCommandAnchor(const char* label,
    105                        const GURL& base_url,
    106                        const char* command,
    107                        const char* param,
    108                        std::string* out) {
    109   std::string query(command);
    110   query.push_back('=');
    111   query.append(param);
    112   GURL::Replacements replacements;
    113   replacements.SetQuery(query.data(), url::Component(0, query.length()));
    114   GURL command_url = base_url.ReplaceComponents(replacements);
    115   EmitAnchor(command_url.spec(), label, out);
    116 }
    117 
    118 void EmitAppCacheInfo(const GURL& base_url,
    119                       AppCacheServiceImpl* service,
    120                       const AppCacheInfo* info,
    121                       std::string* out) {
    122   std::string manifest_url_base64;
    123   base::Base64Encode(info->manifest_url.spec(), &manifest_url_base64);
    124 
    125   out->append("\n<p>");
    126   out->append(kManifest);
    127   EmitAnchor(info->manifest_url.spec(), info->manifest_url.spec(), out);
    128   out->append("<br/>\n");
    129   if (!service->appcache_policy()->CanLoadAppCache(
    130           info->manifest_url, info->manifest_url)) {
    131     out->append(kFormattedDisabledAppCacheMsg);
    132   }
    133   out->append("\n<br/>\n");
    134   EmitCommandAnchor(kRemoveCacheLabel, base_url,
    135                     kRemoveCacheCommand, manifest_url_base64.c_str(), out);
    136   out->append("&nbsp;&nbsp;");
    137   EmitCommandAnchor(kViewCacheLabel, base_url,
    138                     kViewCacheCommand, manifest_url_base64.c_str(), out);
    139   out->append("\n<br/>\n");
    140   out->append("<ul>");
    141   EmitListItem(
    142       kSize,
    143       base::UTF16ToUTF8(FormatBytesUnlocalized(info->size)),
    144       out);
    145   EmitListItem(
    146       kCreationTime,
    147       base::UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info->creation_time)),
    148       out);
    149   EmitListItem(
    150       kLastUpdateTime,
    151       base::UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info->last_update_time)),
    152       out);
    153   EmitListItem(
    154       kLastAccessTime,
    155       base::UTF16ToUTF8(TimeFormatFriendlyDateAndTime(info->last_access_time)),
    156       out);
    157   out->append("</ul></p></br>\n");
    158 }
    159 
    160 void EmitAppCacheInfoVector(
    161     const GURL& base_url,
    162     AppCacheServiceImpl* service,
    163     const AppCacheInfoVector& appcaches,
    164     std::string* out) {
    165   for (std::vector<AppCacheInfo>::const_iterator info =
    166            appcaches.begin();
    167        info != appcaches.end(); ++info) {
    168     EmitAppCacheInfo(base_url, service, &(*info), out);
    169   }
    170 }
    171 
    172 void EmitTableData(const std::string& data, bool align_right, bool bold,
    173                    std::string* out) {
    174   if (align_right)
    175     out->append("<td align='right'>");
    176   else
    177     out->append("<td>");
    178   if (bold)
    179     out->append("<b>");
    180   out->append(data);
    181   if (bold)
    182     out->append("</b>");
    183   out->append("</td>");
    184 }
    185 
    186 std::string FormFlagsString(const AppCacheResourceInfo& info) {
    187   std::string str;
    188   if (info.is_manifest)
    189     str.append("Manifest, ");
    190   if (info.is_master)
    191     str.append("Master, ");
    192   if (info.is_intercept)
    193     str.append("Intercept, ");
    194   if (info.is_fallback)
    195     str.append("Fallback, ");
    196   if (info.is_explicit)
    197     str.append("Explicit, ");
    198   if (info.is_foreign)
    199     str.append("Foreign, ");
    200   return str;
    201 }
    202 
    203 std::string FormViewEntryAnchor(const GURL& base_url,
    204                                 const GURL& manifest_url, const GURL& entry_url,
    205                                 int64 response_id,
    206                                 int64 group_id) {
    207   std::string manifest_url_base64;
    208   std::string entry_url_base64;
    209   std::string response_id_string;
    210   std::string group_id_string;
    211   base::Base64Encode(manifest_url.spec(), &manifest_url_base64);
    212   base::Base64Encode(entry_url.spec(), &entry_url_base64);
    213   response_id_string = base::Int64ToString(response_id);
    214   group_id_string = base::Int64ToString(group_id);
    215 
    216   std::string query(kViewEntryCommand);
    217   query.push_back('=');
    218   query.append(manifest_url_base64);
    219   query.push_back('|');
    220   query.append(entry_url_base64);
    221   query.push_back('|');
    222   query.append(response_id_string);
    223   query.push_back('|');
    224   query.append(group_id_string);
    225 
    226   GURL::Replacements replacements;
    227   replacements.SetQuery(query.data(), url::Component(0, query.length()));
    228   GURL view_entry_url = base_url.ReplaceComponents(replacements);
    229 
    230   std::string anchor;
    231   EmitAnchor(view_entry_url.spec(), entry_url.spec(), &anchor);
    232   return anchor;
    233 }
    234 
    235 void EmitAppCacheResourceInfoVector(
    236     const GURL& base_url,
    237     const GURL& manifest_url,
    238     const AppCacheResourceInfoVector& resource_infos,
    239     int64 group_id,
    240     std::string* out) {
    241   out->append("<table border='0'>\n");
    242   out->append("<tr>");
    243   EmitTableData("Flags", false, true, out);
    244   EmitTableData("URL", false, true, out);
    245   EmitTableData("Size (headers and data)", true, true, out);
    246   out->append("</tr>\n");
    247   for (AppCacheResourceInfoVector::const_iterator
    248           iter = resource_infos.begin();
    249        iter != resource_infos.end(); ++iter) {
    250     out->append("<tr>");
    251     EmitTableData(FormFlagsString(*iter), false, false, out);
    252     EmitTableData(FormViewEntryAnchor(base_url, manifest_url,
    253                                       iter->url, iter->response_id,
    254                                       group_id),
    255                   false, false, out);
    256     EmitTableData(base::UTF16ToUTF8(FormatBytesUnlocalized(iter->size)),
    257                   true, false, out);
    258     out->append("</tr>\n");
    259   }
    260   out->append("</table>\n");
    261 }
    262 
    263 void EmitResponseHeaders(net::HttpResponseHeaders* headers, std::string* out) {
    264   out->append("<hr><pre>");
    265   out->append(net::EscapeForHTML(headers->GetStatusLine()));
    266   out->push_back('\n');
    267 
    268   void* iter = NULL;
    269   std::string name, value;
    270   while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
    271     out->append(net::EscapeForHTML(name));
    272     out->append(": ");
    273     out->append(net::EscapeForHTML(value));
    274     out->push_back('\n');
    275   }
    276   out->append("</pre>");
    277 }
    278 
    279 void EmitHexDump(const char *buf, size_t buf_len, size_t total_len,
    280                  std::string* out) {
    281   out->append("<hr><pre>");
    282   base::StringAppendF(out, "Showing %d of %d bytes\n\n",
    283                       static_cast<int>(buf_len), static_cast<int>(total_len));
    284   net::ViewCacheHelper::HexDump(buf, buf_len, out);
    285   if (buf_len < total_len)
    286     out->append("\nNote: data is truncated...");
    287   out->append("</pre>");
    288 }
    289 
    290 GURL DecodeBase64URL(const std::string& base64) {
    291   std::string url;
    292   base::Base64Decode(base64, &url);
    293   return GURL(url);
    294 }
    295 
    296 bool ParseQuery(const std::string& query,
    297                 std::string* command, std::string* value) {
    298   size_t position = query.find("=");
    299   if (position == std::string::npos)
    300     return false;
    301   *command = query.substr(0, position);
    302   *value = query.substr(position + 1);
    303   return !command->empty() && !value->empty();
    304 }
    305 
    306 bool SortByManifestUrl(const AppCacheInfo& lhs,
    307                        const AppCacheInfo& rhs) {
    308   return lhs.manifest_url.spec() < rhs.manifest_url.spec();
    309 }
    310 
    311 bool SortByResourceUrl(const AppCacheResourceInfo& lhs,
    312                        const AppCacheResourceInfo& rhs) {
    313   return lhs.url.spec() < rhs.url.spec();
    314 }
    315 
    316 GURL ClearQuery(const GURL& url) {
    317   GURL::Replacements replacements;
    318   replacements.ClearQuery();
    319   return url.ReplaceComponents(replacements);
    320 }
    321 
    322 // Simple base class for the job subclasses defined here.
    323 class BaseInternalsJob : public net::URLRequestSimpleJob,
    324                          public AppCacheServiceImpl::Observer {
    325  protected:
    326   BaseInternalsJob(net::URLRequest* request,
    327                    net::NetworkDelegate* network_delegate,
    328                    AppCacheServiceImpl* service)
    329       : URLRequestSimpleJob(request, network_delegate),
    330         appcache_service_(service),
    331         appcache_storage_(service->storage()) {
    332     appcache_service_->AddObserver(this);
    333   }
    334 
    335   virtual ~BaseInternalsJob() {
    336     appcache_service_->RemoveObserver(this);
    337   }
    338 
    339   virtual void OnServiceReinitialized(
    340       AppCacheStorageReference* old_storage_ref) OVERRIDE {
    341     if (old_storage_ref->storage() == appcache_storage_)
    342       disabled_storage_reference_ = old_storage_ref;
    343   }
    344 
    345   AppCacheServiceImpl* appcache_service_;
    346   AppCacheStorage* appcache_storage_;
    347   scoped_refptr<AppCacheStorageReference> disabled_storage_reference_;
    348 };
    349 
    350 // Job that lists all appcaches in the system.
    351 class MainPageJob : public BaseInternalsJob {
    352  public:
    353   MainPageJob(net::URLRequest* request,
    354               net::NetworkDelegate* network_delegate,
    355               AppCacheServiceImpl* service)
    356       : BaseInternalsJob(request, network_delegate, service),
    357         weak_factory_(this) {
    358   }
    359 
    360   virtual void Start() OVERRIDE {
    361     DCHECK(request_);
    362     info_collection_ = new AppCacheInfoCollection;
    363     appcache_service_->GetAllAppCacheInfo(
    364         info_collection_.get(),
    365         base::Bind(&MainPageJob::OnGotInfoComplete,
    366                    weak_factory_.GetWeakPtr()));
    367   }
    368 
    369   // Produces a page containing the listing
    370   virtual int GetData(std::string* mime_type,
    371                       std::string* charset,
    372                       std::string* out,
    373                       const net::CompletionCallback& callback) const OVERRIDE {
    374     mime_type->assign("text/html");
    375     charset->assign("UTF-8");
    376 
    377     out->clear();
    378     EmitPageStart(out);
    379     if (!info_collection_.get()) {
    380       out->append(kErrorMessage);
    381     } else if (info_collection_->infos_by_origin.empty()) {
    382       out->append(kEmptyAppCachesMessage);
    383     } else {
    384       typedef std::map<GURL, AppCacheInfoVector> InfoByOrigin;
    385       AppCacheInfoVector appcaches;
    386       for (InfoByOrigin::const_iterator origin =
    387                info_collection_->infos_by_origin.begin();
    388            origin != info_collection_->infos_by_origin.end(); ++origin) {
    389         appcaches.insert(appcaches.end(),
    390                          origin->second.begin(), origin->second.end());
    391       }
    392       std::sort(appcaches.begin(), appcaches.end(), SortByManifestUrl);
    393 
    394       GURL base_url = ClearQuery(request_->url());
    395       EmitAppCacheInfoVector(base_url, appcache_service_, appcaches, out);
    396     }
    397     EmitPageEnd(out);
    398     return net::OK;
    399   }
    400 
    401  private:
    402   virtual ~MainPageJob() {}
    403 
    404   void OnGotInfoComplete(int rv) {
    405     if (rv != net::OK)
    406       info_collection_ = NULL;
    407     StartAsync();
    408   }
    409 
    410   scoped_refptr<AppCacheInfoCollection> info_collection_;
    411   base::WeakPtrFactory<MainPageJob> weak_factory_;
    412   DISALLOW_COPY_AND_ASSIGN(MainPageJob);
    413 };
    414 
    415 // Job that redirects back to the main appcache internals page.
    416 class RedirectToMainPageJob : public BaseInternalsJob {
    417  public:
    418   RedirectToMainPageJob(net::URLRequest* request,
    419                         net::NetworkDelegate* network_delegate,
    420                         AppCacheServiceImpl* service)
    421       : BaseInternalsJob(request, network_delegate, service) {}
    422 
    423   virtual int GetData(std::string* mime_type,
    424                       std::string* charset,
    425                       std::string* data,
    426                       const net::CompletionCallback& callback) const OVERRIDE {
    427     return net::OK;  // IsRedirectResponse induces a redirect.
    428   }
    429 
    430   virtual bool IsRedirectResponse(GURL* location,
    431                                   int* http_status_code) OVERRIDE {
    432     *location = ClearQuery(request_->url());
    433     *http_status_code = 307;
    434     return true;
    435   }
    436 
    437  protected:
    438   virtual ~RedirectToMainPageJob() {}
    439 };
    440 
    441 // Job that removes an appcache and then redirects back to the main page.
    442 class RemoveAppCacheJob : public RedirectToMainPageJob {
    443  public:
    444   RemoveAppCacheJob(
    445       net::URLRequest* request,
    446       net::NetworkDelegate* network_delegate,
    447       AppCacheServiceImpl* service,
    448       const GURL& manifest_url)
    449       : RedirectToMainPageJob(request, network_delegate, service),
    450         manifest_url_(manifest_url),
    451         weak_factory_(this) {
    452   }
    453 
    454   virtual void Start() OVERRIDE {
    455     DCHECK(request_);
    456 
    457     appcache_service_->DeleteAppCacheGroup(
    458         manifest_url_,base::Bind(&RemoveAppCacheJob::OnDeleteAppCacheComplete,
    459                                  weak_factory_.GetWeakPtr()));
    460   }
    461 
    462  private:
    463   virtual ~RemoveAppCacheJob() {}
    464 
    465   void OnDeleteAppCacheComplete(int rv) {
    466     StartAsync();  // Causes the base class to redirect.
    467   }
    468 
    469   GURL manifest_url_;
    470   base::WeakPtrFactory<RemoveAppCacheJob> weak_factory_;
    471 };
    472 
    473 
    474 // Job shows the details of a particular manifest url.
    475 class ViewAppCacheJob : public BaseInternalsJob,
    476                         public AppCacheStorage::Delegate {
    477  public:
    478   ViewAppCacheJob(
    479       net::URLRequest* request,
    480       net::NetworkDelegate* network_delegate,
    481       AppCacheServiceImpl* service,
    482       const GURL& manifest_url)
    483       : BaseInternalsJob(request, network_delegate, service),
    484         manifest_url_(manifest_url) {}
    485 
    486   virtual void Start() OVERRIDE {
    487     DCHECK(request_);
    488     appcache_storage_->LoadOrCreateGroup(manifest_url_, this);
    489   }
    490 
    491   // Produces a page containing the entries listing.
    492   virtual int GetData(std::string* mime_type,
    493                       std::string* charset,
    494                       std::string* out,
    495                       const net::CompletionCallback& callback) const OVERRIDE {
    496     mime_type->assign("text/html");
    497     charset->assign("UTF-8");
    498     out->clear();
    499     EmitPageStart(out);
    500     if (appcache_info_.manifest_url.is_empty()) {
    501       out->append(kManifestNotFoundMessage);
    502     } else {
    503       GURL base_url = ClearQuery(request_->url());
    504       EmitAppCacheInfo(base_url, appcache_service_, &appcache_info_, out);
    505       EmitAppCacheResourceInfoVector(base_url,
    506                                      manifest_url_,
    507                                      resource_infos_,
    508                                      appcache_info_.group_id,
    509                                      out);
    510     }
    511     EmitPageEnd(out);
    512     return net::OK;
    513   }
    514 
    515  private:
    516   virtual ~ViewAppCacheJob() {
    517     appcache_storage_->CancelDelegateCallbacks(this);
    518   }
    519 
    520   // AppCacheStorage::Delegate override
    521   virtual void OnGroupLoaded(
    522       AppCacheGroup* group, const GURL& manifest_url) OVERRIDE {
    523     DCHECK_EQ(manifest_url_, manifest_url);
    524     if (group && group->newest_complete_cache()) {
    525       appcache_info_.manifest_url = manifest_url;
    526       appcache_info_.group_id = group->group_id();
    527       appcache_info_.size = group->newest_complete_cache()->cache_size();
    528       appcache_info_.creation_time = group->creation_time();
    529       appcache_info_.last_update_time =
    530           group->newest_complete_cache()->update_time();
    531       appcache_info_.last_access_time = base::Time::Now();
    532       group->newest_complete_cache()->ToResourceInfoVector(&resource_infos_);
    533       std::sort(resource_infos_.begin(), resource_infos_.end(),
    534                 SortByResourceUrl);
    535     }
    536     StartAsync();
    537   }
    538 
    539   GURL manifest_url_;
    540   AppCacheInfo appcache_info_;
    541   AppCacheResourceInfoVector resource_infos_;
    542   DISALLOW_COPY_AND_ASSIGN(ViewAppCacheJob);
    543 };
    544 
    545 // Job that shows the details of a particular cached resource.
    546 class ViewEntryJob : public BaseInternalsJob,
    547                      public AppCacheStorage::Delegate {
    548  public:
    549   ViewEntryJob(
    550       net::URLRequest* request,
    551       net::NetworkDelegate* network_delegate,
    552       AppCacheServiceImpl* service,
    553       const GURL& manifest_url,
    554       const GURL& entry_url,
    555       int64 response_id, int64 group_id)
    556       : BaseInternalsJob(request, network_delegate, service),
    557         manifest_url_(manifest_url), entry_url_(entry_url),
    558         response_id_(response_id), group_id_(group_id), amount_read_(0) {
    559   }
    560 
    561   virtual void Start() OVERRIDE {
    562     DCHECK(request_);
    563     appcache_storage_->LoadResponseInfo(
    564         manifest_url_, group_id_, response_id_, this);
    565   }
    566 
    567   // Produces a page containing the response headers and data.
    568   virtual int GetData(std::string* mime_type,
    569                       std::string* charset,
    570                       std::string* out,
    571                       const net::CompletionCallback& callback) const OVERRIDE {
    572     mime_type->assign("text/html");
    573     charset->assign("UTF-8");
    574     out->clear();
    575     EmitPageStart(out);
    576     EmitAnchor(entry_url_.spec(), entry_url_.spec(), out);
    577     out->append("<br/>\n");
    578     if (response_info_.get()) {
    579       if (response_info_->http_response_info())
    580         EmitResponseHeaders(response_info_->http_response_info()->headers.get(),
    581                             out);
    582       else
    583         out->append("Failed to read response headers.<br>");
    584 
    585       if (response_data_.get()) {
    586         EmitHexDump(response_data_->data(),
    587                     amount_read_,
    588                     response_info_->response_data_size(),
    589                     out);
    590       } else {
    591         out->append("Failed to read response data.<br>");
    592       }
    593     } else {
    594       out->append("Failed to read response headers and data.<br>");
    595     }
    596     EmitPageEnd(out);
    597     return net::OK;
    598   }
    599 
    600  private:
    601   virtual ~ViewEntryJob() {
    602     appcache_storage_->CancelDelegateCallbacks(this);
    603   }
    604 
    605   virtual void OnResponseInfoLoaded(
    606       AppCacheResponseInfo* response_info, int64 response_id) OVERRIDE {
    607     if (!response_info) {
    608       StartAsync();
    609       return;
    610     }
    611     response_info_ = response_info;
    612 
    613     // Read the response data, truncating if its too large.
    614     const int64 kLimit = 100 * 1000;
    615     int64 amount_to_read =
    616         std::min(kLimit, response_info->response_data_size());
    617     response_data_ = new net::IOBuffer(amount_to_read);
    618 
    619     reader_.reset(appcache_storage_->CreateResponseReader(
    620         manifest_url_, group_id_, response_id_));
    621     reader_->ReadData(
    622         response_data_.get(),
    623         amount_to_read,
    624         base::Bind(&ViewEntryJob::OnReadComplete, base::Unretained(this)));
    625   }
    626 
    627   void OnReadComplete(int result) {
    628     reader_.reset();
    629     amount_read_ = result;
    630     if (result < 0)
    631       response_data_ = NULL;
    632     StartAsync();
    633   }
    634 
    635   GURL manifest_url_;
    636   GURL entry_url_;
    637   int64 response_id_;
    638   int64 group_id_;
    639   scoped_refptr<AppCacheResponseInfo> response_info_;
    640   scoped_refptr<net::IOBuffer> response_data_;
    641   int amount_read_;
    642   scoped_ptr<AppCacheResponseReader> reader_;
    643 };
    644 
    645 }  // namespace
    646 
    647 net::URLRequestJob* ViewAppCacheInternalsJobFactory::CreateJobForRequest(
    648     net::URLRequest* request,
    649     net::NetworkDelegate* network_delegate,
    650     AppCacheServiceImpl* service) {
    651   if (!request->url().has_query())
    652     return new MainPageJob(request, network_delegate, service);
    653 
    654   std::string command;
    655   std::string param;
    656   ParseQuery(request->url().query(), &command, &param);
    657 
    658   if (command == kRemoveCacheCommand)
    659     return new RemoveAppCacheJob(request, network_delegate, service,
    660                                  DecodeBase64URL(param));
    661 
    662   if (command == kViewCacheCommand)
    663     return new ViewAppCacheJob(request, network_delegate, service,
    664                                DecodeBase64URL(param));
    665 
    666   std::vector<std::string> tokens;
    667   int64 response_id;
    668   int64 group_id;
    669   if (command == kViewEntryCommand && Tokenize(param, "|", &tokens) == 4u &&
    670       base::StringToInt64(tokens[2], &response_id) &&
    671       base::StringToInt64(tokens[3], &group_id)) {
    672     return new ViewEntryJob(request, network_delegate, service,
    673                             DecodeBase64URL(tokens[0]),  // manifest url
    674                             DecodeBase64URL(tokens[1]),  // entry url
    675                             response_id, group_id);
    676   }
    677 
    678   return new RedirectToMainPageJob(request, network_delegate, service);
    679 }
    680 
    681 }  // namespace content
    682