Repository: yt-dlp/yt-dlp
File: test/test_utils.py (Python)

Code before:
        self.assertEqual(sanitize_path('\\\\?\\UNC/ComputerName/abc'), '\\\\?\\UNC\\ComputerName\\abc')

        self.assertEqual(sanitize_path('\\\\?\\C:\\abc'), '\\\\?\\C:\\abc')
        self.assertEqual(sanitize_path('\\\\?\\C:/abc'), '\\\\?\\C:\\abc')
        self.assertEqual(sanitize_path('\\\\?\\C:\\ab?c\\de:f'), '\\\\?\\C:\\ab#c\\de#f')
        self.assertEqual(sanitize_path('\\\\?\\C:\\abc'), '\\\\?\\C:\\abc')

        self.assertEqual(
            sanitize_path('youtube/%(uploader)s/%(autonumber)s-%(title)s-%(upload_date)s.%(ext)s'),
            'youtube\\%(uploader)s\\%(autonumber)s-%(title)s-%(upload_date)s.%(ext)s')

        self.assertEqual(
            sanitize_path('youtube/TheWreckingYard ./00001-Not bad, Especially for Free! (1987 Yamaha 700)-20141116.mp4.part'),
            'youtube\\TheWreckingYard #\\00001-Not bad, Especially for Free! (1987 Yamaha 700)-20141116.mp4.part')
        self.assertEqual(sanitize_path('abc/def...'), 'abc\\def..#')
        self.assertEqual(sanitize_path('abc.../def'), 'abc..#\\def')
        self.assertEqual(sanitize_path('abc.../def...'), 'abc..#\\def..#')

        self.assertEqual(sanitize_path('../abc'), '..\\abc')
        self.assertEqual(sanitize_path('../../abc'), '..\\..\\abc')
        self.assertEqual(sanitize_path('./abc'), 'abc')
        self.assertEqual(sanitize_path('./../abc'), '..\\abc')

        self.assertEqual(sanitize_path('\\abc'), '\\abc')
        self.assertEqual(sanitize_path('C:abc'), 'C:abc')
        self.assertEqual(sanitize_path('C:abc\\..\\'), 'C:..')
        self.assertEqual(sanitize_path('C:\\abc:%(title)s.%(ext)s'), 'C:\\abc#%(title)s.%(ext)s')

    def test_sanitize_url(self):
        self.assertEqual(sanitize_url('//foo.bar'), 'http://foo.bar')
        self.assertEqual(sanitize_url('httpss://foo.bar'), 'https://foo.bar')
        self.assertEqual(sanitize_url('rmtps://foo.bar'), 'rtmps://foo.bar')
        self.assertEqual(sanitize_url('https://foo.bar'), 'https://foo.bar')
        self.assertEqual(sanitize_url('foo bar'), 'foo bar')

    def test_expand_path(self):
        def env(var):
            return f'%{var}%' if sys.platform == 'win32' else f'${var}'

        os.environ['yt_dlp_EXPATH_PATH'] = 'expanded'
        self.assertEqual(expand_path(env('yt_dlp_EXPATH_PATH')), 'expanded')

        old_home = os.environ.get('HOME')
        test_str = R'C:\Documents and Settings\тест\Application Data'
        try:
            os.environ['HOME'] = test_str
            self.assertEqual(expand_path(env('HOME')), os.getenv('HOME'))
            self.assertEqual(expand_path('~'), os.getenv('HOME'))
            self.assertEqual(
                expand_path('~/{}'.format(env('yt_dlp_EXPATH_PATH'))),
                '{}/expanded'.format(os.getenv('HOME')))

Review comment (security):
In the [CPython source](https://github.com/python/cpython/blob/3.13/Lib/ntpath.py#LC510), a simpler tactic like this (what I would have expected) is used:
```suggestion
        try:
            from nt import _path_normpath as nt_path_normpath
        except ImportError:
            nt_path_normpath = None
```
But then if the import succeeds here it would have succeeded in the Python library. This means having a sufficiently new Python (>=3.11) running on Windows. In that case `os.path.normpath` is an alias of `nt_path_normpath`: shouldn't `sanitize_path()` just punt to `os.path.normpath()` then? And then you wonder whether the library fallback code for 3.9 and 3.10 could replace the custom code in `sanitize_path()`.

Code after:
        self.assertEqual(sanitize_path('\\\\?\\UNC/ComputerName/abc'), '\\\\?\\UNC\\ComputerName\\abc')

        self.assertEqual(sanitize_path('\\\\?\\C:\\abc'), '\\\\?\\C:\\abc')
        self.assertEqual(sanitize_path('\\\\?\\C:/abc'), '\\\\?\\C:\\abc')
        self.assertEqual(sanitize_path('\\\\?\\C:\\ab?c\\de:f'), '\\\\?\\C:\\ab#c\\de#f')
        self.assertEqual(sanitize_path('\\\\?\\C:\\abc'), '\\\\?\\C:\\abc')

        self.assertEqual(
            sanitize_path('youtube/%(uploader)s/%(autonumber)s-%(title)s-%(upload_date)s.%(ext)s'),
            'youtube\\%(uploader)s\\%(autonumber)s-%(title)s-%(upload_date)s.%(ext)s')

        self.assertEqual(
            sanitize_path('youtube/TheWreckingYard ./00001-Not bad, Especially for Free! (1987 Yamaha 700)-20141116.mp4.part'),
            'youtube\\TheWreckingYard #\\00001-Not bad, Especially for Free! (1987 Yamaha 700)-20141116.mp4.part')
        self.assertEqual(sanitize_path('abc/def...'), 'abc\\def..#')
        self.assertEqual(sanitize_path('abc.../def'), 'abc..#\\def')
        self.assertEqual(sanitize_path('abc.../def...'), 'abc..#\\def..#')
        self.assertEqual(sanitize_path('C:\\abc:%(title)s.%(ext)s'), 'C:\\abc#%(title)s.%(ext)s')

        # Check with nt._path_normpath if available
        try:
            import nt

            nt_path_normpath = getattr(nt, '_path_normpath', None)
        except Exception:
            nt_path_normpath = None

        for test, expected in [
            ('C:\\', 'C:\\'),
            ('../abc', '..\\abc'),
            ('../../abc', '..\\..\\abc'),
            ('./abc', 'abc'),
            ('./../abc', '..\\abc'),
            ('\\abc', '\\abc'),
            ('C:abc', 'C:abc'),
            ('C:abc\\..\\', 'C:'),
            ('C:abc\\..\\def\\..\\..\\', 'C:..'),
            ('C:\\abc\\xyz///..\\def\\', 'C:\\abc\\def'),
            ('abc/../', '.'),
            ('./abc/../', '.'),
        ]:
            result = sanitize_path(test)
            assert result == expected, f'{test} was incorrectly resolved'
            assert result == sanitize_path(result), f'{test} changed after sanitizing again'
            if nt_path_normpath:
                assert result == nt_path_normpath(test), f'{test} does not match nt._path_normpath'

    def test_sanitize_url(self):
        self.assertEqual(sanitize_url('//foo.bar'), 'http://foo.bar')
        self.assertEqual(sanitize_url('httpss://foo.bar'), 'https://foo.bar')
        self.assertEqual(sanitize_url('rmtps://foo.bar'), 'rtmps://foo.bar')