@@ -312,6 +312,13 @@ class Parser : public AsyncWrap, public StreamListener {
312312 current_buffer_data_(nullptr ),
313313 binding_data_(binding_data) {}
314314
315+ enum InternalFields {
316+ kOnHeadersCompleteCallback = AsyncWrap::kInternalFieldCount ,
317+ kOnBodyCallback ,
318+ kOnMessageCompleteCallback ,
319+ kInternalFieldCount
320+ };
321+
315322 SET_NO_MEMORY_INFO ()
316323 SET_MEMORY_INFO_NAME (Parser)
317324 SET_SELF_SIZE (Parser)
@@ -441,9 +448,8 @@ class Parser : public AsyncWrap, public StreamListener {
441448 };
442449
443450 Local<Value> argv[A_MAX ];
444- Local<Object> obj = object ();
445- Local<Value> cb = obj->Get (env ()->context (),
446- kOnHeadersComplete ).ToLocalChecked ();
451+ Local<Value> cb =
452+ CachedCallback (kOnHeadersComplete , kOnHeadersCompleteCallback );
447453
448454 if (!cb->IsFunction ())
449455 return 0 ;
@@ -520,7 +526,7 @@ class Parser : public AsyncWrap, public StreamListener {
520526 Environment* env = this ->env ();
521527 HandleScope handle_scope (env->isolate ());
522528
523- Local<Value> cb = object ()-> Get (env-> context (), kOnBody ). ToLocalChecked ( );
529+ Local<Value> cb = CachedCallback ( kOnBody , kOnBodyCallback );
524530
525531 if (!cb->IsFunction ())
526532 return 0 ;
@@ -553,9 +559,8 @@ class Parser : public AsyncWrap, public StreamListener {
553559
554560 header_pairs_ = 0 ;
555561
556- Local<Object> obj = object ();
557- Local<Value> cb = obj->Get (env ()->context (),
558- kOnMessageComplete ).ToLocalChecked ();
562+ Local<Value> cb =
563+ CachedCallback (kOnMessageComplete , kOnMessageCompleteCallback );
559564
560565 if (!cb->IsFunction ())
561566 return 0 ;
@@ -624,6 +629,7 @@ class Parser : public AsyncWrap, public StreamListener {
624629 // it needs to be triggered manually.
625630 parser->EmitTraceEventDestroy ();
626631 parser->EmitDestroy ();
632+ parser->ClearCachedCallbacks ();
627633 }
628634
629635 // TODO(@anonrig): Add V8 Fast API
@@ -946,6 +952,9 @@ class Parser : public AsyncWrap, public StreamListener {
946952 Local<Value> headers_v[kMaxHeaderFieldsCount * 2 ];
947953
948954 for (size_t i = 0 ; i < num_values_; ++i) {
955+ // Field names are not internalized: header names are attacker
956+ // controlled, so a flood of unique names would grow V8's string table
957+ // and pay the interning cost on every request with no dedup benefit.
949958 headers_v[i * 2 ] = fields_[i].ToString (env ());
950959 headers_v[i * 2 + 1 ] = values_[i].ToTrimmedString (env ());
951960 }
@@ -980,12 +989,33 @@ class Parser : public AsyncWrap, public StreamListener {
980989 have_flushed_ = true ;
981990 }
982991
992+ void ClearCachedCallbacks () {
993+ Local<Value> undefined = Undefined (env ()->isolate ());
994+ object ()->SetInternalField (kOnHeadersCompleteCallback , undefined);
995+ object ()->SetInternalField (kOnBodyCallback , undefined);
996+ object ()->SetInternalField (kOnMessageCompleteCallback , undefined);
997+ }
998+
999+ // Keep cached callbacks on the JS object so they do not keep the parser
1000+ // alive when a callback closes over it.
1001+ Local<Value> CachedCallback (uint32_t index, int field) {
1002+ Local<Object> obj = object ();
1003+ Local<Value> cb = obj->GetInternalField (field).As <Value>();
1004+ if (cb->IsFunction ()) return cb;
1005+
1006+ cb = obj->Get (env ()->context (), index).ToLocalChecked ();
1007+ if (cb->IsFunction ()) obj->SetInternalField (field, cb);
1008+ return cb;
1009+ }
1010+
9831011 void Init (llhttp_type_t type,
9841012 uint64_t max_http_header_size,
9851013 uint32_t lenient_flags,
9861014 size_t max_header_pairs) {
9871015 llhttp_init (&parser_, type, &settings);
9881016
1017+ ClearCachedCallbacks ();
1018+
9891019 if (lenient_flags & kLenientHeaders ) {
9901020 llhttp_set_lenient_headers (&parser_, 1 );
9911021 }
0 commit comments