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