Skip to content

Commit c0b0822

Browse files
Jake ChampionJakeChampion
authored andcommitted
fix: implement validation for Dictionary names and keys
This commit updates our Dictionary implementation to ensure we throw nice error messages when given invalid dictionary names or keys, such as a name for a non-existant dictionary or a key that is too long.
1 parent 6f1cf88 commit c0b0822

File tree

6 files changed

+687
-29
lines changed

6 files changed

+687
-29
lines changed

c-dependencies/js-compute-runtime/builtins/dictionary.cpp

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,32 @@ DictionaryHandle Dictionary::dictionary_handle(JSObject *obj) {
1111
bool Dictionary::get(JSContext *cx, unsigned argc, JS::Value *vp) {
1212
METHOD_HEADER(1)
1313

14+
JS::HandleValue name_arg = args.get(0);
15+
1416
size_t name_len;
15-
JS::UniqueChars name = encode(cx, args[0], &name_len);
17+
// Convert into a String following https://tc39.es/ecma262/#sec-tostring
18+
JS::UniqueChars name_chars = encode(cx, name_arg, &name_len);
19+
if (!name_chars) {
20+
return false;
21+
}
22+
23+
// If the converted string has a length of 0 then we throw an Error
24+
// because Dictionary keys have to be at-least 1 character.
25+
if (name_len == 0) {
26+
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DICTIONARY_KEY_EMPTY);
27+
return false;
28+
}
29+
// key has to be less than 256
30+
if (name_len > 255) {
31+
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DICTIONARY_KEY_TOO_LONG);
32+
return false;
33+
}
1634

1735
OwnedHostCallBuffer buffer;
1836
size_t nwritten = 0;
19-
auto status = convert_to_fastly_status(xqd_dictionary_get(Dictionary::dictionary_handle(self),
20-
name.get(), name_len, buffer.get(),
21-
DICTIONARY_ENTRY_MAX_LEN, &nwritten));
37+
auto status = convert_to_fastly_status(
38+
xqd_dictionary_get(Dictionary::dictionary_handle(self), name_chars.get(), name_len,
39+
buffer.get(), DICTIONARY_ENTRY_MAX_LEN, &nwritten));
2240
// FastlyStatus::none indicates the key wasn't found, so we return null.
2341
if (status == FastlyStatus::None) {
2442
args.rval().setNull();
@@ -45,17 +63,65 @@ bool Dictionary::constructor(JSContext *cx, unsigned argc, JS::Value *vp) {
4563
REQUEST_HANDLER_ONLY("The Dictionary builtin");
4664
CTOR_HEADER("Dictionary", 1);
4765

66+
JS::HandleValue name_arg = args.get(0);
67+
4868
size_t name_len;
49-
JS::UniqueChars name = encode(cx, args[0], &name_len);
69+
// Convert into a String following https://tc39.es/ecma262/#sec-tostring
70+
JS::UniqueChars name_chars = encode(cx, name_arg, &name_len);
71+
if (!name_chars) {
72+
return false;
73+
}
74+
75+
// If the converted string has a length of 0 then we throw an Error
76+
// because Dictionary names have to be at-least 1 character.
77+
if (name_len == 0) {
78+
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DICTIONARY_NAME_EMPTY);
79+
return false;
80+
}
81+
82+
// If the converted string has a length of more than 255 then we throw an Error
83+
// because Dictionary names have to be less than 255 characters.
84+
if (name_len > 255) {
85+
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DICTIONARY_NAME_TOO_LONG);
86+
return false;
87+
}
88+
89+
std::string_view name(name_chars.get(), name_len);
90+
91+
// Name must start with ascii alphabetical and contain only ascii alphanumeric, underscore, and
92+
// whitespace
93+
if (!std::isalpha(name.front())) {
94+
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
95+
JSMSG_DICTIONARY_NAME_START_WITH_ASCII_ALPHA);
96+
return false;
97+
}
98+
99+
auto is_valid_name = std::all_of(std::next(name.begin(), 1), name.end(), [&](auto character) {
100+
return std::isalnum(character) || character == '_' || character == ' ';
101+
});
102+
103+
if (!is_valid_name) {
104+
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
105+
JSMSG_DICTIONARY_NAME_CONTAINS_INVALID_CHARACTER);
106+
return false;
107+
}
50108
JS::RootedObject dictionary(cx, JS_NewObjectForConstructor(cx, &class_, args));
51109
DictionaryHandle dict_handle = {INVALID_HANDLE};
52-
if (!HANDLE_RESULT(cx, xqd_dictionary_open(name.get(), name_len, &dict_handle)))
110+
auto status = convert_to_fastly_status(xqd_dictionary_open(name.data(), name_len, &dict_handle));
111+
if (status == FastlyStatus::BadF) {
112+
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DICTIONARY_DOES_NOT_EXIST,
113+
name.data());
53114
return false;
115+
}
116+
if (!HANDLE_RESULT(cx, status)) {
117+
return false;
118+
}
54119

