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