Skip to content

Commit dfafda8

Browse files
committed
http: internalize only know headers
1 parent 91ca169 commit dfafda8

1 file changed

Lines changed: 106 additions & 10 deletions

File tree

src/node_http_parser.cc

Lines changed: 106 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@
2828
#include "llhttp.h"
2929
#include "memory_tracker-inl.h"
3030
#include "node_external_reference.h"
31-
#include "node_http_common.h"
3231
#include "stream_base-inl.h"
3332
#include "v8.h"
3433

3534
#include <cstdlib> // free()
3635
#include <cstring> // strdup(), strchr()
37-
36+
#include <string_view>
37+
#include <unordered_set>
3838

3939
// This is a binding to llhttp (https://github.com/nodejs/llhttp)
4040
// The goal is to decouple sockets from parsing for more javascript-level
@@ -109,6 +109,106 @@ inline bool IsOWS(char c) {
109109
return c == ' ' || c == '\t';
110110
}
111111

112+
constexpr std::string_view kKnownHeaderNames[] = {
113+
"Accept-Encoding",
114+
"Accept-Language",
115+
"Accept-Ranges",
116+
"Accept",
117+
"Access-Control-Allow-Credentials",
118+
"Access-Control-Allow-Headers",
119+
"Access-Control-Allow-Methods",
120+
"Access-Control-Allow-Origin",
121+
"Access-Control-Expose-Headers",
122+
"Access-Control-Request-Headers",
123+
"Access-Control-Request-Method",
124+
"Age",
125+
"Authorization",
126+
"Cache-Control",
127+
"Connection",
128+
"Content-Disposition",
129+
"Content-Encoding",
130+
"Content-Length",
131+
"Content-Type",
132+
"Cookie",
133+
"Date",
134+
"ETag",
135+
"Forwarded",
136+
"Host",
137+
"If-Modified-Since",
138+
"If-None-Match",
139+
"If-Range",
140+
"Last-Modified",
141+
"Link",
142+
"Location",
143+
"Range",
144+
"Referer",
145+
"Server",
146+
"Set-Cookie",
147+
"Strict-Transport-Security",
148+
"Transfer-Encoding",
149+
"TE",
150+
"Upgrade-Insecure-Requests",
151+
"Upgrade",
152+
"User-Agent",
153+
"Vary",
154+
"X-Content-Type-Options",
155+
"X-Frame-Options",
156+
"Keep-Alive",
157+
"Proxy-Connection",
158+
"X-XSS-Protection",
159+
"Alt-Svc",
160+
"Content-Security-Policy",
161+
"Early-Data",
162+
"Expect-CT",
163+
"Origin",
164+
"Purpose",
165+
"Timing-Allow-Origin",
166+
"X-Forwarded-For",
167+
"Priority",
168+
"Accept-Charset",
169+
"Access-Control-Max-Age",
170+
"Allow",
171+
"Content-Language",
172+
"Content-Location",
173+
"Content-MD5",
174+
"Content-Range",
175+
"DNT",
176+
"Expect",
177+
"Expires",
178+
"From",
179+
"If-Match",
180+
"If-Unmodified-Since",
181+
"Max-Forwards",
182+
"Prefer",
183+
"Proxy-Authenticate",
184+
"Proxy-Authorization",
185+
"Refresh",
186+
"Retry-After",
187+
"Trailer",
188+
"Tk",
189+
"Via",
190+
"Warning",
191+
"WWW-Authenticate",
192+
"HTTP2-Settings",
193+
};
194+
195+
constexpr size_t kMaxKnownHeaderNameLength = [] {
196+
size_t max = 0;
197+
for (std::string_view name : kKnownHeaderNames) {
198+
if (name.size() > max) max = name.size();
199+
}
200+
return max;
201+
}();
202+
203+
// Only known header field names in their canonical Train-Case form may be
204+
// internalized. Internalizing arbitrary client-controlled names would let
205+
// peers pollute the isolate-wide string table.
206+
bool IsKnownHeaderName(const char* str, size_t size) {
207+
static const std::unordered_set<std::string_view> known_names(
208+
std::begin(kKnownHeaderNames), std::end(kKnownHeaderNames));
209+
return known_names.contains(std::string_view(str, size));
210+
}
211+
112212
class BindingData : public BaseObject {
113213
public:
114214
BindingData(Realm* realm, Local<Object> obj) : BaseObject(realm, obj) {}
@@ -236,13 +336,9 @@ struct StringPtr {
236336
}
237337

238338
Local<String> ToInternalizedString(Environment* env) const {
239-
// Only internalize short names to avoid pressuring the string table.
240-
if (size_ != 0 && size_ < kMaxInternalizedHeaderNameLength) {
241-
return String::NewFromOneByte(env->isolate(),
242-
reinterpret_cast<const uint8_t*>(str_),
243-
NewStringType::kInternalized,
244-
size_)
245-
.ToLocalChecked();
339+
if (size_ <= kMaxKnownHeaderNameLength && IsKnownHeaderName(str_, size_)) {
340+
return OneByteString(
341+
env->isolate(), str_, size_, NewStringType::kInternalized);
246342
}
247343
return ToString(env);
248344
}
@@ -954,7 +1050,7 @@ class Parser : public AsyncWrap, public StreamListener {
9541050
Local<Value> headers_v[kMaxHeaderFieldsCount * 2];
9551051

9561052
for (size_t i = 0; i < num_values_; ++i) {
957-
// Field names repeat across requests, so internalize them.
1053+
// Known field names repeat across requests, so internalize them.
9581054
headers_v[i * 2] = fields_[i].ToInternalizedString(env());
9591055
headers_v[i * 2 + 1] = values_[i].ToTrimmedString(env());
9601056
}

0 commit comments

Comments
 (0)