Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions runtime/fastly/builtins/fetch/request-response.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2492,6 +2492,104 @@ bool Request::clone(JSContext *cx, unsigned argc, JS::Value *vp) {
return true;
}

bool Request::getBotAnalyzed(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0)

auto res = request_handle(self).downstream_bot_analyzed();
if (res.is_err()) {
args.rval().setBoolean(false);
return true;
}

args.rval().setBoolean(res.unwrap());
return true;
}

bool Request::getBotDetected(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0)

auto res = request_handle(self).downstream_bot_detected();
if (res.is_err()) {
args.rval().setBoolean(false);
return true;
}

args.rval().setBoolean(res.unwrap());
return true;
}

bool Request::getBotName(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0)

auto res = request_handle(self).downstream_bot_name();
if (res.is_err()) {
args.rval().setNull();
return true;
}

auto bot_name = std::move(res.unwrap());
if (!bot_name.has_value()) {
args.rval().setNull();
return true;
}

args.rval().setString(JS_NewStringCopyN(cx, bot_name->begin(), bot_name->size()));
return true;
}

bool Request::getBotCategory(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0)

auto res = request_handle(self).downstream_bot_category();
if (res.is_err()) {
args.rval().setNull();
return true;
}

auto bot_category = std::move(res.unwrap());
if (!bot_category.has_value()) {
args.rval().setNull();
return true;
}

args.rval().setString(JS_NewStringCopyN(cx, bot_category->begin(), bot_category->size()));
return true;
}

bool Request::getBotCategoryKind(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0)

auto res = request_handle(self).downstream_bot_category_kind();
if (res.is_err()) {
args.rval().setNull();
return true;
}

auto bot_category_kind = std::move(res.unwrap());
if (!bot_category_kind.has_value()) {
args.rval().setNull();
return true;
}

auto kind_str = to_string(bot_category_kind.value());
args.rval().setString(
JS_NewStringCopyN(cx, kind_str.begin(), kind_str.size()));
return true;
}

bool Request::getBotVerified(JSContext *cx, unsigned argc, JS::Value *vp) {
METHOD_HEADER(0)

auto res = request_handle(self).downstream_bot_verified();
if (res.is_err()) {
args.rval().setBoolean(false);
return true;
}

args.rval().setBoolean(res.unwrap());
return true;
}

const JSFunctionSpec Request::static_methods[] = {
JS_FS_END,
};
Expand All @@ -2512,6 +2610,12 @@ const JSFunctionSpec Request::methods[] = {
JS_FN("setCacheKey", Request::setCacheKey, 0, JSPROP_ENUMERATE),
JS_FN("setManualFramingHeaders", Request::setManualFramingHeaders, 1, JSPROP_ENUMERATE),
JS_FN("clone", Request::clone, 0, JSPROP_ENUMERATE),
JS_FN("getBotAnalyzed", Request::getBotAnalyzed, 0, JSPROP_ENUMERATE),
JS_FN("getBotDetected", Request::getBotDetected, 0, JSPROP_ENUMERATE),
JS_FN("getBotName", Request::getBotName, 0, JSPROP_ENUMERATE),
JS_FN("getBotCategory", Request::getBotCategory, 0, JSPROP_ENUMERATE),
JS_FN("getBotCategoryKind", Request::getBotCategoryKind, 0, JSPROP_ENUMERATE),
JS_FN("getBotVerified", Request::getBotVerified, 0, JSPROP_ENUMERATE),
JS_FS_END,
};

