From 8c0cd01151b214d79dbd7d4aac0f04262bdef0d4 Mon Sep 17 00:00:00 2001 From: C?dric Krier Date: Mon, 27 Feb 2012 13:06:38 +0100 Subject: [PATCH] Remove BufferingHTTPServer, reuse the header parser of BaseHTTPServer --- README | 4 +- doc/ARCHITECTURE | 69 +----- doc/Changes | 7 +- doc/TODO | 8 +- doc/interface_class | 16 +- doc/walker | 16 +- pywebdav/lib/AuthServer.py | 243 ++++++--------------- pywebdav/lib/BufferingHTTPServer.py | 89 -------- pywebdav/lib/WebDAVServer.py | 313 ++++++++++++++-------------- 9 files changed, 254 insertions(+), 511 deletions(-) delete mode 100644 pywebdav/lib/BufferingHTTPServer.py diff --git a/README b/README index cfcd9be..e210d7b 100644 --- a/README +++ b/README @@ -79,8 +79,8 @@ OPTIONAL -------- - MySQLdb (http://sourceforge.net/projects/mysql-python) - - Mysql server 4.0+ for Mysql authentication with - with read/write access to one database +- Mysql server 4.0+ for Mysql authentication with + with read/write access to one database NOTES diff --git a/doc/ARCHITECTURE b/doc/ARCHITECTURE index b683365..c6926fa 100644 --- a/doc/ARCHITECTURE +++ b/doc/ARCHITECTURE @@ -4,28 +4,18 @@ OVERVIEW Here is a little overview of the package: -A. In the DAV/ package: +A. In the pywebdav/lib/ package: - 1. BufferingHTTPServer - - This is the same as the normal BasicHTTPServer but instead of - directly sending each string over the network this implementation - caches it and sends it at once after finishing one request. - - This has the advantage that clients like cadaver don't break as - they want to peek at the next lines when encountering e.g. a header. - - 2. AuthHTTPServer - + 1. AuthHTTPServer This works on top of either the BasicHTTPServer or the BufferingHTTPServer and implements basic authentication. - 3. WebDAVServer + 2. WebDAVServer This server uses AuthHTTPServer for the base functionality. It also uses a dav interface class for interfacing with the actual data storage (e.g. a filesystem or a database). -B. In the PyDAVServer directory: +B. In the pywebdav/server/ directory: 1. server.py Main file for server. Serves as a start point for the WebDAV server @@ -47,51 +37,13 @@ B. In the PyDAVServer directory: PyWebDav configuration file. -Class Tree ----------- - -PyWebDAVServer.fileauth.DAVAuthHandler - - - | - | - | - -DAV.WebDAVServer.DAVRequestHandler - - - | - | - | - -DAV.AuthServer.BufferedAuthRequestHandler - - - | | - | | - | | - -DAV.BufferingHTTPServer DAV.AuthServer.AuthRequestHandler - DAV commands> - - Information ---------- This document describes the architecture of the python davserver. -The main programm is stored in DAV/WebDAVServer.py. It exports a class WebDAVServer -which is subclassed from AuthServer which again is subclassed from -BufferingHTTPServer. - -The BufferingHTTPServer class extends the BaseHTTPServer class by -storing all output in a buffer and sending it at once when the request -is finished. Otherwise clients like cadaver might break. +The main programm is stored in pywebdav/lib/WebDAVServer.py. It exports a class +WebDAVServer which is subclassed from AuthServer. The AuthServer class implements Basic Authentication in order to make connections somewhat more secure. @@ -99,7 +51,7 @@ connections somewhat more secure. For processing requests the WebDAVServer class needs some connection to the actual data on server. In contrast to a normal web server this data must not simply be stored on a filesystem but can also live in -databases and the like. +databases and the like. Thus the WebDAVServer class needs an interface to this data which is implemented via an interface class (in our @@ -124,10 +76,9 @@ following: You might use the existing class as skeleton and explanation of which methods are needed. -That should be basically all you need to do. Have a look at PyDAVServer/fileauth.py in order -to get an example how to subclass WebDAVServer. +That should be basically all you need to do. Have a look at +pywebdav/server/fileauth.py in order to get an example how to subclass +WebDAVServer. === * describe the methods which need to be implemented. - - diff --git a/doc/Changes b/doc/Changes index a830ef5..361df2b 100644 --- a/doc/Changes +++ b/doc/Changes @@ -1,11 +1,14 @@ -0.9.8 (Feb 2011) ----------------- +0.9.8 +----- Restructured. Moved DAV package to pywebdav.lib. All integrators must simply replace ''from DAV'' imports to ''from pywebdav.lib''. [Simon Pamies] +Remove BufferingHTTPServer, reuse the header parser of BaseHTTPServer. +[Cédric Krier] + Fix issue 44: Incomplete PROPFIND response [Sascha Silbe] diff --git a/doc/TODO b/doc/TODO index b36f8e2..f97d9be 100644 --- a/doc/TODO +++ b/doc/TODO @@ -2,10 +2,10 @@ GENERAL ------- - web page needs to get done: - - Download - - News - - TODO list - - Changes + - Download + - News + - TODO list + - Changes - Name - use a better solution than DAV/INI_Parse.py [Stephane Klein] diff --git a/doc/interface_class b/doc/interface_class index 74946b4..6810fc0 100644 --- a/doc/interface_class +++ b/doc/interface_class @@ -15,7 +15,7 @@ and change it. You actually have implement the following methods: -get_childs(self,uri) +get_childs(self, uri, filter=None) This method should return a list of all childs for the object specified by the given uri. @@ -29,10 +29,10 @@ get_props(self,uri,values=None,all=None,proplist=[]) about properties for the object specified with the given uri. The parameters are as follows: - values -- ?? cannot remember ;-) - all -- if set to 1 return all properties - proplist -- alternatively you can give get a list of - properties to return + values -- ?? cannot remember ;-) + all -- if set to 1 return all properties + proplist -- alternatively you can give get a list of + properties to return The result of this method should be a dictionary of the form @@ -50,7 +50,7 @@ get_data(self,uri) get_dav(self,uri,propname) - + This method will be called when the server needs access to a DAV property. In the example implementation it will simply delegate it to the corresponding _get_dav_ method. You maybe should @@ -58,7 +58,7 @@ get_dav(self,uri,propname) _get_dav_(uri) - + These methods will be called by get_dav() when the value of a DAV property is needed. The defined properties are: @@ -100,7 +100,7 @@ rmcol(self,uri) rm(self,uri) - + This is the same for single objects, the same as above applies. diff --git a/doc/walker b/doc/walker index cb33a27..a6264e7 100644 --- a/doc/walker +++ b/doc/walker @@ -3,7 +3,7 @@ Walker methods In the COPY, DELETE and MOVE methods we need to walk over a tree of resources and collections in order to copy, delete -or move them. +or move them. The difference between all these walks is only that we perform a different action on the resources we visit. Thus it might be @@ -28,17 +28,17 @@ perform on it. Here the iterative approach (in order to save memory): -dc=dataclass -queue=list=[start_uri] +dc = dataclass +queue = list = [start_uri] while len(queue): - element=queue[-1] - childs=dc.get_childs(element) + element = queue[-1] + childs = dc.get_childs(element) if childs: - list=list+childs + list = list + childs # update queue - del queue[-1] + del queue[-1] if childs: - queue=queue+childs + queue = queue + childs (first try..) diff --git a/pywebdav/lib/AuthServer.py b/pywebdav/lib/AuthServer.py index d46b07c..d4c26c5 100644 --- a/pywebdav/lib/AuthServer.py +++ b/pywebdav/lib/AuthServer.py @@ -1,219 +1,102 @@ -""" - Authenticating HTTP Server +"""Authenticating HTTP Server - This module builds on BaseHTTPServer and implements - basic authentication +This module builds on BaseHTTPServer and implements basic authentication """ -import os -import sys -import time -import socket -import string -import posixpath -import SocketServer -import BufferingHTTPServer -import BaseHTTPServer import base64 +import binascii +import BaseHTTPServer -from string import atoi,split - -from pywebdav.lib import VERSION, AUTHOR -AUTH_ERROR_MSG=""" - -401 Authorization Required - -

Authorization Required

-This server could not verify that you +DEFAULT_AUTH_ERROR_MESSAGE = """ + +%(code)s - %(message)s + + +

Authorization Required

+this server could not verify that you are authorized to access the document requested. Either you supplied the wrong credentials (e.g., bad password), or your browser doesn't understand how to supply -the credentials required.

-""" +the credentials required. +""" -class AuthRequestHandler: - """ - Simple handler that use buffering and can check for auth headers - In order to use it create a subclass of BufferedAuthRequestHandler - or BasicAuthRequestHandler depending on if you want to send - responses as block or as stream. +def _quote_html(html): + return html.replace("&", "&").replace("<", "<").replace(">", ">") - In your subclass you have to define the method get_userinfo(user,pw) + +class AuthRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + """ + Simple handler that can check for auth headers + + In your subclass you have to define the method get_userinfo(user, password) which should return 1 or None depending on whether the password was ok or not. None means that the user is not authorized. """ # False means no authentiation - DO_AUTH=1 - - AUTH_ERROR_MSG=""" - - 401 Authorization Required - -

Authorization Required

- This server could not verify that you - are authorized to access the document - requested. Either you supplied the wrong - credentials (e.g., bad password), or your - browser doesn't understand how to supply - the credentials required.

- """ - - server_version = "AuthHTTP/" + VERSION - - def _log(self, message): - pass - - def handle(self): - """ - Special handle method with buffering and authentication - """ - - self.raw_requestline = self.rfile.readline() - self.request_version = version = "HTTP/0.9" # Default - requestline = self.raw_requestline - - # needed by send_error - self.command = requestline - self.headers = {} - - if requestline[-2:] == '\r\n': - requestline = requestline[:-2] - elif requestline[-1:] == '\n': - requestline = requestline[:-1] - - self.requestline = requestline - words = string.split(requestline) - if len(words) == 3: - [command, path, version] = words - if version[:5] != 'HTTP/': - self.send_error(400, "Bad request version (%s)" % `version`) - return - elif len(words) == 2: - [command, path] = words - if command != 'GET': - self.send_error(400, - "Bad HTTP/0.9 request type (%s)" % `command`) - return - else: - self.send_error(400, "Bad request syntax (%s)" % `requestline`) - return - - self.command, self.path, self.request_version = command, path, version - self.headers = self.MessageClass(self.rfile, 0) - - # test authentification - if self.DO_AUTH: - try: - a=self.headers["Authorization"] - m,up=string.split(a) - up2=base64.decodestring(up) - user,pw=string.split(up2,":") - - # Check if the given user can access - if not self.get_userinfo(user,pw,command): - self.send_autherror(401,"Authorization Required"); return - except: - self.send_autherror(401,"Authorization Required") - return - - # check for methods starting with do_ - mname = 'do_' + command - if not hasattr(self, mname): - self.send_error(501, "Unsupported method (%s)" % `command`) - return - - method = getattr(self, mname) - method() - - self._flush() - - def send_response(self,code, message=None): - """Override send_response to use the correct http version - in the response.""" - - if message is None: - if self.responses.has_key(code): - message = self.responses[code][0] - else: - message = '' - - if self.request_version != 'HTTP/0.9': - self._append("%s %s %s\r\n" % - (self.request_version, str(code), message)) - - self.send_header('Server', self.version_string()) - self.send_header('Date', self.date_time_string()) - self.send_header('Connection', 'close') + DO_AUTH = 1 - def send_head(self): - """Common code for GET and HEAD commands. + def parse_request(self): + if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self): + return False - This sends the response code and MIME headers. - - Return value is either a file object (which has to be copied - to the outputfile by the caller unless the command was HEAD, - and must be closed by the caller under all circumstances), or - None, in which case the caller has nothing further to do. + if self.DO_AUTH: + authorization = self.headers.get('Authorization', '') + if not authorization: + self.send_autherror(401, "Authorization Required") + return False + scheme, credentials = authorization.split() + if scheme != 'Basic': + self.send_error(501) + return False + credentials = base64.decodestring(credentials) + user, password = credentials.split(':', 2) + if not self.get_userinfo(user, password, self.command): + self.send_autherror(401, "Authorization Required") + return False + return True + + def send_autherror(self, code, message=None): + """Send and log an auth error reply. + + Arguments are the error code, and a detailed message. + The detailed message defaults to the short entry matching the + response code. + + This sends an error response (so it must be called before any + output has been generated), logs the error, and finally sends + a piece of HTML explaining the error to the user. """ - path = self.translate_path(self.path) - if os.path.isdir(path): - self.send_error(403, "Directory listing not supported") - return None - try: - f = open(path, 'rb') - except IOError: - self.send_error(404, "File not found") - return None - - self.send_response(200) - self.send_header("Content-type", self.guess_type(path)) - self.end_headers() - return f - - def send_autherror(self,code,message=None): try: short, long = self.responses[code] except KeyError: short, long = '???', '???' - if not message: + if message is None: message = short explain = long - - emsg=self.AUTH_ERROR_MSG self.log_error("code %d, message %s", code, message) + + # using _quote_html to prevent Cross Site Scripting attacks (see bug + # #1100201) + content = (self.error_auth_message_format % {'code': code, 'message': + _quote_html(message), 'explain': explain}) self.send_response(code, message) - self.send_header("WWW-Authenticate","Basic realm=\"PyWebDAV\"") - self.send_header("Content-Type", 'text/html') + self.send_header('Content-Type', self.error_content_type) + self.send_header('WWW-Authenticate', 'Basic realm="PyWebDAV"') + self.send_header('Connection', 'close') self.end_headers() + self.wfile.write(content) - lines=split(emsg,"\n") - for l in lines: - self._append("%s\r\n" %l) + error_auth_message_format = DEFAULT_AUTH_ERROR_MESSAGE - def get_userinfo(self,user, password, command): + def get_userinfo(self, user, password, command): """Checks if the given user and the given password are allowed to access. """ - # Always reject return None - -class BufferedAuthRequestHandler(BufferingHTTPServer.BufferedHTTPRequestHandler,AuthRequestHandler): - - def handle(self): - self._init_buffer() - AuthRequestHandler.handle(self) - self._flush() - -class BasicAuthRequestHandler(BufferingHTTPServer.BufferedHTTPRequestHandler,AuthRequestHandler): - - def _append(self,s): - """ write the string to wfile """ - self.wfile.write(s) - diff --git a/pywebdav/lib/BufferingHTTPServer.py b/pywebdav/lib/BufferingHTTPServer.py deleted file mode 100644 index ff18856..0000000 --- a/pywebdav/lib/BufferingHTTPServer.py +++ /dev/null @@ -1,89 +0,0 @@ - -from BaseHTTPServer import BaseHTTPRequestHandler - -import logging -log = logging.getLogger('pywebdav') - -class BufferedHTTPRequestHandler(BaseHTTPRequestHandler): - """ - Buffering HTTP Request Handler - - This class is an extension to the BaseHTTPRequestHandler - class which buffers the whole output and sends it at once - after the processing if the request is finished. - - This makes it possible to work together with some clients - which otherwise would break (e.g. cadaver) - - """ - - responses = dict( - BaseHTTPRequestHandler.responses.items() + - { - 424: ('Failed Dependency', 'The request failed due to failure of a previous request') - }.items() - ) - - def _init_buffer(self): - """initialize the buffer. - - If you override the handle() method remember to call - this (see below) - """ - self.__buffer="" - - def _append(self,s): - """ append a string to the buffer """ - self.__buffer=self.__buffer+s - - def _flush(self): - """ flush the buffer to wfile """ - self.wfile.write(self.__buffer) - self.wfile.flush() - self.__buffer="" - - def _verbose(self): - return self.server.RequestHandlerClass.verbose - - def handle(self): - """ Handle a HTTP request """ - - self._init_buffer() - BaseHTTPRequestHandler.handle(self) - self._flush() - - def send_header(self, keyword, value): - """Send a MIME header.""" - if self.request_version != 'HTTP/0.9': - self._append("%s: %s\r\n" % (keyword, value)) - - def end_headers(self): - """Send the blank line ending the MIME headers.""" - if self.request_version != 'HTTP/0.9': - if self._verbose(): - log.info('Headers:\n%s' % self.__buffer) - - self._append("\r\n") - - def send_response(self, code, message=None): - self.log_request(code) - - if message is None: - if self.responses.has_key(code): - message = self.responses[code][0] - else: - message = '' - - if self.request_version != 'HTTP/0.9': - self._append("%s %s %s\r\n" % - (self.protocol_version, str(code), message)) - - self.send_header('Server', self.version_string()) - - self.send_header('Connection', 'close') - self.close_connection = 1 - - self.send_header('Date', self.date_time_string()) - - protocol_version="HTTP/1.1" - diff --git a/pywebdav/lib/WebDAVServer.py b/pywebdav/lib/WebDAVServer.py index b7fd270..beecc67 100644 --- a/pywebdav/lib/WebDAVServer.py +++ b/pywebdav/lib/WebDAVServer.py @@ -1,16 +1,11 @@ -DEBUG=None - -import os -import sys -import time -import socket -import string -import posixpath -import base64 +"""DAV HTTP Server + +This module builds on BaseHTTPServer and implements DAV commands + +""" import AuthServer import urlparse import urllib -import random import logging from propfind import PROPFIND @@ -20,8 +15,8 @@ from davmove import MOVE from utils import rfc1123_date, IfParser, tokenFinder -from string import atoi,split -from errors import * +from string import atoi +from errors import DAV_Error, DAV_NotFound from constants import DAV_VERSION_1, DAV_VERSION_2 from locks import LockManager @@ -34,9 +29,10 @@ log = logging.getLogger(__name__) -BUFFER_SIZE = 128 * 1000 # 128 Ko +BUFFER_SIZE = 128 * 1000 # 128 Ko -class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager): + +class DAVRequestHandler(AuthServer.AuthRequestHandler, LockManager): """Simple DAV request handler with - GET @@ -58,21 +54,22 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager): """ server_version = "DAV/" + VERSION - encode_threshold = 1400 # common MTU + encode_threshold = 1400 # common MTU - def send_body(self, DATA, code = None, msg = None, desc = None, ctype='application/octet-stream', headers={}): + def send_body(self, DATA, code=None, msg=None, desc=None, + ctype='application/octet-stream', headers={}): """ send a body in one part """ log.debug("Use send_body method") - self.send_response(code,message=msg) + self.send_response(code, message=msg) self.send_header("Connection", "close") self.send_header("Accept-Ranges", "bytes") self.send_header('Date', rfc1123_date()) self._send_dav_version() - for a,v in headers.items(): - self.send_header(a,v) + for a, v in headers.items(): + self.send_header(a, v) if DATA: if 'gzip' in self.headers.get('Accept-Encoding', '').split(',') \ @@ -98,53 +95,46 @@ def send_body(self, DATA, code = None, msg = None, desc = None, ctype='applicati if DATA: if isinstance(DATA, str) or isinstance(DATA, unicode): log.debug("Don't use iterator") - self._append(DATA) + self.wfile.write(DATA) else: if self._config.DAV.getboolean('http_response_use_iterator'): # Use iterator to reduce using memory log.debug("Use iterator") - self.wfile.write(self._buffer) - self.wfile.flush() - self._buffer="" for buf in DATA: self.wfile.write(buf) self.wfile.flush() else: # Don't use iterator, it's a compatibility option log.debug("Don't use iterator") - self._append(DATA.read()) + self.wfile.write(DATA.read()) - def send_body_chunks_if_http11(self, DATA, code, msg = None, desc = None, - ctype='text/xml; encoding="utf-8"', + def send_body_chunks_if_http11(self, DATA, code, msg=None, desc=None, + ctype='text/xml; encoding="utf-8"', headers={}): - if ( - self.request_version == 'HTTP/1.0' or - not self._config.DAV.getboolean('chunked_http_response') - ): + if (self.request_version == 'HTTP/1.0' or + not self._config.DAV.getboolean('chunked_http_response')): self.send_body(DATA, code, msg, desc, ctype, headers) else: self.send_body_chunks(DATA, code, msg, desc, ctype, headers) - - def send_body_chunks(self, DATA, code, msg=None, desc=None, - ctype='text/xml"', headers={}): + + def send_body_chunks(self, DATA, code, msg=None, desc=None, + ctype='text/xml"', headers={}): """ send a body in chunks """ - self.responses[207]=(msg,desc) - self.send_response(code,message=msg) + self.responses[207] = (msg, desc) + self.send_response(code, message=msg) self.send_header("Content-type", ctype) self.send_header("Transfer-Encoding", "chunked") self.send_header('Date', rfc1123_date()) - + self._send_dav_version() - for a,v in headers.items(): - self.send_header(a,v) + for a, v in headers.items(): + self.send_header(a, v) if DATA: - if ( - ('gzip' in self.headers.get('Accept-Encoding', '').split(',')) and - (len(DATA) > self.encode_threshold) - ): + if ('gzip' in self.headers.get('Accept-Encoding', '').split(',') + and len(DATA) > self.encode_threshold): buffer = StringIO.StringIO() output = gzip.GzipFile(mode='wb', fileobj=buffer) if isinstance(DATA, str): @@ -167,20 +157,16 @@ def send_body_chunks(self, DATA, code, msg=None, desc=None, if DATA: if isinstance(DATA, str) or isinstance(DATA, unicode): - self._append(hex(len(DATA))[2:]+"\r\n") - self._append(DATA) - self._append("\r\n") - self._append("0\r\n") - self._append("\r\n") + self.wfile.write(hex(len(DATA))[2:] + "\r\n") + self.wfile.write(DATA) + self.wfile.write("\r\n") + self.wfile.write("0\r\n") + self.wfile.write("\r\n") else: if self._config.DAV.getboolean('http_response_use_iterator'): # Use iterator to reduce using memory - self.wfile.write(self._BufferedHTTPRequestHandler__buffer) - self.wfile.flush() - self._BufferedHTTPRequestHandler__buffer="" - for buf in DATA: - self.wfile.write(hex(len(buf))[2:]+"\r\n") + self.wfile.write(hex(len(buf))[2:] + "\r\n") self.wfile.write(buf) self.wfile.write("\r\n") @@ -188,16 +174,17 @@ def send_body_chunks(self, DATA, code, msg=None, desc=None, self.wfile.write("\r\n") else: # Don't use iterator, it's a compatibility option - self._append(hex(len(DATA))[2:]+"\r\n") - self._append(DATA.read()) - self._append("\r\n") - self._append("0\r\n") - self._append("\r\n") + self.wfile.write(hex(len(DATA))[2:] + "\r\n") + self.wfile.write(DATA.read()) + self.wfile.write("\r\n") + self.wfile.write("0\r\n") + self.wfile.write("\r\n") def _send_dav_version(self): - if self._config.DAV.getboolean('lockemulation') is True: + if self._config.DAV.getboolean('lockemulation'): self.send_header('DAV', DAV_VERSION_2['version']) - else: self.send_header('DAV', DAV_VERSION_1['version']) + else: + self.send_header('DAV', DAV_VERSION_1['version']) ### HTTP METHODS called by the server @@ -207,13 +194,14 @@ def do_OPTIONS(self): self.send_response(200) self.send_header("Content-Length", 0) - if self._config.DAV.getboolean('lockemulation') is True: + if self._config.DAV.getboolean('lockemulation'): self.send_header('Allow', DAV_VERSION_2['options']) - else: self.send_header('Allow', DAV_VERSION_1['options']) + else: + self.send_header('Allow', DAV_VERSION_1['options']) self._send_dav_version() - self.send_header('MS-Author-Via', 'DAV') # this is for M$ + self.send_header('MS-Author-Via', 'DAV') # this is for M$ self.end_headers() def _HEAD_GET(self, with_body=False): @@ -227,18 +215,22 @@ def _HEAD_GET(self, with_body=False): # get the last modified date (RFC 1123!) try: - headers['Last-Modified'] = dc.get_prop(uri,"DAV:","getlastmodified") - except DAV_NotFound: headers['Last-Modified'] = "Sun, 01 Dec 2038 00:00:00 GMT" + headers['Last-Modified'] = dc.get_prop( + uri, "DAV:", "getlastmodified") + except DAV_NotFound: + pass # get the ETag if any try: headers['Etag'] = dc.get_prop(uri, "DAV:", "getetag") - except DAV_NotFound: pass + except DAV_NotFound: + pass # get the content type try: - content_type = dc.get_prop(uri,"DAV:","getcontenttype") - except DAV_NotFound: content_type = "application/octet-stream" + content_type = dc.get_prop(uri, "DAV:", "getcontenttype") + except DAV_NotFound: + content_type = "application/octet-stream" range = None status_code = 200 @@ -251,7 +243,7 @@ def _HEAD_GET(self, with_body=False): # get the data try: data = dc.get_data(uri, range) - except DAV_Error, (ec,dd): + except DAV_Error, (ec, dd): self.send_status(ec) return ec @@ -260,11 +252,12 @@ def _HEAD_GET(self, with_body=False): data = None if isinstance(data, str) or isinstance(data, unicode): - self.send_body(data, status_code, None, None, content_type, headers) + self.send_body(data, status_code, None, None, content_type, + headers) else: headers['Keep-Alive'] = 'timeout=15, max=86' headers['Connection'] = 'Keep-Alive' - self.send_body_chunks_if_http11(data, status_code, None, None, + self.send_body_chunks_if_http11(data, status_code, None, None, content_type, headers) return status_code @@ -277,7 +270,7 @@ def do_HEAD(self): def do_GET(self): """Serve a GET request.""" - log.info(self.headers) + log.debug(self.headers) try: status_code = self._HEAD_GET(with_body=True) @@ -290,15 +283,15 @@ def do_GET(self): raise def do_TRACE(self): - """ This will always fail because we can not reproduce HTTP requests. + """ This will always fail because we can not reproduce HTTP requests. We send back a 405=Method Not Allowed. """ - self.send_body(None, '405', 'Method Not Allowed', 'Method Not Allowed') + self.send_body(None, 405, 'Method Not Allowed', 'Method Not Allowed') def do_POST(self): """ Replacement for GET response. Not implemented here. """ - self.send_body(None, '405', 'Method Not Allowed', 'Method Not Allowed') + self.send_body(None, 405, 'Method Not Allowed', 'Method Not Allowed') def do_PROPPATCH(self): # currently unsupported @@ -312,7 +305,7 @@ def do_PROPFIND(self): # read the body containing the xml request # iff there is no body then this is an ALLPROP request body = None - if self.headers.has_key('Content-Length'): + if 'Content-Length' in self.headers: l = self.headers['Content-Length'] body = self.rfile.read(atoi(l)) @@ -327,7 +320,7 @@ def do_PROPFIND(self): try: DATA = '%s\n' % pf.createResponse() - except DAV_Error, (ec,dd): + except DAV_Error, (ec, dd): return self.send_status(ec) # work around MSIE DAV bug for creation and modified date @@ -335,11 +328,18 @@ def do_PROPFIND(self): if (self.headers.get('User-Agent') == 'Microsoft Data Access Internet Publishing Provider DAV 1.1'): DATA = DATA.replace('', - '') + '') DATA = DATA.replace('', - '') + '') - self.send_body_chunks_if_http11(DATA, '207','Multi-Status','Multiple responses') + self.send_body_chunks_if_http11(DATA, 207, 'Multi-Status', + 'Multiple responses') def do_REPORT(self): """ Query properties on defined resource. """ @@ -349,7 +349,7 @@ def do_REPORT(self): # read the body containing the xml request # iff there is no body then this is an ALLPROP request body = None - if self.headers.has_key('Content-Length'): + if 'Content-Length' in self.headers: l = self.headers['Content-Length'] body = self.rfile.read(atoi(l)) @@ -360,32 +360,33 @@ def do_REPORT(self): try: DATA = '%s\n' % rp.createResponse() - except DAV_Error, (ec,dd): + except DAV_Error, (ec, dd): return self.send_status(ec) - self.send_body_chunks_if_http11(DATA, '207','Multi-Status','Multiple responses') + self.send_body_chunks_if_http11(DATA, 207, 'Multi-Status', + 'Multiple responses') def do_MKCOL(self): """ create a new collection """ # according to spec body must be empty body = None - if self.headers.has_key('Content-Length'): + if 'Content-Length' in self.headers: l = self.headers['Content-Length'] body = self.rfile.read(atoi(l)) if body: return self.send_status(415) - dc=self.IFACE_CLASS - uri=urlparse.urljoin(self.get_baseuri(dc), self.path) - uri=urllib.unquote(uri) + dc = self.IFACE_CLASS + uri = urlparse.urljoin(self.get_baseuri(dc), self.path) + uri = urllib.unquote(uri) try: dc.mkcol(uri) self.send_status(201) self.log_request(201) - except DAV_Error, (ec,dd): + except DAV_Error, (ec, dd): self.log_request(ec) return self.send_status(ec) @@ -397,15 +398,15 @@ def do_DELETE(self): uri = urllib.unquote(uri) # hastags not allowed - if uri.find('#')>=0: + if uri.find('#') >= 0: return self.send_status(404) # locked resources are not allowed to delete if self._l_isLocked(uri): - return self.send_body(None, '423', 'Locked', 'Locked') + return self.send_body(None, 423, 'Locked', 'Locked') # Handle If-Match - if self.headers.has_key('If-Match'): + if 'If-Match' in self.headers: test = False etag = None try: @@ -427,7 +428,7 @@ def do_DELETE(self): return # Handle If-None-Match - if self.headers.has_key('If-None-Match'): + if 'If-None-Match' in self.headers: test = True etag = None try: @@ -449,28 +450,28 @@ def do_DELETE(self): return try: - dl = DELETE(uri,dc) + dl = DELETE(uri, dc) if dc.is_collection(uri): - res=dl.delcol() + res = dl.delcol() if res: - self.send_status(207,body=res) + self.send_status(207, body=res) else: self.send_status(204) else: - res=dl.delone() or 204 - self.send_status(res) + res = dl.delone() or 204 + self.send_status(res) except DAV_NotFound: - self.send_body(None, '404', 'Not Found', 'Not Found') + self.send_body(None, 404, 'Not Found', 'Not Found') def do_PUT(self): - dc=self.IFACE_CLASS - uri=urlparse.urljoin(self.get_baseuri(dc), self.path) - uri=urllib.unquote(uri) + dc = self.IFACE_CLASS + uri = urlparse.urljoin(self.get_baseuri(dc), self.path) + uri = urllib.unquote(uri) log.debug("do_PUT: uri = %s" % uri) log.debug('do_PUT: headers = %s' % self.headers) # Handle If-Match - if self.headers.has_key('If-Match'): + if 'If-Match' in self.headers: log.debug("do_PUT: If-Match %s" % self.headers['If-Match']) test = False etag = None @@ -496,8 +497,9 @@ def do_PUT(self): return # Handle If-None-Match - if self.headers.has_key('If-None-Match'): - log.debug("do_PUT: If-None-Match %s" % self.headers['If-None-Match']) + if 'If-None-Match' in self.headers: + log.debug("do_PUT: If-None-Match %s" % + self.headers['If-None-Match']) test = True etag = None @@ -528,7 +530,7 @@ def do_PUT(self): (self._l_isLocked(uri)) and (not ifheader) ): - return self.send_body(None, '423', 'Locked', 'Locked') + return self.send_body(None, 423, 'Locked', 'Locked') if ((self._l_isLocked(uri)) and (ifheader)): uri_token = self._l_getLockForUri(uri) @@ -547,7 +549,7 @@ def do_PUT(self): if found: break if not found: - res = self.send_body(None, '423', 'Locked', 'Locked') + res = self.send_body(None, 423, 'Locked', 'Locked') self.log_request(423) return res @@ -557,10 +559,9 @@ def do_PUT(self): self.protocol_version >= 'HTTP/1.1' and self.request_version >= 'HTTP/1.1'): self.send_status(100) - self._flush() content_type = None - if self.headers.has_key("Content-Type"): + if 'Content-Type' in self.headers: content_type = self.headers['Content-Type'] headers = {} @@ -578,26 +579,25 @@ def do_PUT(self): self.protocol_version >= 'HTTP/1.1' and self.request_version >= 'HTTP/1.1' ): - self.send_body(None, '201', 'Created', '', headers=headers) - self._flush() + self.send_body(None, 201, 'Created', '', headers=headers) dc.put(uri, self._readChunkedData(), content_type) else: # read the body - body=None - if self.headers.has_key("Content-Length"): - l=self.headers['Content-Length'] + body = None + if 'Content-Length' in self.headers: + l = self.headers['Content-Length'] log.debug("do_PUT: Content-Length = %s" % l) - body=self._readNoChunkedData(atoi(l)) + body = self._readNoChunkedData(atoi(l)) else: log.debug("do_PUT: Content-Length = empty") try: dc.put(uri, body, content_type) - except DAV_Error, (ec,dd): + except DAV_Error, (ec, dd): return self.send_status(ec) - self.send_body(None, '201', 'Created', '', headers=headers) + self.send_body(None, 201, 'Created', '', headers=headers) self.log_request(201) def _readChunkedData(self): @@ -615,7 +615,7 @@ def _readNoChunkedData(self, content_length): else: # Don't use iterator, it's a compatibility option return self.__readNoChunkedDataWithoutIterator(content_length) - + def __readNoChunkedDataWithIterator(self, content_length): while True: if content_length > BUFFER_SIZE: @@ -630,105 +630,100 @@ def __readNoChunkedDataWithIterator(self, content_length): def __readNoChunkedDataWithoutIterator(self, content_length): return self.rfile.read(content_length) - def do_COPY(self): """ copy one resource to another """ try: self.copymove(COPY) - except DAV_Error, (ec,dd): + except DAV_Error, (ec, dd): return self.send_status(ec) def do_MOVE(self): """ move one resource to another """ try: self.copymove(MOVE) - except DAV_Error, (ec,dd): + except DAV_Error, (ec, dd): return self.send_status(ec) - def copymove(self,CLASS): + def copymove(self, CLASS): """ common method for copying or moving objects """ - dc=self.IFACE_CLASS + dc = self.IFACE_CLASS # get the source URI - source_uri=urlparse.urljoin(self.get_baseuri(dc),self.path) - source_uri=urllib.unquote(source_uri) + source_uri = urlparse.urljoin(self.get_baseuri(dc), self.path) + source_uri = urllib.unquote(source_uri) # get the destination URI - dest_uri=self.headers['Destination'] - dest_uri=urllib.unquote(dest_uri) + dest_uri = self.headers['Destination'] + dest_uri = urllib.unquote(dest_uri) # check locks on source and dest if self._l_isLocked(source_uri) or self._l_isLocked(dest_uri): - return self.send_body(None, '423', 'Locked', 'Locked') + return self.send_body(None, 423, 'Locked', 'Locked') # Overwrite? - overwrite=1 - result_code=204 - if self.headers.has_key("Overwrite"): - if self.headers['Overwrite']=="F": - overwrite=None - result_code=201 + overwrite = 1 + result_code = 204 + if 'Overwrite' in self.headers: + if self.headers['Overwrite'] == "F": + overwrite = None + result_code = 201 # instanciate ACTION class - cp=CLASS(dc,source_uri,dest_uri,overwrite) + cp = CLASS(dc, source_uri, dest_uri, overwrite) # Depth? - d="infinity" - if self.headers.has_key("Depth"): - d=self.headers['Depth'] + d = "infinity" + if 'Depth' in self.headers: + d = self.headers['Depth'] - if d!="0" and d!="infinity": + if d != "0" and d != "infinity": self.send_status(400) return - if d=="0": - res=cp.single_action() + if d == "0": + res = cp.single_action() self.send_status(res or 201) return - # now it only can be "infinity" but we nevertheless check for a collection + # now it only can be "infinity" but we nevertheless check for a + # collection if dc.is_collection(source_uri): try: - res=cp.tree_action() - except DAV_Error, (ec,dd): + res = cp.tree_action() + except DAV_Error, (ec, dd): self.send_status(ec) return else: try: - res=cp.single_action() - except DAV_Error, (ec,dd): + res = cp.single_action() + except DAV_Error, (ec, dd): self.send_status(ec) return if res: - self.send_body_chunks_if_http11(res,207,self.responses[207][0], - self.responses[207][1], - ctype='text/xml; charset="utf-8"') + self.send_body_chunks_if_http11(res, 207, self.responses[207][0], + self.responses[207][1], + ctype='text/xml; charset="utf-8"') else: self.send_status(result_code) - def get_userinfo(self,user,pw): + def get_userinfo(self, user, pw): """ Dummy method which lets all users in """ - return 1 - def send_status(self,code=200,mediatype='text/xml; charset="utf-8"', \ - msg=None,body=None): + def send_status(self, code=200, mediatype='text/xml; charset="utf-8"', + msg=None, body=None): if not msg: - msg=self.responses.get(code, ['', ''])[1] + msg = self.responses.get(code, ['', ''])[1] - self.send_body(body,code,self.responses.get(code, [''])[0],msg,mediatype) + self.send_body(body, code, self.responses.get(code, [''])[0], msg, + mediatype) def get_baseuri(self, dc): baseuri = dc.baseuri - if self.headers.has_key('Host'): + if 'Host' in self.headers: uparts = list(urlparse.urlparse(dc.baseuri)) uparts[1] = self.headers['Host'] baseuri = urlparse.urlunparse(uparts) return baseuri - - def log_message(self, *args): - AuthServer.BufferedAuthRequestHandler.log_message(self, - *tuple(('- %s - ' + args[0],) + (self.headers.get('User-Agent', '?'),) + args[1:]) - )