Skip to content

Commit efbba60

Browse files
authoredSep 14, 2024··
path: fix bugs and inconsistencies
Fixes: #54025 PR-URL: #54224 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
1 parent c3e1c31 commit efbba60

File tree

9 files changed

+185
-70
lines changed

9 files changed

+185
-70
lines changed
 

Diff for: ‎lib/internal/modules/cjs/loader.js

+13-2
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,7 @@ Module._findPath = function(request, paths, isMain) {
702702

703703
let exts;
704704
const trailingSlash = request.length > 0 &&
705-
(StringPrototypeCharCodeAt(request, request.length - 1) === CHAR_FORWARD_SLASH || (
705+
((StringPrototypeCharCodeAt(request, request.length - 1) === CHAR_FORWARD_SLASH || (
706706
StringPrototypeCharCodeAt(request, request.length - 1) === CHAR_DOT &&
707707
(
708708
request.length === 1 ||
@@ -712,7 +712,18 @@ Module._findPath = function(request, paths, isMain) {
712712
StringPrototypeCharCodeAt(request, request.length - 3) === CHAR_FORWARD_SLASH
713713
))
714714
)
715-
));
715+
)) || (isWindows && (
716+
StringPrototypeCharCodeAt(request, request.length - 1) === CHAR_BACKWARD_SLASH || (
717+
StringPrototypeCharCodeAt(request, request.length - 1) === CHAR_DOT &&
718+
(
719+
request.length === 1 ||
720+
StringPrototypeCharCodeAt(request, request.length - 2) === CHAR_BACKWARD_SLASH ||
721+
(StringPrototypeCharCodeAt(request, request.length - 2) === CHAR_DOT && (
722+
request.length === 2 ||
723+
StringPrototypeCharCodeAt(request, request.length - 3) === CHAR_BACKWARD_SLASH
724+
))
725+
)
726+
))));
716727

717728
const isRelative = StringPrototypeCharCodeAt(request, 0) === CHAR_DOT &&
718729
(

Diff for: ‎lib/internal/url.js

-9
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,7 @@ const {
7474
} = require('internal/errors');
7575
const {
7676
CHAR_AMPERSAND,
77-
CHAR_BACKWARD_SLASH,
7877
CHAR_EQUAL,
79-
CHAR_FORWARD_SLASH,
8078
CHAR_LOWERCASE_A,
8179
CHAR_LOWERCASE_Z,
8280
CHAR_PERCENT,
@@ -1569,13 +1567,6 @@ function pathToFileURL(filepath, options = kEmptyObject) {
15691567
return outURL;
15701568
}
15711569
let resolved = (windows ?? isWindows) ? path.win32.resolve(filepath) : path.posix.resolve(filepath);
1572-
// path.resolve strips trailing slashes so we must add them back
1573-
const filePathLast = StringPrototypeCharCodeAt(filepath,
1574-
filepath.length - 1);
1575-
if ((filePathLast === CHAR_FORWARD_SLASH ||
1576-
((windows ?? isWindows) && filePathLast === CHAR_BACKWARD_SLASH)) &&
1577-
resolved[resolved.length - 1] !== path.sep)
1578-
resolved += '/';
15791570

15801571
// Call encodePathChars first to avoid encoding % again for ? and #.
15811572
resolved = encodePathChars(resolved, { windows });

Diff for: ‎lib/path.js

+98-26
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ const win32 = {
190190
let resolvedDevice = '';
191191
let resolvedTail = '';
192192
let resolvedAbsolute = false;
193+
let slashCheck = false;
193194

194195
for (let i = args.length - 1; i >= -1; i--) {
195196
let path;
@@ -221,6 +222,10 @@ const win32 = {
221222
}
222223
}
223224

225+
if (i === args.length - 1 &&
226+
isPathSeparator(StringPrototypeCharCodeAt(path, path.length - 1))) {
227+
slashCheck = true;
228+
}
224229
const len = path.length;
225230
let rootEnd = 0;
226231
let device = '';
@@ -268,10 +273,16 @@ const win32 = {
268273
j++;
269274
}
270275
if (j === len || j !== last) {
271-
// We matched a UNC root
272-
device =
273-
`\\\\${firstPart}\\${StringPrototypeSlice(path, last, j)}`;
274-
rootEnd = j;
276+
if (firstPart !== '.' && firstPart !== '?') {
277+
// We matched a UNC root
278+
device =
279+
`\\\\${firstPart}\\${StringPrototypeSlice(path, last, j)}`;
280+
rootEnd = j;
281+
} else {
282+
// We matched a device root (e.g. \\\\.\\PHYSICALDRIVE0)
283+
device = `\\\\${firstPart}`;
284+
rootEnd = 4;
285+
}
275286
}
276287
}
277288
}
@@ -323,9 +334,21 @@ const win32 = {
323334
resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\',
324335
isPathSeparator);
325336

326-
return resolvedAbsolute ?
327-
`${resolvedDevice}\\${resolvedTail}` :
328-
`${resolvedDevice}${resolvedTail}` || '.';
337+
if (!resolvedAbsolute) {
338+
return `${resolvedDevice}${resolvedTail}` || '.';
339+
}
340+
341+
if (resolvedTail.length === 0) {
342+
return slashCheck ? `${resolvedDevice}\\` : resolvedDevice;
343+
}
344+
345+
if (slashCheck) {
346+
return resolvedTail === '\\' ?
347+
`${resolvedDevice}\\` :
348+
`${resolvedDevice}\\${resolvedTail}\\`;
349+
}
350+
351+
return `${resolvedDevice}\\${resolvedTail}`;
329352
},
330353

