• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""
2A helper module that can work with paths
3that can refer to data inside a zipfile
4
5XXX: Need to determine if isdir("zipfile.zip")
6should return True or False. Currently returns
7True, but that might do the wrong thing with
8data-files that are zipfiles.
9"""
10import os as _os
11import zipfile as _zipfile
12import errno as _errno
13import time as _time
14import sys as _sys
15import stat as _stat
16
17_DFLT_DIR_MODE = (
18      _stat.S_IFDIR
19    | _stat.S_IXOTH
20    | _stat.S_IXGRP
21    | _stat.S_IXUSR
22    | _stat.S_IROTH
23    | _stat.S_IRGRP
24    | _stat.S_IRUSR)
25
26_DFLT_FILE_MODE = (
27      _stat.S_IFREG
28    | _stat.S_IROTH
29    | _stat.S_IRGRP
30    | _stat.S_IRUSR)
31
32
33if _sys.version_info[0] == 2:
34    from  StringIO import StringIO as _BaseStringIO
35    from  StringIO import StringIO as _BaseBytesIO
36
37    class _StringIO (_BaseStringIO):
38        def __enter__(self):
39            return self
40
41        def __exit__(self, exc_type, exc_value, traceback):
42            self.close()
43            return False
44
45    class _BytesIO (_BaseBytesIO):
46        def __enter__(self):
47            return self
48
49        def __exit__(self, exc_type, exc_value, traceback):
50            self.close()
51            return False
52
53else:
54    from io import StringIO as _StringIO
55    from io import BytesIO as _BytesIO
56
57
58
59
60def _locate(path):
61    full_path = path
62    if _os.path.exists(path):
63        return path, None
64
65    else:
66        rest = []
67        root = _os.path.splitdrive(path)
68        while path and path != root:
69            path, bn = _os.path.split(path)
70            rest.append(bn)
71            if _os.path.exists(path):
72                break
73
74        if path == root:
75            raise IOError(
76                _errno.ENOENT, full_path,
77                "No such file or directory")
78
79        if not _os.path.isfile(path):
80            raise IOError(
81                _errno.ENOENT, full_path,
82                "No such file or directory")
83
84        rest.reverse()
85        return path, '/'.join(rest).strip('/')
86
87_open = open
88def open(path, mode='r'):
89    if 'w' in mode or 'a' in mode:
90        raise IOError(
91            _errno.EINVAL, path, "Write access not supported")
92    elif 'r+' in mode:
93        raise IOError(
94            _errno.EINVAL, path, "Write access not supported")
95
96    full_path = path
97    path, rest = _locate(path)
98    if not rest:
99        return _open(path, mode)
100
101    else:
102        try:
103            zf = _zipfile.ZipFile(path, 'r')
104
105        except _zipfile.error:
106            raise IOError(
107                _errno.ENOENT, full_path,
108                "No such file or directory")
109
110        try:
111            data = zf.read(rest)
112        except (_zipfile.error, KeyError):
113            zf.close()
114            raise IOError(
115                _errno.ENOENT, full_path,
116                "No such file or directory")
117        zf.close()
118
119        if mode == 'rb':
120            return _BytesIO(data)
121
122        else:
123            if _sys.version_info[0] == 3:
124                data = data.decode('ascii')
125
126            return _StringIO(data)
127
128def listdir(path):
129    full_path = path
130    path, rest = _locate(path)
131    if not rest and not _os.path.isfile(path):
132        return _os.listdir(path)
133
134    else:
135        try:
136            zf = _zipfile.ZipFile(path, 'r')
137
138        except _zipfile.error:
139            raise IOError(
140                _errno.ENOENT, full_path,
141                "No such file or directory")
142
143        result = set()
144        seen = False
145        try:
146            for nm in zf.namelist():
147                if rest is None:
148                    seen = True
149                    value = nm.split('/')[0]
150                    if value:
151                        result.add(value)
152
153                elif nm.startswith(rest):
154                    if nm == rest:
155                        seen = True
156                        value = ''
157                        pass
158                    elif nm[len(rest)] == '/':
159                        seen = True
160                        value = nm[len(rest)+1:].split('/')[0]
161                    else:
162                        value = None
163
164                    if value:
165                        result.add(value)
166        except _zipfile.error:
167            zf.close()
168            raise IOError(
169                _errno.ENOENT, full_path,
170                "No such file or directory")
171
172        zf.close()
173
174        if not seen:
175            raise IOError(
176                _errno.ENOENT, full_path,
177                "No such file or directory")
178
179        return list(result)
180
181def isfile(path):
182    full_path = path
183    path, rest = _locate(path)
184    if not rest:
185        ok =  _os.path.isfile(path)
186        if ok:
187            try:
188                zf = _zipfile.ZipFile(path, 'r')
189                return False
190            except (_zipfile.error, IOError):
191                return True
192        return False
193
194    zf = None
195    try:
196        zf = _zipfile.ZipFile(path, 'r')
197        info = zf.getinfo(rest)
198        zf.close()
199        return True
200    except (KeyError, _zipfile.error):
201        if zf is not None:
202            zf.close()
203
204        # Check if this is a directory
205        try:
206            info = zf.getinfo(rest + '/')
207        except KeyError:
208            pass
209        else:
210            return False
211
212        rest = rest + '/'
213        for nm in zf.namelist():
214            if nm.startswith(rest):
215                # Directory
216                return False
217
218        # No trace in zipfile
219        raise IOError(
220            _errno.ENOENT, full_path,
221            "No such file or directory")
222
223
224
225
226def isdir(path):
227    full_path = path
228    path, rest = _locate(path)
229    if not rest:
230        ok =  _os.path.isdir(path)
231        if not ok:
232            try:
233                zf = _zipfile.ZipFile(path, 'r')
234            except (_zipfile.error, IOError):
235                return False
236            return True
237        return True
238
239    zf = None
240    try:
241        try:
242            zf = _zipfile.ZipFile(path)
243        except _zipfile.error:
244            raise IOError(
245                _errno.ENOENT, full_path,
246                "No such file or directory")
247
248        try:
249            info = zf.getinfo(rest)
250        except KeyError:
251            pass
252        else:
253            # File found
254            return False
255
256        rest = rest + '/'
257        try:
258            info = zf.getinfo(rest)
259        except KeyError:
260            pass
261        else:
262            # Directory entry found
263            return True
264
265        for nm in zf.namelist():
266            if nm.startswith(rest):
267                return True
268
269        raise IOError(
270            _errno.ENOENT, full_path,
271            "No such file or directory")
272    finally:
273        if zf is not None:
274            zf.close()
275
276
277def islink(path):
278    full_path = path
279    path, rest = _locate(path)
280    if not rest:
281        return _os.path.islink(path)
282
283    try:
284        zf = _zipfile.ZipFile(path)
285    except _zipfile.error:
286        raise IOError(
287            _errno.ENOENT, full_path,
288            "No such file or directory")
289    try:
290
291
292        try:
293            info = zf.getinfo(rest)
294        except KeyError:
295            pass
296        else:
297            # File
298            return False
299
300        rest += '/'
301        try:
302            info = zf.getinfo(rest)
303        except KeyError:
304            pass
305        else:
306            # Directory
307            return False
308
309        for nm in zf.namelist():
310            if nm.startswith(rest):
311                # Directory without listing
312                return False
313
314        raise IOError(
315            _errno.ENOENT, full_path,
316            "No such file or directory")
317
318    finally:
319        zf.close()
320
321
322def readlink(path):
323    full_path = path
324    path, rest = _locate(path)
325    if rest:
326        # No symlinks inside zipfiles
327        raise OSError(
328            _errno.ENOENT, full_path,
329            "No such file or directory")
330
331    return _os.readlink(path)
332
333def getmode(path):
334    full_path = path
335    path, rest = _locate(path)
336    if not rest:
337        return _os.stat(path).st_mode
338
339    zf = None
340    try:
341        zf = _zipfile.ZipFile(path)
342        info = None
343
344        try:
345            info = zf.getinfo(rest)
346        except KeyError:
347            pass
348
349        if info is None:
350            try:
351                info = zf.getinfo(rest + '/')
352            except KeyError:
353                pass
354
355        if info is None:
356            rest = rest + '/'
357            for nm in zf.namelist():
358                if nm.startswith(rest):
359                    break
360            else:
361                raise IOError(
362                    _errno.ENOENT, full_path,
363                    "No such file or directory")
364
365            # Directory exists, but has no entry of its own.
366            return _DFLT_DIR_MODE
367
368        # The mode is stored without file-type in external_attr.
369        if (info.external_attr >> 16) != 0:
370            return _stat.S_IFREG | (info.external_attr >> 16)
371        else:
372            return _DFLT_FILE_MODE
373
374
375    except KeyError:
376        if zf is not None:
377            zf.close()
378        raise IOError(
379            _errno.ENOENT, full_path,
380            "No such file or directory")
381
382def getmtime(path):
383    full_path = path
384    path, rest = _locate(path)
385    if not rest:
386        return _os.path.getmtime(path)
387
388    zf = None
389    try:
390        zf = _zipfile.ZipFile(path)
391        info = None
392
393        try:
394            info = zf.getinfo(rest)
395        except KeyError:
396            pass
397
398        if info is None:
399            try:
400                info = zf.getinfo(rest + '/')
401            except KeyError:
402                pass
403
404        if info is None:
405            rest = rest + '/'
406            for nm in zf.namelist():
407                if nm.startswith(rest):
408                    break
409            else:
410                raise IOError(
411                    _errno.ENOENT, full_path,
412                    "No such file or directory")
413
414            # Directory exists, but has no entry of its
415            # own, fake mtime by using the timestamp of
416            # the zipfile itself.
417            return _os.path.getmtime(path)
418
419        return _time.mktime(info.date_time + (0, 0, -1))
420
421    except KeyError:
422        if zf is not None:
423            zf.close()
424        raise IOError(
425            _errno.ENOENT, full_path,
426            "No such file or directory")
427