#include "content/shell/browser/shell_file_select_helper.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "net/base/filename_util.h"
#include "net/base/mime_util.h"
#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/shell_dialogs/select_file_policy.h"
#include "ui/shell_dialogs/selected_file_info.h"
namespace content {
namespace {
std::unique_ptr<ui::SelectFileDialog::FileTypeInfo> GetFileTypesFromAcceptType(
const std::vector<std::u16string>& accept_types) {
auto base_file_type = std::make_unique<ui::SelectFileDialog::FileTypeInfo>();
if (accept_types.empty()) {
return base_file_type;
}
auto file_type =
std::make_unique<ui::SelectFileDialog::FileTypeInfo>(*base_file_type);
file_type->extensions.resize(1);
std::vector<base::FilePath::StringType>* extensions =
&file_type->extensions.back();
size_t valid_type_count = 0;
for (const auto& accept_type : accept_types) {
size_t old_extension_size = extensions->size();
if (accept_type[0] == '.') {
base::FilePath::StringType ext =
base::FilePath::FromUTF16Unsafe(accept_type).value();
extensions->push_back(ext.substr(1));
} else {
if (!base::IsStringASCII(accept_type)) {
continue;
}
std::string ascii_type = base::UTF16ToASCII(accept_type);
net::GetExtensionsForMimeType(ascii_type, extensions);
}
if (extensions->size() > old_extension_size) {
valid_type_count++;
}
}
if (valid_type_count == 0) {
return base_file_type;
}
return file_type;
}
}
struct ShellFileSelectHelper::ActiveDirectoryEnumeration {
explicit ActiveDirectoryEnumeration(const base::FilePath& path)
: path_(path) {}
std::unique_ptr<net::DirectoryLister> lister_;
const base::FilePath path_;
std::vector<base::FilePath> results_;
};
void ShellFileSelectHelper::RunFileChooser(
content::RenderFrameHost* render_frame_host,
scoped_refptr<FileSelectListener> listener,
const blink::mojom::FileChooserParams& params) {
scoped_refptr<ShellFileSelectHelper> file_select_helper(
new ShellFileSelectHelper());
file_select_helper->RunFileChooser(render_frame_host, std::move(listener),
params.Clone());
}
ShellFileSelectHelper::ShellFileSelectHelper() = default;
ShellFileSelectHelper::~ShellFileSelectHelper() {
if (select_file_dialog_) {
select_file_dialog_->ListenerDestroyed();
}
}
void ShellFileSelectHelper::RunFileChooser(
content::RenderFrameHost* render_frame_host,
scoped_refptr<FileSelectListener> listener,
blink::mojom::FileChooserParamsPtr params) {
DCHECK(!web_contents_);
DCHECK(listener);
DCHECK(!listener_);
DCHECK(!select_file_dialog_);
select_file_dialog_ = ui::SelectFileDialog::Create(this, nullptr);
if (!select_file_dialog_) {
listener->FileSelectionCanceled();
return;
}
listener_ = std::move(listener);
web_contents_ = content::WebContents::FromRenderFrameHost(render_frame_host)
->GetWeakPtr();
select_file_types_ = GetFileTypesFromAcceptType(params->accept_types);
select_file_types_->allowed_paths =
params->need_local_path ? ui::SelectFileDialog::FileTypeInfo::NATIVE_PATH
: ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
int file_type_index =
select_file_types_ && !select_file_types_->extensions.empty() ? 1 : 0;
dialog_mode_ = params->mode;
switch (params->mode) {
case blink::mojom::FileChooserParams::Mode::kOpen:
dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
break;
case blink::mojom::FileChooserParams::Mode::kOpenMultiple:
dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
break;
case blink::mojom::FileChooserParams::Mode::kUploadFolder:
dialog_type_ = ui::SelectFileDialog::SELECT_UPLOAD_FOLDER;
break;
case blink::mojom::FileChooserParams::Mode::kSave:
dialog_type_ = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
break;
default:
dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
NOTREACHED();
}
gfx::NativeWindow owning_window = web_contents_->GetTopLevelNativeWindow();
select_file_dialog_->SelectFile(dialog_type_, std::u16string(),
base::FilePath(), select_file_types_.get(),
file_type_index, base::FilePath::StringType(),
owning_window, nullptr);
AddRef();
}
void ShellFileSelectHelper::RunFileChooserEnd() {
if (listener_) {
listener_->FileSelectionCanceled();
}
select_file_dialog_->ListenerDestroyed();
select_file_dialog_.reset();
Release();
}
void ShellFileSelectHelper::FileSelected(const ui::SelectedFileInfo& file,
int index) {
if (dialog_type_ == ui::SelectFileDialog::SELECT_UPLOAD_FOLDER) {
StartNewEnumeration(file.local_path);
return;
}
ConvertToFileChooserFileInfoList({file});
}
void ShellFileSelectHelper::MultiFilesSelected(
const std::vector<ui::SelectedFileInfo>& files) {
ConvertToFileChooserFileInfoList(files);
}
void ShellFileSelectHelper::FileSelectionCanceled() {
RunFileChooserEnd();
}
void ShellFileSelectHelper::StartNewEnumeration(const base::FilePath& path) {
base_dir_ = path;
auto entry = std::make_unique<ActiveDirectoryEnumeration>(path);
entry->lister_ = base::WrapUnique(new net::DirectoryLister(
path, net::DirectoryLister::NO_SORT_RECURSIVE, this));
entry->lister_->Start();
directory_enumeration_ = std::move(entry);
}
void ShellFileSelectHelper::OnListFile(
const net::DirectoryLister::DirectoryListerData& data) {
if (data.info.IsDirectory()) {
return;
}
directory_enumeration_->results_.push_back(data.path);
}
void ShellFileSelectHelper::OnListDone(int error) {
if (!web_contents_) {
RunFileChooserEnd();
return;
}
std::unique_ptr<ActiveDirectoryEnumeration> entry =
std::move(directory_enumeration_);
if (error) {
FileSelectionCanceled();
return;
}
std::vector<ui::SelectedFileInfo> selected_files =
ui::FilePathListToSelectedFileInfoList(entry->results_);
std::vector<blink::mojom::FileChooserFileInfoPtr> chooser_files;
for (const auto& file_path : entry->results_) {
chooser_files.push_back(blink::mojom::FileChooserFileInfo::NewNativeFile(
blink::mojom::NativeFileInfo::New(file_path, std::u16string(),
std::vector<std::u16string>())));
}
listener_->FileSelected(std::move(chooser_files), base_dir_,
blink::mojom::FileChooserParams::Mode::kUploadFolder);
listener_.reset();
RunFileChooserEnd();
}
void ShellFileSelectHelper::ConvertToFileChooserFileInfoList(
const std::vector<ui::SelectedFileInfo>& files) {
if (!web_contents_) {
RunFileChooserEnd();
return;
}
std::vector<blink::mojom::FileChooserFileInfoPtr> chooser_files;
for (const auto& file : files) {
chooser_files.push_back(blink::mojom::FileChooserFileInfo::NewNativeFile(
blink::mojom::NativeFileInfo::New(
file.local_path, base::FilePath(file.display_name).AsUTF16Unsafe(),
std::vector<std::u16string>())));
}
listener_->FileSelected(std::move(chooser_files), base::FilePath(),
dialog_mode_);
listener_ = nullptr;
RunFileChooserEnd();
}
}