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

path: fix posix.relative returns different results #13738

Closed

Conversation

DuanPengfei
Copy link
Contributor

Throw an error ERR_UNSUPPORTED_PLATFOMR when direct use
path.posix.resolve on Windows or direct use path.win32.resolve
on *nix.

Update docs, list win32 functions do not support direct use on
*nix and posix functions do not support direct use on Windows.

Update tests, only run current platform type test.

Fixes: #13683
Refs: #13714

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
Affected core subsystem(s)

doc, path

@nodejs-github-bot nodejs-github-bot added errors Issues and PRs related to JavaScript errors originated in Node.js core. path Issues and PRs related to the path subsystem. labels Jun 17, 2017
@addaleax addaleax added the semver-major PRs that contain breaking changes and should be released in the next major version. label Jun 17, 2017
@addaleax
Copy link
Member

I’m sorry, but imho this is not worth breaking people’s code.

@XadillaX
Copy link
Contributor

XadillaX commented Jun 17, 2017

@addaleax we've discussed in #13683 with @refack.

Anyway, did you prefer process.emitWarning?

@addaleax
Copy link
Member

@XadillaX I’ve replied in the issue thread, but this just seems like it’s breaking code without a good reason, switching to a warning doesn’t change that.

@mscdex
Copy link
Contributor

mscdex commented Jun 17, 2017

-1 I don't think we should be artificially limiting these functions.

@mscdex mscdex removed the errors Issues and PRs related to JavaScript errors originated in Node.js core. label Jun 17, 2017
@refack
Copy link
Contributor

refack commented Jun 17, 2017

I’m sorry, but imho this is not worth breaking people’s code.

-1 I don't think we should be artificially limiting these functions.

@addaleax @mscdex do you think limiting the assertion to only to when process.cwd() is used, will be acceptable?
IMHO those cases are bugs:

> path.posix.resolve('./gaga/')
'C:\\bin\\dev\\node/gaga'  // bug
> path.posix.resolve('/gaga/')
'/gaga'   // Ok

@mscdex
Copy link
Contributor

mscdex commented Jun 17, 2017

I really don't think they need changing. IMHO when you explicitly access the path functions for a different platform, you should already be aware of what you're getting into...

@refack
Copy link
Contributor

refack commented Jun 17, 2017

I really don't think they need changing. IMHO when you explicitly access the path functions for a different platform, you should already be aware of what you're getting into...

🤔 it's a thinker indeed...
I just have a gut feeling.. If we know we are going to return a nonsense answer we should throw or warn. I'm not sure that devs really know what they are getting into, they assume it'll just work, and work right.

@DuanPengfei
Copy link
Contributor Author

It would be better to only throw error when rely on cwd. If choose this solution, we also need to chang the path.posix.relative. We found path.posix.resolve return mistake result in the process of resolving this issue 13683. So I think need to do something to make path.posix.relative return the corrent result in most cases.

@addaleax
Copy link
Member

If we know we are going to return a nonsense answer we should throw or warn.

I might agree if the answer was actually nonsense … but it isn’t? The result may not directly usable for file system operations, but that doesn’t make it less consistent or meaningless.

It would be better to only throw error when rely on cwd.

I agree with @mscdex, we should not artificially limit these methods.

@DuanPengfei
Copy link
Contributor Author

I will continue to fix it.

@DuanPengfei
Copy link
Contributor Author

@addaleax If do not limit the method user will get incorrect result, it is also unreasonable.

@refack
Copy link
Contributor

refack commented Jun 18, 2017

IMHO this is nonsensical:

> path.posix.resolve('./gaga/')
'C:\\bin\\dev\\node/gaga'  // bug
> path.win32.resolve('.\\gaga\\')
'\\mnt\\d\\code\\node\\gaga'

Consistent, yes, meaningful, I don't think so.
We've seen several times devs who are surprised it doesn't just work...

P.S. Just a point of thought, the use of process.cwd() is borderline actual FS access, and not pure string manipulation anymore.
Just as an example for a platform independent path manipulator URL requires a absolute base argument when first argument is relative.

P.P.S. Let see what @DuanPengfei comes up with 👍

@DuanPengfei
Copy link
Contributor Author

