/** * Given an Accept header and a list of possible content types, pick * the most suitable one to respond with * @param {string} accept * @param {string[]} types */ export function negotiate(accept, types) { /** @type {Array<{ type: string, subtype: string, q: number, i: number }>} */ const parts = []; accept.split(',').forEach((str, i) => { const match = /([^/ \t]+)\/([^; \t]+)[ \t]*(?:;[ \t]*q=([0-9.]+))?/.exec(str); // no match equals invalid header — ignore if (match) { const [, type, subtype, q = '1'] = match; parts.push({ type, subtype, q: +q, i }); } }); parts.sort((a, b) => { if (a.q !== b.q) { return b.q - a.q; } if ((a.subtype === '*') !== (b.subtype === '*')) { return a.subtype === '*' ? 1 : -1; } if ((a.type === '*') !== (b.type === '*')) { return a.type === '*' ? 1 : -1; } return a.i - b.i; }); let accepted; let min_priority = Infinity; for (const mimetype of types) { const [type, subtype] = mimetype.split('/'); const priority = parts.findIndex( (part) => (part.type === type || part.type === '*') && (part.subtype === subtype || part.subtype === '*') ); if (priority !== -1 && priority < min_priority) { accepted = mimetype; min_priority = priority; } } return accepted; } /** * Returns `true` if the request contains a `content-type` header with the given type * @param {Request} request * @param {...string} types */ function is_content_type(request, ...types) { const type = request.headers.get('content-type')?.split(';', 1)[0].trim() ?? ''; return types.includes(type.toLowerCase()); } /** * @param {Request} request */ export function is_form_content_type(request) { // These content types must be protected against CSRF // https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/enctype return is_content_type( request, 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain' ); }