Skip to content

Fix #78939 - Windows relative path with drive letter #5001

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

Closed

Conversation

mvorisek
Copy link
Contributor

Fix # 78939, paths like "C:x.txt" are relative, see https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats

Please note that path like "C:" is not absolute too (tested in Windows command line by cd C: - cwd did not changed).

@nikic nikic requested a review from cmb69 December 10, 2019 16:01
Copy link
Member

@cmb69 cmb69 left a comment

Choose a reason for hiding this comment

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

Well, that doesn't look quite right; C: is an absolute path, even though C:foo is not. Since this is rather critical code, it needs to be investigated thoroughly. Also, please have a look at the failing test.

@mvorisek
Copy link
Contributor Author

mvorisek commented Dec 10, 2019

@cmb69 Why do you think C: is an absolute path? According to https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats it is not absolute and if you run cd C: in Windows command line the path do not change to C: neither. C:/ or C:\ paths are absolute.

C: test in C#: https://dotnetfiddle.net/y6w0Eu

using System;
using System.IO;				

public class Program
{
	public static void Main()
	{
		Console.WriteLine(Path.GetFullPath(@"C:"));
		Console.WriteLine(Path.GetFullPath(@"C:/"));
	}
}

output:

C:\Resources\directory\64b308a5de824a85b48a86202ba65723.DotNetFiddle.Web.LocalResources\Sandbox\e1338a4e-4596-4049-88f8-3431465a922d
C:\

@mvorisek mvorisek force-pushed the fix_win_relative_path_with_drive_letter branch from 5840af3 to 4ba5474 Compare December 11, 2019 08:53
@mvorisek
Copy link
Contributor Author

@cmb69 code updated, now only this test is failing:

  • Phar - Bug #71625 - Crash in php7.dll [C:\projects\php-src\ext\phar\tests\bug71625.phpt]
    local output:
Warning: Phar::offsetSet(C:/phpbuild/php-src/x64/Release_TS/A:A:.phar): failed to open stream: No such file or directory in C:\phpbuild\php-src\ext\phar\tests\bug71625.phpt on line 17

Fatal error: Uncaught PharException: unable to open new phar "C:/phpbuild/php-src/x64/Release_TS/A:A:.phar" for writing in C:\phpbuild\php-src\ext\phar\tests\bug71625.phpt:17
Stack trace:
#0 C:\phpbuild\php-src\ext\phar\tests\bug71625.phpt(17): Phar->offsetSet('hello_habr.txt', '<? Hello Habr!?...')
#1 {main}
  thrown in C:\phpbuild\php-src\ext\phar\tests\bug71625.phpt on line 17

It seems that the bug #71625 was solved with this PR, how to update this test?

@cmb69
Copy link
Member

cmb69 commented Dec 11, 2019

Why do you think C: is an absolute path?

Because I misinterpreted a quick test, and didn't read the docs thoroughly, which state that C:Projects\apilibrary\apilibrary.sln is "a relative path from the current directory of the C: drive." So apparently each drive has its own current directory. Let's check:

@echo off

:: create test folder layout, unless already there
for %%i in (C:\test5001 C:\test5001\c D:\test5001 D:\test5001\d) do (
    if not exist %%i mkdir %%i
)

:: set cwd for each drive
cd C:\test5001
cd D:\test5001

:: verify cwd is set independently for each drive
C:
echo %CD%
D:
echo %CD%

:: check PHP's behavior
php -r "var_dump(ZEND_THREAD_SAFE)";
php -r "var_dump(file_exists('C:c'));"
php -r "var_dump(realpath('C:c'));"

I get

C:\test5001
D:\test5001
bool(true)
bool(false)
bool(false)

and

C:\test5001
D:\test5001
bool(false)
bool(true)
bool(false)

