"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.c0ControlCodesExclude = c0ControlCodesExclude; exports.defaultMinimizerOptions = void 0; exports.getExportCode = getExportCode; exports.getFilter = getFilter; exports.getImportCode = getImportCode; exports.getModuleCode = getModuleCode; exports.isUrlRequestable = isUrlRequestable; exports.normalizeOptions = normalizeOptions; exports.parseSrc = parseSrc; exports.parseSrcset = parseSrcset; exports.pluginRunner = pluginRunner; exports.requestify = requestify; exports.srcType = srcType; exports.srcsetType = srcsetType; exports.traverse = traverse; exports.webpackIgnoreCommentRegexp = void 0; var _path = _interopRequireDefault(require("path")); var _HtmlSourceError = _interopRequireDefault(require("./HtmlSourceError")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function isASCIIWhitespace(character) { return (// Horizontal tab character === "\u0009" || // New line character === "\u000A" || // Form feed character === "\u000C" || // Carriage return character === "\u000D" || // Space character === "\u0020" ); } // (Don't use \s, to avoid matching non-breaking space) // eslint-disable-next-line no-control-regex const regexLeadingSpaces = /^[ \t\n\r\u000c]+/; // eslint-disable-next-line no-control-regex const regexLeadingCommasOrSpaces = /^[, \t\n\r\u000c]+/; // eslint-disable-next-line no-control-regex const regexLeadingNotSpaces = /^[^ \t\n\r\u000c]+/; const regexTrailingCommas = /[,]+$/; const regexNonNegativeInteger = /^\d+$/; // ( Positive or negative or unsigned integers or decimals, without or without exponents. // Must include at least one digit. // According to spec tests any decimal point must be followed by a digit. // No leading plus sign is allowed.) // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-floating-point-number const regexFloatingPoint = /^-?(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/; function parseSrcset(input) { // 1. Let input be the value passed to this algorithm. const inputLength = input.length; let url; let descriptors; let currentDescriptor; let state; let c; // 2. Let position be a pointer into input, initially pointing at the start // of the string. let position = 0; let startOffset; // eslint-disable-next-line consistent-return function collectCharacters(regEx) { let chars; const match = regEx.exec(input.substring(position)); if (match) { [chars] = match; position += chars.length; return chars; } } // 3. Let candidates be an initially empty source set. const candidates = []; // 4. Splitting loop: Collect a sequence of characters that are space // characters or U+002C COMMA characters. If any U+002C COMMA characters // were collected, that is a parse error. // eslint-disable-next-line no-constant-condition while (true) { collectCharacters(regexLeadingCommasOrSpaces); // 5. If position is past the end of input, return candidates and abort these steps. if (position >= inputLength) { if (candidates.length === 0) { throw new Error("Must contain one or more image candidate strings"); } // (we're done, this is the sole return path) return candidates; } // 6. Collect a sequence of characters that are not space characters, // and let that be url. startOffset = position; url = collectCharacters(regexLeadingNotSpaces); // 7. Let descriptors be a new empty list. descriptors = []; // 8. If url ends with a U+002C COMMA character (,), follow these substeps: // (1). Remove all trailing U+002C COMMA characters from url. If this removed // more than one character, that is a parse error. if (url.slice(-1) === ",") { url = url.replace(regexTrailingCommas, ""); // (Jump ahead to step 9 to skip tokenization and just push the candidate). parseDescriptors(); } // Otherwise, follow these substeps: else { tokenize(); } // 16. Return to the step labeled splitting loop. } /** * Tokenizes descriptor properties prior to parsing * Returns undefined. */ function tokenize() { // 8.1. Descriptor tokenizer: Skip whitespace collectCharacters(regexLeadingSpaces); // 8.2. Let current descriptor be the empty string. currentDescriptor = ""; // 8.3. Let state be in descriptor. state = "in descriptor"; // eslint-disable-next-line no-constant-condition while (true) { // 8.4. Let c be the character at position. c = input.charAt(position); // Do the following depending on the value of state. // For the purpose of this step, "EOF" is a special character representing // that position is past the end of input. // In descriptor if (state === "in descriptor") { // Do the following, depending on the value of c: // Space character // If current descriptor is not empty, append current descriptor to // descriptors and let current descriptor be the empty string. // Set state to after descriptor. if (isASCIIWhitespace(c)) { if (currentDescriptor) { descriptors.push(currentDescriptor); currentDescriptor = ""; state = "after descriptor"; } } // U+002C COMMA (,) // Advance position to the next character in input. If current descriptor // is not empty, append current descriptor to descriptors. Jump to the step // labeled descriptor parser. else if (c === ",") { position += 1; if (currentDescriptor) { descriptors.push(currentDescriptor); } parseDescriptors(); return; } // U+0028 LEFT PARENTHESIS (() // Append c to current descriptor. Set state to in parens. else if (c === "\u0028") { currentDescriptor += c; state = "in parens"; } // EOF // If current descriptor is not empty, append current descriptor to // descriptors. Jump to the step labeled descriptor parser. else if (c === "") { if (currentDescriptor) { descriptors.push(currentDescriptor); } parseDescriptors(); return; // Anything else // Append c to current descriptor. } else { currentDescriptor += c; } } // In parens else if (state === "in parens") { // U+0029 RIGHT PARENTHESIS ()) // Append c to current descriptor. Set state to in descriptor. if (c === ")") { currentDescriptor += c; state = "in descriptor"; } // EOF // Append current descriptor to descriptors. Jump to the step labeled // descriptor parser. else if (c === "") { descriptors.push(currentDescriptor); parseDescriptors(); return; } // Anything else // Append c to current descriptor. else { currentDescriptor += c; } } // After descriptor else if (state === "after descriptor") { // Do the following, depending on the value of c: if (isASCIIWhitespace(c)) {// Space character: Stay in this state. } // EOF: Jump to the step labeled descriptor parser. else if (c === "") { parseDescriptors(); return; } // Anything else // Set state to in descriptor. Set position to the previous character in input. else { state = "in descriptor"; position -= 1; } } // Advance position to the next character in input. position += 1; } } /** * Adds descriptor properties to a candidate, pushes to the candidates array * @return undefined */ // Declared outside of the while loop so that it's only created once. function parseDescriptors() { // 9. Descriptor parser: Let error be no. let pError = false; // 10. Let width be absent. // 11. Let density be absent. // 12. Let future-compat-h be absent. (We're implementing it now as h) let w; let d; let h; let i; const candidate = {}; let desc; let lastChar; let value; let intVal; let floatVal; // 13. For each descriptor in descriptors, run the appropriate set of steps // from the following list: for (i = 0; i < descriptors.length; i++) { desc = descriptors[i]; lastChar = desc[desc.length - 1]; value = desc.substring(0, desc.length - 1); intVal = parseInt(value, 10); floatVal = parseFloat(value); // If the descriptor consists of a valid non-negative integer followed by // a U+0077 LATIN SMALL LETTER W character if (regexNonNegativeInteger.test(value) && lastChar === "w") { // If width and density are not both absent, then let error be yes. if (w || d) { pError = true; } // Apply the rules for parsing non-negative integers to the descriptor. // If the result is zero, let error be yes. // Otherwise, let width be the result. if (intVal === 0) { pError = true; } else { w = intVal; } } // If the descriptor consists of a valid floating-point number followed by // a U+0078 LATIN SMALL LETTER X character else if (regexFloatingPoint.test(value) && lastChar === "x") { // If width, density and future-compat-h are not all absent, then let error // be yes. if (w || d || h) { pError = true; } // Apply the rules for parsing floating-point number values to the descriptor. // If the result is less than zero, let error be yes. Otherwise, let density // be the result. if (floatVal < 0) { pError = true; } else { d = floatVal; } } // If the descriptor consists of a valid non-negative integer followed by // a U+0068 LATIN SMALL LETTER H character else if (regexNonNegativeInteger.test(value) && lastChar === "h") { // If height and density are not both absent, then let error be yes. if (h || d) { pError = true; } // Apply the rules for parsing non-negative integers to the descriptor. // If the result is zero, let error be yes. Otherwise, let future-compat-h // be the result. if (intVal === 0) { pError = true; } else { h = intVal; } // Anything else, Let error be yes. } else { pError = true; } } // 15. If error is still no, then append a new image source to candidates whose // URL is url, associated with a width width if not absent and a pixel // density density if not absent. Otherwise, there is a parse error. if (!pError) { candidate.source = { value: url, startOffset }; if (w) { candidate.width = { value: w }; } if (d) { candidate.density = { value: d }; } if (h) { candidate.height = { value: h }; } candidates.push(candidate); } else { throw new Error(`Invalid srcset descriptor found in '${input}' at '${desc}'`); } } } function parseSrc(input) { if (!input) { throw new Error("Must be non-empty"); } let startOffset = 0; let value = input; while (isASCIIWhitespace(value.substring(0, 1))) { startOffset += 1; value = value.substring(1, value.length); } while (isASCIIWhitespace(value.substring(value.length - 1, value.length))) { value = value.substring(0, value.length - 1); } if (!value) { throw new Error("Must be non-empty"); } return { value, startOffset }; } const WINDOWS_ABS_PATH_REGEXP = /^[a-zA-Z]:[\\/]|^\\\\/; function isUrlRequestable(url) { // Protocol-relative URLs if (/^\/\//.test(url)) { return false; } // `file:` protocol if (/^file:/i.test(url)) { return true; } // Absolute URLs if (/^[a-z][a-z0-9+.-]*:/i.test(url) && !WINDOWS_ABS_PATH_REGEXP.test(url)) { return false; } // It's some kind of url for a template if (/^[{}[\]#*;,'§$%&(=?`´^°<>]/.test(url)) { return false; } return true; } const WINDOWS_PATH_SEPARATOR_REGEXP = /\\/g; const RELATIVE_PATH_REGEXP = /^\.\.?[/\\]/; const absoluteToRequest = (context, maybeAbsolutePath) => { if (maybeAbsolutePath[0] === "/") { if (maybeAbsolutePath.length > 1 && maybeAbsolutePath[maybeAbsolutePath.length - 1] === "/") { // this 'path' is actually a regexp generated by dynamic requires. // Don't treat it as an absolute path. return maybeAbsolutePath; } const querySplitPos = maybeAbsolutePath.indexOf("?"); let resource = querySplitPos === -1 ? maybeAbsolutePath : maybeAbsolutePath.slice(0, querySplitPos); resource = _path.default.posix.relative(context, resource); if (!resource.startsWith("../")) { resource = `./${resource}`; } return querySplitPos === -1 ? resource : resource + maybeAbsolutePath.slice(querySplitPos); } if (WINDOWS_ABS_PATH_REGEXP.test(maybeAbsolutePath)) { const querySplitPos = maybeAbsolutePath.indexOf("?"); let resource = querySplitPos === -1 ? maybeAbsolutePath : maybeAbsolutePath.slice(0, querySplitPos); resource = _path.default.win32.relative(context, resource); if (!WINDOWS_ABS_PATH_REGEXP.test(resource)) { resource = resource.replace(WINDOWS_PATH_SEPARATOR_REGEXP, "/"); if (!resource.startsWith("../")) { resource = `./${resource}`; } } return querySplitPos === -1 ? resource : resource + maybeAbsolutePath.slice(querySplitPos); } if (!RELATIVE_PATH_REGEXP.test(maybeAbsolutePath)) { return `./${maybeAbsolutePath.replace(WINDOWS_PATH_SEPARATOR_REGEXP, "/")}`; } // not an absolute path return maybeAbsolutePath; }; const contextify = (context, request) => request.split("!").map(r => absoluteToRequest(context, r)).join("!"); const MODULE_REQUEST_REGEXP = /^[^?]*~/; function requestify(context, request) { const isWindowsAbsolutePath = WINDOWS_ABS_PATH_REGEXP.test(request); const newRequest = isWindowsAbsolutePath ? decodeURI(request).replace(/[\t\n\r]/g, "") : decodeURI(request).replace(/[\t\n\r]/g, "").replace(/\\/g, "/"); if (isWindowsAbsolutePath || newRequest[0] === "/") { return newRequest; } if (/^file:/i.test(newRequest)) { return newRequest; } // A `~` makes the url an module if (MODULE_REQUEST_REGEXP.test(newRequest)) { return newRequest.replace(MODULE_REQUEST_REGEXP, ""); } // every other url is threaded like a relative url return contextify(context, newRequest); } function isProductionMode(loaderContext) { return loaderContext.mode === "production" || !loaderContext.mode; } const defaultMinimizerOptions = { caseSensitive: true, // `collapseBooleanAttributes` is not always safe, since this can break CSS attribute selectors and not safe for XHTML collapseWhitespace: true, conservativeCollapse: true, keepClosingSlash: true, // We need ability to use cssnano, or setup own function without extra dependencies minifyCSS: true, minifyJS: true, // `minifyURLs` is unsafe, because we can't guarantee what the base URL is // `removeAttributeQuotes` is not safe in some rare cases, also HTML spec recommends against doing this removeComments: true, // `removeEmptyAttributes` is not safe, can affect certain style or script behavior, look at https://github.com/webpack-contrib/html-loader/issues/323 // `removeRedundantAttributes` is not safe, can affect certain style or script behavior, look at https://github.com/webpack-contrib/html-loader/issues/323 removeScriptTypeAttributes: true, removeStyleLinkTypeAttributes: true // `useShortDoctype` is not safe for XHTML }; exports.defaultMinimizerOptions = defaultMinimizerOptions; function getMinimizeOption(rawOptions, loaderContext) { if (typeof rawOptions.minimize === "undefined") { return isProductionMode(loaderContext) ? defaultMinimizerOptions : false; } if (typeof rawOptions.minimize === "boolean") { return rawOptions.minimize === true ? defaultMinimizerOptions : false; } return rawOptions.minimize; } function getAttributeValue(attributes, name) { const [result] = attributes.filter(i => i.name.toLowerCase() === name); return typeof result === "undefined" ? result : result.value; } function scriptSrcFilter(tag, attribute, attributes) { let type = getAttributeValue(attributes, "type"); if (!type) { return true; } type = type.trim(); if (!type) { return false; } if (type !== "module" && type !== "text/javascript" && type !== "application/javascript") { return false; } return true; } function linkHrefFilter(tag, attribute, attributes) { let rel = getAttributeValue(attributes, "rel"); if (!rel) { return false; } rel = rel.trim(); if (!rel) { return false; } rel = rel.toLowerCase(); const usedRels = rel.split(" ").filter(value => value); const allowedRels = ["stylesheet", "icon", "mask-icon", "apple-touch-icon", "apple-touch-icon-precomposed", "apple-touch-startup-image", "manifest", "prefetch", "preload"]; return allowedRels.filter(value => usedRels.includes(value)).length > 0; } const META = new Map([["name", new Set([// msapplication-TileImage "msapplication-tileimage", "msapplication-square70x70logo", "msapplication-square150x150logo", "msapplication-wide310x150logo", "msapplication-square310x310logo", "msapplication-config", "msapplication-task", "twitter:image"])], ["property", new Set(["og:image", "og:image:url", "og:image:secure_url", "og:audio", "og:audio:secure_url", "og:video", "og:video:secure_url", "vk:image"])], ["itemprop", new Set(["image", "logo", "screenshot", "thumbnailurl", "contenturl", "downloadurl", "duringmedia", "embedurl", "installurl", "layoutimage"])]]); function linkItempropFilter(tag, attribute, attributes) { let name = getAttributeValue(attributes, "itemprop"); if (name) { name = name.trim(); if (!name) { return false; } name = name.toLowerCase(); return META.get("itemprop").has(name); } return false; } function linkUnionFilter(tag, attribute, attributes) { return linkHrefFilter(tag, attribute, attributes) || linkItempropFilter(tag, attribute, attributes); } function metaContentFilter(tag, attribute, attributes) { for (const item of META) { const [key, allowedNames] = item; let name = getAttributeValue(attributes, key); if (name) { name = name.trim(); if (!name) { // eslint-disable-next-line no-continue continue; } name = name.toLowerCase(); return allowedNames.has(name); } } return false; } function srcType(options) { let source; try { source = parseSrc(options.value); } catch (error) { throw new _HtmlSourceError.default(`Bad value for attribute "${options.attribute}" on element "${options.tag}": ${error.message}`, options.attributeStartOffset, options.attributeEndOffset, options.html); } source = c0ControlCodesExclude(source); if (!isUrlRequestable(source.value)) { return []; } const startOffset = options.valueStartOffset + source.startOffset; const endOffset = startOffset + source.value.length; return [{ value: source.value, startOffset, endOffset }]; } function srcsetType(options) { let sourceSet; try { sourceSet = parseSrcset(options.value); } catch (error) { throw new _HtmlSourceError.default(`Bad value for attribute "${options.attribute}" on element "${options.tag}": ${error.message}`, options.attributeStartOffset, options.attributeEndOffset, options.html); } const result = []; sourceSet.forEach(sourceItem => { let { source } = sourceItem; source = c0ControlCodesExclude(source); if (!isUrlRequestable(source.value)) { return false; } const startOffset = options.valueStartOffset + source.startOffset; const endOffset = startOffset + source.value.length; result.push({ value: source.value, startOffset, endOffset }); return false; }); return result; } function metaContentType(options) { const isMsapplicationTask = options.attributes.find(i => i.name.toLowerCase() === "name" && i.value.toLowerCase() === "msapplication-task"); if (isMsapplicationTask) { let startOffset = options.valueStartOffset; let endOffset = options.valueStartOffset; let value; const parts = options.value.split(";"); for (const [index, part] of parts.entries()) { const isLastIteration = index === parts.length - 1; if (/^icon-uri/i.test(part.trim())) { const [name, src] = part.split("="); startOffset += name.length + 1; let source; try { source = parseSrc(src); } catch (error) { throw new _HtmlSourceError.default(`Bad value for attribute "icon-uri" on element "${options.tag}": ${error.message}`, options.attributeStartOffset, options.attributeEndOffset, options.html); } source = c0ControlCodesExclude(source); ({ value } = source); startOffset += source.startOffset; endOffset = startOffset + value.length; break; } // +1 because of ";" startOffset += part.length + (isLastIteration ? 0 : 1); } if (!value) { return []; } return [{ startOffset, endOffset, value }]; } return srcType(options); } // function webpackImportType(options) { // let source; // // try { // source = parseSrc(options.value); // } catch (error) { // throw new HtmlSourceError( // `Bad value for attribute "${options.attribute}" on element "${options.tag}": ${error.message}`, // options.attributeStartOffset, // options.attributeEndOffset, // options.html // ); // } // // source = c0ControlCodesExclude(source); // // if (!isUrlRequestable(source.value)) { // return []; // } // // const { startOffset } = options.startTag; // let { endOffset } = options.startTag; // // if (options.endTag) { // ({ endOffset } = options.endTag); // } // // return [ // { // format: 'import', // runtime: false, // value: source.value, // startOffset, // endOffset, // }, // ]; // } const defaultSources = new Map([["audio", new Map([["src", { type: srcType }]])], ["embed", new Map([["src", { type: srcType }]])], ["img", new Map([["src", { type: srcType }], ["srcset", { type: srcsetType }]])], ["input", new Map([["src", { type: srcType }]])], ["link", new Map([["href", { type: srcType, filter: linkUnionFilter }], ["imagesrcset", { type: srcsetType, filter: linkHrefFilter }]])], ["meta", new Map([["content", { type: metaContentType, filter: metaContentFilter }]])], ["object", new Map([["data", { type: srcType }]])], ["script", new Map([["src", { type: srcType, filter: scriptSrcFilter }], // Using href with " to "<" + "script>" or "<" + "/script>". code = code.replace(/<(\/?script)/g, (_, s) => `<" + "${s}`); return `// Module\n${replacersCode}var code = ${code};\n`; } function getExportCode(html, options) { if (options.esModule) { return `// Exports\nexport default code;`; } return `// Exports\nmodule.exports = code;`; } function isASCIIC0group(character) { // C0 and   // eslint-disable-next-line no-control-regex return /^[\u0001-\u0019\u00a0]/.test(character); } function c0ControlCodesExclude(source) { let { value, startOffset } = source; if (!value) { throw new Error("Must be non-empty"); } while (isASCIIC0group(value.substring(0, 1))) { startOffset += 1; value = value.substring(1, value.length); } while (isASCIIC0group(value.substring(value.length - 1, value.length))) { value = value.substring(0, value.length - 1); } if (!value) { throw new Error("Must be non-empty"); } return { value, startOffset }; } function traverse(root, callback) { const visit = (node, parent) => { let res; if (callback) { res = callback(node, parent); } let { childNodes } = node; // in case a