Skip to content

Commit 60b0a63

Browse files
authored
Merge pull request #473 from PyFilesystem/fix-451
Fix FTP not properly parsing lines with sticky/SUID/SGID permissions
2 parents 4efe083 + 7f76454 commit 60b0a63

File tree

3 files changed

+129
-32
lines changed

3 files changed

+129
-32
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2525
test suites.
2626
- `FSTestCases` now builds the large data required for `upload` and `download` tests only
2727
once in order to reduce the total testing time.
28-
- `MemoryFS.move` and `MemoryFS.movedir` will now avoid copying data.
28+
- `MemoryFS.move` and `MemoryFS.movedir` will now avoid copying data.
2929
Closes [#452](https://github.com/PyFilesystem/pyfilesystem2/issues/452).
3030

3131
### Fixed
@@ -36,6 +36,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
3636
- Avoid creating a new connection on every call of `FTPFS.upload`. Closes [#455](https://github.com/PyFilesystem/pyfilesystem2/issues/455).
3737
- `WrapReadOnly.removetree` not raising a `ResourceReadOnly` when called. Closes [#468](https://github.com/PyFilesystem/pyfilesystem2/issues/468).
3838
- `WrapCachedDir.isdir` and `WrapCachedDir.isfile` raising a `ResourceNotFound` error on non-existing path ([#470](https://github.com/PyFilesystem/pyfilesystem2/pull/470)).
39+
- `FTPFS` not listing certain entries with sticky/SUID/SGID permissions set by Linux server ([#473](https://github.com/PyFilesystem/pyfilesystem2/pull/473)).
40+
Closes [#451](https://github.com/PyFilesystem/pyfilesystem2/issues/451).
3941

4042

4143
## [2.4.12] - 2021-01-14

fs/_ftp_parse.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
RE_LINUX = re.compile(
2020
r"""
2121
^
22-
([ldrwx-]{10})
22+
([-dlpscbD])
23+
([r-][w-][xsS-][r-][w-][xsS-][r-][w-][xtT-][\.\+]?)
2324
\s+?
2425
(\d+)
2526
\s+?
@@ -110,14 +111,14 @@ def _decode_linux_time(mtime):
110111

111112

112113
def decode_linux(line, match):
113-
perms, links, uid, gid, size, mtime, name = match.groups()
114-
is_link = perms.startswith("l")
115-
is_dir = perms.startswith("d") or is_link
114+
ty, perms, links, uid, gid, size, mtime, name = match.groups()
115+
is_link = ty == "l"
116+
is_dir = ty == "d" or is_link
116117
if is_link:
117118
name, _, _link_name = name.partition("->")
118119
name = name.strip()
119120
_link_name = _link_name.strip()
120-
permissions = Permissions.parse(perms[1:])
121+
permissions = Permissions.parse(perms)
121122

122123
mtime_epoch = _decode_linux_time(mtime)
123124

tests/test_ftp_parse.py

Lines changed: 120 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import unicode_literals
22

3+
import textwrap
34
import time
45
import unittest
56

@@ -33,23 +34,25 @@ def test_parse_time(self, mock_localtime):
3334
self.assertEqual(ftp_parse._parse_time("notadate", formats=["%b %d %Y"]), None)
3435

3536
def test_parse(self):
36-
self.assertEqual(ftp_parse.parse([""]), [])
37+
self.assertListEqual(ftp_parse.parse([""]), [])
3738

3839
def test_parse_line(self):
3940
self.assertIs(ftp_parse.parse_line("not a dir"), None)
4041

4142
@mock.patch("time.localtime")
4243
def test_decode_linux(self, mock_localtime):
4344
mock_localtime.return_value = time2017
44-
directory = """\
45-
lrwxrwxrwx 1 0 0 19 Jan 18 2006 debian -> ./pub/mirror/debian
46-
drwxr-xr-x 10 0 0 4096 Aug 03 09:21 debian-archive
47-
lrwxrwxrwx 1 0 0 27 Nov 30 2015 debian-backports -> pub/mirror/debian-backports
48-
drwxr-xr-x 12 0 0 4096 Sep 29 13:13 pub
49-
-rw-r--r-- 1 0 0 26 Mar 04 2010 robots.txt
50-
drwxr-xr-x 8 foo bar 4096 Oct 4 09:05 test
51-
drwxr-xr-x 2 foo-user foo-group 0 Jan 5 11:59 240485
52-
"""
45+
directory = textwrap.dedent(
46+
"""
47+
lrwxrwxrwx 1 0 0 19 Jan 18 2006 debian -> ./pub/mirror/debian
48+
drwxr-xr-x 10 0 0 4096 Aug 03 09:21 debian-archive
49+
lrwxrwxrwx 1 0 0 27 Nov 30 2015 debian-backports -> pub/mirror/debian-backports
50+
drwxr-xr-x 12 0 0 4096 Sep 29 13:13 pub
51+
-rw-r--r-- 1 0 0 26 Mar 04 2010 robots.txt
52+
drwxr-xr-x 8 foo bar 4096 Oct 4 09:05 test
53+
drwxr-xr-x 2 foo-user foo-group 0 Jan 5 11:59 240485
54+
"""
55+
)
5356

5457
expected = [
5558
{
@@ -158,25 +161,27 @@ def test_decode_linux(self, mock_localtime):
158161
},
159162
]
160163

161-
parsed = ftp_parse.parse(directory.splitlines())
162-
self.assertEqual(parsed, expected)
164+
parsed = ftp_parse.parse(directory.strip().splitlines())
165+
self.assertListEqual(parsed, expected)
163166

164167
@mock.patch("time.localtime")
165168
def test_decode_windowsnt(self, mock_localtime):
166169
mock_localtime.return_value = time2017
167-
directory = """\
168-
unparsable line
169-
11-02-17 02:00AM <DIR> docs
170-
11-02-17 02:12PM <DIR> images
171-
11-02-17 02:12PM <DIR> AM to PM
172-
11-02-17 03:33PM 9276 logo.gif
173-
05-11-20 22:11 <DIR> src
174-
11-02-17 01:23 1 12
175-
11-02-17 4:54 0 icon.bmp
176-
11-02-17 4:54AM 0 icon.gif
177-
11-02-17 4:54PM 0 icon.png
178-
11-02-17 16:54 0 icon.jpg
179-
"""
170+
directory = textwrap.dedent(
171+
"""
172+
unparsable line
173+
11-02-17 02:00AM <DIR> docs
174+
11-02-17 02:12PM <DIR> images
175+
11-02-17 02:12PM <DIR> AM to PM
176+
11-02-17 03:33PM 9276 logo.gif
177+
05-11-20 22:11 <DIR> src
178+
11-02-17 01:23 1 12
179+
11-02-17 4:54 0 icon.bmp
180+
11-02-17 4:54AM 0 icon.gif
181+
11-02-17 4:54PM 0 icon.png
182+
11-02-17 16:54 0 icon.jpg
183+
"""
184+
)
180185
expected = [
181186
{
182187
"basic": {"is_dir": True, "name": "docs"},
@@ -230,5 +235,94 @@ def test_decode_windowsnt(self, mock_localtime):
230235
},
231236
]
232237

233-
parsed = ftp_parse.parse(directory.splitlines())
238+
parsed = ftp_parse.parse(directory.strip().splitlines())
234239
self.assertEqual(parsed, expected)
240+
241+
@mock.patch("time.localtime")
242+
def test_decode_linux_suid(self, mock_localtime):
243+
# reported in #451
244+
mock_localtime.return_value = time2017
245+
directory = textwrap.dedent(
246+
"""
247+
drwxr-sr-x 66 ftp ftp 8192 Mar 16 17:54 pub
248+
-rw-r--r-- 1 ftp ftp 25 Mar 18 19:34 robots.txt
249+
"""
250+
)
251+
expected = [
252+
{
253+
"access": {
254+
"group": "ftp",
255+
"permissions": [
256+
"g_r",
257+
"g_s",
258+
"o_r",
259+
"o_x",
260+
"u_r",
261+
"u_w",
262+
"u_x",
263+
],
264+
"user": "ftp",
265+
},
266+
"basic": {"is_dir": True, "name": "pub"},
267+
"details": {"modified": 1489686840.0, "size": 8192, "type": 1},
268+
"ftp": {
269+
"ls": "drwxr-sr-x 66 ftp ftp 8192 Mar 16 17:54 pub"
270+
},
271+
},
272+
{
273+
"access": {
274+
"group": "ftp",
275+
"permissions": [
276+
"g_r",
277+
"o_r",
278+
"u_r",
279+
"u_w",
280+
],
281+
"user": "ftp",
282+
},
283+
"basic": {"is_dir": False, "name": "robots.txt"},
284+
"details": {"modified": 1489865640.0, "size": 25, "type": 2},
285+
"ftp": {
286+
"ls": "-rw-r--r-- 1 ftp ftp 25 Mar 18 19:34 robots.txt"
287+
},
288+
},
289+
]
290+
291+
parsed = ftp_parse.parse(directory.strip().splitlines())
292+
self.assertListEqual(parsed, expected)
293+
294+
@mock.patch("time.localtime")
295+
def test_decode_linux_sticky(self, mock_localtime):
296+
# reported in #451
297+
mock_localtime.return_value = time2017
298+
directory = textwrap.dedent(
299+
"""
300+
drwxr-xr-t 66 ftp ftp 8192 Mar 16 17:54 pub
301+
"""
302+
)
303+
expected = [
304+
{
305+
"access": {
306+
"group": "ftp",
307+
"permissions": [
308+
"g_r",
309+
"g_x",
310+
"o_r",
311+
"o_t",
312+
"u_r",
313+
"u_w",
314+
"u_x",
315+
],
316+
"user": "ftp",
317+
},
318+
"basic": {"is_dir": True, "name": "pub"},
319+
"details": {"modified": 1489686840.0, "size": 8192, "type": 1},
320+
"ftp": {
321+
"ls": "drwxr-xr-t 66 ftp ftp 8192 Mar 16 17:54 pub"
322+
},
323+
},
324+
]
325+
326+
self.maxDiff = None
327+
parsed = ftp_parse.parse(directory.strip().splitlines())
328+
self.assertListEqual(parsed, expected)

0 commit comments

Comments
 (0)