Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -1896,6 +1896,18 @@ console.log(JSON.stringify(myMIMES));
// Prints: ["image/png", "image/gif"]
```

### `MIMEType.parse(string)`
Comment thread
jasnell marked this conversation as resolved.

<!--
added: REPLACEME
-->

* `string` {string} The input MIME to parse
* Returns: {MIMEType|null}

Attempts to parse the given `string` as a MIMEType. If the string cannot be
parsed, `null` is returned.

## Class: `util.MIMEParams`

<!-- YAML
Expand Down
9 changes: 2 additions & 7 deletions lib/internal/data_url.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,8 @@ function dataURLProcessor(dataURL) {
// mimeType.
// 14. If mimeTypeRecord is failure, then set
// mimeTypeRecord to text/plain;charset=US-ASCII.
let mimeTypeRecord;

try {
mimeTypeRecord = new MIMEType(mimeType);
} catch {
mimeTypeRecord = new MIMEType('text/plain;charset=US-ASCII');
}
const mimeTypeRecord = MIMEType.parse(mimeType) ||
new MIMEType('text/plain;charset=US-ASCII');

// 15. Return a new data: URL struct whose MIME
// type is mimeTypeRecord and body is body.
Expand Down
13 changes: 3 additions & 10 deletions lib/internal/inspector/network.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,9 @@ function getNextRequestId() {
};

function sniffMimeType(contentType) {
let mimeType;
let charset;
try {
const mimeTypeObj = new MIMEType(contentType);
mimeType = StringPrototypeToLowerCase(mimeTypeObj.essence || '');
charset = StringPrototypeToLowerCase(mimeTypeObj.params.get('charset') || '');
} catch {
mimeType = '';
charset = '';
}
const mimeTypeObj = MIMEType.parse(contentType);
const mimeType = StringPrototypeToLowerCase(mimeTypeObj?.essence || '');
const charset = StringPrototypeToLowerCase(mimeTypeObj?.params.get('charset') || '');

return {
__proto__: null,
Expand Down
30 changes: 24 additions & 6 deletions lib/internal/mime.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ const {
StringPrototypeIndexOf,
StringPrototypeSlice,
StringPrototypeToLowerCase,
Symbol,
SymbolIterator,
} = primordials;
const {
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_MIME_SYNTAX,
} = require('internal/errors').codes;

Expand All @@ -22,6 +24,8 @@ const NOT_HTTP_QUOTED_STRING_CODE_POINT = /[^\t\u0020-~\u0080-\u00FF]/g;
const END_BEGINNING_WHITESPACE = /[^\r\n\t ]|$/;
const START_ENDING_WHITESPACE = /[\r\n\t ]*$/;

const kNoThrow = Symbol('kNoThrow');

function toASCIILower(str) {
// eslint-disable-next-line no-control-regex
if (!/[^\x00-\x7f]/.test(str)) return StringPrototypeToLowerCase(str);
Expand All @@ -39,7 +43,7 @@ function toASCIILower(str) {
const SOLIDUS = '/';
const SEMICOLON = ';';

function parseTypeAndSubtype(str) {
function parseTypeAndSubtype(str, noThrow = null) {
// Skip only HTTP whitespace from start
let position = SafeStringPrototypeSearch(str, END_BEGINNING_WHITESPACE);
// read until '/'
Expand All @@ -50,6 +54,7 @@ function parseTypeAndSubtype(str) {
const invalidTypeIndex = SafeStringPrototypeSearch(trimmedType,
NOT_HTTP_TOKEN_CODE_POINT);
if (trimmedType === '' || invalidTypeIndex !== -1 || typeEnd === -1) {
if (noThrow === kNoThrow) return null;
throw new ERR_INVALID_MIME_SYNTAX('type', str, invalidTypeIndex);
}
// skip type and '/'
Expand All @@ -72,6 +77,7 @@ function parseTypeAndSubtype(str) {
const invalidSubtypeIndex = SafeStringPrototypeSearch(trimmedSubtype,
NOT_HTTP_TOKEN_CODE_POINT);
if (trimmedSubtype === '' || invalidSubtypeIndex !== -1) {
if (noThrow === kNoThrow) return null;
throw new ERR_INVALID_MIME_SYNTAX('subtype', str, invalidSubtypeIndex);
}
const subtype = toASCIILower(trimmedSubtype);
Expand Down Expand Up @@ -335,12 +341,24 @@ class MIMEType {
#type;
#subtype;
#parameters;
constructor(string) {
constructor(string, noThrowSymbol = null) {
string = `${string}`;
const data = parseTypeAndSubtype(string);
this.#type = data[0];
this.#subtype = data[1];
this.#parameters = instantiateMimeParams(StringPrototypeSlice(string, data[2]));
// noThrowSymbol can be null or kNoThrow, but not any other value
if (noThrowSymbol != null && noThrowSymbol !== kNoThrow) {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
const data = parseTypeAndSubtype(string, noThrowSymbol);
if (data != null) {
this.#type = data[0];
this.#subtype = data[1];
this.#parameters = instantiateMimeParams(StringPrototypeSlice(string, data[2]));
}
}

// Like the constructor, but returns null instead of throwing on invalid input.
static parse(string) {
const mt = new MIMEType(string, kNoThrow);
return mt.type ? mt : null;
}

get type() {
Expand Down
14 changes: 14 additions & 0 deletions test/parallel/test-mime-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,17 @@ assert.throws(() => params.set('x', `x${NOT_HTTP_QUOTED_STRING_CODE_POINT}`), /p
assert.strictEqual(params.has('foo'), false);
assert.deepStrictEqual([...params], []);
}

{
// Non-throwing MimeType.parse, works for valid
const mime = MIMEType.parse('text/plain;Charset=value');
assert.strictEqual(mime.params.get('Charset'), 'value');
assert.strictEqual(mime.params.get('charset'), 'value');
assert.strictEqual(mime.params.get('CHARSET'), 'value');
assert.strictEqual(mime.params.has('Charset'), true);
assert.strictEqual(`${mime.params}`, 'charset=value');

// Returns null on Invalid
const invalidMime = MIMEType.parse('text plain');
assert.strictEqual(invalidMime, null);
}
Loading