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