331354
/**
@@ -381,17 +404,22 @@ const win32 = {
381404
!isPathSeparator(StringPrototypeCharCodeAt(path, j))) {
382405
j++;
383406
}
384-
if (j === len) {
385-
// We matched a UNC root only
386-
// Return the normalized version of the UNC root since there
387-
// is nothing left to process
388-
return `\\\\${firstPart}\\${StringPrototypeSlice(path, last)}\\`;
389-
}
390-
if (j !== last) {
391-
// We matched a UNC root with leftovers
392-
device =
393-
`\\\\${firstPart}\\${StringPrototypeSlice(path, last, j)}`;
394-
rootEnd = j;
407+
if (j === len || j !== last) {
408+
if (firstPart === '.' || firstPart === '?') {
409+
// We matched a device root (e.g. \\\\.\\PHYSICALDRIVE0)
410+
device = `\\\\${firstPart}`;
411+
rootEnd = 4;
412+
} else if (j === len) {
413+
// We matched a UNC root only
414+
// Return the normalized version of the UNC root since there
415+
// is nothing left to process
416+
return `\\\\${firstPart}\\${StringPrototypeSlice(path, last)}\\`;
417+
} else {
418+
// We matched a UNC root with leftovers
419+
device =
420+
`\\\\${firstPart}\\${StringPrototypeSlice(path, last, j)}`;
421+
rootEnd = j;
422+
}
395423
}
396424
}
397425
}
@@ -1162,6 +1190,7 @@ const posix = {
11621190
resolve(...args) {
11631191
let resolvedPath = '';
11641192
let resolvedAbsolute = false;
1193+
let slashCheck = false;
11651194

11661195
for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) {
11671196
const path = i >= 0 ? args[i] : posixCwd();
@@ -1171,8 +1200,17 @@ const posix = {
11711200
if (path.length === 0) {
11721201
continue;
11731202
}
1203+
if (i === args.length - 1 &&
1204+
isPosixPathSeparator(StringPrototypeCharCodeAt(path,
1205+
path.length - 1))) {
1206+
slashCheck = true;
1207+
}
11741208

1175-
resolvedPath = `${path}/${resolvedPath}`;
1209+
if (resolvedPath.length !== 0) {
1210+
resolvedPath = `${path}/${resolvedPath}`;
1211+
} else {
1212+
resolvedPath = path;
1213+
}
11761214
resolvedAbsolute =
11771215
StringPrototypeCharCodeAt(path, 0) === CHAR_FORWARD_SLASH;
11781216
}
@@ -1184,10 +1222,20 @@ const posix = {
11841222
resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/',
11851223
isPosixPathSeparator);
11861224

1187-
if (resolvedAbsolute) {
1188-
return `/${resolvedPath}`;
1225+
if (!resolvedAbsolute) {
1226+
if (resolvedPath.length === 0) {
1227+
return '.';
1228+
}
1229+
if (slashCheck) {
1230+
return `${resolvedPath}/`;
1231+
}
1232+
return resolvedPath;
1233+
}
1234+
1235+
if (resolvedPath.length === 0 || resolvedPath === '/') {
1236+
return '/';
11891237
}
1190-
return resolvedPath.length > 0 ? resolvedPath : '.';
1238+
return slashCheck ? `/${resolvedPath}/` : `/${resolvedPath}`;
11911239
},
11921240

11931241
/**
@@ -1271,11 +1319,35 @@ const posix = {
12711319
if (from === to)
12721320
return '';
12731321

1274-
const fromStart = 1;
1275-
const fromEnd = from.length;
1322+
// Trim any leading slashes
1323+
let fromStart = 0;
1324+
while (fromStart < from.length &&
1325+
StringPrototypeCharCodeAt(from, fromStart) === CHAR_FORWARD_SLASH) {
1326+
fromStart++;
1327+
}
1328+
// Trim trailing slashes
1329+
let fromEnd = from.length;
1330+
while (
1331+
fromEnd - 1 > fromStart &&
1332+
StringPrototypeCharCodeAt(from, fromEnd - 1) === CHAR_FORWARD_SLASH
1333+
) {
1334+
fromEnd--;
1335+
}
12761336
const fromLen = fromEnd - fromStart;
1277-
const toStart = 1;
1278-
const toLen = to.length - toStart;
1337+
1338+
// Trim any leading slashes
1339+
let toStart = 0;
1340+
while (toStart < to.length &&
1341+
StringPrototypeCharCodeAt(to, toStart) === CHAR_FORWARD_SLASH) {
1342+
toStart++;
1343+
}
1344+
// Trim trailing slashes
1345+
let toEnd = to.length;
1346+
while (toEnd - 1 > toStart &&
1347+
StringPrototypeCharCodeAt(to, toEnd - 1) === CHAR_FORWARD_SLASH) {
1348+
toEnd--;
1349+
}
1350+
const toLen = toEnd - toStart;
12791351

12801352
// Compare paths to find the longest common path from root
12811353
const length = (fromLen < toLen ? fromLen : toLen);

Diff for: ‎src/path.cc

+48-13
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ std::string PathResolve(Environment* env,
9797
std::string resolvedDevice = "";
9898
std::string resolvedTail = "";
9999
bool resolvedAbsolute = false;
100+
bool slashCheck = false;
100101
const size_t numArgs = paths.size();
101102
auto cwd = env->GetCwd(env->exec_path());
102103

@@ -126,6 +127,10 @@ std::string PathResolve(Environment* env,
126127
}
127128
}
128129

130+
if (static_cast<size_t>(i) == numArgs - 1 &&
131+
IsPathSeparator(path[path.length() - 1])) {
132+
slashCheck = true;
133+
}
129134
const size_t len = path.length();
130135
int rootEnd = 0;
131136
std::string device = "";
@@ -170,9 +175,16 @@ std::string PathResolve(Environment* env,
170175
j++;
171176
}
172177
if (j == len || j != last) {
173-
// We matched a UNC root
174-
device = "\\\\" + firstPart + "\\" + path.substr(last, j - last);
175-
rootEnd = j;
178+
if (firstPart != "." && firstPart != "?") {
179+
// We matched a UNC root
180+
device =
181+
"\\\\" + firstPart + "\\" + path.substr(last, j - last);
182+
rootEnd = j;
183+
} else {
184+
// We matched a device root (e.g. \\\\.\\PHYSICALDRIVE0)
185+
device = "\\\\" + firstPart;
186+
rootEnd = 4;
187+
}
176188
}
177189
}
178190
}
@@ -220,15 +232,27 @@ std::string PathResolve(Environment* env,
220232
// Normalize the tail path
221233
resolvedTail = NormalizeString(resolvedTail, !resolvedAbsolute, "\\");
222234

223-
if (resolvedAbsolute) {
224-
return resolvedDevice + "\\" + resolvedTail;
235+
if (!resolvedAbsolute) {
236+
if (!resolvedDevice.empty() || !resolvedTail.empty()) {
237+
return resolvedDevice + resolvedTail;
238+
}
239+
return ".";
225240
}
226241

227-
if (!resolvedDevice.empty() || !resolvedTail.empty()) {
228-
return resolvedDevice + resolvedTail;
242+
if (resolvedTail.empty()) {
243+
if (slashCheck) {
244+
return resolvedDevice + "\\";
245+
}
246+
return resolvedDevice;
229247
}
230248

231-
return ".";
249+
if (slashCheck) {
250+
if (resolvedTail == "\\") {
251+
return resolvedDevice + "\\";
252+
}
253+
return resolvedDevice + "\\" + resolvedTail + "\\";
254+
}
255+
return resolvedDevice + "\\" + resolvedTail;
232256
}
233257
#else // _WIN32
234258
std::string PathResolve(Environment* env,
@@ -237,10 +261,15 @@ std::string PathResolve(Environment* env,
237261
bool resolvedAbsolute = false;
238262
auto cwd = env->GetCwd(env->exec_path());
239263
const size_t numArgs = paths.size();
264+
bool slashCheck = false;
240265

241266
for (int i = numArgs - 1; i >= -1 && !resolvedAbsolute; i--) {
242267
const std::string& path = (i >= 0) ? std::string(paths[i]) : cwd;
243268

269+
if (static_cast<size_t>(i) == numArgs - 1 && path.back() == '/') {
270+
slashCheck = true;
271+
}
272+
244273
if (!path.empty()) {
245274
resolvedPath = std::string(path) + "/" + resolvedPath;
246275

@@ -254,15 +283,21 @@ std::string PathResolve(Environment* env,
254283
// Normalize the path
255284
auto normalizedPath = NormalizeString(resolvedPath, !resolvedAbsolute, "/");
256285

257-
if (resolvedAbsolute) {
258-
return "/" + normalizedPath;
286+
if (!resolvedAbsolute) {
287+
if (normalizedPath.empty()) {
288+
return ".";
289+
}
290+
if (slashCheck) {
291+
return normalizedPath + "/";
292+
}
293+
return normalizedPath;
259294
}
260295

261-
if (normalizedPath.empty()) {
262-
return ".";
296+
if (normalizedPath.empty() || normalizedPath == "/") {
297+
return "/";
263298
}
264299

265-
return normalizedPath;
300+
return slashCheck ? "/" + normalizedPath + "/" : "/" + normalizedPath;
266301
}
267302
#endif // _WIN32
268303

Diff for: ‎test/cctest/test_path.cc

+11-9
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,28 @@ TEST_F(PathTest, PathResolve) {
2525
"d:\\e.exe");
2626
EXPECT_EQ(PathResolve(*env, {"c:/ignore", "c:/some/file"}), "c:\\some\\file");
2727
EXPECT_EQ(PathResolve(*env, {"d:/ignore", "d:some/dir//"}),
28-
"d:\\ignore\\some\\dir");
28+
"d:\\ignore\\some\\dir\\");
2929
EXPECT_EQ(PathResolve(*env, {"."}), cwd);
3030
EXPECT_EQ(PathResolve(*env, {"//server/share", "..", "relative\\"}),
31-
"\\\\server\\share\\relative");
31+
"\\\\server\\share\\relative\\");
3232
EXPECT_EQ(PathResolve(*env, {"c:/", "//"}), "c:\\");
3333
EXPECT_EQ(PathResolve(*env, {"c:/", "//dir"}), "c:\\dir");
34-
EXPECT_EQ(PathResolve(*env, {"c:/", "//server/share"}),
35-
"\\\\server\\share\\");
36-
EXPECT_EQ(PathResolve(*env, {"c:/", "//server//share"}),
37-
"\\\\server\\share\\");
34+
EXPECT_EQ(PathResolve(*env, {"c:/", "//server/share"}), "\\\\server\\share");
35+
EXPECT_EQ(PathResolve(*env, {"c:/", "//server//share"}), "\\\\server\\share");
3836
EXPECT_EQ(PathResolve(*env, {"c:/", "///some//dir"}), "c:\\some\\dir");
3937
EXPECT_EQ(
4038
PathResolve(*env, {"C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js"}),
4139
"C:\\foo\\tmp.3\\cycles\\root.js");
40+
EXPECT_EQ(PathResolve(*env, {"\\\\.\\PHYSICALDRIVE0"}),
41+
"\\\\.\\PHYSICALDRIVE0");
42+
EXPECT_EQ(PathResolve(*env, {"\\\\?\\PHYSICALDRIVE0"}),
43+
"\\\\?\\PHYSICALDRIVE0");
4244
#else
43-
EXPECT_EQ(PathResolve(*env, {"/var/lib", "../", "file/"}), "/var/file");
44-
EXPECT_EQ(PathResolve(*env, {"/var/lib", "/../", "file/"}), "/file");
45+
EXPECT_EQ(PathResolve(*env, {"/var/lib", "../", "file/"}), "/var/file/");
46+
EXPECT_EQ(PathResolve(*env, {"/var/lib", "/../", "file/"}), "/file/");
4547
EXPECT_EQ(PathResolve(*env, {"a/b/c/", "../../.."}), cwd);
4648
EXPECT_EQ(PathResolve(*env, {"."}), cwd);
47-
EXPECT_EQ(PathResolve(*env, {"/some/dir", ".", "/absolute/"}), "/absolute");
49+
EXPECT_EQ(PathResolve(*env, {"/some/dir", ".", "/absolute/"}), "/absolute/");
4850
EXPECT_EQ(PathResolve(*env, {"/foo/tmp.3/", "../tmp.3/cycles/root.js"}),
4951
"/foo/tmp.3/cycles/root.js");
5052
#endif

0 commit comments

Comments
 (0)
Please sign in to comment.