Skip to content

Commit

Permalink
fix: fix some possible issues with file paths (#377)
Browse files Browse the repository at this point in the history
  • Loading branch information
RAX7 committed Nov 10, 2022
1 parent 5eba696 commit e40308a
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 25 deletions.
5 changes: 4 additions & 1 deletion src/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,12 @@ async function loader(content) {
}
}

const isAbsolute = isAbsoluteURL(this.resourcePath);
let isAbsolute = isAbsoluteURL(this.resourcePath);

const filename = isAbsolute
? this.resourcePath
: path.relative(this.rootContext, this.resourcePath);

const minifyOptions =
/** @type {import("./index").InternalWorkerOptions<T>} */ ({
input: content,
Expand Down Expand Up @@ -243,6 +245,7 @@ async function loader(content) {
query = query.length > 0 ? `?${query}` : "";
}

isAbsolute = isAbsoluteURL(output.filename);
// Old approach for `file-loader` and other old loaders
changeResource(this, isAbsolute, output, query);

Expand Down
61 changes: 48 additions & 13 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,34 @@ const path = require("path");
* @typedef {() => Promise<T>} Task
*/

/**
* @param {string} filename file path without query params (e.g. `path/img.png`)
* @param {string} ext new file extension without `.` (e.g. `webp`)
* @returns {string} new filename `path/img.png` -> `path/img.webp`
*/
function replaceFileExtension(filename, ext) {
let dotIndex = -1;

for (let i = filename.length - 1; i > -1; i--) {
const char = filename[i];

if (char === ".") {
dotIndex = i;
break;
}

if (char === "/" || char === "\\") {
break;
}
}

if (dotIndex === -1) {
return filename;
}

return `${filename.slice(0, dotIndex)}.${ext}`;
}

/**
* Run tasks with limited concurrency.
* @template T
Expand Down Expand Up @@ -72,17 +100,18 @@ function throttleAll(limit, tasks) {

const ABSOLUTE_URL_REGEX = /^[a-zA-Z][a-zA-Z\d+\-.]*?:/;
const WINDOWS_PATH_REGEX = /^[a-zA-Z]:\\/;
const POSIX_PATH_REGEX = /^\//;

/**
* @param {string} url
* @returns {boolean}
*/
function isAbsoluteURL(url) {
if (WINDOWS_PATH_REGEX.test(url)) {
return false;
}

return ABSOLUTE_URL_REGEX.test(url);
return (
WINDOWS_PATH_REGEX.test(url) ||
POSIX_PATH_REGEX.test(url) ||
ABSOLUTE_URL_REGEX.test(url)
);
}

/**
Expand Down Expand Up @@ -592,10 +621,7 @@ async function imageminGenerate(original, minimizerOptions) {
let newFilename = original.filename;

if (extOutput && extInput !== extOutput) {
newFilename = original.filename.replace(
new RegExp(`${extInput}$`),
`${extOutput}`
);
newFilename = replaceFileExtension(original.filename, extOutput);
}

return {
Expand Down Expand Up @@ -795,8 +821,7 @@ async function squooshGenerate(original, minifyOptions) {
const { binary, extension } = await Object.values(image.encodedWith)[0];
const { width, height } = (await image.decoded).bitmap;

const { dir: fileDir, name: fileName } = path.parse(original.filename);
const filename = path.join(fileDir, `${fileName}.${extension}`);
const filename = replaceFileExtension(original.filename, extension);

return {
filename,
Expand Down Expand Up @@ -1040,13 +1065,22 @@ async function sharpTransform(
// ====== rename ======

const outputExt = targetFormat ? outputFormat : inputExt;
const { dir: fileDir, name: fileName } = path.parse(original.filename);
const { width, height } = result.info;

const sizeSuffix =
typeof minimizerOptions.sizeSuffix === "function"
? minimizerOptions.sizeSuffix(width, height)
: "";
const filename = path.join(fileDir, `${fileName}${sizeSuffix}.${outputExt}`);

const dotIndex = original.filename.lastIndexOf(".");
const filename =
dotIndex > -1
? `${original.filename.slice(0, dotIndex)}${sizeSuffix}.${outputExt}`
: original.filename;

// TODO use this then remove `sizeSuffix`
// const filename = replaceFileExtension(original.filename, outputExt);

const processedFlag = targetFormat ? "generated" : "minimized";
const processedBy = targetFormat ? "generatedBy" : "minimizedBy";

Expand Down Expand Up @@ -1174,6 +1208,7 @@ async function svgoMinify(original, minimizerOptions) {
module.exports = {
throttleAll,
isAbsoluteURL,
replaceFileExtension,
imageminNormalizeConfig,
imageminMinify,
imageminGenerate,
Expand Down
6 changes: 3 additions & 3 deletions test/ImageminPlugin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,7 @@ describe("imagemin plugin", () => {
const stringStats = stats.toString({ relatedAssets: true });

expect(stringStats).toMatch(
/asset loader-test.webp.+\[from: loader-test.png\] \[generated\]/
/asset loader-test.webp.+\[from: .+loader-test.png\] \[generated\]/
);
});

Expand Down Expand Up @@ -1358,7 +1358,7 @@ describe("imagemin plugin", () => {
expect(warnings).toHaveLength(0);
expect(errors).toHaveLength(1);
expect(errors[0].message).toMatch(
/Multiple values for the 'encodeOptions' option is not supported for 'loader-test.png', specify only one codec for the generator/
/Multiple values for the 'encodeOptions' option is not supported for '.+loader-test.png', specify only one codec for the generator/
);
});

Expand Down Expand Up @@ -1728,7 +1728,7 @@ describe("imagemin plugin", () => {
expect(warnings).toHaveLength(0);
expect(errors).toHaveLength(1);
expect(errors[0].message).toMatch(
/Error with 'loader-test.txt': Input file has an unsupported format/g
/Error with '.+loader-test.txt': Input file has an unsupported format/g
);
});

Expand Down
72 changes: 72 additions & 0 deletions test/utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { isAbsoluteURL, replaceFileExtension } from "../src/utils.js";

describe("utils", () => {
it("should distinguish between relative and absolute file paths", () => {
expect(isAbsoluteURL("/home/user/img.jpg")).toBe(true);
expect(isAbsoluteURL("user/img.jpg")).toBe(false);
expect(isAbsoluteURL("./user/img.jpg")).toBe(false);
expect(isAbsoluteURL("../user/img.jpg")).toBe(false);

expect(isAbsoluteURL("C:\\user\\img.jpg")).toBe(true);
expect(isAbsoluteURL("CC:\\user\\img.jpg")).toBe(true);
expect(isAbsoluteURL("user\\img.jpg")).toBe(false);
expect(isAbsoluteURL(".\\user\\img.jpg")).toBe(false);
expect(isAbsoluteURL("..\\user\\img.jpg")).toBe(false);

expect(isAbsoluteURL("file:/user/img.jpg")).toBe(true);
expect(isAbsoluteURL("file-url:/user/img.jpg")).toBe(true);
expect(isAbsoluteURL("0file:/user/img.jpg")).toBe(false);
});

it("should replace file extension", () => {
expect(replaceFileExtension("img.jpg", "png")).toBe("img.png");
expect(replaceFileExtension(".img.jpg", "png")).toBe(".img.png");

expect(replaceFileExtension("/user/img.jpg", "png")).toBe("/user/img.png");
expect(replaceFileExtension("file:///user/img.jpg", "png")).toBe(
"file:///user/img.png"
);
expect(replaceFileExtension("C:\\user\\img.jpg", "png")).toBe(
"C:\\user\\img.png"
);

expect(replaceFileExtension("user/img.jpg", "png")).toBe("user/img.png");
expect(replaceFileExtension("user\\img.jpg", "png")).toBe("user\\img.png");

expect(replaceFileExtension("/user/img.jpg.gz", "png")).toBe(
"/user/img.jpg.png"
);
expect(replaceFileExtension("file:///user/img.jpg.gz", "png")).toBe(
"file:///user/img.jpg.png"
);
expect(replaceFileExtension("C:\\user\\img.jpg.gz", "png")).toBe(
"C:\\user\\img.jpg.png"
);

expect(replaceFileExtension("/user/img", "png")).toBe("/user/img");
expect(replaceFileExtension("file:///user/img", "png")).toBe(
"file:///user/img"
);
expect(replaceFileExtension("C:\\user\\img", "png")).toBe("C:\\user\\img");

expect(replaceFileExtension("/user/.img", "png")).toBe("/user/.png");
expect(replaceFileExtension("file:///user/.img", "png")).toBe(
"file:///user/.png"
);
expect(replaceFileExtension("C:\\user\\.img", "png")).toBe(
"C:\\user\\.png"
);

expect(replaceFileExtension("/use.r/img", "png")).toBe("/use.r/img");
expect(replaceFileExtension("file:///use.r/img", "png")).toBe(
"file:///use.r/img"
);
expect(replaceFileExtension("C:\\use.r\\img", "png")).toBe(
"C:\\use.r\\img"
);

expect(replaceFileExtension("C:\\user/img.jpg", "png")).toBe(
"C:\\user/img.png"
);
});
});
22 changes: 14 additions & 8 deletions types/utils.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,6 @@ export type SharpOptions = {
encodeOptions?: SharpEncodeOptions | undefined;
};
export type SizeSuffix = (width: number, height: number) => string;
/** @typedef {import("./index").WorkerResult} WorkerResult */
/** @typedef {import("./index").SquooshOptions} SquooshOptions */
/** @typedef {import("imagemin").Options} ImageminOptions */
/** @typedef {import("webpack").WebpackError} WebpackError */
/**
* @template T
* @typedef {() => Promise<T>} Task
*/
/**
* Run tasks with limited concurrency.
* @template T
Expand All @@ -61,6 +53,20 @@ export function throttleAll<T>(limit: number, tasks: Task<T>[]): Promise<T[]>;
* @returns {boolean}
*/
export function isAbsoluteURL(url: string): boolean;
/** @typedef {import("./index").WorkerResult} WorkerResult */
/** @typedef {import("./index").SquooshOptions} SquooshOptions */
/** @typedef {import("imagemin").Options} ImageminOptions */
/** @typedef {import("webpack").WebpackError} WebpackError */
/**
* @template T
* @typedef {() => Promise<T>} Task
*/
/**
* @param {string} filename file path without query params (e.g. `path/img.png`)
* @param {string} ext new file extension without `.` (e.g. `webp`)
* @returns {string} new filename `path/img.png` -> `path/img.webp`
*/
export function replaceFileExtension(filename: string, ext: string): string;
/**
* @template T
* @param {ImageminOptions} imageminConfig
Expand Down

0 comments on commit e40308a

Please sign in to comment.