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
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 ['']