#include "network_agent.h"
#include <string>
#include "debug_utils-inl.h"
#include "env-inl.h"
#include "inspector/inspector_object_utils.h"
#include "inspector/network_resource_manager.h"
#include "inspector/protocol_helper.h"
#include "network_inspector.h"
#include "node_metadata.h"
#include "util-inl.h"
#include "uv.h"
#include "v8-context.h"
#include "v8.h"
namespace node {
namespace inspector {
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::Uint8Array;
using v8::Value;
constexpr size_t kDefaultMaxTotalBufferSize = 100 * 1024 * 1024; // 100MB
static void ThrowEventError(v8::Isolate* isolate, const std::string& message) {
isolate->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(isolate, message.c_str()).ToLocalChecked()));
}
// Create a protocol::Network::Headers from the v8 object.
std::unique_ptr<protocol::Network::Headers>
NetworkAgent::createHeadersFromObject(v8::Local<v8::Context> context,
Local<Object> headers_obj) {
HandleScope handle_scope(Isolate::GetCurrent());
Isolate* isolate = env_->isolate();
std::unique_ptr<protocol::DictionaryValue> dict =
protocol::DictionaryValue::create();
Local<v8::Array> property_names;
if (!headers_obj->GetOwnPropertyNames(context).ToLocal(&property_names)) {
ThrowEventError(isolate, "Missing response headers in event");
return {};
}
for (size_t idx = 0; idx < property_names->Length(); idx++) {
Local<v8::Value> property_name_val;
if (!property_names->Get(context, idx).ToLocal(&property_name_val) ||
!property_name_val->IsString()) {
ThrowEventError(isolate, "Invalid header name in event");
return {};
}
Local<v8::String> property_name = property_name_val.As<v8::String>();
protocol::String property_value;
if (!ObjectGetProtocolString(context, headers_obj, property_name)
.To(&property_value)) {
ThrowEventError(isolate, "Invalid header value in event");
return {};
}
dict->setString(ToProtocolString(Isolate::GetCurrent(), property_name),
property_value);
}
return std::make_unique<protocol::Network::Headers>(std::move(dict));
}
// Create a protocol::Network::Request from the v8 object.
std::unique_ptr<protocol::Network::Request>
NetworkAgent::createRequestFromObject(v8::Local<v8::Context> context,
Local<Object> request) {
HandleScope handle_scope(Isolate::GetCurrent());
Isolate* isolate = env_->isolate();
protocol::String url;
if (!ObjectGetProtocolString(context, request, "url").To(&url)) {
ThrowEventError(isolate, "Missing request.url in event");
return {};
}
protocol::String method;
if (!ObjectGetProtocolString(context, request, "method").To(&method)) {
ThrowEventError(isolate, "Missing request.method in event");
return {};
}
Local<Object> headers_obj;
if (!ObjectGetObject(context, request, "headers").ToLocal(&headers_obj)) {
ThrowEventError(isolate, "Missing request.headers in event");
return {};
}
std::unique_ptr<protocol::Network::Headers> headers =
createHeadersFromObject(context, headers_obj);
if (!headers) {
return {};
}
bool has_post_data =
ObjectGetBool(context, request, "hasPostData").FromMaybe(false);
return protocol::Network::Request::create()
.setUrl(url)
.setMethod(method)
.setHasPostData(has_post_data)
.setHeaders(std::move(headers))
.build();
}
// Create a protocol::Network::Response from the v8 object.
std::unique_ptr<protocol::Network::Response>
NetworkAgent::createResponseFromObject(v8::Local<v8::Context> context,
Local<Object> response) {
HandleScope handle_scope(Isolate::GetCurrent());
Isolate* isolate = env_->isolate();
protocol::String url;
if (!ObjectGetProtocolString(context, response, "url").To(&url)) {
ThrowEventError(isolate, "Missing response.url in event");
return {};
}
int status;
if (!ObjectGetInt(context, response, "status").To(&status)) {
ThrowEventError(isolate, "Missing response.status in event");
return {};
}
protocol::String statusText;
if (!ObjectGetProtocolString(context, response, "statusText")
.To(&statusText)) {
ThrowEventError(isolate, "Missing response.statusText in event");
return {};
}
Local<Object> headers_obj;
if (!ObjectGetObject(context, response, "headers").ToLocal(&headers_obj)) {
ThrowEventError(isolate, "Missing response.headers in event");
return {};
}
std::unique_ptr<protocol::Network::Headers> headers =
createHeadersFromObject(context, headers_obj);
if (!headers) {
return {};
}
protocol::String mimeType =
ObjectGetProtocolString(context, response, "mimeType").FromMaybe("");
protocol::String charset =
ObjectGetProtocolString(context, response, "charset").FromMaybe("");
return protocol::Network::Response::create()
.setUrl(url)
.setStatus(status)
.setStatusText(statusText)
.setHeaders(std::move(headers))
.setMimeType(mimeType)
.setCharset(charset)
.build();
}
std::unique_ptr<protocol::Network::WebSocketResponse>
NetworkAgent::createWebSocketResponse(v8::Local<v8::Context> context,
Local<Object> response) {
HandleScope handle_scope(Isolate::GetCurrent());
Isolate* isolate = env_->isolate();
int status;
if (!ObjectGetInt(context, response, "status").To(&status)) {
ThrowEventError(isolate, "Missing response.status in event");
return {};
}
protocol::String statusText;
if (!ObjectGetProtocolString(context, response, "statusText")
.To(&statusText)) {
ThrowEventError(isolate, "Missing response.statusText in event");
return {};
}
Local<Object> headers_obj;
if (!ObjectGetObject(context, response, "headers").ToLocal(&headers_obj)) {
ThrowEventError(isolate, "Missing response.headers in event");
return {};
}
std::unique_ptr<protocol::Network::Headers> headers =
createHeadersFromObject(context, headers_obj);
if (!headers) {
return {};
}
return protocol::Network::WebSocketResponse::create()
.setStatus(status)
.setStatusText(statusText)
.setHeaders(std::move(headers))
.build();
}
NetworkAgent::NetworkAgent(
NetworkInspector* inspector,
v8_inspector::V8Inspector* v8_inspector,
Environment* env,
std::shared_ptr<NetworkResourceManager> network_resource_manager)
: inspector_(inspector),
v8_inspector_(v8_inspector),
env_(env),
network_resource_manager_(std::move(network_resource_manager)),
requests_(kDefaultMaxTotalBufferSize) {
event_notifier_map_["requestWillBeSent"] = &NetworkAgent::requestWillBeSent;
event_notifier_map_["responseReceived"] = &NetworkAgent::responseReceived;
event_notifier_map_["loadingFailed"] = &NetworkAgent::loadingFailed;
event_notifier_map_["loadingFinished"] = &NetworkAgent::loadingFinished;
event_notifier_map_["dataSent"] = &NetworkAgent::dataSent;
event_notifier_map_["dataReceived"] = &NetworkAgent::dataReceived;
event_notifier_map_["webSocketCreated"] = &NetworkAgent::webSocketCreated;
event_notifier_map_["webSocketClosed"] = &NetworkAgent::webSocketClosed;
event_notifier_map_["webSocketHandshakeResponseReceived"] =
&NetworkAgent::webSocketHandshakeResponseReceived;
}
void NetworkAgent::webSocketCreated(v8::Local<v8::Context> context,
v8::Local<v8::Object> params) {
Isolate* isolate = env_->isolate();
protocol::String request_id;
if (!ObjectGetProtocolString(context, params, "requestId").To(&request_id)) {
ThrowEventError(isolate, "Missing requestId in event");
return;
}
protocol::String url;
if (!ObjectGetProtocolString(context, params, "url").To(&url)) {
ThrowEventError(isolate, "Missing url in event");
return;
}
std::unique_ptr<protocol::Network::Initiator> initiator =
protocol::Network::Initiator::create()
.setType(protocol::Network::Initiator::TypeEnum::Script)
.setStack(
v8_inspector_->captureStackTrace(true)->buildInspectorObject(0))
.build();
frontend_->webSocketCreated(request_id, url, std::move(initiator));
}
void NetworkAgent::webSocketClosed(v8::Local<v8::Context> context,
v8::Local<v8::Object> params) {
Isolate* isolate = env_->isolate();
protocol::String request_id;
if (!ObjectGetProtocolString(context, params, "requestId").To(&request_id)) {
ThrowEventError(isolate, "Missing requestId in event");
return;
}
double timestamp;
if (!ObjectGetDouble(context, params, "timestamp").To(×tamp)) {
ThrowEventError(isolate, "Missing timestamp in event");
return;
}
frontend_->webSocketClosed(request_id, timestamp);
}
void NetworkAgent::webSocketHandshakeResponseReceived(
v8::Local<v8::Context> context, v8::Local<v8::Object> params) {
Isolate* isolate = env_->isolate();
protocol::String request_id;
if (!ObjectGetProtocolString(context, params, "requestId").To(&request_id)) {
ThrowEventError(isolate, "Missing requestId in event");
return;
}
double timestamp;
if (!ObjectGetDouble(context, params, "timestamp").To(×tamp)) {
ThrowEventError(isolate, "Missing timestamp in event");
return;
}
Local<Object> response_obj;
if (!ObjectGetObject(context, params, "response").ToLocal(&response_obj)) {
ThrowEventError(isolate, "Missing response in event");
return;
}
auto response = createWebSocketResponse(context, response_obj);
if (!response) {
return;
}
frontend_->webSocketHandshakeResponseReceived(
request_id, timestamp, std::move(response));
}
void NetworkAgent::emitNotification(v8::Local<v8::Context> context,
const protocol::String& event,
v8::Local<v8::Object> params) {
if (!inspector_->IsEnabled()) return;
auto it = event_notifier_map_.find(event);
if (it != event_notifier_map_.end()) {
(this->*(it->second))(context, params);
}
}
void NetworkAgent::Wire(protocol::UberDispatcher* dispatcher) {
frontend_ =
std::make_unique<protocol::Network::Frontend>(dispatcher->channel());
protocol::Network::Dispatcher::wire(dispatcher, this);
}
protocol::DispatchResponse NetworkAgent::enable(
std::optional<int> in_maxTotalBufferSize,
std::optional<int> in_maxResourceBufferSize) {
inspector_->Enable();
requests_ = RequestsBuffer(
in_maxTotalBufferSize.value_or(kDefaultMaxTotalBufferSize));
if (in_maxResourceBufferSize) {
max_resource_buffer_size_ = *in_maxResourceBufferSize;
}
return protocol::DispatchResponse::Success();
}
protocol::DispatchResponse NetworkAgent::disable() {
inspector_->Disable();
return protocol::DispatchResponse::Success();
}
protocol::DispatchResponse NetworkAgent::getRequestPostData(
const protocol::String& in_requestId, protocol::String* out_postData) {
auto request_entry = requests_.cfind(in_requestId);
if (request_entry == requests_.end()) {
// Request not found, ignore it.
return protocol::DispatchResponse::InvalidParams("Request not found");
}
if (!request_entry->second.is_request_finished) {
// Request not finished yet.
return protocol::DispatchResponse::InvalidParams(
"Request data is not finished yet");
}
if (request_entry->second.request_charset == Charset::kBinary) {
// The protocol does not support binary request bodies yet.
return protocol::DispatchResponse::ServerError(
"Unable to serialize binary request body");
}
// If the response is UTF-8, we return it as a concatenated string.
CHECK_EQ(request_entry->second.request_charset, Charset::kUTF8);
// Concat response bodies.
protocol::Binary buf =
protocol::Binary::concat(request_entry->second.request_data_blobs());
*out_postData = protocol::StringUtil::fromUTF8(buf.data(), buf.size());
return protocol::DispatchResponse::Success();
}
protocol::DispatchResponse NetworkAgent::getResponseBody(
const protocol::String& in_requestId,
protocol::String* out_body,
bool* out_base64Encoded) {
auto request_entry = requests_.cfind(in_requestId);
if (request_entry == requests_.end()) {
// Request not found, ignore it.
return protocol::DispatchResponse::InvalidParams("Request not found");
}
if (request_entry->second.is_streaming) {
// Streaming request, data is not buffered.
return protocol::DispatchResponse::InvalidParams(
"Response body of the request is been streamed");
}
if (!request_entry->second.is_response_finished) {
// Response not finished yet.
return protocol::DispatchResponse::InvalidParams(
"Response data is not finished yet");
}
// Concat response bodies.
protocol::Binary buf =
protocol::Binary::concat(request_entry->second.response_data_blobs());
if (request_entry->second.response_charset == Charset::kBinary) {
// If the response is binary, we return base64 encoded data.
*out_body = buf.toBase64();
*out_base64Encoded = true;
} else if (request_entry->second.response_charset == Charset::kUTF8) {
// If the response is UTF-8, we return it as a concatenated string.
*out_body = protocol::StringUtil::fromUTF8(buf.data(), buf.size());
*out_base64Encoded = false;
} else {
UNREACHABLE("Response charset not implemented");
}
requests_.erase(request_entry);
return protocol::DispatchResponse::Success();
}
protocol::DispatchResponse NetworkAgent::streamResourceContent(
const protocol::String& in_requestId, protocol::Binary* out_bufferedData) {
bool is_response_finished = false;
{
auto it = requests_.find(in_requestId);
if (it == requests_.end()) {
// Request not found, ignore it.
return protocol::DispatchResponse::InvalidParams("Request not found");
}
auto& request_entry = it->second;
request_entry.is_streaming = true;
// Concat response bodies.
*out_bufferedData =
protocol::Binary::concat(request_entry.response_data_blobs());
// Clear buffered data.
request_entry.clear_response_data_blobs();
is_response_finished = request_entry.is_response_finished;
}
if (is_response_finished) {
// If the request is finished, remove the entry.
requests_.erase(in_requestId);
}
return protocol::DispatchResponse::Success();
}
protocol::DispatchResponse NetworkAgent::loadNetworkResource(
const protocol::String& in_url,
std::unique_ptr<protocol::Network::LoadNetworkResourcePageResult>*
out_resource) {
if (!env_->options()->experimental_inspector_network_resource) {
return protocol::DispatchResponse::ServerError(
"Network resource loading is not enabled. This feature is "
"experimental and requires --experimental-inspector-network-resource "
"flag to be set.");
}
CHECK_NOT_NULL(network_resource_manager_);
std::string data = network_resource_manager_->Get(in_url);
bool found = !data.empty();
if (found) {
auto result = protocol::Network::LoadNetworkResourcePageResult::create()
.setSuccess(true)
.setStream(in_url)
.build();
*out_resource = std::move(result);
return protocol::DispatchResponse::Success();
} else {
auto result = protocol::Network::LoadNetworkResourcePageResult::create()
.setSuccess(false)
.build();
*out_resource = std::move(result);
return protocol::DispatchResponse::Success();
}
}
void NetworkAgent::requestWillBeSent(v8::Local<v8::Context> context,
v8::Local<v8::Object> params) {
Isolate* isolate = env_->isolate();
protocol::String request_id;
if (!ObjectGetProtocolString(context, params, "requestId").To(&request_id)) {
ThrowEventError(isolate, "Missing requestId in event");
return;
}
double timestamp;
if (!ObjectGetDouble(context, params, "timestamp").To(×tamp)) {
ThrowEventError(isolate, "Missing timestamp in event");
return;
}
double wall_time;
if (!ObjectGetDouble(context, params, "wallTime").To(&wall_time)) {
ThrowEventError(isolate, "Missing wallTime in event");
return;
}
protocol::String charset =
ObjectGetProtocolString(context, params, "charset").FromMaybe("");
Local<v8::Object> request_obj;
if (!ObjectGetObject(context, params, "request").ToLocal(&request_obj)) {
ThrowEventError(isolate, "Missing request in event");
return;
}
std::unique_ptr<protocol::Network::Request> request =
createRequestFromObject(context, request_obj);
if (!request) {
return;
}
std::unique_ptr<protocol::Network::Initiator> initiator =
protocol::Network::Initiator::create()
.setType(protocol::Network::Initiator::TypeEnum::Script)
.setStack(
v8_inspector_->captureStackTrace(true)->buildInspectorObject(0))
.build();
if (requests_.contains(request_id)) {
// Duplicate entry, ignore it.
return;
}
auto request_charset = charset == "utf-8" ? Charset::kUTF8 : Charset::kBinary;
requests_.emplace(request_id,
RequestEntry(timestamp,
request_charset,
request->getHasPostData(),
max_resource_buffer_size_));
frontend_->requestWillBeSent(request_id,
std::move(request),
std::move(initiator),
timestamp,
wall_time);
}
void NetworkAgent::responseReceived(v8::Local<v8::Context> context,
v8::Local<v8::Object> params) {
Isolate* isolate = env_->isolate();
protocol::String request_id;
if (!ObjectGetProtocolString(context, params, "requestId").To(&request_id)) {
ThrowEventError(isolate, "Missing requestId in event");
return;
}
double timestamp;
if (!ObjectGetDouble(context, params, "timestamp").To(×tamp)) {
ThrowEventError(isolate, "Missing timestamp in event");
return;
}
protocol::String type;
if (!ObjectGetProtocolString(context, params, "type").To(&type)) {
ThrowEventError(isolate, "Missing type in event");
return;
}
Local<Object> response_obj;
if (!ObjectGetObject(context, params, "response").ToLocal(&response_obj)) {
ThrowEventError(isolate, "Missing response in event");
return;
}
auto response = createResponseFromObject(context, response_obj);
if (!response) {
return;
}
auto request_entry = requests_.find(request_id);
if (request_entry == requests_.end()) {
// No entry found. Ignore it.
return;
}
request_entry->second.response_charset =
response->getCharset() == "utf-8" ? Charset::kUTF8 : Charset::kBinary;
frontend_->responseReceived(request_id, timestamp, type, std::move(response));
}
void NetworkAgent::loadingFailed(v8::Local<v8::Context> context,
v8::Local<v8::Object> params) {
Isolate* isolate = env_->isolate();
protocol::String request_id;
if (!ObjectGetProtocolString(context, params, "requestId").To(&request_id)) {
ThrowEventError(isolate, "Missing requestId in event");
return;
}
double timestamp;
if (!ObjectGetDouble(context, params, "timestamp").To(×tamp)) {
ThrowEventError(isolate, "Missing timestamp in event");
return;
}
protocol::String type;
if (!ObjectGetProtocolString(context, params, "type").To(&type)) {
ThrowEventError(isolate, "Missing type in event");
return;
}
protocol::String error_text;
if (!ObjectGetProtocolString(context, params, "errorText").To(&error_text)) {
ThrowEventError(isolate, "Missing errorText in event");
return;
}
frontend_->loadingFailed(request_id, timestamp, type, error_text);
requests_.erase(request_id);
}
void NetworkAgent::loadingFinished(v8::Local<v8::Context> context,
Local<v8::Object> params) {
Isolate* isolate = env_->isolate();
protocol::String request_id;
if (!ObjectGetProtocolString(context, params, "requestId").To(&request_id)) {
ThrowEventError(isolate, "Missing requestId in event");
return;
}
double timestamp;
if (!ObjectGetDouble(context, params, "timestamp").To(×tamp)) {
ThrowEventError(isolate, "Missing timestamp in event");
return;
}
frontend_->loadingFinished(request_id, timestamp);
auto request_entry = requests_.cfind(request_id);
if (request_entry == requests_.end()) {
// No entry found. Ignore it.
return;
}
if (request_entry->second.is_streaming) {
// Streaming finished, remove the entry.
requests_.erase(request_id);
} else {
requests_.find(request_id)->second.is_response_finished = true;
}
}
void NetworkAgent::dataSent(v8::Local<v8::Context> context,
v8::Local<v8::Object> params) {
Isolate* isolate = env_->isolate();
protocol::String request_id;
if (!ObjectGetProtocolString(context, params, "requestId").To(&request_id)) {
ThrowEventError(isolate, "Missing requestId in event");
return;
}
auto request_entry = requests_.find(request_id);
if (request_entry == requests_.end()) {
// No entry found. Ignore it.
return;
}
bool is_finished =
ObjectGetBool(context, params, "finished").FromMaybe(false);
if (is_finished) {
request_entry->second.is_request_finished = true;
return;
}
double timestamp;
if (!ObjectGetDouble(context, params, "timestamp").To(×tamp)) {
ThrowEventError(isolate, "Missing timestamp in event");
return;
}
int data_length;
if (!ObjectGetInt(context, params, "dataLength").To(&data_length)) {
ThrowEventError(isolate, "Missing dataLength in event");
return;
}
Local<Object> data_obj;
if (!ObjectGetObject(context, params, "data").ToLocal(&data_obj)) {
ThrowEventError(isolate, "Missing data in event");
return;
}
if (!data_obj->IsUint8Array()) {
ThrowEventError(isolate, "Expected data to be Uint8Array in event");
return;
}
Local<Uint8Array> data = data_obj.As<Uint8Array>();
auto data_bin = protocol::Binary::fromUint8Array(data);
request_entry->second.push_request_data_blob(data_bin);
}
void NetworkAgent::dataReceived(v8::Local<v8::Context> context,
v8::Local<v8::Object> params) {
Isolate* isolate = env_->isolate();
protocol::String request_id;
if (!ObjectGetProtocolString(context, params, "requestId").To(&request_id)) {
ThrowEventError(isolate, "Missing requestId in event");
return;
}
double timestamp;
if (!ObjectGetDouble(context, params, "timestamp").To(×tamp)) {
ThrowEventError(isolate, "Missing timestamp in event");
return;
}
int data_length;
if (!ObjectGetInt(context, params, "dataLength").To(&data_length)) {
ThrowEventError(isolate, "Missing dataLength in event");
return;
}
int encoded_data_length;
if (!ObjectGetInt(context, params, "encodedDataLength")
.To(&encoded_data_length)) {
ThrowEventError(isolate, "Missing encodedDataLength in event");
return;
}
Local<Object> data_obj;
if (!ObjectGetObject(context, params, "data").ToLocal(&data_obj)) {
ThrowEventError(isolate, "Missing data in event");
return;
}
if (!data_obj->IsUint8Array()) {
ThrowEventError(isolate, "Expected data to be Uint8Array in event");
return;
}
Local<Uint8Array> data = data_obj.As<Uint8Array>();
auto data_bin = protocol::Binary::fromUint8Array(data);
auto it = requests_.find(request_id);
if (it == requests_.end()) {
// No entry found. Ignore it.
return;
}
auto& request_entry = it->second;
if (request_entry.is_streaming) {
frontend_->dataReceived(
request_id, timestamp, data_length, encoded_data_length, data_bin);
} else {
request_entry.push_response_data_blob(data_bin);
}
}
} // namespace inspector
} // namespace node