Skip to content
File

Blob: src/workerd/api/cf-property.c++

4.7 KB
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#include "cf-property.h"
6 
7#include <workerd/io/features.h>
8#include <workerd/util/own-util.h>
9 
10namespace workerd::api {
11 
12static constexpr kj::StringPtr kDefaultBotManagementValue = R"DATA({
13 "corporateProxy": false,
14 "verifiedBot": false,
15 "jsDetection": { "passed": false },
16 "staticResource": false,
17 "detectionIds": {},
18 "score": 99
19})DATA"_kjc;
20 
21// When the cfBotManagementNoOp compatibility flag is set, we'll check the
22// request cf blob to see if it contains a botManagement field. If it does
23// *not* we will add it using the following default fields.
24// Note that if the botManagement team changes any of the fields they provide,
25// this default value may need to be changed also.
26static void handleDefaultBotManagement(jsg::Lock& js, jsg::JsObject handle) {
27 auto name = "botManagement"_kjc;
28 if (!handle.has(js, name)) {
29 // For performance reasons, we only want to construct the default values
30 // once per isolate so we cache the constructed value using an internal
31 // private field on the global scope. Whenever we need to use it again we
32 // pull the exact same value.
33 auto bm = js.global().getPrivate(js, name);
34 if (bm.isUndefined()) {
35 bm = jsg::JsValue::fromJson(js, kDefaultBotManagementValue);
36 KJ_DASSERT(bm.isObject());
37 js.global().setPrivate(js, name, bm);
38 }
39 handle.set(js, name, bm);
40 }
41}
42 
43CfProperty::CfProperty(kj::Maybe<kj::StringPtr> unparsed) {
44 value = mapCopyString(unparsed);
45}
46 
47CfProperty::CfProperty(jsg::Lock& js, const jsg::JsObject& object)
48 : CfProperty(kj::Maybe(jsg::JsRef(js, object))) {}
49 
50CfProperty::CfProperty(kj::Maybe<jsg::JsRef<jsg::JsObject>>&& parsed) {
51 KJ_IF_SOME(v, parsed) {
52 value = kj::mv(v);
53 }
54}
55 
56jsg::Optional<jsg::JsObject> CfProperty::get(jsg::Lock& js) {
57 return getRef(js).map(
58 [&js](jsg::JsRef<jsg::JsObject>&& ref) mutable { return ref.getHandle(js); });
59}
60 
61jsg::Optional<jsg::JsRef<jsg::JsObject>> CfProperty::getRef(jsg::Lock& js) {
62 KJ_IF_SOME(cf, value) {
63 KJ_SWITCH_ONEOF(cf) {
64 KJ_CASE_ONEOF(parsed, jsg::JsRef<jsg::JsObject>) {
65 return parsed.addRef(js);
66 }
67 KJ_CASE_ONEOF(unparsed, kj::String) {
68 auto parsed = jsg::JsValue::fromJson(js, unparsed);
69 auto object = KJ_ASSERT_NONNULL(parsed.tryCast<jsg::JsObject>());
70 
71 if (!FeatureFlags::get(js).getNoCfBotManagementDefault()) {
72 handleDefaultBotManagement(js, object);
73 }
74 
75 object.recursivelyFreeze(js);
76 
77 // replace unparsed string with a parsed v8 object
78 this->value = object.addRef(js);
79 return jsg::JsRef(js, object);
80 }
81 }
82 }
83 
84 return kj::none;
85}
86 
87kj::Maybe<kj::String> CfProperty::serialize(jsg::Lock& js) {
88 KJ_IF_SOME(cf, value) {
89 KJ_SWITCH_ONEOF(cf) {
90 KJ_CASE_ONEOF(parsed, jsg::JsRef<jsg::JsObject>) {
91 return jsg::JsValue(parsed.getHandle(js)).toJson(js);
92 }
93 KJ_CASE_ONEOF(unparsed, kj::String) {
94 if (!FeatureFlags::get(js).getNoCfBotManagementDefault()) {
95 // we mess up with the value on this code path,
96 // need to parse it, fix it and serialize back
97 jsg::JsValue handle = KJ_ASSERT_NONNULL(getRef(js)).getHandle(js);
98 return handle.toJson(js);
99 }
100 
101 return kj::str(unparsed);
102 }
103 }
104 }
105 
106 return kj::none;
107}
108 
109CfProperty CfProperty::deepClone(jsg::Lock& js) {
110 // By default, when CfProperty is lazily parsed, the resulting JS object
111 // will be recursively frozen, preventing edits. However, when the CfProperty
112 // is cloned and the clone is lazily parsed, the resulting JS object must not
113 // be frozen! So, to ensure that, we'll force the parse to occur here if it
114 // hasn't been parsed already, this will ensure that the clone receives the
115 // parsed object via json cloning below rather than the raw string.
116 // TODO(cleanup): With a bit of refactoring we can preserve the lazy parsing
117 // optimization through the clone. But for now, let's just do the easy thing.
118 getRef(js);
119 KJ_IF_SOME(cf, value) {
120 KJ_SWITCH_ONEOF(cf) {
121 KJ_CASE_ONEOF(parsed, jsg::JsRef<jsg::JsObject>) {
122 return CfProperty(jsg::JsRef(js, parsed.getHandle(js).jsonClone(js)));
123 }
124 KJ_CASE_ONEOF(unparsed, kj::String) {
125 KJ_FAIL_REQUIRE("The cf property should have been lazily parsed!");
126 }
127 }
128 }
129 
130 return nullptr;
131}
132 
133void CfProperty::visitForGc(jsg::GcVisitor& visitor) {
134 KJ_IF_SOME(cf, value) {
135 KJ_SWITCH_ONEOF(cf) {
136 KJ_CASE_ONEOF(parsed, jsg::JsRef<jsg::JsObject>) {
137 visitor.visit(parsed);
138 }
139 KJ_CASE_ONEOF_DEFAULT {}
140 }
141 }
142}
143 
144} // namespace workerd::api