~divmod-dev/divmod.org/trunk

« back to all changes in this revision

Viewing changes to Epsilon/epsilon/hotfixes/filepath_copyTo.py

  • Committer: Jean-Paul Calderone
  • Date: 2014-06-29 20:33:04 UTC
  • mfrom: (2749.1.1 remove-epsilon-1325289)
  • Revision ID: exarkun@twistedmatrix.com-20140629203304-gdkmbwl1suei4m97
mergeĀ lp:~exarkun/divmod.org/remove-epsilon-1325289

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- test-case-name: twisted.test.test_paths -*-
2
 
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
3
 
# See LICENSE for details.
4
 
 
5
 
from __future__ import generators
6
 
 
7
 
import os
8
 
import errno
9
 
import base64
10
 
import random
11
 
import sha
12
 
 
13
 
from os.path import isabs, exists, normpath, abspath, splitext
14
 
from os.path import basename, dirname
15
 
from os.path import join as joinpath
16
 
from os import sep as slash
17
 
from os import listdir, utime, stat
18
 
from os import remove
19
 
 
20
 
from stat import ST_MODE, ST_MTIME, ST_ATIME, ST_CTIME, ST_SIZE
21
 
 
22
 
from stat import S_ISREG, S_ISDIR, S_ISLNK
23
 
 
24
 
try:
25
 
    from os.path import islink
26
 
except ImportError:
27
 
    def islink(path):
28
 
        return False
29
 
 
30
 
try:
31
 
    from os import urandom as randomBytes
32
 
except ImportError:
33
 
    def randomBytes(n):
34
 
        randomData = [random.randrange(256) for n in xrange(n)]
35
 
        return ''.join(map(chr, randomData))
36
 
 
37
 
try:
38
 
    from base64 import urlsafe_b64encode as armor
39
 
except ImportError:
40
 
    def armor(s):
41
 
        return s.encode('hex')
42
 
 
43
 
class InsecurePath(Exception):
44
 
    pass
45
 
 
46
 
def _secureEnoughString():
47
 
    """
48
 
    Create a pseudorandom, 16-character string for use in secure filenames.
49
 
    """
50
 
    return armor(sha.new(randomBytes(64)).digest())[:16]
51
 
 
52
 
class FilePath:
53
 
    """I am a path on the filesystem that only permits 'downwards' access.
54
 
 
55
 
    Instantiate me with a pathname (for example,
56
 
    FilePath('/home/myuser/public_html')) and I will attempt to only provide
57
 
    access to files which reside inside that path.  I may be a path to a file,
58
 
    a directory, or a file which does not exist.
59
 
 
60
 
    The correct way to use me is to instantiate me, and then do ALL filesystem
61
 
    access through me.  In other words, do not import the 'os' module; if you
62
 
    need to open a file, call my 'open' method.  If you need to list a
63
 
    directory, call my 'path' method.
64
 
 
65
 
    Even if you pass me a relative path, I will convert that to an absolute
66
 
    path internally.
67
 
 
68
 
    @type alwaysCreate: C{bool}
69
 
    @ivar alwaysCreate: When opening this file, only succeed if the file does not
70
 
    already exist.
71
 
    """
72
 
 
73
 
    # __slots__ = 'path abs'.split()
74
 
 
75
 
    statinfo = None
76
 
 
77
 
    def __init__(self, path, alwaysCreate=False):
78
 
        self.path = abspath(path)
79
 
        self.alwaysCreate = alwaysCreate
80
 
 
81
 
    def __getstate__(self):
82
 
        d = self.__dict__.copy()
83
 
        if d.has_key('statinfo'):
84
 
            del d['statinfo']
85
 
        return d
86
 
 
87
 
    def child(self, path):
88
 
        norm = normpath(path)
89
 
        if slash in norm:
90
 
            raise InsecurePath("%r contains one or more directory separators" % (path,))
