Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: avoid endless loop when handling symlinks (#109) by @q0rban #109

Merged
merged 7 commits into from
Oct 25, 2024
Merged
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
4 changes: 3 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
lib-es5/
lib-es5/
# Symlink needed for test
test/test-99-#108/lib/log.js
61 changes: 42 additions & 19 deletions lib/walker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,14 +317,37 @@ function stepDetect(
}
}

function findCommonJunctionPoint(file: string, realFile: string) {
/**
* Find a common junction point between a symlink and the real file path.
*
* @param {string} file The file path, including symlink(s).
* @param {string} realFile The real path to the file.
*
* @throws {Error} If no common junction point is found prior to hitting the
* filesystem root.
*/
async function findCommonJunctionPoint(file: string, realFile: string) {
q0rban marked this conversation as resolved.
Show resolved Hide resolved
// find common denominator => where the link changes
while (toNormalizedRealPath(path.dirname(file)) === path.dirname(realFile)) {
while (true) {
const stats = await fs.lstat(file);

if (stats.isSymbolicLink()) {
return { file, realFile };
}

file = path.dirname(file);
realFile = path.dirname(realFile);
}

return { file, realFile };
// If the directory is /, break out of the loop and log an error.
if (
file === path.parse(file).root ||
realFile === path.parse(realFile).root
) {
throw new Error(
'Reached root directory without finding a common junction point',
);
robertsLando marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

export interface WalkerParams {
Expand Down Expand Up @@ -399,8 +422,8 @@ class Walker {
}
}

appendSymlink(file: string, realFile: string) {
const a = findCommonJunctionPoint(file, realFile);
async appendSymlink(file: string, realFile: string) {
const a = await findCommonJunctionPoint(file, realFile);
file = a.file;
realFile = a.realFile;

Expand Down Expand Up @@ -456,7 +479,7 @@ class Walker {
});
}

appendBlobOrContent(task: Task) {
async appendBlobOrContent(task: Task) {
if (strictVerify) {
assert(task.file === normalizePath(task.file));
}
Expand Down Expand Up @@ -486,7 +509,7 @@ class Walker {
}

this.append({ ...task, file: realFile });
this.appendSymlink(task.file, realFile);
await this.appendSymlink(task.file, realFile);
this.appendStat({
file: task.file,
store: STORE_STAT,
Expand Down Expand Up @@ -514,7 +537,7 @@ class Walker {
]);
}

this.appendBlobOrContent({
await this.appendBlobOrContent({
file: normalizePath(script),
marker,
store: STORE_BLOB,
Expand All @@ -534,7 +557,7 @@ class Walker {
const stat = await fs.stat(asset);

if (stat.isFile()) {
this.appendBlobOrContent({
await this.appendBlobOrContent({
file: normalizePath(asset),
marker,
store: STORE_CONTENT,
Expand All @@ -558,14 +581,14 @@ class Walker {
// 2) non-source (non-js) files of top-level package are shipped as CONTENT
// 3) parsing some js 'files' of non-top-level packages fails, hence all CONTENT
if (marker.toplevel) {
this.appendBlobOrContent({
await this.appendBlobOrContent({
file,
marker,
store: isDotJS(file) ? STORE_BLOB : STORE_CONTENT,
reason: configPath,
});
} else {
this.appendBlobOrContent({
await this.appendBlobOrContent({
file,
marker,
store: STORE_CONTENT,
Expand Down Expand Up @@ -754,7 +777,7 @@ class Walker {
}

if (stat && stat.isFile()) {
this.appendBlobOrContent({
await this.appendBlobOrContent({
file,
marker,
store: STORE_CONTENT,
Expand Down Expand Up @@ -857,15 +880,15 @@ class Walker {
normalizePath(newPackageForNewRecords.packageJson),
);
}
this.appendBlobOrContent({
await this.appendBlobOrContent({
file: newPackageForNewRecords.packageJson,
marker: newPackageForNewRecords.marker,
store: STORE_CONTENT,
reason: record.file,
});
}

this.appendBlobOrContent({
await this.appendBlobOrContent({
file: newFile,
marker: newPackageForNewRecords ? newPackageForNewRecords.marker : marker,
store: STORE_BLOB,
Expand Down Expand Up @@ -921,7 +944,7 @@ class Walker {

if (store === STORE_BLOB) {
if (unlikelyJavascript(record.file) || isDotNODE(record.file)) {
this.appendBlobOrContent({
await this.appendBlobOrContent({
file: record.file,
marker,
store: STORE_CONTENT,
Expand All @@ -930,7 +953,7 @@ class Walker {
}

if (marker.public || marker.hasDictionary) {
this.appendBlobOrContent({
await this.appendBlobOrContent({
file: record.file,
marker,
store: STORE_CONTENT,
Expand Down Expand Up @@ -1090,15 +1113,15 @@ class Walker {

entrypoint = normalizePath(entrypoint);

this.appendBlobOrContent({
await this.appendBlobOrContent({
file: entrypoint,
marker,
store: STORE_BLOB,
});

if (addition) {
addition = normalizePath(addition);
this.appendBlobOrContent({
await this.appendBlobOrContent({
file: addition,
marker,
store: STORE_CONTENT,
Expand Down
1 change: 1 addition & 0 deletions test/test-99-#108/lib/log.js
3 changes: 3 additions & 0 deletions test/test-99-#108/lib/realLog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';

module.exports = console.log;
26 changes: 26 additions & 0 deletions test/test-99-#108/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env node

'use strict';

const path = require('path');
const assert = require('assert');
const utils = require('../utils.js');

assert(require.main.filename === __filename);
assert(__dirname === process.cwd());

// test symlinks on unix only // TODO junction
if (process.platform === 'win32') return;

const target = process.argv[2] || 'host';
const input = './test-x-index.js';
const output = './test-output.exe';

utils.pkg.sync(['--target', target, '--output', output, input]);

const result = utils.spawn.sync('./' + path.basename(output), [], {
cwd: path.dirname(output),
});

assert.strictEqual(result, '42\n');
utils.vacuum.sync(output);
5 changes: 5 additions & 0 deletions test/test-99-#108/test-x-index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

const log = require('./lib/log');

log(42);