python-babel / babel

The official repository for Babel, the Python Internationalization Library
http://babel.pocoo.org/
BSD 3-Clause "New" or "Revised" License
1.29k stars 433 forks source link

Crash with double-slashes in /etc/localtime on Python 3.9 #990

Open torfsen opened 1 year ago

torfsen commented 1 year ago

Overview Description

Importing babel.localtime crashes on Python 3.9 if /etc/localtime contains double-slashes.

Steps to Reproduce

ls -lh /etc/localtime 
lrwxrwxrwx 1 root root 24 Mar  2 03:25 /etc/localtime -> /usr/share/zoneinfo//UTC

Note the double-slash, //UTC.

# python
Python 3.9.16 (main, Mar 12 2023, 19:18:41) 
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import babel.localtime
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.9/site-packages/babel/localtime/__init__.py", line 41, in <module>
    LOCALTZ = get_localzone()
  File "/usr/local/lib/python3.9/site-packages/babel/localtime/__init__.py", line 37, in get_localzone
    return _get_localzone()
  File "/usr/local/lib/python3.9/site-packages/babel/localtime/_unix.py", line 49, in _get_localzone
    tzinfo = _get_tzinfo(zone_name)
  File "/usr/local/lib/python3.9/site-packages/babel/localtime/_helpers.py", line 21, in _get_tzinfo
    return zoneinfo.ZoneInfo(tzenv)
  File "/usr/local/lib/python3.9/zoneinfo/_tzpath.py", line 67, in find_tzfile
    _validate_tzfile_path(key)
  File "/usr/local/lib/python3.9/zoneinfo/_tzpath.py", line 81, in _validate_tzfile_path
    raise ValueError(
ValueError: ZoneInfo keys may not be absolute paths, got: /UTC
>>> import babel
>>> babel.__version__
'2.12.1'

Actual Results

Crash.

Expected Results

No crash.

Reproducibility

Crashes for me on Python 3.9 with babel 2.12.1. Works on Python 3.8.

The problem is that babel.localtime._unix._get_localzone has naive path handling.

Additional Information

I'm not sure whether babel is to blame here, but since this is working on Python 3.8 it would be nice to make babel more robust here. As far as I can tell, double slashes are valid in POSIX and should be treated as single slashes.

alexmaragko commented 1 year ago

Using babel version v2.11.0 with Python 3.9 does not give the error, so this becomes an issue in the latest versions.

mdklatt commented 1 year ago

I just encountered this with Babel 2.12.1 running with Python 3.9 inside an Ubuntu 22.04 (Jammy) container. The problem is definitely the double slash in the /etc/localtime link. I don't know why the Ubuntu installer creates an irregular link like that, but it is indeed valid.

It looks like the problem is in localtime/_unix.py. The os.readlink() function is used to get the target of /etc/localtime. This does not normalize the path, so the time zone ID is being parsed incorrectly as /UTC instead of UTC.


def _get_localzone(_root: str = '/') -> datetime.tzinfo:
    """Tries to find the local timezone configuration.
    This method prefers finding the timezone name and passing that to
    zoneinfo or pytz, over passing in the localtime file, as in the later
    case the zoneinfo name is unknown.
    The parameter _root makes the function look for files like /etc/localtime
    beneath the _root directory. This is primarily used by the tests.
    In normal usage you call the function without parameters.
    """
    # <snip>

    # This is actually a pretty reliable way to test for the local time
    # zone on operating systems like OS X.  On OS X especially this is the
    # only one that actually works.
    try:
        link_dst = os.readlink('/etc/localtime')
    except OSError:
        pass

    # <snip>
mdklatt commented 1 year ago

I think the solution to this is to replace os.readlink() with pathlib.Path().resolve(), which will normalize the path and remove the double slashes. I created a fork, but coming up with a test for this is tricky.

natescherer commented 11 months ago

Confirmed this is also a problem with Python 3.10 on Ubuntu.

mdklatt commented 8 months ago

I submitted https://github.com/python-babel/babel/pull/1006 for this back in June. The PR could not be automatically merged because some unrelated broken tests were failing. It looks like it was manually merged by a maintainer, but I'm not sure what the current status is.

mdklatt commented 8 months ago

The broken unit tests have been fixed upstream. I rebased my fixes onto master and submitted https://github.com/python-babel/babel/pull/1035. This should pass all automated checks, which will hopefully streamline the merge process and speed up the resolution of this issue.

mdklatt commented 8 months ago

For anybody that needs a temporary workaround, I was able to fix this in an Ubuntu Docker image by recreating the /etc/localtime link to remove the double slashes:

rm -f /etc/localtime
ln -s /usr/share/zoneinfo/Etc/UTC /etc/localtime
igormcoelho commented 8 months ago

I'm having this issue with Ubuntu 22.04, python 3.10 and babel 2.13.

Traceback (most recent call last):
  File "/usr/local/bin/sphinx-build", line 5, in <module>
    from sphinx.cmd.build import main
  File "/usr/local/lib/python3.10/dist-packages/sphinx/cmd/build.py", line 25, in <module>
    from sphinx.application import Sphinx
  File "/usr/local/lib/python3.10/dist-packages/sphinx/application.py", line 32, in <module>
    from sphinx.config import Config
  File "/usr/local/lib/python3.10/dist-packages/sphinx/config.py", line 22, in <module>
    from sphinx.util.i18n import format_date
  File "/usr/local/lib/python3.10/dist-packages/sphinx/util/i18n.py", line 17, in <module>
    import babel.dates
  File "/usr/local/lib/python3.10/dist-packages/babel/dates.py", line 34, in <module>
    from babel import localtime
  File "/usr/local/lib/python3.10/dist-packages/babel/localtime/__init__.py", line 41, in <module>
    LOCALTZ = get_localzone()
  File "/usr/local/lib/python3.10/dist-packages/babel/localtime/__init__.py", line 37, in get_localzone
    return _get_localzone()
  File "/usr/local/lib/python3.10/dist-packages/babel/localtime/_unix.py", line 49, in _get_localzone
    tzinfo = _get_tzinfo(zone_name)
  File "/usr/local/lib/python3.10/dist-packages/babel/localtime/_helpers.py", line 21, in _get_tzinfo
    return zoneinfo.ZoneInfo(tzenv)
  File "/usr/lib/python3.10/zoneinfo/_tzpath.py", line 67, in find_tzfile
    _validate_tzfile_path(key)
  File "/usr/lib/python3.10/zoneinfo/_tzpath.py", line 81, in _validate_tzfile_path
    raise ValueError(
ValueError: ZoneInfo keys may not be absolute paths, got: /UTC

The strange thing is that my /etc/localtime does not have any slash... but it is quite strange anyway:

$ cat /etc/localtime 
TZif2UTCTZif2UTC
UTC0

Don't know how to fix it.

[EDIT] Sorry! Now I noticed that the double slash is on the filepath itself...

$ ls -la /etc/localtime
/etc/localtime -> /usr/share/zoneinfo//UTC

So this solution indeed fixed my error:

$ rm -f /etc/localtime
$ ln -s /usr/share/zoneinfo/UTC /etc/localtime