91
 
        newpath = abspath(joinpath(self.path, norm))
92
 
        if not newpath.startswith(self.path):
93
 
            raise InsecurePath("%r is not a child of %s" % (newpath, self.path))
94
 
        return self.clonePath(newpath)
95
 
 
96
 
    def preauthChild(self, path):
97
 
        """
98
 
        Use me if `path' might have slashes in it, but you know they're safe.
99
 
 
100
 
        (NOT slashes at the beginning. It still needs to be a _child_).
101
 
        """
102
 
        newpath = abspath(joinpath(self.path, normpath(path)))
103
 
        if not newpath.startswith(self.path):
104
 
            raise InsecurePath("%s is not a child of %s" % (newpath, self.path))
105
 
        return self.clonePath(newpath)
106
 
 
107
 
    def childSearchPreauth(self, *paths):
108
 
        """Return my first existing child with a name in 'paths'.
109
 
 
110
 
        paths is expected to be a list of *pre-secured* path fragments; in most
111
 
        cases this will be specified by a system administrator and not an
112
 
        arbitrary user.
113
 
 
114
 
        If no appropriately-named children exist, this will return None.
115
 
        """
116
 
        p = self.path
117
 
        for child in paths:
118
 
            jp = joinpath(p, child)
119
 
            if exists(jp):
120
 
                return self.clonePath(jp)
121
 
 
122
 
    def siblingExtensionSearch(self, *exts):
123
 
        """Attempt to return a path with my name, given multiple possible
124
 
        extensions.
125
 
 
126
 
        Each extension in exts will be tested and the first path which exists
127
 
        will be returned.  If no path exists, None will be returned.  If '' is
128
 
        in exts, then if the file referred to by this path exists, 'self' will
129
 
        be returned.
130
 
 
131
 
        The extension '*' has a magic meaning, which means "any path that
132
 
        begins with self.path+'.' is acceptable".
133
 
        """
134
 
        p = self.path
135
 
        for ext in exts:
136
 
            if not ext and self.exists():
137
 
                return self
138
 
            if ext == '*':
139
 
                basedot = basename(p)+'.'
140
 
                for fn in listdir(dirname(p)):
141
 
                    if fn.startswith(basedot):
142
 
                        return self.clonePath(joinpath(dirname(p), fn))
143
 
            p2 = p + ext
144
 
            if exists(p2):
145
 
                return self.clonePath(p2)
146
 
 
147
 
    def siblingExtension(self, ext):
148
 
        return self.clonePath(self.path+ext)
149
 
 
150
 
    def open(self, mode='r'):
151
 
        if self.alwaysCreate:
152
 
            assert 'a' not in mode, "Appending not supported when alwaysCreate == True"
153
 
            return self.create()
154
 
        return open(self.path, mode+'b')
155
 
 
156
 
    # stat methods below
157
 
 
158
 
    def restat(self, reraise=True):
159
 
        try:
160
 
            self.statinfo = stat(self.path)
161
 
        except OSError:
162
 
            self.statinfo = 0
163
 
            if reraise:
164
 
                raise
165
 
 
166
 
    def getsize(self):
167
 
        st = self.statinfo
168
 
        if not st:
169
 
            self.restat()
170
 
            st = self.statinfo
171
 
        return st[ST_SIZE]
172
 
 
173
 
    def getmtime(self):
174
 
        st = self.statinfo
175
 
        if not st:
176
 
            self.restat()
177
 
            st = self.statinfo
178
 
        return st[ST_MTIME]
179
 
 
180
 
    def getctime(self):
181
 
        st = self.statinfo
182
 
        if not st:
183
 
            self.restat()
184
 
            st = self.statinfo
185
 
        return st[ST_CTIME]
186
 
 
187
 
    def getatime(self):
188
 
        st = self.statinfo
189
 
        if not st:
190
 
            self.restat()
191
 
            st = self.statinfo
192
 
        return st[ST_ATIME]
