Content-Type Header Parser

Parse media types and parameters from Content-Type headers, handling quoted strings, charset and duplicate names.

TypescriptData Fetching

Code

snippet
export interface ContentType {
  type: string;
  subtype: string;
  essence: string;
  parameters: Record<string, string>;
}

function splitParameters(header: string): string[] {
  const parts: string[] = [];
  let current = "";
  let quoted = false;
  for (let index = 0; index < header.length; index += 1) {
    const character = header[index]!;
    if (character === "\\" && quoted && index + 1 < header.length) {
      current += character + header[index + 1];
      index += 1;
      continue;
    }
    if (character === '"') quoted = !quoted;
    if (character === ";" && !quoted) {
      parts.push(current);
      current = "";
      continue;
    }
    current += character;
  }
  parts.push(current);
  return parts;
}

export function parseContentType(header: string): ContentType {
  if (typeof header !== "string" || header.trim() === "") {
    throw new TypeError("header must be a non-empty string");
  }
  const [essencePart, ...parameterParts] = splitParameters(header);
  const [type, subtype] = (essencePart ?? "").split("/");
  const token = /^[a-z0-9!#$&^_.+*-]+$/i;
  if (!type || !subtype || !token.test(type) || !token.test(subtype)) {
    throw new SyntaxError("invalid media type: " + essencePart);
  }
  const parameters: Record<string, string> = {};
  for (const part of parameterParts) {
    const separator = part.indexOf("=");
    if (separator <= 0) continue;
    const name = part.slice(0, separator).trim().toLowerCase();
    let value = part.slice(separator + 1).trim();
    if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
      value = value.slice(1, -1).replace(/\\(.)/g, "$1");
    }
    if (!(name in parameters)) parameters[name] = value;
  }
  return {
    type: type.toLowerCase(),
    subtype: subtype.toLowerCase(),
    essence: type.toLowerCase() + "/" + subtype.toLowerCase(),
    parameters,
  };
}