My idea is make the result correct when do't rely on cwd. If rely on cwd will throw an error Error: ERR_UNSUPPORTED_PLATFORM. Because of relative and _makeLong rely on resolve function, also need to modify relative and _makeLong, make their result as correct as possible in most case.

@DuanPengfei
Copy link
Contributor Author

DuanPengfei commented Jun 20, 2017

Implemented some feature in my local code.

Idea

resolve: It will throw error when rely on cwd.

relative:

If from and to are both relative path, will generate fake path then call resolve to produce the absolute path, then continue run the next code.

win32 fake path: C:\Users\node\${from/to}, posix fake path: /Users/node/${from/to}

If one of from and to is relative path, will use another absolute path as the cwd to generate fake path then call resolve to produce the absolute path, then continue run the next code.

win32: if from is absolute, to = resolve(from + '\' + to) else from = resolve(to + '\' + from)
posix: if from is absolute, to = resolve(from + '/' + to) else from = resolve(to + '/' + from)

Examples

If ../../x's upper dirctory to long, e.g ../../../../../../x, the resolve function only can resolve to the root directory /x

on Windows

// both relative
path.posix.relative('a/b/c', '../../x');  // => '../../../../../x'
path.posix.relative('../../x, 'a/b/c'');  // => '../home/node/a/b/c'

/**
 * `from`'s upper path is too long beyond the root path
 */
path.posix.relative('../../../../../../../x, 'a/b/c'');  // => '../home/node/a/b/c'

// one relative
path.posix.relative('/a/b/c', 'd/e/f');  // => 'd/e/f'
path.posix.relative('a/b/c', '/d/e/f');  // => '../../..'

// both absolute
path.posix.relative('/a/b/c', '/d/e/f');  // => '../../../d/e/f'

on POSIX

// both relative
path.win32.relative('a\b\c', '..\..\x');  // => '..\..\..\..\..\x'
path.win32.relative('..\..\..\..\..\x', 'a\b\c');  // => '.\Users\nodea\b\c'

// one relative
path.win32.relative('C:\a\b\c', '..\..\..\x');  // => '..\..\..\x'
path.win32.relative('C:\..\..\..\x', 'a\b\c');  // => 'a\b\c'

// both absolute
path.win32.relative('C:\a\b\c', 'C:\d\e\f');  // => '..\..\..\d\e\f'

@refack @addaleax @mscdex What do you think of how this is done?

@refack
Copy link
Contributor

refack commented Jun 20, 2017

@refack @addaleax @mscdex What do you think of how this is done?

IMHO it sounds like a good solution. If you replace the word "fake" with "surrogate" it'll sound even more sophisticated.

@DuanPengfei
Copy link
Contributor Author

😁 My ability to speak in English is weak. I will improve the code, document and test, then push the code.

By the way, for the surrogate path, my colleague suggested me use C:\fakepath as the Chrome did when upload file. I have no preference for using C:\fakepath or C:\Users\node, do you have any preference?

@refack
Copy link
Contributor

refack commented Jun 21, 2017

C:\fakepath or C:\Users\node

If chrome is using c:\fakepath that SGTM (but for some reason I'd prefer a lowercase c 🤷‍♂️ )

Throw an error `ERR_UNSUPPORTED_PLATFOMR` when direct use
`path.posix.resolve` on Windows or direct use `path.win32.resolve`
on *nix.

Update docs, list win32 functions do not support direct use on
*nix and posix functions do not support direct use on Windows.

Update tests, only run current platform type test.

Fixes: nodejs#13683
Refs: nodejs#13714
@DuanPengfei
Copy link
Contributor Author

According to the idea, implement the code.

Copy link
Contributor

@refack refack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look good, just a few nits.


if (process.platform !== 'win32') {
if (!isFromAbsolute && !isToAbsolute) {
from = 'c:\\fakepath\\' + from;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

store the surrogate in a const (or even a function if it works)

function absoluteWithSurrogate(p) {
  return win32.resolve(win32.join(`c:\\fakepath`, p));
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌

}

if (process.platform !== 'win32') {
if (!isFromAbsolute && !isToAbsolute) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like you can do this separately for to and from

if (isFromAbsolute) ...
else ...
if (isToAbsolute) ...
else ...

Or maybe I'm wrong?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because need to know both type of from and to, so I think I can't do this separately.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok 👍


if (process.platform === 'win32') {
if (!isFromAbsolute && !isToAbsolute) {
from = '/fakepath/' + from;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrap (same as above)

};

{
let tests;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const tests = common.isWindows ? resolveTest.win32 : resolveTest.posix;
const throwing = common.isWindows ? path.posix.resolve : path.posix.resolve;

failures.push(`\n${message}`);
};
{
let tests;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

['\\\\foo\\baz', '\\\\foo\\baz-quux', '..\\baz-quux'],
['C:\\baz', '\\\\foo\\bar\\baz', '\\\\foo\\bar\\baz'],
['\\\\foo\\bar\\baz', 'C:\\baz', 'C:\\baz']
const relativeTest = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be best to split out only the tests that would throw.
The other cases need to be tested on all platforms.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌

Copy link
Contributor

@mscdex mscdex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making my -1 explicit. I don't think we should be changing the behavior of these functions.

@refack
Copy link
Contributor

refack commented Jun 22, 2017

@addaleax @mscdex since this is a good change, if we split the throwing logic to a separate PR, and just keep the fix with the surrogate, will you approve?

@mscdex
Copy link
Contributor

mscdex commented Jun 22, 2017

@refack As I said, I don't think we should be changing the behavior of these functions. That includes both the throwing and the injecting of arbitrary path components into path function results.

@addaleax
Copy link
Member

I’m sorry, but I agree with @mscdex; we really should not be doing this kind of change.

IIUC, the original bug report had one clear problem it pointed to: path.posix.relative('a/b/c', '../../x'); on Windows changed over time and produced a result that included .... as a component. We won’t be doing our users any favours if we replace one invalid part of the result with another invalid part.

@refack
Copy link
Contributor

refack commented Jun 22, 2017

I’m sorry, but I agree with @mscdex; we really should not be doing this kind of change.

IIUC, the original bug report had one clear problem it pointed to: path.posix.relative('a/b/c', '../../x'); on Windows changed over time and produced a result that included .... as a component. We won’t be doing our users any favours if we replace one invalid part of the result with another invalid part.

Trying to figure out a path forward (sorry about the pun). Should we keep the path.posix.relative/path.win32.relative fixes here, and move the cwd assertion to a different PR, and discuss merits there?
@DuanPengfei tl;dr the tests should not change, just add more cases that will work correctly after the fix.

@addaleax
Copy link
Member

Should we keep the path.posix.relative/path.win32.relative fixes here

I wouldn’t think of adding fakepath as a fix.

and move the cwd assertion to a different PR, and discuss merits there?

You can do that but I think both @mscdex and I have made it pretty clear that we don’t think those changes are a good idea at all.

@refack
Copy link
Contributor

refack commented Jun 22, 2017

Should we keep the path.posix.relative/path.win32.relative fixes here

I wouldn’t think of adding fakepath as a fix.

So the main goal is to add more working cases to the tests (no changes). Specifically path.posix.relative('a/b/c', '../../x') and path.win32.relative('a\b\c', '..\..\x');

@DuanPengfei
Copy link
Contributor Author

😵 Change so much code is because in the modification of this problem I found more bottom bug. Inject fakepath to path is compromise way. So now I think I should fix the issue 13683 here (only make the test case path.posix.relative('a/b/c', '../../x') return the expected result). Then I will open a new issue about path.posix.resolve, path.win32.resolve, path.posix.relative and path.win32.relative return wrong path. We can continue discuss how to resolve the new problem there.

@DuanPengfei DuanPengfei reopened this Jun 23, 2017
@DuanPengfei
Copy link
Contributor Author

DuanPengfei commented Jun 23, 2017

#13887

@refack
Copy link
Contributor

refack commented Jun 23, 2017

😵 Change so much code is because in the modification of this problem I found more bottom bug. Inject fakepath to path is compromise way. So now I think I should fix the issue 13683 here (only make the test case path.posix.relative('a/b/c', '../../x') return the expected result). Then I will open a new issue about path.posix.resolve, path.win32.resolve, path.posix.relative and path.win32.relative return wrong path. We can continue discuss how to resolve the new problem there.

👍 I think that will be good.

@@ -431,6 +431,39 @@ added: v0.11.15
The `path.posix` property provides access to POSIX specific implementations
of the `path` methods.

*Note*: Be careful when using the following functions directly on Windows.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This note should be moved into the bodies of each of the API sections. Sitting above it, it is far more likely that the Note will become disconnected...

### path.posix.resolve([...path])
If after processing all given `path` segments an absolute path has not yet
been generated, `path.posix.resolve` will throw [`UNSUPPORTED_PLATFORM`] error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps:

*Note*: Care should be taken when using `path.posix.resolve()` on Windows due to ...

Then include a short snippet about why...

Same with the path.posix.relative() below.

Copy link
Contributor

@refack refack Jun 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion:

*Note*: Care should be taken when using `path.posix.resolve()` on Windows due to the fact that resolve will use `process.cwd()` verbatim, and so the output will be a concatenation of Windows and POSIX style paths.

been generated, `path.posix.resolve` will throw [`UNSUPPORTED_PLATFORM`] error.

### path.posix.relative(from, to)
If `from` and `to` are both relative path, `path.posix.relative` will use the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please include the () when referencing functions... e.g. path.posix.relative()

@@ -187,6 +199,10 @@ const win32 = {
path = arguments[i];
} else if (!resolvedDevice) {
path = process.cwd();

// If you use the current working directory,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// If the current working directory is used ...

@@ -201,6 +217,10 @@ const win32 = {
path.slice(0, 3).toLowerCase() !==
resolvedDevice.toLowerCase() + '\\') {
path = resolvedDevice + '\\';
} else {
// If you use the current working directory,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// The the current working directory is used...

@DuanPengfei
Copy link
Contributor Author

DuanPengfei commented Jun 27, 2017

👌 Then I will modify the code only resolve the problem path.posix.relative return different results in this PR. I will create a new PR and modify the code according to your advice to resolve the problem #13887.

@refack
Copy link
Contributor

refack commented Jul 24, 2017

@DuanPengfei anything I can do you assist in this?

@DuanPengfei
Copy link
Contributor Author

@refack 😂 Sorry for a long time no response. Some time ago sick leave, this weekend will continue to repair the bug. If there is a need, I will ask you for help.

@DuanPengfei
Copy link
Contributor Author

Accept the recommendations of @timothy Gu, decide to refer to the implementation of the Python or any other language. Do you think this is a good way?

@TimothyGu
Copy link
Member

FWIW Python 3's relative() and resolve() equivalents do the same things as Node.js right now. Note: Python's relpath's operands are reversed: (to, from) rather than (from, to).

>>> ntpath.realpath(r'.\gaga')
'\\home\\timothy-gu\\dev\\node\\node\\gaga'
>>> ntpath.relpath(r'a\b\c', r'..\..\x')
'..\\node\\node\\a\\b\\c'

(I'm on Linux.)

I'm reluctant to change Node.js' current behavior.

@DuanPengfei
Copy link
Contributor Author

We will not change the current behavior. And in this PR I will only make the return of path.relative('a/b/c', '../../x') be ../../../../../x as the Python do. Then think of what to do in #13887. To be honest, I do't think it's a bug. It's normal to return the unexpected result when use path.posix.relative on Windows. Python or any other language also have this problem. 😂

@BridgeAR
Copy link
Member

I am closing this due to the long inactivity.

@DuanPengfei please feel free to leave a comment if you would like to follow up on this by fixing the issue itself. Your work is much appreciated nevertheless.

@BridgeAR BridgeAR closed this Sep 19, 2017
AngusMorton added a commit to AngusMorton/run that referenced this pull request May 2, 2023
Using path.posix.relative is not an option because it returns the wrong path.
See:
* nodejs/node#13887
* nodejs/node#13738
rturnq pushed a commit to marko-js/run that referenced this pull request May 3, 2023
Using path.posix.relative is not an option because it returns the wrong path.
See:
* nodejs/node#13887
* nodejs/node#13738
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
path Issues and PRs related to the path subsystem. semver-major PRs that contain breaking changes and should be released in the next major version.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

path.posix.relative returns different results for *nix and Windows versions of node
9 participants