blob: 458c4fbe4629d69df7a39dbd306099ad295f5a2e [file] [log] [blame]
// Copyright 2017 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 "components/exo/data_source.h"
#include <limits>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/files/file_util.h"
#include "base/i18n/character_encoding.h"
#include "base/i18n/icu_string_conversions.h"
#include "base/optional.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/string_util.h"
#include "base/task/post_task.h"
#include "base/task/thread_pool.h"
#include "components/exo/data_source_delegate.h"
#include "components/exo/data_source_observer.h"
#include "components/exo/mime_utils.h"
#include "net/base/mime_util.h"
#include "third_party/blink/public/common/mime_util/mime_util.h"
#include "third_party/icu/source/common/unicode/ucnv.h"
#include "ui/base/clipboard/clipboard_constants.h"
namespace exo {
namespace {
constexpr char kTextPlain[] = "text/plain";
constexpr char kTextRTF[] = "text/rtf";
constexpr char kTextHTML[] = "text/html";
constexpr char kTextUriList[] = "text/uri-list";
constexpr char kUtfPrefix[] = "UTF";
constexpr char kEncoding16[] = "16";
constexpr char kEncodingASCII[] = "ASCII";
constexpr char kUTF16Unspecified[] = "UTF-16";
constexpr char kUTF16LittleEndian[] = "UTF-16LE";
constexpr char kUTF16BigEndian[] = "UTF-16BE";
constexpr uint8_t kByteOrderMark[] = {0xFE, 0xFF};
constexpr int kByteOrderMarkSize = sizeof(kByteOrderMark);
constexpr char kImageBitmap[] = "image/bmp";
constexpr char kImagePNG[] = "image/png";
constexpr char kImageAPNG[] = "image/apng";
base::Optional<std::vector<uint8_t>> ReadDataOnWorkerThread(base::ScopedFD fd) {
constexpr size_t kChunkSize = 1024;
std::vector<uint8_t> bytes;
while (true) {
uint8_t chunk[kChunkSize];
ssize_t bytes_read = HANDLE_EINTR(read(fd.get(), chunk, kChunkSize));
if (bytes_read > 0) {
bytes.insert(bytes.end(), chunk, chunk + bytes_read);
continue;
}
if (!bytes_read)
return bytes;
if (bytes_read < 0) {
PLOG(ERROR) << "Failed to read selection data from clipboard";
return base::nullopt;
}
}
}
// Map a named character set to an integer ranking, lower is better. This is an
// implementation detail of DataSource::GetPreferredMimeTypes and should not be
// considered to have any greater meaning. In particular, these are not expected
// to remain stable over time.
int GetCharsetRank(const std::string& charset_input) {
std::string charset = base::ToUpperASCII(charset_input);
// Prefer UTF-16 over all other encodings, because that's what the clipboard
// interface takes as input; then other unicode encodings; then any non-ASCII
// encoding, because most or all such encodings are super-sets of ASCII;
// finally, only use ASCII if nothing else is available.
if (base::StartsWith(charset, kUtfPrefix, base::CompareCase::SENSITIVE)) {
if (charset.find(kEncoding16) != std::string::npos)
return 0;
return 1;
} else if (charset.find(kEncodingASCII) == std::string::npos) {
return 2;
}
return 3;
}
// Map an image MIME type to an integer ranking, lower is better. This is an
// implementation detail of DataSource::GetPreferredMimeTypes and should not be
// considered to have any greater meaning. In particular, these are not expected
// to remain stable over time.
int GetImageTypeRank(const std::string& mime_type) {
// Prefer bitmaps most of all to avoid needing to decode the image, followed
// by other lossless formats, followed by any other format we support.
if (net::MatchesMimeType(std::string(kImageBitmap), mime_type))
return 0;
if (net::MatchesMimeType(std::string(kImagePNG), mime_type) ||
net::MatchesMimeType(std::string(kImageAPNG), mime_type))
return 1;
return 2;
}
base::string16 CodepageToUTF16(const std::vector<uint8_t>& data,
const std::string& charset_input) {
base::string16 output;
base::StringPiece piece(reinterpret_cast<const char*>(data.data()),
data.size());
const char* charset = charset_input.c_str();
// Despite claims in the documentation to the contrary, the ICU UTF-16
// converter does not automatically detect and interpret the byte order
// mark. Therefore, we must do this ourselves.
if (!ucnv_compareNames(charset, kUTF16Unspecified) &&
data.size() >= kByteOrderMarkSize) {
if (static_cast<uint8_t>(piece.data()[0]) == kByteOrderMark[0] &&
static_cast<uint8_t>(piece.data()[1]) == kByteOrderMark[1]) {
// BOM is in big endian format. Consume the BOM so it doesn't get
// interpreted as a character.
piece.remove_prefix(2);
charset = kUTF16BigEndian;
} else if (static_cast<uint8_t>(piece.data()[0]) == kByteOrderMark[1] &&
static_cast<uint8_t>(piece.data()[1]) == kByteOrderMark[0]) {
// BOM is in little endian format. Consume the BOM so it doesn't get
// interpreted as a character.
piece.remove_prefix(2);
charset = kUTF16LittleEndian;
}
}
base::CodepageToUTF16(
piece, charset, base::OnStringConversionError::Type::SUBSTITUTE, &output);
return output;
}
} // namespace
ScopedDataSource::ScopedDataSource(DataSource* data_source,
DataSourceObserver* observer)
: data_source_(data_source), observer_(observer) {
data_source_->AddObserver(observer_);
}
ScopedDataSource::~ScopedDataSource() {
data_source_->RemoveObserver(observer_);
}
DataSource::DataSource(DataSourceDelegate* delegate)
: delegate_(delegate), finished_(false) {}
DataSource::~DataSource() {
delegate_->OnDataSourceDestroying(this);
for (DataSourceObserver& observer : observers_) {
observer.OnDataSourceDestroying(this);
}
}
void DataSource::AddObserver(DataSourceObserver* observer) {
observers_.AddObserver(observer);
}
void DataSource::RemoveObserver(DataSourceObserver* observer) {
observers_.RemoveObserver(observer);
}
void DataSource::Offer(const std::string& mime_type) {
mime_types_.insert(mime_type);
}
void DataSource::SetActions(const base::flat_set<DndAction>& dnd_actions) {
dnd_actions_ = dnd_actions;
}
void DataSource::Target(const base::Optional<std::string>& mime_type) {
delegate_->OnTarget(mime_type);
}
void DataSource::Action(DndAction action) {
delegate_->OnAction(action);
}
void DataSource::DndDropPerformed() {
delegate_->OnDndDropPerformed();
}
void DataSource::Cancelled() {
finished_ = true;
read_data_weak_ptr_factory_.InvalidateWeakPtrs();
delegate_->OnCancelled();
}
void DataSource::DndFinished() {
finished_ = true;
read_data_weak_ptr_factory_.InvalidateWeakPtrs();
delegate_->OnDndFinished();
}
void DataSource::ReadDataForTesting(const std::string& mime_type,
ReadDataCallback callback) {
ReadData(mime_type, std::move(callback), base::DoNothing());
}
void DataSource::ReadData(const std::string& mime_type,
ReadDataCallback callback,
base::OnceClosure failure_callback) {
// This DataSource does not contain the requested MIME type.
if (mime_type.empty() || !mime_types_.count(mime_type) || finished_) {
std::move(failure_callback).Run();
return;
}
base::ScopedFD read_fd;
base::ScopedFD write_fd;
PCHECK(base::CreatePipe(&read_fd, &write_fd));
delegate_->OnSend(mime_type, std::move(write_fd));
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_BLOCKING,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&ReadDataOnWorkerThread, std::move(read_fd)),
base::BindOnce(
&DataSource::OnDataRead, read_data_weak_ptr_factory_.GetWeakPtr(),
std::move(callback), mime_type, std::move(failure_callback)));
}
void DataSource::OnDataRead(ReadDataCallback callback,
const std::string& mime_type,
base::OnceClosure failure_callback,
const base::Optional<std::vector<uint8_t>>& data) {
if (!data) {
std::move(failure_callback).Run();
return;
}
std::move(callback).Run(mime_type, *data);
}
void DataSource::GetDataForPreferredMimeTypes(
ReadTextDataCallback text_reader,
ReadDataCallback rtf_reader,
ReadTextDataCallback html_reader,
ReadDataCallback image_reader,
ReadDataCallback filenames_reader,
base::RepeatingClosure failure_callback) {
std::string text_mime, rtf_mime, html_mime, image_mime, filenames_mime;
int text_rank = std::numeric_limits<int>::max();
int html_rank = std::numeric_limits<int>::max();
int image_rank = std::numeric_limits<int>::max();
for (auto mime_type : mime_types_) {
if (net::MatchesMimeType(std::string(kTextPlain), mime_type) ||
mime_type == ui::kMimeTypeLinuxUtf8String) {
if (text_reader.is_null())
continue;
std::string charset;
charset = GetCharset(mime_type);
int new_rank = GetCharsetRank(charset);
if (new_rank < text_rank) {
text_mime = mime_type;
text_rank = new_rank;
}
} else if (net::MatchesMimeType(std::string(kTextRTF), mime_type)) {
if (rtf_reader.is_null())
continue;
// The RTF MIME type will never have a character set because it only uses
// 7-bit bytes and stores character set information internally.
rtf_mime = mime_type;
} else if (net::MatchesMimeType(std::string(kTextHTML), mime_type)) {
if (html_reader.is_null())
continue;
auto charset = GetCharset(mime_type);
int new_rank = GetCharsetRank(charset);
if (new_rank < html_rank) {
html_mime = mime_type;
html_rank = new_rank;
}
} else if (blink::IsSupportedImageMimeType(mime_type)) {
if (image_reader.is_null())
continue;
int new_rank = GetImageTypeRank(mime_type);
if (new_rank < image_rank) {
image_mime = mime_type;
image_rank = new_rank;
}
} else if (net::MatchesMimeType(std::string(kTextUriList), mime_type)) {
if (filenames_reader.is_null())
continue;
filenames_mime = mime_type;
}
}
ReadData(text_mime,
base::BindOnce(&DataSource::OnTextRead,
read_data_weak_ptr_factory_.GetWeakPtr(),
std::move(text_reader)),
failure_callback);
ReadData(rtf_mime, std::move(rtf_reader), failure_callback);
ReadData(html_mime,
base::BindOnce(&DataSource::OnTextRead,
read_data_weak_ptr_factory_.GetWeakPtr(),
std::move(html_reader)),
failure_callback);
ReadData(image_mime, std::move(image_reader), failure_callback);
ReadData(filenames_mime, std::move(filenames_reader), failure_callback);
}
void DataSource::OnTextRead(ReadTextDataCallback callback,
const std::string& mime_type,
const std::vector<uint8_t>& data) {
base::string16 output = CodepageToUTF16(data, GetCharset(mime_type));
std::move(callback).Run(mime_type, std::move(output));
}
bool DataSource::CanBeDataSourceForCopy(Surface* surface) const {
return delegate_->CanAcceptDataEventsForSurface(surface);
}
} // namespace exo