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