193
 
 
194
 
    def exists(self):
195
 
        if self.statinfo:
196
 
            return True
197
 
        elif self.statinfo is None:
198
 
            self.restat(False)
199
 
            return self.exists()
200
 
        else:
201
 
            return False
202
 
 
203
 
    def isdir(self):
204
 
        st = self.statinfo
205
 
        if not st:
206
 
            self.restat(False)
207
 
            st = self.statinfo
208
 
            if not st:
209
 
                return False
210
 
        return S_ISDIR(st[ST_MODE])
211
 
 
212
 
    def isfile(self):
213
 
        st = self.statinfo
214
 
        if not st:
215
 
            self.restat(False)
216
 
            st = self.statinfo
217
 
            if not st:
218
 
                return False
219
 
        return S_ISREG(st[ST_MODE])
220
 
 
221
 
    def islink(self):
222
 
        st = self.statinfo
223
 
        if not st:
224
 
            self.restat(False)
225
 
            st = self.statinfo
226
 
            if not st:
227
 
                return False
228
 
        return S_ISLNK(st[ST_MODE])
229
 
 
230
 
    def isabs(self):
231
 
        return isabs(self.path)
232
 
 
233
 
    def listdir(self):
234
 
        return listdir(self.path)
235
 
 
236
 
    def splitext(self):
237
 
        return splitext(self.path)
238
 
 
239
 
    def __repr__(self):
240
 
        return 'FilePath(%r)' % self.path
241
 
 
242
 
    def touch(self):
243
 
        try:
244
 
            self.open('a').close()
245
 
        except IOError:
246
 
            pass
247
 
        utime(self.path, None)
248
 
 
249
 
    def remove(self):
250
 
        if self.isdir():
251
 
            for child in self.children():
252
 
                child.remove()
253
 
            os.rmdir(self.path)
254
 
        else:
255
 
            os.remove(self.path)
256
 
        self.restat(False)
257
 
 
258
 
    def makedirs(self):
259
 
        return os.makedirs(self.path)
260
 
 
261
 
    def globChildren(self, pattern):
262
 
        """
263
 
        Assuming I am representing a directory, return a list of
264
 
        FilePaths representing my children that match the given
265
 
        pattern.
266
 
        """
267
 
        import glob
268
 
        path = self.path[-1] == '/' and self.path + pattern or slash.join([self.path, pattern])
269
 
        return map(self.clonePath, glob.glob(path))
270
 
 
271
 
    def basename(self):
272
 
        return basename(self.path)
273
 
 
274
 
    def dirname(self):
275
 
        return dirname(self.path)
276
 
 
277
 
    def parent(self):
278
 
        return self.clonePath(self.dirname())
279
 
 
280
 
    def setContent(self, content, ext='.new'):
281
 
        sib = self.siblingExtension(ext)
282
 
        sib.open('w').write(content)
283
 
        os.rename(sib.path, self.path)
284
 
 
285
 
    def getContent(self):
286
 
        return self.open().read()
287
 
 
288
 
    # new in 2.2.0
289
 
 
290
 
    def __cmp__(self, other):
291
 
        if not isinstance(other, FilePath):
292
 
            return NotImplemented
293
 
        return cmp(self.path, other.path)
294
 
 
295
 
    def createDirectory(self):
296
 
        os.mkdir(self.path)
297
 
 
298
 
    def requireCreate(self, val=1):
299
 
        self.alwaysCreate = val
300
 
 
301
 
    def create(self):
302
 
        """Exclusively create a file, only if this file previously did not exist.
303
 
        """
304
 
        fdint = os.open(self.path, (os.O_EXCL |
305
 
                                    os.O_CREAT |
306
 
                                    os.O_RDWR))
307
 
 
308
 
        # XXX TODO: 'name' attribute of returned files is not mutable or
309
 
        # settable via fdopen, so this file is slighly less functional than the
