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: Add dynamic device path handling for windows file access #25851

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

yazan-abdalrahman
Copy link
Contributor

fix #24791

@yazan-abdalrahman
Copy link
Contributor Author

@dsherret @bartlomieju @piscisaureus @lucacasonato Please review this Pull Request.

|| path_bytes.starts_with(b"//./")
|| path_bytes.starts_with(b"//?/")
|| path_bytes.starts_with(br"\\?\")
|| path_bytes.ends_with(b"$"))
&& !path_bytes.contains(&b':');
Copy link
Member

Choose a reason for hiding this comment

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

This condition is also very crude and probably wrong. To exclude "file paths" then we should look for:

  • paths that start with \\.\ + letter + :\
  • paths that start with \\.\UNC\

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This condition is also very crude and probably wrong. To exclude "file paths" then we should look for:

  • paths that start with \\.\ + letter + :\
  • paths that start with \\.\UNC\

It doesn't look for :\ It excludes &&!path_bytes.contains(&b':');

I did not add this \\.\ This was added to #23721.

So, the main issue comes from this circumstance.

image

So I think this is the correct condition; if not, what should I do?

|| path_bytes.starts_with(b"//./")
|| path_bytes.starts_with(b"//?/")
|| path_bytes.starts_with(br"\\?\")
|| path_bytes.ends_with(b"$"))
Copy link
Contributor

Choose a reason for hiding this comment

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

ends_with(b"$") seems to broad.

To my knowledge only CONIN$ and CONOUT$ are the only $-ending special file names. Otherwise, files ending with $ are allowed and are regular files.

@rivy
Copy link
Contributor

rivy commented Oct 5, 2024

In addition to the above code, as Windows is, by default (always?), case-ignoring for these device names, I realized that path_bytes and the path prefix bytes need to be shifted to upper case for the comparison.

Since the possible comparisons are all composed of single byte ASCII characters, a simple quick shift of byte - (b"a" - b"A") should be possible to convert the case for the comparisons.

@rivy
Copy link
Contributor

rivy commented Oct 6, 2024

This is also seems to be complicated by permissions; see PR #25132 for issue #24703.

@rivy
Copy link
Contributor

rivy commented Oct 6, 2024

And, as the WinOS API is "seperator/slash-agnostic", combinations of both forward and back slashes should be accepted (eg, \\.\, //./, but also \\./, \/./, , \/.\, ...). There are eight combinations of each prefix, so, it's probably easiest and most efficient to copy the first four bytes, convert to slash or backslash, and then compare to that prefix, or use regex.

@rivy
Copy link
Contributor

rivy commented Oct 6, 2024

This is TS code that I'm using to accomplish a similar task...

// `pathFromURL()`
/** Extract the "path" (absolute file path for 'file://' URLs, otherwise the href URL-string) from the `url`.
* * `no-throw` ~ function returns `undefined` upon any error
@param [url] • URL for path extraction
@tags `no-throw`
*/
export function pathFromURL(url?: URL) {
	if (url == null) return undefined;
	let path = url.href;
	// console.warn('pathFromURL:', { url, path });
	if (url.protocol === 'file:') {
		try {
			path = $path.fromFileUrl(path);
		} catch (_error) {
			return undefined;
		}
	}
	// console.warn('pathFromURL:', { url, path });
	const isWinOS = Deno.build.os === 'windows';
	if (isWinOS) {
		// WinOS ~ handle special device paths
		// ref: [File path formats](https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats) @@ <https://archive.is/0shPL>
		// ref: [Naming Files, Paths, and Namespaces](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file) @@ <https://archive.is/mQOTg>

		// * no further processing for paths with device prefixes (eg, '\\.\', '\\?\')
		if (!path.match(/^[/\\][/\\][.?][/\\]/)) {
			// * note: this translation is explicitly equivalent to the more sensible (and least surprise) Win11 version, keeping the full file name in the path
			//     ... Win10 (surprisingly) ignores text past the filePrefix for legacy device names; eg Win10 sees 'CON.txt' as '\\.\CON'
			const fileBaseName = $path.basename(path).toLocaleUpperCase();
			const filePrefix = fileBaseName.replace(/[.].*$/, '');
			const specialDeviceBaseNames = ['CONIN$', 'CONOUT$'];
			const specialDevicePrefixes = ([] as string[]).concat(
				['CON', 'PRN', 'AUX', 'NUL'], // legacy device names
				['COM0', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9'], // legacy COM device names
				['LPT0', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'], // legacy LPT device names
			);
			if (
				specialDeviceBaseNames.includes(fileBaseName) ||
				specialDevicePrefixes.includes(filePrefix)
			) {
				path = `\\\\?\\${path}`;
			}
		}
	}
	// `\\?\...` is likely the more "correct" prefix as it skips further Windows normalization via `GetFullPathName()`; but Deno and the standard URL class do not support it
	// * instead use the usually equivalent `\\.\` prefix for better compatibility with Deno and the standard URL class
	path = path.replace(/^[/\\][/\\][?][/\\]/, `${$path.SEP}${$path.SEP}.${$path.SEP}`);
	return path;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

🐛(WinOS) Unable to open 'CONIN$' or 'CONOUT$' for all versions >= 1.43.0
3 participants