stringify-form-data.js 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
  1. import { HttpProxyMiddlewareError } from '../../errors.js';
  2. const CR_OR_LF = /[\r\n]/;
  3. /**
  4. * HPM_ERR_INVALID_MULTIPART prefixed error code will be used in
  5. * [status-code.ts]({@link ../../status-code.ts}) to return status code 400.
  6. */
  7. export const HPM_ERR_INVALID_MULTIPART = 'HPM_ERR_INVALID_MULTIPART';
  8. /**
  9. * stringify FormData data
  10. * @param contentType
  11. * @param data
  12. * @returns
  13. */
  14. export function stringifyFormData(contentType, data) {
  15. const boundary = getMultipartBoundary(contentType);
  16. let str = '';
  17. for (const [key, value] of Object.entries(data)) {
  18. const normalizedKey = String(key);
  19. const normalizedValue = String(value);
  20. // Reject potentially dangerous sequences to prevent multipart header/body injection.
  21. validateMultipartField(normalizedKey, normalizedValue, boundary);
  22. str += `--${boundary}\r\nContent-Disposition: form-data; name="${escapeMultipartFieldName(normalizedKey)}"\r\n\r\n${normalizedValue}\r\n`;
  23. }
  24. return str;
  25. }
  26. function getMultipartBoundary(contentType) {
  27. const boundaryMatch = /(?:^|;)\s*boundary=(?:"([^"]+)"|([^;]+))/i.exec(contentType);
  28. // Keep backward-compatible behavior when boundary is omitted: fall back to legacy extraction.
  29. const boundary = (boundaryMatch?.[1] ?? boundaryMatch?.[2] ?? contentType).trim();
  30. if (!boundary || CR_OR_LF.test(boundary)) {
  31. throw new HttpProxyMiddlewareError('[HPM] invalid multipart boundary detected.', `${HPM_ERR_INVALID_MULTIPART}_BOUNDARY`);
  32. }
  33. return boundary;
  34. }
  35. function validateMultipartField(fieldName, fieldValue, boundary) {
  36. const boundaryDelimiter = `--${boundary}`;
  37. if (CR_OR_LF.test(fieldName)) {
  38. throw new HttpProxyMiddlewareError(`[HPM] invalid multipart field name "${fieldName}" detected.`, `${HPM_ERR_INVALID_MULTIPART}_FIELD_NAME`);
  39. }
  40. if (CR_OR_LF.test(fieldValue) || fieldValue.includes(boundaryDelimiter)) {
  41. throw new HttpProxyMiddlewareError(`[HPM] invalid multipart field value for "${fieldName}" detected.`, `${HPM_ERR_INVALID_MULTIPART}_FIELD_VALUE`);
  42. }
  43. }
  44. function escapeMultipartFieldName(fieldName) {
  45. return fieldName.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
  46. }