Expand Down
7 changes: 7 additions & 0 deletions runtime/fastly/builtins/fetch/request-response.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ class Request final : public builtins::BuiltinImpl<Request> {
static bool body_get(JSContext *cx, unsigned argc, JS::Value *vp);
static bool bodyUsed_get(JSContext *cx, unsigned argc, JS::Value *vp);

static bool getBotAnalyzed(JSContext *cx, unsigned argc, JS::Value *vp);
static bool getBotDetected(JSContext *cx, unsigned argc, JS::Value *vp);
static bool getBotName(JSContext *cx, unsigned argc, JS::Value *vp);
static bool getBotCategory(JSContext *cx, unsigned argc, JS::Value *vp);
static bool getBotCategoryKind(JSContext *cx, unsigned argc, JS::Value *vp);
static bool getBotVerified(JSContext *cx, unsigned argc, JS::Value *vp);

static bool setCacheOverride(JSContext *cx, unsigned argc, JS::Value *vp);
static bool setCacheKey(JSContext *cx, unsigned argc, JS::Value *vp);
static bool setManualFramingHeaders(JSContext *cx, unsigned argc, JS::Value *vp);
Expand Down
20 changes: 20 additions & 0 deletions runtime/fastly/host-api/fastly.h
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,26 @@ WASM_IMPORT("fastly_http_downstream", "downstream_client_oh_fingerprint")
int http_downstream_client_oh_fingerprint(uint32_t req_handle, uint8_t *ret, size_t ret_len,
size_t *nwritten);

WASM_IMPORT("fastly_http_downstream", "downstream_bot_analyzed")
int http_downstream_bot_analyzed(uint32_t req_handle, uint32_t *bot_analyzed_out);

WASM_IMPORT("fastly_http_downstream", "downstream_bot_detected")
int http_downstream_bot_detected(uint32_t req_handle, uint32_t *bot_detected_out);

WASM_IMPORT("fastly_http_downstream", "downstream_bot_name")
int http_downstream_bot_name(uint32_t req_handle, uint8_t *bot_name_out, size_t bot_name_max_len,
size_t *nwritten);

WASM_IMPORT("fastly_http_downstream", "downstream_bot_category")
int http_downstream_bot_category(uint32_t req_handle, uint8_t *bot_category_out,
size_t bot_category_max_len, size_t *nwritten);

WASM_IMPORT("fastly_http_downstream", "downstream_bot_category_kind")
int http_downstream_bot_category_kind(uint32_t req_handle, uint32_t *bot_category_kind_out);

WASM_IMPORT("fastly_http_downstream", "downstream_bot_verified")
int http_downstream_bot_verified(uint32_t req_handle, uint32_t *bot_verified_out);

WASM_IMPORT("fastly_http_req", "new")
int req_new(uint32_t *req_handle_out);

Expand Down
118 changes: 118 additions & 0 deletions runtime/fastly/host-api/host_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1964,6 +1964,124 @@ Result<std::optional<HostString>> HttpReq::http_req_downstream_client_oh_fingerp
return res;
}

namespace {

// Helper for bool-valued bot flags (uint32 != 0). Pass none_is_false=true to
// return false instead of an error when the host signals OPTIONAL_NONE.
template <typename Fn>
Result<bool> bot_flag(uint32_t handle, Fn &&fn, bool none_is_false = false) {
fastly::fastly_host_error err;
uint32_t val = 0;
Result<bool> res;
if (!convert_result(fn(handle, &val), &err)) {
if (none_is_false && error_is_optional_none(err)) {
res.emplace(false);
} else {
res.emplace_err(err);
}
} else {
res.emplace(val != 0);
}
return res;
}

// Helper for optional string bot fields with automatic buffer resize.
template <typename Fn>
Result<std::optional<HostString>> bot_string(uint32_t handle, Fn &&fn) {
fastly::fastly_host_error err;
fastly::fastly_world_string ret;
auto default_size = 32;
ret.ptr = static_cast<uint8_t *>(cabi_malloc(default_size, 4));
auto status = fn(handle, ret.ptr, default_size, &ret.len);
if (status == FASTLY_HOST_ERROR_BUFFER_LEN) {
ret.ptr = static_cast<uint8_t *>(cabi_realloc(ret.ptr, default_size, 4, ret.len));
status = fn(handle, ret.ptr, ret.len, &ret.len);
}
Result<std::optional<HostString>> res;
if (!convert_result(status, &err)) {
cabi_free(ret.ptr);
if (error_is_optional_none(err)) {
res.emplace(std::nullopt);
} else {
res.emplace_err(err);
}
} else {
res.emplace(make_host_string(ret));
}
return res;
}

// Helper for optional uint32 bot fields; T defaults to uint32_t but can be
// any type constructible from uint32_t (e.g. an enum class) via static_cast.
template <typename T = uint32_t, typename Fn>
Result<std::optional<T>> bot_optional_u32(uint32_t handle, Fn &&fn) {
fastly::fastly_host_error err;
uint32_t val = 0;
Result<std::optional<T>> res;
if (!convert_result(fn(handle, &val), &err)) {
if (error_is_optional_none(err)) {
res.emplace(std::nullopt);
} else {
res.emplace_err(err);
}
} else {
res.emplace(static_cast<T>(val));
}
return res;
}

} // namespace