55120
JS::SetReservedSlot(dictionary, Dictionary::Slots::Handle,
56121
JS::Int32Value((int)dict_handle.handle));
57-
if (!dictionary)
122+
if (!dictionary) {
58123
return false;
124+
}
59125
args.rval().setObject(*dictionary);
60126
return true;
61127
}

c-dependencies/js-compute-runtime/error-numbers.msg

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ MSG_DEF(JSMSG_INCOMPATIBLE_INSTANCE, 2, JSEXN_TYPEERR,
4545
MSG_DEF(JSMSG_INVALID_BUFFER_ARG, 2, JSEXN_TYPEERR, "{0} must be of type ArrayBuffer or ArrayBufferView but got \"{1}\"")
4646
MSG_DEF(JSMSG_INVALID_COMPRESSION_FORMAT, 1, JSEXN_TYPEERR, "'format' has to be \"deflate\", \"deflate-raw\", or \"gzip\", but got \"{0}\"")
4747
MSG_DEF(JSMSG_DECOMPRESSING_ERROR, 0, JSEXN_TYPEERR, "DecompressionStream transform: error decompressing chunk")
48+
MSG_DEF(JSMSG_DICTIONARY_DOES_NOT_EXIST, 1, JSEXN_TYPEERR, "Dictionary constructor: No Dictionary named '{0}' exists")
49+
MSG_DEF(JSMSG_DICTIONARY_KEY_EMPTY, 0, JSEXN_TYPEERR, "Dictionary key can not be an empty string")
50+
MSG_DEF(JSMSG_DICTIONARY_KEY_TOO_LONG, 0, JSEXN_TYPEERR, "Dictionary key can not be more than 255 characters")
51+
MSG_DEF(JSMSG_DICTIONARY_NAME_CONTAINS_INVALID_CHARACTER, 0, JSEXN_TYPEERR, "Dictionary constructor: name can contain only ascii alphanumeric characters, underscores, and ascii whitespace")
52+
MSG_DEF(JSMSG_DICTIONARY_NAME_EMPTY, 0, JSEXN_TYPEERR, "Dictionary constructor: name can not be an empty string")
53+
MSG_DEF(JSMSG_DICTIONARY_NAME_START_WITH_ASCII_ALPHA, 0, JSEXN_TYPEERR, "Dictionary constructor: name must start with an ascii alpabetical character")
54+
MSG_DEF(JSMSG_DICTIONARY_NAME_TOO_LONG, 0, JSEXN_TYPEERR, "Dictionary constructor: name can not be more than 255 characters")
4855
MSG_DEF(JSMSG_OBJECT_STORE_NAME_EMPTY, 0, JSEXN_TYPEERR, "ObjectStore constructor: name can not be an empty string")
4956
MSG_DEF(JSMSG_OBJECT_STORE_NAME_TOO_LONG, 0, JSEXN_TYPEERR, "ObjectStore constructor: name can not be more than 255 characters")
5057
MSG_DEF(JSMSG_OBJECT_STORE_NAME_NO_CONTROL_CHARACTERS, 0, JSEXN_TYPEERR, "ObjectStore constructor: name can not contain control characters (\\u0000-\\u001F)")

0 commit comments

Comments
 (0)