Waarom toString een lijst werd in twee Node.js HTTP-adapters
Inversify is een dependency-injection-library voor TypeScript. De HTTP-pakketten hebben adapters voor Express, Fastify, Hono en uWebSockets. Ik vond deze bug toen ik vergeleek hoe de adapters querystrings en formulieren uitlezen.
De bug
De Hono- en uWebSockets-adapters verzamelden formuliervelden in een gewoon object, en de uWebSockets-adapter deed dat ook voor queryparameters. Om herhaalde sleutels zoals tag=a&tag=b te ondersteunen, keek de parser of een sleutel al een waarde had.
if (parsedBody[key] === undefined) {
parsedBody[key] = value;
} else if (Array.isArray(parsedBody[key])) {
parsedBody[key].push(value);
} else {
parsedBody[key] = [parsedBody[key], value];
}Een gewoon object erft van Object.prototype. Bij een sleutel als toString of constructor is parsedBody[key] niet undefined maar de geërfde functie. De eerste waarde werd daardoor als herhaling gezien, en het resultaat was een lijst met de functie en de waarde. De Express-adapter gaf voor hetzelfde verzoek gewoon toString: 'x' terug.
De oplossing
De query van uWebSockets komt nu in een object dat gemaakt is met Object.create(null). Dat heeft geen prototype, net als de query-objecten van de andere adapters. De body-parsers houden een gewoon object, controleren sleutels met Object.hasOwn en voegen nieuwe sleutels toe met Object.defineProperty.
const existingValue = Object.hasOwn(parsedBody, key) ? parsedBody[key] : undefined;
if (existingValue === undefined) {
Object.defineProperty(parsedBody, key, {
configurable: true,
enumerable: true,
value,
writable: true,
});
} else if (Array.isArray(existingValue)) {
existingValue.push(value);
} else {
parsedBody[key] = [existingValue, value];
}Die defineProperty is nodig voor één sleutel. Een gewone toewijzing zoals parsedBody['__proto__'] = 'z' maakt geen veld aan. Het roept de __proto__-setter aan, die een string negeert, dus het veld verdwijnt zonder foutmelding. defineProperty maakt altijd een eigen property aan.
De maintainer vroeg om tests met een herhaalde prototype-sleutel en __proto__. De uiteindelijke tests sturen toString=x&toString=y&__proto__=z en verwachten dat toString gelijk is aan ["x", "y"] en __proto__ aan "z". Zonder defineProperty faalt de __proto__-test.
Wat je ervan meeneemt
Bouw je een object op uit gebruikersinvoer, controleer sleutels dan met Object.hasOwn in plaats van obj[key] === undefined. Heeft het object geen prototype-methodes nodig, begin dan met Object.create(null).
De oplossing is gemerged in inversify/monorepo#2166.