Repository: PyFilesystem/pyfilesystem2

Issue:
fs/_ftp_parse.py fails when parsing directory listing on NT format if folder or filename contains AM or PM
The regex pattern used to parse the directory listing for NT servers uses a greedy (default) pattern when parsing the modified date column. This causes the `(<modified>)` capture group to capture until the last AM or PM, which could be in the file name.

Example file listing that fails:
```
01-29-20  04:11AM       <DIR>          Clock at AM or PM
```
This fails with the following trace
```
  File ".venv\lib\site-packages\fs\ftpfs.py", line 746, in scandir
    if not self.supports_mlst and not self.getinfo(path).is_dir:
  File ".venv\lib\site-packages\fs\ftpfs.py", line 614, in getinfo
    directory = self._read_dir(dir_name)
  File ".venv\lib\site-packages\fs\ftpfs.py", line 495, in _read_dir
    _list = [Info(raw_info) for raw_info in ftp_parse.parse(lines)]
  File ".venv\lib\site-packages\fs\_ftp_parse.py", line 70, in parse
    raw_info = parse_line(line)
  File ".venv\lib\site-packages\fs\_ftp_parse.py", line 81, in parse_line
    return decode_callable(line, match)
  File ".venv\lib\site-packages\fs\_ftp_parse.py", line 163, in decode_windowsnt
    raw_info["details"]["size"] = int(match.group("size"))
ValueError: invalid literal for int() with base 10: ''
```



Discussion:
lurch: The tests pass, so LGTM.

@willmcgugan Slightly off-topic question, but is there a good reason for the inconsistency of having the RE_LINUX using numbered capture-groups but the RE_WINDOWSNT using named capture-groups?
willmcgugan: @lurch It's been so long, I'd be guessing. But I reckon I copied those regexes from somewhere.
xoriath: So, anything else needed? I don't seem to have merge rights :) 
willmcgugan: Sorry. There is a back log. Plan to clear it soon.

On Mon, 18 May 2020 at 17:18, Morten Engelhardt Olsen <
notifications@github.com> wrote:

> So, anything else needed? I don't seem to have merge rights :)
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/PyFilesystem/pyfilesystem2/pull/396#issuecomment-630290453>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAEHLAM3ZRU4EHB3YBWOJR3RSFNWRANCNFSM4NCXBWJA>
> .
>
-- 
Will McGugan
https://www.willmcgugan.com <http://www.willmcgugan.com>

xoriath: Hihihi, no problem!
richardcooper: I tripped over this bug recently. I'm applying this patch locally and can confirm that it fixes the problem.
althonos: @xoriath sorry to bother after such a long time, could you please update the `CHANGELOG.md` and `CONTRIBUTORS.md` files so that I can merge this ? Thanks!
xoriath: No problem, @althonos , updated the files and rebased to upstream master

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0449d14..7cec240 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
   ensuring `conftest.py` is present, fixes [#364](https://github.com/PyFilesystem/pyfilesystem2/issues/364).
 - Stop patching copy with Python 3.8+ because it already uses sendfile.
 - Fixed crash when CPython's -OO flag is used
+- Fixed error when parsing timestamps from a FTP directory served from a WindowsNT FTP Server, fixes [#395](https://github.com/PyFilesystem/pyfilesystem2/issues/395).
 
 ## [2.4.11] - 2019-09-07
 
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index de54b66..3b3e8c2 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -11,3 +11,4 @@ Many thanks to the following developers for contributing to this project:
 - [Martin Larralde](https://github.com/althonos)
 - [Will McGugan](https://github.com/willmcgugan)
 - [Zmej Serow](https://github.com/zmej-serow)
+- [Morten Engelhardt Olsen](https://github.com/xoriath)
diff --git a/fs/_ftp_parse.py b/fs/_ftp_parse.py
index b50f75e..b503d73 100644
--- a/fs/_ftp_parse.py
+++ b/fs/_ftp_parse.py
@@ -41,7 +41,7 @@ RE_LINUX = re.compile(
 RE_WINDOWSNT = re.compile(
     r"""
     ^
-    (?P<modified>.*(AM|PM))
+    (?P<modified>.*?(AM|PM))
     \s*
     (?P<size>(<DIR>|\d*))
     \s*


Test patch:
diff --git a/tests/test_ftp_parse.py b/tests/test_ftp_parse.py
index c649a1f..d0abc05 100644
--- a/tests/test_ftp_parse.py
+++ b/tests/test_ftp_parse.py
@@ -166,6 +166,7 @@ drwxr-xr-x   2 foo-user foo-group         0 Jan  5 11:59 240485
         directory = """\
 11-02-17  02:00AM       <DIR>          docs
 11-02-17  02:12PM       <DIR>          images
+11-02-17  02:12PM       <DIR>          AM to PM
 11-02-17  03:33PM                 9276 logo.gif
 """
         expected = [
@@ -183,6 +184,13 @@ drwxr-xr-x   2 foo-user foo-group         0 Jan  5 11:59 240485
                     "ls": "11-02-17  02:12PM       <DIR>          images"
                 },
             },
+            {
+                "basic": {"is_dir": True, "name": "AM to PM"},
+                "details": {"modified": 1486822320.0, "type": 1},
+                "ftp": {
+                    "ls": "11-02-17  02:12PM       <DIR>          AM to PM"
+                },
+            },
             {
                 "basic": {"is_dir": False, "name": "logo.gif"},
                 "details": {"modified": 1486827180.0, "size": 9276, "type": 2},