HostString to_string(BotCategoryKind kind) {
switch (kind) {
case BotCategoryKind::None: return HostString("None");
case BotCategoryKind::Suspected: return HostString("Suspected");
case BotCategoryKind::Accessibility: return HostString("Accessibility");
case BotCategoryKind::AiCrawler: return HostString("AiCrawler");
case BotCategoryKind::AiFetcher: return HostString("AiFetcher");
case BotCategoryKind::ContentFetcher: return HostString("ContentFetcher");
case BotCategoryKind::MonitoringSiteTools: return HostString("MonitoringSiteTools");
case BotCategoryKind::OnlineMarketing: return HostString("OnlineMarketing");
case BotCategoryKind::PagePreview: return HostString("PagePreview");
case BotCategoryKind::PlatformIntegrations: return HostString("PlatformIntegrations");
case BotCategoryKind::Research: return HostString("Research");
case BotCategoryKind::SearchEngineCrawler: return HostString("SearchEngineCrawler");
case BotCategoryKind::SearchEngineSpecialization: return HostString("SearchEngineSpecialization");
case BotCategoryKind::SecurityTools: return HostString("SecurityTools");
case BotCategoryKind::Unknown: return HostString("Unknown");
}
}

Result<bool> HttpReq::downstream_bot_analyzed() {
TRACE_CALL()
return bot_flag(this->handle, fastly::http_downstream_bot_analyzed);
}

Result<bool> HttpReq::downstream_bot_detected() {
TRACE_CALL()
return bot_flag(this->handle, fastly::http_downstream_bot_detected);
}

Result<std::optional<HostString>> HttpReq::downstream_bot_name() {
TRACE_CALL()
return bot_string(this->handle, fastly::http_downstream_bot_name);
}

Result<std::optional<HostString>> HttpReq::downstream_bot_category() {
TRACE_CALL()
return bot_string(this->handle, fastly::http_downstream_bot_category);
}

Result<std::optional<BotCategoryKind>> HttpReq::downstream_bot_category_kind() {
TRACE_CALL()
return bot_optional_u32<BotCategoryKind>(this->handle, fastly::http_downstream_bot_category_kind);
}

Result<bool> HttpReq::downstream_bot_verified() {
TRACE_CALL()
return bot_flag(this->handle, fastly::http_downstream_bot_verified, /*none_is_false=*/true);
}

bool HttpReq::is_valid() const { return this->handle != HttpReq::invalid; }

Result<HttpVersion> HttpReq::get_version() const {
Expand Down
27 changes: 27 additions & 0 deletions runtime/fastly/host-api/host_api_fastly.h
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,26 @@ class InspectOptions final {
explicit InspectOptions(uint32_t req, uint32_t body) : req_handle{req}, body_handle{body} {}
};

enum class BotCategoryKind : uint32_t {
None = 0,
Suspected = 1,
Accessibility = 2,
AiCrawler = 3,
AiFetcher = 4,
ContentFetcher = 5,
MonitoringSiteTools = 6,
OnlineMarketing = 7,
PagePreview = 8,
PlatformIntegrations = 9,
Research = 10,
SearchEngineCrawler = 11,
SearchEngineSpecialization = 12,
SecurityTools = 13,
Unknown = 14,
};

HostString to_string(BotCategoryKind kind);

class HttpReq final : public HttpBase {
public:
using Handle = uint32_t;
Expand Down Expand Up @@ -595,6 +615,13 @@ class HttpReq final : public HttpBase {

Result<std::optional<HostString>> http_req_downstream_client_oh_fingerprint();

Result<bool> downstream_bot_analyzed();
Result<bool> downstream_bot_detected();
Result<std::optional<HostString>> downstream_bot_name();
Result<std::optional<HostString>> downstream_bot_category();
Result<std::optional<BotCategoryKind>> downstream_bot_category_kind();
Result<bool> downstream_bot_verified();

Result<Void> auto_decompress_gzip();

/// Send this request synchronously, and wait for the response.
Expand Down
Loading