Adopts #10490: feat: support v4 signed url endpoint#10709
Conversation
….com/7hokerz/firebase-tools into 7hokerz/emulator-support-v4-endpoint
Wiz Scan Summary
To detect these findings earlier in the dev lifecycle, try using Wiz Code VS Code Extension. |
There was a problem hiding this comment.
Code Review
This pull request adds support for signed POST policy uploads via the emulator endpoint by introducing a new multipart/form-data parser and integrating it into the GCS emulator. Feedback on the changes highlights an invalid TypeScript type assertion when mapping headers to metadata, suggesting a more type-safe approach, and points out that the content-type check and boundary extraction should be case-insensitive to comply with RFC 2045.
| const HEADER_MAP: Record<string, keyof IncomingMetadata> = { | ||
| "content-type": "contentType", | ||
| "cache-control": "cacheControl", | ||
| "content-disposition": "contentDisposition", | ||
| "content-encoding": "contentEncoding", | ||
| "content-language": "contentLanguage", | ||
| }; | ||
| for (const part of formData) { | ||
| if (part.type === "file" || part.name === "key") continue; | ||
| const key = part.name.toLowerCase(); | ||
| if (key.startsWith("x-goog-meta-")) { | ||
| metadata.metadata![key.substring(12)] = part.value; | ||
| } else if (HEADER_MAP[key]) { | ||
| (metadata[HEADER_MAP[key]] as string) = part.value.trim(); | ||
| } | ||
| } |
There was a problem hiding this comment.
The type assertion on the left-hand side of the assignment (metadata[HEADER_MAP[key]] as string) = ... is invalid TypeScript and can cause compilation errors depending on the compiler configuration. We can make this completely type-safe and avoid any type assertions by typing HEADER_MAP more precisely using Omit to exclude non-string properties of IncomingMetadata.
const HEADER_MAP: Record<string, keyof Omit<IncomingMetadata, "metadata" | "name">> = {
"content-type": "contentType",
"cache-control": "cacheControl",
"content-disposition": "contentDisposition",
"content-encoding": "contentEncoding",
"content-language": "contentLanguage",
};
for (const part of formData) {
if (part.type === "file" || part.name === "key") continue;
const key = part.name.toLowerCase();
if (key.startsWith("x-goog-meta-")) {
metadata.metadata![key.substring(12)] = part.value;
} else {
const metaKey = HEADER_MAP[key];
if (metaKey) {
metadata[metaKey] = part.value.trim();
}
}
}| if (!contentTypeHeader.startsWith("multipart/form-data")) { | ||
| throw new Error(`Bad content type. ${contentTypeHeader}`); | ||
| } | ||
| const boundaryId = contentTypeHeader.split("boundary=")[1]?.split(";")[0]?.trim(); | ||
| if (!boundaryId) { | ||
| throw new Error(`Bad content type. ${contentTypeHeader}`); | ||
| } |
There was a problem hiding this comment.
The contentTypeHeader check and boundary extraction are case-sensitive, which violates RFC 2045 (stating that media types and parameter names are case-insensitive). For example, if a client sends Multipart/Form-Data or uses Boundary=..., the parsing will fail. We should perform a case-insensitive check on the media type and use a regular expression to extract the boundary parameter case-insensitively while preserving the case of the boundary value itself.
| if (!contentTypeHeader.startsWith("multipart/form-data")) { | |
| throw new Error(`Bad content type. ${contentTypeHeader}`); | |
| } | |
| const boundaryId = contentTypeHeader.split("boundary=")[1]?.split(";")[0]?.trim(); | |
| if (!boundaryId) { | |
| throw new Error(`Bad content type. ${contentTypeHeader}`); | |
| } | |
| if (!contentTypeHeader.toLowerCase().startsWith("multipart/form-data")) { | |
| throw new Error("Bad content type. " + contentTypeHeader); | |
| } | |
| const boundaryMatch = contentTypeHeader.match(/boundary\s*=\s*([^;]+)/i); | |
| const boundaryId = boundaryMatch?.[1]?.trim(); | |
| if (!boundaryId) { | |
| throw new Error("Bad content type. " + contentTypeHeader); | |
| } |
| return res.sendStatus(404); | ||
| } | ||
| if (err instanceof Error) { | ||
| return res.status(400).send(err.message); |
adopts PR# 10490 for merging