@@ -32,6 +32,7 @@ const {
3232 ObjectKeys,
3333 String,
3434 StringPrototypeCharCodeAt,
35+ StringPrototypeIndexOf,
3536 StringPrototypeSlice,
3637 decodeURIComponent,
3738} = primordials ;
@@ -82,6 +83,11 @@ const unhexTable = new Int8Array([
8283 * @returns {string }
8384 */
8485function unescapeBuffer ( s , decodeSpaces ) {
86+ // No encoded bytes: latin1-copy the string without walking %XX.
87+ if ( StringPrototypeIndexOf ( s , '%' ) === - 1 ) {
88+ if ( ! decodeSpaces || StringPrototypeIndexOf ( s , '+' ) === - 1 )
89+ return Buffer . from ( s , 'latin1' ) ;
90+ }
8591 const out = Buffer . allocUnsafe ( s . length ) ;
8692 let index = 0 ;
8793 let outIndex = 0 ;
@@ -303,6 +309,120 @@ function addKeyVal(obj, key, value, keyEncoded, valEncoded, decode) {
303309 }
304310}
305311
312+ /**
313+ * Default querystring parser: separator '&', equals '=', native unescape.
314+ * Single-character separators avoid the sep/eq index state machine.
315+ * @param {string } qs
316+ * @param {Record<string, string | string[]> } obj
317+ * @returns {Record<string, string | string[]> }
318+ */
319+ function parseAmpEq ( qs , obj ) {
320+ let pairs = 1000 ;
321+ let lastPos = 0 ;
322+ let eqPos = - 1 ;
323+ let key = '' ;
324+ let value = '' ;
325+ let keyEncoded = false ;
326+ let valEncoded = false ;
327+ let encodeCheck = 0 ;
328+ const plusChar = ' ' ;
329+ const len = qs . length ;
330+
331+ for ( let i = 0 ; i < len ; ++ i ) {
332+ const code = StringPrototypeCharCodeAt ( qs , i ) ;
333+
334+ if ( code === 38 ) { // '&'
335+ const end = i ;
336+ if ( eqPos === - 1 ) {
337+ if ( lastPos < end )
338+ key += StringPrototypeSlice ( qs , lastPos , end ) ;
339+ else if ( key . length === 0 ) {
340+ if ( -- pairs === 0 )
341+ return obj ;
342+ lastPos = i + 1 ;
343+ encodeCheck = 0 ;
344+ continue ;
345+ }
346+ } else if ( lastPos < end ) {
347+ value += StringPrototypeSlice ( qs , lastPos , end ) ;
348+ }
349+
350+ addKeyVal ( obj , key , value , keyEncoded , valEncoded , qsUnescape ) ;
351+
352+ if ( -- pairs === 0 )
353+ return obj ;
354+ keyEncoded = false ;
355+ valEncoded = false ;
356+ key = '' ;
357+ value = '' ;
358+ encodeCheck = 0 ;
359+ lastPos = i + 1 ;
360+ eqPos = - 1 ;
361+ continue ;
362+ }
363+
364+ if ( eqPos === - 1 ) {
365+ if ( code === 61 ) { // '='
366+ if ( lastPos < i )
367+ key += StringPrototypeSlice ( qs , lastPos , i ) ;
368+ encodeCheck = 0 ;
369+ lastPos = i + 1 ;
370+ eqPos = i ;
371+ continue ;
372+ }
373+ if ( ! keyEncoded ) {
374+ if ( code === 37 ) {
375+ encodeCheck = 1 ;
376+ } else if ( encodeCheck > 0 ) {
377+ if ( isHexTable [ code ] === 1 ) {
378+ if ( ++ encodeCheck === 3 )
379+ keyEncoded = true ;
380+ } else {
381+ encodeCheck = 0 ;
382+ }
383+ }
384+ }
385+ if ( code === 43 ) {
386+ if ( lastPos < i )
387+ key += StringPrototypeSlice ( qs , lastPos , i ) ;
388+ key += plusChar ;
389+ lastPos = i + 1 ;
390+ }
391+ continue ;
392+ }
393+
394+ if ( code === 43 ) {
395+ if ( lastPos < i )
396+ value += StringPrototypeSlice ( qs , lastPos , i ) ;
397+ value += plusChar ;
398+ lastPos = i + 1 ;
399+ } else if ( ! valEncoded ) {
400+ if ( code === 37 ) {
401+ encodeCheck = 1 ;
402+ } else if ( encodeCheck > 0 ) {
403+ if ( isHexTable [ code ] === 1 ) {
404+ if ( ++ encodeCheck === 3 )
405+ valEncoded = true ;
406+ } else {
407+ encodeCheck = 0 ;
408+ }
409+ }
410+ }
411+ }
412+
413+ if ( lastPos < len ) {
414+ if ( eqPos === - 1 )
415+ key += StringPrototypeSlice ( qs , lastPos ) ;
416+ else
417+ value += StringPrototypeSlice ( qs , lastPos ) ;
418+ } else if ( eqPos === - 1 && key . length === 0 ) {
419+ return obj ;
420+ }
421+
422+ addKeyVal ( obj , key , value , keyEncoded , valEncoded , qsUnescape ) ;
423+ return obj ;
424+ }
425+
306426/**
307427 * Parse a key/val string.
308428 * @param {string } qs
@@ -321,6 +441,16 @@ function parse(qs, sep, eq, options) {
321441 return obj ;
322442 }
323443
444+ // Default '&' / '=' / unescape: single-char scanner, no sep/eq arrays.
445+ if ( ( sep === undefined || sep === '&' ) &&
446+ ( eq === undefined || eq === '=' ) &&
447+ QueryString . unescape === qsUnescape &&
448+ ( ! options ||
449+ ( options . maxKeys === undefined &&
450+ options . decodeURIComponent === undefined ) ) ) {
451+ return parseAmpEq ( qs , obj ) ;
452+ }
453+
324454 const sepCodes = ( ! sep ? defSepCodes : charCodes ( String ( sep ) ) ) ;
325455 const eqCodes = ( ! eq ? defEqCodes : charCodes ( String ( eq ) ) ) ;
326456 const sepLen = sepCodes . length ;
0 commit comments