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 "chrome/browser/extensions/api/page_capture/page_capture_api.h"
6
7#include <limits>
8
9#include "base/bind.h"
10#include "base/files/file_util.h"
11#include "chrome/browser/browser_process.h"
12#include "chrome/browser/extensions/extension_tab_util.h"
13#include "chrome/browser/profiles/profile.h"
14#include "content/public/browser/child_process_security_policy.h"
15#include "content/public/browser/notification_details.h"
16#include "content/public/browser/notification_source.h"
17#include "content/public/browser/notification_types.h"
18#include "content/public/browser/render_process_host.h"
19#include "content/public/browser/render_view_host.h"
20#include "content/public/browser/web_contents.h"
21#include "extensions/common/extension_messages.h"
22
23using content::BrowserThread;
24using content::ChildProcessSecurityPolicy;
25using content::WebContents;
26using extensions::PageCaptureSaveAsMHTMLFunction;
27using storage::ShareableFileReference;
28
29namespace SaveAsMHTML = extensions::api::page_capture::SaveAsMHTML;
30
31namespace {
32
33const char kFileTooBigError[] = "The MHTML file generated is too big.";
34const char kMHTMLGenerationFailedError[] = "Failed to generate MHTML.";
35const char kTemporaryFileError[] = "Failed to create a temporary file.";
36const char kTabClosedError[] = "Cannot find the tab for this request.";
37
38}  // namespace
39
40static PageCaptureSaveAsMHTMLFunction::TestDelegate* test_delegate_ = NULL;
41
42PageCaptureSaveAsMHTMLFunction::PageCaptureSaveAsMHTMLFunction() {
43}
44
45PageCaptureSaveAsMHTMLFunction::~PageCaptureSaveAsMHTMLFunction() {
46  if (mhtml_file_.get()) {
47    storage::ShareableFileReference* to_release = mhtml_file_.get();
48    to_release->AddRef();
49    mhtml_file_ = NULL;
50    BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, to_release);
51  }
52}
53
54void PageCaptureSaveAsMHTMLFunction::SetTestDelegate(TestDelegate* delegate) {
55  test_delegate_ = delegate;
56}
57
58bool PageCaptureSaveAsMHTMLFunction::RunAsync() {
59  params_ = SaveAsMHTML::Params::Create(*args_);
60  EXTENSION_FUNCTION_VALIDATE(params_.get());
61
62  AddRef();  // Balanced in ReturnFailure/ReturnSuccess()
63
64  BrowserThread::PostTask(
65      BrowserThread::FILE, FROM_HERE,
66      base::Bind(&PageCaptureSaveAsMHTMLFunction::CreateTemporaryFile, this));
67  return true;
68}
69
70bool PageCaptureSaveAsMHTMLFunction::OnMessageReceived(
71    const IPC::Message& message) {
72  if (message.type() != ExtensionHostMsg_ResponseAck::ID)
73    return false;
74
75  int message_request_id;
76  PickleIterator iter(message);
77  if (!message.ReadInt(&iter, &message_request_id)) {
78    NOTREACHED() << "malformed extension message";
79    return true;
80  }
81
82  if (message_request_id != request_id())
83    return false;
84
85  // The extension process has processed the response and has created a
86  // reference to the blob, it is safe for us to go away.
87  Release();  // Balanced in Run()
88
89  return true;
90}
91
92void PageCaptureSaveAsMHTMLFunction::CreateTemporaryFile() {
93  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
94  bool success = base::CreateTemporaryFile(&mhtml_path_);
95  BrowserThread::PostTask(
96      BrowserThread::IO, FROM_HERE,
97      base::Bind(&PageCaptureSaveAsMHTMLFunction::TemporaryFileCreated, this,
98                 success));
99}
100
101void PageCaptureSaveAsMHTMLFunction::TemporaryFileCreated(bool success) {
102  if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
103    if (success) {
104      // Setup a ShareableFileReference so the temporary file gets deleted
105      // once it is no longer used.
106      mhtml_file_ = ShareableFileReference::GetOrCreate(
107          mhtml_path_,
108          ShareableFileReference::DELETE_ON_FINAL_RELEASE,
109          BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)
110              .get());
111    }
112    BrowserThread::PostTask(
113        BrowserThread::UI, FROM_HERE,
114        base::Bind(&PageCaptureSaveAsMHTMLFunction::TemporaryFileCreated, this,
115                   success));
116    return;
117  }
118
119  DCHECK_CURRENTLY_ON(BrowserThread::UI);
120  if (!success) {
121    ReturnFailure(kTemporaryFileError);
122    return;
123  }
124
125  if (test_delegate_)
126    test_delegate_->OnTemporaryFileCreated(mhtml_path_);
127
128  WebContents* web_contents = GetWebContents();
129  if (!web_contents) {
130    ReturnFailure(kTabClosedError);
131    return;
132  }
133
134  web_contents->GenerateMHTML(
135      mhtml_path_,
136      base::Bind(&PageCaptureSaveAsMHTMLFunction::MHTMLGenerated, this));
137}
138
139void PageCaptureSaveAsMHTMLFunction::MHTMLGenerated(
140    int64 mhtml_file_size) {
141  if (mhtml_file_size <= 0) {
142    ReturnFailure(kMHTMLGenerationFailedError);
143    return;
144  }
145
146  if (mhtml_file_size > std::numeric_limits<int>::max()) {
147    ReturnFailure(kFileTooBigError);
148    return;
149  }
150
151  ReturnSuccess(mhtml_file_size);
152}
153
154void PageCaptureSaveAsMHTMLFunction::ReturnFailure(const std::string& error) {
155  DCHECK_CURRENTLY_ON(BrowserThread::UI);
156
157  error_ = error;
158
159  SendResponse(false);
160
161  Release();  // Balanced in Run()
162}
163
164void PageCaptureSaveAsMHTMLFunction::ReturnSuccess(int64 file_size) {
165  DCHECK_CURRENTLY_ON(BrowserThread::UI);
166
167  WebContents* web_contents = GetWebContents();
168  if (!web_contents || !render_view_host()) {
169    ReturnFailure(kTabClosedError);
170    return;
171  }
172
173  int child_id = render_view_host()->GetProcess()->GetID();
174  ChildProcessSecurityPolicy::GetInstance()->GrantReadFile(
175      child_id, mhtml_path_);
176
177  base::DictionaryValue* dict = new base::DictionaryValue();
178  SetResult(dict);
179  dict->SetString("mhtmlFilePath", mhtml_path_.value());
180  dict->SetInteger("mhtmlFileLength", file_size);
181
182  SendResponse(true);
183
184  // Note that we'll wait for a response ack message received in
185  // OnMessageReceived before we call Release() (to prevent the blob file from
186  // being deleted).
187}
188
189WebContents* PageCaptureSaveAsMHTMLFunction::GetWebContents() {
190  Browser* browser = NULL;
191  content::WebContents* web_contents = NULL;
192
193  if (!ExtensionTabUtil::GetTabById(params_->details.tab_id,
194                                    GetProfile(),
195                                    include_incognito(),
196                                    &browser,
197                                    NULL,
198                                    &web_contents,
199                                    NULL)) {
200    return NULL;
201  }
202  return web_contents;
203}
204