]> git.phdru.name Git - m_lib.git/blob - m_lib/net/ftp/ftpparse.py
Build(setup.py): Fix trove classifiers
[m_lib.git] / m_lib / net / ftp / ftpparse.py
1 #! /usr/bin/env python
2 """Parse output of FTP LIST command.
3    Pure python implementation.
4
5    See http://cr.yp.to/ftpparse.html, http://effbot.org/downloads#ftpparse,
6    http://c0re.23.nu/c0de/ftpparsemodule/ and http://www.ocgy.ubc.ca/~tang/treeftp
7
8 Currently covered formats:
9    UNIX ls, with or without gid;
10    Windoze FTP Servers;
11    VMS;
12    WFTPD;
13    NetPresenz (Mac);
14    NetWare;
15    DOS;
16
17 Definitely not covered:
18    EPLF (show me an FTP server first...);
19    Long VMS filenames, with information split across two lines;
20    NCSA Telnet FTP server. Has LIST = NLST (and bad NLST for directories).
21 """
22
23
24 from __future__ import print_function
25 try:
26    from mx import DateTime
27 except ImportError:
28    _parse_datetime = False
29 else:
30    _parse_datetime = True
31
32
33 class parse_error(Exception): pass
34
35
36 class entry:
37    def __init__(self, name=None, perm=None, nlink=None, user=None, group=None, \
38          size=None, mtime=None, links_to=None, file_type=None):
39
40       if mtime:
41          mtime = ' '.join(mtime)
42          if _parse_datetime:
43             try:
44                mtime = DateTime.DateTimeFrom(mtime)
45             except DateTime.Error:
46                pass
47
48       self.name = name
49       self.perm = perm
50       self.nlink = nlink
51       self.user = user
52       self.group = group
53       self.size = size
54       self.mtime = mtime
55       self.links_to = links_to
56       self.file_type = file_type # f - regular file, d - directory, l - symlink
57
58
59    def __str__(self):
60       return """<%s: name=%s, perm=%s, nlink=%s, user=%s, group=%s, size=%s, mtime=%s, links-to=%s, type=%s at 0x%x>""" % (
61       self.__class__.__name__, self.name, self.perm, self.nlink,
62          self.user, self.group, self.size, self.mtime,
63          self.links_to, self.file_type, id(self))
64
65
66 def _parse_unix(line, parts):
67    name = None
68    perm = None
69    nlink = None
70    user = None
71    group = None
72    size = None
73    mtime = None
74    links_to = None
75    file_type = None
76
77    perm = parts[0]
78
79    if parts[1][0] == '[': # NetWare
80       if perm[0] == 'd':
81          file_type = 'd'
82       elif perm[0] == '-':
83          file_type = 'f'
84       else:
85          return None
86       perm = perm + ' ' + parts[1]
87       user = parts[2]
88       size = parts[3]
89       mtime = parts[4:7]
90
91       parts = line.split(None, 7) # resplit the original line...
92       name = parts[7]             # ...in case the filename contains whitespaces
93
94    elif parts[1] == "folder": # NetPresenz for the Mac
95       file_type = 'd'
96       size = parts[2]
97       mtime = parts[3:6]
98
99       parts = line.split(None, 6)
100       name = parts[6]
101
102    elif parts[0][0] == 'l': # symlink
103       file_type = 'l'
104       nlink = int(parts[1])
105       user = parts[2]
106       group = parts[3]
107       size = parts[4]
108       mtime = parts[5:8]
109
110       parts = line.split(None, 8)
111       link = parts[8]
112       parts = link.split(" -> ")
113       name = parts[0]
114       links_to = parts[1]
115
116    elif len(parts) > 8:
117       if perm[0] == 'd':
118          file_type = 'd'
119       elif perm[0] == '-':
120          file_type = 'f'
121       else:
122          return None
123       nlink = int(parts[1])
124       user = parts[2]
125       group = parts[3]
126       size = parts[4]
127       mtime = parts[5:8]
128
129       parts = line.split(None, 7)
130       name = parts[7]
131       parts = name.split(' ')[1:]
132       name = ' '.join(parts)
133
134    else:
135       if parts[2].isdigit(): # NetPresenz
136          file_type = 'f'
137          size = parts[3]
138          mtime = parts[4:7]
139       else:
140          if perm[0] == 'd':
141             file_type = 'd'
142          elif perm[0] == '-':
143             file_type = 'f'
144          else:
145             return None
146          nlink = int(parts[1])
147          user = parts[2]
148          size = parts[3]
149          mtime = parts[4:7]
150
151       parts = line.split(None, 7)
152       name = parts[7]
153
154    return entry(name, perm, nlink, user, group, size, mtime, links_to, file_type)
155
156
157 def _parse_vms(parts):
158    name = parts[0]
159    perm = parts[5]
160    nlink = parts[1]
161    user = parts[4][1:-1]
162    group = None
163    size = None
164    mtime = parts[2:4]
165    links_to = None
166    file_type = None
167
168    if ',' in user:
169       parts = user.split(',')
170       user = parts[0]
171       group = parts[1]
172
173    return entry(name, perm, nlink, user, group, size, mtime, links_to, file_type)
174
175
176 def _parse_dos(parts):
177    name = parts[3]
178    perm = None
179    nlink = None
180    user = None
181    group = None
182    size = None
183    links_to = None
184
185    if _parse_datetime:
186       # Change %m-%d-%y format to %y-%m-%d
187       date = parts[0]
188       date_parts = date.split('-')
189       date = "%s-%s-%s" % (date_parts[2], date_parts[0], date_parts[1])
190       time = parts[1]
191       mtime = [date, time]
192    else:
193       mtime = parts[0:2]
194
195    if parts[2] == "<DIR>":
196       file_type = 'd'
197    else:
198       file_type = 'f'
199       size = int(parts[2])
200
201    return entry(name, perm, nlink, user, group, size, mtime, links_to, file_type)
202
203
204 def ftpparse(line):
205    parts = line.split()
206    c = parts[0][0]
207
208    if c == '+': # EPLF format is not supported
209       return None
210
211    if c in ('b', 'c', 'd', 'l', 'p', 's', '-'): # UNIX-like
212       return _parse_unix(line, parts)
213
214    if ';' in parts[0]: # VMS
215       return _parse_vms(parts)
216
217    if '0' <= c <= '9': # DOS
218       return _parse_dos(parts)
219
220
221    #Some useless lines, safely ignored:
222    #"Total of 11 Files, 10966 Blocks." (VMS)
223    #"total 14786" (UNIX)
224    #"DISK$ANONFTP:[ANONYMOUS]" (VMS)
225    #"Directory DISK$PCSA:[ANONYM]" (VMS)
226
227    return None
228
229
230 def test():
231    #UNIX-style listing, without inum and without blocks
232    print(ftpparse("-rw-r--r--   1 root     other        531 Jan 29 03:26 README"))
233    print(ftpparse("dr-xr-xr-x   2 root     other        512 Apr  8  1994 etc"))
234    print(ftpparse("dr-xr-xr-x   2 root     512 Apr  8  1994 etc"))
235    print(ftpparse("lrwxrwxrwx   1 root     other          7 Jan 25 00:17 bin -> usr/bin"))
236    #FTP servers for Windoze:
237    print(ftpparse("----------   1 owner    group         1803128 Jul 10 10:18 ls-lR.Z"))
238    print(ftpparse("d---------   1 owner    group               0 May  9 19:45 Softlib"))
239    #WFTPD for DOS:
240    print(ftpparse("-rwxrwxrwx   1 noone    nogroup      322 Aug 19  1996 message.ftp"))
241    #NetWare:
242    print(ftpparse("d [R----F--] supervisor            512       Jan 16 18:53    login"))
243    print(ftpparse("- [R----F--] rhesus             214059       Oct 20 15:27    cx.exe"))
244    #NetPresenz for the Mac:
245    print(ftpparse("-------r--         326  1391972  1392298 Nov 22  1995 MegaPhone.sit"))
246    print(ftpparse("drwxrwxr-x               folder        2 May 10  1996 network"))
247
248    #MultiNet (some spaces removed from examples)
249    print(ftpparse("00README.TXT;1      2 30-DEC-1996 17:44 [SYSTEM] (RWED,RWED,RE,RE)"))
250    print(ftpparse("CORE.DIR;1          1  8-SEP-1996 16:09 [SYSTEM] (RWE,RWE,RE,RE)"))
251    #non-MutliNet VMS:
252    print(ftpparse("CII-MANUAL.TEX;1  213/216  29-JAN-1996 03:33:12  [ANONYMOU,ANONYMOUS]   (RWED,RWED,,)"))
253
254    #DOS format
255    print(ftpparse("04-27-00  09:09PM       <DIR>          licensed"))
256    print(ftpparse("07-18-00  10:16AM       <DIR>          pub"))
257    print(ftpparse("04-14-00  03:47PM                  589 readme.htm"))
258
259    print(ftpparse("-rw-r--r--   1 root     other        531 Jan 29 03:26 READ ME"))
260    print(ftpparse("-rw-r--r--   1 root     other        531 Jan 29 03:26  DO NOT READ ME "))
261
262 if __name__ == "__main__":
263    test()