So realpath() (and scandir(); I didn't check other functions) do not support drive relative paths at all; for NTS builds, the behavior is inconsistent wrt. file_exists(). Anyhow, PHP, and particularly TSRM, has no notion of a current working directory per drive, so supporting this is hardly possible, and certainly not something that we'd like to introduce in a stable version. Instead we may consider to let file_exists() fail for drive relative paths in NTS builds as well. And of course, we should document that.

@cmb69 cmb69 added the Bug label Dec 11, 2019
@mvorisek
Copy link
Contributor Author

mvorisek commented Dec 11, 2019

So apparently each drive has its own current directory.

According to https://devblogs.microsoft.com/oldnewthing/20101011-00/?p=12563 there should be only one current working directory and the behaviour in cmd should be considered cmd only. And the linked article (link in the previous article does not work) https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133

So if there is only one cwd and this cwd is very well set/reset by php (otherwise standard relative paths will not work) and if relative links are properly detected (by this PR), all relative links should work if the resolution is done by the OS itself.

The different results of file_exists (and also of all other file functions) should be fixed by this commit to behave exactly the same between TS and NTS build (prior this PR one build used buggy IS_ABSOLUTE_PATH). Did you run your code with PHP with this PR patch?

Additionally, this PR fixes:

It seems that the bug #71625 was solved with this PR, how to update this test?

How should I fix this test? The bug is about A:A filename, which no longer resolves to absolute path and the nature of the bug changed - this filename is now just like any other unsuported filename (colon is not allowed in filenames on Windows). Should the test be simply updated with the new exception message or what is the standard process to fix these tests when the tested bug is no longer presented?

Instead we may consider to let file_exists() fail for drive relative paths in NTS builds as well. And of course, we should document that.

This was my first thought too and I support it. Even Microsoft is writing in every 2nd line that this is common source of errors. But for now I would propose to fix the original issue, i.e. wrong IS_ABSOLUTE_PATH.

@mvorisek mvorisek force-pushed the fix_win_relative_path_with_drive_letter branch from 4ba5474 to 9c30e95 Compare December 11, 2019 15:43
@mvorisek mvorisek force-pushed the fix_win_relative_path_with_drive_letter branch 2 times, most recently from 5223feb to a43805e Compare December 11, 2019 17:49
@mvorisek
Copy link
Contributor Author

@cmb69 Can you please review the changes and merge?

@mvorisek mvorisek force-pushed the fix_win_relative_path_with_drive_letter branch 3 times, most recently from 3f20997 to a43805e Compare December 12, 2019 16:57
@mvorisek
Copy link
Contributor Author

If there are no any other questions, the PR is ready to be merged.

@cmb69
Copy link
Member

cmb69 commented Dec 13, 2019

<?php
var_dump(chdir('C:/'));
var_dump(file_exists('C:/test5001'));
var_dump(file_exists('C:test5001'));

prints now for NTS:

bool(true)
bool(true)
bool(false)

and for ZTS:

bool(true)
bool(true)
bool(true)

That doesn't look like the desired output. What am I missing?

@mvorisek
Copy link
Contributor Author

@cmb69 Please add a test case and I will try to make the code to pass.

@cmb69
Copy link
Member

cmb69 commented Dec 14, 2019

Pretty strange, but I can no longer reproduce the different behavior of NTS vs. ZTS with or without this patch. Maybe that is related to the latest Windows updates I got yesterday?

Anyhow, I think it's best to start with a simple test, just to show the desired change:

--TEST--
Bug #78939 (Windows relative path with drive letter)
--SKIPIF--
<?php
if (PHP_OS_FAMILY !== 'Windows') die('skip this test is for Windows platforms only');
?>
--FILE--
<?php
list($drive, $topdir) = (explode('\\', __FILE__, 3));
var_dump(chdir("$drive\\"));
var_dump(file_exists("$drive\\$topdir"));
var_dump(file_exists("$drive$topdir"));
?>
--EXPECT--
bool(true)
bool(true)
bool(false)

This passes for me with the patch, while versions without this patch would have printed bool(true) three times.

And please rebase onto any of the actively supported PHP versions or master.

cmb69
cmb69 previously requested changes Dec 14, 2019
Copy link
Member

@cmb69 cmb69 left a comment

Choose a reason for hiding this comment

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

Okay, original feedback has been addressed, but this definitely needs more test (and likely also manual testing).

@mvorisek mvorisek force-pushed the fix_win_relative_path_with_drive_letter branch 3 times, most recently from c382be7 to 5c384d0 Compare December 14, 2019 21:28
@mvorisek
Copy link
Contributor Author

@cmb69 I have added a test case.

I have also found that file_exists (exists functions in general) and is_dir (is some file type functions) is processed differently. I have identified the cause and fixed that code as well. Even if I do not like the relative Windows paths, they should now work as specified by Microsoft on both TS & NTS builds and the PR is complete now.

@mvorisek
Copy link
Contributor Author

@cmb69 Can you review and merge to close it?

Copy link
Member

@cmb69 cmb69 left a comment

Choose a reason for hiding this comment

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

Thanks for the ping – sorry, for the delay.

The patch doesn't look bad, but should have also tests to actually check whether drive relative paths work as expected for different file system functions (e.g. fopen(), file_get_contents()), but also for other functions which could be affected (cf. 3d3f11e). Also there should be tests regarding the behavior of open_basedir, but that may not be possible. It also would be good to have which show the behavior of changing to a drive relative path on another drive, but that may also be a bit of an issue (test must be skipped if other drive is not available; how to get info of available drives?) If automated tests are not possible, this would have to be tested manually.

Anyhow, neither I nor the PHP 7.4 RM @derickr would like to see this patch going into the release branches for stability reason. Therefore, please rebase onto master.

Thanks.

@cmb69 cmb69 dismissed their stale review January 10, 2020 12:31

Changes have been made.

@mvorisek mvorisek changed the base branch from PHP-7.2 to master January 10, 2020 12:40
@mvorisek mvorisek force-pushed the fix_win_relative_path_with_drive_letter branch from 5c384d0 to 7224c94 Compare January 10, 2020 12:52
@mvorisek
Copy link
Contributor Author

The patch doesn't look bad, but should have also tests to actually check whether drive relative paths work as expected for different file system functions (e.g. fopen(), file_get_contents()), but also for other functions which could be affected (cf. 3d3f11e). Also there should be tests regarding the behavior of open_basedir, but that may not be possible. It also would be good to have which show the behavior of changing to a drive relative path on another drive, but that may also be a bit of an issue (test must be skipped if other drive is not available; how to get info of available drives?) If automated tests are not possible, this would have to be tested manually.

Standard system functions use the same underlaying path resolution. Feel free to add more tests if needed, same for the manual multidrive test if needed.

Anyhow, neither I nor the PHP 7.4 RM @derickr would like to see this patch going into the release branches for stability reason. Therefore, please rebase onto master.

Rebased and changed the merge target. All tests passed.

@mvorisek mvorisek requested review from cmb69 and nikic January 26, 2020 17:59
@mvorisek
Copy link
Contributor Author

@cmb69 , @nikic Is this PR welcomed?

@nikic nikic removed their request for review February 10, 2020 19:27
@nikic
Copy link
Member

nikic commented Feb 10, 2020

Sorry, I don't have familiarity with Windows.

Copy link
Member

@cmb69 cmb69 left a comment

Choose a reason for hiding this comment

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

Like I said above, I think this needs to be thoroughly tested (not necessarily automated), and edge-cases should be checked. My time for doing so is pretty limited, though.

Maybe @weltling and @KalleZ have something to say about this patch.

@weltling
Copy link
Contributor

Thanks for the ping, @cmb69. I would recommend against implementing this feature. We might not be able to guarantee the drive relative path is safe. There are similar issues with resolving symlinks, thanks for reminding on #1243 which looks quite similar . What comes in mind that needs to be tested

  • thread safe SAPI, in particular Apache with a good concurrency level
  • open_basedir compatibility
  • chdir() done internally in the runtime

I would at least recommend to be hesitant merging at once, until these and possibly more tests have succeeded. The discrepancy between TS/NTS and sensitivity to internal chdir() calls might be hard to solve.

Thanks.

@mvorisek
Copy link
Contributor Author

@weltling Appreciate your feedback. I am also more again supporting the drive relative paths as almost noone knows about this feature and when someone uses it, in a lot of cases it is by a mistake.

I have implemented this patch as there is a partial support currently (in a few places in the PHP code) and I decided to improve the support.

What do you think @weltling, @cmb69, @nikic about completely removing the support of drive relative paths and to return false from all file functions with warning? More specifically the same result as if path is outside open_basedir.

@cmb69
Copy link
Member

cmb69 commented Feb 13, 2020

[…] completely removing the support of drive relative paths […]

I think that would be reasonable, but probably we should first deprecate using these drive relative paths.

@nikic
Copy link
Member

nikic commented Feb 13, 2020

@cmb69 I think given that a) this is very niche, b) it's already broken / support is hit and miss, and c) we're at a major version, it makes sense to remove support directly (with a nice error message). Otherwise we have to carry around this potentially dangerous functionality for the whole major version.

@cmb69
Copy link
Member

cmb69 commented Feb 14, 2020

@nikic well, that actually makes sense.

@mvorisek
Copy link
Contributor Author

I think given that a) this is very niche, b) it's already broken / support is hit and miss, and c) we're at a major version, it makes sense to remove support directly (with a nice error message). Otherwise we have to carry around this potentially dangerous functionality for the whole major version.

well, that actually makes sense.

I agree as well, Windows relative path with drive letter should be not supported, almost noone knows about it and thus very confusing.

Can someone update this PR to reject all Windows relative pathed with drive letter in all functions/extensions?

@mvorisek
Copy link
Contributor Author

closing as I have no time to remove the support as discussed in #5001 (comment)

@mvorisek mvorisek closed this Apr 17, 2021
@mvorisek mvorisek deleted the fix_win_relative_path_with_drive_letter branch April 17, 2021 21:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants