blob: 21e0e4ce9daa87319701ece06d045e86b188d8f6 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/page_capture/page_capture_api.h"
#include <limits>
#include <memory>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file_util.h"
#include "base/memory/ptr_util.h"
#include "base/task_scheduler/post_task.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/mhtml_generation_params.h"
#include "extensions/common/extension_messages.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/extensions/public_session_permission_helper.h"
#endif
using content::BrowserThread;
using content::ChildProcessSecurityPolicy;
using content::WebContents;
using extensions::PageCaptureSaveAsMHTMLFunction;
using storage::ShareableFileReference;
namespace SaveAsMHTML = extensions::api::page_capture::SaveAsMHTML;
namespace {
const char kFileTooBigError[] = "The MHTML file generated is too big.";
const char kMHTMLGenerationFailedError[] = "Failed to generate MHTML.";
const char kTemporaryFileError[] = "Failed to create a temporary file.";
const char kTabClosedError[] = "Cannot find the tab for this request.";
#if defined(OS_CHROMEOS)
const char kUserDenied[] = "User denied request.";
#endif
constexpr base::TaskTraits kCreateTemporaryFileTaskTraits = {
// Requires IO.
base::MayBlock(),
// TaskPriority: Inherit.
// TaskShutdownBehavior: TemporaryFileCreated*() called from
// CreateTemporaryFile() might access global variable, so use
// SKIP_ON_SHUTDOWN. See ShareableFileReference::GetOrCreate().
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN};
void ClearFileReferenceOnIOThread(
scoped_refptr<storage::ShareableFileReference>) {}
} // namespace
static PageCaptureSaveAsMHTMLFunction::TestDelegate* test_delegate_ = NULL;
PageCaptureSaveAsMHTMLFunction::PageCaptureSaveAsMHTMLFunction() {
}
PageCaptureSaveAsMHTMLFunction::~PageCaptureSaveAsMHTMLFunction() {
if (mhtml_file_.get()) {
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::BindOnce(&ClearFileReferenceOnIOThread,
base::Passed(&mhtml_file_)));
}
}
void PageCaptureSaveAsMHTMLFunction::SetTestDelegate(TestDelegate* delegate) {
test_delegate_ = delegate;
}
bool PageCaptureSaveAsMHTMLFunction::RunAsync() {
params_ = SaveAsMHTML::Params::Create(*args_);
EXTENSION_FUNCTION_VALIDATE(params_.get());
AddRef(); // Balanced in ReturnFailure/ReturnSuccess()
// In Public Sessions, extensions (and apps) are force-installed by admin
// policy so the user does not get a chance to review the permissions for
// these extensions. This is not acceptable from a security/privacy
// standpoint, so when an extension uses the PageCapture API for the first
// time, we show the user a dialog where they can choose whether to allow the
// extension access to the API.
#if defined(OS_CHROMEOS)
if (profiles::IsPublicSession()) {
WebContents* web_contents = GetWebContents();
if (!web_contents) {
ReturnFailure(kTabClosedError);
return true;
}
// This Unretained is safe because this object is Released() in
// OnMessageReceived which gets called at some point after callback is run.
auto callback =
base::Bind(&PageCaptureSaveAsMHTMLFunction::ResolvePermissionRequest,
base::Unretained(this));
permission_helper::HandlePermissionRequest(
*extension(), {APIPermission::kPageCapture}, web_contents, callback,
permission_helper::PromptFactory());
return true;
}
#endif
base::PostTaskWithTraits(
FROM_HERE, kCreateTemporaryFileTaskTraits,
base::BindOnce(&PageCaptureSaveAsMHTMLFunction::CreateTemporaryFile,
this));
return true;
}
bool PageCaptureSaveAsMHTMLFunction::OnMessageReceived(
const IPC::Message& message) {
if (message.type() != ExtensionHostMsg_ResponseAck::ID)
return false;
int message_request_id;
base::PickleIterator iter(message);
if (!iter.ReadInt(&message_request_id)) {
NOTREACHED() << "malformed extension message";
return true;
}
if (message_request_id != request_id())
return false;
// The extension process has processed the response and has created a
// reference to the blob, it is safe for us to go away.
Release(); // Balanced in Run()
return true;
}
#if defined(OS_CHROMEOS)
void PageCaptureSaveAsMHTMLFunction::ResolvePermissionRequest(
const PermissionIDSet& allowed_permissions) {
if (allowed_permissions.ContainsID(APIPermission::kPageCapture)) {
base::PostTaskWithTraits(
FROM_HERE, kCreateTemporaryFileTaskTraits,
base::BindOnce(&PageCaptureSaveAsMHTMLFunction::CreateTemporaryFile,
this));
} else {
ReturnFailure(kUserDenied);
}
}
#endif
void PageCaptureSaveAsMHTMLFunction::CreateTemporaryFile() {
base::ThreadRestrictions::AssertIOAllowed();
bool success = base::CreateTemporaryFile(&mhtml_path_);
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&PageCaptureSaveAsMHTMLFunction::TemporaryFileCreatedOnIO,
this, success));
}
void PageCaptureSaveAsMHTMLFunction::TemporaryFileCreatedOnIO(bool success) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (success) {
// Setup a ShareableFileReference so the temporary file gets deleted
// once it is no longer used.
mhtml_file_ = ShareableFileReference::GetOrCreate(
mhtml_path_, ShareableFileReference::DELETE_ON_FINAL_RELEASE,
base::CreateSequencedTaskRunnerWithTraits(
{// Requires IO.
base::MayBlock(),
// TaskPriority: Inherit.
// Because we are using DELETE_ON_FINAL_RELEASE here, the
// storage::ScopedFile inside ShareableFileReference requires
// a shutdown blocking task runner to ensure that its deletion
// task runs.
base::TaskShutdownBehavior::BLOCK_SHUTDOWN})
.get());
}
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&PageCaptureSaveAsMHTMLFunction::TemporaryFileCreatedOnUI,
this, success));
}
void PageCaptureSaveAsMHTMLFunction::TemporaryFileCreatedOnUI(bool success) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!success) {
ReturnFailure(kTemporaryFileError);
return;
}
if (test_delegate_)
test_delegate_->OnTemporaryFileCreated(mhtml_path_);
WebContents* web_contents = GetWebContents();
if (!web_contents) {
ReturnFailure(kTabClosedError);
return;
}
web_contents->GenerateMHTML(
content::MHTMLGenerationParams(mhtml_path_),
base::Bind(&PageCaptureSaveAsMHTMLFunction::MHTMLGenerated, this));
}
void PageCaptureSaveAsMHTMLFunction::MHTMLGenerated(int64_t mhtml_file_size) {
if (mhtml_file_size <= 0) {
ReturnFailure(kMHTMLGenerationFailedError);
return;
}
if (mhtml_file_size > std::numeric_limits<int>::max()) {
ReturnFailure(kFileTooBigError);
return;
}
ReturnSuccess(mhtml_file_size);
}
void PageCaptureSaveAsMHTMLFunction::ReturnFailure(const std::string& error) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
error_ = error;
SendResponse(false);
// Must not Release() here, OnMessageReceived will call it eventually.
}
void PageCaptureSaveAsMHTMLFunction::ReturnSuccess(int64_t file_size) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
WebContents* web_contents = GetWebContents();
if (!web_contents || !render_frame_host()) {
ReturnFailure(kTabClosedError);
return;
}
int child_id = render_frame_host()->GetProcess()->GetID();
ChildProcessSecurityPolicy::GetInstance()->GrantReadFile(
child_id, mhtml_path_);
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->SetString("mhtmlFilePath", mhtml_path_.value());
dict->SetInteger("mhtmlFileLength", file_size);
SetResult(std::move(dict));
SendResponse(true);
// Note that we'll wait for a response ack message received in
// OnMessageReceived before we call Release() (to prevent the blob file from
// being deleted).
}
WebContents* PageCaptureSaveAsMHTMLFunction::GetWebContents() {
Browser* browser = NULL;
content::WebContents* web_contents = NULL;
if (!ExtensionTabUtil::GetTabById(params_->details.tab_id,
GetProfile(),
include_incognito(),
&browser,
NULL,
&web_contents,
NULL)) {
return NULL;
}
return web_contents;
}