310
 
        # one returned from 'open' by default.  send a patch to Python...
311
 
 
312
 
        return os.fdopen(fdint, 'w+b')
313
 
 
314
 
    def temporarySibling(self):
315
 
        """
316
 
        Create a path naming a temporary sibling of this path in a secure fashion.
317
 
        """
318
 
        sib = self.parent().child(_secureEnoughString() + self.basename())
319
 
        sib.requireCreate()
320
 
        return sib
321
 
 
322
 
    def children(self):
323
 
        return map(self.child, self.listdir())
324
 
 
325
 
    def walk(self):
326
 
        yield self
327
 
        if self.isdir():
328
 
            for c in self.children():
329
 
                for subc in c.walk():
330
 
                    yield subc
331
 
 
332
 
    _chunkSize = 2 ** 2 ** 2 ** 2
333
 
 
334
 
    def copyTo(self, destination):
335
 
        # XXX TODO: *thorough* audit and documentation of the exact desired
336
 
        # semantics of this code.  Right now the behavior of existent
337
 
        # destination symlinks is convenient, and quite possibly correct, but
338
 
        # its security properties need to be explained.
339
 
        if self.isdir():
340
 
            if not destination.exists():
341
 
                destination.createDirectory()
342
 
            for child in self.children():
343
 
                destChild = destination.child(child.basename())
344
 
                child.copyTo(destChild)
345
 
        elif self.isfile():
346
 
            writefile = destination.open('w')
347
 
            readfile = self.open()
348
 
            while 1:
349
 
                # XXX TODO: optionally use os.open, os.read and O_DIRECT and
350
 
                # use os.fstatvfs to determine chunk sizes and make
351
 
                # *****sure**** copy is page-atomic; the following is good
352
 
                # enough for 99.9% of everybody and won't take a week to audit
353
 
                # though.
354
 
                chunk = readfile.read(self._chunkSize)
355
 
                writefile.write(chunk)
356
 
                if len(chunk) < self._chunkSize:
357
 
                    break
358
 
            writefile.close()
359
 
            readfile.close()
360
 
        else:
361
 
            # If you see the following message because you want to copy
362
 
            # symlinks, fifos, block devices, character devices, or unix
363
 
            # sockets, please feel free to add support to do sensible things in
364
 
            # reaction to those types!
365
 
            raise NotImplementedError(
366
 
                "Only copying of files and directories supported")
367
 
 
368
 
    def moveTo(self, destination):
369
 
        try:
370
 
            os.rename(self.path, destination.path)
371
 
            self.restat(False)
372
 
        except OSError, ose:
373
 
            if ose.errno == errno.EXDEV:
374
 
                # man 2 rename, ubuntu linux 5.10 "breezy":
375
 
 
376
 
                #   oldpath and newpath are not on the same mounted filesystem.
377
 
                #   (Linux permits a filesystem to be mounted at multiple
378
 
                #   points, but rename(2) does not work across different mount
379
 
                #   points, even if the same filesystem is mounted on both.)
380
 
 
381
 
                # that means it's time to copy trees of directories!
382
 
                secsib = destination.secureSibling()
383
 
                self.copyTo(secsib) # slow
384
 
                secsib.moveTo(destination) # visible
385
 
 
386
 
                # done creating new stuff.  let's clean me up.
387
 
                mysecsib = self.secureSibling()
388
 
                self.moveTo(mysecsib) # visible
389
 
                mysecsib.remove() # slow
390
 
            else:
391
 
                raise
392
 
 
393
 
 
394
 
FilePath.clonePath = FilePath
395
 
 
396
 
 
397
 
def install():
398
 
    global FilePath
399
 
 
400
 
    from twisted.python import filepath
401
 
    filepath.FilePath.__dict__ = FilePath.__dict__
402
 
    filepath.FilePath.__dict__['__module__'] = 'twisted.python.filepath'
403
 
    FilePath = filepath.FilePath