0001import os
0002import shutil
0003from paste.urlparser import StaticURLParser
0004from paste.fileapp import FileApp
0005from paste import httpexceptions
0006from paste import request
0007import cgi
0008import urllib
0009
0010class LilDAV(object):
0011
0012    """
0013    A DAV-like WSGI app that only knows GET, PUT, DELETE, MKCOL.
0014    PROPLIST is also planned, but probably nothing else.
0015    """
0016
0017    def __init__(self, directory, root_directory=None):
0018        if os.path.sep != '/':
0019            directory = directory.replace(os.path.sep, '/')
0020        self.directory = directory
0021        if root_directory is not None:
0022            root_directory = os.path.normpath(root_directory)
0023        else:
0024            root_directory = directory
0025        self.root_directory = root_directory
0026        if os.path.sep != '/':
0027            directory = directory.replace('/', os.path.sep)
0028            self.root_directory = self.root_directory.replace('/', os.path.sep)
0029
0030    def __call__(self, environ, start_response):
0031        path_info = environ.get('PATH_INFO', '')
0032        if not path_info:
0033            return self.add_slash(environ, start_response)
0034        filename = request.path_info_pop(environ)
0035        full = os.path.normpath(os.path.join(self.directory, filename))
0036        if os.path.sep != '/':
0037            full = full.replace('/', os.path.sep)
0038        if self.root_directory is not None and not full.startswith(self.root_directory):
0039            # Out of bounds
0040            exc = httpexceptions.HTTPNotFound('The request path is invalid')
0041            return exc(environ, start_response)
0042        if os.path.isdir(full) and environ['PATH_INFO']:
0043            subapp = self.__class__(
0044                full, root_directory=self.root_directory)
0045            return subapp(environ, start_response)
0046        fa = self.make_app(full)
0047        return fa(environ, start_response)
0048
0049    def make_app(self, filename):
0050        return PuttableFileApp(filename)
0051
0052    def add_slash(self, environ, start_response):
0053        """
0054        This happens when you try to get to a directory
0055        without a trailing /
0056        """
0057        url = request.construct_url(environ, with_query_string=False)
0058        url += '/'
0059        if environ.get('QUERY_STRING'):
0060            url += '?' + environ['QUERY_STRING']
0061        exc = httpexceptions.HTTPMovedPermanently(
0062            'The resource has moved to %s - you should be redirected '
0063            'automatically.''' % url,
0064            headers=[('location', url)])
0065        return exc.wsgi_application(environ, start_response)
0066
0067    def __repr__(self):
0068        return '<%s %r>' % (self.__class__.__name__, self.directory)
0069
0070class PuttableFileApp(FileApp):
0071    def __call__(self, environ, start_response):
0072        method = environ['REQUEST_METHOD']
0073        if method == 'PUT':
0074            return self.put(environ, start_response)
0075        elif method == 'DELETE':
0076            return self.delete(environ, start_response)
0077        elif method == 'MKCOL':
0078            return self.mkcol(environ, start_response)
0079        elif method == 'GET':
0080            if os.path.isdir(self.filename):
0081                return self.directory_index(environ, start_response)
0082            else:
0083                return FileApp.__call__(self, environ, start_response)
0084        else:
0085            exc = httpexceptions.HTTPMethodNotAllowed()
0086            return exc(environ, start_response)
0087
0088    def directory_index(self, environ, start_response):
0089        filenames = os.listdir(self.filename)
0090        filenames.sort()
0091        parts = [
0092            '<html><head><title>Directory Listing</title></head>\n'
0093            '<body><h1>Directory Listing</h1>\n'
0094            '<ul>\n']
0095        for filename in filenames:
0096            parts.append(
0097                '<li><a href="%s">%s</a></li>\n'
0098                % (cgi.escape(urllib.quote(filename), 1),
0099                   cgi.escape(filename, 1)))
0100        parts.append(
0101            '</ul></body></html>')
0102        body = ''.join(parts)
0103        start_response(
0104            '200 OK', [('Content-type', 'text/html'),
0105                       ('Content-length', str(len(body)))])
0106        return [body]
0107
0108    def update(self, force=False):
0109        try:
0110            return FileApp.update(self, force=force)
0111        except OSError:
0112            pass
0113
0114    def put(self, environ, start_response):
0115        exists = os.path.exists(self.filename)
0116        f = open(self.filename, 'wb')
0117        size = int(environ['CONTENT_LENGTH'])
0118        input = environ['wsgi.input']
0119        while size > 0:
0120            c = input.read(max(size, 4096))
0121            size -= len(c)
0122            f.write(c)
0123        f.close()
0124        if exists:
0125            start_response('204 No Content', [])
0126        else:
0127            start_response('201 Created', [])
0128        return ['']
0129
0130    def delete(self, environ, start_response):
0131        if not os.path.exists(self.filename):
0132            exc = httpexceptions.HTTPPreconditionFailed('File %s does not exists' % self.filename)
0133            return exc(environ, start_response)
0134        if os.path.isdir(self.filename):
0135            shutil.rmtree(self.filename)
0136        else:
0137            os.unlink(self.filename)
0138        start_response('204 No Content', [])
0139        return ['']
0140
0141    def mkcol(self, environ, start_response):
0142        if os.path.exists(self.filename):
0143            exc = httpexceptions.HTTPPreconditionFailed('%s already exists' % self.filename)
0144            return exc(environ, start_response)
0145        os.mkdir(self.filename)
0146        start_response('201 Created', [])
0147        return ['']