Files
electron/shell/browser/api/atom_api_url_request_ns.cc
Electron Bot c7a3142bab chore: bump chromium to 78.0.3896.6 (7-0-x) (#19609)
* chore: Bump 78.0.3894.0

* chore: bump chromium to 32e0bab929213da1019992bf31d29 (master) (#19488)

* chore: bump chromium to cbeb16cf544f79c1990f1eae4d4fe (master) (#19610)

Co-authored-by: Erick Zhao <erickzhao@github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
Co-authored-by Micha Hanselmann <DeerMichel@github.com>

* chore: bump chromium to 62327c655093c821aa0fcfc6db53f5fd943e08c7 (master) (#19792)

* chore: bump chromium in DEPS to f3bf493731e868e1f5f48e7e1adc02ea5eccfbbd

* chore: bump chromium in DEPS to 4db0c87d4aa6f27ffa0b5fc77d20e10047962484

* chore: bump chromium in DEPS to d933a504c264dc8fe85267f47aef3588531875b5

* chore: bump chromium in DEPS to 34afdb68980f581ae911b85b727bc17e126cf5f9

* update disable-redraw-lock.patch

https://chromium-review.googlesource.com/c/chromium/src/+/1600387

* update desktop_media_list.patch

https://chromium-review.googlesource.com/c/chromium/src/+/1729156

* update notification_provenance.patch

https://chromium-review.googlesource.com/c/chromium/src/+/1742779

* update printing.patch

https://chromium-review.googlesource.com/c/chromium/src/+/1646772

* update verbose_generate_bpad_syms.patch

https://chromium-review.googlesource.com/c/chromium/src/+/1745986

* update patch metadata

* remove printing_compositor manifests

https://chromium-review.googlesource.com/c/chromium/src/+/1742734

* update for URLLoaderFactoryType enum

https://chromium-review.googlesource.com/c/chromium/src/+/1754716

* remove gin string16 converter

https://chromium-review.googlesource.com/c/chromium/src/+/1750093

* ClearCompositorFrame() has been removed

https://chromium-review.googlesource.com/c/chromium/src/+/1746301

* message_loop -> message_loop_current

https://chromium-review.googlesource.com/c/chromium/src/+/1738552

* include resource_response header

* pdf compositor no longer uses service manager

https://chromium-review.googlesource.com/c/chromium/src/+/1742734

* chore: bump chromium in DEPS to 00d5933101d8d8dc9546eadbe7ee1b41077e6db1

* pane focus fns aren't pure virtual anymore

https://chromium-review.googlesource.com/c/chromium/src/+/1708767

* fix: make std::hash value-non-const

broken by https://chromium-review.googlesource.com/c/chromium/src/+/1711202

* update swiftshader in zip_manifests

https://swiftshader-review.googlesource.com/c/SwiftShader/+/34911

* address feedback from @deepak1556

* don't enable kLegacyWindowsDWriteFontFallback

https://chromium-review.googlesource.com/c/chromium/src/+/1753006

* chore: bump chromium in DEPS to 84497314005e1968da06804f8fde539d9872310e

* update printing.patch

remove bottom diff owing to https://chromium-review.googlesource.com/c/chromium/src/+/1678182 and update for https://chromium-review.googlesource.com/c/chromium/src/+/1678182

* convert CookieChangeListener to new Mojo types

https://chromium-review.googlesource.com/c/chromium/src/+/1753371

* rename ui::ClipboardType -> ui::ClipboardBuffer

https://chromium-review.googlesource.com/c/chromium/src/+/1758730

* logging::LoggingSettings log_file -> log_file_path

https://chromium-review.googlesource.com/c/chromium/src/+/1699477

* roll DEPS to latest lkgr

* fix: override GetFontLookupTableCacheDir()

When Chromium goes to use its fallback font table creation code paths,
it creates the cache directory it uses by calling
GetFontLookupTableCacheDir() with a path that doesn't exist in Electron.
To ensure that a legitimate file path is created, we need to override it
with Electron's DIR_USER_DATA so it doesn't use chrome::DIR_USER_DATA.

* chore: bump chromium in DEPS to 6758a0879931bc4df630a80a36c82d7855ae3155

* update pthread_fchdir patch

https://chromium-review.googlesource.com/c/chromium/src/+/1759149

* update printing patch

* update cookie usage and fn signatures

https://chromium-review.googlesource.com/c/chromium/src/+/1758437

* chore: bump chromium in DEPS to bdaca97e1cc27fb977e56f30f74cdb906da9527e

* remove fix_make_std_hash_value-non-const.patch

https://chromium-review.googlesource.com/c/chromium/src/+/1762335

* Convert enum to enum class for FocusManager::FocusChangeReason

https://chromium-review.googlesource.com/c/chromium/src/+/1767281

* roll DEPS to latest lkgr

* update dom_storage_limits.patch

https://chromium-review.googlesource.com/c/chromium/src/+/1767556

* chore: remove pre network service classes from shell/browser/net (#19644)

* refactor: rm IOThread class

* chore: rm expose-net-observer-api.patch

* chore: rm unused shell/browser/net/ classes

* chore: mv CertVerifierClient to separate header

* chore: rm url_request_context_getter references

* chore: update patches

* Require task posters to specify an explicit destination

https://chromium-review.googlesource.com/c/chromium/src/+/1769080

* chore: Revert "Cleanup: Remove Menu Subtitles/Sublabels"

* chore: Bump chromium 78.0.3896.0

* build: add checkout_openxr=False to DEPS

Refs: https://chromium-review.googlesource.com/c/chromium/src/+/1745502

* chore: update patches

* Convert TrustedURLLoaderHeaderClient and TrustedHeaderClient to new mojo types

https://chromium-review.googlesource.com/c/chromium/src/+/1767298
https://chromium-review.googlesource.com/c/chromium/src/+/1768841

* skia: more rect api simplifications

https://skia-review.googlesource.com/c/skia/+/237038

* iwyu

* test: fix clearAuthCache test (#20015)

* fix: nws13n: make ses.setUserAgent work  (#20014)

* refactor tests to better control window creation

* fix: nws13n: make ses.setUserAgent work

* chore: update v8 patches

* Add enterprise policy for renderer CIG.

https://chromium-review.googlesource.com/c/chromium/src/+/1758589

* Convert enum to enum class for Wigdet::FrameType

https://chromium-review.googlesource.com/c/chromium/src/+/1767292

* [JJI] Convert to use string16 for data from JavaScript/Java

https://chromium-review.googlesource.com/c/chromium/src/+/1750093

* chore: Bump chromium 78.0.3896.6
2019-08-30 12:57:57 -04:00

544 lines
17 KiB
C++

// Copyright (c) 2019 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/api/atom_api_url_request_ns.h"
#include <utility>
#include "mojo/public/cpp/system/string_data_source.h"
#include "native_mate/dictionary.h"
#include "native_mate/object_template_builder.h"
#include "net/http/http_util.h"
#include "services/network/public/mojom/chunked_data_pipe_getter.mojom.h"
#include "shell/browser/api/atom_api_session.h"
#include "shell/browser/atom_browser_context.h"
#include "shell/common/native_mate_converters/gurl_converter.h"
#include "shell/common/native_mate_converters/net_converter.h"
#include "shell/common/node_includes.h"
namespace mate {
template <>
struct Converter<network::mojom::RedirectMode> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
network::mojom::RedirectMode* out) {
std::string mode;
if (!ConvertFromV8(isolate, val, &mode))
return false;
if (mode == "follow")
*out = network::mojom::RedirectMode::kFollow;
else if (mode == "error")
*out = network::mojom::RedirectMode::kError;
else if (mode == "manual")
*out = network::mojom::RedirectMode::kManual;
else
return false;
return true;
}
};
} // namespace mate
namespace electron {
namespace api {
namespace {
// Network state for request and response.
enum State {
STATE_STARTED = 1 << 0,
STATE_FINISHED = 1 << 1,
STATE_CANCELED = 1 << 2,
STATE_FAILED = 1 << 3,
STATE_CLOSED = 1 << 4,
STATE_ERROR = STATE_CANCELED | STATE_FAILED | STATE_CLOSED,
};
// Annotation tag passed to NetworkService.
const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("electron_net_module", R"(
semantics {
sender: "Electron Net module"
description:
"Issue HTTP/HTTPS requests using Chromium's native networking "
"library."
trigger: "Using the Net module"
data: "Anything the user wants to send."
destination: OTHER
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting: "This feature cannot be disabled."
})");
} // namespace
// Common class for streaming data.
class UploadDataPipeGetter {
public:
explicit UploadDataPipeGetter(URLRequestNS* request) : request_(request) {}
virtual ~UploadDataPipeGetter() = default;
virtual void AttachToRequestBody(network::ResourceRequestBody* body) = 0;
protected:
void SetCallback(network::mojom::DataPipeGetter::ReadCallback callback) {
request_->size_callback_ = std::move(callback);
}
void SetPipe(mojo::ScopedDataPipeProducerHandle pipe) {
request_->producer_ =
std::make_unique<mojo::DataPipeProducer>(std::move(pipe));
request_->StartWriting();
}
private:
URLRequestNS* request_;
DISALLOW_COPY_AND_ASSIGN(UploadDataPipeGetter);
};
// Streaming multipart data to NetworkService.
class MultipartDataPipeGetter : public UploadDataPipeGetter,
public network::mojom::DataPipeGetter {
public:
explicit MultipartDataPipeGetter(URLRequestNS* request)
: UploadDataPipeGetter(request) {}
~MultipartDataPipeGetter() override = default;
void AttachToRequestBody(network::ResourceRequestBody* body) override {
network::mojom::DataPipeGetterPtr data_pipe_getter;
binding_set_.AddBinding(this, mojo::MakeRequest(&data_pipe_getter));
body->AppendDataPipe(std::move(data_pipe_getter));
}
private:
// network::mojom::DataPipeGetter:
void Read(mojo::ScopedDataPipeProducerHandle pipe,
ReadCallback callback) override {
SetCallback(std::move(callback));
SetPipe(std::move(pipe));
}
void Clone(network::mojom::DataPipeGetterRequest request) override {
binding_set_.AddBinding(this, std::move(request));
}
mojo::BindingSet<network::mojom::DataPipeGetter> binding_set_;
};
// Streaming chunked data to NetworkService.
class ChunkedDataPipeGetter : public UploadDataPipeGetter,
public network::mojom::ChunkedDataPipeGetter {
public:
explicit ChunkedDataPipeGetter(URLRequestNS* request)
: UploadDataPipeGetter(request) {}
~ChunkedDataPipeGetter() override = default;
void AttachToRequestBody(network::ResourceRequestBody* body) override {
network::mojom::ChunkedDataPipeGetterPtr data_pipe_getter;
binding_set_.AddBinding(this, mojo::MakeRequest(&data_pipe_getter));
body->SetToChunkedDataPipe(std::move(data_pipe_getter));
}
private:
// network::mojom::ChunkedDataPipeGetter:
void GetSize(GetSizeCallback callback) override {
SetCallback(std::move(callback));
}
void StartReading(mojo::ScopedDataPipeProducerHandle pipe) override {
SetPipe(std::move(pipe));
}
mojo::BindingSet<network::mojom::ChunkedDataPipeGetter> binding_set_;
};
URLRequestNS::URLRequestNS(mate::Arguments* args) : weak_factory_(this) {
request_ = std::make_unique<network::ResourceRequest>();
mate::Dictionary dict;
if (args->GetNext(&dict)) {
dict.Get("method", &request_->method);
dict.Get("url", &request_->url);
dict.Get("redirect", &redirect_mode_);
request_->redirect_mode = redirect_mode_;
}
std::string partition;
mate::Handle<api::Session> session;
if (!dict.Get("session", &session)) {
if (dict.Get("partition", &partition))
session = Session::FromPartition(args->isolate(), partition);
else // default session
session = Session::FromPartition(args->isolate(), "");
}
url_loader_factory_ = session->browser_context()->GetURLLoaderFactory();
InitWith(args->isolate(), args->GetThis());
}
URLRequestNS::~URLRequestNS() {}
bool URLRequestNS::NotStarted() const {
return request_state_ == 0;
}
bool URLRequestNS::Finished() const {
return request_state_ & STATE_FINISHED;
}
void URLRequestNS::Cancel() {
// Cancel only once.
if (request_state_ & (STATE_CANCELED | STATE_CLOSED))
return;
// Mark as canceled.
request_state_ |= STATE_CANCELED;
EmitEvent(EventType::kRequest, true, "abort");
if ((response_state_ & STATE_STARTED) && !(response_state_ & STATE_FINISHED))
EmitEvent(EventType::kResponse, true, "aborted");
Close();
}
void URLRequestNS::Close() {
if (!(request_state_ & STATE_CLOSED)) {
request_state_ |= STATE_CLOSED;
if (response_state_ & STATE_STARTED) {
// Emit a close event if we really have a response object.
EmitEvent(EventType::kResponse, true, "close");
}
EmitEvent(EventType::kRequest, true, "close");
}
Unpin();
loader_.reset();
}
bool URLRequestNS::Write(v8::Local<v8::Value> data, bool is_last) {
if (request_state_ & (STATE_FINISHED | STATE_ERROR))
return false;
size_t length = node::Buffer::Length(data);
if (!loader_) {
// Pin on first write.
request_state_ = STATE_STARTED;
Pin();
// Create the loader.
network::ResourceRequest* request_ref = request_.get();
loader_ = network::SimpleURLLoader::Create(std::move(request_),
kTrafficAnnotation);
loader_->SetOnResponseStartedCallback(base::Bind(
&URLRequestNS::OnResponseStarted, weak_factory_.GetWeakPtr()));
loader_->SetOnRedirectCallback(
base::Bind(&URLRequestNS::OnRedirect, weak_factory_.GetWeakPtr()));
loader_->SetOnUploadProgressCallback(base::Bind(
&URLRequestNS::OnUploadProgress, weak_factory_.GetWeakPtr()));
// Create upload data pipe if we have data to write.
if (length > 0) {
request_ref->request_body = new network::ResourceRequestBody();
if (is_chunked_upload_)
data_pipe_getter_ = std::make_unique<ChunkedDataPipeGetter>(this);
else
data_pipe_getter_ = std::make_unique<MultipartDataPipeGetter>(this);
data_pipe_getter_->AttachToRequestBody(request_ref->request_body.get());
}
// Start downloading.
loader_->DownloadAsStream(url_loader_factory_.get(), this);
}
if (length > 0)
pending_writes_.emplace_back(node::Buffer::Data(data), length);
if (is_last) {
// The ElementsUploadDataStream requires the knowledge of content length
// before doing upload, while Node's stream does not give us any size
// information. So the only option left for us is to keep all the write
// data in memory and flush them after the write is done.
//
// While this looks frustrating, it is actually the behavior of the non-
// NetworkService implementation, and we are not breaking anything.
if (!pending_writes_.empty()) {
last_chunk_written_ = true;
StartWriting();
}
request_state_ |= STATE_FINISHED;
EmitEvent(EventType::kRequest, true, "finish");
}
return true;
}
void URLRequestNS::FollowRedirect() {
if (request_state_ & (STATE_CANCELED | STATE_CLOSED))
return;
follow_redirect_ = true;
}
bool URLRequestNS::SetExtraHeader(const std::string& name,
const std::string& value) {
if (!request_)
return false;
if (!net::HttpUtil::IsValidHeaderName(name))
return false;
if (!net::HttpUtil::IsValidHeaderValue(value))
return false;
request_->headers.SetHeader(name, value);
return true;
}
void URLRequestNS::RemoveExtraHeader(const std::string& name) {
if (request_)
request_->headers.RemoveHeader(name);
}
void URLRequestNS::SetChunkedUpload(bool is_chunked_upload) {
if (request_)
is_chunked_upload_ = is_chunked_upload;
}
mate::Dictionary URLRequestNS::GetUploadProgress() {
mate::Dictionary progress = mate::Dictionary::CreateEmpty(isolate());
if (loader_) {
if (request_)
progress.Set("started", false);
else
progress.Set("started", true);
progress.Set("current", upload_position_);
progress.Set("total", upload_total_);
progress.Set("active", true);
} else {
progress.Set("active", false);
}
return progress;
}
int URLRequestNS::StatusCode() const {
if (response_headers_)
return response_headers_->response_code();
return -1;
}
std::string URLRequestNS::StatusMessage() const {
if (response_headers_)
return response_headers_->GetStatusText();
return "";
}
net::HttpResponseHeaders* URLRequestNS::RawResponseHeaders() const {
return response_headers_.get();
}
uint32_t URLRequestNS::ResponseHttpVersionMajor() const {
if (response_headers_)
return response_headers_->GetHttpVersion().major_value();
return 0;
}
uint32_t URLRequestNS::ResponseHttpVersionMinor() const {
if (response_headers_)
return response_headers_->GetHttpVersion().minor_value();
return 0;
}
void URLRequestNS::OnDataReceived(base::StringPiece data,
base::OnceClosure resume) {
// In case we received an unexpected event from Chromium net, don't emit any
// data event after request cancel/error/close.
if (!(request_state_ & STATE_ERROR) && !(response_state_ & STATE_ERROR)) {
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Value> buffer;
auto maybe = node::Buffer::Copy(isolate(), data.data(), data.size());
if (maybe.ToLocal(&buffer))
Emit("data", buffer);
}
std::move(resume).Run();
}
void URLRequestNS::OnRetry(base::OnceClosure start_retry) {}
void URLRequestNS::OnComplete(bool success) {
if (success) {
// In case we received an unexpected event from Chromium net, don't emit any
// data event after request cancel/error/close.
if (!(request_state_ & STATE_ERROR) && !(response_state_ & STATE_ERROR)) {
response_state_ |= STATE_FINISHED;
Emit("end");
}
} else { // failed
// If response is started then emit response event, else emit request error.
//
// Error is only emitted when there is no previous failure. This is to align
// with the behavior of non-NetworkService implementation.
std::string error = net::ErrorToString(loader_->NetError());
if (response_state_ & STATE_STARTED) {
if (!(response_state_ & STATE_FAILED))
EmitError(EventType::kResponse, error);
} else {
if (!(request_state_ & STATE_FAILED))
EmitError(EventType::kRequest, error);
}
}
Close();
}
void URLRequestNS::OnResponseStarted(
const GURL& final_url,
const network::ResourceResponseHead& response_head) {
// Don't emit any event after request cancel.
if (request_state_ & STATE_ERROR)
return;
response_headers_ = response_head.headers;
response_state_ |= STATE_STARTED;
Emit("response");
}
void URLRequestNS::OnRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head,
std::vector<std::string>* to_be_removed_headers) {
if (!loader_)
return;
if (request_state_ & (STATE_CLOSED | STATE_CANCELED)) {
NOTREACHED();
Cancel();
return;
}
switch (redirect_mode_) {
case network::mojom::RedirectMode::kError:
EmitError(
EventType::kRequest,
"Request cannot follow redirect with the current redirect mode");
break;
case network::mojom::RedirectMode::kManual:
// When redirect mode is "manual", the user has to explicitly call the
// FollowRedirect method to continue redirecting, otherwise the request
// would be cancelled.
//
// Note that the SimpleURLLoader always calls FollowRedirect and does not
// provide a formal way for us to cancel redirection, we have to cancel
// the request to prevent the redirection.
follow_redirect_ = false;
EmitEvent(EventType::kRequest, false, "redirect",
redirect_info.status_code, redirect_info.new_method,
redirect_info.new_url, response_head.headers.get());
if (!follow_redirect_)
Cancel();
break;
case network::mojom::RedirectMode::kFollow:
EmitEvent(EventType::kRequest, false, "redirect",
redirect_info.status_code, redirect_info.new_method,
redirect_info.new_url, response_head.headers.get());
break;
}
}
void URLRequestNS::OnUploadProgress(uint64_t position, uint64_t total) {
upload_position_ = position;
upload_total_ = total;
}
void URLRequestNS::OnWrite(MojoResult result) {
if (result != MOJO_RESULT_OK)
return;
// Continue the pending writes.
pending_writes_.pop_front();
if (!pending_writes_.empty())
DoWrite();
}
void URLRequestNS::DoWrite() {
DCHECK(producer_);
DCHECK(!pending_writes_.empty());
producer_->Write(
std::make_unique<mojo::StringDataSource>(
pending_writes_.front(), mojo::StringDataSource::AsyncWritingMode::
STRING_STAYS_VALID_UNTIL_COMPLETION),
base::BindOnce(&URLRequestNS::OnWrite, weak_factory_.GetWeakPtr()));
}
void URLRequestNS::StartWriting() {
if (!last_chunk_written_ || size_callback_.is_null())
return;
size_t size = 0;
for (const auto& data : pending_writes_)
size += data.size();
std::move(size_callback_).Run(net::OK, size);
DoWrite();
}
void URLRequestNS::Pin() {
if (wrapper_.IsEmpty()) {
wrapper_.Reset(isolate(), GetWrapper());
}
}
void URLRequestNS::Unpin() {
wrapper_.Reset();
}
void URLRequestNS::EmitError(EventType type, base::StringPiece message) {
if (type == EventType::kRequest)
request_state_ |= STATE_FAILED;
else
response_state_ |= STATE_FAILED;
v8::HandleScope handle_scope(isolate());
auto error = v8::Exception::Error(mate::StringToV8(isolate(), message));
EmitEvent(type, false, "error", error);
}
template <typename... Args>
void URLRequestNS::EmitEvent(EventType type, Args... args) {
const char* method =
type == EventType::kRequest ? "_emitRequestEvent" : "_emitResponseEvent";
v8::HandleScope handle_scope(isolate());
mate::CustomEmit(isolate(), GetWrapper(), method, args...);
}
// static
mate::WrappableBase* URLRequestNS::New(mate::Arguments* args) {
return new URLRequestNS(args);
}
// static
void URLRequestNS::BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype) {
prototype->SetClassName(mate::StringToV8(isolate, "URLRequest"));
mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
.MakeDestroyable()
.SetMethod("write", &URLRequestNS::Write)
.SetMethod("cancel", &URLRequestNS::Cancel)
.SetMethod("setExtraHeader", &URLRequestNS::SetExtraHeader)
.SetMethod("removeExtraHeader", &URLRequestNS::RemoveExtraHeader)
.SetMethod("setChunkedUpload", &URLRequestNS::SetChunkedUpload)
.SetMethod("followRedirect", &URLRequestNS::FollowRedirect)
.SetMethod("getUploadProgress", &URLRequestNS::GetUploadProgress)
.SetProperty("notStarted", &URLRequestNS::NotStarted)
.SetProperty("finished", &URLRequestNS::Finished)
.SetProperty("statusCode", &URLRequestNS::StatusCode)
.SetProperty("statusMessage", &URLRequestNS::StatusMessage)
.SetProperty("rawResponseHeaders", &URLRequestNS::RawResponseHeaders)
.SetProperty("httpVersionMajor", &URLRequestNS::ResponseHttpVersionMajor)
.SetProperty("httpVersionMinor", &URLRequestNS::ResponseHttpVersionMinor);
}
} // namespace api
} // namespace electron