|
| 1 | +#include <algorithm> |
| 2 | +#include <cstring> |
| 3 | +#include <iostream> |
| 4 | +#include <optional> |
| 5 | +#include <string> |
| 6 | + |
1 | 7 | // TODO: remove these once the warnings are fixed
|
2 | 8 | #pragma clang diagnostic push
|
3 | 9 | #pragma clang diagnostic ignored "-Winvalid-offsetof"
|
|
13 | 19 | #include "host_call.h"
|
14 | 20 | #include "js-compute-builtins.h"
|
15 | 21 |
|
| 22 | +std::string_view bad_chars{"#?*[]\n\r"}; |
| 23 | + |
| 24 | +std::optional<char> find_invalid_character_for_object_store_key(const char *str) { |
| 25 | + std::optional<char> res; |
| 26 | + |
| 27 | + std::string_view view{str, strlen(str)}; |
| 28 | + |
| 29 | + auto it = std::find_if(view.begin(), view.end(), |
| 30 | + [](auto c) { return bad_chars.find(c) != std::string_view::npos; }); |
| 31 | + |
| 32 | + if (it != view.end()) { |
| 33 | + res = *it; |
| 34 | + } |
| 35 | + |
| 36 | + return res; |
| 37 | +} |
| 38 | + |
16 | 39 | namespace ObjectStoreEntry {
|
17 | 40 | namespace Slots {
|
18 | 41 | enum {
|
@@ -91,88 +114,66 @@ ObjectStoreHandle object_store_handle(JSObject *obj) {
|
91 | 114 |
|
92 | 115 | const unsigned ctor_length = 1;
|
93 | 116 |
|
94 |
| -bool check_receiver(JSContext *cx, JS::HandleValue receiver, const char *method_name); |
95 |
| - |
96 |
| -bool lookup(JSContext *cx, unsigned argc, JS::Value *vp) { |
97 |
| - METHOD_HEADER(1) |
98 |
| - |
99 |
| - JS::RootedObject result_promise(cx, JS::NewPromiseObject(cx, nullptr)); |
100 |
| - if (!result_promise) { |
101 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
102 |
| - } |
103 |
| - |
104 |
| - size_t key_len; |
| 117 | +std::optional<char *> parse_and_validate_key(JSContext *cx, JS::HandleValue val, size_t *key_len) { |
105 | 118 | // Convert the key argument into a String following https://tc39.es/ecma262/#sec-tostring
|
106 |
| - JS::UniqueChars key = encode(cx, args.get(0), &key_len); |
| 119 | + JS::UniqueChars key = encode(cx, val, key_len); |
107 | 120 | if (!key) {
|
108 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
| 121 | + return std::nullopt; |
109 | 122 | }
|
110 | 123 |
|
111 | 124 | // If the converted string has a length of 0 then we throw an Error
|
112 | 125 | // because ObjectStore Keys have to be at-least 1 character.
|
113 |
| - if (key_len == 0) { |
| 126 | + if (*key_len == 0) { |
114 | 127 | JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_OBJECT_STORE_KEY_EMPTY);
|
115 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
| 128 | + return std::nullopt; |
116 | 129 | }
|
117 | 130 |
|
118 | 131 | // If the converted string has a length of more than 1024 then we throw an Error
|
119 | 132 | // because ObjectStore Keys have to be less than 1025 characters.
|
120 |
| - if (key_len > 1024) { |
| 133 | + if (*key_len > 1024) { |
121 | 134 | JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_OBJECT_STORE_KEY_TOO_LONG);
|
122 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
| 135 | + return std::nullopt; |
123 | 136 | }
|
124 | 137 |
|
125 | 138 | char *key_chars = key.get();
|
126 | 139 |
|
127 |
| - if (strchr(key_chars, '#') != NULL) { |
128 |
| - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, |
129 |
| - JSMSG_OBJECT_STORE_KEY_INVALID_CHARACTER, "#"); |
130 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
131 |
| - } |
132 |
| - if (strchr(key_chars, '?') != NULL) { |
133 |
| - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, |
134 |
| - JSMSG_OBJECT_STORE_KEY_INVALID_CHARACTER, "?"); |
135 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
136 |
| - } |
137 |
| - if (strchr(key_chars, '*') != NULL) { |
138 |
| - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, |
139 |
| - JSMSG_OBJECT_STORE_KEY_INVALID_CHARACTER, "*"); |
140 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
141 |
| - } |
142 |
| - if (strchr(key_chars, '[') != NULL) { |
143 |
| - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, |
144 |
| - JSMSG_OBJECT_STORE_KEY_INVALID_CHARACTER, "["); |
145 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
146 |
| - } |
147 |
| - if (strchr(key_chars, ']') != NULL) { |
| 140 | + if (auto res = find_invalid_character_for_object_store_key(key_chars)) { |
148 | 141 | JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
149 |
| - JSMSG_OBJECT_STORE_KEY_INVALID_CHARACTER, "]"); |
150 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
151 |
| - } |
152 |
| - if (strchr(key_chars, '\n') != NULL) { |
153 |
| - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, |
154 |
| - JSMSG_OBJECT_STORE_KEY_INVALID_CHARACTER, "newline"); |
155 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
156 |
| - } |
157 |
| - if (strchr(key_chars, '\r') != NULL) { |
158 |
| - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, |
159 |
| - JSMSG_OBJECT_STORE_KEY_INVALID_CHARACTER, "carriage return"); |
160 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
| 142 | + JSMSG_OBJECT_STORE_KEY_INVALID_CHARACTER, *res); |
| 143 | + return std::nullopt; |
161 | 144 | }
|
162 | 145 | auto acme_challenge = ".well-known/acme-challenge/";
|
163 | 146 | if (strncmp(key_chars, acme_challenge, strlen(acme_challenge)) == 0) {
|
164 | 147 | JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_OBJECT_STORE_KEY_ACME);
|
165 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
| 148 | + return std::nullopt; |
166 | 149 | }
|
167 | 150 |
|
168 | 151 | if (strcmp(key_chars, ".") == 0 || strcmp(key_chars, "..") == 0) {
|
169 | 152 | JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_OBJECT_STORE_KEY_RELATIVE);
|
| 153 | + return std::nullopt; |
| 154 | + } |
| 155 | + |
| 156 | + return key_chars; |
| 157 | +} |
| 158 | + |
| 159 | +bool check_receiver(JSContext *cx, JS::HandleValue receiver, const char *method_name); |
| 160 | + |
| 161 | +bool lookup(JSContext *cx, unsigned argc, JS::Value *vp) { |
| 162 | + METHOD_HEADER(1) |
| 163 | + |
| 164 | + JS::RootedObject result_promise(cx, JS::NewPromiseObject(cx, nullptr)); |
| 165 | + if (!result_promise) { |
170 | 166 | return ReturnPromiseRejectedWithPendingError(cx, args);
|
171 | 167 | }
|
172 | 168 |
|
| 169 | + size_t key_len; |
| 170 | + std::optional<char *> key_chars = parse_and_validate_key(cx, args.get(0), &key_len); |
| 171 | + if (!key_chars) { |
| 172 | + return ReturnPromiseRejectedWithPendingError(cx, args); |
| 173 | + } |
173 | 174 | BodyHandle body_handle = {INVALID_HANDLE};
|
174 |
| - int status = |
175 |
| - fastly_object_store_lookup(object_store_handle(self), key_chars, key_len, &body_handle); |
| 175 | + int status = fastly_object_store_lookup(object_store_handle(self), key_chars.value(), key_len, |
| 176 | + &body_handle); |
176 | 177 | if (!HANDLE_RESULT(cx, status)) {
|
177 | 178 | return false;
|
178 | 179 | }
|
@@ -206,74 +207,11 @@ bool put(JSContext *cx, unsigned argc, JS::Value *vp) {
|
206 | 207 | }
|
207 | 208 |
|
208 | 209 | size_t key_len;
|
209 |
| - // Convert the key argument into a String following https://tc39.es/ecma262/#sec-tostring |
210 |
| - JS::UniqueChars key = encode(cx, args.get(0), &key_len); |
211 |
| - if (!key) { |
212 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
213 |
| - } |
214 |
| - |
215 |
| - // If the converted string has a length of 0 then we throw an Error |
216 |
| - // because ObjectStore Keys have to be at-least 1 character. |
217 |
| - if (key_len == 0) { |
218 |
| - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_OBJECT_STORE_KEY_EMPTY); |
219 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
220 |
| - } |
221 |
| - |
222 |
| - // If the converted string has a length of more than 1024 then we throw an Error |
223 |
| - // because ObjectStore Keys have to be less than 1025 characters. |
224 |
| - if (key_len > 1024) { |
225 |
| - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_OBJECT_STORE_KEY_TOO_LONG); |
226 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
227 |
| - } |
228 |
| - |
229 |
| - char *key_chars = key.get(); |
230 |
| - |
231 |
| - if (strchr(key_chars, '#') != NULL) { |
232 |
| - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, |
233 |
| - JSMSG_OBJECT_STORE_KEY_INVALID_CHARACTER, "#"); |
234 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
235 |
| - } |
236 |
| - if (strchr(key_chars, '?') != NULL) { |
237 |
| - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, |
238 |
| - JSMSG_OBJECT_STORE_KEY_INVALID_CHARACTER, "?"); |
239 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
240 |
| - } |
241 |
| - if (strchr(key_chars, '*') != NULL) { |
242 |
| - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, |
243 |
| - JSMSG_OBJECT_STORE_KEY_INVALID_CHARACTER, "*"); |
244 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
245 |
| - } |
246 |
| - if (strchr(key_chars, '[') != NULL) { |
247 |
| - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, |
248 |
| - JSMSG_OBJECT_STORE_KEY_INVALID_CHARACTER, "["); |
249 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
250 |
| - } |
251 |
| - if (strchr(key_chars, ']') != NULL) { |
252 |
| - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, |
253 |
| - JSMSG_OBJECT_STORE_KEY_INVALID_CHARACTER, "]"); |
254 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
255 |
| - } |
256 |
| - if (strchr(key_chars, '\n') != NULL) { |
257 |
| - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, |
258 |
| - JSMSG_OBJECT_STORE_KEY_INVALID_CHARACTER, "newline"); |
259 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
260 |
| - } |
261 |
| - if (strchr(key_chars, '\r') != NULL) { |
262 |
| - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, |
263 |
| - JSMSG_OBJECT_STORE_KEY_INVALID_CHARACTER, "carriage return"); |
264 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
265 |
| - } |
266 |
| - auto acme_challenge = ".well-known/acme-challenge/"; |
267 |
| - if (strncmp(key_chars, acme_challenge, strlen(acme_challenge)) == 0) { |
268 |
| - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_OBJECT_STORE_KEY_ACME); |
269 |
| - return ReturnPromiseRejectedWithPendingError(cx, args); |
270 |
| - } |
271 |
| - |
272 |
| - if (strcmp(key_chars, ".") == 0 || strcmp(key_chars, "..") == 0) { |
273 |
| - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_OBJECT_STORE_KEY_RELATIVE); |
| 210 | + size_t key_len; |
| 211 | + std::optional<char *> key_chars = parse_and_validate_key(cx, args.get(0), &key_len); |
| 212 | + if (!key_chars) { |
274 | 213 | return ReturnPromiseRejectedWithPendingError(cx, args);
|
275 | 214 | }
|
276 |
| - |
277 | 215 | JS::HandleValue body_val = args.get(1);
|
278 | 216 |
|
279 | 217 | // We currently support five types of body inputs:
|
@@ -302,7 +240,8 @@ bool put(JSContext *cx, unsigned argc, JS::Value *vp) {
|
302 | 240 | JS::RootedObject source_owner(cx, builtins::NativeStreamSource::owner(stream_source));
|
303 | 241 | BodyHandle body = RequestOrResponse::body_handle(source_owner);
|
304 | 242 |
|
305 |
| - int status = fastly_object_store_insert(object_store_handle(self), key_chars, key_len, body); |
| 243 | + int status = |
| 244 | + fastly_object_store_insert(object_store_handle(self), key_chars.value(), key_len, body); |
306 | 245 | if (!HANDLE_RESULT(cx, status)) {
|
307 | 246 | return ReturnPromiseRejectedWithPendingError(cx, args);
|
308 | 247 | }
|
@@ -376,8 +315,8 @@ bool put(JSContext *cx, unsigned argc, JS::Value *vp) {
|
376 | 315 | return ReturnPromiseRejectedWithPendingError(cx, args);
|
377 | 316 | }
|
378 | 317 |
|
379 |
| - int status = |
380 |
| - fastly_object_store_insert(object_store_handle(self), key_chars, key_len, body_handle); |
| 318 | + int status = fastly_object_store_insert(object_store_handle(self), key_chars.value(), key_len, |
| 319 | + body_handle); |
381 | 320 | // Ensure that we throw an exception for all unexpected host errors.
|
382 | 321 | if (!HANDLE_RESULT(cx, status)) {
|
383 | 322 | return RejectPromiseWithPendingError(cx, result_promise);
|
|
0 commit comments