@schukai/monster
Version:
Monster is a simple library for creating fast, robust and lightweight websites.
219 lines (189 loc) • 5.02 kB
JavaScript
/**
* Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved.
* Node module: @schukai/monster
*
* This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
* The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
*
* For those who do not wish to adhere to the AGPLv3, a commercial license is available.
* Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
* For more information about purchasing a commercial license, please contact Volker Schukai.
*
* SPDX-License-Identifier: AGPL-3.0
*/
import { Base } from "./base.mjs";
import { isString } from "./is.mjs";
import { validateArray, validateString } from "./validate.mjs";
import { instanceSymbol } from "../constants.mjs";
export { MediaType, parseMediaType };
/**
* @private
* @type {symbol}
*/
const internal = Symbol("internal");
/**
* @typedef {Object} Parameter
* @property {string} key
* @property {string} value
*/
/**
* You can create an object via the monster namespace `new MediaType()`.
*
* @license AGPLv3
* @since 1.8.0
* @copyright Volker Schukai
*/
class MediaType extends Base {
/**
*
* @param {String} type
* @param {String} subtype
* @param {Parameter[]} parameter
*/
constructor(type, subtype, parameter) {
super();
this[internal] = {
type: validateString(type).toLowerCase(),
subtype: validateString(subtype).toLowerCase(),
parameter: [],
};
if (parameter !== undefined) {
this[internal]["parameter"] = validateArray(parameter);
}
}
/**
* This method is called by the `instanceof` operator.
* @return {symbol}
* @since 2.1.0
*/
static get [instanceSymbol]() {
return Symbol.for("@schukai/monster/types/media-type");
}
/**
* @return {String}
*/
get type() {
return this[internal].type;
}
/**
* @return {String}
*/
get subtype() {
return this[internal].subtype;
}
// /**
// * @return {Parameter[]}
// */
// get parameter() {
// return this[internal].parameter;
// }
/**
*
*
* @return {Map}
*/
get parameter() {
const result = new Map();
this[internal]["parameter"].forEach((p) => {
let value = p.value;
// internally special values are partly stored with quotes, this function removes them.
if (value.startsWith('"') && value.endsWith('"')) {
value = value.substring(1, value.length - 1);
}
result.set(p.key, value);
});
return result;
}
/**
*
* @return {string}
*/
toString() {
const parameter = [];
for (const a of this[internal].parameter) {
parameter.push(`${a.key}=${a.value}`);
}
return `${this[internal].type}/${this[internal].subtype}${
parameter.length > 0 ? `;${parameter.join(";")}` : ""
}`;
}
}
/**
* You can call the function via the monster namespace `parseMediaType()`.
*
* ```
* <script type="module">
* import {Monster} from '@schukai/monster/source/monster.mjs';
* console.log(parseMediaType())
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {parseMediaType} from '@schukai/monster/source/types/dataurl.mjs';
* console.log(parseMediaType())
* </script>
* ```
*
* Specification:
*
* ```
* dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
* mediatype := [ type "/" subtype ] *( ";" parameter )
* data := *urlchar
* parameter := attribute "=" value
* ```
*
* @param {String} mediatype
* @return {MediaType}
* @see https://datatracker.ietf.org/doc/html/rfc2045#section-5.1
* @throws {TypeError} the mimetype can not be parsed
* @throws {TypeError} blank value is not allowed
* @throws {TypeError} malformed data url
*/
function parseMediaType(mediatype) {
const regex =
/(?<type>[A-Za-z]+|\*)\/(?<subtype>([a-zA-Z0-9.\+_\-]+)|\*|)(?<parameter>\s*;\s*([a-zA-Z0-9]+)\s*(=\s*("?[A-Za-z0-9_\-]+"?))?)*/g;
const result = regex.exec(validateString(mediatype));
const groups = result?.["groups"];
if (groups === undefined) {
throw new TypeError("the mimetype can not be parsed");
}
const type = groups?.["type"];
const subtype = groups?.["subtype"];
const parameter = groups?.["parameter"];
if (subtype === "" || type === "") {
throw new TypeError("blank value is not allowed");
}
return new MediaType(type, subtype, parseParameter(parameter));
}
/**
* @private
* @license AGPLv3
* @since 1.18.0
* @param {String} parameter
* @return {Parameter[]|undefined}
*/
function parseParameter(parameter) {
if (!isString(parameter)) {
return undefined;
}
const result = [];
parameter.split(";").forEach((entry) => {
entry = entry.trim();
if (entry === "") {
return;
}
const kv = entry.split("=");
const key = validateString(kv?.[0]).trim();
const value = validateString(kv?.[1]).trim();
// if values are quoted, they remain so internally
result.push({
key: key,
value: value,
});
});
return result;
}