[tryton-debian-vcs] pywebdav branch upstream created. b7905edefb68e2899bcccf84d9b1903e3a42d729
Mathias Behrle
tryton-debian-vcs at alioth.debian.org
Wed Nov 27 16:48:27 UTC 2013
The following commit has been merged in the upstream branch:
https://alioth.debian.org/plugins/scmgit/cgi-bin/gitweb.cgi/?p=tryton/pywebdav.git;a=commitdiff;h=b7905edefb68e2899bcccf84d9b1903e3a42d729
commit b7905edefb68e2899bcccf84d9b1903e3a42d729
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Tue Mar 27 12:08:46 2012 +0200
Adding upstream version 0.9.8.
diff --git a/DAV/AuthServer.py b/DAV/AuthServer.py
deleted file mode 100644
index b6815f2..0000000
--- a/DAV/AuthServer.py
+++ /dev/null
@@ -1,238 +0,0 @@
-#Copyright (c) 2009 Simon Pamies (s.pamies at banality.de)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
-
-"""
- Authenticating HTTP Server
-
- This module builds on BaseHTTPServer and implements
- basic authentication
-
-"""
-
-from DAV.utils import VERSION, AUTHOR
-__version__ = VERSION
-__author__ = AUTHOR
-
-import os
-import sys
-import time
-import socket
-import string
-import posixpath
-import SocketServer
-import BufferingHTTPServer
-import BaseHTTPServer
-import base64
-
-from string import atoi,split
-
-AUTH_ERROR_MSG="""<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
-<HTML><HEAD>
-<TITLE>401 Authorization Required</TITLE>
-</HEAD><BODY>
-<H1>Authorization Required</H1>
-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.<P>
-</BODY></HTML>"""
-
-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.
-
- In your subclass you have to define the method get_userinfo(user,pw)
- 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="""<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
- <HTML><HEAD>
- <TITLE>401 Authorization Required</TITLE>
- </HEAD><BODY>
- <H1>Authorization Required</H1>
- 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.<P>
- </BODY></HTML>"""
-
- 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')
-
- def send_head(self):
- """Common code for GET and HEAD commands.
-
- 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.
-
- """
- 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:
- message = short
- explain = long
-
- emsg=self.AUTH_ERROR_MSG
- self.log_error("code %d, message %s", code, message)
- self.send_response(code, message)
- self.send_header("WWW-Authenticate","Basic realm=\"PyWebDAV\"")
- self.send_header("Content-Type", 'text/html')
- self.end_headers()
-
- lines=split(emsg,"\n")
- for l in lines:
- self._append("%s\r\n" %l)
-
- 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/DAV/BufferingHTTPServer.py b/DAV/BufferingHTTPServer.py
deleted file mode 100644
index 897fe59..0000000
--- a/DAV/BufferingHTTPServer.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
-
-"""
- Buffering HTTP Server
-
-"""
-
-
-from DAV.utils import VERSION, AUTHOR
-__version__ = VERSION
-__author__ = AUTHOR
-
-from BaseHTTPServer import BaseHTTPRequestHandler
-
-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 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':
- 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.send_header('Date', self.date_time_string())
-
- protocol_version="HTTP/1.1"
-
diff --git a/DAV/__init__.py b/DAV/__init__.py
deleted file mode 100644
index 5a88f42..0000000
--- a/DAV/__init__.py
+++ /dev/null
@@ -1,22 +0,0 @@
-#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
-
-
-"""
- python davserver
-
-"""
diff --git a/DAV/constants.py b/DAV/constants.py
deleted file mode 100644
index 131f80b..0000000
--- a/DAV/constants.py
+++ /dev/null
@@ -1,41 +0,0 @@
-#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
-
-# definition for resourcetype
-COLLECTION=1
-OBJECT=None
-
-# attributes for resources
-DAV_PROPS=['creationdate', 'displayname', 'getcontentlanguage', 'getcontentlength', 'getcontenttype', 'getetag', 'getlastmodified', 'lockdiscovery', 'resourcetype', 'source', 'supportedlock']
-
-# Request classes in propfind
-RT_ALLPROP=1
-RT_PROPNAME=2
-RT_PROP=3
-
-# server mode
-DAV_VERSION_1 = {
- 'version' : '1',
- 'options' :
- 'GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE, REPORT'
-}
-
-DAV_VERSION_2 = {
- 'version' : '1,2',
- 'options' :
- DAV_VERSION_1['options'] + ', LOCK, UNLOCK'
-}
diff --git a/DAV/delete.py b/DAV/delete.py
deleted file mode 100644
index 79eb8ba..0000000
--- a/DAV/delete.py
+++ /dev/null
@@ -1,49 +0,0 @@
-#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
-
-import os
-import string
-import urllib
-from StringIO import StringIO
-
-from utils import gen_estring, quote_uri, make_xmlresponse
-from davcmd import deltree
-
-class DELETE:
-
- def __init__(self,uri,dataclass):
- self.__dataclass=dataclass
- self.__uri=uri
-
- def delcol(self):
- """ delete a collection """
-
- dc=self.__dataclass
- result=dc.deltree(self.__uri)
-
- if not len(result.items()):
- return None # everything ok
-
- # create the result element
- return make_xmlresponse(result)
-
- def delone(self):
- """ delete a resource """
-
- dc=self.__dataclass
- return dc.delone(self.__uri)
-
diff --git a/DAV/status.py b/DAV/status.py
deleted file mode 100644
index 4133b40..0000000
--- a/DAV/status.py
+++ /dev/null
@@ -1,48 +0,0 @@
-#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
-
-"""
-
-status codes for DAV services
-
-
-"""
-
-
-STATUS_CODES={
- 100: "Continue",
- 102: "Processing",
- 200: "Ok",
- 201: "Created",
- 204: "No Content",
- 207: "Multi-Status",
- 201: "Created",
- 400: "Bad Request",
- 403: "Forbidden",
- 404: "Not Found",
- 405: "Method Not Allowed",
- 409: "Conflict",
- 412: "Precondition failed",
- 423: "Locked",
- 415: "Unsupported Media Type",
- 507: "Insufficient Storage",
- 422: "Unprocessable Entity",
- 423: "Locked",
- 424: "Failed Dependency",
- 502: "Bad Gateway",
- 507: "Insufficient Storage"
-}
diff --git a/MANIFEST.in b/MANIFEST.in
index 22c139a..1ece202 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,3 @@
include setup.py README VERSION
include doc/*
-include DAVServer/*.py DAVServer/*.ini
+include pywebdav/lib/*.py pywebdav/server/*.ini
diff --git a/PKG-INFO b/PKG-INFO
index 128ea8d..7d97168 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: PyWebDAV
-Version: 0.9.4.1
+Version: 0.9.8
Summary: WebDAV library including a standalone server for python
Home-page: http://code.google.com/p/pywebdav/
Author: Simon Pamies
@@ -14,40 +14,41 @@ Description:
Currently supports
- * WebDAV level 1
- * Level 2 (LOCK, UNLOCK)
- * Experimental iterator support
+ * WebDAV level 1
+ * Level 2 (LOCK, UNLOCK)
+ * Experimental iterator support
It plays nice with
- * Mac OS X Finder
- * Windows Explorer
- * iCal
- * cadaver
- * Nautilus
+ * Mac OS X Finder
+ * Windows Explorer
+ * iCal
+ * cadaver
+ * Nautilus
This package does *not* provide client functionality.
Installation
============
- After installation of this package you will have a new script in you $PYTHON/bin directory called
- *davserver*. This serves as the main entry point to the server.
+ After installation of this package you will have a new script in you
+ $PYTHON/bin directory called *davserver*. This serves as the main entry point
+ to the server.
Examples
========
Example (using easy_install)::
- easy_install PyWebDAV
- davserver -D /tmp -n
+ easy_install PyWebDAV
+ davserver -D /tmp -n
Example (unpacking file locally)::
- tar xvzf PyWebDAV-$VERSION.tar.gz
- cd pywebdav
- python setup.py develop
- davserver -D /tmp -n
+ tar xvzf PyWebDAV-$VERSION.tar.gz
+ cd pywebdav
+ python setup.py develop
+ davserver -D /tmp -n
For more information: http://code.google.com/p/pywebdav/
@@ -55,16 +56,22 @@ Description:
=======
- 0.9.4.1 (Feb 17 2011)
- -------------------
+ 0.9.8 (March 25 2011)
+ ---------------------
- Fixed MySQL injection possibility in MySQLAuthHandler
- Found by Teeed <op.pl> filed under CVE-2011-C0432
+ 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]
+
0.9.4 (April 15 2010)
---------------------
-
+
Add somme configuration setting variable to enable/disable iterator and chunk support
[Stephane Klein]
@@ -156,7 +163,7 @@ Description:
Make propfind respect the depth from queries
[Cedric Krier]
- Add ETag in the header of GET. This is needed to implement
+ Add ETag in the header of GET. This is needed to implement
GroupDAV, CardDAV and CalDAV.
[Cedric Krier]
@@ -249,9 +256,9 @@ Description:
Changes since 0.5
-----------------
- added constants.py
+ added constants.py
data.py must now return COLLECTION or OBJECT when getting asked for
- resourcetype. propfind.py will automatically generate the right xml
+ resourcetype. propfind.py will automatically generate the right xml
element.
<href> now only contains the path
changed HTTP/1.0 header to HTTP/1.1 which makes it work with WebFolders
diff --git a/PyWebDAV.egg-info/PKG-INFO b/PyWebDAV.egg-info/PKG-INFO
index 128ea8d..7d97168 100644
--- a/PyWebDAV.egg-info/PKG-INFO
+++ b/PyWebDAV.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: PyWebDAV
-Version: 0.9.4.1
+Version: 0.9.8
Summary: WebDAV library including a standalone server for python
Home-page: http://code.google.com/p/pywebdav/
Author: Simon Pamies
@@ -14,40 +14,41 @@ Description:
Currently supports
- * WebDAV level 1
- * Level 2 (LOCK, UNLOCK)
- * Experimental iterator support
+ * WebDAV level 1
+ * Level 2 (LOCK, UNLOCK)
+ * Experimental iterator support
It plays nice with
- * Mac OS X Finder
- * Windows Explorer
- * iCal
- * cadaver
- * Nautilus
+ * Mac OS X Finder
+ * Windows Explorer
+ * iCal
+ * cadaver
+ * Nautilus
This package does *not* provide client functionality.
Installation
============
- After installation of this package you will have a new script in you $PYTHON/bin directory called
- *davserver*. This serves as the main entry point to the server.
+ After installation of this package you will have a new script in you
+ $PYTHON/bin directory called *davserver*. This serves as the main entry point
+ to the server.
Examples
========
Example (using easy_install)::
- easy_install PyWebDAV
- davserver -D /tmp -n
+ easy_install PyWebDAV
+ davserver -D /tmp -n
Example (unpacking file locally)::
- tar xvzf PyWebDAV-$VERSION.tar.gz
- cd pywebdav
- python setup.py develop
- davserver -D /tmp -n
+ tar xvzf PyWebDAV-$VERSION.tar.gz
+ cd pywebdav
+ python setup.py develop
+ davserver -D /tmp -n
For more information: http://code.google.com/p/pywebdav/
@@ -55,16 +56,22 @@ Description:
=======
- 0.9.4.1 (Feb 17 2011)
- -------------------
+ 0.9.8 (March 25 2011)
+ ---------------------
- Fixed MySQL injection possibility in MySQLAuthHandler
- Found by Teeed <op.pl> filed under CVE-2011-C0432
+ 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]
+
0.9.4 (April 15 2010)
---------------------
-
+
Add somme configuration setting variable to enable/disable iterator and chunk support
[Stephane Klein]
@@ -156,7 +163,7 @@ Description:
Make propfind respect the depth from queries
[Cedric Krier]
- Add ETag in the header of GET. This is needed to implement
+ Add ETag in the header of GET. This is needed to implement
GroupDAV, CardDAV and CalDAV.
[Cedric Krier]
@@ -249,9 +256,9 @@ Description:
Changes since 0.5
-----------------
- added constants.py
+ added constants.py
data.py must now return COLLECTION or OBJECT when getting asked for
- resourcetype. propfind.py will automatically generate the right xml
+ resourcetype. propfind.py will automatically generate the right xml
element.
<href> now only contains the path
changed HTTP/1.0 header to HTTP/1.1 which makes it work with WebFolders
diff --git a/PyWebDAV.egg-info/SOURCES.txt b/PyWebDAV.egg-info/SOURCES.txt
index e80cab5..c811ea1 100644
--- a/PyWebDAV.egg-info/SOURCES.txt
+++ b/PyWebDAV.egg-info/SOURCES.txt
@@ -1,34 +1,8 @@
MANIFEST.in
README
VERSION
-ez_setup.py
setup.cfg
setup.py
-DAV/AuthServer.py
-DAV/BufferingHTTPServer.py
-DAV/INI_Parse.py
-DAV/WebDAVServer.py
-DAV/__init__.py
-DAV/constants.py
-DAV/davcmd.py
-DAV/davcopy.py
-DAV/davmove.py
-DAV/dbconn.py
-DAV/delete.py
-DAV/errors.py
-DAV/iface.py
-DAV/locks.py
-DAV/propfind.py
-DAV/report.py
-DAV/status.py
-DAV/utils.py
-DAVServer/__init__.py
-DAVServer/config.ini
-DAVServer/daemonize.py
-DAVServer/fileauth.py
-DAVServer/fshandler.py
-DAVServer/mysqlauth.py
-DAVServer/server.py
PyWebDAV.egg-info/PKG-INFO
PyWebDAV.egg-info/SOURCES.txt
PyWebDAV.egg-info/dependency_links.txt
@@ -37,9 +11,33 @@ PyWebDAV.egg-info/not-zip-safe
PyWebDAV.egg-info/top_level.txt
doc/ARCHITECTURE
doc/Changes
-doc/Changes.rej
doc/INSTALL
doc/LICENSE
doc/TODO
doc/interface_class
-doc/walker
\ No newline at end of file
+doc/walker
+pywebdav/__init__.py
+pywebdav/lib/AuthServer.py
+pywebdav/lib/INI_Parse.py
+pywebdav/lib/WebDAVServer.py
+pywebdav/lib/__init__.py
+pywebdav/lib/constants.py
+pywebdav/lib/davcmd.py
+pywebdav/lib/davcopy.py
+pywebdav/lib/davmove.py
+pywebdav/lib/dbconn.py
+pywebdav/lib/delete.py
+pywebdav/lib/errors.py
+pywebdav/lib/iface.py
+pywebdav/lib/locks.py
+pywebdav/lib/propfind.py
+pywebdav/lib/report.py
+pywebdav/lib/status.py
+pywebdav/lib/utils.py
+pywebdav/server/__init__.py
+pywebdav/server/config.ini
+pywebdav/server/daemonize.py
+pywebdav/server/fileauth.py
+pywebdav/server/fshandler.py
+pywebdav/server/mysqlauth.py
+pywebdav/server/server.py
\ No newline at end of file
diff --git a/PyWebDAV.egg-info/entry_points.txt b/PyWebDAV.egg-info/entry_points.txt
index 5a11fa1..6b9ffae 100644
--- a/PyWebDAV.egg-info/entry_points.txt
+++ b/PyWebDAV.egg-info/entry_points.txt
@@ -1,3 +1,3 @@
[console_scripts]
-davserver = DAVServer.server:run
+davserver = pywebdav.server.server:run
diff --git a/PyWebDAV.egg-info/top_level.txt b/PyWebDAV.egg-info/top_level.txt
index e863022..68ad5b3 100644
--- a/PyWebDAV.egg-info/top_level.txt
+++ b/PyWebDAV.egg-info/top_level.txt
@@ -1,2 +1 @@
-DAVServer
-DAV
+pywebdav
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/VERSION b/VERSION
index 199bf2a..e3e1807 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.9.4.1
+0.9.8
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
-<Very simple handler for authentication. Must have its data
-injected (look at server.py). Overwrites get_userinfo from AuthRequestHandler>
-
- |
- |
- |
-
-DAV.WebDAVServer.DAVRequestHandler
-<Provides methods for DAV commands. These methods are triggered
-by AuthRequestHandler.handle().>
-
- |
- |
- |
-
-DAV.AuthServer.BufferedAuthRequestHandler
-<Calls the right methods to buffer request data>
-
- | |
- | |
- | |
-
-DAV.BufferingHTTPServer DAV.AuthServer.AuthRequestHandler
-<Saves the complete result <Overwrites handle() method in order
-in one file and returns to provide authentication and to pass
-only if request is control to the do_ methods handling
-complete> 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 a346e06..6f17fc4 100644
--- a/doc/Changes
+++ b/doc/Changes
@@ -1,11 +1,17 @@
-0.9.4.1 (Feb 17 2011)
--------------------
+0.9.8 (March 25 2011)
+---------------------
-Fixed MySQL injection possibility in MySQLAuthHandler
-Found by Teeed <op.pl> filed under CVE-2011-C0432
+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]
+
0.9.4 (April 15 2010)
---------------------
diff --git a/doc/Changes.rej b/doc/Changes.rej
deleted file mode 100644
index 6d7fc72..0000000
--- a/doc/Changes.rej
+++ /dev/null
@@ -1,34 +0,0 @@
-***************
-*** 1,4 ****
-- - Enhance logging mechanism
- [Stephane Klein]
-
- - Fix issue 15 : I've error when I execute PUT action with Apple Finder client
---- 1,27 ----
-+ - Add somme configuration setting variable to enable/disable iterator and chunk support
-+ [Stephane Klein]
-+
-+ - "log_request" is called after action (like Apache and other server), not before action
-+ [Stephane Klein]
-+
-+ - Fix issue 23 : PyWebDAV need to use iterator to avoid over memory consuption
-+ [Stephane Klein]
-+
-+ - Fix issue 22 : pywebdav need handle "Range" header information
-+ in do_GET request
-+ [Stephane Klein]
-+
-+ - Fix issue 21 : Add thread support
-+ [Stephane Klein]
-+
-+ - Print User-Agent information in log request.
-+ [Stephane Klein]
-+
-+ - Fix issue 13 : return http 1.0 compatible response (not chunked) when
-+ request http version is 1.0
-+ [cliff.wells]
-+
-+ - Fix issue 18 : Enhance logging mechanism
- [Stephane Klein]
-
- - Fix issue 15 : I've error when I execute PUT action with Apple Finder client
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_<propname> method. You maybe should
@@ -58,7 +58,7 @@ get_dav(self,uri,propname)
_get_dav_<propname>(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/ez_setup.py b/ez_setup.py
deleted file mode 100644
index d24e845..0000000
--- a/ez_setup.py
+++ /dev/null
@@ -1,276 +0,0 @@
-#!python
-"""Bootstrap setuptools installation
-
-If you want to use setuptools in your package's setup.py, just include this
-file in the same directory with it, and add this to the top of your setup.py::
-
- from ez_setup import use_setuptools
- use_setuptools()
-
-If you want to require a specific version of setuptools, set a download
-mirror, or use an alternate download directory, you can do so by supplying
-the appropriate options to ``use_setuptools()``.
-
-This file can also be run as a script to install or upgrade setuptools.
-"""
-import sys
-DEFAULT_VERSION = "0.6c9"
-DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
-
-md5_data = {
- 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
- 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
- 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
- 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
- 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
- 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
- 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
- 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
- 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
- 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
- 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
- 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
- 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
- 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
- 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
- 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
- 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
- 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
- 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
- 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
- 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
- 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
- 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
- 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
- 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
- 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
- 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
- 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902',
- 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de',
- 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b',
- 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03',
- 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a',
- 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6',
- 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a',
-}
-
-import sys, os
-try: from hashlib import md5
-except ImportError: from md5 import md5
-
-def _validate_md5(egg_name, data):
- if egg_name in md5_data:
- digest = md5(data).hexdigest()
- if digest != md5_data[egg_name]:
- print >>sys.stderr, (
- "md5 validation of %s failed! (Possible download problem?)"
- % egg_name
- )
- sys.exit(2)
- return data
-
-def use_setuptools(
- version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
- download_delay=15
-):
- """Automatically find/download setuptools and make it available on sys.path
-
- `version` should be a valid setuptools version number that is available
- as an egg for download under the `download_base` URL (which should end with
- a '/'). `to_dir` is the directory where setuptools will be downloaded, if
- it is not already available. If `download_delay` is specified, it should
- be the number of seconds that will be paused before initiating a download,
- should one be required. If an older version of setuptools is installed,
- this routine will print a message to ``sys.stderr`` and raise SystemExit in
- an attempt to abort the calling script.
- """
- was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules
- def do_download():
- egg = download_setuptools(version, download_base, to_dir, download_delay)
- sys.path.insert(0, egg)
- import setuptools; setuptools.bootstrap_install_from = egg
- try:
- import pkg_resources
- except ImportError:
- return do_download()
- try:
- pkg_resources.require("setuptools>="+version); return
- except pkg_resources.VersionConflict, e:
- if was_imported:
- print >>sys.stderr, (
- "The required version of setuptools (>=%s) is not available, and\n"
- "can't be installed while this script is running. Please install\n"
- " a more recent version first, using 'easy_install -U setuptools'."
- "\n\n(Currently using %r)"
- ) % (version, e.args[0])
- sys.exit(2)
- else:
- del pkg_resources, sys.modules['pkg_resources'] # reload ok
- return do_download()
- except pkg_resources.DistributionNotFound:
- return do_download()
-
-def download_setuptools(
- version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
- delay = 15
-):
- """Download setuptools from a specified location and return its filename
-
- `version` should be a valid setuptools version number that is available
- as an egg for download under the `download_base` URL (which should end
- with a '/'). `to_dir` is the directory where the egg will be downloaded.
- `delay` is the number of seconds to pause before an actual download attempt.
- """
- import urllib2, shutil
- egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
- url = download_base + egg_name
- saveto = os.path.join(to_dir, egg_name)
- src = dst = None
- if not os.path.exists(saveto): # Avoid repeated downloads
- try:
- from distutils import log
- if delay:
- log.warn("""
----------------------------------------------------------------------------
-This script requires setuptools version %s to run (even to display
-help). I will attempt to download it for you (from
-%s), but
-you may need to enable firewall access for this script first.
-I will start the download in %d seconds.
-
-(Note: if this machine does not have network access, please obtain the file
-
- %s
-
-and place it in this directory before rerunning this script.)
----------------------------------------------------------------------------""",
- version, download_base, delay, url
- ); from time import sleep; sleep(delay)
- log.warn("Downloading %s", url)
- src = urllib2.urlopen(url)
- # Read/write all in one block, so we don't create a corrupt file
- # if the download is interrupted.
- data = _validate_md5(egg_name, src.read())
- dst = open(saveto,"wb"); dst.write(data)
- finally:
- if src: src.close()
- if dst: dst.close()
- return os.path.realpath(saveto)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-def main(argv, version=DEFAULT_VERSION):
- """Install or upgrade setuptools and EasyInstall"""
- try:
- import setuptools
- except ImportError:
- egg = None
- try:
- egg = download_setuptools(version, delay=0)
- sys.path.insert(0,egg)
- from setuptools.command.easy_install import main
- return main(list(argv)+[egg]) # we're done here
- finally:
- if egg and os.path.exists(egg):
- os.unlink(egg)
- else:
- if setuptools.__version__ == '0.0.1':
- print >>sys.stderr, (
- "You have an obsolete version of setuptools installed. Please\n"
- "remove it from your system entirely before rerunning this script."
- )
- sys.exit(2)
-
- req = "setuptools>="+version
- import pkg_resources
- try:
- pkg_resources.require(req)
- except pkg_resources.VersionConflict:
- try:
- from setuptools.command.easy_install import main
- except ImportError:
- from easy_install import main
- main(list(argv)+[download_setuptools(delay=0)])
- sys.exit(0) # try to force an exit
- else:
- if argv:
- from setuptools.command.easy_install import main
- main(argv)
- else:
- print "Setuptools version",version,"or greater has been installed."
- print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
-
-def update_md5(filenames):
- """Update our built-in md5 registry"""
-
- import re
-
- for name in filenames:
- base = os.path.basename(name)
- f = open(name,'rb')
- md5_data[base] = md5(f.read()).hexdigest()
- f.close()
-
- data = [" %r: %r,\n" % it for it in md5_data.items()]
- data.sort()
- repl = "".join(data)
-
- import inspect
- srcfile = inspect.getsourcefile(sys.modules[__name__])
- f = open(srcfile, 'rb'); src = f.read(); f.close()
-
- match = re.search("\nmd5_data = {\n([^}]+)}", src)
- if not match:
- print >>sys.stderr, "Internal error!"
- sys.exit(2)
-
- src = src[:match.start(1)] + repl + src[match.end(1):]
- f = open(srcfile,'w')
- f.write(src)
- f.close()
-
-
-if __name__=='__main__':
- if len(sys.argv)>2 and sys.argv[1]=='--md5update':
- update_md5(sys.argv[2:])
- else:
- main(sys.argv[1:])
-
-
-
-
-
-
diff --git a/DAVServer/__init__.py b/pywebdav/__init__.py
similarity index 100%
rename from DAVServer/__init__.py
rename to pywebdav/__init__.py
diff --git a/pywebdav/lib/AuthServer.py b/pywebdav/lib/AuthServer.py
new file mode 100644
index 0000000..d4c26c5
--- /dev/null
+++ b/pywebdav/lib/AuthServer.py
@@ -0,0 +1,102 @@
+"""Authenticating HTTP Server
+
+This module builds on BaseHTTPServer and implements basic authentication
+
+"""
+
+import base64
+import binascii
+import BaseHTTPServer
+
+
+DEFAULT_AUTH_ERROR_MESSAGE = """
+<head>
+<title>%(code)s - %(message)s</title>
+</head>
+<body>
+<h1>Authorization Required</h1>
+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.
+</body>"""
+
+
+def _quote_html(html):
+ return html.replace("&", "&").replace("<", "<").replace(">", ">")
+
+
+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
+
+ def parse_request(self):
+ if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
+ return False
+
+ 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.
+
+ """
+ try:
+ short, long = self.responses[code]
+ except KeyError:
+ short, long = '???', '???'
+ if message is None:
+ message = short
+ explain = long
+ 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('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)
+
+ error_auth_message_format = DEFAULT_AUTH_ERROR_MESSAGE
+
+ def get_userinfo(self, user, password, command):
+ """Checks if the given user and the given
+ password are allowed to access.
+ """
+ # Always reject
+ return None
diff --git a/DAV/INI_Parse.py b/pywebdav/lib/INI_Parse.py
similarity index 100%
rename from DAV/INI_Parse.py
rename to pywebdav/lib/INI_Parse.py
diff --git a/DAV/WebDAVServer.py b/pywebdav/lib/WebDAVServer.py
similarity index 68%
rename from DAV/WebDAVServer.py
rename to pywebdav/lib/WebDAVServer.py
index 481e32a..beecc67 100644
--- a/DAV/WebDAVServer.py
+++ b/pywebdav/lib/WebDAVServer.py
@@ -1,31 +1,11 @@
-"""
- Python WebDAV Server.
-
- This module builds on AuthServer by implementing the standard DAV
- methods.
+"""DAV HTTP Server
- Subclass this class and specify an IFACE_CLASS. See example.
+This module builds on BaseHTTPServer and implements DAV commands
"""
-
-DEBUG=None
-
-from DAV.utils import VERSION, AUTHOR
-__version__ = VERSION
-__author__ = AUTHOR
-
-
-import os
-import sys
-import time
-import socket
-import string
-import posixpath
-import base64
import AuthServer
import urlparse
import urllib
-import random
import logging
from propfind import PROPFIND
@@ -35,19 +15,24 @@ from davcopy import COPY
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
import gzip
import StringIO
+from pywebdav.lib import VERSION
+
+from xml.parsers.expat import ExpatError
+
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
@@ -68,21 +53,23 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
"""
- server_version = "DAV/" + __version__
- encode_threshold = 1400 # common MTU
+ server_version = "DAV/" + VERSION
+ 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_header('DAV', DAV_VERSION_2['version'])
- for a,v in headers.items():
- self.send_header(a,v)
+ self._send_dav_version()
+
+ for a, v in headers.items():
+ self.send_header(a, v)
if DATA:
if 'gzip' in self.headers.get('Accept-Encoding', '').split(',') \
@@ -108,52 +95,46 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
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_header('DAV', DAV_VERSION_2['version'])
- for a,v in headers.items():
- self.send_header(a,v)
+ self._send_dav_version()
+
+ 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):
@@ -176,20 +157,16 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
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._buffer)
- self.wfile.flush()
- self._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")
@@ -197,11 +174,17 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
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'):
+ self.send_header('DAV', DAV_VERSION_2['version'])
+ else:
+ self.send_header('DAV', DAV_VERSION_1['version'])
### HTTP METHODS called by the server
@@ -211,18 +194,14 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
self.send_response(200)
self.send_header("Content-Length", 0)
- if self._config.DAV.getboolean('lockemulation') is True:
- if self._config.DAV.getboolean('verbose') is True:
- log.info('Activated LOCK,UNLOCK emulation (experimental)')
-
+ if self._config.DAV.getboolean('lockemulation'):
self.send_header('Allow', DAV_VERSION_2['options'])
- self.send_header('DAV', DAV_VERSION_2['version'])
-
else:
self.send_header('Allow', DAV_VERSION_1['options'])
- self.send_header('DAV', DAV_VERSION_1['version'])
- self.send_header('MS-Author-Via', 'DAV') # this is for M$
+ self._send_dav_version()
+
+ self.send_header('MS-Author-Via', 'DAV') # this is for M$
self.end_headers()
def _HEAD_GET(self, with_body=False):
@@ -236,18 +215,22 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
# 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
@@ -260,7 +243,7 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
# 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
@@ -269,15 +252,15 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
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
-
def do_HEAD(self):
""" Send a HEAD response: Retrieves resource information w/o body """
@@ -300,15 +283,19 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
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
+ return self.send_status(423)
def do_PROPFIND(self):
""" Retrieve properties on defined resource. """
@@ -318,18 +305,22 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
# 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))
uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
uri = urllib.unquote(uri)
- pf = PROPFIND(uri, dc, self.headers.get('Depth', 'infinity'), body)
+ try:
+ pf = PROPFIND(uri, dc, self.headers.get('Depth', 'infinity'), body)
+ except ExpatError:
+ # parse error
+ return self.send_status(400)
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
@@ -337,11 +328,18 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
if (self.headers.get('User-Agent') ==
'Microsoft Data Access Internet Publishing Provider DAV 1.1'):
DATA = DATA.replace('<ns0:getlastmodified xmlns:ns0="DAV:">',
- '<ns0:getlastmodified xmlns:n="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.rfc1123">')
+ '<ns0:getlastmodified xmlns:n="DAV:" '
+ 'xmlns:b="urn:uuid:'
+ 'c2f41010-65b3-11d1-a29f-00aa00c14882/" '
+ 'b:dt="dateTime.rfc1123">')
DATA = DATA.replace('<ns0:creationdate xmlns:ns0="DAV:">',
- '<ns0:creationdate xmlns:n="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.tz">')
+ '<ns0:creationdate xmlns:n="DAV:" '
+ 'xmlns:b="urn:uuid:'
+ 'c2f41010-65b3-11d1-a29f-00aa00c14882/" '
+ 'b:dt="dateTime.tz">')
- 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. """
@@ -351,7 +349,7 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
# 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))
@@ -362,23 +360,33 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
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 """
- dc=self.IFACE_CLASS
- uri=urlparse.urljoin(self.get_baseuri(dc), self.path)
- uri=urllib.unquote(uri)
+ # according to spec body must be empty
+ body = None
+ 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)
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)
@@ -389,8 +397,16 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
uri = urllib.unquote(uri)
+ # hastags not allowed
+ 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')
+
# Handle If-Match
- if self.headers.has_key('If-Match'):
+ if 'If-Match' in self.headers:
test = False
etag = None
try:
@@ -412,7 +428,7 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
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:
@@ -433,33 +449,29 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
self.log_request(412)
return
- # locked resources are not allowed to delete
- if self._l_isLocked(uri):
- return self.send_body(None, '423', 'Locked', 'Locked')
-
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
@@ -485,8 +497,9 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
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
@@ -517,7 +530,7 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
(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)
@@ -536,7 +549,7 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
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
@@ -546,10 +559,9 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
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 = {}
@@ -567,26 +579,25 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
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):
@@ -604,7 +615,7 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
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:
@@ -619,105 +630,100 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
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()
- self.send_status(res)
+ 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:])
- )
diff --git a/pywebdav/lib/__init__.py b/pywebdav/lib/__init__.py
new file mode 100644
index 0000000..e1af983
--- /dev/null
+++ b/pywebdav/lib/__init__.py
@@ -0,0 +1,9 @@
+
+import pkg_resources
+
+# get version from package
+package = pkg_resources.require('PyWebDAV')[0]
+VERSION = package.version
+
+# author hardcoded here
+AUTHOR = 'Simon Pamies (spamsch at gmail.com)'
diff --git a/pywebdav/lib/constants.py b/pywebdav/lib/constants.py
new file mode 100644
index 0000000..4eb99b9
--- /dev/null
+++ b/pywebdav/lib/constants.py
@@ -0,0 +1,24 @@
+# definition for resourcetype
+COLLECTION=1
+OBJECT=None
+
+# attributes for resources
+DAV_PROPS=['creationdate', 'displayname', 'getcontentlanguage', 'getcontentlength', 'getcontenttype', 'getetag', 'getlastmodified', 'lockdiscovery', 'resourcetype', 'source', 'supportedlock']
+
+# Request classes in propfind
+RT_ALLPROP=1
+RT_PROPNAME=2
+RT_PROP=3
+
+# server mode
+DAV_VERSION_1 = {
+ 'version' : '1',
+ 'options' :
+ 'GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE, REPORT'
+}
+
+DAV_VERSION_2 = {
+ 'version' : '1,2',
+ 'options' :
+ DAV_VERSION_1['options'] + ', LOCK, UNLOCK'
+}
diff --git a/DAV/davcmd.py b/pywebdav/lib/davcmd.py
similarity index 85%
rename from DAV/davcmd.py
rename to pywebdav/lib/davcmd.py
index a69d0a0..b7b24d1 100644
--- a/DAV/davcmd.py
+++ b/pywebdav/lib/davcmd.py
@@ -1,22 +1,3 @@
-#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-#Copyright (c) 2009 Simon Pamies (s.pamies at banality.de)
-#Copyright (c) 2009 Cedric Krier (cedric.krier at b2ck.com)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
-
"""
davcmd.py
diff --git a/DAV/davcopy.py b/pywebdav/lib/davcopy.py
similarity index 80%
rename from DAV/davcopy.py
rename to pywebdav/lib/davcopy.py
index 77485db..7de5173 100644
--- a/DAV/davcopy.py
+++ b/pywebdav/lib/davcopy.py
@@ -1,20 +1,3 @@
-#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
-
import xml.dom.minidom
domimpl = xml.dom.minidom.getDOMImplementation()
diff --git a/DAV/davmove.py b/pywebdav/lib/davmove.py
similarity index 74%
rename from DAV/davmove.py
rename to pywebdav/lib/davmove.py
index 87229d0..ce43152 100644
--- a/DAV/davmove.py
+++ b/pywebdav/lib/davmove.py
@@ -1,21 +1,3 @@
-#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
-
-
import sys
import string
import urlparse
diff --git a/DAV/dbconn.py b/pywebdav/lib/dbconn.py
similarity index 57%
rename from DAV/dbconn.py
rename to pywebdav/lib/dbconn.py
index 2541d3f..c80ec8c 100644
--- a/DAV/dbconn.py
+++ b/pywebdav/lib/dbconn.py
@@ -1,22 +1,4 @@
-#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
-
import logging
-import sys
log = logging.getLogger(__name__)
@@ -26,27 +8,26 @@ except ImportError:
log.info('No SQL support - MySQLdb missing...')
pass
-class Mconn:
+import sys
+class Mconn:
def connect(self,username,userpasswd,host,port,db):
- try:
- connection = DB.connect(host=host, port=int(port), user=username, passwd=userpasswd,db=db)
+ try: connection = MySQLdb.connect(host=host, port=int(port), user=username, passwd=userpasswd,db=db)
except MySQLdb.OperationalError, message:
log.error("%d:\n%s" % (message[ 0 ], message[ 1 ] ))
return 0
else:
self.db = connection.cursor()
+
return 1
- def execute(self, qry, parameters):
+ def execute(self,qry):
if self.db:
- try:
- # parameters are escaped by DB API correctly
- res = self.db.execute(qry, parameters)
-
+ try: res=self.db.execute(qry)
except MySQLdb.OperationalError, message:
log.error("Error %d:\n%s" % (message[ 0 ], message[ 1 ] ))
return 0
+
except MySQLdb.ProgrammingError, message:
log.error("Error %d:\n%s" % (message[ 0 ], message[ 1 ] ))
return 0
@@ -56,11 +37,11 @@ class Mconn:
return self.db.fetchall()
def create_user(self,user,passwd):
- qry="select * from Users where User=%s"
- res=self.execute(qry, user)
+ qry="select * from Users where User='%s'"%(user)
+ res=self.execute(qry)
if not res or len(res) ==0:
- qry="insert into Users (User,Pass) values (%s,%s)"
- res=self.execute(qry, (user, passwd))
+ qry="insert into Users (User,Pass) values('%s','%s')"%(user,passwd)
+ res=self.execute(qry)
else:
log.debug("Username already in use")
@@ -72,7 +53,8 @@ class Mconn:
`Write` tinyint(1) default '0',
PRIMARY KEY (`uid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1"""
- self.execute(qry, tuple())
+ self.execute(qry)
+
def first_run(self,user,passwd):
res= self.execute('select * from Users')
@@ -82,6 +64,7 @@ class Mconn:
self.create_table()
self.create_user(user,passwd)
+
def __init__(self,user,password,host,port,db):
self.db=0
self.connect(user,password,host,port,db)
diff --git a/pywebdav/lib/delete.py b/pywebdav/lib/delete.py
new file mode 100644
index 0000000..3ca081b
--- /dev/null
+++ b/pywebdav/lib/delete.py
@@ -0,0 +1,32 @@
+import os
+import string
+import urllib
+from StringIO import StringIO
+
+from utils import gen_estring, quote_uri, make_xmlresponse
+from davcmd import deltree
+
+class DELETE:
+
+ def __init__(self,uri,dataclass):
+ self.__dataclass=dataclass
+ self.__uri=uri
+
+ def delcol(self):
+ """ delete a collection """
+
+ dc=self.__dataclass
+ result=dc.deltree(self.__uri)
+
+ if not len(result.items()):
+ return None # everything ok
+
+ # create the result element
+ return make_xmlresponse(result)
+
+ def delone(self):
+ """ delete a resource """
+
+ dc=self.__dataclass
+ return dc.delone(self.__uri)
+
diff --git a/DAV/errors.py b/pywebdav/lib/errors.py
similarity index 65%
rename from DAV/errors.py
rename to pywebdav/lib/errors.py
index a4ec9a2..d174fb2 100644
--- a/DAV/errors.py
+++ b/pywebdav/lib/errors.py
@@ -1,20 +1,3 @@
-#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
-
"""
Exceptions for the DAVserver implementation
diff --git a/DAV/iface.py b/pywebdav/lib/iface.py
similarity index 91%
rename from DAV/iface.py
rename to pywebdav/lib/iface.py
index e3582cb..3a793ff 100644
--- a/DAV/iface.py
+++ b/pywebdav/lib/iface.py
@@ -1,20 +1,3 @@
-#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
-
"""
basic interface class
diff --git a/DAV/locks.py b/pywebdav/lib/locks.py
similarity index 89%
rename from DAV/locks.py
rename to pywebdav/lib/locks.py
index d32d90b..83c223f 100644
--- a/DAV/locks.py
+++ b/pywebdav/lib/locks.py
@@ -1,20 +1,3 @@
-#Copyright (c) 2009 Simon Pamies (s.pamies at banality.de)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
-
import os
import sys
import time
@@ -111,6 +94,10 @@ class LockManager:
uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
uri = urllib.unquote(uri)
+ # check lock token - must contain a dash
+ if not self.headers.get('Lock-Token', '').find('-')>0:
+ return self.send_status(400)
+
token = tokenFinder(self.headers.get('Lock-Token'))
if self._l_isLocked(uri):
self._l_delLock(token)
@@ -122,7 +109,7 @@ class LockManager:
dc = self.IFACE_CLASS
- log.debug('LOCKing resource %s' % self.headers)
+ log.info('LOCKing resource %s' % self.headers)
body = None
if self.headers.has_key('Content-Length'):
@@ -133,11 +120,11 @@ class LockManager:
uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
uri = urllib.unquote(uri)
- log.debug('do_LOCK: uri = %s' % uri)
+ log.info('do_LOCK: uri = %s' % uri)
ifheader = self.headers.get('If')
alreadylocked = self._l_isLocked(uri)
- log.debug('do_LOCK: alreadylocked = %s' % alreadylocked)
+ log.info('do_LOCK: alreadylocked = %s' % alreadylocked)
if body and alreadylocked:
# Full LOCK request but resource already locked
diff --git a/DAV/propfind.py b/pywebdav/lib/propfind.py
similarity index 53%
rename from DAV/propfind.py
rename to pywebdav/lib/propfind.py
index 8d4650e..07ac6e7 100644
--- a/DAV/propfind.py
+++ b/pywebdav/lib/propfind.py
@@ -1,38 +1,19 @@
-#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
-
import xml.dom.minidom
domimpl = xml.dom.minidom.getDOMImplementation()
import logging
-import sys
-import string
import urlparse
import urllib
-from StringIO import StringIO
import utils
-from constants import COLLECTION, OBJECT, DAV_PROPS, RT_ALLPROP, RT_PROPNAME, RT_PROP
-from errors import *
+from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP
+from errors import DAV_Error, DAV_NotFound
log = logging.getLogger(__name__)
+
class PROPFIND:
- """ parse a propfind xml element and extract props
+ """ parse a propfind xml element and extract props
It will set the following instance vars:
@@ -46,25 +27,26 @@ class PROPFIND:
"""
- def __init__(self,uri,dataclass,depth, body):
- self.request_type=None
- self.nsmap={}
- self.proplist={}
- self.default_ns=None
- self._dataclass=dataclass
- self._depth=str(depth)
- self._uri=uri.rstrip('/')
- self._has_body=None # did we parse a body?
+ def __init__(self, uri, dataclass, depth, body):
+ self.request_type = None
+ self.nsmap = {}
+ self.proplist = {}
+ self.default_ns = None
+ self._dataclass = dataclass
+ self._depth = str(depth)
+ self._uri = uri.rstrip('/')
+ self._has_body = None # did we parse a body?
if dataclass.verbose:
log.info('PROPFIND: Depth is %s, URI is %s' % (depth, uri))
if body:
- self.request_type, self.proplist, self.namespaces = utils.parse_propfind(body)
+ self.request_type, self.proplist, self.namespaces = \
+ utils.parse_propfind(body)
self._has_body = True
def createResponse(self):
- """ Create the multistatus response
+ """ Create the multistatus response
This will be delegated to the specific method
depending on which request (allprop, propname, prop)
@@ -83,13 +65,13 @@ class PROPFIND:
raise DAV_NotFound
df = None
- if self.request_type==RT_ALLPROP:
+ if self.request_type == RT_ALLPROP:
df = self.create_allprop()
- if self.request_type==RT_PROPNAME:
+ if self.request_type == RT_PROPNAME:
df = self.create_propname()
- if self.request_type==RT_PROP:
+ if self.request_type == RT_PROP:
df = self.create_prop()
if df != None:
@@ -102,33 +84,33 @@ class PROPFIND:
def create_propname(self):
""" create a multistatus response for the prop names """
- dc=self._dataclass
+ dc = self._dataclass
# create the document generator
doc = domimpl.createDocument(None, "multistatus", None)
ms = doc.documentElement
ms.setAttribute("xmlns:D", "DAV:")
ms.tagName = 'D:multistatus'
- if self._depth=="0":
- pnames=dc.get_propnames(self._uri)
- re=self.mk_propname_response(self._uri,pnames, doc)
+ if self._depth == "0":
+ pnames = dc.get_propnames(self._uri)
+ re = self.mk_propname_response(self._uri, pnames, doc)
ms.appendChild(re)
- elif self._depth=="1":
- pnames=dc.get_propnames(self._uri)
- re=self.mk_propname_response(self._uri,pnames, doc)
+ elif self._depth == "1":
+ pnames = dc.get_propnames(self._uri)
+ re = self.mk_propname_response(self._uri, pnames, doc)
ms.appendChild(re)
for newuri in dc.get_childs(self._uri):
- pnames=dc.get_propnames(newuri)
- re=self.mk_propname_response(newuri,pnames, doc)
+ pnames = dc.get_propnames(newuri)
+ re = self.mk_propname_response(newuri, pnames, doc)
ms.appendChild(re)
- elif self._depth=='infinity':
+ elif self._depth == 'infinity':
uri_list = [self._uri]
while uri_list:
uri = uri_list.pop()
- pnames=dc.get_propnames(uri)
- re=self.mk_propname_response(uri,pnames, doc)
+ pnames = dc.get_propnames(uri)
+ re = self.mk_propname_response(uri, pnames, doc)
ms.appendChild(re)
uri_childs = self._dataclass.get_childs(uri)
if uri_childs:
@@ -138,10 +120,10 @@ class PROPFIND:
def create_allprop(self):
""" return a list of all properties """
- self.proplist={}
- self.namespaces=[]
- for ns,plist in self._dataclass.get_propnames(self._uri).items():
- self.proplist[ns]=plist
+ self.proplist = {}
+ self.namespaces = []
+ for ns, plist in self._dataclass.get_propnames(self._uri).items():
+ self.proplist[ns] = plist
self.namespaces.append(ns)
return self.create_prop()
@@ -153,7 +135,7 @@ class PROPFIND:
1. set up the <multistatus>-Framework
- 2. read the property values for each URI
+ 2. read the property values for each URI
(which is dependant on the Depth header)
This is done by the get_propvalues() method.
@@ -162,39 +144,37 @@ class PROPFIND:
document.
We differ between "good" properties, which have been
- assigned a value by the interface class and "bad"
+ assigned a value by the interface class and "bad"
properties, which resulted in an error, either 404
(Not Found) or 403 (Forbidden).
"""
-
-
# create the document generator
doc = domimpl.createDocument(None, "multistatus", None)
ms = doc.documentElement
ms.setAttribute("xmlns:D", "DAV:")
ms.tagName = 'D:multistatus'
- if self._depth=="0":
- gp,bp=self.get_propvalues(self._uri)
- res=self.mk_prop_response(self._uri,gp,bp,doc)
+ if self._depth == "0":
+ gp, bp = self.get_propvalues(self._uri)
+ res = self.mk_prop_response(self._uri, gp, bp, doc)
ms.appendChild(res)
- elif self._depth=="1":
- gp,bp=self.get_propvalues(self._uri)
- res=self.mk_prop_response(self._uri,gp,bp,doc)
+ elif self._depth == "1":
+ gp, bp = self.get_propvalues(self._uri)
+ res = self.mk_prop_response(self._uri, gp, bp, doc)
ms.appendChild(res)
for newuri in self._dataclass.get_childs(self._uri):
- gp,bp=self.get_propvalues(newuri)
- res=self.mk_prop_response(newuri,gp,bp,doc)
+ gp, bp = self.get_propvalues(newuri)
+ res = self.mk_prop_response(newuri, gp, bp, doc)
ms.appendChild(res)
- elif self._depth=='infinity':
+ elif self._depth == 'infinity':
uri_list = [self._uri]
while uri_list:
uri = uri_list.pop()
- gp,bp=self.get_propvalues(uri)
- res=self.mk_prop_response(uri,gp,bp,doc)
+ gp, bp = self.get_propvalues(uri)
+ res = self.mk_prop_response(uri, gp, bp, doc)
ms.appendChild(res)
uri_childs = self._dataclass.get_childs(uri)
if uri_childs:
@@ -202,100 +182,112 @@ class PROPFIND:
return doc.toxml(encoding="utf-8")
- def mk_propname_response(self,uri,propnames,doc):
- """ make a new <prop> result element for a PROPNAME request
+ def mk_propname_response(self, uri, propnames, doc):
+ """ make a new <prop> result element for a PROPNAME request
This will simply format the propnames list.
propnames should have the format {NS1 : [prop1, prop2, ...], NS2: ...}
"""
- re=doc.createElement("D:response")
+ re = doc.createElement("D:response")
+
+ if self._dataclass.baseurl:
+ uri = self._dataclass.baseurl + '/' + '/'.join(uri.split('/')[3:])
# write href information
- uparts=urlparse.urlparse(uri)
- fileloc=uparts[2]
- href=doc.createElement("D:href")
- huri=doc.createTextNode(uparts[0]+'://'+'/'.join(uparts[1:2]) + urllib.quote(fileloc))
+ uparts = urlparse.urlparse(uri)
+ fileloc = uparts[2]
+ href = doc.createElement("D:href")
+
+ huri = doc.createTextNode(uparts[0] + '://' +
+ '/'.join(uparts[1:2]) +
+ urllib.quote(fileloc))
href.appendChild(huri)
re.appendChild(href)
- ps=doc.createElement("D:propstat")
- nsnum=0
+ ps = doc.createElement("D:propstat")
+ nsnum = 0
- for ns,plist in propnames.items():
+ for ns, plist in propnames.items():
# write prop element
- pr=doc.createElement("D:prop")
- nsp="ns"+str(nsnum)
- pr.setAttribute("xmlns:"+nsp,ns)
- nsnum=nsnum+1
+ pr = doc.createElement("D:prop")
+ nsp = "ns" + str(nsnum)
+ pr.setAttribute("xmlns:" + nsp, ns)
+ nsnum += 1
- # write propertynames
- for p in plist:
- pe=doc.createElement(nsp+":"+p)
- pr.appendChild(pe)
+ # write propertynames
+ for p in plist:
+ pe = doc.createElement(nsp + ":" + p)
+ pr.appendChild(pe)
- ps.appendChild(pr)
+ ps.appendChild(pr)
re.appendChild(ps)
return re
- def mk_prop_response(self,uri,good_props,bad_props,doc):
- """ make a new <prop> result element
+ def mk_prop_response(self, uri, good_props, bad_props, doc):
+ """ make a new <prop> result element
We differ between the good props and the bad ones for
each generating an extra <propstat>-Node (for each error
one, that means).
"""
- re=doc.createElement("D:response")
+ re = doc.createElement("D:response")
# append namespaces to response
- nsnum=0
+ nsnum = 0
for nsname in self.namespaces:
if nsname != 'DAV:':
- re.setAttribute("xmlns:ns"+str(nsnum),nsname)
- nsnum=nsnum+1
+ re.setAttribute("xmlns:ns" + str(nsnum), nsname)
+ nsnum += 1
+
+ if self._dataclass.baseurl:
+ uri = self._dataclass.baseurl + '/' + '/'.join(uri.split('/')[3:])
# write href information
- uparts=urlparse.urlparse(uri)
- fileloc=uparts[2]
- href=doc.createElement("D:href")
- huri=doc.createTextNode(uparts[0]+'://'+'/'.join(uparts[1:2]) + urllib.quote(fileloc))
+ uparts = urlparse.urlparse(uri)
+ fileloc = uparts[2]
+ href = doc.createElement("D:href")
+
+ huri = doc.createTextNode(uparts[0] + '://' +
+ '/'.join(uparts[1:2]) +
+ urllib.quote(fileloc))
href.appendChild(huri)
re.appendChild(href)
# write good properties
- ps=doc.createElement("D:propstat")
+ ps = doc.createElement("D:propstat")
if good_props:
re.appendChild(ps)
- gp=doc.createElement("D:prop")
+ gp = doc.createElement("D:prop")
for ns in good_props.keys():
if ns != 'DAV:':
- ns_prefix="ns"+str(self.namespaces.index(ns))+":"
+ ns_prefix = "ns" + str(self.namespaces.index(ns)) + ":"
else:
ns_prefix = 'D:'
- for p,v in good_props[ns].items():
+ for p, v in good_props[ns].items():
- pe=doc.createElement(ns_prefix+str(p))
- if hasattr(v, '__class__') and v.__class__.__name__ == 'Element':
+ pe = doc.createElement(ns_prefix + str(p))
+ if isinstance(v, xml.dom.minidom.Element):
pe.appendChild(v)
elif isinstance(v, list):
for val in v:
pe.appendChild(val)
else:
- if p=="resourcetype":
- if v==1:
- ve=doc.createElement("D:collection")
+ if p == "resourcetype":
+ if v == 1:
+ ve = doc.createElement("D:collection")
pe.appendChild(ve)
else:
- ve=doc.createTextNode(v)
+ ve = doc.createTextNode(v)
pe.appendChild(ve)
gp.appendChild(pe)
ps.appendChild(gp)
- s=doc.createElement("D:status")
- t=doc.createTextNode("HTTP/1.1 200 OK")
+ s = doc.createElement("D:status")
+ t = doc.createTextNode("HTTP/1.1 200 OK")
s.appendChild(t)
ps.appendChild(s)
re.appendChild(ps)
@@ -305,23 +297,23 @@ class PROPFIND:
# write a propstat for each error code
for ecode in bad_props.keys():
- ps=doc.createElement("D:propstat")
+ ps = doc.createElement("D:propstat")
re.appendChild(ps)
- bp=doc.createElement("D:prop")
+ bp = doc.createElement("D:prop")
ps.appendChild(bp)
for ns in bad_props[ecode].keys():
if ns != 'DAV:':
- ns_prefix="ns"+str(self.namespaces.index(ns))+":"
+ ns_prefix = "ns" + str(self.namespaces.index(ns)) + ":"
else:
ns_prefix = 'D:'
for p in bad_props[ecode][ns]:
- pe=doc.createElement(ns_prefix+str(p))
+ pe = doc.createElement(ns_prefix + str(p))
bp.appendChild(pe)
- s=doc.createElement("D:status")
- t=doc.createTextNode(utils.gen_estring(ecode))
+ s = doc.createElement("D:status")
+ t = doc.createTextNode(utils.gen_estring(ecode))
s.appendChild(t)
ps.appendChild(s)
re.appendChild(ps)
@@ -329,8 +321,8 @@ class PROPFIND:
# return the new response element
return re
- def get_propvalues(self,uri):
- """ create lists of property values for an URI
+ def get_propvalues(self, uri):
+ """ create lists of property values for an URI
We create two lists for an URI: the properties for
which we found a value and the ones for which we
@@ -338,30 +330,30 @@ class PROPFIND:
found or the user is not allowed to read them.
"""
- good_props={}
- bad_props={}
+ good_props = {}
+ bad_props = {}
ddc = self._dataclass
- for (ns,plist) in self.proplist.items():
- good_props[ns]={}
+ for ns, plist in self.proplist.items():
+ good_props[ns] = {}
for prop in plist:
ec = 0
try:
- r=ddc.get_prop(uri,ns,prop)
- good_props[ns][prop]=r
+ r = ddc.get_prop(uri, ns, prop)
+ good_props[ns][prop] = r
except DAV_Error, error_code:
- ec=error_code[0]
+ ec = error_code[0]
# ignore props with error_code if 0 (invisible)
- if ec==0: continue
+ if ec == 0:
+ continue
- if bad_props.has_key(ec):
- if bad_props[ec].has_key(ns):
+ if ec in bad_props:
+ if ns in bad_props[ec]:
bad_props[ec][ns].append(prop)
else:
- bad_props[ec][ns]=[prop]
+ bad_props[ec][ns] = [prop]
else:
- bad_props[ec]={ns:[prop]}
+ bad_props[ec] = {ns: [prop]}
return good_props, bad_props
-
diff --git a/DAV/report.py b/pywebdav/lib/report.py
similarity index 84%
rename from DAV/report.py
rename to pywebdav/lib/report.py
index 39457b7..a34a04e 100644
--- a/DAV/report.py
+++ b/pywebdav/lib/report.py
@@ -1,25 +1,8 @@
-#Copyright (c) 2009 Cedric Krier (cedric.krier at b2ck.com)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
-
from propfind import PROPFIND
from xml.dom import minidom
domimpl = minidom.getDOMImplementation()
-from utils import get_parenturi
+from utils import get_parenturi
class REPORT(PROPFIND):
@@ -135,4 +118,3 @@ class REPORT(PROPFIND):
return doc.toxml(encoding="utf-8")
-
diff --git a/pywebdav/lib/status.py b/pywebdav/lib/status.py
new file mode 100644
index 0000000..bbfda33
--- /dev/null
+++ b/pywebdav/lib/status.py
@@ -0,0 +1,24 @@
+
+STATUS_CODES={
+ 100: "Continue",
+ 102: "Processing",
+ 200: "Ok",
+ 201: "Created",
+ 204: "No Content",
+ 207: "Multi-Status",
+ 201: "Created",
+ 400: "Bad Request",
+ 403: "Forbidden",
+ 404: "Not Found",
+ 405: "Method Not Allowed",
+ 409: "Conflict",
+ 412: "Precondition failed",
+ 423: "Locked",
+ 415: "Unsupported Media Type",
+ 507: "Insufficient Storage",
+ 422: "Unprocessable Entity",
+ 423: "Locked",
+ 424: "Failed Dependency",
+ 502: "Bad Gateway",
+ 507: "Insufficient Storage"
+}
diff --git a/DAV/utils.py b/pywebdav/lib/utils.py
similarity index 88%
rename from DAV/utils.py
rename to pywebdav/lib/utils.py
index 53da88d..43394aa 100755
--- a/DAV/utils.py
+++ b/pywebdav/lib/utils.py
@@ -1,20 +1,3 @@
-#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
-
import time
import re
@@ -27,9 +10,6 @@ from StringIO import StringIO
from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP
from BaseHTTPServer import BaseHTTPRequestHandler
-VERSION = '0.9.4-dev'
-AUTHOR = 'Simon Pamies <s.pamies at banality.de>'
-
def gen_estring(ecode):
""" generate an error string from the given code """
ec=atoi(str(ecode))
diff --git a/pywebdav/server/__init__.py b/pywebdav/server/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pywebdav/server/__init__.py
@@ -0,0 +1 @@
+
diff --git a/DAVServer/config.ini b/pywebdav/server/config.ini
similarity index 100%
rename from DAVServer/config.ini
rename to pywebdav/server/config.ini
diff --git a/DAVServer/daemonize.py b/pywebdav/server/daemonize.py
similarity index 100%
rename from DAVServer/daemonize.py
rename to pywebdav/server/daemonize.py
diff --git a/DAVServer/fileauth.py b/pywebdav/server/fileauth.py
similarity index 94%
rename from DAVServer/fileauth.py
rename to pywebdav/server/fileauth.py
index f2a649d..4fde33f 100644
--- a/DAVServer/fileauth.py
+++ b/pywebdav/server/fileauth.py
@@ -22,12 +22,13 @@ This is an example implementation of a DAVserver using the DAV package.
"""
+import sys
import logging
-from DAV.WebDAVServer import DAVRequestHandler
+from pywebdav.lib.WebDAVServer import DAVRequestHandler
+from pywebdav.lib.dbconn import Mconn
+
from fshandler import FilesystemHandler
-import sys
-from DAV.dbconn import Mconn
log = logging.getLogger()
diff --git a/DAVServer/fshandler.py b/pywebdav/server/fshandler.py
similarity index 90%
rename from DAVServer/fshandler.py
rename to pywebdav/server/fshandler.py
index 58323b7..40ecc09 100644
--- a/DAVServer/fshandler.py
+++ b/pywebdav/server/fshandler.py
@@ -1,20 +1,3 @@
-#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
-
import sys
import urlparse
import os
@@ -24,11 +7,10 @@ import logging
import types
import shutil
-from DAV.constants import COLLECTION, OBJECT
-from DAV.errors import *
-from DAV.iface import *
-
-from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree
+from pywebdav.lib.constants import COLLECTION, OBJECT
+from pywebdav.lib.errors import *
+from pywebdav.lib.iface import *
+from pywebdav.lib.davcmd import copyone, copytree, moveone, movetree, delone, deltree
log = logging.getLogger(__name__)
@@ -38,7 +20,9 @@ MAGIC_AVAILABLE = False
try:
import mimetypes
MAGIC_AVAILABLE = True
+ log.info('Mimetype support ENABLED')
except ImportError:
+ log.info('Mimetype support DISABLED')
pass
class Resource(object):
@@ -87,7 +71,7 @@ class FilesystemHandler(dav_interface):
# should we be verbose?
self.verbose = verbose
- log.info('Initialized with %s-%s' % (directory, uri))
+ log.info('Initialized with %s %s' % (directory, uri))
def setDirectory(self, path):
""" Sets the directory """
@@ -121,7 +105,7 @@ class FilesystemHandler(dav_interface):
return uri
- def get_childs(self,uri):
+ def get_childs(self, uri, filter=None):
""" return the child objects as self.baseuris for the given URI """
fileloc=self.uri2local(uri)
@@ -144,6 +128,7 @@ class FilesystemHandler(dav_interface):
def get_data(self,uri, range = None):
""" return the content of an object """
+
path=self.uri2local(uri)
if os.path.exists(path):
if os.path.isfile(path):
@@ -173,6 +158,11 @@ class FilesystemHandler(dav_interface):
fp.seek(range[0])
log.info('Serving range %s -> %s content of %s' % (range[0], range[1], uri))
return Resource(fp, range[1] - range[0])
+ elif os.path.isdir(path):
+ # GET for collections is defined as 'return s/th meaningful' :-)
+ from StringIO import StringIO
+ stio = StringIO('Directory at %s' % uri)
+ return Resource(StringIO('Directory at %s' % uri), stio.len)
else:
# also raise an error for collections
# don't know what should happen then..
@@ -399,9 +389,9 @@ class FilesystemHandler(dav_interface):
dstfile=self.uri2local(dst)
try:
shutil.copy(srcfile, dstfile)
- except OSError:
+ except (OSError, IOError):
log.info('copy: forbidden')
- raise DAV_Error, Forbidden
+ raise DAV_Error, 409
def copycol(self, src, dst):
""" copy a collection.
diff --git a/DAVServer/mysqlauth.py b/pywebdav/server/mysqlauth.py
similarity index 93%
rename from DAVServer/mysqlauth.py
rename to pywebdav/server/mysqlauth.py
index 7a86199..ab2d7d4 100644
--- a/DAVServer/mysqlauth.py
+++ b/pywebdav/server/mysqlauth.py
@@ -33,8 +33,8 @@ class MySQLAuthHandler(DAVAuthHandler):
if self.verbose:
print >>sys.stderr,user,command
- qry="select * from %s.Users where User=%%s and Pass=%%s" % Mysql.dbtable
- Auth=DB.execute(qry, (user, pw))
+ qry="select * from %s.Users where User='%s' and Pass='%s'"%(Mysql.dbtable,user,pw)
+ Auth=DB.execute(qry)
if len(Auth) == 1:
can_write=Auth[0][3]
diff --git a/DAVServer/server.py b/pywebdav/server/server.py
similarity index 84%
rename from DAVServer/server.py
rename to pywebdav/server/server.py
index 1fbe47e..c5d3c97 100755
--- a/DAVServer/server.py
+++ b/pywebdav/server/server.py
@@ -1,20 +1,4 @@
#!/usr/bin/env python
-#Copyright (c) 1999-2005 Christian Scholz (cs at comlounge.net)
-#
-#This library is free software; you can redistribute it and/or
-#modify it under the terms of the GNU Library General Public
-#License as published by the Free Software Foundation; either
-#version 2 of the License, or (at your option) any later version.
-#
-#This library is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#Library General Public License for more details.
-#
-#You should have received a copy of the GNU Library General Public
-#License along with this library; if not, write to the Free
-#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-#MA 02111-1307, USA
"""
Python WebDAV Server.
@@ -33,20 +17,18 @@ from BaseHTTPServer import HTTPServer
from SocketServer import ThreadingMixIn
try:
- import DAV
+ import pywebdav.lib
except ImportError:
- print 'DAV package not found! Please install into site-packages or set PYTHONPATH!'
+ print 'pywebdav.lib package not found! Please install into site-packages or set PYTHONPATH!'
sys.exit(2)
-from DAV.utils import VERSION, AUTHOR
-__version__ = VERSION
-__author__ = AUTHOR
-
from fileauth import DAVAuthHandler
from mysqlauth import MySQLAuthHandler
from fshandler import FilesystemHandler
from daemonize import startstop
-from DAV.INI_Parse import Configuration
+
+from pywebdav.lib.INI_Parse import Configuration
+from pywebdav.lib import VERSION, AUTHOR
LEVELS = {'debug': logging.DEBUG,
'info': logging.INFO,
@@ -105,6 +87,10 @@ def runserver(
handler.IFACE_CLASS.mimecheck = False
log.info('Disabled mimetype sniffing (All files will have type application/octet-stream)')
+ if handler._config.DAV.baseurl:
+ log.info('Using %s as base url for PROPFIND requests' % handler._config.DAV.baseurl)
+ handler.IFACE_CLASS.baseurl = handler._config.DAV.baseurl
+
# initialize server on specified port
runner = server( (host, port), handler )
print('Listening on %s (%i)' % (host, port))
@@ -130,6 +116,10 @@ Parameters:
The user that runs this server must have permissions
on that directory. NEVER run as root!
Default directory is /tmp
+ -B, --baseurl Behind a proxy pywebdav needs to generate other URIs for PROPFIND.
+ If you are experiencing problems with links or such when behind
+ a proxy then just set this to a sensible default (e.g. http://dav.domain.com).
+ Make sure that you include the protocol.
-H, --host Host where to listen on (default: localhost)
-P, --port Port to bind server to (default: 8008)
-u, --user Username for authentication
@@ -139,11 +129,12 @@ Parameters:
-m, --mysql Pass this parameter if you want MySQL based authentication.
If you want to use MySQL then the usage of a configuration
file is mandatory.
- -J, --lockemu Activate experimental LOCK and UNLOCK mode (WebDAV Version 2).
- Currently know to work but needs more tests. Default is ON.
+ -J, --nolock Deactivate LOCK and UNLOCK mode (WebDAV Version 2).
-M, --nomime Deactivate mimetype sniffing. Sniffing is based on magic numbers
detection but can be slow under heavy load. If you are experiencing
speed problems try to use this parameter.
+ -T, --noiter Deactivate iterator. Use this if you encounter file corruption during
+ download. Also disables chunked body response.
-i, --icounter If you want to run multiple instances then you have to
give each instance it own number so that logfiles and such
can be identified. Default is 0
@@ -156,13 +147,13 @@ Parameters:
restart - Restart complete server
status - Returns status of server
- -v, --verbose Be verbose
+ -v, --verbose Be verbose.
-l, --loglevel Select the log level : DEBUG, INFO, WARNING, ERROR, CRITICAL
Default is WARNING
-h, --help Show this screen
Please send bug reports and feature requests to %s
-""" % (__version__, __author__)
+""" % (VERSION, AUTHOR)
def setupDummyConfig(**kw):
@@ -191,16 +182,20 @@ def run():
counter = 0
mysql = False
lockemulation = True
+ http_response_use_iterator = True
+ chunked_http_response = True
configfile = ''
mimecheck = True
loglevel = 'warning'
+ baseurl = ''
# parse commandline
try:
- opts, args = getopt.getopt(sys.argv[1:], 'P:D:H:d:u:p:nvhmJi:c:Ml:',
+ opts, args = getopt.getopt(sys.argv[1:], 'P:D:H:d:u:p:nvhmJi:c:Ml:TB:',
['host=', 'port=', 'directory=', 'user=', 'password=',
'daemon=', 'noauth', 'help', 'verbose', 'mysql',
- 'icounter=', 'config=', 'lockemu', 'nomime', 'loglevel'])
+ 'icounter=', 'config=', 'nolock', 'nomime', 'loglevel', 'noiter',
+ 'baseurl='])
except getopt.GetoptError, e:
print usage
print '>>>> ERROR: %s' % str(e)
@@ -216,8 +211,12 @@ def run():
if o in ['-M', '--nomime']:
mimecheck = False
- if o in ['-J', '--lockemu']:
- lockemulation = True
+ if o in ['-J', '--nolock']:
+ lockemulation = False
+
+ if o in ['-T', '--noiter']:
+ http_response_use_iterator = False
+ chunked_http_response = False
if o in ['-c', '--config']:
configfile = a
@@ -254,11 +253,11 @@ def run():
daemonize = True
daemonaction = a
- chunked_http_response = 1
+ if o in ['-B', '--baseurl']:
+ baseurl = a.lower()
# This feature are disabled because they are unstable
http_request_use_iterator = 0
- http_response_use_iterator = 0
conf = None
if configfile != '':
@@ -306,7 +305,8 @@ def run():
'mimecheck' : mimecheck,
'chunked_http_response': chunked_http_response,
'http_request_use_iterator': http_request_use_iterator,
- 'http_response_use_iterator': http_response_use_iterator
+ 'http_response_use_iterator': http_response_use_iterator,
+ 'baseurl' : baseurl
}
conf = setupDummyConfig(**_dc)
@@ -316,20 +316,24 @@ def run():
logging.getLogger().setLevel(LEVELS[loglevel])
+ formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
+ for handler in logging.getLogger().handlers:
+ handler.setFormatter(formatter)
+
if mysql == True and configfile == '':
log.error('You can only use MySQL with configuration file!')
sys.exit(3)
if daemonaction != 'stop':
- log.info('Starting up PyWebDAV server (version %s)' % __version__)
+ log.info('Starting up PyWebDAV server (version %s)' % VERSION)
else:
- log.info('Stopping PyWebDAV server (version %s)' % __version__)
+ log.info('Stopping PyWebDAV server (version %s)' % VERSION)
if not noauth and daemonaction not in ['status', 'stop']:
if not user:
print usage
- print >>sys.stderr, '>> ERROR: No usable parameter specified!'
- print >>sys.stderr, '>> Example: davserver -D /home/files -n'
+ print >>sys.stderr, '>> ERROR: No parameter specified!'
+ print >>sys.stderr, '>> Example: davserver -D /tmp -n'
sys.exit(3)
if daemonaction == 'status':
diff --git a/setup.py b/setup.py
index 6357c73..ea8f87e 100644
--- a/setup.py
+++ b/setup.py
@@ -1,12 +1,12 @@
#!/usr/bin/env python
-try:
- from ez_setup import use_setuptools
- use_setuptools()
-except ImportError:
- pass
+try:
+ from ez_setup import use_setuptools
+ use_setuptools()
+except ImportError:
+ pass
-from setuptools import setup
+from setuptools import setup, find_packages
VERSION = open('VERSION', 'r').read()
VERSION = VERSION.replace('\n', '')
@@ -14,12 +14,12 @@ VERSION = VERSION.replace('\n', '')
CHANGES = open('doc/Changes', 'r').read()
DOC = """
-WebDAV library for python.
+WebDAV library for python.
Consists of a *server* that is ready to run
Serve and the DAV package that provides WebDAV server(!) functionality.
-Currently supports
+Currently supports
* WebDAV level 1
* Level 2 (LOCK, UNLOCK)
@@ -38,8 +38,9 @@ This package does *not* provide client functionality.
Installation
============
-After installation of this package you will have a new script in you $PYTHON/bin directory called
-*davserver*. This serves as the main entry point to the server.
+After installation of this package you will have a new script in you
+$PYTHON/bin directory called *davserver*. This serves as the main entry point
+to the server.
Examples
========
@@ -64,7 +65,6 @@ Changes
%s
""" % CHANGES
-from distutils.core import setup
setup(name='PyWebDAV',
description='WebDAV library including a standalone server for python',
author='Simon Pamies',
@@ -76,30 +76,31 @@ setup(name='PyWebDAV',
license='GPL v2',
version=VERSION,
long_description=DOC,
- classifiers = [
- 'Development Status :: 5 - Production/Stable',
- 'Environment :: Console',
- 'Environment :: Web Environment',
- 'Intended Audience :: Developers',
- 'Intended Audience :: System Administrators',
- 'License :: OSI Approved :: GNU General Public License (GPL)',
- 'Operating System :: MacOS :: MacOS X',
- 'Operating System :: POSIX',
- 'Programming Language :: Python',
- 'Topic :: Software Development :: Libraries'
- ],
- keywords = ['webdav',
- 'server',
- 'dav',
- 'standalone',
- 'library',
- 'gpl',
- 'http',
- 'rfc2518',
- 'rfc 2518'],
- packages=['DAV', 'DAVServer'],
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Console',
+ 'Environment :: Web Environment',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: System Administrators',
+ 'License :: OSI Approved :: GNU General Public License (GPL)',
+ 'Operating System :: MacOS :: MacOS X',
+ 'Operating System :: POSIX',
+ 'Programming Language :: Python',
+ 'Topic :: Software Development :: Libraries',
+ ],
+ keywords=['webdav',
+ 'server',
+ 'dav',
+ 'standalone',
+ 'library',
+ 'gpl',
+ 'http',
+ 'rfc2518',
+ 'rfc 2518'
+ ],
+ packages=find_packages(),
zip_safe=False,
entry_points={
- 'console_scripts' : ['davserver = DAVServer.server:run']
- }
+ 'console_scripts': ['davserver = pywebdav.server.server:run']
+ }
)
commit 1d109ec1e7ed4af8d1f6e1c19d6a62702683c2c0
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Fri Feb 18 17:05:40 2011 +0100
Adding upstream version 0.9.4.1.
diff --git a/DAV/AuthServer.py b/DAV/AuthServer.py
index dff2ddd..b6815f2 100644
--- a/DAV/AuthServer.py
+++ b/DAV/AuthServer.py
@@ -98,6 +98,7 @@ class AuthRequestHandler:
# needed by send_error
self.command = requestline
+ self.headers = {}
if requestline[-2:] == '\r\n':
requestline = requestline[:-2]
diff --git a/DAV/WebDAVServer.py b/DAV/WebDAVServer.py
index 89e5a47..481e32a 100644
--- a/DAV/WebDAVServer.py
+++ b/DAV/WebDAVServer.py
@@ -437,17 +437,19 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
if self._l_isLocked(uri):
return self.send_body(None, '423', 'Locked', 'Locked')
- dl = DELETE(uri,dc)
- if dc.is_collection(uri):
- res=dl.delcol()
- if res:
- self.send_status(207,body=res)
+ try:
+ dl = DELETE(uri,dc)
+ if dc.is_collection(uri):
+ res=dl.delcol()
+ if res:
+ self.send_status(207,body=res)
+ else:
+ self.send_status(204)
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')
def do_PUT(self):
dc=self.IFACE_CLASS
diff --git a/DAV/dbconn.py b/DAV/dbconn.py
index 17dbee0..2541d3f 100644
--- a/DAV/dbconn.py
+++ b/DAV/dbconn.py
@@ -16,34 +16,37 @@
#MA 02111-1307, USA
import logging
+import sys
log = logging.getLogger(__name__)
try:
import MySQLdb
except ImportError:
+ log.info('No SQL support - MySQLdb missing...')
pass
-import sys
-
class Mconn:
+
def connect(self,username,userpasswd,host,port,db):
- try: connection = MySQLdb.connect(host=host, port=int(port), user=username, passwd=userpasswd,db=db)
+ try:
+ connection = DB.connect(host=host, port=int(port), user=username, passwd=userpasswd,db=db)
except MySQLdb.OperationalError, message:
log.error("%d:\n%s" % (message[ 0 ], message[ 1 ] ))
return 0
else:
self.db = connection.cursor()
-
return 1
- def execute(self,qry):
+ def execute(self, qry, parameters):
if self.db:
- try: res=self.db.execute(qry)
+ try:
+ # parameters are escaped by DB API correctly
+ res = self.db.execute(qry, parameters)
+
except MySQLdb.OperationalError, message:
log.error("Error %d:\n%s" % (message[ 0 ], message[ 1 ] ))
return 0
-
except MySQLdb.ProgrammingError, message:
log.error("Error %d:\n%s" % (message[ 0 ], message[ 1 ] ))
return 0
@@ -53,11 +56,11 @@ class Mconn:
return self.db.fetchall()
def create_user(self,user,passwd):
- qry="select * from Users where User='%s'"%(user)
- res=self.execute(qry)
+ qry="select * from Users where User=%s"
+ res=self.execute(qry, user)
if not res or len(res) ==0:
- qry="insert into Users (User,Pass) values('%s','%s')"%(user,passwd)
- res=self.execute(qry)
+ qry="insert into Users (User,Pass) values (%s,%s)"
+ res=self.execute(qry, (user, passwd))
else:
log.debug("Username already in use")
@@ -69,8 +72,7 @@ class Mconn:
`Write` tinyint(1) default '0',
PRIMARY KEY (`uid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1"""
- self.execute(qry)
-
+ self.execute(qry, tuple())
def first_run(self,user,passwd):
res= self.execute('select * from Users')
@@ -80,7 +82,7 @@ class Mconn:
self.create_table()
self.create_user(user,passwd)
-
def __init__(self,user,password,host,port,db):
self.db=0
self.connect(user,password,host,port,db)
+
diff --git a/DAV/iface.py b/DAV/iface.py
index 8151527..e3582cb 100644
--- a/DAV/iface.py
+++ b/DAV/iface.py
@@ -101,7 +101,7 @@ class dav_interface:
### DATA methods (for GET and PUT)
###
- def get_data(self,uri):
+ def get_data(self, uri, range=None):
""" return the content of an object
return data or raise an exception
@@ -109,7 +109,7 @@ class dav_interface:
"""
raise DAV_NotFound
- def put(self,uri,data):
+ def put(self, uri, data, content_type=None):
""" write an object to the repository
return the location uri or raise an exception
diff --git a/DAVServer/fshandler.py b/DAVServer/fshandler.py
index 70e366f..58323b7 100644
--- a/DAVServer/fshandler.py
+++ b/DAVServer/fshandler.py
@@ -153,16 +153,16 @@ class FilesystemHandler(dav_interface):
log.info('Serving content of %s' % uri)
return Resource(fp, file_size)
else:
- if range[0] == '':
- range[0] = 0
- else:
- range[0] = int(range[0])
-
if range[1] == '':
range[1] = file_size
else:
range[1] = int(range[1])
+ if range[0] == '':
+ range[0] = file_size - range[1]
+ else:
+ range[0] = int(range[0])
+
if range[0] > file_size:
raise DAV_Requested_Range_Not_Satisfiable
diff --git a/DAVServer/mysqlauth.py b/DAVServer/mysqlauth.py
index ab2d7d4..7a86199 100644
--- a/DAVServer/mysqlauth.py
+++ b/DAVServer/mysqlauth.py
@@ -33,8 +33,8 @@ class MySQLAuthHandler(DAVAuthHandler):
if self.verbose:
print >>sys.stderr,user,command
- qry="select * from %s.Users where User='%s' and Pass='%s'"%(Mysql.dbtable,user,pw)
- Auth=DB.execute(qry)
+ qry="select * from %s.Users where User=%%s and Pass=%%s" % Mysql.dbtable
+ Auth=DB.execute(qry, (user, pw))
if len(Auth) == 1:
can_write=Auth[0][3]
diff --git a/PKG-INFO b/PKG-INFO
index 946c108..128ea8d 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: PyWebDAV
-Version: 0.9.4
+Version: 0.9.4.1
Summary: WebDAV library including a standalone server for python
Home-page: http://code.google.com/p/pywebdav/
Author: Simon Pamies
@@ -55,6 +55,13 @@ Description:
=======
+ 0.9.4.1 (Feb 17 2011)
+ -------------------
+
+ Fixed MySQL injection possibility in MySQLAuthHandler
+ Found by Teeed <op.pl> filed under CVE-2011-C0432
+ [Simon Pamies]
+
0.9.4 (April 15 2010)
---------------------
diff --git a/PyWebDAV.egg-info/PKG-INFO b/PyWebDAV.egg-info/PKG-INFO
index 946c108..128ea8d 100644
--- a/PyWebDAV.egg-info/PKG-INFO
+++ b/PyWebDAV.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: PyWebDAV
-Version: 0.9.4
+Version: 0.9.4.1
Summary: WebDAV library including a standalone server for python
Home-page: http://code.google.com/p/pywebdav/
Author: Simon Pamies
@@ -55,6 +55,13 @@ Description:
=======
+ 0.9.4.1 (Feb 17 2011)
+ -------------------
+
+ Fixed MySQL injection possibility in MySQLAuthHandler
+ Found by Teeed <op.pl> filed under CVE-2011-C0432
+ [Simon Pamies]
+
0.9.4 (April 15 2010)
---------------------
diff --git a/VERSION b/VERSION
index a602fc9..199bf2a 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.9.4
+0.9.4.1
diff --git a/doc/Changes b/doc/Changes
index 85a6769..a346e06 100644
--- a/doc/Changes
+++ b/doc/Changes
@@ -1,4 +1,11 @@
+0.9.4.1 (Feb 17 2011)
+-------------------
+
+Fixed MySQL injection possibility in MySQLAuthHandler
+Found by Teeed <op.pl> filed under CVE-2011-C0432
+[Simon Pamies]
+
0.9.4 (April 15 2010)
---------------------
commit b04235b2e5e2eea20964f436d5a2fa9177dc418d
Author: Daniel Baumann <daniel at debian.org>
Date: Fri Apr 23 07:11:52 2010 +0200
Adding upstream version 0.9.4.
diff --git a/DAV/AuthServer.py b/DAV/AuthServer.py
index 2058ede..dff2ddd 100644
--- a/DAV/AuthServer.py
+++ b/DAV/AuthServer.py
@@ -1,9 +1,24 @@
-#!/usr/bin/env python
+#Copyright (c) 2009 Simon Pamies (s.pamies at banality.de)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
"""
Authenticating HTTP Server
- This module builds on BaseHTTPServer and implements
+ This module builds on BaseHTTPServer and implements
basic authentication
"""
@@ -51,7 +66,7 @@ class AuthRequestHandler:
ok or not. None means that the user is not authorized.
"""
- # False means no authentiation
+ # False means no authentiation
DO_AUTH=1
AUTH_ERROR_MSG="""<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
@@ -76,7 +91,7 @@ class AuthRequestHandler:
"""
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
@@ -105,7 +120,7 @@ class AuthRequestHandler:
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)
@@ -139,7 +154,6 @@ class AuthRequestHandler:
"""Override send_response to use the correct http version
in the response."""
- self.log_request(code)
if message is None:
if self.responses.has_key(code):
message = self.responses[code][0]
@@ -195,7 +209,7 @@ class AuthRequestHandler:
self.send_header("WWW-Authenticate","Basic realm=\"PyWebDAV\"")
self.send_header("Content-Type", 'text/html')
self.end_headers()
-
+
lines=split(emsg,"\n")
for l in lines:
self._append("%s\r\n" %l)
@@ -204,7 +218,7 @@ class AuthRequestHandler:
"""Checks if the given user and the given
password are allowed to access.
"""
-
+
# Always reject
return None
diff --git a/DAV/BufferingHTTPServer.py b/DAV/BufferingHTTPServer.py
index db96618..897fe59 100644
--- a/DAV/BufferingHTTPServer.py
+++ b/DAV/BufferingHTTPServer.py
@@ -1,22 +1,22 @@
-#!/usr/bin/env python
+#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
"""
Buffering HTTP Server
- Copyright (C) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public
- License along with this library; if not, write to the Free
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
@@ -40,15 +40,21 @@ class BufferedHTTPRequestHandler(BaseHTTPRequestHandler):
"""
+ 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.
-
+ """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
@@ -88,7 +94,7 @@ class BufferedHTTPRequestHandler(BaseHTTPRequestHandler):
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.send_header('Date', self.date_time_string())
diff --git a/DAV/INI_Parse.py b/DAV/INI_Parse.py
index 03cdff8..efd61fb 100644
--- a/DAV/INI_Parse.py
+++ b/DAV/INI_Parse.py
@@ -1,21 +1,19 @@
-#!/usr/bin/python
-
from ConfigParser import SafeConfigParser
class Configuration:
- def __init__ (self, fileName):
+ def __init__(self, fileName):
cp = SafeConfigParser()
cp.read(fileName)
self.__parser = cp
self.fileName = fileName
-
- def __getattr__ (self, name):
+
+ def __getattr__(self, name):
if name in self.__parser.sections():
return Section(name, self.__parser)
else:
return None
-
- def __str__ (self):
+
+ def __str__(self):
p = self.__parser
result = []
result.append('<Configuration from %s>' % self.fileName)
@@ -26,14 +24,35 @@ class Configuration:
return '\n'.join(result)
class Section:
- def __init__ (self, name, parser):
+ def __init__(self, name, parser):
self.name = name
self.__parser = parser
- def __getattr__ (self, name):
+
+ def __getattr__(self, name):
return self.__parser.get(self.name, name)
+ def __str__(self):
+ return str(self.__repr__())
+
+ def __repr__(self):
+ return self.__parser.items(self.name)
+
+ def getboolean(self, name):
+ return self.__parser.getboolean(self.name, name)
+
+ def __contains__(self, name):
+ return self.__parser.has_option(self.name, name)
+
+ def get(self, name, default):
+ if name in self:
+ return self.__getattr__(name)
+ else:
+ return default
+
+ def set(self, name, value):
+ self.__parser.set(self.name, name, str(value))
+
# Test
if __name__ == '__main__':
c = Configuration('Importador.ini')
print c.Origem.host, c.Origem.port
-
\ No newline at end of file
diff --git a/DAV/WebDAVServer.py b/DAV/WebDAVServer.py
index bbc60ec..89e5a47 100644
--- a/DAV/WebDAVServer.py
+++ b/DAV/WebDAVServer.py
@@ -1,25 +1,10 @@
"""
Python WebDAV Server.
- Copyright (C) 1999 Christian Scholz (ruebe at aachen.heimat.de)
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
+ This module builds on AuthServer by implementing the standard DAV
+ methods.
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public
- License along with this library; if not, write to the Free
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-This module builds on AuthServer by implementing the standard DAV
-methods.
-
-Subclass this class and specify an IFACE_CLASS. See example.
+ Subclass this class and specify an IFACE_CLASS. See example.
"""
@@ -41,6 +26,7 @@ import AuthServer
import urlparse
import urllib
import random
+import logging
from propfind import PROPFIND
from report import REPORT
@@ -48,7 +34,7 @@ from delete import DELETE
from davcopy import COPY
from davmove import MOVE
-from utils import rfc1123_date
+from utils import rfc1123_date, IfParser, tokenFinder
from string import atoi,split
from errors import *
@@ -57,9 +43,13 @@ from locks import LockManager
import gzip
import StringIO
+log = logging.getLogger(__name__)
+
+BUFFER_SIZE = 128 * 1000 # 128 Ko
+
class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
- """Simple DAV request handler with
-
+ """Simple DAV request handler with
+
- GET
- HEAD
- PUT
@@ -75,33 +65,35 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
It uses the resource/collection classes for serving and
storing content.
-
+
"""
server_version = "DAV/" + __version__
encode_threshold = 1400 # common MTU
- ### utility functions
- def _log(self, message):
- pass
-
- def send_body(self,DATA,code,msg,desc,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_header("Connection", "close")
self.send_header("Accept-Ranges", "bytes")
self.send_header('Date', rfc1123_date())
-
+ self.send_header('DAV', DAV_VERSION_2['version'])
+
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:
buffer = StringIO.StringIO()
output = gzip.GzipFile(mode='wb', fileobj=buffer)
- output.write(DATA)
+ if isinstance(DATA, str) or isinstance(DATA, unicode):
+ output.write(DATA)
+ else:
+ for buf in DATA:
+ output.write(buf)
output.close()
buffer.seek(0)
DATA = buffer.getvalue()
@@ -111,38 +103,105 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
self.send_header('Content-Type', ctype)
else:
self.send_header('Content-Length', 0)
-
+
self.end_headers()
if DATA:
- self._append(DATA)
-
- def send_body_chunks(self,DATA,code,msg,desc,ctype='text/xml; encoding="utf-8"'):
+ if isinstance(DATA, str) or isinstance(DATA, unicode):
+ log.debug("Don't use iterator")
+ self._append(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())
+
+ 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')
+ ):
+ 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={}):
""" send a body in chunks """
-
+
self.responses[207]=(msg,desc)
self.send_response(code,message=msg)
self.send_header("Content-type", ctype)
- self.send_header("Connection", "close")
self.send_header("Transfer-Encoding", "chunked")
self.send_header('Date', rfc1123_date())
+ self.send_header('DAV', DAV_VERSION_2['version'])
+
+ for a,v in headers.items():
+ self.send_header(a,v)
- if 'gzip' in self.headers.get('Accept-Encoding', '').split(',') \
- and len(DATA) > self.encode_threshold:
+ if DATA:
+ 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)
- output.write(DATA)
+ if isinstance(DATA, str):
+ output.write(DATA)
+ else:
+ for buf in DATA:
+ output.write(buf)
output.close()
buffer.seek(0)
DATA = buffer.getvalue()
self.send_header('Content-Encoding', 'gzip')
+ self.send_header('Content-Length', len(DATA))
+ self.send_header('Content-Type', ctype)
+
+ else:
+ self.send_header('Content-Length', 0)
+
self.end_headers()
- self._append(hex(len(DATA))[2:]+"\r\n")
- self._append(DATA)
- self._append("\r\n")
- self._append("0\r\n")
- self._append("\r\n")
+ 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")
+ else:
+ if self._config.DAV.getboolean('http_response_use_iterator'):
+ # Use iterator to reduce using memory
+ self.wfile.write(self._buffer)
+ self.wfile.flush()
+ self._buffer=""
+
+ for buf in DATA:
+ self.wfile.write(hex(len(buf))[2:]+"\r\n")
+ self.wfile.write(buf)
+ self.wfile.write("\r\n")
+
+ self.wfile.write("0\r\n")
+ 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")
### HTTP METHODS called by the server
@@ -152,9 +211,9 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
self.send_response(200)
self.send_header("Content-Length", 0)
- if self._config.DAV.lockemulation is True:
- if self._config.DAV.verbose is True:
- print >>sys.stderr, 'Activated LOCK,UNLOCK emulation (experimental)'
+ if self._config.DAV.getboolean('lockemulation') is True:
+ if self._config.DAV.getboolean('verbose') is True:
+ log.info('Activated LOCK,UNLOCK emulation (experimental)')
self.send_header('Allow', DAV_VERSION_2['options'])
self.send_header('DAV', DAV_VERSION_2['version'])
@@ -190,18 +249,35 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
content_type = dc.get_prop(uri,"DAV:","getcontenttype")
except DAV_NotFound: content_type = "application/octet-stream"
+ range = None
+ status_code = 200
+ if 'Range' in self.headers:
+ p = self.headers['Range'].find("bytes=")
+ if p != -1:
+ range = self.headers['Range'][p + 6:].split("-")
+ status_code = 206
+
# get the data
try:
- data = dc.get_data(uri)
+ data = dc.get_data(uri, range)
except DAV_Error, (ec,dd):
self.send_status(ec)
- return
+ return ec
# send the data
if with_body is False:
data = None
- self.send_body(data, '200', "OK", "OK", content_type, headers)
+ if isinstance(data, str) or isinstance(data, unicode):
+ 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,
+ content_type, headers)
+
+ return status_code
+
def do_HEAD(self):
""" Send a HEAD response: Retrieves resource information w/o body """
@@ -211,7 +287,17 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
def do_GET(self):
"""Serve a GET request."""
- return self._HEAD_GET(with_body=True)
+ log.debug(self.headers)
+
+ try:
+ status_code = self._HEAD_GET(with_body=True)
+ self.log_request(status_code)
+ return status_code
+ except IOError, e:
+ if e.errno == 32:
+ self.log_request(206)
+ else:
+ raise
def do_TRACE(self):
""" This will always fail because we can not reproduce HTTP requests.
@@ -255,7 +341,7 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
DATA = DATA.replace('<ns0:creationdate xmlns:ns0="DAV:">',
'<ns0:creationdate xmlns:n="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.tz">')
- self.send_body_chunks(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. """
@@ -272,14 +358,14 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
uri = urllib.unquote(uri)
- rp = REPORT(uri, dc, self.headers.get('Depth', 'infinity'), body)
+ rp = REPORT(uri, dc, self.headers.get('Depth', '0'), body)
try:
DATA = '%s\n' % rp.createResponse()
except DAV_Error, (ec,dd):
return self.send_status(ec)
- self.send_body_chunks(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 """
@@ -291,7 +377,9 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
try:
dc.mkcol(uri)
self.send_status(201)
+ self.log_request(201)
except DAV_Error, (ec,dd):
+ self.log_request(ec)
return self.send_status(ec)
def do_DELETE(self):
@@ -320,6 +408,7 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
break
if not test:
self.send_status(412)
+ self.log_request(412)
return
# Handle If-None-Match
@@ -341,6 +430,7 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
break
if not test:
self.send_status(412)
+ self.log_request(412)
return
# locked resources are not allowed to delete
@@ -350,25 +440,34 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
dl = DELETE(uri,dc)
if dc.is_collection(uri):
res=dl.delcol()
- else: res=dl.delone()
+ if res:
+ self.send_status(207,body=res)
+ else:
+ self.send_status(204)
+ else:
+ res=dl.delone() or 204
+ self.send_status(res)
- if res:
- self.send_status(207,body=res)
- else: self.send_status(204)
def do_PUT(self):
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'):
+ log.debug("do_PUT: If-Match %s" % self.headers['If-Match'])
test = False
etag = None
try:
etag = dc.get_prop(uri, "DAV:", "getetag")
except:
pass
+
+ log.debug("do_PUT: etag = %s" % etag)
+
for match in self.headers['If-Match'].split(','):
if match == '*':
if dc.exists(uri):
@@ -380,16 +479,22 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
break
if not test:
self.send_status(412)
+ self.log_request(412)
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'])
+
test = True
etag = None
try:
etag = dc.get_prop(uri, "DAV:", "getetag")
except:
pass
+
+ log.debug("do_PUT: etag = %s" % etag)
+
for match in self.headers['If-None-Match'].split(','):
if match == '*':
if dc.exists(uri):
@@ -401,8 +506,38 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
break
if not test:
self.send_status(412)
+ self.log_request(412)
return
+ # locked resources are not allowed to be overwritten
+ ifheader = self.headers.get('If')
+ if (
+ (self._l_isLocked(uri)) and
+ (not ifheader)
+ ):
+ return self.send_body(None, '423', 'Locked', 'Locked')
+
+ if ((self._l_isLocked(uri)) and (ifheader)):
+ uri_token = self._l_getLockForUri(uri)
+ taglist = IfParser(ifheader)
+ found = False
+ for tag in taglist:
+ for listitem in tag.list:
+ token = tokenFinder(listitem)
+ if (
+ token and
+ (self._l_hasLock(token)) and
+ (self._l_getLock(token) == uri_token)
+ ):
+ found = True
+ break
+ if found:
+ break
+ if not found:
+ res = self.send_body(None, '423', 'Locked', 'Locked')
+ self.log_request(423)
+ return res
+
# Handle expect
expect = self.headers.get('Expect', '')
if (expect.lower() == '100-continue' and
@@ -411,35 +546,77 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
self.send_status(100)
self._flush()
- # read the body
- body=None
- if self.headers.has_key("Content-Length"):
- l=self.headers['Content-Length']
- body=self.rfile.read(atoi(l))
-
- # locked resources are not allowed to be overwritten
- if self._l_isLocked(uri):
- return self.send_body(None, '423', 'Locked', 'Locked')
-
- ct=None
+ content_type = None
if self.headers.has_key("Content-Type"):
- ct=self.headers['Content-Type']
- try:
- location = dc.put(uri,body,ct)
- except DAV_Error, (ec,dd):
- return self.send_status(ec)
+ content_type = self.headers['Content-Type']
headers = {}
- if location:
- headers['Location'] = location
+ headers['Location'] = uri
try:
- etag = dc.get_prop(location or uri, "DAV:", "getetag")
+ etag = dc.get_prop(uri, "DAV:", "getetag")
headers['ETag'] = etag
except:
pass
- self.send_body(None, '201', 'Created', '', headers=headers)
+ expect = self.headers.get('transfer-encoding', '')
+ if (
+ expect.lower() == 'chunked' and
+ self.protocol_version >= 'HTTP/1.1' and
+ self.request_version >= 'HTTP/1.1'
+ ):
+ self.send_body(None, '201', 'Created', '', headers=headers)
+ self._flush()
+
+ 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']
+ log.debug("do_PUT: Content-Length = %s" % 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):
+ return self.send_status(ec)
+
+ self.send_body(None, '201', 'Created', '', headers=headers)
+ self.log_request(201)
+
+ def _readChunkedData(self):
+ l = int(self.rfile.readline(), 16)
+ while l > 0:
+ buf = self.rfile.read(l)
+ yield buf
+ self.rfile.readline()
+ l = int(self.rfile.readline(), 16)
+
+ def _readNoChunkedData(self, content_length):
+ if self._config.DAV.getboolean('http_request_use_iterator'):
+ # Use iterator to reduce using memory
+ return self.__readNoChunkedDataWithIterator(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:
+ buf = self.rfile.read(BUFFER_SIZE)
+ content_length -= BUFFER_SIZE
+ yield buf
+ else:
+ buf = self.rfile.read(content_length)
+ yield buf
+ break
+
+ def __readNoChunkedDataWithoutIterator(self, content_length):
+ return self.rfile.read(content_length)
+
def do_COPY(self):
""" copy one resource to another """
@@ -486,12 +663,12 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
d="infinity"
if self.headers.has_key("Depth"):
d=self.headers['Depth']
-
- if d!="0" and d!="infinity":
+
+ if d!="0" and d!="infinity":
self.send_status(400)
return
-
- if d=="0":
+
+ if d=="0":
res=cp.single_action()
self.send_status(res)
return
@@ -511,7 +688,7 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
return
if res:
- self.send_body_chunks(res,207,self.responses[207][0],
+ self.send_body_chunks_if_http11(res,207,self.responses[207][0],
self.responses[207][1],
ctype='text/xml; charset="utf-8"')
else:
@@ -525,8 +702,10 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
def send_status(self,code=200,mediatype='text/xml; charset="utf-8"', \
msg=None,body=None):
- if not msg: msg=self.responses[code][1]
- self.send_body(body,code,self.responses[code][0],msg,mediatype)
+ if not msg:
+ msg=self.responses.get(code, ['', ''])[1]
+
+ self.send_body(body,code,self.responses.get(code, [''])[0],msg,mediatype)
def get_baseuri(self, dc):
baseuri = dc.baseuri
@@ -535,3 +714,8 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
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:])
+ )
diff --git a/DAV/__init__.py b/DAV/__init__.py
index 10abdb4..5a88f42 100644
--- a/DAV/__init__.py
+++ b/DAV/__init__.py
@@ -1,20 +1,22 @@
-"""
- python davserver
- Copyright (C) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
+#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
- You should have received a copy of the GNU Library General Public
- License along with this library; if not, write to the Free
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
+ python davserver
-
+"""
diff --git a/DAV/constants.py b/DAV/constants.py
index 4eb99b9..131f80b 100644
--- a/DAV/constants.py
+++ b/DAV/constants.py
@@ -1,3 +1,20 @@
+#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
+
# definition for resourcetype
COLLECTION=1
OBJECT=None
diff --git a/DAV/davcmd.py b/DAV/davcmd.py
index cd2f418..a69d0a0 100644
--- a/DAV/davcmd.py
+++ b/DAV/davcmd.py
@@ -1,3 +1,22 @@
+#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+#Copyright (c) 2009 Simon Pamies (s.pamies at banality.de)
+#Copyright (c) 2009 Cedric Krier (cedric.krier at b2ck.com)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
+
"""
davcmd.py
@@ -15,7 +34,7 @@ from utils import create_treelist, is_prefix
from errors import *
def deltree(dc,uri,exclude={}):
- """ delete a tree of resources
+ """ delete a tree of resources
dc -- dataclass to use
uri -- root uri to delete
@@ -36,7 +55,7 @@ def deltree(dc,uri,exclude={}):
for i in range(len(tlist),0,-1):
problem_uris=result.keys()
element=tlist[i-1]
-
+
# test here, if an element is a prefix of an uri which
# generated an error before.
# note that we walk here from childs to parents, thus
@@ -47,31 +66,31 @@ def deltree(dc,uri,exclude={}):
if is_prefix(element,p):
ok=None
break
-
- if not ok: continue
-
+
+ if not ok: continue
+
# here we test for the exclude list which is the other way round!
for p in exclude.keys():
if is_prefix(p,element):
ok=None
break
-
- if not ok: continue
-
+
+ if not ok: continue
+
# now delete stuff
try:
delone(dc,element)
- except DAV_Error, (ec,dd):
+ except DAV_Error, (ec,dd):
result[element]=ec
-
+
return result
def delone(dc,uri):
""" delete a single object """
if dc.is_collection(uri):
- dc.rmcol(uri) # should be empty
+ return dc.rmcol(uri) # should be empty
else:
- dc.rm(uri)
+ return dc.rm(uri)
###
### COPY
@@ -80,24 +99,25 @@ def delone(dc,uri):
# helper function
def copy(dc,src,dst):
- """ only copy the element
+ """ only copy the element
This is just a helper method factored out from copy and
copytree. It will not handle the overwrite or depth header.
-
+
"""
# destination should have been deleted before
- if dc.exists(dst): raise DAV_Error, 412
+ if dc.exists(dst):
+ raise DAV_Error, 412
# source should exist also
- if not dc.exists(src): raise DAV_NotFound
+ if not dc.exists(src):
+ raise DAV_NotFound
if dc.is_collection(src):
- dc.copycol(src,dst) # an exception will be passed thru
+ dc.copycol(src, dst) # an exception will be passed thru
else:
- dc.copy(src,dst) # an exception will be passed thru
-
+ dc.copy(src, dst) # an exception will be passed thru
# the main functions
@@ -105,16 +125,17 @@ def copyone(dc,src,dst,overwrite=None):
""" copy one resource to a new destination """
if overwrite and dc.exists(dst):
- delres=deltree(dc,dst)
+ delres = deltree(dc, dst)
else:
delres={}
# if we cannot delete everything, then do not copy!
- if delres: return delres
-
+ if delres:
+ return delres
+
try:
- copy(dc,src,dst) # pass thru exceptions
- except DAV_Error, (ec,dd):
+ copy(dc, src, dst) # pass thru exceptions
+ except DAV_Error, (ec, dd):
return ec
def copytree(dc,src,dst,overwrite=None):
@@ -122,7 +143,7 @@ def copytree(dc,src,dst,overwrite=None):
dc -- dataclass to use
src -- src uri from where to copy
- dst -- dst uri
+ dst -- dst uri
overwrite -- if 1 then delete dst uri before
returns dict of uri:error_code tuples from which
@@ -137,17 +158,18 @@ def copytree(dc,src,dst,overwrite=None):
delres={}
# if we cannot delete everything, then do not copy!
- if delres: return delres
+ if delres:
+ return delres
# get the tree we have to copy
- tlist=create_treelist(dc,src)
- result={}
+ tlist = create_treelist(dc,src)
+ result = {}
# prepare destination URIs (get the prefix)
- dpath=urlparse.urlparse(dst)[2]
+ dpath = urlparse.urlparse(dst)[2]
for element in tlist:
- problem_uris=result.keys()
+ problem_uris = result.keys()
# now URIs get longer and longer thus we have
# to test if we had a parent URI which we were not
@@ -159,8 +181,8 @@ def copytree(dc,src,dst,overwrite=None):
if is_prefix(p,element):
ok=None
break
-
- if not ok: continue
+
+ if not ok: continue
# now create the destination URI which corresponds to
# the actual source URI. -> actual_dst
@@ -172,12 +194,11 @@ def copytree(dc,src,dst,overwrite=None):
# now copy stuff
try:
copy(dc,element,actual_dst)
- except DAV_Error, (ec,dd):
+ except DAV_Error, (ec,dd):
result[element]=ec
return result
-
-
+
###
### MOVE
@@ -192,11 +213,11 @@ def moveone(dc,src,dst,overwrite=None):
"""
# first copy it
- copyone(dc,src,dst,overwrite)
+ copyone(dc, src, dst, overwrite)
# then delete it
dc.rm(src)
-
+
def movetree(dc,src,dst,overwrite=None):
""" move a collection
@@ -208,10 +229,10 @@ def movetree(dc,src,dst,overwrite=None):
"""
# first copy it
- res=copytree(dc,src,dst,overwrite)
+ res = copytree(dc,src,dst,overwrite)
# then delete it
- res=deltree(dc,src,exclude=res)
+ res = deltree(dc,src,exclude=res)
return res
-
+
diff --git a/DAV/davcopy.py b/DAV/davcopy.py
index 726a0f1..77485db 100644
--- a/DAV/davcopy.py
+++ b/DAV/davcopy.py
@@ -1,3 +1,19 @@
+#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
import xml.dom.minidom
domimpl = xml.dom.minidom.getDOMImplementation()
diff --git a/DAV/davmove.py b/DAV/davmove.py
index e0ea082..87229d0 100644
--- a/DAV/davmove.py
+++ b/DAV/davmove.py
@@ -1,3 +1,21 @@
+#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
+
+
import sys
import string
import urlparse
@@ -15,10 +33,10 @@ class MOVE:
""" move resources and eventually create multistatus responses
This module implements the MOVE class which is responsible for
- moving resources.
+ moving resources.
MOVE is implemented by a COPY followed by a DELETE of the old
- resource.
+ resource.
"""
@@ -69,7 +87,7 @@ class MOVE:
ps=urlparse.urlparse(self.__src)[2]
pd=urlparse.urlparse(self.__dst)[2]
if ps==pd: raise DAV_Error, 403
-
+
result=dc.movetree(self.__src,self.__dst,self.__overwrite)
if not result: return None
diff --git a/DAV/dbconn.py b/DAV/dbconn.py
index 6805b7f..17dbee0 100644
--- a/DAV/dbconn.py
+++ b/DAV/dbconn.py
@@ -1,3 +1,24 @@
+#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
+
+import logging
+
+log = logging.getLogger(__name__)
+
try:
import MySQLdb
except ImportError:
@@ -6,61 +27,60 @@ except ImportError:
import sys
class Mconn:
- def connect(self,username,userpasswd,host,port,db):
- try: connection = MySQLdb.connect(host=host, port=int(port), user=username, passwd=userpasswd,db=db)
- except MySQLdb.OperationalError, message:
- print >>sys.stderr,"Error %d:\n%s" % (message[ 0 ], message[ 1 ] )
- return 0
- else:
- self.db = connection.cursor()
-
- return 1
+ def connect(self,username,userpasswd,host,port,db):
+ try: connection = MySQLdb.connect(host=host, port=int(port), user=username, passwd=userpasswd,db=db)
+ except MySQLdb.OperationalError, message:
+ log.error("%d:\n%s" % (message[ 0 ], message[ 1 ] ))
+ return 0
+ else:
+ self.db = connection.cursor()
+
+ return 1
+
+ def execute(self,qry):
+ if self.db:
+ try: res=self.db.execute(qry)
+ except MySQLdb.OperationalError, message:
+ log.error("Error %d:\n%s" % (message[ 0 ], message[ 1 ] ))
+ return 0
+
+ except MySQLdb.ProgrammingError, message:
+ log.error("Error %d:\n%s" % (message[ 0 ], message[ 1 ] ))
+ return 0
+
+ else:
+ log.debug('Query Returned '+str(res)+' results')
+ return self.db.fetchall()
+
+ def create_user(self,user,passwd):
+ qry="select * from Users where User='%s'"%(user)
+ res=self.execute(qry)
+ if not res or len(res) ==0:
+ qry="insert into Users (User,Pass) values('%s','%s')"%(user,passwd)
+ res=self.execute(qry)
+ else:
+ log.debug("Username already in use")
+
+ def create_table(self):
+ qry="""CREATE TABLE `Users` (
+ `uid` int(10) NOT NULL auto_increment,
+ `User` varchar(60) default NULL,
+ `Pass` varchar(60) default NULL,
+ `Write` tinyint(1) default '0',
+ PRIMARY KEY (`uid`)
+ ) ENGINE=MyISAM DEFAULT CHARSET=latin1"""
+ self.execute(qry)
+
+
+ def first_run(self,user,passwd):
+ res= self.execute('select * from Users')
+ if res or type(res)==type(()) :
+ pass
+ else:
+ self.create_table()
+ self.create_user(user,passwd)
+
- def execute(self,qry):
- if self.db:
- try: res=self.db.execute(qry)
- except MySQLdb.OperationalError, message:
- print >>sys.stderr, "Error %d:\n%s" % (message[ 0 ], message[ 1 ] )
- return 0
-
- except MySQLdb.ProgrammingError, message:
- print >>sys.stderr,"Error %d:\n%s" % (message[ 0 ], message[ 1 ] )
- return 0
-
- else:
- print >>sys.stderr,'Query Returned '+str(res)+' results'
- return self.db.fetchall()
-
- def create_user(self,user,passwd):
- qry="select * from Users where User='%s'"%(user)
- res=self.execute(qry)
- if not res or len(res) ==0:
- qry="insert into Users (User,Pass) values('%s','%s')"%(user,passwd)
- res=self.execute(qry)
- else:
- print >>sys.stderr, "Username already in use"
-
- def create_table(self):
- qry="""CREATE TABLE `Users` (
- `uid` int(10) NOT NULL auto_increment,
- `User` varchar(60) default NULL,
- `Pass` varchar(60) default NULL,
- `Write` tinyint(1) default '0',
- PRIMARY KEY (`uid`)
- ) ENGINE=MyISAM DEFAULT CHARSET=latin1"""
- self.execute(qry)
-
-
- def first_run(self,user,passwd):
- res= self.execute('select * from Users')
- if res or type(res)==type(()) :
- pass
- else:
- self.create_table()
- self.create_user(user,passwd)
-
-
- def __init__(self,user,password,host,port,db):
- self.db=0
- self.connect(user,password,host,port,db)
-
+ def __init__(self,user,password,host,port,db):
+ self.db=0
+ self.connect(user,password,host,port,db)
diff --git a/DAV/delete.py b/DAV/delete.py
index 15c880d..79eb8ba 100644
--- a/DAV/delete.py
+++ b/DAV/delete.py
@@ -1,3 +1,20 @@
+#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
+
import os
import string
import urllib
@@ -28,12 +45,5 @@ class DELETE:
""" delete a resource """
dc=self.__dataclass
- result=dc.delone(self.__uri)
-
- if not result: return None
- if not len(result.items()):
- return None # everything ok
-
- # create the result element
- return make_xmlresponse(result)
+ return dc.delone(self.__uri)
diff --git a/DAV/errors.py b/DAV/errors.py
index c2b12d1..a4ec9a2 100644
--- a/DAV/errors.py
+++ b/DAV/errors.py
@@ -1,4 +1,20 @@
-#!/usr/bin/env python
+#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
+
"""
Exceptions for the DAVserver implementation
@@ -8,8 +24,8 @@
class DAV_Error(Exception):
""" in general we can have the following arguments:
- 1. the error code
- 2. the error result element, e.g. a <multistatus> element
+ 1. the error code
+ 2. the error result element, e.g. a <multistatus> element
"""
def __init__(self,*args):
@@ -17,10 +33,10 @@ class DAV_Error(Exception):
self.args=(args[0],"")
else:
self.args=args
-
+
class DAV_Secret(DAV_Error):
""" the user is not allowed to know anything about it
-
+
returning this for a property value means to exclude it
from the response xml element.
"""
@@ -31,7 +47,7 @@ class DAV_Secret(DAV_Error):
class DAV_NotFound(DAV_Error):
""" a requested property was not found for a resource """
-
+
def __init__(self,*args):
if len(args):
DAV_Error.__init__(self,404,args[0])
@@ -42,7 +58,7 @@ class DAV_NotFound(DAV_Error):
class DAV_Forbidden(DAV_Error):
""" a method on a resource is not allowed """
-
+
def __init__(self,*args):
if len(args):
DAV_Error.__init__(self,403,args[0])
@@ -50,3 +66,14 @@ class DAV_Forbidden(DAV_Error):
DAV_Error.__init__(self,403)
pass
+class DAV_Requested_Range_Not_Satisfiable(DAV_Error):
+ """ none of the range-specifier values overlap the current extent
+ of the selected resource """
+
+ def __init__(self, *args):
+ if len(args):
+ DAV_Error.__init__(self, 416, args[0])
+ else:
+ DAV_Error.__init__(self, 416)
+ pass
+
diff --git a/DAV/iface.py b/DAV/iface.py
index ad78ce7..8151527 100644
--- a/DAV/iface.py
+++ b/DAV/iface.py
@@ -1,3 +1,20 @@
+#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
+
"""
basic interface class
@@ -20,16 +37,16 @@ class dav_interface:
### defined properties (modify this but let the DAV stuff there!)
### the format is namespace: [list of properties]
- PROPS={"DAV:" : ('creationdate',
- 'displayname',
- 'getcontentlanguage',
- 'getcontentlength',
- 'getcontenttype',
- 'getetag',
- 'getlastmodified',
- 'lockdiscovery',
- 'resourcetype',
- 'source',
+ PROPS={"DAV:" : ('creationdate',
+ 'displayname',
+ 'getcontentlanguage',
+ 'getcontentlength',
+ 'getcontenttype',
+ 'getetag',
+ 'getlastmodified',
+ 'lockdiscovery',
+ 'resourcetype',
+ 'source',
'supportedlock'),
"NS2" : ("p1","p2")
}
@@ -42,20 +59,20 @@ class dav_interface:
"NS2" : "ns2" }
def get_propnames(self,uri):
- """ return the property names allowed for the given URI
+ """ return the property names allowed for the given URI
In this method we simply return the above defined properties
- assuming that they are valid for any resource.
+ assuming that they are valid for any resource.
You can override this in order to return a different set
of property names for each resource.
-
+
"""
return self.PROPS
def get_prop2(self,uri,ns,pname):
- """ return the value of a property
+ """ return the value of a property
+
-
"""
if lower(ns)=="dav:": return self.get_dav(uri,pname)
@@ -74,26 +91,26 @@ class dav_interface:
raise DAV_NotFound
mname=prefix+"_"+propname.replace('-', '_')
try:
- m=getattr(self,mname)
- r=m(uri)
- return r
+ m=getattr(self,mname)
+ r=m(uri)
+ return r
except AttributeError:
- raise DAV_NotFound
+ raise DAV_NotFound
###
### DATA methods (for GET and PUT)
###
def get_data(self,uri):
- """ return the content of an object
+ """ return the content of an object
return data or raise an exception
-
+
"""
raise DAV_NotFound
def put(self,uri,data):
- """ write an object to the repository
+ """ write an object to the repository
return the location uri or raise an exception
"""
@@ -153,7 +170,7 @@ class dav_interface:
""" return the last modification date of the resource """
return time.time()
-
+
###
### COPY MOVE DELETE
###
@@ -167,7 +184,7 @@ class dav_interface:
before by the DELETE class in DAV/delete.py
return a success code or raise an exception
-
+
"""
raise DAV_NotFound
@@ -175,7 +192,7 @@ class dav_interface:
""" delete a single resource
return a success code or raise an exception
-
+
"""
raise DAV_NotFound
diff --git a/DAV/locks.py b/DAV/locks.py
index 0badcc9..d32d90b 100644
--- a/DAV/locks.py
+++ b/DAV/locks.py
@@ -1,3 +1,19 @@
+#Copyright (c) 2009 Simon Pamies (s.pamies at banality.de)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
import os
import sys
@@ -10,6 +26,11 @@ import urlparse
import urllib
import random
+import logging
+
+log = logging.getLogger(__name__)
+
+import xml.dom
from xml.dom import minidom
from utils import rfc1123_date, IfParser, tokenFinder
@@ -84,8 +105,8 @@ class LockManager:
dc = self.IFACE_CLASS
- if self._config.DAV.verbose is True:
- print >>sys.stderr, 'UNLOCKing resource %s' % self.headers
+ if self._config.DAV.getboolean('verbose') is True:
+ log.info('UNLOCKing resource %s' % self.headers)
uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
uri = urllib.unquote(uri)
@@ -93,7 +114,7 @@ class LockManager:
token = tokenFinder(self.headers.get('Lock-Token'))
if self._l_isLocked(uri):
self._l_delLock(token)
-
+
self.send_body(None, '204', 'Ok', 'Ok')
def do_LOCK(self):
@@ -101,8 +122,7 @@ class LockManager:
dc = self.IFACE_CLASS
- if self._config.DAV.verbose is True:
- print >>sys.stderr, 'LOCKing resource %s' % self.headers
+ log.debug('LOCKing resource %s' % self.headers)
body = None
if self.headers.has_key('Content-Length'):
@@ -113,29 +133,31 @@ class LockManager:
uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
uri = urllib.unquote(uri)
+ log.debug('do_LOCK: uri = %s' % uri)
ifheader = self.headers.get('If')
alreadylocked = self._l_isLocked(uri)
+ log.debug('do_LOCK: alreadylocked = %s' % alreadylocked)
if body and alreadylocked:
# Full LOCK request but resource already locked
self.responses[423] = ('Locked', 'Already locked')
return self.send_status(423)
-
+
elif body and not ifheader:
# LOCK with XML information
data = self._lock_unlock_parse(body)
token, result = self._lock_unlock_create(uri, 'unknown', depth, data)
if result:
- self.send_body(result, '207', 'Error', 'Error',
+ self.send_body(result, '207', 'Error', 'Error',
'text/xml; charset="utf-8"')
else:
lock = self._l_getLock(token)
- self.send_body(lock.asXML(), '200', 'OK', 'OK',
+ self.send_body(lock.asXML(), '200', 'OK', 'OK',
'text/xml; charset="utf-8"',
- {'Lock-Token' : 'opaquelocktoken:%s' % token})
+ {'Lock-Token' : '<opaquelocktoken:%s>' % token})
else:
@@ -166,7 +188,7 @@ class LockItem:
""" Lock with support for exclusive write locks. Some code taken from
webdav.LockItem from the Zope project. """
- def __init__(self, uri, creator, lockowner, depth=0, timeout='Infinite',
+ def __init__(self, uri, creator, lockowner, depth=0, timeout='Infinite',
locktype='write', lockscope='exclusive', token=None, **kw):
self.uri = uri
@@ -206,6 +228,12 @@ class LockItem:
self.modified = time.time()
def asXML(self, namespace='d', discover=False):
+ owner_str = ''
+ if isinstance(self.owner, str):
+ owner_str = self.owner
+ elif isinstance(self.owner, xml.dom.minicompat.NodeList):
+ owner_str = "".join([node.toxml() for node in self.owner[0].childNodes])
+
token = self.token
base = ('<%(ns)s:activelock>\n'
' <%(ns)s:locktype><%(ns)s:%(locktype)s/></%(ns)s:locktype>\n'
@@ -222,7 +250,7 @@ class LockItem:
'locktype': self.locktype,
'lockscope': self.lockscope,
'depth': self.depth,
- 'owner': self.owner and self.owner or '',
+ 'owner': owner_str,
'timeout': self.getTimeoutString(),
'locktoken': token,
}
diff --git a/DAV/propfind.py b/DAV/propfind.py
index da56db6..8d4650e 100644
--- a/DAV/propfind.py
+++ b/DAV/propfind.py
@@ -1,7 +1,24 @@
+#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
import xml.dom.minidom
domimpl = xml.dom.minidom.getDOMImplementation()
+import logging
import sys
import string
import urlparse
@@ -12,6 +29,8 @@ import utils
from constants import COLLECTION, OBJECT, DAV_PROPS, RT_ALLPROP, RT_PROPNAME, RT_PROP
from errors import *
+log = logging.getLogger(__name__)
+
class PROPFIND:
""" parse a propfind xml element and extract props
@@ -23,8 +42,8 @@ class PROPFIND:
The list of properties will contain tuples of the form
(element name, ns_prefix, ns_uri)
-
-
+
+
"""
def __init__(self,uri,dataclass,depth, body):
@@ -38,7 +57,7 @@ class PROPFIND:
self._has_body=None # did we parse a body?
if dataclass.verbose:
- print >>sys.stderr, 'PROPFIND: Depth is %s, URI is %s' % (depth, uri)
+ log.info('PROPFIND: Depth is %s, URI is %s' % (depth, uri))
if body:
self.request_type, self.proplist, self.namespaces = utils.parse_propfind(body)
@@ -66,10 +85,10 @@ class PROPFIND:
df = None
if self.request_type==RT_ALLPROP:
df = self.create_allprop()
-
+
if self.request_type==RT_PROPNAME:
df = self.create_propname()
-
+
if self.request_type==RT_PROP:
df = self.create_prop()
@@ -79,10 +98,10 @@ class PROPFIND:
# no body means ALLPROP!
df = self.create_allprop()
return df
-
+
def create_propname(self):
""" create a multistatus response for the prop names """
-
+
dc=self._dataclass
# create the document generator
doc = domimpl.createDocument(None, "multistatus", None)
@@ -99,7 +118,7 @@ class PROPFIND:
pnames=dc.get_propnames(self._uri)
re=self.mk_propname_response(self._uri,pnames, doc)
ms.appendChild(re)
-
+
for newuri in dc.get_childs(self._uri):
pnames=dc.get_propnames(newuri)
re=self.mk_propname_response(newuri,pnames, doc)
@@ -124,7 +143,7 @@ class PROPFIND:
for ns,plist in self._dataclass.get_propnames(self._uri).items():
self.proplist[ns]=plist
self.namespaces.append(ns)
-
+
return self.create_prop()
def create_prop(self):
@@ -137,7 +156,7 @@ class PROPFIND:
2. read the property values for each URI
(which is dependant on the Depth header)
This is done by the get_propvalues() method.
-
+
3. For each URI call the append_result() method
to append the actual <result>-Tag to the result
document.
@@ -160,12 +179,12 @@ class PROPFIND:
gp,bp=self.get_propvalues(self._uri)
res=self.mk_prop_response(self._uri,gp,bp,doc)
ms.appendChild(res)
-
+
elif self._depth=="1":
gp,bp=self.get_propvalues(self._uri)
res=self.mk_prop_response(self._uri,gp,bp,doc)
ms.appendChild(res)
-
+
for newuri in self._dataclass.get_childs(self._uri):
gp,bp=self.get_propvalues(newuri)
res=self.mk_prop_response(newuri,gp,bp,doc)
@@ -188,7 +207,7 @@ class PROPFIND:
This will simply format the propnames list.
propnames should have the format {NS1 : [prop1, prop2, ...], NS2: ...}
-
+
"""
re=doc.createElement("D:response")
@@ -199,7 +218,7 @@ class PROPFIND:
huri=doc.createTextNode(uparts[0]+'://'+'/'.join(uparts[1:2]) + urllib.quote(fileloc))
href.appendChild(huri)
re.appendChild(href)
-
+
ps=doc.createElement("D:propstat")
nsnum=0
@@ -214,7 +233,7 @@ class PROPFIND:
for p in plist:
pe=doc.createElement(nsp+":"+p)
pr.appendChild(pe)
-
+
ps.appendChild(pr)
re.appendChild(ps)
@@ -226,15 +245,16 @@ class PROPFIND:
We differ between the good props and the bad ones for
each generating an extra <propstat>-Node (for each error
one, that means).
-
+
"""
re=doc.createElement("D:response")
# append namespaces to response
nsnum=0
for nsname in self.namespaces:
- re.setAttribute("xmlns:ns"+str(nsnum),nsname)
+ if nsname != 'DAV:':
+ re.setAttribute("xmlns:ns"+str(nsnum),nsname)
nsnum=nsnum+1
-
+
# write href information
uparts=urlparse.urlparse(uri)
fileloc=uparts[2]
@@ -250,12 +270,18 @@ class PROPFIND:
gp=doc.createElement("D:prop")
for ns in good_props.keys():
- ns_prefix="ns"+str(self.namespaces.index(ns))+":"
+ if ns != 'DAV:':
+ ns_prefix="ns"+str(self.namespaces.index(ns))+":"
+ else:
+ ns_prefix = 'D:'
for p,v in good_props[ns].items():
pe=doc.createElement(ns_prefix+str(p))
if hasattr(v, '__class__') and v.__class__.__name__ == 'Element':
pe.appendChild(v)
+ elif isinstance(v, list):
+ for val in v:
+ pe.appendChild(val)
else:
if p=="resourcetype":
if v==1:
@@ -266,7 +292,7 @@ class PROPFIND:
pe.appendChild(ve)
gp.appendChild(pe)
-
+
ps.appendChild(gp)
s=doc.createElement("D:status")
t=doc.createTextNode("HTTP/1.1 200 OK")
@@ -285,12 +311,15 @@ class PROPFIND:
ps.appendChild(bp)
for ns in bad_props[ecode].keys():
- ns_prefix="ns"+str(self.namespaces.index(ns))+":"
-
- for p in bad_props[ecode][ns]:
- pe=doc.createElement(ns_prefix+str(p))
- bp.appendChild(pe)
-
+ if ns != 'DAV:':
+ ns_prefix="ns"+str(self.namespaces.index(ns))+":"
+ else:
+ ns_prefix = 'D:'
+
+ for p in bad_props[ecode][ns]:
+ pe=doc.createElement(ns_prefix+str(p))
+ bp.appendChild(pe)
+
s=doc.createElement("D:status")
t=doc.createTextNode(utils.gen_estring(ecode))
s.appendChild(t)
@@ -307,7 +336,7 @@ class PROPFIND:
which we found a value and the ones for which we
only got an error, either because they haven't been
found or the user is not allowed to read them.
-
+
"""
good_props={}
bad_props={}
@@ -315,7 +344,6 @@ class PROPFIND:
ddc = self._dataclass
for (ns,plist) in self.proplist.items():
good_props[ns]={}
- bad_props={}
for prop in plist:
ec = 0
try:
@@ -323,10 +351,10 @@ class PROPFIND:
good_props[ns][prop]=r
except DAV_Error, error_code:
ec=error_code[0]
-
+
# ignore props with error_code if 0 (invisible)
if ec==0: continue
-
+
if bad_props.has_key(ec):
if bad_props[ec].has_key(ns):
bad_props[ec][ns].append(prop)
diff --git a/DAV/propfind.py.orig b/DAV/propfind.py.orig
deleted file mode 100644
index 371d012..0000000
--- a/DAV/propfind.py.orig
+++ /dev/null
@@ -1,340 +0,0 @@
-#!/usr/bin/env python
-
-"""
- python davserver
- Copyright (C) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public
- License along with this library; if not, write to the Free
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-"""
-
-
-from xml.dom import ext
-from xml.dom.Document import Document
-
-import sys
-import string
-import urlparse
-import urllib
-from StringIO import StringIO
-
-import utils
-from constants import COLLECTION, OBJECT, DAV_PROPS, RT_ALLPROP, RT_PROPNAME, RT_PROP
-from errors import *
-
-class PROPFIND:
- """ parse a propfind xml element and extract props
-
- It will set the following instance vars:
-
- request_class : ALLPROP | PROPNAME | PROP
- proplist : list of properties
- nsmap : map of namespaces
-
- The list of properties will contain tuples of the form
- (element name, ns_prefix, ns_uri)
-
-
- """
-
-
- def __init__(self,uri,dataclass,depth):
- self.request_type=None
- self.nsmap={}
- self.proplist={}
- self.default_ns=None
- self.__dataclass=dataclass
- self.__depth=str(depth)
- self.__uri=uri
- self.__has_body=None # did we parse a body?
-
- def read_propfind(self,xml_doc):
- self.request_type,self.proplist,self.namespaces=utils.parse_propfind(xml_doc)
-
- def createResponse(self):
- """ create the multistatus response
-
- This will be delegated to the specific method
- depending on which request (allprop, propname, prop)
- was found.
-
- If we get a PROPNAME then we simply return the list with empty
- values which we get from the interface class
-
- If we get an ALLPROP we first get the list of properties and then
- we do the same as with a PROP method.
-
- """
-
- df = None
- if self.request_type==RT_ALLPROP:
- df = self.create_allprop()
-
- if self.request_type==RT_PROPNAME:
- df = self.create_propname()
-
- if self.request_type==RT_PROP:
- df = self.create_prop()
-
- if df != None:
- return df
-
- # no body means ALLPROP!
- df = self.create_allprop()
- return df
-
- def create_propname(self):
- """ create a multistatus response for the prop names """
-
- dc=self.__dataclass
- # create the document generator
- doc = Document(None)
- ms=doc.createElement("D:multistatus")
- ms.setAttribute("xmlns:D","DAV:")
- doc.appendChild(ms)
-
- if self.__depth=="0":
- pnames=dc.get_propnames(self.__uri)
- re=self.mk_propname_response(self.__uri,pnames, doc)
- ms.appendChild(re)
-
- elif self.__depth=="1":
- pnames=dc.get_propnames(self.__uri)
- re=self.mk_propname_response(self.__uri,pnames, doc)
- ms.appendChild(re)
-
- for newuri in dc.get_childs(self.__uri):
- pnames=dc.get_propnames(newuri)
- re=self.mk_propname_response(newuri,pnames, doc)
- ms.appendChild(re)
- # *** depth=="infinity"
-
- sfile=StringIO()
- ext.PrettyPrint(doc,stream=sfile)
- s=sfile.getvalue()
- sfile.close()
- return s
-
- def create_allprop(self):
- """ return a list of all properties """
- self.proplist={}
- self.namespaces=[]
- for ns,plist in self.__dataclass.get_propnames(self.__uri).items():
- self.proplist[ns]=plist
- self.namespaces.append(ns)
-
- return self.create_prop()
-
- def create_prop(self):
- """ handle a <prop> request
-
- This will
-
- 1. set up the <multistatus>-Framework
-
- 2. read the property values for each URI
- (which is dependant on the Depth header)
- This is done by the get_propvalues() method.
-
- 3. For each URI call the append_result() method
- to append the actual <result>-Tag to the result
- document.
-
- We differ between "good" properties, which have been
- assigned a value by the interface class and "bad"
- properties, which resulted in an error, either 404
- (Not Found) or 403 (Forbidden).
-
- """
-
-
- # create the document generator
- doc = Document(None)
- ms=doc.createElement("D:multistatus")
- ms.setAttribute("xmlns:D","DAV:")
- doc.appendChild(ms)
-
- if self.__depth=="0":
- gp,bp=self.get_propvalues(self.__uri)
- res=self.mk_prop_response(self.__uri,gp,bp,doc)
- ms.appendChild(res)
-
- elif self.__depth=="1":
- gp,bp=self.get_propvalues(self.__uri)
- res=self.mk_prop_response(self.__uri,gp,bp,doc)
- ms.appendChild(res)
-
- for newuri in self.__dataclass.get_childs(self.__uri):
- gp,bp=self.get_propvalues(newuri)
- res=self.mk_prop_response(newuri,gp,bp,doc)
- ms.appendChild(res)
-
- sfile=StringIO()
- ext.PrettyPrint(doc,stream=sfile)
- s=sfile.getvalue()
- sfile.close()
- return s
-
-
- def mk_propname_response(self,uri,propnames,doc):
- """ make a new <prop> result element for a PROPNAME request
-
- This will simply format the propnames list.
- propnames should have the format {NS1 : [prop1, prop2, ...], NS2: ...}
-
- """
- re=doc.createElement("D:response")
-
- # write href information
- uparts=urlparse.urlparse(uri)
- fileloc=uparts[2]
- href=doc.createElement("D:href")
- huri=doc.createTextNode(urllib.quote(fileloc))
- href.appendChild(huri)
- re.appendChild(href)
-
- ps=doc.createElement("D:propstat")
- nsnum=0
-
- for ns,plist in propnames.items():
- # write prop element
- pr=doc.createElement("D:prop")
- nsp="ns"+str(nsnum)
- pr.setAttribute("xmlns:"+nsp,ns)
- nsnum=nsnum+1
-
- # write propertynames
- for p in plist:
- pe=doc.createElement(nsp+":"+p)
- pr.appendChild(pe)
-
- ps.appendChild(pr)
- re.appendChild(ps)
-
- return re
-
- def mk_prop_response(self,uri,good_props,bad_props,doc):
- """ make a new <prop> result element
-
- We differ between the good props and the bad ones for
- each generating an extra <propstat>-Node (for each error
- one, that means).
-
- """
- re=doc.createElement("D:response")
- # append namespaces to response
- nsnum=0
- for nsname in self.namespaces:
- re.setAttribute("xmlns:ns"+str(nsnum),nsname)
- nsnum=nsnum+1
-
- # write href information
- uparts=urlparse.urlparse(uri)
- fileloc=uparts[2]
- href=doc.createElement("D:href")
- huri=doc.createTextNode(urllib.quote(fileloc))
- href.appendChild(huri)
- re.appendChild(href)
-
- # write good properties
- ps=doc.createElement("D:propstat")
- if good_props:
- re.appendChild(ps)
-
- gp=doc.createElement("D:prop")
- for ns in good_props.keys():
- ns_prefix="ns"+str(self.namespaces.index(ns))+":"
- for p,v in good_props[ns].items():
- pe=doc.createElement(ns_prefix+str(p))
- if p=="resourcetype":
- if v=="1":
- ve=doc.createElement("D:collection")
- pe.appendChild(ve)
- else:
- ve=doc.createTextNode(str(v))
- pe.appendChild(ve)
-
- gp.appendChild(pe)
-
- ps.appendChild(gp)
- s=doc.createElement("D:status")
- t=doc.createTextNode("HTTP/1.1 200 OK")
- s.appendChild(t)
- ps.appendChild(s)
- re.appendChild(ps)
-
- # now write the errors!
- if len(bad_props.items()):
-
- # write a propstat for each error code
- for ecode in bad_props.keys():
- ps=doc.createElement("D:propstat")
- re.appendChild(ps)
- bp=doc.createElement("D:prop")
- ps.appendChild(bp)
-
- for ns in bad_props[ecode].keys():
- ns_prefix="ns"+str(self.namespaces.index(ns))+":"
-
- for p in bad_props[ecode][ns]:
- pe=doc.createElement(ns_prefix+str(p))
- bp.appendChild(pe)
-
- s=doc.createElement("D:status")
- t=doc.createTextNode(utils.gen_estring(ecode))
- s.appendChild(t)
- ps.appendChild(s)
- re.appendChild(ps)
-
- # return the new response element
- return re
-
- def get_propvalues(self,uri):
- """ create lists of property values for an URI
-
- We create two lists for an URI: the properties for
- which we found a value and the ones for which we
- only got an error, either because they haven't been
- found or the user is not allowed to read them.
-
- """
- good_props={}
- bad_props={}
-
- ddc = self.__dataclass
- for (ns,plist) in self.proplist.items():
- good_props[ns]={}
- bad_props={}
- for prop in plist:
- ec = 0
- try:
- r=ddc.get_prop(uri,ns,prop)
- good_props[ns][prop]=str(r)
- except DAV_Error, error_code:
- ec=error_code[0]
-
- # ignore props with error_code if 0 (invisible)
- if ec==0: continue
-
- if bad_props.has_key(ec):
- if bad_props[ec].has_key(ns):
- bad_props[ec][ns].append(prop)
- else:
- bad_props[ec][ns]=[prop]
- else:
- bad_props[ec]={ns:[prop]}
-
- return good_props, bad_props
-
diff --git a/DAV/report.py b/DAV/report.py
index a12721d..39457b7 100644
--- a/DAV/report.py
+++ b/DAV/report.py
@@ -1,3 +1,19 @@
+#Copyright (c) 2009 Cedric Krier (cedric.krier at b2ck.com)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
from propfind import PROPFIND
from xml.dom import minidom
diff --git a/DAV/status.py b/DAV/status.py
index f719987..4133b40 100644
--- a/DAV/status.py
+++ b/DAV/status.py
@@ -1,6 +1,23 @@
+#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
+
"""
-status codes for DAV services
+status codes for DAV services
"""
diff --git a/DAV/utils.py b/DAV/utils.py
index 2aa1961..53da88d 100755
--- a/DAV/utils.py
+++ b/DAV/utils.py
@@ -1,3 +1,20 @@
+#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
+
import time
import re
@@ -10,7 +27,7 @@ from StringIO import StringIO
from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP
from BaseHTTPServer import BaseHTTPRequestHandler
-VERSION = '0.9.3'
+VERSION = '0.9.4-dev'
AUTHOR = 'Simon Pamies <s.pamies at banality.de>'
def gen_estring(ecode):
@@ -115,8 +132,9 @@ def get_parenturi(uri):
def make_xmlresponse(result):
""" construct a response from a dict of uri:error_code elements """
- doc = minidom.getDOMImplementation().createDocument(None, "D:multistatus", None)
+ doc = minidom.getDOMImplementation().createDocument(None, "multistatus", None)
doc.documentElement.setAttribute("xmlns:D","DAV:")
+ doc.documentElement.tagName = "D:multistatus"
for el,ec in result.items():
re=doc.createElementNS("DAV:","response")
diff --git a/DAVServer/config.ini b/DAVServer/config.ini
index f9740b3..c46187f 100644
--- a/DAVServer/config.ini
+++ b/DAVServer/config.ini
@@ -23,8 +23,12 @@ firstrun=0
[DAV]
# Verbose?
+# verbose enabled is like loglevel = INFO
verbose = 1
+#log level : DEBUG, INFO, WARNING, ERROR, CRITICAL (Default is WARNING)
+#loglevel = WARNING
+
# main directory
directory = /home/spamies/tmp
@@ -54,3 +58,9 @@ mimecheck = 1
# webdav level (1 = webdav level 2)
lockemulation = 1
+
+# internal features
+#chunked_http_response = 1
+#http_request_use_iterator = 0
+#http_response_use_iterator = 0
+
diff --git a/DAVServer/daemonize.py b/DAVServer/daemonize.py
index 84e527d..409fb3a 100644
--- a/DAVServer/daemonize.py
+++ b/DAVServer/daemonize.py
@@ -1,9 +1,30 @@
+#Copyright (c) 2005 Simon Pamies (s.pamies at banality.de)
+#Copyright (c) 2003 Clark Evans
+#Copyright (c) 2002 Noah Spurrier
+#Copyright (c) 2001 Juergen Hermann
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
+
+
'''
This module is used to fork the current process into a daemon.
- Almost none of this is necessary (or advisable) if your daemon
- is being started by inetd. In that case, stdin, stdout and stderr are
- all set up for you to refer to the network connection, and the fork()s
- and session manipulation should not be done (to avoid confusing inetd).
+ Almost none of this is necessary (or advisable) if your daemon
+ is being started by inetd. In that case, stdin, stdout and stderr are
+ all set up for you to refer to the network connection, and the fork()s
+ and session manipulation should not be done (to avoid confusing inetd).
Only the chdir() and umask() steps remain as useful.
References:
UNIX Programming FAQ
@@ -36,26 +57,26 @@ def deamonize(stdout='/dev/null', stderr=None, stdin='/dev/null',
may not appear in the order that you expect.
'''
# Do first fork.
- try:
- pid = os.fork()
+ try:
+ pid = os.fork()
if pid > 0: sys.exit(0) # Exit first parent.
- except OSError, e:
+ except OSError, e:
sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
sys.exit(1)
-
+
# Decouple from parent environment.
- os.chdir("/")
- os.umask(0)
- os.setsid()
-
+ os.chdir("/")
+ os.umask(0)
+ os.setsid()
+
# Do second fork.
- try:
- pid = os.fork()
+ try:
+ pid = os.fork()
if pid > 0: sys.exit(0) # Exit second parent.
- except OSError, e:
+ except OSError, e:
sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
sys.exit(1)
-
+
# Open file descriptors and print start message
if not stderr: stderr = stdout
si = file(stdin, 'r')
@@ -69,7 +90,7 @@ def deamonize(stdout='/dev/null', stderr=None, stdin='/dev/null',
if sys.stdin.closed: sys.stdin = open('/dev/null', 'r')
if sys.stdout.closed: sys.stdout = open('/dev/null', 'a+')
if sys.stderr.closed: sys.stderr = open('/dev/null', 'a+')
-
+
# Redirect standard file descriptors.
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
@@ -84,7 +105,7 @@ def startstop(stdout='/dev/null', stderr=None, stdin='/dev/null',
pf.close()
except IOError:
pid = None
-
+
if 'stop' == action or 'restart' == action:
if not pid:
mess = "Could not stop, pid file '%s' missing.\n"
@@ -109,7 +130,7 @@ def startstop(stdout='/dev/null', stderr=None, stdin='/dev/null',
else:
print str(err)
sys.exit(1)
-
+
if 'start' == action:
if pid:
mess = "Start aborted since pid file '%s' exists.\n"
diff --git a/DAVServer/fileauth.py b/DAVServer/fileauth.py
index 5668e50..f2a649d 100644
--- a/DAVServer/fileauth.py
+++ b/DAVServer/fileauth.py
@@ -1,55 +1,58 @@
-#!/usr/bin/env python
+#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
"""
Python WebDAV Server.
-Copyright (C) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public
- License along with this library; if not, write to the Free
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
This is an example implementation of a DAVserver using the DAV package.
"""
+import logging
from DAV.WebDAVServer import DAVRequestHandler
from fshandler import FilesystemHandler
import sys
from DAV.dbconn import Mconn
+log = logging.getLogger()
+
class DAVAuthHandler(DAVRequestHandler):
"""
Provides authentication based on parameters. The calling
class has to inject password and username into this.
(Variables: auth_user and auth_pass)
"""
-
+
# Do not forget to set IFACE_CLASS by caller
# ex.: IFACE_CLASS = FilesystemHandler('/tmp', 'http://localhost/')
verbose = False
-
+
def _log(self, message):
if self.verbose:
- print >>sys.stderr, '>> (DAVAuthHandler) %s' % message
+ log.info(message)
def get_userinfo(self,user,pw,command):
""" authenticate user """
if user == self._config.DAV.user and pw == self._config.DAV.password:
- self._log('Successfully authenticated user %s' % user)
+ log.info('Successfully authenticated user %s' % user)
return 1
- self._log('Authentication failed for user %s' % user)
+ log.info('Authentication failed for user %s' % user)
return 0
diff --git a/DAVServer/fshandler.py b/DAVServer/fshandler.py
index 8d464fb..70e366f 100644
--- a/DAVServer/fshandler.py
+++ b/DAVServer/fshandler.py
@@ -1,9 +1,28 @@
+#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
import sys
import urlparse
import os
import time
from string import joinfields, split, lower
+import logging
+import types
+import shutil
from DAV.constants import COLLECTION, OBJECT
from DAV.errors import *
@@ -11,14 +30,43 @@ from DAV.iface import *
from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree
+log = logging.getLogger(__name__)
+
+BUFFER_SIZE = 128 * 1000
# include magic support to correctly determine mimetypes
MAGIC_AVAILABLE = False
try:
- import magic
+ import mimetypes
MAGIC_AVAILABLE = True
except ImportError:
pass
+class Resource(object):
+ # XXX this class is ugly
+ def __init__(self, fp, file_size):
+ self.__fp = fp
+ self.__file_size = file_size
+
+ def __len__(self):
+ return self.__file_size
+
+ def __iter__(self):
+ while 1:
+ data = self.__fp.read(BUFFER_SIZE)
+ if not data:
+ break
+ yield data
+ time.sleep(0.005)
+ self.__fp.close()
+
+ def read(self, length = 0):
+ if length == 0:
+ length = self.__file_size
+
+ data = self.__fp.read(length)
+ return data
+
+
class FilesystemHandler(dav_interface):
"""
Model a filesystem for DAV
@@ -39,18 +87,14 @@ class FilesystemHandler(dav_interface):
# should we be verbose?
self.verbose = verbose
- self._log('Initialized with %s-%s' % (directory, uri))
-
- def _log(self, message):
- if self.verbose:
- print >>sys.stderr, '>> (FilesystemHandler) %s' % message
+ log.info('Initialized with %s-%s' % (directory, uri))
def setDirectory(self, path):
""" Sets the directory """
if not os.path.isdir(path):
raise Exception, '%s not must be a directory!' % path
-
+
self.directory = path
def setBaseURI(self, uri):
@@ -79,7 +123,7 @@ class FilesystemHandler(dav_interface):
def get_childs(self,uri):
""" return the child objects as self.baseuris for the given URI """
-
+
fileloc=self.uri2local(uri)
filelist=[]
@@ -89,34 +133,51 @@ class FilesystemHandler(dav_interface):
files=os.listdir(fileloc)
except:
raise DAV_NotFound
-
+
for file in files:
newloc=os.path.join(fileloc,file)
filelist.append(self.local2uri(newloc))
-
- self._log('get_childs: Childs %s' % filelist)
+
+ log.info('get_childs: Childs %s' % filelist)
return filelist
- def get_data(self,uri):
+ def get_data(self,uri, range = None):
""" return the content of an object """
path=self.uri2local(uri)
if os.path.exists(path):
if os.path.isfile(path):
- s=""
- fp=open(path,"r")
- while 1:
- a=fp.read()
- if not a: break
- s=s+a
- fp.close()
- self._log('Serving content of %s' % uri)
- return s
+ file_size = os.path.getsize(path)
+ if range == None:
+ fp=open(path,"r")
+ log.info('Serving content of %s' % uri)
+ return Resource(fp, file_size)
+ else:
+ if range[0] == '':
+ range[0] = 0
+ else:
+ range[0] = int(range[0])
+
+ if range[1] == '':
+ range[1] = file_size
+ else:
+ range[1] = int(range[1])
+
+ if range[0] > file_size:
+ raise DAV_Requested_Range_Not_Satisfiable
+
+ if range[1] > file_size:
+ range[1] = file_size
+
+ fp=open(path,"r")
+ fp.seek(range[0])
+ log.info('Serving range %s -> %s content of %s' % (range[0], range[1], uri))
+ return Resource(fp, range[1] - range[0])
else:
# also raise an error for collections
# don't know what should happen then..
- self._log('get_data: %s not found' % path)
-
+ log.info('get_data: %s not found' % path)
+
raise DAV_NotFound
def _get_dav_resourcetype(self,uri):
@@ -129,7 +190,7 @@ class FilesystemHandler(dav_interface):
return COLLECTION
raise DAV_NotFound
-
+
def _get_dav_displayname(self,uri):
raise DAV_Secret # do not show
@@ -140,7 +201,7 @@ class FilesystemHandler(dav_interface):
if os.path.isfile(path):
s=os.stat(path)
return str(s[6])
-
+
return '0'
def get_lastmodified(self,uri):
@@ -173,7 +234,7 @@ class FilesystemHandler(dav_interface):
or self.mimecheck is False:
return 'application/octet-stream'
else:
- ret = magic.file(path)
+ ret, encoding = mimetypes.guess_type(path)
# for non mimetype related result we
# simply return an appropriate type
@@ -190,16 +251,21 @@ class FilesystemHandler(dav_interface):
raise DAV_NotFound, 'Could not find %s' % path
- def put(self,uri,data,content_type=None):
+ def put(self, uri, data, content_type=None):
""" put the object into the filesystem """
path=self.uri2local(uri)
try:
- fp=open(path,"w+")
- fp.write(data)
+ fp=open(path, "w+")
+ if isinstance(data, types.GeneratorType):
+ for d in data:
+ fp.write(d)
+ else:
+ if data:
+ fp.write(data)
fp.close()
- self._log('put: Created %s' % uri)
+ log.info('put: Created %s' % uri)
except:
- self._log('put: Could not create %s' % uri)
+ log.info('put: Could not create %s' % uri)
raise DAV_Error, 424
return None
@@ -222,11 +288,11 @@ class FilesystemHandler(dav_interface):
# test, if we are allowed to create it
try:
- os.system("mkdir '%s'" % path)
- self._log('mkcol: Created new collection %s' % path)
+ os.mkdir(path)
+ log.info('mkcol: Created new collection %s' % path)
return 201
except:
- self._log('mkcol: Creation of %s denied' % path)
+ log.info('mkcol: Creation of %s denied' % path)
raise DAV_Forbidden
### ?? should we do the handler stuff for DELETE, too ?
@@ -238,22 +304,27 @@ class FilesystemHandler(dav_interface):
if not os.path.exists(path):
raise DAV_NotFound
- if not os.system("rmdir '%s'" %path):
- return 204
- else:
+ try:
+ shutil.rmtree(path)
+ except OSError:
raise DAV_Forbidden # forbidden
+
+ return 204
def rm(self,uri):
""" delete a normal resource """
path=self.uri2local(uri)
if not os.path.exists(path):
raise DAV_NotFound
- if not os.system("rm -f '%s'" %path):
- return 204
- else:
- self._log('rm: Forbidden')
+
+ try:
+ os.unlink(path)
+ except OSError, ex:
+ log.info('rm: Forbidden (%s)' % ex)
raise DAV_Forbidden # forbidden
+ return 204
+
###
### DELETE handlers (examples)
### (we use the predefined methods in davcmd instead of doing
@@ -261,12 +332,12 @@ class FilesystemHandler(dav_interface):
###
def delone(self,uri):
- """ delete a single resource
+ """ delete a single resource
You have to return a result dict of the form
uri:error_code
or None if everything's ok
-
+
"""
return delone(self,uri)
@@ -286,40 +357,14 @@ class FilesystemHandler(dav_interface):
###
def moveone(self,src,dst,overwrite):
- """ move one resource with Depth=0
-
- an alternative implementation would be
-
- result_code=201
- if overwrite:
- result_code=204
- r=os.system("rm -f '%s'" %dst)
- if r: return 412
- r=os.system("mv '%s' '%s'" %(src,dst))
- if r: return 412
- return result_code
-
- (untested!). This would not use the davcmd functions
- and thus can only detect errors directly on the root node.
+ """ move one resource with Depth=0
"""
+
return moveone(self,src,dst,overwrite)
def movetree(self,src,dst,overwrite):
""" move a collection with Depth=infinity
-
- an alternative implementation would be
-
- result_code=201
- if overwrite:
- result_code=204
- r=os.system("rm -rf '%s'" %dst)
- if r: return 412
- r=os.system("mv '%s' '%s'" %(src,dst))
- if r: return 412
- return result_code
-
- (untested!). This would not use the davcmd functions
- and thus can only detect errors directly on the root node"""
+ """
return movetree(self,src,dst,overwrite)
@@ -328,40 +373,14 @@ class FilesystemHandler(dav_interface):
###
def copyone(self,src,dst,overwrite):
- """ copy one resource with Depth=0
-
- an alternative implementation would be
-
- result_code=201
- if overwrite:
- result_code=204
- r=os.system("rm -f '%s'" %dst)
- if r: return 412
- r=os.system("cp '%s' '%s'" %(src,dst))
- if r: return 412
- return result_code
-
- (untested!). This would not use the davcmd functions
- and thus can only detect errors directly on the root node.
+ """ copy one resource with Depth=0
"""
+
return copyone(self,src,dst,overwrite)
def copytree(self,src,dst,overwrite):
""" copy a collection with Depth=infinity
-
- an alternative implementation would be
-
- result_code=201
- if overwrite:
- result_code=204
- r=os.system("rm -rf '%s'" %dst)
- if r: return 412
- r=os.system("cp -r '%s' '%s'" %(src,dst))
- if r: return 412
- return result_code
-
- (untested!). This would not use the davcmd functions
- and thus can only detect errors directly on the root node"""
+ """
return copytree(self,src,dst,overwrite)
@@ -375,15 +394,16 @@ class FilesystemHandler(dav_interface):
def copy(self,src,dst):
""" copy a resource from src to dst """
+
srcfile=self.uri2local(src)
dstfile=self.uri2local(dst)
try:
- os.system("cp '%s' '%s'" %(srcfile,dstfile))
- except:
- self._log('copy: forbidden')
+ shutil.copy(srcfile, dstfile)
+ except OSError:
+ log.info('copy: forbidden')
raise DAV_Error, Forbidden
- def copycol(self,src,dst):
+ def copycol(self, src, dst):
""" copy a collection.
As this is not recursive (the davserver recurses itself)
@@ -394,7 +414,6 @@ class FilesystemHandler(dav_interface):
return self.mkcol(dst)
-
def exists(self,uri):
""" test if a resource exists """
path=self.uri2local(uri)
@@ -409,4 +428,3 @@ class FilesystemHandler(dav_interface):
return 1
else:
return 0
-
diff --git a/DAVServer/magic.py b/DAVServer/magic.py
deleted file mode 100644
index b95951b..0000000
--- a/DAVServer/magic.py
+++ /dev/null
@@ -1,1118 +0,0 @@
-#!/usr/bin/env python
-'''
-magic.py
- determines a file type by its magic number
-
- (C)opyright 2000 Jason Petrone <jp_py at jsnp.net>
- All Rights Reserved
-
- Command Line Usage: running as `python magic.py file` will print
- a description of what 'file' is.
-
- Module Usage:
- magic.whatis(data): when passed a string 'data' containing
- binary or text data, a description of
- what the data is will be returned.
-
- magic.file(filename): returns a description of what the file
- 'filename' contains.
-'''
-
-import re, struct, string
-
-__version__ = '0.1'
-
-magic = [
- [0L, 'leshort', '=', 1538L, 'application/x-alan-adventure-game'],
- [0L, 'string', '=', 'TADS', 'application/x-tads-game'],
- [0L, 'short', '=', 420L, 'application/x-executable-file'],
- [0L, 'short', '=', 421L, 'application/x-executable-file'],
- [0L, 'leshort', '=', 603L, 'application/x-executable-file'],
- [0L, 'string', '=', 'Core\001', 'application/x-executable-file'],
- [0L, 'string', '=', 'AMANDA: TAPESTART DATE', 'application/x-amanda-header'],
- [0L, 'belong', '=', 1011L, 'application/x-executable-file'],
- [0L, 'belong', '=', 999L, 'application/x-library-file'],
- [0L, 'belong', '=', 435L, 'video/mpeg'],
- [0L, 'belong', '=', 442L, 'video/mpeg'],
- [0L, 'beshort&0xfff0', '=', 65520L, 'audio/mpeg'],
- [4L, 'leshort', '=', 44817L, 'video/fli'],
- [4L, 'leshort', '=', 44818L, 'video/flc'],
- [0L, 'string', '=', 'MOVI', 'video/x-sgi-movie'],
- [4L, 'string', '=', 'moov', 'video/quicktime'],
- [4L, 'string', '=', 'mdat', 'video/quicktime'],
- [0L, 'long', '=', 100554L, 'application/x-apl-workspace'],
- [0L, 'string', '=', 'FiLeStArTfIlEsTaRt', 'text/x-apple-binscii'],
- [0L, 'string', '=', '\012GL', 'application/data'],
- [0L, 'string', '=', 'v\377', 'application/data'],
- [0L, 'string', '=', 'NuFile', 'application/data'],
- [0L, 'string', '=', 'N\365F\351l\345', 'application/data'],
- [0L, 'belong', '=', 333312L, 'application/data'],
- [0L, 'belong', '=', 333319L, 'application/data'],
- [257L, 'string', '=', 'ustar\000', 'application/x-tar'],
- [257L, 'string', '=', 'ustar \000', 'application/x-gtar'],
- [0L, 'short', '=', 70707L, 'application/x-cpio'],
- [0L, 'short', '=', 143561L, 'application/x-bcpio'],
- [0L, 'string', '=', '070707', 'application/x-cpio'],
- [0L, 'string', '=', '070701', 'application/x-cpio'],
- [0L, 'string', '=', '070702', 'application/x-cpio'],
- [0L, 'string', '=', '!<arch>\012debian', 'application/x-dpkg'],
- [0L, 'long', '=', 177555L, 'application/x-ar'],
- [0L, 'short', '=', 177555L, 'application/data'],
- [0L, 'long', '=', 177545L, 'application/data'],
- [0L, 'short', '=', 177545L, 'application/data'],
- [0L, 'long', '=', 100554L, 'application/x-apl-workspace'],
- [0L, 'string', '=', '<ar>', 'application/x-ar'],
- [0L, 'string', '=', '!<arch>\012__________E', 'application/x-ar'],
- [0L, 'string', '=', '-h-', 'application/data'],
- [0L, 'string', '=', '!<arch>', 'application/x-ar'],
- [0L, 'string', '=', '<ar>', 'application/x-ar'],
- [0L, 'string', '=', '<ar>', 'application/x-ar'],
- [0L, 'belong', '=', 1711210496L, 'application/x-ar'],
- [0L, 'belong', '=', 1013019198L, 'application/x-ar'],
- [0L, 'long', '=', 557605234L, 'application/x-ar'],
- [0L, 'lelong', '=', 177555L, 'application/data'],
- [0L, 'leshort', '=', 177555L, 'application/data'],
- [0L, 'lelong', '=', 177545L, 'application/data'],
- [0L, 'leshort', '=', 177545L, 'application/data'],
- [0L, 'lelong', '=', 236525L, 'application/data'],
- [0L, 'lelong', '=', 236526L, 'application/data'],
- [0L, 'lelong&0x8080ffff', '=', 2074L, 'application/x-arc'],
- [0L, 'lelong&0x8080ffff', '=', 2330L, 'application/x-arc'],
- [0L, 'lelong&0x8080ffff', '=', 538L, 'application/x-arc'],
- [0L, 'lelong&0x8080ffff', '=', 794L, 'application/x-arc'],
- [0L, 'lelong&0x8080ffff', '=', 1050L, 'application/x-arc'],
- [0L, 'lelong&0x8080ffff', '=', 1562L, 'application/x-arc'],
- [0L, 'string', '=', '\032archive', 'application/data'],
- [0L, 'leshort', '=', 60000L, 'application/x-arj'],
- [0L, 'string', '=', 'HPAK', 'application/data'],
- [0L, 'string', '=', '\351,\001JAM application/data', ''],
- [2L, 'string', '=', '-lh0-', 'application/x-lha'],
- [2L, 'string', '=', '-lh1-', 'application/x-lha'],
- [2L, 'string', '=', '-lz4-', 'application/x-lha'],
- [2L, 'string', '=', '-lz5-', 'application/x-lha'],
- [2L, 'string', '=', '-lzs-', 'application/x-lha'],
- [2L, 'string', '=', '-lh -', 'application/x-lha'],
- [2L, 'string', '=', '-lhd-', 'application/x-lha'],
- [2L, 'string', '=', '-lh2-', 'application/x-lha'],
- [2L, 'string', '=', '-lh3-', 'application/x-lha'],
- [2L, 'string', '=', '-lh4-', 'application/x-lha'],
- [2L, 'string', '=', '-lh5-', 'application/x-lha'],
- [0L, 'string', '=', 'Rar!', 'application/x-rar'],
- [0L, 'string', '=', 'SQSH', 'application/data'],
- [0L, 'string', '=', 'UC2\032', 'application/data'],
- [0L, 'string', '=', 'PK\003\004', 'application/zip'],
- [20L, 'lelong', '=', 4257523676L, 'application/x-zoo'],
- [10L, 'string', '=', '# This is a shell archive', 'application/x-shar'],
- [0L, 'string', '=', '*STA', 'application/data'],
- [0L, 'string', '=', '2278', 'application/data'],
- [0L, 'beshort', '=', 560L, 'application/x-executable-file'],
- [0L, 'beshort', '=', 561L, 'application/x-executable-file'],
- [0L, 'string', '=', '\000\004\036\212\200', 'application/core'],
- [0L, 'string', '=', '.snd', 'audio/basic'],
- [0L, 'lelong', '=', 6583086L, 'audio/basic'],
- [0L, 'string', '=', 'MThd', 'audio/midi'],
- [0L, 'string', '=', 'CTMF', 'audio/x-cmf'],
- [0L, 'string', '=', 'SBI', 'audio/x-sbi'],
- [0L, 'string', '=', 'Creative Voice File', 'audio/x-voc'],
- [0L, 'belong', '=', 1314148939L, 'audio/x-multitrack'],
- [0L, 'string', '=', 'RIFF', 'audio/x-wav'],
- [0L, 'string', '=', 'EMOD', 'audio/x-emod'],
- [0L, 'belong', '=', 779248125L, 'audio/x-pn-realaudio'],
- [0L, 'string', '=', 'MTM', 'audio/x-multitrack'],
- [0L, 'string', '=', 'if', 'audio/x-669-mod'],
- [0L, 'string', '=', 'FAR', 'audio/mod'],
- [0L, 'string', '=', 'MAS_U', 'audio/x-multimate-mod'],
- [44L, 'string', '=', 'SCRM', 'audio/x-st3-mod'],
- [0L, 'string', '=', 'GF1PATCH110\000ID#000002\000', 'audio/x-gus-patch'],
- [0L, 'string', '=', 'GF1PATCH100\000ID#000002\000', 'audio/x-gus-patch'],
- [0L, 'string', '=', 'JN', 'audio/x-669-mod'],
- [0L, 'string', '=', 'UN05', 'audio/x-mikmod-uni'],
- [0L, 'string', '=', 'Extended Module:', 'audio/x-ft2-mod'],
- [21L, 'string', '=', '!SCREAM!', 'audio/x-st2-mod'],
- [1080L, 'string', '=', 'M.K.', 'audio/x-protracker-mod'],
- [1080L, 'string', '=', 'M!K!', 'audio/x-protracker-mod'],
- [1080L, 'string', '=', 'FLT4', 'audio/x-startracker-mod'],
- [1080L, 'string', '=', '4CHN', 'audio/x-fasttracker-mod'],
- [1080L, 'string', '=', '6CHN', 'audio/x-fasttracker-mod'],
- [1080L, 'string', '=', '8CHN', 'audio/x-fasttracker-mod'],
- [1080L, 'string', '=', 'CD81', 'audio/x-oktalyzer-mod'],
- [1080L, 'string', '=', 'OKTA', 'audio/x-oktalyzer-mod'],
- [1080L, 'string', '=', '16CN', 'audio/x-taketracker-mod'],
- [1080L, 'string', '=', '32CN', 'audio/x-taketracker-mod'],
- [0L, 'string', '=', 'TOC', 'audio/x-toc'],
- [0L, 'short', '=', 3401L, 'application/x-executable-file'],
- [0L, 'long', '=', 406L, 'application/x-executable-file'],
- [0L, 'short', '=', 406L, 'application/x-executable-file'],
- [0L, 'short', '=', 3001L, 'application/x-executable-file'],
- [0L, 'lelong', '=', 314L, 'application/x-executable-file'],
- [0L, 'string', '=', '//', 'text/cpp'],
- [0L, 'string', '=', '\\\\1cw\\', 'application/data'],
- [0L, 'string', '=', '\\\\1cw', 'application/data'],
- [0L, 'belong&0xffffff00', '=', 2231440384L, 'application/data'],
- [0L, 'belong&0xffffff00', '=', 2231487232L, 'application/data'],
- [0L, 'short', '=', 575L, 'application/x-executable-file'],
- [0L, 'short', '=', 577L, 'application/x-executable-file'],
- [4L, 'string', '=', 'pipe', 'application/data'],
- [4L, 'string', '=', 'prof', 'application/data'],
- [0L, 'string', '=', ': shell', 'application/data'],
- [0L, 'string', '=', '#!/bin/sh', 'application/x-sh'],
- [0L, 'string', '=', '#! /bin/sh', 'application/x-sh'],
- [0L, 'string', '=', '#! /bin/sh', 'application/x-sh'],
- [0L, 'string', '=', '#!/bin/csh', 'application/x-csh'],
- [0L, 'string', '=', '#! /bin/csh', 'application/x-csh'],
- [0L, 'string', '=', '#! /bin/csh', 'application/x-csh'],
- [0L, 'string', '=', '#!/bin/ksh', 'application/x-ksh'],
- [0L, 'string', '=', '#! /bin/ksh', 'application/x-ksh'],
- [0L, 'string', '=', '#! /bin/ksh', 'application/x-ksh'],
- [0L, 'string', '=', '#!/bin/tcsh', 'application/x-csh'],
- [0L, 'string', '=', '#! /bin/tcsh', 'application/x-csh'],
- [0L, 'string', '=', '#! /bin/tcsh', 'application/x-csh'],
- [0L, 'string', '=', '#!/usr/local/tcsh', 'application/x-csh'],
- [0L, 'string', '=', '#! /usr/local/tcsh', 'application/x-csh'],
- [0L, 'string', '=', '#!/usr/local/bin/tcsh', 'application/x-csh'],
- [0L, 'string', '=', '#! /usr/local/bin/tcsh', 'application/x-csh'],
- [0L, 'string', '=', '#! /usr/local/bin/tcsh', 'application/x-csh'],
- [0L, 'string', '=', '#!/usr/local/bin/zsh', 'application/x-zsh'],
- [0L, 'string', '=', '#! /usr/local/bin/zsh', 'application/x-zsh'],
- [0L, 'string', '=', '#! /usr/local/bin/zsh', 'application/x-zsh'],
- [0L, 'string', '=', '#!/usr/local/bin/ash', 'application/x-sh'],
- [0L, 'string', '=', '#! /usr/local/bin/ash', 'application/x-zsh'],
- [0L, 'string', '=', '#! /usr/local/bin/ash', 'application/x-zsh'],
- [0L, 'string', '=', '#!/usr/local/bin/ae', 'text/script'],
- [0L, 'string', '=', '#! /usr/local/bin/ae', 'text/script'],
- [0L, 'string', '=', '#! /usr/local/bin/ae', 'text/script'],
- [0L, 'string', '=', '#!/bin/nawk', 'application/x-awk'],
- [0L, 'string', '=', '#! /bin/nawk', 'application/x-awk'],
- [0L, 'string', '=', '#! /bin/nawk', 'application/x-awk'],
- [0L, 'string', '=', '#!/usr/bin/nawk', 'application/x-awk'],
- [0L, 'string', '=', '#! /usr/bin/nawk', 'application/x-awk'],
- [0L, 'string', '=', '#! /usr/bin/nawk', 'application/x-awk'],
- [0L, 'string', '=', '#!/usr/local/bin/nawk', 'application/x-awk'],
- [0L, 'string', '=', '#! /usr/local/bin/nawk', 'application/x-awk'],
- [0L, 'string', '=', '#! /usr/local/bin/nawk', 'application/x-awk'],
- [0L, 'string', '=', '#!/bin/gawk', 'application/x-awk'],
- [0L, 'string', '=', '#! /bin/gawk', 'application/x-awk'],
- [0L, 'string', '=', '#! /bin/gawk', 'application/x-awk'],
- [0L, 'string', '=', '#!/usr/bin/gawk', 'application/x-awk'],
- [0L, 'string', '=', '#! /usr/bin/gawk', 'application/x-awk'],
- [0L, 'string', '=', '#! /usr/bin/gawk', 'application/x-awk'],
- [0L, 'string', '=', '#!/usr/local/bin/gawk', 'application/x-awk'],
- [0L, 'string', '=', '#! /usr/local/bin/gawk', 'application/x-awk'],
- [0L, 'string', '=', '#! /usr/local/bin/gawk', 'application/x-awk'],
- [0L, 'string', '=', '#!/bin/awk', 'application/x-awk'],
- [0L, 'string', '=', '#! /bin/awk', 'application/x-awk'],
- [0L, 'string', '=', '#! /bin/awk', 'application/x-awk'],
- [0L, 'string', '=', '#!/usr/bin/awk', 'application/x-awk'],
- [0L, 'string', '=', '#! /usr/bin/awk', 'application/x-awk'],
- [0L, 'string', '=', '#! /usr/bin/awk', 'application/x-awk'],
- [0L, 'string', '=', 'BEGIN', 'application/x-awk'],
- [0L, 'string', '=', '#!/bin/perl', 'application/x-perl'],
- [0L, 'string', '=', '#! /bin/perl', 'application/x-perl'],
- [0L, 'string', '=', '#! /bin/perl', 'application/x-perl'],
- [0L, 'string', '=', 'eval "exec /bin/perl', 'application/x-perl'],
- [0L, 'string', '=', '#!/usr/bin/perl', 'application/x-perl'],
- [0L, 'string', '=', '#! /usr/bin/perl', 'application/x-perl'],
- [0L, 'string', '=', '#! /usr/bin/perl', 'application/x-perl'],
- [0L, 'string', '=', 'eval "exec /usr/bin/perl', 'application/x-perl'],
- [0L, 'string', '=', '#!/usr/local/bin/perl', 'application/x-perl'],
- [0L, 'string', '=', '#! /usr/local/bin/perl', 'application/x-perl'],
- [0L, 'string', '=', '#! /usr/local/bin/perl', 'application/x-perl'],
- [0L, 'string', '=', 'eval "exec /usr/local/bin/perl', 'application/x-perl'],
- [0L, 'string', '=', '#!/bin/python', 'application/x-python'],
- [0L, 'string', '=', '#! /bin/python', 'application/x-python'],
- [0L, 'string', '=', '#! /bin/python', 'application/x-python'],
- [0L, 'string', '=', 'eval "exec /bin/python', 'application/x-python'],
- [0L, 'string', '=', '#!/usr/bin/python', 'application/x-python'],
- [0L, 'string', '=', '#! /usr/bin/python', 'application/x-python'],
- [0L, 'string', '=', '#! /usr/bin/python', 'application/x-python'],
- [0L, 'string', '=', 'eval "exec /usr/bin/python', 'application/x-python'],
- [0L, 'string', '=', '#!/usr/local/bin/python', 'application/x-python'],
- [0L, 'string', '=', '#! /usr/local/bin/python', 'application/x-python'],
- [0L, 'string', '=', '#! /usr/local/bin/python', 'application/x-python'],
- [0L, 'string', '=', 'eval "exec /usr/local/bin/python', 'application/x-python'],
- [0L, 'string', '=', '#!/usr/bin/env python', 'application/x-python'],
- [0L, 'string', '=', '#! /usr/bin/env python', 'application/x-python'],
- [0L, 'string', '=', '#!/bin/rc', 'text/script'],
- [0L, 'string', '=', '#! /bin/rc', 'text/script'],
- [0L, 'string', '=', '#! /bin/rc', 'text/script'],
- [0L, 'string', '=', '#!/bin/bash', 'application/x-sh'],
- [0L, 'string', '=', '#! /bin/bash', 'application/x-sh'],
- [0L, 'string', '=', '#! /bin/bash', 'application/x-sh'],
- [0L, 'string', '=', '#!/usr/local/bin/bash', 'application/x-sh'],
- [0L, 'string', '=', '#! /usr/local/bin/bash', 'application/x-sh'],
- [0L, 'string', '=', '#! /usr/local/bin/bash', 'application/x-sh'],
- [0L, 'string', '=', '#! /', 'text/script'],
- [0L, 'string', '=', '#! /', 'text/script'],
- [0L, 'string', '=', '#!/', 'text/script'],
- [0L, 'string', '=', '#! text/script', ''],
- [0L, 'string', '=', '\037\235', 'application/compress'],
- [0L, 'string', '=', '\037\213', 'application/x-gzip'],
- [0L, 'string', '=', '\037\036', 'application/data'],
- [0L, 'short', '=', 17437L, 'application/data'],
- [0L, 'short', '=', 8191L, 'application/data'],
- [0L, 'string', '=', '\377\037', 'application/data'],
- [0L, 'short', '=', 145405L, 'application/data'],
- [0L, 'string', '=', 'BZh', 'application/x-bzip2'],
- [0L, 'leshort', '=', 65398L, 'application/data'],
- [0L, 'leshort', '=', 65142L, 'application/data'],
- [0L, 'leshort', '=', 64886L, 'application/x-lzh'],
- [0L, 'string', '=', '\037\237', 'application/data'],
- [0L, 'string', '=', '\037\236', 'application/data'],
- [0L, 'string', '=', '\037\240', 'application/data'],
- [0L, 'string', '=', 'BZ', 'application/x-bzip'],
- [0L, 'string', '=', '\211LZO\000\015\012\032\012', 'application/data'],
- [0L, 'belong', '=', 507L, 'application/x-object-file'],
- [0L, 'belong', '=', 513L, 'application/x-executable-file'],
- [0L, 'belong', '=', 515L, 'application/x-executable-file'],
- [0L, 'belong', '=', 517L, 'application/x-executable-file'],
- [0L, 'belong', '=', 70231L, 'application/core'],
- [24L, 'belong', '=', 60011L, 'application/data'],
- [24L, 'belong', '=', 60012L, 'application/data'],
- [24L, 'belong', '=', 60013L, 'application/data'],
- [24L, 'belong', '=', 60014L, 'application/data'],
- [0L, 'belong', '=', 601L, 'application/x-object-file'],
- [0L, 'belong', '=', 607L, 'application/data'],
- [0L, 'belong', '=', 324508366L, 'application/x-gdbm'],
- [0L, 'lelong', '=', 324508366L, 'application/x-gdbm'],
- [0L, 'string', '=', 'GDBM', 'application/x-gdbm'],
- [0L, 'belong', '=', 398689L, 'application/x-db'],
- [0L, 'belong', '=', 340322L, 'application/x-db'],
- [0L, 'string', '=', '<list>\012<protocol bbn-m', 'application/data'],
- [0L, 'string', '=', 'diff text/x-patch', ''],
- [0L, 'string', '=', '*** text/x-patch', ''],
- [0L, 'string', '=', 'Only in text/x-patch', ''],
- [0L, 'string', '=', 'Common subdirectories: text/x-patch', ''],
- [0L, 'string', '=', '!<arch>\012________64E', 'application/data'],
- [0L, 'leshort', '=', 387L, 'application/x-executable-file'],
- [0L, 'leshort', '=', 392L, 'application/x-executable-file'],
- [0L, 'leshort', '=', 399L, 'application/x-object-file'],
- [0L, 'string', '=', '\377\377\177', 'application/data'],
- [0L, 'string', '=', '\377\377|', 'application/data'],
- [0L, 'string', '=', '\377\377~', 'application/data'],
- [0L, 'string', '=', '\033c\033', 'application/data'],
- [0L, 'long', '=', 4553207L, 'image/x11'],
- [0L, 'string', '=', '!<PDF>!\012', 'application/x-prof'],
- [0L, 'short', '=', 1281L, 'application/x-locale'],
- [24L, 'belong', '=', 60012L, 'application/x-dump'],
- [24L, 'belong', '=', 60011L, 'application/x-dump'],
- [24L, 'lelong', '=', 60012L, 'application/x-dump'],
- [24L, 'lelong', '=', 60011L, 'application/x-dump'],
- [0L, 'string', '=', '\177ELF', 'application/x-executable-file'],
- [0L, 'short', '=', 340L, 'application/data'],
- [0L, 'short', '=', 341L, 'application/x-executable-file'],
- [1080L, 'leshort', '=', 61267L, 'application/x-linux-ext2fs'],
- [0L, 'string', '=', '\366\366\366\366', 'application/x-pc-floppy'],
- [774L, 'beshort', '=', 55998L, 'application/data'],
- [510L, 'leshort', '=', 43605L, 'application/data'],
- [1040L, 'leshort', '=', 4991L, 'application/x-filesystem'],
- [1040L, 'leshort', '=', 5007L, 'application/x-filesystem'],
- [1040L, 'leshort', '=', 9320L, 'application/x-filesystem'],
- [1040L, 'leshort', '=', 9336L, 'application/x-filesystem'],
- [0L, 'string', '=', '-rom1fs-\000', 'application/x-filesystem'],
- [395L, 'string', '=', 'OS/2', 'application/x-bootable'],
- [0L, 'string', '=', 'FONT', 'font/x-vfont'],
- [0L, 'short', '=', 436L, 'font/x-vfont'],
- [0L, 'short', '=', 17001L, 'font/x-vfont'],
- [0L, 'string', '=', '%!PS-AdobeFont-1.0', 'font/type1'],
- [6L, 'string', '=', '%!PS-AdobeFont-1.0', 'font/type1'],
- [0L, 'belong', '=', 4L, 'font/x-snf'],
- [0L, 'lelong', '=', 4L, 'font/x-snf'],
- [0L, 'string', '=', 'STARTFONT font/x-bdf', ''],
- [0L, 'string', '=', '\001fcp', 'font/x-pcf'],
- [0L, 'string', '=', 'D1.0\015', 'font/x-speedo'],
- [0L, 'string', '=', 'flf', 'font/x-figlet'],
- [0L, 'string', '=', 'flc', 'application/x-font'],
- [0L, 'belong', '=', 335698201L, 'font/x-libgrx'],
- [0L, 'belong', '=', 4282797902L, 'font/x-dos'],
- [7L, 'belong', '=', 4540225L, 'font/x-dos'],
- [7L, 'belong', '=', 5654852L, 'font/x-dos'],
- [4098L, 'string', '=', 'DOSFONT', 'font/x-dos'],
- [0L, 'string', '=', '<MakerFile', 'application/x-framemaker'],
- [0L, 'string', '=', '<MIFFile', 'application/x-framemaker'],
- [0L, 'string', '=', '<MakerDictionary', 'application/x-framemaker'],
- [0L, 'string', '=', '<MakerScreenFont', 'font/x-framemaker'],
- [0L, 'string', '=', '<MML', 'application/x-framemaker'],
- [0L, 'string', '=', '<BookFile', 'application/x-framemaker'],
- [0L, 'string', '=', '<Maker', 'application/x-framemaker'],
- [0L, 'lelong&0377777777', '=', 41400407L, 'application/x-executable-file'],
- [0L, 'lelong&0377777777', '=', 41400410L, 'application/x-executable-file'],
- [0L, 'lelong&0377777777', '=', 41400413L, 'application/x-executable-file'],
- [0L, 'lelong&0377777777', '=', 41400314L, 'application/x-executable-file'],
- [7L, 'string', '=', '\357\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000', 'application/core'],
- [0L, 'lelong', '=', 11421044151L, 'application/data'],
- [0L, 'string', '=', 'GIMP Gradient', 'application/x-gimp-gradient'],
- [0L, 'string', '=', 'gimp xcf', 'application/x-gimp-image'],
- [20L, 'string', '=', 'GPAT', 'application/x-gimp-pattern'],
- [20L, 'string', '=', 'GIMP', 'application/x-gimp-brush'],
- [0L, 'string', '=', '\336\022\004\225', 'application/x-locale'],
- [0L, 'string', '=', '\225\004\022\336', 'application/x-locale'],
- [0L, 'beshort', '=', 627L, 'application/x-executable-file'],
- [0L, 'beshort', '=', 624L, 'application/x-executable-file'],
- [0L, 'string', '=', '\000\001\000\000\000', 'font/ttf'],
- [0L, 'long', '=', 1203604016L, 'application/data'],
- [0L, 'long', '=', 1702407010L, 'application/data'],
- [0L, 'long', '=', 1003405017L, 'application/data'],
- [0L, 'long', '=', 1602007412L, 'application/data'],
- [0L, 'belong', '=', 34603270L, 'application/x-object-file'],
- [0L, 'belong', '=', 34603271L, 'application/x-executable-file'],
- [0L, 'belong', '=', 34603272L, 'application/x-executable-file'],
- [0L, 'belong', '=', 34603275L, 'application/x-executable-file'],
- [0L, 'belong', '=', 34603278L, 'application/x-library-file'],
- [0L, 'belong', '=', 34603277L, 'application/x-library-file'],
- [0L, 'belong', '=', 34865414L, 'application/x-object-file'],
- [0L, 'belong', '=', 34865415L, 'application/x-executable-file'],
- [0L, 'belong', '=', 34865416L, 'application/x-executable-file'],
- [0L, 'belong', '=', 34865419L, 'application/x-executable-file'],
- [0L, 'belong', '=', 34865422L, 'application/x-library-file'],
- [0L, 'belong', '=', 34865421L, 'application/x-object-file'],
- [0L, 'belong', '=', 34275590L, 'application/x-object-file'],
- [0L, 'belong', '=', 34275591L, 'application/x-executable-file'],
- [0L, 'belong', '=', 34275592L, 'application/x-executable-file'],
- [0L, 'belong', '=', 34275595L, 'application/x-executable-file'],
- [0L, 'belong', '=', 34275598L, 'application/x-library-file'],
- [0L, 'belong', '=', 34275597L, 'application/x-library-file'],
- [0L, 'belong', '=', 557605234L, 'application/x-ar'],
- [0L, 'long', '=', 34078982L, 'application/x-executable-file'],
- [0L, 'long', '=', 34078983L, 'application/x-executable-file'],
- [0L, 'long', '=', 34078984L, 'application/x-executable-file'],
- [0L, 'belong', '=', 34341128L, 'application/x-executable-file'],
- [0L, 'belong', '=', 34341127L, 'application/x-executable-file'],
- [0L, 'belong', '=', 34341131L, 'application/x-executable-file'],
- [0L, 'belong', '=', 34341126L, 'application/x-executable-file'],
- [0L, 'belong', '=', 34210056L, 'application/x-executable-file'],
- [0L, 'belong', '=', 34210055L, 'application/x-executable-file'],
- [0L, 'belong', '=', 34341134L, 'application/x-library-file'],
- [0L, 'belong', '=', 34341133L, 'application/x-library-file'],
- [0L, 'long', '=', 65381L, 'application/x-library-file'],
- [0L, 'long', '=', 34275173L, 'application/x-library-file'],
- [0L, 'long', '=', 34406245L, 'application/x-library-file'],
- [0L, 'long', '=', 34144101L, 'application/x-library-file'],
- [0L, 'long', '=', 22552998L, 'application/core'],
- [0L, 'long', '=', 1302851304L, 'font/x-hp-windows'],
- [0L, 'string', '=', 'Bitmapfile', 'image/unknown'],
- [0L, 'string', '=', 'IMGfile', 'CIS image/unknown'],
- [0L, 'long', '=', 34341132L, 'application/x-lisp'],
- [0L, 'string', '=', 'msgcat01', 'application/x-locale'],
- [0L, 'string', '=', 'HPHP48-', 'HP48 binary'],
- [0L, 'string', '=', '%%HP:', 'HP48 text'],
- [0L, 'beshort', '=', 200L, 'hp200 (68010) BSD'],
- [0L, 'beshort', '=', 300L, 'hp300 (68020+68881) BSD'],
- [0L, 'beshort', '=', 537L, '370 XA sysV executable'],
- [0L, 'beshort', '=', 532L, '370 XA sysV pure executable'],
- [0L, 'beshort', '=', 54001L, '370 sysV pure executable'],
- [0L, 'beshort', '=', 55001L, '370 XA sysV pure executable'],
- [0L, 'beshort', '=', 56401L, '370 sysV executable'],
- [0L, 'beshort', '=', 57401L, '370 XA sysV executable'],
- [0L, 'beshort', '=', 531L, 'SVR2 executable (Amdahl-UTS)'],
- [0L, 'beshort', '=', 534L, 'SVR2 pure executable (Amdahl-UTS)'],
- [0L, 'beshort', '=', 530L, 'SVR2 pure executable (USS/370)'],
- [0L, 'beshort', '=', 535L, 'SVR2 executable (USS/370)'],
- [0L, 'beshort', '=', 479L, 'executable (RISC System/6000 V3.1) or obj module'],
- [0L, 'beshort', '=', 260L, 'shared library'],
- [0L, 'beshort', '=', 261L, 'ctab data'],
- [0L, 'beshort', '=', 65028L, 'structured file'],
- [0L, 'string', '=', '0xabcdef', 'AIX message catalog'],
- [0L, 'belong', '=', 505L, 'AIX compiled message catalog'],
- [0L, 'string', '=', '<aiaff>', 'archive'],
- [0L, 'string', '=', 'FORM', 'IFF data'],
- [0L, 'string', '=', 'P1', 'image/x-portable-bitmap'],
- [0L, 'string', '=', 'P2', 'image/x-portable-graymap'],
- [0L, 'string', '=', 'P3', 'image/x-portable-pixmap'],
- [0L, 'string', '=', 'P4', 'image/x-portable-bitmap'],
- [0L, 'string', '=', 'P5', 'image/x-portable-graymap'],
- [0L, 'string', '=', 'P6', 'image/x-portable-pixmap'],
- [0L, 'string', '=', 'IIN1', 'image/tiff'],
- [0L, 'string', '=', 'MM\000*', 'image/tiff'],
- [0L, 'string', '=', 'II*\000', 'image/tiff'],
- [0L, 'string', '=', '\211PNG', 'image/x-png'],
- [1L, 'string', '=', 'PNG', 'image/x-png'],
- [0L, 'string', '=', 'GIF8', 'image/gif'],
- [0L, 'string', '=', '\361\000@\273', 'image/x-cmu-raster'],
- [0L, 'string', '=', 'id=ImageMagick', 'MIFF image data'],
- [0L, 'long', '=', 1123028772L, 'Artisan image data'],
- [0L, 'string', '=', '#FIG', 'FIG image text'],
- [0L, 'string', '=', 'ARF_BEGARF', 'PHIGS clear text archive'],
- [0L, 'string', '=', '@(#)SunPHIGS', 'SunPHIGS'],
- [0L, 'string', '=', 'GKSM', 'GKS Metafile'],
- [0L, 'string', '=', 'BEGMF', 'clear text Computer Graphics Metafile'],
- [0L, 'beshort&0xffe0', '=', 32L, 'binary Computer Graphics Metafile'],
- [0L, 'beshort', '=', 12320L, 'character Computer Graphics Metafile'],
- [0L, 'string', '=', 'yz', 'MGR bitmap, modern format, 8-bit aligned'],
- [0L, 'string', '=', 'zz', 'MGR bitmap, old format, 1-bit deep, 16-bit aligned'],
- [0L, 'string', '=', 'xz', 'MGR bitmap, old format, 1-bit deep, 32-bit aligned'],
- [0L, 'string', '=', 'yx', 'MGR bitmap, modern format, squeezed'],
- [0L, 'string', '=', '%bitmap\000', 'FBM image data'],
- [1L, 'string', '=', 'PC Research, Inc', 'group 3 fax data'],
- [0L, 'beshort', '=', 65496L, 'image/jpeg'],
- [0L, 'string', '=', 'hsi1', 'image/x-jpeg-proprietary'],
- [0L, 'string', '=', 'BM', 'image/x-bmp'],
- [0L, 'string', '=', 'IC', 'image/x-ico'],
- [0L, 'string', '=', 'PI', 'PC pointer image data'],
- [0L, 'string', '=', 'CI', 'PC color icon data'],
- [0L, 'string', '=', 'CP', 'PC color pointer image data'],
- [0L, 'string', '=', '/* XPM */', 'X pixmap image text'],
- [0L, 'leshort', '=', 52306L, 'RLE image data,'],
- [0L, 'string', '=', 'Imagefile version-', 'iff image data'],
- [0L, 'belong', '=', 1504078485L, 'x/x-image-sun-raster'],
- [0L, 'beshort', '=', 474L, 'x/x-image-sgi'],
- [0L, 'string', '=', 'IT01', 'FIT image data'],
- [0L, 'string', '=', 'IT02', 'FIT image data'],
- [2048L, 'string', '=', 'PCD_IPI', 'x/x-photo-cd-pack-file'],
- [0L, 'string', '=', 'PCD_OPA', 'x/x-photo-cd-overfiew-file'],
- [0L, 'string', '=', 'SIMPLE =', 'FITS image data'],
- [0L, 'string', '=', 'This is a BitMap file', 'Lisp Machine bit-array-file'],
- [0L, 'string', '=', '!!', 'Bennet Yee\'s "face" format'],
- [0L, 'beshort', '=', 4112L, 'PEX Binary Archive'],
- [3000L, 'string', '=', 'Visio (TM) Drawing', '%s'],
- [0L, 'leshort', '=', 502L, 'basic-16 executable'],
- [0L, 'leshort', '=', 503L, 'basic-16 executable (TV)'],
- [0L, 'leshort', '=', 510L, 'application/x-executable-file'],
- [0L, 'leshort', '=', 511L, 'application/x-executable-file'],
- [0L, 'leshort', '=', 512L, 'application/x-executable-file'],
- [0L, 'leshort', '=', 522L, 'application/x-executable-file'],
- [0L, 'leshort', '=', 514L, 'application/x-executable-file'],
- [0L, 'string', '=', '\210OPS', 'Interleaf saved data'],
- [0L, 'string', '=', '<!OPS', 'Interleaf document text'],
- [4L, 'string', '=', 'pgscriptver', 'IslandWrite document'],
- [13L, 'string', '=', 'DrawFile', 'IslandDraw document'],
- [0L, 'leshort&0xFFFC', '=', 38400L, 'little endian ispell'],
- [0L, 'beshort&0xFFFC', '=', 38400L, 'big endian ispell'],
- [0L, 'belong', '=', 3405691582L, 'compiled Java class data,'],
- [0L, 'beshort', '=', 44269L, 'Java serialization data'],
- [0L, 'string', '=', 'KarmaRHD', 'Version Karma Data Structure Version'],
- [0L, 'string', '=', 'lect', 'DEC SRC Virtual Paper Lectern file'],
- [53L, 'string', '=', 'yyprevious', 'C program text (from lex)'],
- [21L, 'string', '=', 'generated by flex', 'C program text (from flex)'],
- [0L, 'string', '=', '%{', 'lex description text'],
- [0L, 'short', '=', 32768L, 'lif file'],
- [0L, 'lelong', '=', 6553863L, 'Linux/i386 impure executable (OMAGIC)'],
- [0L, 'lelong', '=', 6553864L, 'Linux/i386 pure executable (NMAGIC)'],
- [0L, 'lelong', '=', 6553867L, 'Linux/i386 demand-paged executable (ZMAGIC)'],
- [0L, 'lelong', '=', 6553804L, 'Linux/i386 demand-paged executable (QMAGIC)'],
- [0L, 'string', '=', '\007\001\000', 'Linux/i386 object file'],
- [0L, 'string', '=', '\001\003\020\004', 'Linux-8086 impure executable'],
- [0L, 'string', '=', '\001\003 \004', 'Linux-8086 executable'],
- [0L, 'string', '=', '\243\206\001\000', 'Linux-8086 object file'],
- [0L, 'string', '=', '\001\003\020\020', 'Minix-386 impure executable'],
- [0L, 'string', '=', '\001\003 \020', 'Minix-386 executable'],
- [0L, 'string', '=', '*nazgul*', 'Linux compiled message catalog'],
- [216L, 'lelong', '=', 421L, 'Linux/i386 core file'],
- [2L, 'string', '=', 'LILO', 'Linux/i386 LILO boot/chain loader'],
- [0L, 'string', '=', '0.9', ''],
- [0L, 'leshort', '=', 1078L, 'font/linux-psf'],
- [4086L, 'string', '=', 'SWAP-SPACE', 'Linux/i386 swap file'],
- [0L, 'leshort', '=', 387L, 'ECOFF alpha'],
- [514L, 'string', '=', 'HdrS', 'Linux kernel'],
- [0L, 'belong', '=', 3099592590L, 'Linux kernel'],
- [0L, 'string', '=', 'Begin3', 'Linux Software Map entry text'],
- [0L, 'string', '=', ';;', 'Lisp/Scheme program text'],
- [0L, 'string', '=', '\012(', 'byte-compiled Emacs-Lisp program data'],
- [0L, 'string', '=', ';ELC\023\000\000\000', 'byte-compiled Emacs-Lisp program data'],
- [0L, 'string', '=', "(SYSTEM::VERSION '", 'CLISP byte-compiled Lisp program text'],
- [0L, 'long', '=', 1886817234L, 'CLISP memory image data'],
- [0L, 'long', '=', 3532355184L, 'CLISP memory image data, other endian'],
- [0L, 'long', '=', 3725722773L, 'GNU-format message catalog data'],
- [0L, 'long', '=', 2500072158L, 'GNU-format message catalog data'],
- [0L, 'belong', '=', 3405691582L, 'mach-o fat file'],
- [0L, 'belong', '=', 4277009102L, 'mach-o'],
- [11L, 'string', '=', 'must be converted with BinHex', 'BinHex binary text'],
- [0L, 'string', '=', 'SIT!', 'StuffIt Archive (data)'],
- [65L, 'string', '=', 'SIT!', 'StuffIt Archive (rsrc + data)'],
- [0L, 'string', '=', 'SITD', 'StuffIt Deluxe (data)'],
- [65L, 'string', '=', 'SITD', 'StuffIt Deluxe (rsrc + data)'],
- [0L, 'string', '=', 'Seg', 'StuffIt Deluxe Segment (data)'],
- [65L, 'string', '=', 'Seg', 'StuffIt Deluxe Segment (rsrc + data)'],
- [0L, 'string', '=', 'APPL', 'Macintosh Application (data)'],
- [65L, 'string', '=', 'APPL', 'Macintosh Application (rsrc + data)'],
- [0L, 'string', '=', 'zsys', 'Macintosh System File (data)'],
- [65L, 'string', '=', 'zsys', 'Macintosh System File(rsrc + data)'],
- [0L, 'string', '=', 'FNDR', 'Macintosh Finder (data)'],
- [65L, 'string', '=', 'FNDR', 'Macintosh Finder(rsrc + data)'],
- [0L, 'string', '=', 'libr', 'Macintosh Library (data)'],
- [65L, 'string', '=', 'libr', 'Macintosh Library(rsrc + data)'],
- [0L, 'string', '=', 'shlb', 'Macintosh Shared Library (data)'],
- [65L, 'string', '=', 'shlb', 'Macintosh Shared Library(rsrc + data)'],
- [0L, 'string', '=', 'cdev', 'Macintosh Control Panel (data)'],
- [65L, 'string', '=', 'cdev', 'Macintosh Control Panel(rsrc + data)'],
- [0L, 'string', '=', 'INIT', 'Macintosh Extension (data)'],
- [65L, 'string', '=', 'INIT', 'Macintosh Extension(rsrc + data)'],
- [0L, 'string', '=', 'FFIL', 'font/ttf'],
- [65L, 'string', '=', 'FFIL', 'font/ttf'],
- [0L, 'string', '=', 'LWFN', 'font/type1'],
- [65L, 'string', '=', 'LWFN', 'font/type1'],
- [0L, 'string', '=', 'PACT', 'Macintosh Compact Pro Archive (data)'],
- [65L, 'string', '=', 'PACT', 'Macintosh Compact Pro Archive(rsrc + data)'],
- [0L, 'string', '=', 'ttro', 'Macintosh TeachText File (data)'],
- [65L, 'string', '=', 'ttro', 'Macintosh TeachText File(rsrc + data)'],
- [0L, 'string', '=', 'TEXT', 'Macintosh TeachText File (data)'],
- [65L, 'string', '=', 'TEXT', 'Macintosh TeachText File(rsrc + data)'],
- [0L, 'string', '=', 'PDF', 'Macintosh PDF File (data)'],
- [65L, 'string', '=', 'PDF', 'Macintosh PDF File(rsrc + data)'],
- [0L, 'string', '=', '# Magic', 'magic text file for file(1) cmd'],
- [0L, 'string', '=', 'Relay-Version:', 'old news text'],
- [0L, 'string', '=', '#! rnews', 'batched news text'],
- [0L, 'string', '=', 'N#! rnews', 'mailed, batched news text'],
- [0L, 'string', '=', 'Forward to', 'mail forwarding text'],
- [0L, 'string', '=', 'Pipe to', 'mail piping text'],
- [0L, 'string', '=', 'Return-Path:', 'message/rfc822'],
- [0L, 'string', '=', 'Path:', 'message/news'],
- [0L, 'string', '=', 'Xref:', 'message/news'],
- [0L, 'string', '=', 'From:', 'message/rfc822'],
- [0L, 'string', '=', 'Article', 'message/news'],
- [0L, 'string', '=', 'BABYL', 'message/x-gnu-rmail'],
- [0L, 'string', '=', 'Received:', 'message/rfc822'],
- [0L, 'string', '=', 'MIME-Version:', 'MIME entity text'],
- [0L, 'string', '=', 'Content-Type: ', ''],
- [0L, 'string', '=', 'Content-Type:', ''],
- [0L, 'long', '=', 31415L, 'Mirage Assembler m.out executable'],
- [0L, 'string', '=', '\311\304', 'ID tags data'],
- [0L, 'string', '=', '\001\001\001\001', 'MMDF mailbox'],
- [4L, 'string', '=', 'Research,', 'Digifax-G3-File'],
- [0L, 'short', '=', 256L, 'raw G3 data, byte-padded'],
- [0L, 'short', '=', 5120L, 'raw G3 data'],
- [0L, 'string', '=', 'RMD1', 'raw modem data'],
- [0L, 'string', '=', 'PVF1\012', 'portable voice format'],
- [0L, 'string', '=', 'PVF2\012', 'portable voice format'],
- [0L, 'beshort', '=', 520L, 'mc68k COFF'],
- [0L, 'beshort', '=', 521L, 'mc68k executable (shared)'],
- [0L, 'beshort', '=', 522L, 'mc68k executable (shared demand paged)'],
- [0L, 'beshort', '=', 554L, '68K BCS executable'],
- [0L, 'beshort', '=', 555L, '88K BCS executable'],
- [0L, 'string', '=', 'S0', 'Motorola S-Record; binary data in text format'],
- [0L, 'string', '=', '@echo off', 'MS-DOS batch file text'],
- [128L, 'string', '=', 'PE\000\000', 'MS Windows PE'],
- [0L, 'leshort', '=', 332L, 'MS Windows COFF Intel 80386 object file'],
- [0L, 'leshort', '=', 358L, 'MS Windows COFF MIPS R4000 object file'],
- [0L, 'leshort', '=', 388L, 'MS Windows COFF Alpha object file'],
- [0L, 'leshort', '=', 616L, 'MS Windows COFF Motorola 68000 object file'],
- [0L, 'leshort', '=', 496L, 'MS Windows COFF PowerPC object file'],
- [0L, 'leshort', '=', 656L, 'MS Windows COFF PA-RISC object file'],
- [0L, 'string', '=', 'MZ', 'application/x-ms-dos-executable'],
- [0L, 'string', '=', 'LZ', 'MS-DOS executable (built-in)'],
- [0L, 'string', '=', 'regf', 'Windows NT Registry file'],
- [2080L, 'string', '=', 'Microsoft Word 6.0 Document', 'text/vnd.ms-word'],
- [2080L, 'string', '=', 'Documento Microsoft Word 6', 'text/vnd.ms-word'],
- [2112L, 'string', '=', 'MSWordDoc', 'text/vnd.ms-word'],
- [0L, 'belong', '=', 834535424L, 'text/vnd.ms-word'],
- [0L, 'string', '=', 'PO^Q`', 'text/vnd.ms-word'],
- [2080L, 'string', '=', 'Microsoft Excel 5.0 Worksheet', 'application/vnd.ms-excel'],
- [2114L, 'string', '=', 'Biff5', 'application/vnd.ms-excel'],
- [0L, 'belong', '=', 6656L, 'Lotus 1-2-3'],
- [0L, 'belong', '=', 512L, 'Lotus 1-2-3'],
- [1L, 'string', '=', 'WPC', 'text/vnd.wordperfect'],
- [0L, 'beshort', '=', 610L, 'Tower/XP rel 2 object'],
- [0L, 'beshort', '=', 615L, 'Tower/XP rel 2 object'],
- [0L, 'beshort', '=', 620L, 'Tower/XP rel 3 object'],
- [0L, 'beshort', '=', 625L, 'Tower/XP rel 3 object'],
- [0L, 'beshort', '=', 630L, 'Tower32/600/400 68020 object'],
- [0L, 'beshort', '=', 640L, 'Tower32/800 68020'],
- [0L, 'beshort', '=', 645L, 'Tower32/800 68010'],
- [0L, 'lelong', '=', 407L, 'NetBSD little-endian object file'],
- [0L, 'belong', '=', 407L, 'NetBSD big-endian object file'],
- [0L, 'belong&0377777777', '=', 41400413L, 'NetBSD/i386 demand paged'],
- [0L, 'belong&0377777777', '=', 41400410L, 'NetBSD/i386 pure'],
- [0L, 'belong&0377777777', '=', 41400407L, 'NetBSD/i386'],
- [0L, 'belong&0377777777', '=', 41400507L, 'NetBSD/i386 core'],
- [0L, 'belong&0377777777', '=', 41600413L, 'NetBSD/m68k demand paged'],
- [0L, 'belong&0377777777', '=', 41600410L, 'NetBSD/m68k pure'],
- [0L, 'belong&0377777777', '=', 41600407L, 'NetBSD/m68k'],
- [0L, 'belong&0377777777', '=', 41600507L, 'NetBSD/m68k core'],
- [0L, 'belong&0377777777', '=', 42000413L, 'NetBSD/m68k4k demand paged'],
- [0L, 'belong&0377777777', '=', 42000410L, 'NetBSD/m68k4k pure'],
- [0L, 'belong&0377777777', '=', 42000407L, 'NetBSD/m68k4k'],
- [0L, 'belong&0377777777', '=', 42000507L, 'NetBSD/m68k4k core'],
- [0L, 'belong&0377777777', '=', 42200413L, 'NetBSD/ns32532 demand paged'],
- [0L, 'belong&0377777777', '=', 42200410L, 'NetBSD/ns32532 pure'],
- [0L, 'belong&0377777777', '=', 42200407L, 'NetBSD/ns32532'],
- [0L, 'belong&0377777777', '=', 42200507L, 'NetBSD/ns32532 core'],
- [0L, 'belong&0377777777', '=', 42400413L, 'NetBSD/sparc demand paged'],
- [0L, 'belong&0377777777', '=', 42400410L, 'NetBSD/sparc pure'],
- [0L, 'belong&0377777777', '=', 42400407L, 'NetBSD/sparc'],
- [0L, 'belong&0377777777', '=', 42400507L, 'NetBSD/sparc core'],
- [0L, 'belong&0377777777', '=', 42600413L, 'NetBSD/pmax demand paged'],
- [0L, 'belong&0377777777', '=', 42600410L, 'NetBSD/pmax pure'],
- [0L, 'belong&0377777777', '=', 42600407L, 'NetBSD/pmax'],
- [0L, 'belong&0377777777', '=', 42600507L, 'NetBSD/pmax core'],
- [0L, 'belong&0377777777', '=', 43000413L, 'NetBSD/vax demand paged'],
- [0L, 'belong&0377777777', '=', 43000410L, 'NetBSD/vax pure'],
- [0L, 'belong&0377777777', '=', 43000407L, 'NetBSD/vax'],
- [0L, 'belong&0377777777', '=', 43000507L, 'NetBSD/vax core'],
- [0L, 'lelong', '=', 459141L, 'ECOFF NetBSD/alpha binary'],
- [0L, 'belong&0377777777', '=', 43200507L, 'NetBSD/alpha core'],
- [0L, 'belong&0377777777', '=', 43400413L, 'NetBSD/mips demand paged'],
- [0L, 'belong&0377777777', '=', 43400410L, 'NetBSD/mips pure'],
- [0L, 'belong&0377777777', '=', 43400407L, 'NetBSD/mips'],
- [0L, 'belong&0377777777', '=', 43400507L, 'NetBSD/mips core'],
- [0L, 'belong&0377777777', '=', 43600413L, 'NetBSD/arm32 demand paged'],
- [0L, 'belong&0377777777', '=', 43600410L, 'NetBSD/arm32 pure'],
- [0L, 'belong&0377777777', '=', 43600407L, 'NetBSD/arm32'],
- [0L, 'belong&0377777777', '=', 43600507L, 'NetBSD/arm32 core'],
- [0L, 'string', '=', 'StartFontMetrics', 'font/x-sunos-news'],
- [0L, 'string', '=', 'StartFont', 'font/x-sunos-news'],
- [0L, 'belong', '=', 326773060L, 'font/x-sunos-news'],
- [0L, 'belong', '=', 326773063L, 'font/x-sunos-news'],
- [0L, 'belong', '=', 326773072L, 'font/x-sunos-news'],
- [0L, 'belong', '=', 326773073L, 'font/x-sunos-news'],
- [8L, 'belong', '=', 326773573L, 'font/x-sunos-news'],
- [8L, 'belong', '=', 326773576L, 'font/x-sunos-news'],
- [0L, 'string', '=', 'Octave-1-L', 'Octave binary data (little endian)'],
- [0L, 'string', '=', 'Octave-1-B', 'Octave binary data (big endian)'],
- [0L, 'string', '=', '\177OLF', 'OLF'],
- [0L, 'beshort', '=', 34765L, 'OS9/6809 module:'],
- [0L, 'beshort', '=', 19196L, 'OS9/68K module:'],
- [0L, 'long', '=', 61374L, 'OSF/Rose object'],
- [0L, 'short', '=', 565L, 'i386 COFF object'],
- [0L, 'short', '=', 10775L, '"compact bitmap" format (Poskanzer)'],
- [0L, 'string', '=', '%PDF-', 'PDF document'],
- [0L, 'lelong', '=', 101555L, 'PDP-11 single precision APL workspace'],
- [0L, 'lelong', '=', 101554L, 'PDP-11 double precision APL workspace'],
- [0L, 'leshort', '=', 407L, 'PDP-11 executable'],
- [0L, 'leshort', '=', 401L, 'PDP-11 UNIX/RT ldp'],
- [0L, 'leshort', '=', 405L, 'PDP-11 old overlay'],
- [0L, 'leshort', '=', 410L, 'PDP-11 pure executable'],
- [0L, 'leshort', '=', 411L, 'PDP-11 separate I&D executable'],
- [0L, 'leshort', '=', 437L, 'PDP-11 kernel overlay'],
- [0L, 'beshort', '=', 39168L, 'PGP key public ring'],
- [0L, 'beshort', '=', 38145L, 'PGP key security ring'],
- [0L, 'beshort', '=', 38144L, 'PGP key security ring'],
- [0L, 'beshort', '=', 42496L, 'PGP encrypted data'],
- [0L, 'string', '=', '-----BEGIN PGP', 'PGP armored data'],
- [0L, 'string', '=', '# PaCkAgE DaTaStReAm', 'pkg Datastream (SVR4)'],
- [0L, 'short', '=', 601L, 'mumps avl global'],
- [0L, 'short', '=', 602L, 'mumps blt global'],
- [0L, 'string', '=', '%!', 'application/postscript'],
- [0L, 'string', '=', '\004%!', 'application/postscript'],
- [0L, 'belong', '=', 3318797254L, 'DOS EPS Binary File'],
- [0L, 'string', '=', '*PPD-Adobe:', 'PPD file'],
- [0L, 'string', '=', '\033%-12345X at PJL', 'HP Printer Job Language data'],
- [0L, 'string', '=', '\033%-12345X at PJL', 'HP Printer Job Language data'],
- [0L, 'string', '=', '\033E\033', 'image/x-pcl-hp'],
- [0L, 'string', '=', '@document(', 'Imagen printer'],
- [0L, 'string', '=', 'Rast', 'RST-format raster font data'],
- [0L, 'belong&0xff00ffff', '=', 1442840576L, 'ps database'],
- [0L, 'long', '=', 1351614727L, 'Pyramid 90x family executable'],
- [0L, 'long', '=', 1351614728L, 'Pyramid 90x family pure executable'],
- [0L, 'long', '=', 1351614731L, 'Pyramid 90x family demand paged pure executable'],
- [0L, 'beshort', '=', 60843L, ''],
- [0L, 'string', '=', '{\\\\rtf', 'Rich Text Format data,'],
- [38L, 'string', '=', 'Spreadsheet', 'sc spreadsheet file'],
- [8L, 'string', '=', '\001s SCCS', 'archive data'],
- [0L, 'byte', '=', 46L, 'Sendmail frozen configuration'],
- [0L, 'short', '=', 10012L, 'Sendmail frozen configuration'],
- [0L, 'lelong', '=', 234L, 'BALANCE NS32000 .o'],
- [0L, 'lelong', '=', 4330L, 'BALANCE NS32000 executable (0 @ 0)'],
- [0L, 'lelong', '=', 8426L, 'BALANCE NS32000 executable (invalid @ 0)'],
- [0L, 'lelong', '=', 12522L, 'BALANCE NS32000 standalone executable'],
- [0L, 'leshort', '=', 4843L, 'SYMMETRY i386 .o'],
- [0L, 'leshort', '=', 8939L, 'SYMMETRY i386 executable (0 @ 0)'],
- [0L, 'leshort', '=', 13035L, 'SYMMETRY i386 executable (invalid @ 0)'],
- [0L, 'leshort', '=', 17131L, 'SYMMETRY i386 standalone executable'],
- [0L, 'string', '=', 'kbd!map', 'kbd map file'],
- [0L, 'belong', '=', 407L, 'old SGI 68020 executable'],
- [0L, 'belong', '=', 410L, 'old SGI 68020 pure executable'],
- [0L, 'beshort', '=', 34661L, 'disk quotas file'],
- [0L, 'beshort', '=', 1286L, 'IRIS Showcase file'],
- [0L, 'beshort', '=', 550L, 'IRIS Showcase template'],
- [0L, 'belong', '=', 1396917837L, 'IRIS Showcase file'],
- [0L, 'belong', '=', 1413695053L, 'IRIS Showcase template'],
- [0L, 'belong', '=', 3735927486L, 'IRIX Parallel Arena'],
- [0L, 'beshort', '=', 352L, 'MIPSEB COFF executable'],
- [0L, 'beshort', '=', 354L, 'MIPSEL COFF executable'],
- [0L, 'beshort', '=', 24577L, 'MIPSEB-LE COFF executable'],
- [0L, 'beshort', '=', 25089L, 'MIPSEL-LE COFF executable'],
- [0L, 'beshort', '=', 355L, 'MIPSEB MIPS-II COFF executable'],
- [0L, 'beshort', '=', 358L, 'MIPSEL MIPS-II COFF executable'],
- [0L, 'beshort', '=', 25345L, 'MIPSEB-LE MIPS-II COFF executable'],
- [0L, 'beshort', '=', 26113L, 'MIPSEL-LE MIPS-II COFF executable'],
- [0L, 'beshort', '=', 320L, 'MIPSEB MIPS-III COFF executable'],
- [0L, 'beshort', '=', 322L, 'MIPSEL MIPS-III COFF executable'],
- [0L, 'beshort', '=', 16385L, 'MIPSEB-LE MIPS-III COFF executable'],
- [0L, 'beshort', '=', 16897L, 'MIPSEL-LE MIPS-III COFF executable'],
- [0L, 'beshort', '=', 384L, 'MIPSEB Ucode'],
- [0L, 'beshort', '=', 386L, 'MIPSEL Ucode'],
- [0L, 'belong', '=', 3735924144L, 'IRIX core dump'],
- [0L, 'belong', '=', 3735924032L, 'IRIX 64-bit core dump'],
- [0L, 'belong', '=', 3133063355L, 'IRIX N32 core dump'],
- [0L, 'string', '=', 'CrshDump', 'IRIX vmcore dump of'],
- [0L, 'string', '=', 'SGIAUDIT', 'SGI Audit file'],
- [0L, 'string', '=', 'WNGZWZSC', 'Wingz compiled script'],
- [0L, 'string', '=', 'WNGZWZSS', 'Wingz spreadsheet'],
- [0L, 'string', '=', 'WNGZWZHP', 'Wingz help file'],
- [0L, 'string', '=', '\\#Inventor', 'V IRIS Inventor 1.0 file'],
- [0L, 'string', '=', '\\#Inventor', 'V2 Open Inventor 2.0 file'],
- [0L, 'string', '=', 'glfHeadMagic();', 'GLF_TEXT'],
- [4L, 'belong', '=', 1090584576L, 'GLF_BINARY_LSB_FIRST'],
- [4L, 'belong', '=', 321L, 'GLF_BINARY_MSB_FIRST'],
- [0L, 'string', '=', '<!DOCTYPE HTML', 'text/html'],
- [0L, 'string', '=', '<!doctype html', 'text/html'],
- [0L, 'string', '=', '<HEAD', 'text/html'],
- [0L, 'string', '=', '<head', 'text/html'],
- [0L, 'string', '=', '<TITLE', 'text/html'],
- [0L, 'string', '=', '<title', 'text/html'],
- [0L, 'string', '=', '<html', 'text/html'],
- [0L, 'string', '=', '<HTML', 'text/html'],
- [0L, 'string', '=', '<!DOCTYPE', 'exported SGML document text'],
- [0L, 'string', '=', '<!doctype', 'exported SGML document text'],
- [0L, 'string', '=', '<!SUBDOC', 'exported SGML subdocument text'],
- [0L, 'string', '=', '<!subdoc', 'exported SGML subdocument text'],
- [0L, 'string', '=', '<!--', 'exported SGML document text'],
- [0L, 'string', '=', 'RTSS', 'NetMon capture file'],
- [0L, 'string', '=', 'TRSNIFF data \032', 'Sniffer capture file'],
- [0L, 'string', '=', 'XCP\000', 'NetXRay capture file'],
- [0L, 'ubelong', '=', 2712847316L, 'tcpdump capture file (big-endian)'],
- [0L, 'ulelong', '=', 2712847316L, 'tcpdump capture file (little-endian)'],
- [0L, 'string', '=', '<!SQ DTD>', 'Compiled SGML rules file'],
- [0L, 'string', '=', '<!SQ A/E>', 'A/E SGML Document binary'],
- [0L, 'string', '=', '<!SQ STS>', 'A/E SGML binary styles file'],
- [0L, 'short', '=', 49374L, 'Compiled PSI (v1) data'],
- [0L, 'short', '=', 49370L, 'Compiled PSI (v2) data'],
- [0L, 'short', '=', 125252L, 'SoftQuad DESC or font file binary'],
- [0L, 'string', '=', 'SQ BITMAP1', 'SoftQuad Raster Format text'],
- [0L, 'string', '=', 'X SoftQuad', 'troff Context intermediate'],
- [0L, 'belong&077777777', '=', 600413L, 'sparc demand paged'],
- [0L, 'belong&077777777', '=', 600410L, 'sparc pure'],
- [0L, 'belong&077777777', '=', 600407L, 'sparc'],
- [0L, 'belong&077777777', '=', 400413L, 'mc68020 demand paged'],
- [0L, 'belong&077777777', '=', 400410L, 'mc68020 pure'],
- [0L, 'belong&077777777', '=', 400407L, 'mc68020'],
- [0L, 'belong&077777777', '=', 200413L, 'mc68010 demand paged'],
- [0L, 'belong&077777777', '=', 200410L, 'mc68010 pure'],
- [0L, 'belong&077777777', '=', 200407L, 'mc68010'],
- [0L, 'belong', '=', 407L, 'old sun-2 executable'],
- [0L, 'belong', '=', 410L, 'old sun-2 pure executable'],
- [0L, 'belong', '=', 413L, 'old sun-2 demand paged executable'],
- [0L, 'belong', '=', 525398L, 'SunOS core file'],
- [0L, 'long', '=', 4197695630L, 'SunPC 4.0 Hard Disk'],
- [0L, 'string', '=', '#SUNPC_CONFIG', 'SunPC 4.0 Properties Values'],
- [0L, 'string', '=', 'snoop', 'Snoop capture file'],
- [36L, 'string', '=', 'acsp', 'Kodak Color Management System, ICC Profile'],
- [0L, 'string', '=', '#!teapot\012xdr', 'teapot work sheet (XDR format)'],
- [0L, 'string', '=', '\032\001', 'Compiled terminfo entry'],
- [0L, 'short', '=', 433L, 'Curses screen image'],
- [0L, 'short', '=', 434L, 'Curses screen image'],
- [0L, 'string', '=', '\367\002', 'TeX DVI file'],
- [0L, 'string', '=', '\367\203', 'font/x-tex'],
- [0L, 'string', '=', '\367Y', 'font/x-tex'],
- [0L, 'string', '=', '\367\312', 'font/x-tex'],
- [0L, 'string', '=', 'This is TeX,', 'TeX transcript text'],
- [0L, 'string', '=', 'This is METAFONT,', 'METAFONT transcript text'],
- [2L, 'string', '=', '\000\021', 'font/x-tex-tfm'],
- [2L, 'string', '=', '\000\022', 'font/x-tex-tfm'],
- [0L, 'string', '=', '\\\\input\\', 'texinfo Texinfo source text'],
- [0L, 'string', '=', 'This is Info file', 'GNU Info text'],
- [0L, 'string', '=', '\\\\input', 'TeX document text'],
- [0L, 'string', '=', '\\\\section', 'LaTeX document text'],
- [0L, 'string', '=', '\\\\setlength', 'LaTeX document text'],
- [0L, 'string', '=', '\\\\documentstyle', 'LaTeX document text'],
- [0L, 'string', '=', '\\\\chapter', 'LaTeX document text'],
- [0L, 'string', '=', '\\\\documentclass', 'LaTeX 2e document text'],
- [0L, 'string', '=', '\\\\relax', 'LaTeX auxiliary file'],
- [0L, 'string', '=', '\\\\contentsline', 'LaTeX table of contents'],
- [0L, 'string', '=', '\\\\indexentry', 'LaTeX raw index file'],
- [0L, 'string', '=', '\\\\begin{theindex}', 'LaTeX sorted index'],
- [0L, 'string', '=', '\\\\glossaryentry', 'LaTeX raw glossary'],
- [0L, 'string', '=', '\\\\begin{theglossary}', 'LaTeX sorted glossary'],
- [0L, 'string', '=', 'This is makeindex', 'Makeindex log file'],
- [0L, 'string', '=', '**TI82**', 'TI-82 Graphing Calculator'],
- [0L, 'string', '=', '**TI83**', 'TI-83 Graphing Calculator'],
- [0L, 'string', '=', '**TI85**', 'TI-85 Graphing Calculator'],
- [0L, 'string', '=', '**TI92**', 'TI-92 Graphing Calculator'],
- [0L, 'string', '=', '**TI80**', 'TI-80 Graphing Calculator File.'],
- [0L, 'string', '=', '**TI81**', 'TI-81 Graphing Calculator File.'],
- [0L, 'string', '=', 'TZif', 'timezone data'],
- [0L, 'string', '=', '\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000', 'old timezone data'],
- [0L, 'string', '=', '\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\002\000', 'old timezone data'],
- [0L, 'string', '=', '\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\003\000', 'old timezone data'],
- [0L, 'string', '=', '\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000', 'old timezone data'],
- [0L, 'string', '=', '\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\005\000', 'old timezone data'],
- [0L, 'string', '=', '\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\006\000', 'old timezone data'],
- [0L, 'string', '=', '.\\\\"', 'troff or preprocessor input text'],
- [0L, 'string', '=', '\'\\\\"', 'troff or preprocessor input text'],
- [0L, 'string', '=', '\'.\\\\"', 'troff or preprocessor input text'],
- [0L, 'string', '=', '\\\\"', 'troff or preprocessor input text'],
- [0L, 'string', '=', 'x T', 'ditroff text'],
- [0L, 'string', '=', '@\357', 'very old (C/A/T) troff output data'],
- [0L, 'string', '=', 'Interpress/Xerox', 'Xerox InterPress data'],
- [0L, 'short', '=', 263L, 'unknown machine executable'],
- [0L, 'short', '=', 264L, 'unknown pure executable'],
- [0L, 'short', '=', 265L, 'PDP-11 separate I&D'],
- [0L, 'short', '=', 267L, 'unknown pure executable'],
- [0L, 'long', '=', 268L, 'unknown demand paged pure executable'],
- [0L, 'long', '=', 269L, 'unknown demand paged pure executable'],
- [0L, 'long', '=', 270L, 'unknown readable demand paged pure executable'],
- [0L, 'string', '=', 'begin uuencoded', 'or xxencoded text'],
- [0L, 'string', '=', 'xbtoa Begin', "btoa'd text"],
- [0L, 'string', '=', '$\012ship', "ship'd binary text"],
- [0L, 'string', '=', 'Decode the following with bdeco', 'bencoded News text'],
- [11L, 'string', '=', 'must be converted with BinHex', 'BinHex binary text'],
- [0L, 'short', '=', 610L, 'Perkin-Elmer executable'],
- [0L, 'beshort', '=', 572L, 'amd 29k coff noprebar executable'],
- [0L, 'beshort', '=', 1572L, 'amd 29k coff prebar executable'],
- [0L, 'beshort', '=', 160007L, 'amd 29k coff archive'],
- [6L, 'beshort', '=', 407L, 'unicos (cray) executable'],
- [596L, 'string', '=', 'X\337\377\377', 'Ultrix core file'],
- [0L, 'string', '=', 'Joy!peffpwpc', 'header for PowerPC PEF executable'],
- [0L, 'lelong', '=', 101557L, 'VAX single precision APL workspace'],
- [0L, 'lelong', '=', 101556L, 'VAX double precision APL workspace'],
- [0L, 'lelong', '=', 407L, 'VAX executable'],
- [0L, 'lelong', '=', 410L, 'VAX pure executable'],
- [0L, 'lelong', '=', 413L, 'VAX demand paged pure executable'],
- [0L, 'leshort', '=', 570L, 'VAX COFF executable'],
- [0L, 'leshort', '=', 575L, 'VAX COFF pure executable'],
- [0L, 'string', '=', 'LBLSIZE=', 'VICAR image data'],
- [43L, 'string', '=', 'SFDU_LABEL', 'VICAR label file'],
- [0L, 'short', '=', 21845L, 'VISX image file'],
- [0L, 'string', '=', '\260\0000\000', 'VMS VAX executable'],
- [0L, 'belong', '=', 50331648L, 'VMS Alpha executable'],
- [1L, 'string', '=', 'WPC', '(Corel/WP)'],
- [0L, 'string', '=', 'core', 'core file (Xenix)'],
- [0L, 'byte', '=', 128L, '8086 relocatable (Microsoft)'],
- [0L, 'leshort', '=', 65381L, 'x.out'],
- [0L, 'leshort', '=', 518L, 'Microsoft a.out'],
- [0L, 'leshort', '=', 320L, 'old Microsoft 8086 x.out'],
- [0L, 'lelong', '=', 518L, 'b.out'],
- [0L, 'leshort', '=', 1408L, 'XENIX 8086 relocatable or 80286 small model'],
- [0L, 'long', '=', 59399L, 'object file (z8000 a.out)'],
- [0L, 'long', '=', 59400L, 'pure object file (z8000 a.out)'],
- [0L, 'long', '=', 59401L, 'separate object file (z8000 a.out)'],
- [0L, 'long', '=', 59397L, 'overlay object file (z8000 a.out)'],
- [0L, 'string', '=', 'ZyXEL\002', 'ZyXEL voice data'],
-]
-
-magicNumbers = []
-
-def strToNum(n):
- val = 0
- col = long(1)
- if n[:1] == 'x': n = '0' + n
- if n[:2] == '0x':
- # hex
- n = string.lower(n[2:])
- while len(n) > 0:
- l = n[len(n) - 1]
- val = val + string.hexdigits.index(l) * col
- col = col * 16
- n = n[:len(n)-1]
- elif n[0] == '\\':
- # octal
- n = n[1:]
- while len(n) > 0:
- l = n[len(n) - 1]
- if ord(l) < 48 or ord(l) > 57: break
- val = val + int(l) * col
- col = col * 8
- n = n[:len(n)-1]
- else:
- val = string.atol(n)
- return val
-
-def unescape(s):
- # replace string escape sequences
- while 1:
- m = re.search(r'\\', s)
- if not m: break
- x = m.start()+1
- if m.end() == len(s):
- # escaped space at end
- s = s[:len(s)-1] + ' '
- elif s[x:x+2] == '0x':
- # hex ascii value
- c = chr(strToNum(s[x:x+4]))
- s = s[:x-1] + c + s[x+4:]
- elif s[m.start()+1] == 'x':
- # hex ascii value
- c = chr(strToNum(s[x:x+3]))
- s = s[:x-1] + c + s[x+3:]
- elif ord(s[x]) > 47 and ord(s[x]) < 58:
- # octal ascii value
- end = x
- while (ord(s[end]) > 47 and ord(s[end]) < 58):
- end = end + 1
- if end > len(s) - 1: break
- c = chr(strToNum(s[x-1:end]))
- s = s[:x-1] + c + s[end:]
- elif s[x] == 'n':
- # newline
- s = s[:x-1] + '\n' + s[x+1:]
- else:
- break
- return s
-
-class magicTest:
- def __init__(self, offset, t, op, value, msg, mask = None):
- if t.count('&') > 0:
- mask = strToNum(t[t.index('&')+1:])
- t = t[:t.index('&')]
- if type(offset) == type('a'):
- self.offset = strToNum(offset)
- else:
- self.offset = offset
- self.type = t
- self.msg = msg
- self.subTests = []
- self.op = op
- self.mask = mask
- self.value = value
-
-
- def test(self, data):
- if self.mask:
- data = data & self.mask
- if self.op == '=':
- if self.value == data: return self.msg
- elif self.op == '<':
- pass
- elif self.op == '>':
- pass
- elif self.op == '&':
- pass
- elif self.op == '^':
- pass
- return None
-
- def compare(self, data):
- #print str([self.type, self.value, self.msg])
- try:
- if self.type == 'string':
- c = ''; s = ''
- for i in range(0, len(self.value)+1):
- if i + self.offset > len(data) - 1: break
- s = s + c
- [c] = struct.unpack('c', data[self.offset + i])
- data = s
- elif self.type == 'short':
- [data] = struct.unpack('h', data[self.offset : self.offset + 2])
- elif self.type == 'leshort':
- [data] = struct.unpack('<h', data[self.offset : self.offset + 2])
- elif self.type == 'beshort':
- [data] = struct.unpack('>H', data[self.offset : self.offset + 2])
- elif self.type == 'long':
- [data] = struct.unpack('l', data[self.offset : self.offset + 4])
- elif self.type == 'lelong':
- [data] = struct.unpack('<l', data[self.offset : self.offset + 4])
- elif self.type == 'belong':
- [data] = struct.unpack('>l', data[self.offset : self.offset + 4])
- else:
- #print 'UNKNOWN TYPE: ' + self.type
- pass
- except:
- return None
-
-# print str([self.msg, self.value, data])
- return self.test(data)
-
-
-def load(file):
- global magicNumbers
- lines = open(file).readlines()
- last = { 0: None }
- for line in lines:
- if re.match(r'\s*#', line):
- # comment
- continue
- else:
- # split up by space delimiters, and remove trailing space
- line = string.rstrip(line)
- line = re.split(r'\s*', line)
- if len(line) < 3:
- # bad line
- continue
- offset = line[0]
- type = line[1]
- value = line[2]
- level = 0
- while offset[0] == '>':
- # count the level of the type
- level = level + 1
- offset = offset[1:]
- l = magicNumbers
- if level > 0:
- l = last[level - 1].subTests
- if offset[0] == '(':
- # don't handle indirect offsets just yet
- print 'SKIPPING ' + string.join(list(line[3:]))
- pass
- elif offset[0] == '&':
- # don't handle relative offsets just yet
- print 'SKIPPING ' + string.join(list(line[3:]))
- pass
- else:
- operands = ['=', '<', '>', '&']
- if operands.count(value[0]) > 0:
- # a comparison operator is specified
- op = value[0]
- value = value[1:]
- else:
- print str([value, operands])
- if len(value) >1 and value[0] == '\\' and operands.count(value[1]) >0:
- # literal value that collides with operands is escaped
- value = value[1:]
- op = '='
-
- mask = None
- if type == 'string':
- while 1:
- value = unescape(value)
- if value[len(value)-1] == ' ' and len(line) > 3:
- # last value was an escaped space, join
- value = value + line[3]
- del line[3]
- else:
- break
- else:
- if value.count('&') != 0:
- mask = value[(value.index('&') + 1):]
- print 'MASK: ' + mask
- value = value[:(value.index('&')+1)]
- try: value = strToNum(value)
- except: continue
- msg = string.join(list(line[3:]))
- new = magicTest(offset, type, op, value, msg, mask)
- last[level] = new
- l.append(new)
-
-def whatis(data):
- for test in magicNumbers:
- m = test.compare(data)
- if m: return m
- # no matching, magic number. is it binary or text?
- for c in data:
- if ord(c) > 128:
- return 'data'
- # its ASCII, now do text tests
- if string.find('The', data, 0, 8192) > -1:
- return 'English text'
- if string.find('def', data, 0, 8192) > -1:
- return 'Python Source'
- return 'ASCII text'
-
-
-def file(file):
- try:
- return whatis(open(file, 'r').read(8192))
- except Exception, e:
- if str(e) == '[Errno 21] Is a directory':
- return 'directory'
- else:
- raise e
-
-
-#### BUILD DATA ####
-#load('mime-magic')
-#f = open('out', 'w')
-#for m in magicNumbers:
-# f.write(str([m.offset, m.type, m.op, m.value, m.msg]) + ',\n')
-#f.close
-
-import sys
-for m in magic:
- magicNumbers.append(magicTest(m[0], m[1], m[2], m[3], m[4]))
-
-if __name__ == '__main__':
- import sys
- for arg in sys.argv[1:]:
- msg = file(arg)
- if msg:
- print arg + ': ' + msg
- else:
- print arg + ': unknown'
diff --git a/DAVServer/mysqlauth.py b/DAVServer/mysqlauth.py
index da6249c..ab2d7d4 100644
--- a/DAVServer/mysqlauth.py
+++ b/DAVServer/mysqlauth.py
@@ -1,3 +1,20 @@
+#Copyright (c) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
+
from fileauth import DAVAuthHandler
class MySQLAuthHandler(DAVAuthHandler):
@@ -18,7 +35,7 @@ class MySQLAuthHandler(DAVAuthHandler):
qry="select * from %s.Users where User='%s' and Pass='%s'"%(Mysql.dbtable,user,pw)
Auth=DB.execute(qry)
-
+
if len(Auth) == 1:
can_write=Auth[0][3]
if not can_write and not command in nowrite:
@@ -30,7 +47,7 @@ class MySQLAuthHandler(DAVAuthHandler):
else:
self._log('Authentication failed for user %s' % user)
return 0
-
+
self._log('Authentication failed for user %s' % user)
return 0
diff --git a/DAVServer/server.py b/DAVServer/server.py
index b070a5c..1fbe47e 100755
--- a/DAVServer/server.py
+++ b/DAVServer/server.py
@@ -1,29 +1,36 @@
#!/usr/bin/env python
+#Copyright (c) 1999-2005 Christian Scholz (cs at comlounge.net)
+#
+#This library is free software; you can redistribute it and/or
+#modify it under the terms of the GNU Library General Public
+#License as published by the Free Software Foundation; either
+#version 2 of the License, or (at your option) any later version.
+#
+#This library is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#Library General Public License for more details.
+#
+#You should have received a copy of the GNU Library General Public
+#License along with this library; if not, write to the Free
+#Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+#MA 02111-1307, USA
"""
Python WebDAV Server.
-Copyright (C) 1999-2005 Christian Scholz (cs at comlounge.net)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public
- License along with this library; if not, write to the Free
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
This is an example implementation of a DAVserver using the DAV package.
"""
import getopt, sys, os
-import BaseHTTPServer
+import logging
+
+logging.basicConfig(level=logging.WARNING)
+log = logging.getLogger('pywebdav')
+
+from BaseHTTPServer import HTTPServer
+from SocketServer import ThreadingMixIn
try:
import DAV
@@ -41,6 +48,15 @@ from fshandler import FilesystemHandler
from daemonize import startstop
from DAV.INI_Parse import Configuration
+LEVELS = {'debug': logging.DEBUG,
+ 'info': logging.INFO,
+ 'warning': logging.WARNING,
+ 'error': logging.ERROR,
+ 'critical': logging.CRITICAL}
+
+class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
+ """Handle requests in a separate thread."""
+
def runserver(
port = 8008, host='localhost',
directory='/tmp',
@@ -49,26 +65,26 @@ def runserver(
user = '',
password = '',
handler = DAVAuthHandler,
- server = BaseHTTPServer.HTTPServer):
+ server = ThreadedHTTPServer):
directory = directory.strip()
directory = directory.rstrip('/')
host = host.strip()
-
+
if not os.path.isdir(directory):
- print >>sys.stderr, '>> ERROR: %s is not a valid directory!' % directory
+ log.error('%s is not a valid directory!' % directory)
return sys.exit(233)
-
+
# basic checks against wrong hosts
if host.find('/') != -1 or host.find(':') != -1:
- print >>sys.stderr, '>> ERROR: Malformed host %s' % host
+ log.error('Malformed host %s' % host)
return sys.exit(233)
# no root directory
if directory == '/':
- print >>sys.stderr, '>> ERROR: Root directory not allowed!'
+ log.error('Root directory not allowed!')
sys.exit(233)
-
+
# dispatch directory and host to the filesystem handler
# This handler is responsible from where to take the data
handler.IFACE_CLASS = FilesystemHandler(directory, 'http://%s:%s/' % (host, port), verbose )
@@ -76,32 +92,27 @@ def runserver(
# put some extra vars
handler.verbose = verbose
if noauth:
- print >>sys.stderr, '>> ATTENTION: Authentication disabled!'
+ log.warning('Authentication disabled!')
handler.DO_AUTH = False
- print >>sys.stderr, '>> Serving data from %s' % directory
+ log.info('Serving data from %s' % directory)
- if handler._config.DAV.lockemulation is False:
- print >>sys.stderr, '>> Deactivated LOCK, UNLOCK (WebDAV level 2) support'
+ if handler._config.DAV.getboolean('lockemulation') is False:
+ log.info('Deactivated LOCK, UNLOCK (WebDAV level 2) support')
handler.IFACE_CLASS.mimecheck = True
- if handler._config.DAV.mimecheck is False:
+ if handler._config.DAV.getboolean('mimecheck') is False:
handler.IFACE_CLASS.mimecheck = False
- print >>sys.stderr, '>> Disabled mimetype sniffing (All files will have type application/octet-stream)'
-
+ log.info('Disabled mimetype sniffing (All files will have type application/octet-stream)')
+
# initialize server on specified port
runner = server( (host, port), handler )
- print >>sys.stderr, '>> Listening on %s (%i)' % (host, port)
-
- if verbose:
- print >>sys.stderr, '>> Verbose mode ON'
-
- print ''
+ print('Listening on %s (%i)' % (host, port))
try:
runner.serve_forever()
except KeyboardInterrupt:
- print >>sys.stderr, '\n>> Killed by user'
+ log.info('Killed by user')
usage = """PyWebDAV server (version %s)
Standalone WebDAV server
@@ -144,10 +155,12 @@ Parameters:
stop - Stop daemon
restart - Restart complete server
status - Returns status of server
-
+
-v, --verbose Be verbose
+ -l, --loglevel Select the log level : DEBUG, INFO, WARNING, ERROR, CRITICAL
+ Default is WARNING
-h, --help Show this screen
-
+
Please send bug reports and feature requests to %s
""" % (__version__, __author__)
@@ -157,6 +170,9 @@ def setupDummyConfig(**kw):
def __init__(self, **kw):
self.__dict__.update(**kw)
+ def getboolean(self, name):
+ return (str(getattr(self, name, 0)) in ('1', "yes", "true", "on", "True"))
+
class DummyConfig:
DAV = DummyConfigDAV(**kw)
@@ -177,17 +193,19 @@ def run():
lockemulation = True
configfile = ''
mimecheck = True
-
+ loglevel = 'warning'
+
# parse commandline
try:
- opts, args = getopt.getopt(sys.argv[1:], 'P:D:H:d:u:p:nvhmJi:c:M',
- ['host=', 'port=', 'directory=', 'user=', 'password=','daemon=', 'noauth', 'help', 'verbose',
- 'mysql', 'icounter=', 'config=', 'lockemu', 'nomime'])
+ opts, args = getopt.getopt(sys.argv[1:], 'P:D:H:d:u:p:nvhmJi:c:Ml:',
+ ['host=', 'port=', 'directory=', 'user=', 'password=',
+ 'daemon=', 'noauth', 'help', 'verbose', 'mysql',
+ 'icounter=', 'config=', 'lockemu', 'nomime', 'loglevel'])
except getopt.GetoptError, e:
print usage
print '>>>> ERROR: %s' % str(e)
sys.exit(2)
-
+
for o,a in opts:
if o in ['-i', '--icounter']:
counter = int(str(a).strip())
@@ -216,10 +234,13 @@ def run():
if o in ['-v', '--verbose']:
verbose = True
+ if o in ['-l', '--loglevel']:
+ loglevel = a.lower()
+
if o in ['-h', '--help']:
print usage
sys.exit(2)
-
+
if o in ['-n', '--noauth']:
noauth = True
@@ -233,13 +254,20 @@ def run():
daemonize = True
daemonaction = a
+ chunked_http_response = 1
+
+ # This feature are disabled because they are unstable
+ http_request_use_iterator = 0
+ http_response_use_iterator = 0
+
conf = None
if configfile != '':
- print >>sys.stderr, 'Reading configuration from %s' % configfile
+ log.info('Reading configuration from %s' % configfile)
conf = Configuration(configfile)
dv = conf.DAV
verbose = bool(int(dv.verbose))
+ loglevel = dv.get('loglevel', loglevel).lower()
directory = dv.directory
port = dv.port
host = dv.host
@@ -253,6 +281,15 @@ def run():
lockemulation = dv.lockemulation
mimecheck = dv.mimecheck
+ if 'chunked_http_response' not in dv:
+ dv.set('chunked_http_response', chunked_http_response)
+
+ if 'http_request_use_iterator' not in dv:
+ dv.set('http_request_use_iterator', http_request_use_iterator)
+
+ if 'http_response_use_iterator' not in dv:
+ dv.set('http_response_use_iterator', http_response_use_iterator)
+
else:
_dc = { 'verbose' : verbose,
@@ -266,18 +303,27 @@ def run():
'daemonaction' : daemonaction,
'counter' : counter,
'lockemulation' : lockemulation,
- 'mimecheck' : mimecheck}
+ 'mimecheck' : mimecheck,
+ 'chunked_http_response': chunked_http_response,
+ 'http_request_use_iterator': http_request_use_iterator,
+ 'http_response_use_iterator': http_response_use_iterator
+ }
conf = setupDummyConfig(**_dc)
+ if verbose and (LEVELS[loglevel] > LEVELS['info']):
+ loglevel = 'info'
+
+ logging.getLogger().setLevel(LEVELS[loglevel])
+
if mysql == True and configfile == '':
- print >>sys.stderr, '>> ERROR: You can only use MySQL with configuration file!'
+ log.error('You can only use MySQL with configuration file!')
sys.exit(3)
if daemonaction != 'stop':
- print >>sys.stderr, 'Starting up PyWebDAV server (version %s)' % __version__
+ log.info('Starting up PyWebDAV server (version %s)' % __version__)
else:
- print >>sys.stderr, 'Stopping PyWebDAV server (version %s)' % __version__
+ log.info('Stopping PyWebDAV server (version %s)' % __version__)
if not noauth and daemonaction not in ['status', 'stop']:
if not user:
@@ -285,19 +331,23 @@ def run():
print >>sys.stderr, '>> ERROR: No usable parameter specified!'
print >>sys.stderr, '>> Example: davserver -D /home/files -n'
sys.exit(3)
-
+
if daemonaction == 'status':
- print >>sys.stdout, 'Checking for state...'
-
+ log.info('Checking for state...')
+
if type(port) == type(''):
port = int(port.strip())
-
+
+ log.info('chunked_http_response feature %s' % (conf.DAV.getboolean('chunked_http_response') and 'ON' or 'OFF' ))
+ log.info('http_request_use_iterator feature %s' % (conf.DAV.getboolean('http_request_use_iterator') and 'ON' or 'OFF' ))
+ log.info('http_response_use_iterator feature %s' % (conf.DAV.getboolean('http_response_use_iterator') and 'ON' or 'OFF' ))
+
if daemonize:
# check if pid file exists
if os.path.exists('/tmp/pydav%s.pid' % counter) and daemonaction not in ['status', 'stop']:
- print >>sys.stderr, \
- '>> ERROR: Found another instance! Either use -i to specifiy another instance number or remove /tmp/pydav%s.pid!' % counter
+ log.error(
+ 'Found another instance! Either use -i to specifiy another instance number or remove /tmp/pydav%s.pid!' % counter)
sys.exit(3)
startstop(stdout='/tmp/pydav%s.log' % counter,
@@ -305,7 +355,7 @@ def run():
pidfile='/tmp/pydav%s.pid' % counter,
startmsg='>> Started PyWebDAV (PID: %s)',
action=daemonaction)
-
+
# start now
handler = DAVAuthHandler
if mysql == True:
@@ -314,7 +364,8 @@ def run():
# injecting options
handler._config = conf
- runserver(port, host, directory, verbose, noauth, user, password, handler=handler)
+ runserver(port, host, directory, verbose, noauth, user, password,
+ handler=handler)
if __name__ == '__main__':
run()
diff --git a/PKG-INFO b/PKG-INFO
index a24cad4..946c108 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,19 +1,41 @@
Metadata-Version: 1.0
Name: PyWebDAV
-Version: 0.9.3
+Version: 0.9.4
Summary: WebDAV library including a standalone server for python
Home-page: http://code.google.com/p/pywebdav/
Author: Simon Pamies
Author-email: spamsch at gmail.com
License: GPL v2
Description:
- WebDAV library for python. Consists of a server and the DAV package that provides WebDAV server(!) functionality.
- Currently supports WebDAV level 1 and level 2 (LOCK, UNLOCK) making it play nice with cadaver, Mac OS X Finder, Windows Explorer or even iCal.
+ WebDAV library for python.
+
+ Consists of a *server* that is ready to run
+ Serve and the DAV package that provides WebDAV server(!) functionality.
+
+ Currently supports
+
+ * WebDAV level 1
+ * Level 2 (LOCK, UNLOCK)
+ * Experimental iterator support
+
+ It plays nice with
+
+ * Mac OS X Finder
+ * Windows Explorer
+ * iCal
+ * cadaver
+ * Nautilus
+
+ This package does *not* provide client functionality.
+
+ Installation
+ ============
After installation of this package you will have a new script in you $PYTHON/bin directory called
*davserver*. This serves as the main entry point to the server.
- This package does *not* provide client functionality.
+ Examples
+ ========
Example (using easy_install)::
@@ -29,6 +51,242 @@ Description:
For more information: http://code.google.com/p/pywebdav/
+ Changes
+ =======
+
+
+ 0.9.4 (April 15 2010)
+ ---------------------
+
+ Add somme configuration setting variable to enable/disable iterator and chunk support
+ [Stephane Klein]
+
+ Removed os.system calls thus fixing issue 32
+ [Simon Pamies]
+
+ Fixed issue 14
+ [Simon Pamies]
+
+ Removed magic.py module - replaced with mimetypes module
+ [Simon Pamies]
+
+ Print User-Agent information in log request.
+ [Stephane Klein]
+
+ Fix issue 13 : return http 1.0 compatible response (not chunked) when request http version is 1.0
+ [cliff.wells]
+
+ Enhance logging mechanism
+ [Stephane Klein]
+
+ Fix issue 15 : I've error when I execute PUT action with Apple Finder client
+ [Stephane Klein]
+
+ Fix issue 14 : config.ini boolean parameter reading issue
+ [Stephane Klein]
+
+ 0.9.3 (July 2 2009)
+ -------------------
+
+ Setting WebDAV v2 as default because LOCK and UNLOCK seem
+ to be stable by now. -J parameter is ignored and will go away.
+ [Simon Pamies]
+
+ Fix for PROPFIND to return *all* properties
+ [Cedric Krier]
+
+ Fixed do_PUT initialisation
+ [Cedric Krier]
+
+ Added REPORT support
+ [Cedric Krier]
+
+ Added support for gzip encoding
+ [Cedric Krier]
+
+ Fix for wrong --port option
+ [Martin Wendt]
+
+ Handle paths correctly for Windows related env
+ [Martin Wendt]
+
+ Included mimetype check for files
+ based on magic.py from Jason Petrone. Included
+ magic.py into this package. All magic.py code
+ (c) 2000 Jason Petrone. Included from
+ http://www.jsnp.net/code/magic.py.
+ [Joerg Friedrich, Simon Pamies]
+
+ Status check not working when server is running
+ [Joerg Friedrich]
+
+ Fixed wrong time formatting for Last-Modified
+ and creationdate (must follow RFC 822 and 3339)
+ [Cedric Krier]
+
+ 0.9.2 (May 11 2009)
+ -------------------
+
+ Fixed COPY, MOVE, DELETE to support locked
+ resources
+ [Simon Pamies]
+
+ Fixed PROPFIND to return 404 for non existing
+ objects and also reduce property bloat
+ [Simon Pamies]
+
+ Implemented fully working LOCK and UNLOCK based
+ on in memory lock/token database. Now fully supports
+ cadaver and Mac OS X Finder.
+ [Simon Pamies]
+
+ Fixed MKCOL answer to 201
+ [Jesus Cea]
+
+ Fixed MSIE webdav headers
+ [Jesus Cea]
+
+ Make propfind respect the depth from queries
+ [Cedric Krier]
+
+ Add ETag in the header of GET. This is needed to implement
+ GroupDAV, CardDAV and CalDAV.
+ [Cedric Krier]
+
+ Handle the "Expect 100-continue" header
+ [Cedric Krier]
+
+ Remove debug statements and remove logging
+ [Cedric Krier]
+
+ Use the Host header in baseuri if set.
+ [Cedric Krier]
+
+ Adding If-Match on PUT and DELETE
+ [Cedric Krier]
+
+ 0.9.1 (May 4th 2009)
+ --------------------
+
+ Restructured the structure a bit: Made server package
+ a real python package. Adapted error messages. Prepared
+ egg distribution.
+ [Simon Pamies]
+
+ Fix for time formatting bug. Thanks to Ian Kallen
+ [Simon Pamies]
+
+ Small fixes for WebDavServer (status not handled correctly) and
+ propfind (children are returned from a PROPFIND with "Depth: 0")
+ [Kjetil Irbekk]
+
+ 0.8 (Jul 15th 2008)
+ -------------------
+
+ First try of an implementation of the LOCK and UNLOCK features.
+ Still very incomplete (read: very incomplete) and not working
+ in this version.
+ [Simon Pamies]
+
+ Some code cleanups to prepare restructuring
+ [Simon Pamies]
+
+ Port to minidom because PyXML isn't longer maintained
+ [Martin v. Loewis]
+
+ utils.py: Makes use of DOMImplementation class to create a new xml document
+ Uses dom namespace features to create elements within DAV: namespace
+ [Stephane Bonhomme]
+
+ davcmd.py: Missing an indent in loop on remove and copy operations on trees, the
+ effect was that only the last object was removed/copied : always leads
+ to a failure when copying collections.
+ [Stephane Bonhomme]
+
+ propfind.py: missing a return at the end of the createResponse method (case of a
+ propfind without xml body, should act as a allprops).
+ [Stephane Bonhomme]
+
+ 0.7
+ ---
+
+ Added MySQL auth support brought by Vince Spicer
+ Added INI file support also introduced by Vince
+ Some minor bugfixes and integration changes.
+ Added instance counter to make multiple instances possible
+ Extended --help text a bit
+ [Simon Pamies]
+
+ 0.6
+ ---
+
+ Added bugfixes for buggy Mac OS X Finder implementation
+ Finder tries to stat .DS_Store without checking if it exists
+ Cleaned up readme and install files
+ Moved license to extra file
+ Added distutils support
+ Refactored module layout
+ Refactored class and module names
+ Added commandline support
+ Added daemonize support
+ Added logging facilities
+ Added extended arguments
+
+ some more things I can't remember
+ [Simon Pamies]
+
+ Changes since 0.5.1
+ -------------------
+ Updated to work with latest 4Suite
+
+ Changes since 0.5
+ -----------------
+
+ added constants.py
+ data.py must now return COLLECTION or OBJECT when getting asked for
+ resourcetype. propfind.py will automatically generate the right xml
+ element.
+ <href> now only contains the path
+ changed HTTP/1.0 header to HTTP/1.1 which makes it work with WebFolders
+ added DO_AUTH constant to AuthServer.py to control whether authentication
+ should be done or not.
+ added chunked responses in davserver.py
+ One step in order to get a server with keep-alive one day.
+ we now use 4DOM instead if PyDOM
+ the URI in a href is quoted
+ complete rewrite of the PROPFIND stuff:
+ error responses are now generated when a property if not found or not accessible
+ namespace handling is now better. We forget any prefix and create them ourselves later in the response.
+ added superclass iface.py in DAV/ in order to make implementing
+ interface classes easier. See data.py for how to use it.
+ Also note that the way data.py handles things might have changed from
+ the previous release (if you don't like it wait for 1.0!)
+ added functions to iface.py which format creationdate and lastmodified
+ implemented HEAD
+
+ lots of bugfixes
+
+ Changes since 0.3
+ -----------------
+
+ removed hard coded base uri from davserver.py and replaced by
+ a reference to the dataclass. Added this to iface.py where you
+ have to define it in your subclass.
+ added davcmd.py which contains utility functions for copy and move
+ reimplemented DELETE and removed dependencies to pydom. move actual
+ delete method to davcmd.
+ implemented COPY
+ implemented MOVE
+ fixed bugs in errors.py, needs revisiting anyway..
+ URIs are now unquoted in davserver.py before being used
+ paths in data.py are quoted in system calls in order to support
+ blanks in pathnames (e.g. mkdir '%s' )
+ switched to exceptions when catching errors from the interface class
+ added exists() method to data.py
+ added more uri utility functions to utils.py
+ millenium bugfixes ;-)
+
+
Keywords: webdav,server,dav,standalone,library,gpl,http,rfc2518,rfc 2518
Platform: Unix
Platform: Windows
diff --git a/PyWebDAV.egg-info/PKG-INFO b/PyWebDAV.egg-info/PKG-INFO
index a24cad4..946c108 100644
--- a/PyWebDAV.egg-info/PKG-INFO
+++ b/PyWebDAV.egg-info/PKG-INFO
@@ -1,19 +1,41 @@
Metadata-Version: 1.0
Name: PyWebDAV
-Version: 0.9.3
+Version: 0.9.4
Summary: WebDAV library including a standalone server for python
Home-page: http://code.google.com/p/pywebdav/
Author: Simon Pamies
Author-email: spamsch at gmail.com
License: GPL v2
Description:
- WebDAV library for python. Consists of a server and the DAV package that provides WebDAV server(!) functionality.
- Currently supports WebDAV level 1 and level 2 (LOCK, UNLOCK) making it play nice with cadaver, Mac OS X Finder, Windows Explorer or even iCal.
+ WebDAV library for python.
+
+ Consists of a *server* that is ready to run
+ Serve and the DAV package that provides WebDAV server(!) functionality.
+
+ Currently supports
+
+ * WebDAV level 1
+ * Level 2 (LOCK, UNLOCK)
+ * Experimental iterator support
+
+ It plays nice with
+
+ * Mac OS X Finder
+ * Windows Explorer
+ * iCal
+ * cadaver
+ * Nautilus
+
+ This package does *not* provide client functionality.
+
+ Installation
+ ============
After installation of this package you will have a new script in you $PYTHON/bin directory called
*davserver*. This serves as the main entry point to the server.
- This package does *not* provide client functionality.
+ Examples
+ ========
Example (using easy_install)::
@@ -29,6 +51,242 @@ Description:
For more information: http://code.google.com/p/pywebdav/
+ Changes
+ =======
+
+
+ 0.9.4 (April 15 2010)
+ ---------------------
+
+ Add somme configuration setting variable to enable/disable iterator and chunk support
+ [Stephane Klein]
+
+ Removed os.system calls thus fixing issue 32
+ [Simon Pamies]
+
+ Fixed issue 14
+ [Simon Pamies]
+
+ Removed magic.py module - replaced with mimetypes module
+ [Simon Pamies]
+
+ Print User-Agent information in log request.
+ [Stephane Klein]
+
+ Fix issue 13 : return http 1.0 compatible response (not chunked) when request http version is 1.0
+ [cliff.wells]
+
+ Enhance logging mechanism
+ [Stephane Klein]
+
+ Fix issue 15 : I've error when I execute PUT action with Apple Finder client
+ [Stephane Klein]
+
+ Fix issue 14 : config.ini boolean parameter reading issue
+ [Stephane Klein]
+
+ 0.9.3 (July 2 2009)
+ -------------------
+
+ Setting WebDAV v2 as default because LOCK and UNLOCK seem
+ to be stable by now. -J parameter is ignored and will go away.
+ [Simon Pamies]
+
+ Fix for PROPFIND to return *all* properties
+ [Cedric Krier]
+
+ Fixed do_PUT initialisation
+ [Cedric Krier]
+
+ Added REPORT support
+ [Cedric Krier]
+
+ Added support for gzip encoding
+ [Cedric Krier]
+
+ Fix for wrong --port option
+ [Martin Wendt]
+
+ Handle paths correctly for Windows related env
+ [Martin Wendt]
+
+ Included mimetype check for files
+ based on magic.py from Jason Petrone. Included
+ magic.py into this package. All magic.py code
+ (c) 2000 Jason Petrone. Included from
+ http://www.jsnp.net/code/magic.py.
+ [Joerg Friedrich, Simon Pamies]
+
+ Status check not working when server is running
+ [Joerg Friedrich]
+
+ Fixed wrong time formatting for Last-Modified
+ and creationdate (must follow RFC 822 and 3339)
+ [Cedric Krier]
+
+ 0.9.2 (May 11 2009)
+ -------------------
+
+ Fixed COPY, MOVE, DELETE to support locked
+ resources
+ [Simon Pamies]
+
+ Fixed PROPFIND to return 404 for non existing
+ objects and also reduce property bloat
+ [Simon Pamies]
+
+ Implemented fully working LOCK and UNLOCK based
+ on in memory lock/token database. Now fully supports
+ cadaver and Mac OS X Finder.
+ [Simon Pamies]
+
+ Fixed MKCOL answer to 201
+ [Jesus Cea]
+
+ Fixed MSIE webdav headers
+ [Jesus Cea]
+
+ Make propfind respect the depth from queries
+ [Cedric Krier]
+
+ Add ETag in the header of GET. This is needed to implement
+ GroupDAV, CardDAV and CalDAV.
+ [Cedric Krier]
+
+ Handle the "Expect 100-continue" header
+ [Cedric Krier]
+
+ Remove debug statements and remove logging
+ [Cedric Krier]
+
+ Use the Host header in baseuri if set.
+ [Cedric Krier]
+
+ Adding If-Match on PUT and DELETE
+ [Cedric Krier]
+
+ 0.9.1 (May 4th 2009)
+ --------------------
+
+ Restructured the structure a bit: Made server package
+ a real python package. Adapted error messages. Prepared
+ egg distribution.
+ [Simon Pamies]
+
+ Fix for time formatting bug. Thanks to Ian Kallen
+ [Simon Pamies]
+
+ Small fixes for WebDavServer (status not handled correctly) and
+ propfind (children are returned from a PROPFIND with "Depth: 0")
+ [Kjetil Irbekk]
+
+ 0.8 (Jul 15th 2008)
+ -------------------
+
+ First try of an implementation of the LOCK and UNLOCK features.
+ Still very incomplete (read: very incomplete) and not working
+ in this version.
+ [Simon Pamies]
+
+ Some code cleanups to prepare restructuring
+ [Simon Pamies]
+
+ Port to minidom because PyXML isn't longer maintained
+ [Martin v. Loewis]
+
+ utils.py: Makes use of DOMImplementation class to create a new xml document
+ Uses dom namespace features to create elements within DAV: namespace
+ [Stephane Bonhomme]
+
+ davcmd.py: Missing an indent in loop on remove and copy operations on trees, the
+ effect was that only the last object was removed/copied : always leads
+ to a failure when copying collections.
+ [Stephane Bonhomme]
+
+ propfind.py: missing a return at the end of the createResponse method (case of a
+ propfind without xml body, should act as a allprops).
+ [Stephane Bonhomme]
+
+ 0.7
+ ---
+
+ Added MySQL auth support brought by Vince Spicer
+ Added INI file support also introduced by Vince
+ Some minor bugfixes and integration changes.
+ Added instance counter to make multiple instances possible
+ Extended --help text a bit
+ [Simon Pamies]
+
+ 0.6
+ ---
+
+ Added bugfixes for buggy Mac OS X Finder implementation
+ Finder tries to stat .DS_Store without checking if it exists
+ Cleaned up readme and install files
+ Moved license to extra file
+ Added distutils support
+ Refactored module layout
+ Refactored class and module names
+ Added commandline support
+ Added daemonize support
+ Added logging facilities
+ Added extended arguments
+
+ some more things I can't remember
+ [Simon Pamies]
+
+ Changes since 0.5.1
+ -------------------
+ Updated to work with latest 4Suite
+
+ Changes since 0.5
+ -----------------
+
+ added constants.py
+ data.py must now return COLLECTION or OBJECT when getting asked for
+ resourcetype. propfind.py will automatically generate the right xml
+ element.
+ <href> now only contains the path
+ changed HTTP/1.0 header to HTTP/1.1 which makes it work with WebFolders
+ added DO_AUTH constant to AuthServer.py to control whether authentication
+ should be done or not.
+ added chunked responses in davserver.py
+ One step in order to get a server with keep-alive one day.
+ we now use 4DOM instead if PyDOM
+ the URI in a href is quoted
+ complete rewrite of the PROPFIND stuff:
+ error responses are now generated when a property if not found or not accessible
+ namespace handling is now better. We forget any prefix and create them ourselves later in the response.
+ added superclass iface.py in DAV/ in order to make implementing
+ interface classes easier. See data.py for how to use it.
+ Also note that the way data.py handles things might have changed from
+ the previous release (if you don't like it wait for 1.0!)
+ added functions to iface.py which format creationdate and lastmodified
+ implemented HEAD
+
+ lots of bugfixes
+
+ Changes since 0.3
+ -----------------
+
+ removed hard coded base uri from davserver.py and replaced by
+ a reference to the dataclass. Added this to iface.py where you
+ have to define it in your subclass.
+ added davcmd.py which contains utility functions for copy and move
+ reimplemented DELETE and removed dependencies to pydom. move actual
+ delete method to davcmd.
+ implemented COPY
+ implemented MOVE
+ fixed bugs in errors.py, needs revisiting anyway..
+ URIs are now unquoted in davserver.py before being used
+ paths in data.py are quoted in system calls in order to support
+ blanks in pathnames (e.g. mkdir '%s' )
+ switched to exceptions when catching errors from the interface class
+ added exists() method to data.py
+ added more uri utility functions to utils.py
+ millenium bugfixes ;-)
+
+
Keywords: webdav,server,dav,standalone,library,gpl,http,rfc2518,rfc 2518
Platform: Unix
Platform: Windows
diff --git a/PyWebDAV.egg-info/SOURCES.txt b/PyWebDAV.egg-info/SOURCES.txt
index 75f83ea..e80cab5 100644
--- a/PyWebDAV.egg-info/SOURCES.txt
+++ b/PyWebDAV.egg-info/SOURCES.txt
@@ -2,6 +2,7 @@ MANIFEST.in
README
VERSION
ez_setup.py
+setup.cfg
setup.py
DAV/AuthServer.py
DAV/BufferingHTTPServer.py
@@ -18,7 +19,6 @@ DAV/errors.py
DAV/iface.py
DAV/locks.py
DAV/propfind.py
-DAV/propfind.py.orig
DAV/report.py
DAV/status.py
DAV/utils.py
@@ -27,7 +27,6 @@ DAVServer/config.ini
DAVServer/daemonize.py
DAVServer/fileauth.py
DAVServer/fshandler.py
-DAVServer/magic.py
DAVServer/mysqlauth.py
DAVServer/server.py
PyWebDAV.egg-info/PKG-INFO
@@ -38,6 +37,7 @@ PyWebDAV.egg-info/not-zip-safe
PyWebDAV.egg-info/top_level.txt
doc/ARCHITECTURE
doc/Changes
+doc/Changes.rej
doc/INSTALL
doc/LICENSE
doc/TODO
diff --git a/VERSION b/VERSION
index 965065d..a602fc9 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.9.3
+0.9.4
diff --git a/doc/Changes b/doc/Changes
index 2c53012..85a6769 100644
--- a/doc/Changes
+++ b/doc/Changes
@@ -1,151 +1,183 @@
+0.9.4 (April 15 2010)
+---------------------
+
+Add somme configuration setting variable to enable/disable iterator and chunk support
+[Stephane Klein]
+
+Removed os.system calls thus fixing issue 32
+[Simon Pamies]
+
+Fixed issue 14
+[Simon Pamies]
+
+Removed magic.py module - replaced with mimetypes module
+[Simon Pamies]
+
+Print User-Agent information in log request.
+[Stephane Klein]
+
+Fix issue 13 : return http 1.0 compatible response (not chunked) when request http version is 1.0
+[cliff.wells]
+
+Enhance logging mechanism
+[Stephane Klein]
+
+Fix issue 15 : I've error when I execute PUT action with Apple Finder client
+[Stephane Klein]
+
+Fix issue 14 : config.ini boolean parameter reading issue
+[Stephane Klein]
+
0.9.3 (July 2 2009)
---
+-------------------
-- Setting WebDAV v2 as default because LOCK and UNLOCK seem
- to be stable by now. -J parameter is ignored and will go away.
- [Simon Pamies]
+Setting WebDAV v2 as default because LOCK and UNLOCK seem
+to be stable by now. -J parameter is ignored and will go away.
+[Simon Pamies]
-- Fix for PROPFIND to return *all* properties
- [Cedric Krier]
+Fix for PROPFIND to return *all* properties
+[Cedric Krier]
-- Fixed do_PUT initialisation
- [Cedric Krier]
+Fixed do_PUT initialisation
+[Cedric Krier]
-- Added REPORT support
- [Cedric Krier]
+Added REPORT support
+[Cedric Krier]
-- Added support for gzip encoding
- [Cedric Krier]
+Added support for gzip encoding
+[Cedric Krier]
-- Fix for wrong --port option
- [Martin Wendt]
+Fix for wrong --port option
+[Martin Wendt]
-- Handle paths correctly for Windows related env
- [Martin Wendt]
+Handle paths correctly for Windows related env
+[Martin Wendt]
-- Included mimetype check for files
- based on magic.py from Jason Petrone. Included
- magic.py into this package. All magic.py code
- (c) 2000 Jason Petrone. Included from
- http://www.jsnp.net/code/magic.py.
- [Joerg Friedrich, Simon Pamies]
+Included mimetype check for files
+based on magic.py from Jason Petrone. Included
+magic.py into this package. All magic.py code
+(c) 2000 Jason Petrone. Included from
+http://www.jsnp.net/code/magic.py.
+[Joerg Friedrich, Simon Pamies]
-- Status check not working when server is running
- [Joerg Friedrich]
+Status check not working when server is running
+[Joerg Friedrich]
-- Fixed wrong time formatting for Last-Modified
- and creationdate (must follow RFC 822 and 3339)
- [Cedric Krier]
+Fixed wrong time formatting for Last-Modified
+and creationdate (must follow RFC 822 and 3339)
+[Cedric Krier]
0.9.2 (May 11 2009)
---
+-------------------
-- Fixed COPY, MOVE, DELETE to support locked
- resources
- [Simon Pamies]
+Fixed COPY, MOVE, DELETE to support locked
+resources
+[Simon Pamies]
-- Fixed PROPFIND to return 404 for non existing
- objects and also reduce property bloat
- [Simon Pamies]
+Fixed PROPFIND to return 404 for non existing
+objects and also reduce property bloat
+[Simon Pamies]
-- Implemented fully working LOCK and UNLOCK based
- on in memory lock/token database. Now fully supports
- cadaver and Mac OS X Finder.
- [Simon Pamies]
+Implemented fully working LOCK and UNLOCK based
+on in memory lock/token database. Now fully supports
+cadaver and Mac OS X Finder.
+[Simon Pamies]
-- Fixed MKCOL answer to 201
- [Jesus Cea]
+Fixed MKCOL answer to 201
+[Jesus Cea]
-- Fixed MSIE webdav headers
- [Jesus Cea]
+Fixed MSIE webdav headers
+[Jesus Cea]
-- Make propfind respect the depth from queries
- [Cedric Krier]
+Make propfind respect the depth from queries
+[Cedric Krier]
-- Add ETag in the header of GET. This is needed to implement
- GroupDAV, CardDAV and CalDAV.
- [Cedric Krier]
+Add ETag in the header of GET. This is needed to implement
+GroupDAV, CardDAV and CalDAV.
+[Cedric Krier]
-- Handle the "Expect 100-continue" header
- [Cedric Krier]
+Handle the "Expect 100-continue" header
+[Cedric Krier]
-- Remove debug statements and remove logging
- [Cedric Krier]
+Remove debug statements and remove logging
+[Cedric Krier]
-- Use the Host header in baseuri if set.
- [Cedric Krier]
+Use the Host header in baseuri if set.
+[Cedric Krier]
-- Adding If-Match on PUT and DELETE
- [Cedric Krier]
+Adding If-Match on PUT and DELETE
+[Cedric Krier]
0.9.1 (May 4th 2009)
---
+--------------------
-- Restructured the structure a bit: Made server package
- a real python package. Adapted error messages. Prepared
- egg distribution.
- [Simon Pamies]
+Restructured the structure a bit: Made server package
+a real python package. Adapted error messages. Prepared
+egg distribution.
+[Simon Pamies]
-- Fix for time formatting bug. Thanks to Ian Kallen
- [Simon Pamies]
+Fix for time formatting bug. Thanks to Ian Kallen
+[Simon Pamies]
-- Small fixes for WebDavServer (status not handled correctly) and
- propfind (children are returned from a PROPFIND with "Depth: 0")
- [Kjetil Ørbekk]
+Small fixes for WebDavServer (status not handled correctly) and
+propfind (children are returned from a PROPFIND with "Depth: 0")
+[Kjetil Irbekk]
0.8 (Jul 15th 2008)
----
+-------------------
-- First try of an implementation of the LOCK and UNLOCK features.
- Still very incomplete (read: very incomplete) and not working
- in this version.
- [Simon Pamies]
+First try of an implementation of the LOCK and UNLOCK features.
+Still very incomplete (read: very incomplete) and not working
+in this version.
+[Simon Pamies]
-- Some code cleanups to prepare restructuring
- [Simon Pamies]
+Some code cleanups to prepare restructuring
+[Simon Pamies]
-- Port to minidom because PyXML isn't longer maintained
- [Martin v. Loewis]
+Port to minidom because PyXML isn't longer maintained
+[Martin v. Loewis]
-- utils.py: Makes use of DOMImplementation class to create a new xml document
- Uses dom namespace features to create elements within DAV: namespace
- [Stephane Bonhomme]
+utils.py: Makes use of DOMImplementation class to create a new xml document
+Uses dom namespace features to create elements within DAV: namespace
+[Stephane Bonhomme]
-- davcmd.py: Missing an indent in loop on remove and copy operations on trees, the
- effect was that only the last object was removed/copied : always leads
- to a failure when copying collections.
- [Stephane Bonhomme]
+davcmd.py: Missing an indent in loop on remove and copy operations on trees, the
+effect was that only the last object was removed/copied : always leads
+to a failure when copying collections.
+[Stephane Bonhomme]
-- propfind.py: missing a return at the end of the createResponse method (case of a
- propfind without xml body, should act as a allprops).
- [Stephane Bonhomme]
+propfind.py: missing a return at the end of the createResponse method (case of a
+propfind without xml body, should act as a allprops).
+[Stephane Bonhomme]
0.7
---
-- Added MySQL auth support brought by Vince Spicer
-- Added INI file support also introduced by Vince
-- Some minor bugfixes and integration changes.
-- Added instance counter to make multiple instances possible
-- Extended --help text a bit
+Added MySQL auth support brought by Vince Spicer
+Added INI file support also introduced by Vince
+Some minor bugfixes and integration changes.
+Added instance counter to make multiple instances possible
+Extended --help text a bit
+[Simon Pamies]
0.6
---
-- Added bugfixes for buggy Mac OS X Finder implementation
- Finder tries to stat .DS_Store without checking if it exists
-- Cleaned up readme and install files
-- Moved license to extra file
-- Added distutils support
-- Refactored module layout
-- Refactored class and module names
-- Added commandline support
-- Added daemonize support
-- Added logging facilities
-- Added extended arguments
-
-- some more things I can't remember (spamsch)
+Added bugfixes for buggy Mac OS X Finder implementation
+Finder tries to stat .DS_Store without checking if it exists
+Cleaned up readme and install files
+Moved license to extra file
+Added distutils support
+Refactored module layout
+Refactored class and module names
+Added commandline support
+Added daemonize support
+Added logging facilities
+Added extended arguments
+
+some more things I can't remember
+[Simon Pamies]
Changes since 0.5.1
-------------------
@@ -154,49 +186,46 @@ Updated to work with latest 4Suite
Changes since 0.5
-----------------
-- added constants.py
-- data.py must now return COLLECTION or OBJECT when getting asked for
- resourcetype. propfind.py will automatically generate the right xml
- element.
-- <href> now only contains the path
-- changed HTTP/1.0 header to HTTP/1.1 which makes it work with WebFolders
-- added DO_AUTH constant to AuthServer.py to control whether authentication
- should be done or not.
-- added chunked responses in davserver.py
- One step in order to get a server with keep-alive one day.
-- we now use 4DOM instead if PyDOM
-- the URI in a href is quoted
-- complete rewrite of the PROPFIND stuff:
- - error responses are now generated when a property if not found
- or not accessible
- - namespace handling is now better. We forget any prefix and
- create them ourselves later in the response.
-- added superclass iface.py in DAV/ in order to make implementing
- interface classes easier. See data.py for how to use it.
- Also note that the way data.py handles things might have changed from
- the previous release (if you don't like it wait for 1.0!)
-- added functions to iface.py which format creationdate and lastmodified
-- implemented HEAD
-
-- lots of bugfixes
-
+added constants.py
+data.py must now return COLLECTION or OBJECT when getting asked for
+resourcetype. propfind.py will automatically generate the right xml
+element.
+<href> now only contains the path
+changed HTTP/1.0 header to HTTP/1.1 which makes it work with WebFolders
+added DO_AUTH constant to AuthServer.py to control whether authentication
+should be done or not.
+added chunked responses in davserver.py
+One step in order to get a server with keep-alive one day.
+we now use 4DOM instead if PyDOM
+the URI in a href is quoted
+complete rewrite of the PROPFIND stuff:
+error responses are now generated when a property if not found or not accessible
+namespace handling is now better. We forget any prefix and create them ourselves later in the response.
+added superclass iface.py in DAV/ in order to make implementing
+interface classes easier. See data.py for how to use it.
+Also note that the way data.py handles things might have changed from
+the previous release (if you don't like it wait for 1.0!)
+added functions to iface.py which format creationdate and lastmodified
+implemented HEAD
+
+lots of bugfixes
Changes since 0.3
-----------------
-- removed hard coded base uri from davserver.py and replaced by
- a reference to the dataclass. Added this to iface.py where you
- have to define it in your subclass.
-- added davcmd.py which contains utility functions for copy and move
-- reimplemented DELETE and removed dependencies to pydom. move actual
- delete method to davcmd.
-- implemented COPY
-- implemented MOVE
-- fixed bugs in errors.py, needs revisiting anyway..
-- URIs are now unquoted in davserver.py before being used
-- paths in data.py are quoted in system calls in order to support
- blanks in pathnames (e.g. mkdir '%s' )
-- switched to exceptions when catching errors from the interface class
-- added exists() method to data.py
-- added more uri utility functions to utils.py
-- millenium bugfixes ;-)
+removed hard coded base uri from davserver.py and replaced by
+a reference to the dataclass. Added this to iface.py where you
+have to define it in your subclass.
+added davcmd.py which contains utility functions for copy and move
+reimplemented DELETE and removed dependencies to pydom. move actual
+delete method to davcmd.
+implemented COPY
+implemented MOVE
+fixed bugs in errors.py, needs revisiting anyway..
+URIs are now unquoted in davserver.py before being used
+paths in data.py are quoted in system calls in order to support
+blanks in pathnames (e.g. mkdir '%s' )
+switched to exceptions when catching errors from the interface class
+added exists() method to data.py
+added more uri utility functions to utils.py
+millenium bugfixes ;-)
diff --git a/doc/Changes.rej b/doc/Changes.rej
new file mode 100644
index 0000000..6d7fc72
--- /dev/null
+++ b/doc/Changes.rej
@@ -0,0 +1,34 @@
+***************
+*** 1,4 ****
+- - Enhance logging mechanism
+ [Stephane Klein]
+
+ - Fix issue 15 : I've error when I execute PUT action with Apple Finder client
+--- 1,27 ----
++ - Add somme configuration setting variable to enable/disable iterator and chunk support
++ [Stephane Klein]
++
++ - "log_request" is called after action (like Apache and other server), not before action
++ [Stephane Klein]
++
++ - Fix issue 23 : PyWebDAV need to use iterator to avoid over memory consuption
++ [Stephane Klein]
++
++ - Fix issue 22 : pywebdav need handle "Range" header information
++ in do_GET request
++ [Stephane Klein]
++
++ - Fix issue 21 : Add thread support
++ [Stephane Klein]
++
++ - Print User-Agent information in log request.
++ [Stephane Klein]
++
++ - Fix issue 13 : return http 1.0 compatible response (not chunked) when
++ request http version is 1.0
++ [cliff.wells]
++
++ - Fix issue 18 : Enhance logging mechanism
+ [Stephane Klein]
+
+ - Fix issue 15 : I've error when I execute PUT action with Apple Finder client
diff --git a/doc/INSTALL b/doc/INSTALL
index 9e167e1..db61802 100644
--- a/doc/INSTALL
+++ b/doc/INSTALL
@@ -7,7 +7,6 @@ How to install python WebDAV server
Windows seems to work
+ Python >=2.4.x
- + PyXML
2. Run setup.py
diff --git a/doc/LICENSE b/doc/LICENSE
index 40f4b8a..161a3d1 100644
--- a/doc/LICENSE
+++ b/doc/LICENSE
@@ -1,9 +1,8 @@
-
GNU LIBRARY GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1991 Free Software Foundation, Inc.
- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -437,7 +436,7 @@ DAMAGES.
END OF TERMS AND CONDITIONS
- How to Apply These Terms to Your New Libraries
+ Appendix: How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
@@ -465,7 +464,8 @@ convey the exclusion of warranty; and each file should have at least the
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA
Also add information on how to contact you by electronic and paper mail.
@@ -478,3 +478,5 @@ necessary. Here is a sample; alter the names:
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/doc/TODO b/doc/TODO
index fa66e2a..b36f8e2 100644
--- a/doc/TODO
+++ b/doc/TODO
@@ -6,7 +6,10 @@ GENERAL
- News
- TODO list
- Changes
- - Name
+ - Name
+
+- use a better solution than DAV/INI_Parse.py [Stephane Klein]
+- create WSGI server version [Stephane Klein]
MOVE
----
@@ -34,3 +37,9 @@ PROPPATCH
to be done (not that important as only DAV props are supported right now which
you cannot change)
+
+TEST
+----
+
+- create some unit test
+- maybe use davclient package to make the tests
diff --git a/setup.py b/setup.py
index f6149c5..6357c73 100644
--- a/setup.py
+++ b/setup.py
@@ -11,14 +11,38 @@ from setuptools import setup
VERSION = open('VERSION', 'r').read()
VERSION = VERSION.replace('\n', '')
+CHANGES = open('doc/Changes', 'r').read()
+
DOC = """
-WebDAV library for python. Consists of a server and the DAV package that provides WebDAV server(!) functionality.
-Currently supports WebDAV level 1 and level 2 (LOCK, UNLOCK) making it play nice with cadaver, Mac OS X Finder, Windows Explorer or even iCal.
+WebDAV library for python.
+
+Consists of a *server* that is ready to run
+Serve and the DAV package that provides WebDAV server(!) functionality.
+
+Currently supports
+
+ * WebDAV level 1
+ * Level 2 (LOCK, UNLOCK)
+ * Experimental iterator support
+
+It plays nice with
+
+ * Mac OS X Finder
+ * Windows Explorer
+ * iCal
+ * cadaver
+ * Nautilus
+
+This package does *not* provide client functionality.
+
+Installation
+============
After installation of this package you will have a new script in you $PYTHON/bin directory called
*davserver*. This serves as the main entry point to the server.
-This package does *not* provide client functionality.
+Examples
+========
Example (using easy_install)::
@@ -33,7 +57,12 @@ Example (unpacking file locally)::
davserver -D /tmp -n
For more information: http://code.google.com/p/pywebdav/
-"""
+
+Changes
+=======
+
+%s
+""" % CHANGES
from distutils.core import setup
setup(name='PyWebDAV',
commit 2208a4a06c1cea9e4e745341e01b08882429be1b
Author: Daniel Baumann <daniel at debian.org>
Date: Tue Jul 7 16:30:09 2009 +0200
Adding upstream version 0.9.3.
diff --git a/DAV/WebDAVServer.py b/DAV/WebDAVServer.py
index fdfa564..bbc60ec 100644
--- a/DAV/WebDAVServer.py
+++ b/DAV/WebDAVServer.py
@@ -43,6 +43,7 @@ import urllib
import random
from propfind import PROPFIND
+from report import REPORT
from delete import DELETE
from davcopy import COPY
from davmove import MOVE
@@ -53,6 +54,8 @@ from errors import *
from constants import DAV_VERSION_1, DAV_VERSION_2
from locks import LockManager
+import gzip
+import StringIO
class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
"""Simple DAV request handler with
@@ -64,6 +67,7 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
- PROPFIND
- PROPPATCH
- MKCOL
+ - REPORT
experimental
- LOCK
@@ -75,6 +79,7 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
"""
server_version = "DAV/" + __version__
+ encode_threshold = 1400 # common MTU
### utility functions
def _log(self, message):
@@ -92,6 +97,16 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
self.send_header(a,v)
if DATA:
+ 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)
+ output.write(DATA)
+ output.close()
+ buffer.seek(0)
+ DATA = buffer.getvalue()
+ self.send_header('Content-Encoding', 'gzip')
+
self.send_header('Content-Length', len(DATA))
self.send_header('Content-Type', ctype)
else:
@@ -110,6 +125,17 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
self.send_header("Connection", "close")
self.send_header("Transfer-Encoding", "chunked")
self.send_header('Date', rfc1123_date())
+
+ 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)
+ output.write(DATA)
+ output.close()
+ buffer.seek(0)
+ DATA = buffer.getvalue()
+ self.send_header('Content-Encoding', 'gzip')
+
self.end_headers()
self._append(hex(len(DATA))[2:]+"\r\n")
@@ -231,6 +257,30 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
self.send_body_chunks(DATA, '207','Multi-Status','Multiple responses')
+ def do_REPORT(self):
+ """ Query properties on defined resource. """
+
+ dc = self.IFACE_CLASS
+
+ # 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'):
+ l = self.headers['Content-Length']
+ body = self.rfile.read(atoi(l))
+
+ uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
+ uri = urllib.unquote(uri)
+
+ rp = REPORT(uri, dc, self.headers.get('Depth', 'infinity'), body)
+
+ try:
+ DATA = '%s\n' % rp.createResponse()
+ except DAV_Error, (ec,dd):
+ return self.send_status(ec)
+
+ self.send_body_chunks(DATA, '207','Multi-Status','Multiple responses')
+
def do_MKCOL(self):
""" create a new collection """
@@ -308,6 +358,8 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
def do_PUT(self):
dc=self.IFACE_CLASS
+ uri=urlparse.urljoin(self.get_baseuri(dc), self.path)
+ uri=urllib.unquote(uri)
# Handle If-Match
if self.headers.has_key('If-Match'):
@@ -364,8 +416,6 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
if self.headers.has_key("Content-Length"):
l=self.headers['Content-Length']
body=self.rfile.read(atoi(l))
- uri=urlparse.urljoin(self.get_baseuri(dc), self.path)
- uri=urllib.unquote(uri)
# locked resources are not allowed to be overwritten
if self._l_isLocked(uri):
diff --git a/DAV/constants.py b/DAV/constants.py
index 2b1d796..4eb99b9 100644
--- a/DAV/constants.py
+++ b/DAV/constants.py
@@ -14,7 +14,7 @@ RT_PROP=3
DAV_VERSION_1 = {
'version' : '1',
'options' :
- 'GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE'
+ 'GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE, REPORT'
}
DAV_VERSION_2 = {
diff --git a/DAV/davcopy.py b/DAV/davcopy.py
index d9e819d..726a0f1 100644
--- a/DAV/davcopy.py
+++ b/DAV/davcopy.py
@@ -1,26 +1,3 @@
-#!/usr/bin/env python
-
-"""
- python davserver
- Copyright (C) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public
- License along with this library; if not, write to the Free
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-
-"""
-
import xml.dom.minidom
domimpl = xml.dom.minidom.getDOMImplementation()
diff --git a/DAV/davmove.py b/DAV/davmove.py
index 78c1e64..e0ea082 100644
--- a/DAV/davmove.py
+++ b/DAV/davmove.py
@@ -1,27 +1,3 @@
-#!/usr/bin/env python
-
-"""
- python davserver
- Copyright (C) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public
- License along with this library; if not, write to the Free
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-
-"""
-
-
import sys
import string
import urlparse
diff --git a/DAV/delete.py b/DAV/delete.py
index 4f3e4eb..15c880d 100644
--- a/DAV/delete.py
+++ b/DAV/delete.py
@@ -1,26 +1,3 @@
-#!/usr/bin/env python
-
-"""
-
- python davserver
- Copyright (C) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public
- License along with this library; if not, write to the Free
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-
-"""
import os
import string
import urllib
diff --git a/DAV/iface.py b/DAV/iface.py
index 5f3bda7..ad78ce7 100644
--- a/DAV/iface.py
+++ b/DAV/iface.py
@@ -72,7 +72,7 @@ class dav_interface:
prefix=self.M_NS[ns]
else:
raise DAV_NotFound
- mname=prefix+"_"+propname
+ mname=prefix+"_"+propname.replace('-', '_')
try:
m=getattr(self,mname)
r=m(uri)
@@ -132,13 +132,13 @@ class dav_interface:
""" return the creationdate of a resource """
d=self.get_creationdate(uri)
# format it
- return time.strftime("%Y-%m-%dT%H:%M:%SZ",time.localtime(d))
+ return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(d))
def _get_dav_getlastmodified(self,uri):
""" return the last modified date of a resource """
d=self.get_lastmodified(uri)
# format it
- return time.strftime("%a, %d %b %Y %H:%M:%S %Z",time.localtime(d))
+ return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(d))
###
diff --git a/DAV/propfind.py b/DAV/propfind.py
index 4c68cd6..da56db6 100644
--- a/DAV/propfind.py
+++ b/DAV/propfind.py
@@ -32,17 +32,17 @@ class PROPFIND:
self.nsmap={}
self.proplist={}
self.default_ns=None
- self.__dataclass=dataclass
- self.__depth=str(depth)
- self.__uri=uri
- self.__has_body=None # did we parse a body?
+ self._dataclass=dataclass
+ self._depth=str(depth)
+ self._uri=uri.rstrip('/')
+ self._has_body=None # did we parse a body?
if dataclass.verbose:
print >>sys.stderr, 'PROPFIND: Depth is %s, URI is %s' % (depth, uri)
if body:
self.request_type, self.proplist, self.namespaces = utils.parse_propfind(body)
- self.__has_body = True
+ self._has_body = True
def createResponse(self):
""" Create the multistatus response
@@ -60,7 +60,7 @@ class PROPFIND:
"""
# check if resource exists
- if not self.__dataclass.exists(self.__uri):
+ if not self._dataclass.exists(self._uri):
raise DAV_NotFound
df = None
@@ -83,29 +83,37 @@ class PROPFIND:
def create_propname(self):
""" create a multistatus response for the prop names """
- dc=self.__dataclass
+ dc=self._dataclass
# create the document generator
doc = domimpl.createDocument(None, "multistatus", None)
ms = doc.documentElement
ms.setAttribute("xmlns:D", "DAV:")
ms.tagName = 'D:multistatus'
- if self.__depth=="0":
- pnames=dc.get_propnames(self.__uri)
- re=self.mk_propname_response(self.__uri,pnames, doc)
+ if self._depth=="0":
+ pnames=dc.get_propnames(self._uri)
+ re=self.mk_propname_response(self._uri,pnames, doc)
ms.appendChild(re)
- elif self.__depth=="1":
- pnames=dc.get_propnames(self.__uri)
- re=self.mk_propname_response(self.__uri,pnames, doc)
+ elif self._depth=="1":
+ pnames=dc.get_propnames(self._uri)
+ re=self.mk_propname_response(self._uri,pnames, doc)
ms.appendChild(re)
- for newuri in dc.get_childs(self.__uri):
+ for newuri in dc.get_childs(self._uri):
pnames=dc.get_propnames(newuri)
re=self.mk_propname_response(newuri,pnames, doc)
ms.appendChild(re)
-
- # *** depth=="infinity"
+ elif self._depth=='infinity':
+ uri_list = [self._uri]
+ while uri_list:
+ uri = uri_list.pop()
+ pnames=dc.get_propnames(uri)
+ re=self.mk_propname_response(uri,pnames, doc)
+ ms.appendChild(re)
+ uri_childs = self._dataclass.get_childs(uri)
+ if uri_childs:
+ uri_list.extend(uri_childs)
return doc.toxml(encoding="utf-8")
@@ -113,7 +121,7 @@ class PROPFIND:
""" return a list of all properties """
self.proplist={}
self.namespaces=[]
- for ns,plist in self.__dataclass.get_propnames(self.__uri).items():
+ for ns,plist in self._dataclass.get_propnames(self._uri).items():
self.proplist[ns]=plist
self.namespaces.append(ns)
@@ -148,20 +156,30 @@ class PROPFIND:
ms.setAttribute("xmlns:D", "DAV:")
ms.tagName = 'D:multistatus'
- if self.__depth=="0":
- gp,bp=self.get_propvalues(self.__uri)
- res=self.mk_prop_response(self.__uri,gp,bp,doc)
+ if self._depth=="0":
+ gp,bp=self.get_propvalues(self._uri)
+ res=self.mk_prop_response(self._uri,gp,bp,doc)
ms.appendChild(res)
- elif self.__depth=="1":
- gp,bp=self.get_propvalues(self.__uri)
- res=self.mk_prop_response(self.__uri,gp,bp,doc)
+ elif self._depth=="1":
+ gp,bp=self.get_propvalues(self._uri)
+ res=self.mk_prop_response(self._uri,gp,bp,doc)
ms.appendChild(res)
- for newuri in self.__dataclass.get_childs(self.__uri):
+ for newuri in self._dataclass.get_childs(self._uri):
gp,bp=self.get_propvalues(newuri)
res=self.mk_prop_response(newuri,gp,bp,doc)
ms.appendChild(res)
+ elif self._depth=='infinity':
+ uri_list = [self._uri]
+ while uri_list:
+ uri = uri_list.pop()
+ gp,bp=self.get_propvalues(uri)
+ res=self.mk_prop_response(uri,gp,bp,doc)
+ ms.appendChild(res)
+ uri_childs = self._dataclass.get_childs(uri)
+ if uri_childs:
+ uri_list.extend(uri_childs)
return doc.toxml(encoding="utf-8")
@@ -240,11 +258,11 @@ class PROPFIND:
pe.appendChild(v)
else:
if p=="resourcetype":
- if v=="1":
+ if v==1:
ve=doc.createElement("D:collection")
pe.appendChild(ve)
else:
- ve=doc.createTextNode(str(v))
+ ve=doc.createTextNode(v)
pe.appendChild(ve)
gp.appendChild(pe)
@@ -294,7 +312,7 @@ class PROPFIND:
good_props={}
bad_props={}
- ddc = self.__dataclass
+ ddc = self._dataclass
for (ns,plist) in self.proplist.items():
good_props[ns]={}
bad_props={}
@@ -302,11 +320,7 @@ class PROPFIND:
ec = 0
try:
r=ddc.get_prop(uri,ns,prop)
-
- # support for element returns
- if hasattr(r, '__class__') and r.__class__.__name__ == 'Element':
- good_props[ns][prop]=r
- else: good_props[ns][prop]=str(r)
+ good_props[ns][prop]=r
except DAV_Error, error_code:
ec=error_code[0]
diff --git a/DAV/report.py b/DAV/report.py
new file mode 100644
index 0000000..a12721d
--- /dev/null
+++ b/DAV/report.py
@@ -0,0 +1,122 @@
+
+from propfind import PROPFIND
+from xml.dom import minidom
+domimpl = minidom.getDOMImplementation()
+from utils import get_parenturi
+
+
+class REPORT(PROPFIND):
+
+ def __init__(self, uri, dataclass, depth, body):
+ PROPFIND.__init__(self, uri, dataclass, depth, body)
+
+ doc = minidom.parseString(body)
+
+ self.filter = doc.documentElement
+
+ def create_propname(self):
+ """ create a multistatus response for the prop names """
+
+ dc=self._dataclass
+ # create the document generator
+ doc = domimpl.createDocument(None, "multistatus", None)
+ ms = doc.documentElement
+ ms.setAttribute("xmlns:D", "DAV:")
+ ms.tagName = 'D:multistatus'
+
+ if self._depth=="0":
+ if self._uri in self._dataclass.get_childs(get_parenturi(self._uri),
+ self.filter):
+ pnames=dc.get_propnames(self._uri)
+ re=self.mk_propname_response(self._uri,pnames, doc)
+ ms.appendChild(re)
+
+ elif self._depth=="1":
+ if self._uri in self._dataclass.get_childs(get_parenturi(self._uri),
+ self.filter):
+ pnames=dc.get_propnames(self._uri)
+ re=self.mk_propname_response(self._uri,pnames, doc)
+ ms.appendChild(re)
+
+ for newuri in dc.get_childs(self._uri, self.filter):
+ pnames=dc.get_propnames(newuri)
+ re=self.mk_propname_response(newuri,pnames, doc)
+ ms.appendChild(re)
+ elif self._depth=='infinity':
+ uri_list = [self._uri]
+ while uri_list:
+ uri = uri_list.pop()
+ if uri in self._dataclass.get_childs(get_parenturi(uri),
+ self.filter):
+ pnames=dc.get_propnames(uri)
+ re=self.mk_propname_response(uri,pnames, doc)
+ ms.appendChild(re)
+ uri_childs = self._dataclass.get_childs(uri)
+ if uri_childs:
+ uri_list.extend(uri_childs)
+
+ return doc.toxml(encoding="utf-8")
+
+ def create_prop(self):
+ """ handle a <prop> request
+
+ This will
+
+ 1. set up the <multistatus>-Framework
+
+ 2. read the property values for each URI
+ (which is dependant on the Depth header)
+ This is done by the get_propvalues() method.
+
+ 3. For each URI call the append_result() method
+ to append the actual <result>-Tag to the result
+ document.
+
+ We differ between "good" properties, which have been
+ assigned a value by the interface class and "bad"
+ properties, which resulted in an error, either 404
+ (Not Found) or 403 (Forbidden).
+
+ """
+
+
+ # create the document generator
+ doc = domimpl.createDocument(None, "multistatus", None)
+ ms = doc.documentElement
+ ms.setAttribute("xmlns:D", "DAV:")
+ ms.tagName = 'D:multistatus'
+
+ if self._depth=="0":
+ if self._uri in self._dataclass.get_childs(get_parenturi(self._uri),
+ self.filter):
+ gp,bp=self.get_propvalues(self._uri)
+ res=self.mk_prop_response(self._uri,gp,bp,doc)
+ ms.appendChild(res)
+
+ elif self._depth=="1":
+ if self._uri in self._dataclass.get_childs(get_parenturi(self._uri),
+ self.filter):
+ gp,bp=self.get_propvalues(self._uri)
+ res=self.mk_prop_response(self._uri,gp,bp,doc)
+ ms.appendChild(res)
+
+ for newuri in self._dataclass.get_childs(self._uri, self.filter):
+ gp,bp=self.get_propvalues(newuri)
+ res=self.mk_prop_response(newuri,gp,bp,doc)
+ ms.appendChild(res)
+ elif self._depth=='infinity':
+ uri_list = [self._uri]
+ while uri_list:
+ uri = uri_list.pop()
+ if uri in self._dataclass.get_childs(get_parenturi(uri),
+ self.filter):
+ gp,bp=self.get_propvalues(uri)
+ res=self.mk_prop_response(uri,gp,bp,doc)
+ ms.appendChild(res)
+ uri_childs = self._dataclass.get_childs(uri)
+ if uri_childs:
+ uri_list.extend(uri_childs)
+
+ return doc.toxml(encoding="utf-8")
+
+
diff --git a/DAV/utils.py b/DAV/utils.py
index 8150e5b..2aa1961 100755
--- a/DAV/utils.py
+++ b/DAV/utils.py
@@ -10,7 +10,7 @@ from StringIO import StringIO
from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP
from BaseHTTPServer import BaseHTTPRequestHandler
-VERSION = '0.9.2'
+VERSION = '0.9.3'
AUTHOR = 'Simon Pamies <s.pamies at banality.de>'
def gen_estring(ecode):
@@ -38,17 +38,17 @@ def parse_propfind(xml_doc):
request_type = RT_PROPNAME
else:
request_type = RT_PROP
- e = doc.getElementsByTagNameNS("DAV:", "prop")
- for e in e[0].childNodes:
- if e.nodeType != minidom.Node.ELEMENT_NODE:
- continue
- ns = e.namespaceURI
- ename = e.localName
- if props.has_key(ns):
- props[ns].append(ename)
- else:
- props[ns]=[ename]
- namespaces.append(ns)
+ for i in doc.getElementsByTagNameNS("DAV:", "prop"):
+ for e in i.childNodes:
+ if e.nodeType != minidom.Node.ELEMENT_NODE:
+ continue
+ ns = e.namespaceURI
+ ename = e.localName
+ if props.has_key(ns):
+ props[ns].append(ename)
+ else:
+ props[ns]=[ename]
+ namespaces.append(ns)
return request_type,props,namespaces
diff --git a/DAVServer/config.ini b/DAVServer/config.ini
index 47b0d60..f9740b3 100644
--- a/DAVServer/config.ini
+++ b/DAVServer/config.ini
@@ -48,3 +48,9 @@ daemonaction = start
# instance counter
counter = 0
+
+# mimetypes support
+mimecheck = 1
+
+# webdav level (1 = webdav level 2)
+lockemulation = 1
diff --git a/DAVServer/fshandler.py b/DAVServer/fshandler.py
index df6adaa..8d464fb 100644
--- a/DAVServer/fshandler.py
+++ b/DAVServer/fshandler.py
@@ -11,6 +11,14 @@ from DAV.iface import *
from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree
+# include magic support to correctly determine mimetypes
+MAGIC_AVAILABLE = False
+try:
+ import magic
+ MAGIC_AVAILABLE = True
+except ImportError:
+ pass
+
class FilesystemHandler(dav_interface):
"""
Model a filesystem for DAV
@@ -56,13 +64,14 @@ class FilesystemHandler(dav_interface):
uparts=urlparse.urlparse(uri)
fileloc=uparts[2][1:]
filename=os.path.join(self.directory,fileloc)
+ filename=os.path.normpath(filename)
return filename
def local2uri(self,filename):
""" map local filename to self.baseuri """
- pnum=len(split(self.directory,"/"))
- parts=split(filename,"/")[pnum:]
+ pnum=len(split(self.directory.replace("\\","/"),"/"))
+ parts=split(filename.replace("\\","/"),"/")[pnum:]
sparts="/"+joinfields(parts,"/")
uri=urlparse.urljoin(self.baseuri,sparts)
return uri
@@ -122,7 +131,6 @@ class FilesystemHandler(dav_interface):
raise DAV_NotFound
def _get_dav_displayname(self,uri):
- #return uri
raise DAV_Secret # do not show
def _get_dav_getcontentlength(self,uri):
@@ -161,7 +169,22 @@ class FilesystemHandler(dav_interface):
path=self.uri2local(uri)
if os.path.exists(path):
if os.path.isfile(path):
- return "application/octet-stream"
+ if MAGIC_AVAILABLE is False \
+ or self.mimecheck is False:
+ return 'application/octet-stream'
+ else:
+ ret = magic.file(path)
+
+ # for non mimetype related result we
+ # simply return an appropriate type
+ if ret.find('/')==-1:
+ if ret.find('text')>=0:
+ return 'text/plain'
+ else:
+ return 'application/octet-stream'
+ else:
+ return ret
+
elif os.path.isdir(path):
return "httpd/unix-directory"
diff --git a/DAVServer/magic.py b/DAVServer/magic.py
new file mode 100644
index 0000000..b95951b
--- /dev/null
+++ b/DAVServer/magic.py
@@ -0,0 +1,1118 @@
+#!/usr/bin/env python
+'''
+magic.py
+ determines a file type by its magic number
+
+ (C)opyright 2000 Jason Petrone <jp_py at jsnp.net>
+ All Rights Reserved
+
+ Command Line Usage: running as `python magic.py file` will print
+ a description of what 'file' is.
+
+ Module Usage:
+ magic.whatis(data): when passed a string 'data' containing
+ binary or text data, a description of
+ what the data is will be returned.
+
+ magic.file(filename): returns a description of what the file
+ 'filename' contains.
+'''
+
+import re, struct, string
+
+__version__ = '0.1'
+
+magic = [
+ [0L, 'leshort', '=', 1538L, 'application/x-alan-adventure-game'],
+ [0L, 'string', '=', 'TADS', 'application/x-tads-game'],
+ [0L, 'short', '=', 420L, 'application/x-executable-file'],
+ [0L, 'short', '=', 421L, 'application/x-executable-file'],
+ [0L, 'leshort', '=', 603L, 'application/x-executable-file'],
+ [0L, 'string', '=', 'Core\001', 'application/x-executable-file'],
+ [0L, 'string', '=', 'AMANDA: TAPESTART DATE', 'application/x-amanda-header'],
+ [0L, 'belong', '=', 1011L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 999L, 'application/x-library-file'],
+ [0L, 'belong', '=', 435L, 'video/mpeg'],
+ [0L, 'belong', '=', 442L, 'video/mpeg'],
+ [0L, 'beshort&0xfff0', '=', 65520L, 'audio/mpeg'],
+ [4L, 'leshort', '=', 44817L, 'video/fli'],
+ [4L, 'leshort', '=', 44818L, 'video/flc'],
+ [0L, 'string', '=', 'MOVI', 'video/x-sgi-movie'],
+ [4L, 'string', '=', 'moov', 'video/quicktime'],
+ [4L, 'string', '=', 'mdat', 'video/quicktime'],
+ [0L, 'long', '=', 100554L, 'application/x-apl-workspace'],
+ [0L, 'string', '=', 'FiLeStArTfIlEsTaRt', 'text/x-apple-binscii'],
+ [0L, 'string', '=', '\012GL', 'application/data'],
+ [0L, 'string', '=', 'v\377', 'application/data'],
+ [0L, 'string', '=', 'NuFile', 'application/data'],
+ [0L, 'string', '=', 'N\365F\351l\345', 'application/data'],
+ [0L, 'belong', '=', 333312L, 'application/data'],
+ [0L, 'belong', '=', 333319L, 'application/data'],
+ [257L, 'string', '=', 'ustar\000', 'application/x-tar'],
+ [257L, 'string', '=', 'ustar \000', 'application/x-gtar'],
+ [0L, 'short', '=', 70707L, 'application/x-cpio'],
+ [0L, 'short', '=', 143561L, 'application/x-bcpio'],
+ [0L, 'string', '=', '070707', 'application/x-cpio'],
+ [0L, 'string', '=', '070701', 'application/x-cpio'],
+ [0L, 'string', '=', '070702', 'application/x-cpio'],
+ [0L, 'string', '=', '!<arch>\012debian', 'application/x-dpkg'],
+ [0L, 'long', '=', 177555L, 'application/x-ar'],
+ [0L, 'short', '=', 177555L, 'application/data'],
+ [0L, 'long', '=', 177545L, 'application/data'],
+ [0L, 'short', '=', 177545L, 'application/data'],
+ [0L, 'long', '=', 100554L, 'application/x-apl-workspace'],
+ [0L, 'string', '=', '<ar>', 'application/x-ar'],
+ [0L, 'string', '=', '!<arch>\012__________E', 'application/x-ar'],
+ [0L, 'string', '=', '-h-', 'application/data'],
+ [0L, 'string', '=', '!<arch>', 'application/x-ar'],
+ [0L, 'string', '=', '<ar>', 'application/x-ar'],
+ [0L, 'string', '=', '<ar>', 'application/x-ar'],
+ [0L, 'belong', '=', 1711210496L, 'application/x-ar'],
+ [0L, 'belong', '=', 1013019198L, 'application/x-ar'],
+ [0L, 'long', '=', 557605234L, 'application/x-ar'],
+ [0L, 'lelong', '=', 177555L, 'application/data'],
+ [0L, 'leshort', '=', 177555L, 'application/data'],
+ [0L, 'lelong', '=', 177545L, 'application/data'],
+ [0L, 'leshort', '=', 177545L, 'application/data'],
+ [0L, 'lelong', '=', 236525L, 'application/data'],
+ [0L, 'lelong', '=', 236526L, 'application/data'],
+ [0L, 'lelong&0x8080ffff', '=', 2074L, 'application/x-arc'],
+ [0L, 'lelong&0x8080ffff', '=', 2330L, 'application/x-arc'],
+ [0L, 'lelong&0x8080ffff', '=', 538L, 'application/x-arc'],
+ [0L, 'lelong&0x8080ffff', '=', 794L, 'application/x-arc'],
+ [0L, 'lelong&0x8080ffff', '=', 1050L, 'application/x-arc'],
+ [0L, 'lelong&0x8080ffff', '=', 1562L, 'application/x-arc'],
+ [0L, 'string', '=', '\032archive', 'application/data'],
+ [0L, 'leshort', '=', 60000L, 'application/x-arj'],
+ [0L, 'string', '=', 'HPAK', 'application/data'],
+ [0L, 'string', '=', '\351,\001JAM application/data', ''],
+ [2L, 'string', '=', '-lh0-', 'application/x-lha'],
+ [2L, 'string', '=', '-lh1-', 'application/x-lha'],
+ [2L, 'string', '=', '-lz4-', 'application/x-lha'],
+ [2L, 'string', '=', '-lz5-', 'application/x-lha'],
+ [2L, 'string', '=', '-lzs-', 'application/x-lha'],
+ [2L, 'string', '=', '-lh -', 'application/x-lha'],
+ [2L, 'string', '=', '-lhd-', 'application/x-lha'],
+ [2L, 'string', '=', '-lh2-', 'application/x-lha'],
+ [2L, 'string', '=', '-lh3-', 'application/x-lha'],
+ [2L, 'string', '=', '-lh4-', 'application/x-lha'],
+ [2L, 'string', '=', '-lh5-', 'application/x-lha'],
+ [0L, 'string', '=', 'Rar!', 'application/x-rar'],
+ [0L, 'string', '=', 'SQSH', 'application/data'],
+ [0L, 'string', '=', 'UC2\032', 'application/data'],
+ [0L, 'string', '=', 'PK\003\004', 'application/zip'],
+ [20L, 'lelong', '=', 4257523676L, 'application/x-zoo'],
+ [10L, 'string', '=', '# This is a shell archive', 'application/x-shar'],
+ [0L, 'string', '=', '*STA', 'application/data'],
+ [0L, 'string', '=', '2278', 'application/data'],
+ [0L, 'beshort', '=', 560L, 'application/x-executable-file'],
+ [0L, 'beshort', '=', 561L, 'application/x-executable-file'],
+ [0L, 'string', '=', '\000\004\036\212\200', 'application/core'],
+ [0L, 'string', '=', '.snd', 'audio/basic'],
+ [0L, 'lelong', '=', 6583086L, 'audio/basic'],
+ [0L, 'string', '=', 'MThd', 'audio/midi'],
+ [0L, 'string', '=', 'CTMF', 'audio/x-cmf'],
+ [0L, 'string', '=', 'SBI', 'audio/x-sbi'],
+ [0L, 'string', '=', 'Creative Voice File', 'audio/x-voc'],
+ [0L, 'belong', '=', 1314148939L, 'audio/x-multitrack'],
+ [0L, 'string', '=', 'RIFF', 'audio/x-wav'],
+ [0L, 'string', '=', 'EMOD', 'audio/x-emod'],
+ [0L, 'belong', '=', 779248125L, 'audio/x-pn-realaudio'],
+ [0L, 'string', '=', 'MTM', 'audio/x-multitrack'],
+ [0L, 'string', '=', 'if', 'audio/x-669-mod'],
+ [0L, 'string', '=', 'FAR', 'audio/mod'],
+ [0L, 'string', '=', 'MAS_U', 'audio/x-multimate-mod'],
+ [44L, 'string', '=', 'SCRM', 'audio/x-st3-mod'],
+ [0L, 'string', '=', 'GF1PATCH110\000ID#000002\000', 'audio/x-gus-patch'],
+ [0L, 'string', '=', 'GF1PATCH100\000ID#000002\000', 'audio/x-gus-patch'],
+ [0L, 'string', '=', 'JN', 'audio/x-669-mod'],
+ [0L, 'string', '=', 'UN05', 'audio/x-mikmod-uni'],
+ [0L, 'string', '=', 'Extended Module:', 'audio/x-ft2-mod'],
+ [21L, 'string', '=', '!SCREAM!', 'audio/x-st2-mod'],
+ [1080L, 'string', '=', 'M.K.', 'audio/x-protracker-mod'],
+ [1080L, 'string', '=', 'M!K!', 'audio/x-protracker-mod'],
+ [1080L, 'string', '=', 'FLT4', 'audio/x-startracker-mod'],
+ [1080L, 'string', '=', '4CHN', 'audio/x-fasttracker-mod'],
+ [1080L, 'string', '=', '6CHN', 'audio/x-fasttracker-mod'],
+ [1080L, 'string', '=', '8CHN', 'audio/x-fasttracker-mod'],
+ [1080L, 'string', '=', 'CD81', 'audio/x-oktalyzer-mod'],
+ [1080L, 'string', '=', 'OKTA', 'audio/x-oktalyzer-mod'],
+ [1080L, 'string', '=', '16CN', 'audio/x-taketracker-mod'],
+ [1080L, 'string', '=', '32CN', 'audio/x-taketracker-mod'],
+ [0L, 'string', '=', 'TOC', 'audio/x-toc'],
+ [0L, 'short', '=', 3401L, 'application/x-executable-file'],
+ [0L, 'long', '=', 406L, 'application/x-executable-file'],
+ [0L, 'short', '=', 406L, 'application/x-executable-file'],
+ [0L, 'short', '=', 3001L, 'application/x-executable-file'],
+ [0L, 'lelong', '=', 314L, 'application/x-executable-file'],
+ [0L, 'string', '=', '//', 'text/cpp'],
+ [0L, 'string', '=', '\\\\1cw\\', 'application/data'],
+ [0L, 'string', '=', '\\\\1cw', 'application/data'],
+ [0L, 'belong&0xffffff00', '=', 2231440384L, 'application/data'],
+ [0L, 'belong&0xffffff00', '=', 2231487232L, 'application/data'],
+ [0L, 'short', '=', 575L, 'application/x-executable-file'],
+ [0L, 'short', '=', 577L, 'application/x-executable-file'],
+ [4L, 'string', '=', 'pipe', 'application/data'],
+ [4L, 'string', '=', 'prof', 'application/data'],
+ [0L, 'string', '=', ': shell', 'application/data'],
+ [0L, 'string', '=', '#!/bin/sh', 'application/x-sh'],
+ [0L, 'string', '=', '#! /bin/sh', 'application/x-sh'],
+ [0L, 'string', '=', '#! /bin/sh', 'application/x-sh'],
+ [0L, 'string', '=', '#!/bin/csh', 'application/x-csh'],
+ [0L, 'string', '=', '#! /bin/csh', 'application/x-csh'],
+ [0L, 'string', '=', '#! /bin/csh', 'application/x-csh'],
+ [0L, 'string', '=', '#!/bin/ksh', 'application/x-ksh'],
+ [0L, 'string', '=', '#! /bin/ksh', 'application/x-ksh'],
+ [0L, 'string', '=', '#! /bin/ksh', 'application/x-ksh'],
+ [0L, 'string', '=', '#!/bin/tcsh', 'application/x-csh'],
+ [0L, 'string', '=', '#! /bin/tcsh', 'application/x-csh'],
+ [0L, 'string', '=', '#! /bin/tcsh', 'application/x-csh'],
+ [0L, 'string', '=', '#!/usr/local/tcsh', 'application/x-csh'],
+ [0L, 'string', '=', '#! /usr/local/tcsh', 'application/x-csh'],
+ [0L, 'string', '=', '#!/usr/local/bin/tcsh', 'application/x-csh'],
+ [0L, 'string', '=', '#! /usr/local/bin/tcsh', 'application/x-csh'],
+ [0L, 'string', '=', '#! /usr/local/bin/tcsh', 'application/x-csh'],
+ [0L, 'string', '=', '#!/usr/local/bin/zsh', 'application/x-zsh'],
+ [0L, 'string', '=', '#! /usr/local/bin/zsh', 'application/x-zsh'],
+ [0L, 'string', '=', '#! /usr/local/bin/zsh', 'application/x-zsh'],
+ [0L, 'string', '=', '#!/usr/local/bin/ash', 'application/x-sh'],
+ [0L, 'string', '=', '#! /usr/local/bin/ash', 'application/x-zsh'],
+ [0L, 'string', '=', '#! /usr/local/bin/ash', 'application/x-zsh'],
+ [0L, 'string', '=', '#!/usr/local/bin/ae', 'text/script'],
+ [0L, 'string', '=', '#! /usr/local/bin/ae', 'text/script'],
+ [0L, 'string', '=', '#! /usr/local/bin/ae', 'text/script'],
+ [0L, 'string', '=', '#!/bin/nawk', 'application/x-awk'],
+ [0L, 'string', '=', '#! /bin/nawk', 'application/x-awk'],
+ [0L, 'string', '=', '#! /bin/nawk', 'application/x-awk'],
+ [0L, 'string', '=', '#!/usr/bin/nawk', 'application/x-awk'],
+ [0L, 'string', '=', '#! /usr/bin/nawk', 'application/x-awk'],
+ [0L, 'string', '=', '#! /usr/bin/nawk', 'application/x-awk'],
+ [0L, 'string', '=', '#!/usr/local/bin/nawk', 'application/x-awk'],
+ [0L, 'string', '=', '#! /usr/local/bin/nawk', 'application/x-awk'],
+ [0L, 'string', '=', '#! /usr/local/bin/nawk', 'application/x-awk'],
+ [0L, 'string', '=', '#!/bin/gawk', 'application/x-awk'],
+ [0L, 'string', '=', '#! /bin/gawk', 'application/x-awk'],
+ [0L, 'string', '=', '#! /bin/gawk', 'application/x-awk'],
+ [0L, 'string', '=', '#!/usr/bin/gawk', 'application/x-awk'],
+ [0L, 'string', '=', '#! /usr/bin/gawk', 'application/x-awk'],
+ [0L, 'string', '=', '#! /usr/bin/gawk', 'application/x-awk'],
+ [0L, 'string', '=', '#!/usr/local/bin/gawk', 'application/x-awk'],
+ [0L, 'string', '=', '#! /usr/local/bin/gawk', 'application/x-awk'],
+ [0L, 'string', '=', '#! /usr/local/bin/gawk', 'application/x-awk'],
+ [0L, 'string', '=', '#!/bin/awk', 'application/x-awk'],
+ [0L, 'string', '=', '#! /bin/awk', 'application/x-awk'],
+ [0L, 'string', '=', '#! /bin/awk', 'application/x-awk'],
+ [0L, 'string', '=', '#!/usr/bin/awk', 'application/x-awk'],
+ [0L, 'string', '=', '#! /usr/bin/awk', 'application/x-awk'],
+ [0L, 'string', '=', '#! /usr/bin/awk', 'application/x-awk'],
+ [0L, 'string', '=', 'BEGIN', 'application/x-awk'],
+ [0L, 'string', '=', '#!/bin/perl', 'application/x-perl'],
+ [0L, 'string', '=', '#! /bin/perl', 'application/x-perl'],
+ [0L, 'string', '=', '#! /bin/perl', 'application/x-perl'],
+ [0L, 'string', '=', 'eval "exec /bin/perl', 'application/x-perl'],
+ [0L, 'string', '=', '#!/usr/bin/perl', 'application/x-perl'],
+ [0L, 'string', '=', '#! /usr/bin/perl', 'application/x-perl'],
+ [0L, 'string', '=', '#! /usr/bin/perl', 'application/x-perl'],
+ [0L, 'string', '=', 'eval "exec /usr/bin/perl', 'application/x-perl'],
+ [0L, 'string', '=', '#!/usr/local/bin/perl', 'application/x-perl'],
+ [0L, 'string', '=', '#! /usr/local/bin/perl', 'application/x-perl'],
+ [0L, 'string', '=', '#! /usr/local/bin/perl', 'application/x-perl'],
+ [0L, 'string', '=', 'eval "exec /usr/local/bin/perl', 'application/x-perl'],
+ [0L, 'string', '=', '#!/bin/python', 'application/x-python'],
+ [0L, 'string', '=', '#! /bin/python', 'application/x-python'],
+ [0L, 'string', '=', '#! /bin/python', 'application/x-python'],
+ [0L, 'string', '=', 'eval "exec /bin/python', 'application/x-python'],
+ [0L, 'string', '=', '#!/usr/bin/python', 'application/x-python'],
+ [0L, 'string', '=', '#! /usr/bin/python', 'application/x-python'],
+ [0L, 'string', '=', '#! /usr/bin/python', 'application/x-python'],
+ [0L, 'string', '=', 'eval "exec /usr/bin/python', 'application/x-python'],
+ [0L, 'string', '=', '#!/usr/local/bin/python', 'application/x-python'],
+ [0L, 'string', '=', '#! /usr/local/bin/python', 'application/x-python'],
+ [0L, 'string', '=', '#! /usr/local/bin/python', 'application/x-python'],
+ [0L, 'string', '=', 'eval "exec /usr/local/bin/python', 'application/x-python'],
+ [0L, 'string', '=', '#!/usr/bin/env python', 'application/x-python'],
+ [0L, 'string', '=', '#! /usr/bin/env python', 'application/x-python'],
+ [0L, 'string', '=', '#!/bin/rc', 'text/script'],
+ [0L, 'string', '=', '#! /bin/rc', 'text/script'],
+ [0L, 'string', '=', '#! /bin/rc', 'text/script'],
+ [0L, 'string', '=', '#!/bin/bash', 'application/x-sh'],
+ [0L, 'string', '=', '#! /bin/bash', 'application/x-sh'],
+ [0L, 'string', '=', '#! /bin/bash', 'application/x-sh'],
+ [0L, 'string', '=', '#!/usr/local/bin/bash', 'application/x-sh'],
+ [0L, 'string', '=', '#! /usr/local/bin/bash', 'application/x-sh'],
+ [0L, 'string', '=', '#! /usr/local/bin/bash', 'application/x-sh'],
+ [0L, 'string', '=', '#! /', 'text/script'],
+ [0L, 'string', '=', '#! /', 'text/script'],
+ [0L, 'string', '=', '#!/', 'text/script'],
+ [0L, 'string', '=', '#! text/script', ''],
+ [0L, 'string', '=', '\037\235', 'application/compress'],
+ [0L, 'string', '=', '\037\213', 'application/x-gzip'],
+ [0L, 'string', '=', '\037\036', 'application/data'],
+ [0L, 'short', '=', 17437L, 'application/data'],
+ [0L, 'short', '=', 8191L, 'application/data'],
+ [0L, 'string', '=', '\377\037', 'application/data'],
+ [0L, 'short', '=', 145405L, 'application/data'],
+ [0L, 'string', '=', 'BZh', 'application/x-bzip2'],
+ [0L, 'leshort', '=', 65398L, 'application/data'],
+ [0L, 'leshort', '=', 65142L, 'application/data'],
+ [0L, 'leshort', '=', 64886L, 'application/x-lzh'],
+ [0L, 'string', '=', '\037\237', 'application/data'],
+ [0L, 'string', '=', '\037\236', 'application/data'],
+ [0L, 'string', '=', '\037\240', 'application/data'],
+ [0L, 'string', '=', 'BZ', 'application/x-bzip'],
+ [0L, 'string', '=', '\211LZO\000\015\012\032\012', 'application/data'],
+ [0L, 'belong', '=', 507L, 'application/x-object-file'],
+ [0L, 'belong', '=', 513L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 515L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 517L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 70231L, 'application/core'],
+ [24L, 'belong', '=', 60011L, 'application/data'],
+ [24L, 'belong', '=', 60012L, 'application/data'],
+ [24L, 'belong', '=', 60013L, 'application/data'],
+ [24L, 'belong', '=', 60014L, 'application/data'],
+ [0L, 'belong', '=', 601L, 'application/x-object-file'],
+ [0L, 'belong', '=', 607L, 'application/data'],
+ [0L, 'belong', '=', 324508366L, 'application/x-gdbm'],
+ [0L, 'lelong', '=', 324508366L, 'application/x-gdbm'],
+ [0L, 'string', '=', 'GDBM', 'application/x-gdbm'],
+ [0L, 'belong', '=', 398689L, 'application/x-db'],
+ [0L, 'belong', '=', 340322L, 'application/x-db'],
+ [0L, 'string', '=', '<list>\012<protocol bbn-m', 'application/data'],
+ [0L, 'string', '=', 'diff text/x-patch', ''],
+ [0L, 'string', '=', '*** text/x-patch', ''],
+ [0L, 'string', '=', 'Only in text/x-patch', ''],
+ [0L, 'string', '=', 'Common subdirectories: text/x-patch', ''],
+ [0L, 'string', '=', '!<arch>\012________64E', 'application/data'],
+ [0L, 'leshort', '=', 387L, 'application/x-executable-file'],
+ [0L, 'leshort', '=', 392L, 'application/x-executable-file'],
+ [0L, 'leshort', '=', 399L, 'application/x-object-file'],
+ [0L, 'string', '=', '\377\377\177', 'application/data'],
+ [0L, 'string', '=', '\377\377|', 'application/data'],
+ [0L, 'string', '=', '\377\377~', 'application/data'],
+ [0L, 'string', '=', '\033c\033', 'application/data'],
+ [0L, 'long', '=', 4553207L, 'image/x11'],
+ [0L, 'string', '=', '!<PDF>!\012', 'application/x-prof'],
+ [0L, 'short', '=', 1281L, 'application/x-locale'],
+ [24L, 'belong', '=', 60012L, 'application/x-dump'],
+ [24L, 'belong', '=', 60011L, 'application/x-dump'],
+ [24L, 'lelong', '=', 60012L, 'application/x-dump'],
+ [24L, 'lelong', '=', 60011L, 'application/x-dump'],
+ [0L, 'string', '=', '\177ELF', 'application/x-executable-file'],
+ [0L, 'short', '=', 340L, 'application/data'],
+ [0L, 'short', '=', 341L, 'application/x-executable-file'],
+ [1080L, 'leshort', '=', 61267L, 'application/x-linux-ext2fs'],
+ [0L, 'string', '=', '\366\366\366\366', 'application/x-pc-floppy'],
+ [774L, 'beshort', '=', 55998L, 'application/data'],
+ [510L, 'leshort', '=', 43605L, 'application/data'],
+ [1040L, 'leshort', '=', 4991L, 'application/x-filesystem'],
+ [1040L, 'leshort', '=', 5007L, 'application/x-filesystem'],
+ [1040L, 'leshort', '=', 9320L, 'application/x-filesystem'],
+ [1040L, 'leshort', '=', 9336L, 'application/x-filesystem'],
+ [0L, 'string', '=', '-rom1fs-\000', 'application/x-filesystem'],
+ [395L, 'string', '=', 'OS/2', 'application/x-bootable'],
+ [0L, 'string', '=', 'FONT', 'font/x-vfont'],
+ [0L, 'short', '=', 436L, 'font/x-vfont'],
+ [0L, 'short', '=', 17001L, 'font/x-vfont'],
+ [0L, 'string', '=', '%!PS-AdobeFont-1.0', 'font/type1'],
+ [6L, 'string', '=', '%!PS-AdobeFont-1.0', 'font/type1'],
+ [0L, 'belong', '=', 4L, 'font/x-snf'],
+ [0L, 'lelong', '=', 4L, 'font/x-snf'],
+ [0L, 'string', '=', 'STARTFONT font/x-bdf', ''],
+ [0L, 'string', '=', '\001fcp', 'font/x-pcf'],
+ [0L, 'string', '=', 'D1.0\015', 'font/x-speedo'],
+ [0L, 'string', '=', 'flf', 'font/x-figlet'],
+ [0L, 'string', '=', 'flc', 'application/x-font'],
+ [0L, 'belong', '=', 335698201L, 'font/x-libgrx'],
+ [0L, 'belong', '=', 4282797902L, 'font/x-dos'],
+ [7L, 'belong', '=', 4540225L, 'font/x-dos'],
+ [7L, 'belong', '=', 5654852L, 'font/x-dos'],
+ [4098L, 'string', '=', 'DOSFONT', 'font/x-dos'],
+ [0L, 'string', '=', '<MakerFile', 'application/x-framemaker'],
+ [0L, 'string', '=', '<MIFFile', 'application/x-framemaker'],
+ [0L, 'string', '=', '<MakerDictionary', 'application/x-framemaker'],
+ [0L, 'string', '=', '<MakerScreenFont', 'font/x-framemaker'],
+ [0L, 'string', '=', '<MML', 'application/x-framemaker'],
+ [0L, 'string', '=', '<BookFile', 'application/x-framemaker'],
+ [0L, 'string', '=', '<Maker', 'application/x-framemaker'],
+ [0L, 'lelong&0377777777', '=', 41400407L, 'application/x-executable-file'],
+ [0L, 'lelong&0377777777', '=', 41400410L, 'application/x-executable-file'],
+ [0L, 'lelong&0377777777', '=', 41400413L, 'application/x-executable-file'],
+ [0L, 'lelong&0377777777', '=', 41400314L, 'application/x-executable-file'],
+ [7L, 'string', '=', '\357\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000', 'application/core'],
+ [0L, 'lelong', '=', 11421044151L, 'application/data'],
+ [0L, 'string', '=', 'GIMP Gradient', 'application/x-gimp-gradient'],
+ [0L, 'string', '=', 'gimp xcf', 'application/x-gimp-image'],
+ [20L, 'string', '=', 'GPAT', 'application/x-gimp-pattern'],
+ [20L, 'string', '=', 'GIMP', 'application/x-gimp-brush'],
+ [0L, 'string', '=', '\336\022\004\225', 'application/x-locale'],
+ [0L, 'string', '=', '\225\004\022\336', 'application/x-locale'],
+ [0L, 'beshort', '=', 627L, 'application/x-executable-file'],
+ [0L, 'beshort', '=', 624L, 'application/x-executable-file'],
+ [0L, 'string', '=', '\000\001\000\000\000', 'font/ttf'],
+ [0L, 'long', '=', 1203604016L, 'application/data'],
+ [0L, 'long', '=', 1702407010L, 'application/data'],
+ [0L, 'long', '=', 1003405017L, 'application/data'],
+ [0L, 'long', '=', 1602007412L, 'application/data'],
+ [0L, 'belong', '=', 34603270L, 'application/x-object-file'],
+ [0L, 'belong', '=', 34603271L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 34603272L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 34603275L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 34603278L, 'application/x-library-file'],
+ [0L, 'belong', '=', 34603277L, 'application/x-library-file'],
+ [0L, 'belong', '=', 34865414L, 'application/x-object-file'],
+ [0L, 'belong', '=', 34865415L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 34865416L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 34865419L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 34865422L, 'application/x-library-file'],
+ [0L, 'belong', '=', 34865421L, 'application/x-object-file'],
+ [0L, 'belong', '=', 34275590L, 'application/x-object-file'],
+ [0L, 'belong', '=', 34275591L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 34275592L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 34275595L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 34275598L, 'application/x-library-file'],
+ [0L, 'belong', '=', 34275597L, 'application/x-library-file'],
+ [0L, 'belong', '=', 557605234L, 'application/x-ar'],
+ [0L, 'long', '=', 34078982L, 'application/x-executable-file'],
+ [0L, 'long', '=', 34078983L, 'application/x-executable-file'],
+ [0L, 'long', '=', 34078984L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 34341128L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 34341127L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 34341131L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 34341126L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 34210056L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 34210055L, 'application/x-executable-file'],
+ [0L, 'belong', '=', 34341134L, 'application/x-library-file'],
+ [0L, 'belong', '=', 34341133L, 'application/x-library-file'],
+ [0L, 'long', '=', 65381L, 'application/x-library-file'],
+ [0L, 'long', '=', 34275173L, 'application/x-library-file'],
+ [0L, 'long', '=', 34406245L, 'application/x-library-file'],
+ [0L, 'long', '=', 34144101L, 'application/x-library-file'],
+ [0L, 'long', '=', 22552998L, 'application/core'],
+ [0L, 'long', '=', 1302851304L, 'font/x-hp-windows'],
+ [0L, 'string', '=', 'Bitmapfile', 'image/unknown'],
+ [0L, 'string', '=', 'IMGfile', 'CIS image/unknown'],
+ [0L, 'long', '=', 34341132L, 'application/x-lisp'],
+ [0L, 'string', '=', 'msgcat01', 'application/x-locale'],
+ [0L, 'string', '=', 'HPHP48-', 'HP48 binary'],
+ [0L, 'string', '=', '%%HP:', 'HP48 text'],
+ [0L, 'beshort', '=', 200L, 'hp200 (68010) BSD'],
+ [0L, 'beshort', '=', 300L, 'hp300 (68020+68881) BSD'],
+ [0L, 'beshort', '=', 537L, '370 XA sysV executable'],
+ [0L, 'beshort', '=', 532L, '370 XA sysV pure executable'],
+ [0L, 'beshort', '=', 54001L, '370 sysV pure executable'],
+ [0L, 'beshort', '=', 55001L, '370 XA sysV pure executable'],
+ [0L, 'beshort', '=', 56401L, '370 sysV executable'],
+ [0L, 'beshort', '=', 57401L, '370 XA sysV executable'],
+ [0L, 'beshort', '=', 531L, 'SVR2 executable (Amdahl-UTS)'],
+ [0L, 'beshort', '=', 534L, 'SVR2 pure executable (Amdahl-UTS)'],
+ [0L, 'beshort', '=', 530L, 'SVR2 pure executable (USS/370)'],
+ [0L, 'beshort', '=', 535L, 'SVR2 executable (USS/370)'],
+ [0L, 'beshort', '=', 479L, 'executable (RISC System/6000 V3.1) or obj module'],
+ [0L, 'beshort', '=', 260L, 'shared library'],
+ [0L, 'beshort', '=', 261L, 'ctab data'],
+ [0L, 'beshort', '=', 65028L, 'structured file'],
+ [0L, 'string', '=', '0xabcdef', 'AIX message catalog'],
+ [0L, 'belong', '=', 505L, 'AIX compiled message catalog'],
+ [0L, 'string', '=', '<aiaff>', 'archive'],
+ [0L, 'string', '=', 'FORM', 'IFF data'],
+ [0L, 'string', '=', 'P1', 'image/x-portable-bitmap'],
+ [0L, 'string', '=', 'P2', 'image/x-portable-graymap'],
+ [0L, 'string', '=', 'P3', 'image/x-portable-pixmap'],
+ [0L, 'string', '=', 'P4', 'image/x-portable-bitmap'],
+ [0L, 'string', '=', 'P5', 'image/x-portable-graymap'],
+ [0L, 'string', '=', 'P6', 'image/x-portable-pixmap'],
+ [0L, 'string', '=', 'IIN1', 'image/tiff'],
+ [0L, 'string', '=', 'MM\000*', 'image/tiff'],
+ [0L, 'string', '=', 'II*\000', 'image/tiff'],
+ [0L, 'string', '=', '\211PNG', 'image/x-png'],
+ [1L, 'string', '=', 'PNG', 'image/x-png'],
+ [0L, 'string', '=', 'GIF8', 'image/gif'],
+ [0L, 'string', '=', '\361\000@\273', 'image/x-cmu-raster'],
+ [0L, 'string', '=', 'id=ImageMagick', 'MIFF image data'],
+ [0L, 'long', '=', 1123028772L, 'Artisan image data'],
+ [0L, 'string', '=', '#FIG', 'FIG image text'],
+ [0L, 'string', '=', 'ARF_BEGARF', 'PHIGS clear text archive'],
+ [0L, 'string', '=', '@(#)SunPHIGS', 'SunPHIGS'],
+ [0L, 'string', '=', 'GKSM', 'GKS Metafile'],
+ [0L, 'string', '=', 'BEGMF', 'clear text Computer Graphics Metafile'],
+ [0L, 'beshort&0xffe0', '=', 32L, 'binary Computer Graphics Metafile'],
+ [0L, 'beshort', '=', 12320L, 'character Computer Graphics Metafile'],
+ [0L, 'string', '=', 'yz', 'MGR bitmap, modern format, 8-bit aligned'],
+ [0L, 'string', '=', 'zz', 'MGR bitmap, old format, 1-bit deep, 16-bit aligned'],
+ [0L, 'string', '=', 'xz', 'MGR bitmap, old format, 1-bit deep, 32-bit aligned'],
+ [0L, 'string', '=', 'yx', 'MGR bitmap, modern format, squeezed'],
+ [0L, 'string', '=', '%bitmap\000', 'FBM image data'],
+ [1L, 'string', '=', 'PC Research, Inc', 'group 3 fax data'],
+ [0L, 'beshort', '=', 65496L, 'image/jpeg'],
+ [0L, 'string', '=', 'hsi1', 'image/x-jpeg-proprietary'],
+ [0L, 'string', '=', 'BM', 'image/x-bmp'],
+ [0L, 'string', '=', 'IC', 'image/x-ico'],
+ [0L, 'string', '=', 'PI', 'PC pointer image data'],
+ [0L, 'string', '=', 'CI', 'PC color icon data'],
+ [0L, 'string', '=', 'CP', 'PC color pointer image data'],
+ [0L, 'string', '=', '/* XPM */', 'X pixmap image text'],
+ [0L, 'leshort', '=', 52306L, 'RLE image data,'],
+ [0L, 'string', '=', 'Imagefile version-', 'iff image data'],
+ [0L, 'belong', '=', 1504078485L, 'x/x-image-sun-raster'],
+ [0L, 'beshort', '=', 474L, 'x/x-image-sgi'],
+ [0L, 'string', '=', 'IT01', 'FIT image data'],
+ [0L, 'string', '=', 'IT02', 'FIT image data'],
+ [2048L, 'string', '=', 'PCD_IPI', 'x/x-photo-cd-pack-file'],
+ [0L, 'string', '=', 'PCD_OPA', 'x/x-photo-cd-overfiew-file'],
+ [0L, 'string', '=', 'SIMPLE =', 'FITS image data'],
+ [0L, 'string', '=', 'This is a BitMap file', 'Lisp Machine bit-array-file'],
+ [0L, 'string', '=', '!!', 'Bennet Yee\'s "face" format'],
+ [0L, 'beshort', '=', 4112L, 'PEX Binary Archive'],
+ [3000L, 'string', '=', 'Visio (TM) Drawing', '%s'],
+ [0L, 'leshort', '=', 502L, 'basic-16 executable'],
+ [0L, 'leshort', '=', 503L, 'basic-16 executable (TV)'],
+ [0L, 'leshort', '=', 510L, 'application/x-executable-file'],
+ [0L, 'leshort', '=', 511L, 'application/x-executable-file'],
+ [0L, 'leshort', '=', 512L, 'application/x-executable-file'],
+ [0L, 'leshort', '=', 522L, 'application/x-executable-file'],
+ [0L, 'leshort', '=', 514L, 'application/x-executable-file'],
+ [0L, 'string', '=', '\210OPS', 'Interleaf saved data'],
+ [0L, 'string', '=', '<!OPS', 'Interleaf document text'],
+ [4L, 'string', '=', 'pgscriptver', 'IslandWrite document'],
+ [13L, 'string', '=', 'DrawFile', 'IslandDraw document'],
+ [0L, 'leshort&0xFFFC', '=', 38400L, 'little endian ispell'],
+ [0L, 'beshort&0xFFFC', '=', 38400L, 'big endian ispell'],
+ [0L, 'belong', '=', 3405691582L, 'compiled Java class data,'],
+ [0L, 'beshort', '=', 44269L, 'Java serialization data'],
+ [0L, 'string', '=', 'KarmaRHD', 'Version Karma Data Structure Version'],
+ [0L, 'string', '=', 'lect', 'DEC SRC Virtual Paper Lectern file'],
+ [53L, 'string', '=', 'yyprevious', 'C program text (from lex)'],
+ [21L, 'string', '=', 'generated by flex', 'C program text (from flex)'],
+ [0L, 'string', '=', '%{', 'lex description text'],
+ [0L, 'short', '=', 32768L, 'lif file'],
+ [0L, 'lelong', '=', 6553863L, 'Linux/i386 impure executable (OMAGIC)'],
+ [0L, 'lelong', '=', 6553864L, 'Linux/i386 pure executable (NMAGIC)'],
+ [0L, 'lelong', '=', 6553867L, 'Linux/i386 demand-paged executable (ZMAGIC)'],
+ [0L, 'lelong', '=', 6553804L, 'Linux/i386 demand-paged executable (QMAGIC)'],
+ [0L, 'string', '=', '\007\001\000', 'Linux/i386 object file'],
+ [0L, 'string', '=', '\001\003\020\004', 'Linux-8086 impure executable'],
+ [0L, 'string', '=', '\001\003 \004', 'Linux-8086 executable'],
+ [0L, 'string', '=', '\243\206\001\000', 'Linux-8086 object file'],
+ [0L, 'string', '=', '\001\003\020\020', 'Minix-386 impure executable'],
+ [0L, 'string', '=', '\001\003 \020', 'Minix-386 executable'],
+ [0L, 'string', '=', '*nazgul*', 'Linux compiled message catalog'],
+ [216L, 'lelong', '=', 421L, 'Linux/i386 core file'],
+ [2L, 'string', '=', 'LILO', 'Linux/i386 LILO boot/chain loader'],
+ [0L, 'string', '=', '0.9', ''],
+ [0L, 'leshort', '=', 1078L, 'font/linux-psf'],
+ [4086L, 'string', '=', 'SWAP-SPACE', 'Linux/i386 swap file'],
+ [0L, 'leshort', '=', 387L, 'ECOFF alpha'],
+ [514L, 'string', '=', 'HdrS', 'Linux kernel'],
+ [0L, 'belong', '=', 3099592590L, 'Linux kernel'],
+ [0L, 'string', '=', 'Begin3', 'Linux Software Map entry text'],
+ [0L, 'string', '=', ';;', 'Lisp/Scheme program text'],
+ [0L, 'string', '=', '\012(', 'byte-compiled Emacs-Lisp program data'],
+ [0L, 'string', '=', ';ELC\023\000\000\000', 'byte-compiled Emacs-Lisp program data'],
+ [0L, 'string', '=', "(SYSTEM::VERSION '", 'CLISP byte-compiled Lisp program text'],
+ [0L, 'long', '=', 1886817234L, 'CLISP memory image data'],
+ [0L, 'long', '=', 3532355184L, 'CLISP memory image data, other endian'],
+ [0L, 'long', '=', 3725722773L, 'GNU-format message catalog data'],
+ [0L, 'long', '=', 2500072158L, 'GNU-format message catalog data'],
+ [0L, 'belong', '=', 3405691582L, 'mach-o fat file'],
+ [0L, 'belong', '=', 4277009102L, 'mach-o'],
+ [11L, 'string', '=', 'must be converted with BinHex', 'BinHex binary text'],
+ [0L, 'string', '=', 'SIT!', 'StuffIt Archive (data)'],
+ [65L, 'string', '=', 'SIT!', 'StuffIt Archive (rsrc + data)'],
+ [0L, 'string', '=', 'SITD', 'StuffIt Deluxe (data)'],
+ [65L, 'string', '=', 'SITD', 'StuffIt Deluxe (rsrc + data)'],
+ [0L, 'string', '=', 'Seg', 'StuffIt Deluxe Segment (data)'],
+ [65L, 'string', '=', 'Seg', 'StuffIt Deluxe Segment (rsrc + data)'],
+ [0L, 'string', '=', 'APPL', 'Macintosh Application (data)'],
+ [65L, 'string', '=', 'APPL', 'Macintosh Application (rsrc + data)'],
+ [0L, 'string', '=', 'zsys', 'Macintosh System File (data)'],
+ [65L, 'string', '=', 'zsys', 'Macintosh System File(rsrc + data)'],
+ [0L, 'string', '=', 'FNDR', 'Macintosh Finder (data)'],
+ [65L, 'string', '=', 'FNDR', 'Macintosh Finder(rsrc + data)'],
+ [0L, 'string', '=', 'libr', 'Macintosh Library (data)'],
+ [65L, 'string', '=', 'libr', 'Macintosh Library(rsrc + data)'],
+ [0L, 'string', '=', 'shlb', 'Macintosh Shared Library (data)'],
+ [65L, 'string', '=', 'shlb', 'Macintosh Shared Library(rsrc + data)'],
+ [0L, 'string', '=', 'cdev', 'Macintosh Control Panel (data)'],
+ [65L, 'string', '=', 'cdev', 'Macintosh Control Panel(rsrc + data)'],
+ [0L, 'string', '=', 'INIT', 'Macintosh Extension (data)'],
+ [65L, 'string', '=', 'INIT', 'Macintosh Extension(rsrc + data)'],
+ [0L, 'string', '=', 'FFIL', 'font/ttf'],
+ [65L, 'string', '=', 'FFIL', 'font/ttf'],
+ [0L, 'string', '=', 'LWFN', 'font/type1'],
+ [65L, 'string', '=', 'LWFN', 'font/type1'],
+ [0L, 'string', '=', 'PACT', 'Macintosh Compact Pro Archive (data)'],
+ [65L, 'string', '=', 'PACT', 'Macintosh Compact Pro Archive(rsrc + data)'],
+ [0L, 'string', '=', 'ttro', 'Macintosh TeachText File (data)'],
+ [65L, 'string', '=', 'ttro', 'Macintosh TeachText File(rsrc + data)'],
+ [0L, 'string', '=', 'TEXT', 'Macintosh TeachText File (data)'],
+ [65L, 'string', '=', 'TEXT', 'Macintosh TeachText File(rsrc + data)'],
+ [0L, 'string', '=', 'PDF', 'Macintosh PDF File (data)'],
+ [65L, 'string', '=', 'PDF', 'Macintosh PDF File(rsrc + data)'],
+ [0L, 'string', '=', '# Magic', 'magic text file for file(1) cmd'],
+ [0L, 'string', '=', 'Relay-Version:', 'old news text'],
+ [0L, 'string', '=', '#! rnews', 'batched news text'],
+ [0L, 'string', '=', 'N#! rnews', 'mailed, batched news text'],
+ [0L, 'string', '=', 'Forward to', 'mail forwarding text'],
+ [0L, 'string', '=', 'Pipe to', 'mail piping text'],
+ [0L, 'string', '=', 'Return-Path:', 'message/rfc822'],
+ [0L, 'string', '=', 'Path:', 'message/news'],
+ [0L, 'string', '=', 'Xref:', 'message/news'],
+ [0L, 'string', '=', 'From:', 'message/rfc822'],
+ [0L, 'string', '=', 'Article', 'message/news'],
+ [0L, 'string', '=', 'BABYL', 'message/x-gnu-rmail'],
+ [0L, 'string', '=', 'Received:', 'message/rfc822'],
+ [0L, 'string', '=', 'MIME-Version:', 'MIME entity text'],
+ [0L, 'string', '=', 'Content-Type: ', ''],
+ [0L, 'string', '=', 'Content-Type:', ''],
+ [0L, 'long', '=', 31415L, 'Mirage Assembler m.out executable'],
+ [0L, 'string', '=', '\311\304', 'ID tags data'],
+ [0L, 'string', '=', '\001\001\001\001', 'MMDF mailbox'],
+ [4L, 'string', '=', 'Research,', 'Digifax-G3-File'],
+ [0L, 'short', '=', 256L, 'raw G3 data, byte-padded'],
+ [0L, 'short', '=', 5120L, 'raw G3 data'],
+ [0L, 'string', '=', 'RMD1', 'raw modem data'],
+ [0L, 'string', '=', 'PVF1\012', 'portable voice format'],
+ [0L, 'string', '=', 'PVF2\012', 'portable voice format'],
+ [0L, 'beshort', '=', 520L, 'mc68k COFF'],
+ [0L, 'beshort', '=', 521L, 'mc68k executable (shared)'],
+ [0L, 'beshort', '=', 522L, 'mc68k executable (shared demand paged)'],
+ [0L, 'beshort', '=', 554L, '68K BCS executable'],
+ [0L, 'beshort', '=', 555L, '88K BCS executable'],
+ [0L, 'string', '=', 'S0', 'Motorola S-Record; binary data in text format'],
+ [0L, 'string', '=', '@echo off', 'MS-DOS batch file text'],
+ [128L, 'string', '=', 'PE\000\000', 'MS Windows PE'],
+ [0L, 'leshort', '=', 332L, 'MS Windows COFF Intel 80386 object file'],
+ [0L, 'leshort', '=', 358L, 'MS Windows COFF MIPS R4000 object file'],
+ [0L, 'leshort', '=', 388L, 'MS Windows COFF Alpha object file'],
+ [0L, 'leshort', '=', 616L, 'MS Windows COFF Motorola 68000 object file'],
+ [0L, 'leshort', '=', 496L, 'MS Windows COFF PowerPC object file'],
+ [0L, 'leshort', '=', 656L, 'MS Windows COFF PA-RISC object file'],
+ [0L, 'string', '=', 'MZ', 'application/x-ms-dos-executable'],
+ [0L, 'string', '=', 'LZ', 'MS-DOS executable (built-in)'],
+ [0L, 'string', '=', 'regf', 'Windows NT Registry file'],
+ [2080L, 'string', '=', 'Microsoft Word 6.0 Document', 'text/vnd.ms-word'],
+ [2080L, 'string', '=', 'Documento Microsoft Word 6', 'text/vnd.ms-word'],
+ [2112L, 'string', '=', 'MSWordDoc', 'text/vnd.ms-word'],
+ [0L, 'belong', '=', 834535424L, 'text/vnd.ms-word'],
+ [0L, 'string', '=', 'PO^Q`', 'text/vnd.ms-word'],
+ [2080L, 'string', '=', 'Microsoft Excel 5.0 Worksheet', 'application/vnd.ms-excel'],
+ [2114L, 'string', '=', 'Biff5', 'application/vnd.ms-excel'],
+ [0L, 'belong', '=', 6656L, 'Lotus 1-2-3'],
+ [0L, 'belong', '=', 512L, 'Lotus 1-2-3'],
+ [1L, 'string', '=', 'WPC', 'text/vnd.wordperfect'],
+ [0L, 'beshort', '=', 610L, 'Tower/XP rel 2 object'],
+ [0L, 'beshort', '=', 615L, 'Tower/XP rel 2 object'],
+ [0L, 'beshort', '=', 620L, 'Tower/XP rel 3 object'],
+ [0L, 'beshort', '=', 625L, 'Tower/XP rel 3 object'],
+ [0L, 'beshort', '=', 630L, 'Tower32/600/400 68020 object'],
+ [0L, 'beshort', '=', 640L, 'Tower32/800 68020'],
+ [0L, 'beshort', '=', 645L, 'Tower32/800 68010'],
+ [0L, 'lelong', '=', 407L, 'NetBSD little-endian object file'],
+ [0L, 'belong', '=', 407L, 'NetBSD big-endian object file'],
+ [0L, 'belong&0377777777', '=', 41400413L, 'NetBSD/i386 demand paged'],
+ [0L, 'belong&0377777777', '=', 41400410L, 'NetBSD/i386 pure'],
+ [0L, 'belong&0377777777', '=', 41400407L, 'NetBSD/i386'],
+ [0L, 'belong&0377777777', '=', 41400507L, 'NetBSD/i386 core'],
+ [0L, 'belong&0377777777', '=', 41600413L, 'NetBSD/m68k demand paged'],
+ [0L, 'belong&0377777777', '=', 41600410L, 'NetBSD/m68k pure'],
+ [0L, 'belong&0377777777', '=', 41600407L, 'NetBSD/m68k'],
+ [0L, 'belong&0377777777', '=', 41600507L, 'NetBSD/m68k core'],
+ [0L, 'belong&0377777777', '=', 42000413L, 'NetBSD/m68k4k demand paged'],
+ [0L, 'belong&0377777777', '=', 42000410L, 'NetBSD/m68k4k pure'],
+ [0L, 'belong&0377777777', '=', 42000407L, 'NetBSD/m68k4k'],
+ [0L, 'belong&0377777777', '=', 42000507L, 'NetBSD/m68k4k core'],
+ [0L, 'belong&0377777777', '=', 42200413L, 'NetBSD/ns32532 demand paged'],
+ [0L, 'belong&0377777777', '=', 42200410L, 'NetBSD/ns32532 pure'],
+ [0L, 'belong&0377777777', '=', 42200407L, 'NetBSD/ns32532'],
+ [0L, 'belong&0377777777', '=', 42200507L, 'NetBSD/ns32532 core'],
+ [0L, 'belong&0377777777', '=', 42400413L, 'NetBSD/sparc demand paged'],
+ [0L, 'belong&0377777777', '=', 42400410L, 'NetBSD/sparc pure'],
+ [0L, 'belong&0377777777', '=', 42400407L, 'NetBSD/sparc'],
+ [0L, 'belong&0377777777', '=', 42400507L, 'NetBSD/sparc core'],
+ [0L, 'belong&0377777777', '=', 42600413L, 'NetBSD/pmax demand paged'],
+ [0L, 'belong&0377777777', '=', 42600410L, 'NetBSD/pmax pure'],
+ [0L, 'belong&0377777777', '=', 42600407L, 'NetBSD/pmax'],
+ [0L, 'belong&0377777777', '=', 42600507L, 'NetBSD/pmax core'],
+ [0L, 'belong&0377777777', '=', 43000413L, 'NetBSD/vax demand paged'],
+ [0L, 'belong&0377777777', '=', 43000410L, 'NetBSD/vax pure'],
+ [0L, 'belong&0377777777', '=', 43000407L, 'NetBSD/vax'],
+ [0L, 'belong&0377777777', '=', 43000507L, 'NetBSD/vax core'],
+ [0L, 'lelong', '=', 459141L, 'ECOFF NetBSD/alpha binary'],
+ [0L, 'belong&0377777777', '=', 43200507L, 'NetBSD/alpha core'],
+ [0L, 'belong&0377777777', '=', 43400413L, 'NetBSD/mips demand paged'],
+ [0L, 'belong&0377777777', '=', 43400410L, 'NetBSD/mips pure'],
+ [0L, 'belong&0377777777', '=', 43400407L, 'NetBSD/mips'],
+ [0L, 'belong&0377777777', '=', 43400507L, 'NetBSD/mips core'],
+ [0L, 'belong&0377777777', '=', 43600413L, 'NetBSD/arm32 demand paged'],
+ [0L, 'belong&0377777777', '=', 43600410L, 'NetBSD/arm32 pure'],
+ [0L, 'belong&0377777777', '=', 43600407L, 'NetBSD/arm32'],
+ [0L, 'belong&0377777777', '=', 43600507L, 'NetBSD/arm32 core'],
+ [0L, 'string', '=', 'StartFontMetrics', 'font/x-sunos-news'],
+ [0L, 'string', '=', 'StartFont', 'font/x-sunos-news'],
+ [0L, 'belong', '=', 326773060L, 'font/x-sunos-news'],
+ [0L, 'belong', '=', 326773063L, 'font/x-sunos-news'],
+ [0L, 'belong', '=', 326773072L, 'font/x-sunos-news'],
+ [0L, 'belong', '=', 326773073L, 'font/x-sunos-news'],
+ [8L, 'belong', '=', 326773573L, 'font/x-sunos-news'],
+ [8L, 'belong', '=', 326773576L, 'font/x-sunos-news'],
+ [0L, 'string', '=', 'Octave-1-L', 'Octave binary data (little endian)'],
+ [0L, 'string', '=', 'Octave-1-B', 'Octave binary data (big endian)'],
+ [0L, 'string', '=', '\177OLF', 'OLF'],
+ [0L, 'beshort', '=', 34765L, 'OS9/6809 module:'],
+ [0L, 'beshort', '=', 19196L, 'OS9/68K module:'],
+ [0L, 'long', '=', 61374L, 'OSF/Rose object'],
+ [0L, 'short', '=', 565L, 'i386 COFF object'],
+ [0L, 'short', '=', 10775L, '"compact bitmap" format (Poskanzer)'],
+ [0L, 'string', '=', '%PDF-', 'PDF document'],
+ [0L, 'lelong', '=', 101555L, 'PDP-11 single precision APL workspace'],
+ [0L, 'lelong', '=', 101554L, 'PDP-11 double precision APL workspace'],
+ [0L, 'leshort', '=', 407L, 'PDP-11 executable'],
+ [0L, 'leshort', '=', 401L, 'PDP-11 UNIX/RT ldp'],
+ [0L, 'leshort', '=', 405L, 'PDP-11 old overlay'],
+ [0L, 'leshort', '=', 410L, 'PDP-11 pure executable'],
+ [0L, 'leshort', '=', 411L, 'PDP-11 separate I&D executable'],
+ [0L, 'leshort', '=', 437L, 'PDP-11 kernel overlay'],
+ [0L, 'beshort', '=', 39168L, 'PGP key public ring'],
+ [0L, 'beshort', '=', 38145L, 'PGP key security ring'],
+ [0L, 'beshort', '=', 38144L, 'PGP key security ring'],
+ [0L, 'beshort', '=', 42496L, 'PGP encrypted data'],
+ [0L, 'string', '=', '-----BEGIN PGP', 'PGP armored data'],
+ [0L, 'string', '=', '# PaCkAgE DaTaStReAm', 'pkg Datastream (SVR4)'],
+ [0L, 'short', '=', 601L, 'mumps avl global'],
+ [0L, 'short', '=', 602L, 'mumps blt global'],
+ [0L, 'string', '=', '%!', 'application/postscript'],
+ [0L, 'string', '=', '\004%!', 'application/postscript'],
+ [0L, 'belong', '=', 3318797254L, 'DOS EPS Binary File'],
+ [0L, 'string', '=', '*PPD-Adobe:', 'PPD file'],
+ [0L, 'string', '=', '\033%-12345X at PJL', 'HP Printer Job Language data'],
+ [0L, 'string', '=', '\033%-12345X at PJL', 'HP Printer Job Language data'],
+ [0L, 'string', '=', '\033E\033', 'image/x-pcl-hp'],
+ [0L, 'string', '=', '@document(', 'Imagen printer'],
+ [0L, 'string', '=', 'Rast', 'RST-format raster font data'],
+ [0L, 'belong&0xff00ffff', '=', 1442840576L, 'ps database'],
+ [0L, 'long', '=', 1351614727L, 'Pyramid 90x family executable'],
+ [0L, 'long', '=', 1351614728L, 'Pyramid 90x family pure executable'],
+ [0L, 'long', '=', 1351614731L, 'Pyramid 90x family demand paged pure executable'],
+ [0L, 'beshort', '=', 60843L, ''],
+ [0L, 'string', '=', '{\\\\rtf', 'Rich Text Format data,'],
+ [38L, 'string', '=', 'Spreadsheet', 'sc spreadsheet file'],
+ [8L, 'string', '=', '\001s SCCS', 'archive data'],
+ [0L, 'byte', '=', 46L, 'Sendmail frozen configuration'],
+ [0L, 'short', '=', 10012L, 'Sendmail frozen configuration'],
+ [0L, 'lelong', '=', 234L, 'BALANCE NS32000 .o'],
+ [0L, 'lelong', '=', 4330L, 'BALANCE NS32000 executable (0 @ 0)'],
+ [0L, 'lelong', '=', 8426L, 'BALANCE NS32000 executable (invalid @ 0)'],
+ [0L, 'lelong', '=', 12522L, 'BALANCE NS32000 standalone executable'],
+ [0L, 'leshort', '=', 4843L, 'SYMMETRY i386 .o'],
+ [0L, 'leshort', '=', 8939L, 'SYMMETRY i386 executable (0 @ 0)'],
+ [0L, 'leshort', '=', 13035L, 'SYMMETRY i386 executable (invalid @ 0)'],
+ [0L, 'leshort', '=', 17131L, 'SYMMETRY i386 standalone executable'],
+ [0L, 'string', '=', 'kbd!map', 'kbd map file'],
+ [0L, 'belong', '=', 407L, 'old SGI 68020 executable'],
+ [0L, 'belong', '=', 410L, 'old SGI 68020 pure executable'],
+ [0L, 'beshort', '=', 34661L, 'disk quotas file'],
+ [0L, 'beshort', '=', 1286L, 'IRIS Showcase file'],
+ [0L, 'beshort', '=', 550L, 'IRIS Showcase template'],
+ [0L, 'belong', '=', 1396917837L, 'IRIS Showcase file'],
+ [0L, 'belong', '=', 1413695053L, 'IRIS Showcase template'],
+ [0L, 'belong', '=', 3735927486L, 'IRIX Parallel Arena'],
+ [0L, 'beshort', '=', 352L, 'MIPSEB COFF executable'],
+ [0L, 'beshort', '=', 354L, 'MIPSEL COFF executable'],
+ [0L, 'beshort', '=', 24577L, 'MIPSEB-LE COFF executable'],
+ [0L, 'beshort', '=', 25089L, 'MIPSEL-LE COFF executable'],
+ [0L, 'beshort', '=', 355L, 'MIPSEB MIPS-II COFF executable'],
+ [0L, 'beshort', '=', 358L, 'MIPSEL MIPS-II COFF executable'],
+ [0L, 'beshort', '=', 25345L, 'MIPSEB-LE MIPS-II COFF executable'],
+ [0L, 'beshort', '=', 26113L, 'MIPSEL-LE MIPS-II COFF executable'],
+ [0L, 'beshort', '=', 320L, 'MIPSEB MIPS-III COFF executable'],
+ [0L, 'beshort', '=', 322L, 'MIPSEL MIPS-III COFF executable'],
+ [0L, 'beshort', '=', 16385L, 'MIPSEB-LE MIPS-III COFF executable'],
+ [0L, 'beshort', '=', 16897L, 'MIPSEL-LE MIPS-III COFF executable'],
+ [0L, 'beshort', '=', 384L, 'MIPSEB Ucode'],
+ [0L, 'beshort', '=', 386L, 'MIPSEL Ucode'],
+ [0L, 'belong', '=', 3735924144L, 'IRIX core dump'],
+ [0L, 'belong', '=', 3735924032L, 'IRIX 64-bit core dump'],
+ [0L, 'belong', '=', 3133063355L, 'IRIX N32 core dump'],
+ [0L, 'string', '=', 'CrshDump', 'IRIX vmcore dump of'],
+ [0L, 'string', '=', 'SGIAUDIT', 'SGI Audit file'],
+ [0L, 'string', '=', 'WNGZWZSC', 'Wingz compiled script'],
+ [0L, 'string', '=', 'WNGZWZSS', 'Wingz spreadsheet'],
+ [0L, 'string', '=', 'WNGZWZHP', 'Wingz help file'],
+ [0L, 'string', '=', '\\#Inventor', 'V IRIS Inventor 1.0 file'],
+ [0L, 'string', '=', '\\#Inventor', 'V2 Open Inventor 2.0 file'],
+ [0L, 'string', '=', 'glfHeadMagic();', 'GLF_TEXT'],
+ [4L, 'belong', '=', 1090584576L, 'GLF_BINARY_LSB_FIRST'],
+ [4L, 'belong', '=', 321L, 'GLF_BINARY_MSB_FIRST'],
+ [0L, 'string', '=', '<!DOCTYPE HTML', 'text/html'],
+ [0L, 'string', '=', '<!doctype html', 'text/html'],
+ [0L, 'string', '=', '<HEAD', 'text/html'],
+ [0L, 'string', '=', '<head', 'text/html'],
+ [0L, 'string', '=', '<TITLE', 'text/html'],
+ [0L, 'string', '=', '<title', 'text/html'],
+ [0L, 'string', '=', '<html', 'text/html'],
+ [0L, 'string', '=', '<HTML', 'text/html'],
+ [0L, 'string', '=', '<!DOCTYPE', 'exported SGML document text'],
+ [0L, 'string', '=', '<!doctype', 'exported SGML document text'],
+ [0L, 'string', '=', '<!SUBDOC', 'exported SGML subdocument text'],
+ [0L, 'string', '=', '<!subdoc', 'exported SGML subdocument text'],
+ [0L, 'string', '=', '<!--', 'exported SGML document text'],
+ [0L, 'string', '=', 'RTSS', 'NetMon capture file'],
+ [0L, 'string', '=', 'TRSNIFF data \032', 'Sniffer capture file'],
+ [0L, 'string', '=', 'XCP\000', 'NetXRay capture file'],
+ [0L, 'ubelong', '=', 2712847316L, 'tcpdump capture file (big-endian)'],
+ [0L, 'ulelong', '=', 2712847316L, 'tcpdump capture file (little-endian)'],
+ [0L, 'string', '=', '<!SQ DTD>', 'Compiled SGML rules file'],
+ [0L, 'string', '=', '<!SQ A/E>', 'A/E SGML Document binary'],
+ [0L, 'string', '=', '<!SQ STS>', 'A/E SGML binary styles file'],
+ [0L, 'short', '=', 49374L, 'Compiled PSI (v1) data'],
+ [0L, 'short', '=', 49370L, 'Compiled PSI (v2) data'],
+ [0L, 'short', '=', 125252L, 'SoftQuad DESC or font file binary'],
+ [0L, 'string', '=', 'SQ BITMAP1', 'SoftQuad Raster Format text'],
+ [0L, 'string', '=', 'X SoftQuad', 'troff Context intermediate'],
+ [0L, 'belong&077777777', '=', 600413L, 'sparc demand paged'],
+ [0L, 'belong&077777777', '=', 600410L, 'sparc pure'],
+ [0L, 'belong&077777777', '=', 600407L, 'sparc'],
+ [0L, 'belong&077777777', '=', 400413L, 'mc68020 demand paged'],
+ [0L, 'belong&077777777', '=', 400410L, 'mc68020 pure'],
+ [0L, 'belong&077777777', '=', 400407L, 'mc68020'],
+ [0L, 'belong&077777777', '=', 200413L, 'mc68010 demand paged'],
+ [0L, 'belong&077777777', '=', 200410L, 'mc68010 pure'],
+ [0L, 'belong&077777777', '=', 200407L, 'mc68010'],
+ [0L, 'belong', '=', 407L, 'old sun-2 executable'],
+ [0L, 'belong', '=', 410L, 'old sun-2 pure executable'],
+ [0L, 'belong', '=', 413L, 'old sun-2 demand paged executable'],
+ [0L, 'belong', '=', 525398L, 'SunOS core file'],
+ [0L, 'long', '=', 4197695630L, 'SunPC 4.0 Hard Disk'],
+ [0L, 'string', '=', '#SUNPC_CONFIG', 'SunPC 4.0 Properties Values'],
+ [0L, 'string', '=', 'snoop', 'Snoop capture file'],
+ [36L, 'string', '=', 'acsp', 'Kodak Color Management System, ICC Profile'],
+ [0L, 'string', '=', '#!teapot\012xdr', 'teapot work sheet (XDR format)'],
+ [0L, 'string', '=', '\032\001', 'Compiled terminfo entry'],
+ [0L, 'short', '=', 433L, 'Curses screen image'],
+ [0L, 'short', '=', 434L, 'Curses screen image'],
+ [0L, 'string', '=', '\367\002', 'TeX DVI file'],
+ [0L, 'string', '=', '\367\203', 'font/x-tex'],
+ [0L, 'string', '=', '\367Y', 'font/x-tex'],
+ [0L, 'string', '=', '\367\312', 'font/x-tex'],
+ [0L, 'string', '=', 'This is TeX,', 'TeX transcript text'],
+ [0L, 'string', '=', 'This is METAFONT,', 'METAFONT transcript text'],
+ [2L, 'string', '=', '\000\021', 'font/x-tex-tfm'],
+ [2L, 'string', '=', '\000\022', 'font/x-tex-tfm'],
+ [0L, 'string', '=', '\\\\input\\', 'texinfo Texinfo source text'],
+ [0L, 'string', '=', 'This is Info file', 'GNU Info text'],
+ [0L, 'string', '=', '\\\\input', 'TeX document text'],
+ [0L, 'string', '=', '\\\\section', 'LaTeX document text'],
+ [0L, 'string', '=', '\\\\setlength', 'LaTeX document text'],
+ [0L, 'string', '=', '\\\\documentstyle', 'LaTeX document text'],
+ [0L, 'string', '=', '\\\\chapter', 'LaTeX document text'],
+ [0L, 'string', '=', '\\\\documentclass', 'LaTeX 2e document text'],
+ [0L, 'string', '=', '\\\\relax', 'LaTeX auxiliary file'],
+ [0L, 'string', '=', '\\\\contentsline', 'LaTeX table of contents'],
+ [0L, 'string', '=', '\\\\indexentry', 'LaTeX raw index file'],
+ [0L, 'string', '=', '\\\\begin{theindex}', 'LaTeX sorted index'],
+ [0L, 'string', '=', '\\\\glossaryentry', 'LaTeX raw glossary'],
+ [0L, 'string', '=', '\\\\begin{theglossary}', 'LaTeX sorted glossary'],
+ [0L, 'string', '=', 'This is makeindex', 'Makeindex log file'],
+ [0L, 'string', '=', '**TI82**', 'TI-82 Graphing Calculator'],
+ [0L, 'string', '=', '**TI83**', 'TI-83 Graphing Calculator'],
+ [0L, 'string', '=', '**TI85**', 'TI-85 Graphing Calculator'],
+ [0L, 'string', '=', '**TI92**', 'TI-92 Graphing Calculator'],
+ [0L, 'string', '=', '**TI80**', 'TI-80 Graphing Calculator File.'],
+ [0L, 'string', '=', '**TI81**', 'TI-81 Graphing Calculator File.'],
+ [0L, 'string', '=', 'TZif', 'timezone data'],
+ [0L, 'string', '=', '\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000', 'old timezone data'],
+ [0L, 'string', '=', '\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\002\000', 'old timezone data'],
+ [0L, 'string', '=', '\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\003\000', 'old timezone data'],
+ [0L, 'string', '=', '\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000', 'old timezone data'],
+ [0L, 'string', '=', '\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\005\000', 'old timezone data'],
+ [0L, 'string', '=', '\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\006\000', 'old timezone data'],
+ [0L, 'string', '=', '.\\\\"', 'troff or preprocessor input text'],
+ [0L, 'string', '=', '\'\\\\"', 'troff or preprocessor input text'],
+ [0L, 'string', '=', '\'.\\\\"', 'troff or preprocessor input text'],
+ [0L, 'string', '=', '\\\\"', 'troff or preprocessor input text'],
+ [0L, 'string', '=', 'x T', 'ditroff text'],
+ [0L, 'string', '=', '@\357', 'very old (C/A/T) troff output data'],
+ [0L, 'string', '=', 'Interpress/Xerox', 'Xerox InterPress data'],
+ [0L, 'short', '=', 263L, 'unknown machine executable'],
+ [0L, 'short', '=', 264L, 'unknown pure executable'],
+ [0L, 'short', '=', 265L, 'PDP-11 separate I&D'],
+ [0L, 'short', '=', 267L, 'unknown pure executable'],
+ [0L, 'long', '=', 268L, 'unknown demand paged pure executable'],
+ [0L, 'long', '=', 269L, 'unknown demand paged pure executable'],
+ [0L, 'long', '=', 270L, 'unknown readable demand paged pure executable'],
+ [0L, 'string', '=', 'begin uuencoded', 'or xxencoded text'],
+ [0L, 'string', '=', 'xbtoa Begin', "btoa'd text"],
+ [0L, 'string', '=', '$\012ship', "ship'd binary text"],
+ [0L, 'string', '=', 'Decode the following with bdeco', 'bencoded News text'],
+ [11L, 'string', '=', 'must be converted with BinHex', 'BinHex binary text'],
+ [0L, 'short', '=', 610L, 'Perkin-Elmer executable'],
+ [0L, 'beshort', '=', 572L, 'amd 29k coff noprebar executable'],
+ [0L, 'beshort', '=', 1572L, 'amd 29k coff prebar executable'],
+ [0L, 'beshort', '=', 160007L, 'amd 29k coff archive'],
+ [6L, 'beshort', '=', 407L, 'unicos (cray) executable'],
+ [596L, 'string', '=', 'X\337\377\377', 'Ultrix core file'],
+ [0L, 'string', '=', 'Joy!peffpwpc', 'header for PowerPC PEF executable'],
+ [0L, 'lelong', '=', 101557L, 'VAX single precision APL workspace'],
+ [0L, 'lelong', '=', 101556L, 'VAX double precision APL workspace'],
+ [0L, 'lelong', '=', 407L, 'VAX executable'],
+ [0L, 'lelong', '=', 410L, 'VAX pure executable'],
+ [0L, 'lelong', '=', 413L, 'VAX demand paged pure executable'],
+ [0L, 'leshort', '=', 570L, 'VAX COFF executable'],
+ [0L, 'leshort', '=', 575L, 'VAX COFF pure executable'],
+ [0L, 'string', '=', 'LBLSIZE=', 'VICAR image data'],
+ [43L, 'string', '=', 'SFDU_LABEL', 'VICAR label file'],
+ [0L, 'short', '=', 21845L, 'VISX image file'],
+ [0L, 'string', '=', '\260\0000\000', 'VMS VAX executable'],
+ [0L, 'belong', '=', 50331648L, 'VMS Alpha executable'],
+ [1L, 'string', '=', 'WPC', '(Corel/WP)'],
+ [0L, 'string', '=', 'core', 'core file (Xenix)'],
+ [0L, 'byte', '=', 128L, '8086 relocatable (Microsoft)'],
+ [0L, 'leshort', '=', 65381L, 'x.out'],
+ [0L, 'leshort', '=', 518L, 'Microsoft a.out'],
+ [0L, 'leshort', '=', 320L, 'old Microsoft 8086 x.out'],
+ [0L, 'lelong', '=', 518L, 'b.out'],
+ [0L, 'leshort', '=', 1408L, 'XENIX 8086 relocatable or 80286 small model'],
+ [0L, 'long', '=', 59399L, 'object file (z8000 a.out)'],
+ [0L, 'long', '=', 59400L, 'pure object file (z8000 a.out)'],
+ [0L, 'long', '=', 59401L, 'separate object file (z8000 a.out)'],
+ [0L, 'long', '=', 59397L, 'overlay object file (z8000 a.out)'],
+ [0L, 'string', '=', 'ZyXEL\002', 'ZyXEL voice data'],
+]
+
+magicNumbers = []
+
+def strToNum(n):
+ val = 0
+ col = long(1)
+ if n[:1] == 'x': n = '0' + n
+ if n[:2] == '0x':
+ # hex
+ n = string.lower(n[2:])
+ while len(n) > 0:
+ l = n[len(n) - 1]
+ val = val + string.hexdigits.index(l) * col
+ col = col * 16
+ n = n[:len(n)-1]
+ elif n[0] == '\\':
+ # octal
+ n = n[1:]
+ while len(n) > 0:
+ l = n[len(n) - 1]
+ if ord(l) < 48 or ord(l) > 57: break
+ val = val + int(l) * col
+ col = col * 8
+ n = n[:len(n)-1]
+ else:
+ val = string.atol(n)
+ return val
+
+def unescape(s):
+ # replace string escape sequences
+ while 1:
+ m = re.search(r'\\', s)
+ if not m: break
+ x = m.start()+1
+ if m.end() == len(s):
+ # escaped space at end
+ s = s[:len(s)-1] + ' '
+ elif s[x:x+2] == '0x':
+ # hex ascii value
+ c = chr(strToNum(s[x:x+4]))
+ s = s[:x-1] + c + s[x+4:]
+ elif s[m.start()+1] == 'x':
+ # hex ascii value
+ c = chr(strToNum(s[x:x+3]))
+ s = s[:x-1] + c + s[x+3:]
+ elif ord(s[x]) > 47 and ord(s[x]) < 58:
+ # octal ascii value
+ end = x
+ while (ord(s[end]) > 47 and ord(s[end]) < 58):
+ end = end + 1
+ if end > len(s) - 1: break
+ c = chr(strToNum(s[x-1:end]))
+ s = s[:x-1] + c + s[end:]
+ elif s[x] == 'n':
+ # newline
+ s = s[:x-1] + '\n' + s[x+1:]
+ else:
+ break
+ return s
+
+class magicTest:
+ def __init__(self, offset, t, op, value, msg, mask = None):
+ if t.count('&') > 0:
+ mask = strToNum(t[t.index('&')+1:])
+ t = t[:t.index('&')]
+ if type(offset) == type('a'):
+ self.offset = strToNum(offset)
+ else:
+ self.offset = offset
+ self.type = t
+ self.msg = msg
+ self.subTests = []
+ self.op = op
+ self.mask = mask
+ self.value = value
+
+
+ def test(self, data):
+ if self.mask:
+ data = data & self.mask
+ if self.op == '=':
+ if self.value == data: return self.msg
+ elif self.op == '<':
+ pass
+ elif self.op == '>':
+ pass
+ elif self.op == '&':
+ pass
+ elif self.op == '^':
+ pass
+ return None
+
+ def compare(self, data):
+ #print str([self.type, self.value, self.msg])
+ try:
+ if self.type == 'string':
+ c = ''; s = ''
+ for i in range(0, len(self.value)+1):
+ if i + self.offset > len(data) - 1: break
+ s = s + c
+ [c] = struct.unpack('c', data[self.offset + i])
+ data = s
+ elif self.type == 'short':
+ [data] = struct.unpack('h', data[self.offset : self.offset + 2])
+ elif self.type == 'leshort':
+ [data] = struct.unpack('<h', data[self.offset : self.offset + 2])
+ elif self.type == 'beshort':
+ [data] = struct.unpack('>H', data[self.offset : self.offset + 2])
+ elif self.type == 'long':
+ [data] = struct.unpack('l', data[self.offset : self.offset + 4])
+ elif self.type == 'lelong':
+ [data] = struct.unpack('<l', data[self.offset : self.offset + 4])
+ elif self.type == 'belong':
+ [data] = struct.unpack('>l', data[self.offset : self.offset + 4])
+ else:
+ #print 'UNKNOWN TYPE: ' + self.type
+ pass
+ except:
+ return None
+
+# print str([self.msg, self.value, data])
+ return self.test(data)
+
+
+def load(file):
+ global magicNumbers
+ lines = open(file).readlines()
+ last = { 0: None }
+ for line in lines:
+ if re.match(r'\s*#', line):
+ # comment
+ continue
+ else:
+ # split up by space delimiters, and remove trailing space
+ line = string.rstrip(line)
+ line = re.split(r'\s*', line)
+ if len(line) < 3:
+ # bad line
+ continue
+ offset = line[0]
+ type = line[1]
+ value = line[2]
+ level = 0
+ while offset[0] == '>':
+ # count the level of the type
+ level = level + 1
+ offset = offset[1:]
+ l = magicNumbers
+ if level > 0:
+ l = last[level - 1].subTests
+ if offset[0] == '(':
+ # don't handle indirect offsets just yet
+ print 'SKIPPING ' + string.join(list(line[3:]))
+ pass
+ elif offset[0] == '&':
+ # don't handle relative offsets just yet
+ print 'SKIPPING ' + string.join(list(line[3:]))
+ pass
+ else:
+ operands = ['=', '<', '>', '&']
+ if operands.count(value[0]) > 0:
+ # a comparison operator is specified
+ op = value[0]
+ value = value[1:]
+ else:
+ print str([value, operands])
+ if len(value) >1 and value[0] == '\\' and operands.count(value[1]) >0:
+ # literal value that collides with operands is escaped
+ value = value[1:]
+ op = '='
+
+ mask = None
+ if type == 'string':
+ while 1:
+ value = unescape(value)
+ if value[len(value)-1] == ' ' and len(line) > 3:
+ # last value was an escaped space, join
+ value = value + line[3]
+ del line[3]
+ else:
+ break
+ else:
+ if value.count('&') != 0:
+ mask = value[(value.index('&') + 1):]
+ print 'MASK: ' + mask
+ value = value[:(value.index('&')+1)]
+ try: value = strToNum(value)
+ except: continue
+ msg = string.join(list(line[3:]))
+ new = magicTest(offset, type, op, value, msg, mask)
+ last[level] = new
+ l.append(new)
+
+def whatis(data):
+ for test in magicNumbers:
+ m = test.compare(data)
+ if m: return m
+ # no matching, magic number. is it binary or text?
+ for c in data:
+ if ord(c) > 128:
+ return 'data'
+ # its ASCII, now do text tests
+ if string.find('The', data, 0, 8192) > -1:
+ return 'English text'
+ if string.find('def', data, 0, 8192) > -1:
+ return 'Python Source'
+ return 'ASCII text'
+
+
+def file(file):
+ try:
+ return whatis(open(file, 'r').read(8192))
+ except Exception, e:
+ if str(e) == '[Errno 21] Is a directory':
+ return 'directory'
+ else:
+ raise e
+
+
+#### BUILD DATA ####
+#load('mime-magic')
+#f = open('out', 'w')
+#for m in magicNumbers:
+# f.write(str([m.offset, m.type, m.op, m.value, m.msg]) + ',\n')
+#f.close
+
+import sys
+for m in magic:
+ magicNumbers.append(magicTest(m[0], m[1], m[2], m[3], m[4]))
+
+if __name__ == '__main__':
+ import sys
+ for arg in sys.argv[1:]:
+ msg = file(arg)
+ if msg:
+ print arg + ': ' + msg
+ else:
+ print arg + ': unknown'
diff --git a/DAVServer/server.py b/DAVServer/server.py
index 0b2801b..b070a5c 100755
--- a/DAVServer/server.py
+++ b/DAVServer/server.py
@@ -52,6 +52,7 @@ def runserver(
server = BaseHTTPServer.HTTPServer):
directory = directory.strip()
+ directory = directory.rstrip('/')
host = host.strip()
if not os.path.isdir(directory):
@@ -79,6 +80,14 @@ def runserver(
handler.DO_AUTH = False
print >>sys.stderr, '>> Serving data from %s' % directory
+
+ if handler._config.DAV.lockemulation is False:
+ print >>sys.stderr, '>> Deactivated LOCK, UNLOCK (WebDAV level 2) support'
+
+ handler.IFACE_CLASS.mimecheck = True
+ if handler._config.DAV.mimecheck is False:
+ handler.IFACE_CLASS.mimecheck = False
+ print >>sys.stderr, '>> Disabled mimetype sniffing (All files will have type application/octet-stream)'
# initialize server on specified port
runner = server( (host, port), handler )
@@ -95,7 +104,11 @@ def runserver(
print >>sys.stderr, '\n>> Killed by user'
usage = """PyWebDAV server (version %s)
-Standalone WebDAV server based on python
+Standalone WebDAV server
+
+Make sure to activate LOCK, UNLOCK using parameter -J if you want
+to use clients like Windows Explorer or Mac OS X Finder that expect
+LOCK working for write support.
Usage: ./server.py [OPTIONS]
Parameters:
@@ -116,7 +129,10 @@ Parameters:
If you want to use MySQL then the usage of a configuration
file is mandatory.
-J, --lockemu Activate experimental LOCK and UNLOCK mode (WebDAV Version 2).
- Currently know to work but needs more tests.
+ Currently know to work but needs more tests. Default is ON.
+ -M, --nomime Deactivate mimetype sniffing. Sniffing is based on magic numbers
+ detection but can be slow under heavy load. If you are experiencing
+ speed problems try to use this parameter.
-i, --icounter If you want to run multiple instances then you have to
give each instance it own number so that logfiles and such
can be identified. Default is 0
@@ -158,14 +174,15 @@ def run():
daemonaction = 'start'
counter = 0
mysql = False
- lockemulation = False
+ lockemulation = True
configfile = ''
+ mimecheck = True
# parse commandline
try:
- opts, args = getopt.getopt(sys.argv[1:], 'P:D:H:d:u:p:nvhmJi:c:',
+ opts, args = getopt.getopt(sys.argv[1:], 'P:D:H:d:u:p:nvhmJi:c:M',
['host=', 'port=', 'directory=', 'user=', 'password=','daemon=', 'noauth', 'help', 'verbose',
- 'mysql', 'icounter=', 'config=', 'lockemu'])
+ 'mysql', 'icounter=', 'config=', 'lockemu', 'nomime'])
except getopt.GetoptError, e:
print usage
print '>>>> ERROR: %s' % str(e)
@@ -178,6 +195,9 @@ def run():
if o in ['-m', '--mysql']:
mysql = True
+ if o in ['-M', '--nomime']:
+ mimecheck = False
+
if o in ['-J', '--lockemu']:
lockemulation = True
@@ -190,7 +210,7 @@ def run():
if o in ['-H', '--host']:
host = a
- if o in ['-P', '--p']:
+ if o in ['-P', '--port']:
port = a
if o in ['-v', '--verbose']:
@@ -230,6 +250,8 @@ def run():
if daemonaction != 'stop':
daemonaction = dv.daemonaction
counter = int(dv.counter)
+ lockemulation = dv.lockemulation
+ mimecheck = dv.mimecheck
else:
@@ -243,7 +265,8 @@ def run():
'daemonize' : daemonize,
'daemonaction' : daemonaction,
'counter' : counter,
- 'lockemulation' : lockemulation }
+ 'lockemulation' : lockemulation,
+ 'mimecheck' : mimecheck}
conf = setupDummyConfig(**_dc)
@@ -272,7 +295,7 @@ def run():
if daemonize:
# check if pid file exists
- if os.path.exists('/tmp/pydav%s.pid' % counter) and daemonaction != 'stop':
+ if os.path.exists('/tmp/pydav%s.pid' % counter) and daemonaction not in ['status', 'stop']:
print >>sys.stderr, \
'>> ERROR: Found another instance! Either use -i to specifiy another instance number or remove /tmp/pydav%s.pid!' % counter
sys.exit(3)
diff --git a/PKG-INFO b/PKG-INFO
index d95eda0..a24cad4 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,18 +1,20 @@
Metadata-Version: 1.0
Name: PyWebDAV
-Version: 0.9.2
+Version: 0.9.3
Summary: WebDAV library including a standalone server for python
Home-page: http://code.google.com/p/pywebdav/
Author: Simon Pamies
Author-email: spamsch at gmail.com
License: GPL v2
Description:
- WebDAV library for python. Consists of a server and the DAV package that provides WebDAV functionality.
+ WebDAV library for python. Consists of a server and the DAV package that provides WebDAV server(!) functionality.
Currently supports WebDAV level 1 and level 2 (LOCK, UNLOCK) making it play nice with cadaver, Mac OS X Finder, Windows Explorer or even iCal.
After installation of this package you will have a new script in you $PYTHON/bin directory called
*davserver*. This serves as the main entry point to the server.
+ This package does *not* provide client functionality.
+
Example (using easy_install)::
easy_install PyWebDAV
diff --git a/PyWebDAV.egg-info/PKG-INFO b/PyWebDAV.egg-info/PKG-INFO
index d95eda0..a24cad4 100644
--- a/PyWebDAV.egg-info/PKG-INFO
+++ b/PyWebDAV.egg-info/PKG-INFO
@@ -1,18 +1,20 @@
Metadata-Version: 1.0
Name: PyWebDAV
-Version: 0.9.2
+Version: 0.9.3
Summary: WebDAV library including a standalone server for python
Home-page: http://code.google.com/p/pywebdav/
Author: Simon Pamies
Author-email: spamsch at gmail.com
License: GPL v2
Description:
- WebDAV library for python. Consists of a server and the DAV package that provides WebDAV functionality.
+ WebDAV library for python. Consists of a server and the DAV package that provides WebDAV server(!) functionality.
Currently supports WebDAV level 1 and level 2 (LOCK, UNLOCK) making it play nice with cadaver, Mac OS X Finder, Windows Explorer or even iCal.
After installation of this package you will have a new script in you $PYTHON/bin directory called
*davserver*. This serves as the main entry point to the server.
+ This package does *not* provide client functionality.
+
Example (using easy_install)::
easy_install PyWebDAV
diff --git a/PyWebDAV.egg-info/SOURCES.txt b/PyWebDAV.egg-info/SOURCES.txt
index 4c55b7f..75f83ea 100644
--- a/PyWebDAV.egg-info/SOURCES.txt
+++ b/PyWebDAV.egg-info/SOURCES.txt
@@ -19,6 +19,7 @@ DAV/iface.py
DAV/locks.py
DAV/propfind.py
DAV/propfind.py.orig
+DAV/report.py
DAV/status.py
DAV/utils.py
DAVServer/__init__.py
@@ -26,6 +27,7 @@ DAVServer/config.ini
DAVServer/daemonize.py
DAVServer/fileauth.py
DAVServer/fshandler.py
+DAVServer/magic.py
DAVServer/mysqlauth.py
DAVServer/server.py
PyWebDAV.egg-info/PKG-INFO
diff --git a/VERSION b/VERSION
index 2003b63..965065d 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.9.2
+0.9.3
diff --git a/doc/Changes b/doc/Changes
index 0eb5f39..2c53012 100644
--- a/doc/Changes
+++ b/doc/Changes
@@ -1,4 +1,46 @@
+0.9.3 (July 2 2009)
+--
+
+- Setting WebDAV v2 as default because LOCK and UNLOCK seem
+ to be stable by now. -J parameter is ignored and will go away.
+ [Simon Pamies]
+
+- Fix for PROPFIND to return *all* properties
+ [Cedric Krier]
+
+- Fixed do_PUT initialisation
+ [Cedric Krier]
+
+- Added REPORT support
+ [Cedric Krier]
+
+- Added support for gzip encoding
+ [Cedric Krier]
+
+- Fix for wrong --port option
+ [Martin Wendt]
+
+- Handle paths correctly for Windows related env
+ [Martin Wendt]
+
+- Included mimetype check for files
+ based on magic.py from Jason Petrone. Included
+ magic.py into this package. All magic.py code
+ (c) 2000 Jason Petrone. Included from
+ http://www.jsnp.net/code/magic.py.
+ [Joerg Friedrich, Simon Pamies]
+
+- Status check not working when server is running
+ [Joerg Friedrich]
+
+- Fixed wrong time formatting for Last-Modified
+ and creationdate (must follow RFC 822 and 3339)
+ [Cedric Krier]
+
+0.9.2 (May 11 2009)
+--
+
- Fixed COPY, MOVE, DELETE to support locked
resources
[Simon Pamies]
@@ -19,23 +61,23 @@
[Jesus Cea]
- Make propfind respect the depth from queries
- [Cédric Krier]
+ [Cedric Krier]
- Add ETag in the header of GET. This is needed to implement
GroupDAV, CardDAV and CalDAV.
- [Cédric Krier]
+ [Cedric Krier]
- Handle the "Expect 100-continue" header
- [Cédric Krier]
+ [Cedric Krier]
- Remove debug statements and remove logging
- [Cédric Krier]
+ [Cedric Krier]
- Use the Host header in baseuri if set.
- [Cédric Krier]
+ [Cedric Krier]
- Adding If-Match on PUT and DELETE
- [Cédric Krier]
+ [Cedric Krier]
0.9.1 (May 4th 2009)
--
diff --git a/setup.py b/setup.py
index cef371b..f6149c5 100644
--- a/setup.py
+++ b/setup.py
@@ -12,12 +12,14 @@ VERSION = open('VERSION', 'r').read()
VERSION = VERSION.replace('\n', '')
DOC = """
-WebDAV library for python. Consists of a server and the DAV package that provides WebDAV functionality.
+WebDAV library for python. Consists of a server and the DAV package that provides WebDAV server(!) functionality.
Currently supports WebDAV level 1 and level 2 (LOCK, UNLOCK) making it play nice with cadaver, Mac OS X Finder, Windows Explorer or even iCal.
After installation of this package you will have a new script in you $PYTHON/bin directory called
*davserver*. This serves as the main entry point to the server.
+This package does *not* provide client functionality.
+
Example (using easy_install)::
easy_install PyWebDAV
commit 4ee2b74440080853b094474ccec1be1e672bc421
Author: Daniel Baumann <daniel at debian.org>
Date: Tue May 12 22:24:13 2009 +0200
Adding upstream version 0.9.2.
diff --git a/DAV/AuthServer.py b/DAV/AuthServer.py
index ba771e2..2058ede 100644
--- a/DAV/AuthServer.py
+++ b/DAV/AuthServer.py
@@ -77,11 +77,7 @@ class AuthRequestHandler:
Special handle method with buffering and authentication
"""
- self.infp=open("/tmp/in.%s" %self.__class__, "a+")
-
- self.infp.write("------------------------------------------------------------------------------\n")
self.raw_requestline = self.rfile.readline()
- self.infp.write(self.raw_requestline)
self.request_version = version = "HTTP/0.9" # Default
requestline = self.raw_requestline
@@ -112,7 +108,6 @@ class AuthRequestHandler:
self.command, self.path, self.request_version = command, path, version
self.headers = self.MessageClass(self.rfile, 0)
- self.infp.write(str(self.headers))
# test authentification
if self.DO_AUTH:
@@ -138,14 +133,8 @@ class AuthRequestHandler:
method = getattr(self, mname)
method()
- self.infp.flush()
- self.infp.close()
self._flush()
- def write_infp(self,s):
- self.infp.write(str(s))
- self.infp.flush()
-
def send_response(self,code, message=None):
"""Override send_response to use the correct http version
in the response."""
diff --git a/DAV/BufferingHTTPServer.py b/DAV/BufferingHTTPServer.py
index 5984d35..db96618 100644
--- a/DAV/BufferingHTTPServer.py
+++ b/DAV/BufferingHTTPServer.py
@@ -48,7 +48,6 @@ class BufferedHTTPRequestHandler(BaseHTTPRequestHandler):
this (see below)
"""
self.__buffer=""
- self.__outfp=open("/tmp/out.%s" %self.__class__,"a+")
def _append(self,s):
""" append a string to the buffer """
@@ -57,8 +56,6 @@ class BufferedHTTPRequestHandler(BaseHTTPRequestHandler):
def _flush(self):
""" flush the buffer to wfile """
self.wfile.write(self.__buffer)
- self.__outfp.write(self.__buffer)
- self.__outfp.flush()
self.wfile.flush()
self.__buffer=""
diff --git a/DAV/WebDAVServer.py b/DAV/WebDAVServer.py
index 57da08f..fdfa564 100644
--- a/DAV/WebDAVServer.py
+++ b/DAV/WebDAVServer.py
@@ -47,11 +47,14 @@ from delete import DELETE
from davcopy import COPY
from davmove import MOVE
+from utils import rfc1123_date
from string import atoi,split
-from status import STATUS_CODES
from errors import *
-class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler):
+from constants import DAV_VERSION_1, DAV_VERSION_2
+from locks import LockManager
+
+class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler, LockManager):
"""Simple DAV request handler with
- GET
@@ -83,15 +86,16 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler):
self.send_response(code,message=msg)
self.send_header("Connection", "close")
self.send_header("Accept-Ranges", "bytes")
+ self.send_header('Date', rfc1123_date())
for a,v in headers.items():
self.send_header(a,v)
if DATA:
- self.send_header("Content-Length", str(len(DATA)))
- self.send_header("Content-Type", ctype)
+ self.send_header('Content-Length', len(DATA))
+ self.send_header('Content-Type', ctype)
else:
- self.send_header("Content-Length", "0")
+ self.send_header('Content-Length', 0)
self.end_headers()
if DATA:
@@ -105,7 +109,9 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler):
self.send_header("Content-type", ctype)
self.send_header("Connection", "close")
self.send_header("Transfer-Encoding", "chunked")
+ self.send_header('Date', rfc1123_date())
self.end_headers()
+
self._append(hex(len(DATA))[2:]+"\r\n")
self._append(DATA)
self._append("\r\n")
@@ -116,80 +122,101 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler):
def do_OPTIONS(self):
"""return the list of capabilities """
+
self.send_response(200)
- self.send_header("Content-Type", "text/plain")
+ self.send_header("Content-Length", 0)
if self._config.DAV.lockemulation is True:
if self._config.DAV.verbose is True:
- print >>sys.stderr, 'Activated LOCK,UNLOCK emulation for this connection (NOT known to work currently)'
+ print >>sys.stderr, 'Activated LOCK,UNLOCK emulation (experimental)'
- self.send_header('Allow', 'GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE, LOCK, UNLOCK')
- self.send_header('DAV', '1,2')
+ self.send_header('Allow', DAV_VERSION_2['options'])
+ self.send_header('DAV', DAV_VERSION_2['version'])
else:
- self.send_header("Allow", "GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE")
- self.send_header('DAV', '1')
+ self.send_header('Allow', DAV_VERSION_1['options'])
+ self.send_header('DAV', DAV_VERSION_1['version'])
self.send_header('MS-Author-Via', 'DAV') # this is for M$
self.end_headers()
- def _init_locks(self):
- if not hasattr(self, '_lock_table'):
- self._lock_table = {}
+ def _HEAD_GET(self, with_body=False):
+ """ Returns headers and body for given resource """
- return self._lock_table
+ dc = self.IFACE_CLASS
+ uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
+ uri = urllib.unquote(uri)
- def is_locked(self, uri):
- table = self._init_locks()
- return table.has_key(uri)
+ headers = {}
- def do_LOCK(self):
- """ Locking is implemented via in-memory caches. No data is written. """
+ # 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"
- if self._config.DAV.verbose is True:
- print >>sys.stderr, 'LOCKing resource %s' % self.headers
+ # get the ETag if any
+ try:
+ headers['Etag'] = dc.get_prop(uri, "DAV:", "getetag")
+ except DAV_NotFound: pass
- # XXX this is not finished and'll be replaced soon
- token = str(random.randint(123, 12345678))
- header = {'Lock-Token' : token}
- return self.send_status(body=str(token))
+ # get the content type
+ try:
+ content_type = dc.get_prop(uri,"DAV:","getcontenttype")
+ except DAV_NotFound: content_type = "application/octet-stream"
- def do_UNLOCK(self):
- """ Always send OK with no content = Status 204 """
+ # get the data
+ try:
+ data = dc.get_data(uri)
+ except DAV_Error, (ec,dd):
+ self.send_status(ec)
+ return
- if self.DAV._config.DAV.verbose is True:
- print >>sys.stderr, 'UNLOCKing resource %s' % self.headers
+ # send the data
+ if with_body is False:
+ data = None
- return self.send_status(204)
+ self.send_body(data, '200', "OK", "OK", content_type, headers)
- def do_PROPFIND(self):
+ def do_HEAD(self):
+ """ Send a HEAD response: Retrieves resource information w/o body """
- dc=self.IFACE_CLASS
+ return self._HEAD_GET(with_body=False)
- # read the body
- body=None
- if self.headers.has_key("Content-Length"):
- l=self.headers['Content-Length']
- body=self.rfile.read(atoi(l))
- self.write_infp(body)
+ def do_GET(self):
+ """Serve a GET request."""
- # which Depth?
- if self.headers.has_key('Depth'):
- d=self.headers['Depth']
- else:
- d="infinity"
+ return self._HEAD_GET(with_body=True)
- uri=urlparse.urljoin(dc.baseuri,self.path)
- uri=urllib.unquote(uri)
- pf=PROPFIND(uri,dc,d)
+ def do_TRACE(self):
+ """ 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')
- if body:
- pf.read_propfind(body)
+ def do_POST(self):
+ """ Replacement for GET response. Not implemented here. """
+
+ self.send_body(None, '405', 'Method Not Allowed', 'Method Not Allowed')
+
+ def do_PROPFIND(self):
+ """ Retrieve properties on defined resource. """
+
+ dc = self.IFACE_CLASS
+
+ # 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'):
+ l = self.headers['Content-Length']
+ body = self.rfile.read(atoi(l))
+
+ uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
+ uri = urllib.unquote(uri)
+ pf = PROPFIND(uri, dc, self.headers.get('Depth', 'infinity'), body)
try:
- DATA=pf.createResponse()
- DATA=DATA+"\n"
+ DATA = '%s\n' % pf.createResponse()
except DAV_Error, (ec,dd):
return self.send_status(ec)
@@ -197,151 +224,203 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler):
# taken from Resource.py @ Zope webdav
if (self.headers.get('User-Agent') ==
'Microsoft Data Access Internet Publishing Provider DAV 1.1'):
- result = result.replace('<ns0:getlastmodified xmlns:ns0="DAV:">',
+ DATA = DATA.replace('<ns0:getlastmodified xmlns:ns0="DAV:">',
'<ns0:getlastmodified xmlns:n="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.rfc1123">')
- result = result.replace('<ns0:creationdate xmlns:ns0="DAV:">',
+ DATA = DATA.replace('<ns0:creationdate xmlns:ns0="DAV:">',
'<ns0:creationdate xmlns:n="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.tz">')
- self.send_body_chunks(DATA,'207','Multi-Status','Multiple responses')
+ self.send_body_chunks(DATA, '207','Multi-Status','Multiple responses')
- def do_GET(self):
- """Serve a GET request."""
+ def do_MKCOL(self):
+ """ create a new collection """
dc=self.IFACE_CLASS
- uri=urlparse.urljoin(dc.baseuri,self.path)
+ uri=urlparse.urljoin(self.get_baseuri(dc), self.path)
uri=urllib.unquote(uri)
- # get the last modified date
try:
- lm=dc.get_prop(uri,"DAV:","getlastmodified")
- except:
- lm="Sun, 01 Dec 2014 00:00:00 GMT" # dummy!
- headers={"Last-Modified":lm}
-
- # get the content type
- try:
- ct=dc.get_prop(uri,"DAV:","getcontenttype")
- except:
- ct="application/octet-stream"
-
- # get the data
- try:
- data=dc.get_data(uri)
+ dc.mkcol(uri)
+ self.send_status(201)
except DAV_Error, (ec,dd):
- self.send_status(ec)
- return
-
- # send the data
- self.send_body(data,"200","OK","OK",ct,headers)
-
- def do_HEAD(self):
- """ Send a HEAD response """
-
- dc=self.IFACE_CLASS
- uri=urlparse.urljoin(dc.baseuri,self.path)
- uri=urllib.unquote(uri)
-
- # get the last modified date
- try:
- lm=dc.get_prop(uri,"DAV:","getlastmodified")
- except:
- lm="Sun, 01 Dec 2014 00:00:00 GMT" # dummy!
-
- headers={"Last-Modified":lm}
-
- # get the content type
- try:
- ct=dc.get_prop(uri,"DAV:","getcontenttype")
- except:
- ct="application/octet-stream"
+ return self.send_status(ec)
- try:
- data=dc.get_data(uri)
- headers["Content-Length"]=str(len(data))
- except DAV_NotFound:
- self.send_body(None,"404","Not Found","")
- return
+ def do_DELETE(self):
+ """ delete an resource """
- self.send_body(None,"200","OK","OK",ct,headers)
+ dc = self.IFACE_CLASS
+ uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
+ uri = urllib.unquote(uri)
- def do_POST(self):
- self.send_error(404,"File not found")
+ # Handle If-Match
+ if self.headers.has_key('If-Match'):
+ test = False
+ etag = None
+ try:
+ etag = dc.get_prop(uri, "DAV:", "getetag")
+ except:
+ pass
+ for match in self.headers['If-Match'].split(','):
+ if match == '*':
+ if dc.exists(uri):
+ test = True
+ break
+ else:
+ if match == etag:
+ test = True
+ break
+ if not test:
+ self.send_status(412)
+ return
- def do_MKCOL(self):
- """ create a new collection """
+ # Handle If-None-Match
+ if self.headers.has_key('If-None-Match'):
+ test = True
+ etag = None
+ try:
+ etag = dc.get_prop(uri, "DAV:", "getetag")
+ except:
+ pass
+ for match in self.headers['If-None-Match'].split(','):
+ if match == '*':
+ if dc.exists(uri):
+ test = False
+ break
+ else:
+ if match == etag:
+ test = False
+ break
+ if not test:
+ self.send_status(412)
+ return
- dc=self.IFACE_CLASS
- uri=urlparse.urljoin(dc.baseuri,self.path)
- uri=urllib.unquote(uri)
- try:
- dc.mkcol(uri)
- self.send_status(200)
- except DAV_Error, (ec,dd):
- self.send_status(ec)
+ # locked resources are not allowed to delete
+ if self._l_isLocked(uri):
+ return self.send_body(None, '423', 'Locked', 'Locked')
- def do_DELETE(self):
- """ delete an resource """
- dc=self.IFACE_CLASS
- uri=urlparse.urljoin(dc.baseuri,self.path)
- uri=urllib.unquote(uri)
- dl=DELETE(uri,dc)
+ dl = DELETE(uri,dc)
if dc.is_collection(uri):
res=dl.delcol()
- else:
- res=dl.delone()
+ else: res=dl.delone()
if res:
self.send_status(207,body=res)
- else:
- self.send_status(204)
+ else: self.send_status(204)
def do_PUT(self):
dc=self.IFACE_CLASS
+ # Handle If-Match
+ if self.headers.has_key('If-Match'):
+ test = False
+ etag = None
+ try:
+ etag = dc.get_prop(uri, "DAV:", "getetag")
+ except:
+ pass
+ for match in self.headers['If-Match'].split(','):
+ if match == '*':
+ if dc.exists(uri):
+ test = True
+ break
+ else:
+ if match == etag:
+ test = True
+ break
+ if not test:
+ self.send_status(412)
+ return
+
+ # Handle If-None-Match
+ if self.headers.has_key('If-None-Match'):
+ test = True
+ etag = None
+ try:
+ etag = dc.get_prop(uri, "DAV:", "getetag")
+ except:
+ pass
+ for match in self.headers['If-None-Match'].split(','):
+ if match == '*':
+ if dc.exists(uri):
+ test = False
+ break
+ else:
+ if match == etag:
+ test = False
+ break
+ if not test:
+ self.send_status(412)
+ return
+
+ # Handle expect
+ expect = self.headers.get('Expect', '')
+ if (expect.lower() == '100-continue' and
+ self.protocol_version >= 'HTTP/1.1' and
+ self.request_version >= 'HTTP/1.1'):
+ self.send_status(100)
+ self._flush()
+
# read the body
body=None
if self.headers.has_key("Content-Length"):
l=self.headers['Content-Length']
body=self.rfile.read(atoi(l))
- uri=urlparse.urljoin(dc.baseuri,self.path)
+ uri=urlparse.urljoin(self.get_baseuri(dc), self.path)
uri=urllib.unquote(uri)
+ # locked resources are not allowed to be overwritten
+ if self._l_isLocked(uri):
+ return self.send_body(None, '423', 'Locked', 'Locked')
+
ct=None
if self.headers.has_key("Content-Type"):
ct=self.headers['Content-Type']
try:
- dc.put(uri,body,ct)
+ location = dc.put(uri,body,ct)
except DAV_Error, (ec,dd):
- self.send_status(ec)
- return
- self.send_status(201)
+ return self.send_status(ec)
+
+ headers = {}
+ if location:
+ headers['Location'] = location
+
+ try:
+ etag = dc.get_prop(location or uri, "DAV:", "getetag")
+ headers['ETag'] = etag
+ except:
+ pass
+
+ self.send_body(None, '201', 'Created', '', headers=headers)
def do_COPY(self):
""" copy one resource to another """
try:
self.copymove(COPY)
except DAV_Error, (ec,dd):
- self.send_status(ec)
+ return self.send_status(ec)
def do_MOVE(self):
""" move one resource to another """
try:
self.copymove(MOVE)
except DAV_Error, (ec,dd):
- self.send_status(ec)
+ return self.send_status(ec)
def copymove(self,CLASS):
""" common method for copying or moving objects """
dc=self.IFACE_CLASS
# get the source URI
- source_uri=urlparse.urljoin(dc.baseuri,self.path)
+ 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)
+ # 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')
+
# Overwrite?
overwrite=1
result_code=204
@@ -382,7 +461,8 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler):
return
if res:
- self.send_body_chunks(res,207,STATUS_CODES[207],STATUS_CODES[207],
+ self.send_body_chunks(res,207,self.responses[207][0],
+ self.responses[207][1],
ctype='text/xml; charset="utf-8"')
else:
self.send_status(result_code)
@@ -395,6 +475,13 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler):
def send_status(self,code=200,mediatype='text/xml; charset="utf-8"', \
msg=None,body=None):
- if not msg: msg=STATUS_CODES[code]
- self.send_body(body,code,STATUS_CODES[code],msg,mediatype)
+ if not msg: msg=self.responses[code][1]
+ self.send_body(body,code,self.responses[code][0],msg,mediatype)
+ def get_baseuri(self, dc):
+ baseuri = dc.baseuri
+ if self.headers.has_key('Host'):
+ uparts = list(urlparse.urlparse(dc.baseuri))
+ uparts[1] = self.headers['Host']
+ baseuri = urlparse.urlunparse(uparts)
+ return baseuri
diff --git a/DAV/constants.py b/DAV/constants.py
index e858ac5..2b1d796 100644
--- a/DAV/constants.py
+++ b/DAV/constants.py
@@ -1,18 +1,24 @@
-"""
-
-constants definition
-
-
-"""
-
# definition for resourcetype
COLLECTION=1
OBJECT=None
-DAV_PROPS=['creationdate', 'displayname', 'getcontentlanguage', 'getcontentlength', 'getcontenttype', 'getetag', 'getlastmodified', 'lockdiscovery', 'resourcetype', 'source', 'supportedlock']
+# attributes for resources
+DAV_PROPS=['creationdate', 'displayname', 'getcontentlanguage', 'getcontentlength', 'getcontenttype', 'getetag', 'getlastmodified', 'lockdiscovery', 'resourcetype', 'source', 'supportedlock']
# Request classes in propfind
-
RT_ALLPROP=1
RT_PROPNAME=2
RT_PROP=3
+
+# server mode
+DAV_VERSION_1 = {
+ 'version' : '1',
+ 'options' :
+ 'GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE'
+}
+
+DAV_VERSION_2 = {
+ 'version' : '1,2',
+ 'options' :
+ DAV_VERSION_1['options'] + ', LOCK, UNLOCK'
+}
diff --git a/DAV/davcmd.py b/DAV/davcmd.py
index 249b88a..cd2f418 100644
--- a/DAV/davcmd.py
+++ b/DAV/davcmd.py
@@ -169,7 +169,6 @@ def copytree(dc,src,dst,overwrite=None):
esrc=replace(element,src,"")
actual_dst=dpath+esrc
- print actual_dst
# now copy stuff
try:
copy(dc,element,actual_dst)
diff --git a/DAV/delete.py b/DAV/delete.py
index ed017e5..4f3e4eb 100644
--- a/DAV/delete.py
+++ b/DAV/delete.py
@@ -26,7 +26,6 @@ import string
import urllib
from StringIO import StringIO
-from status import STATUS_CODES
from utils import gen_estring, quote_uri, make_xmlresponse
from davcmd import deltree
diff --git a/DAV/iface.py b/DAV/iface.py
index 1d06f16..5f3bda7 100644
--- a/DAV/iface.py
+++ b/DAV/iface.py
@@ -7,6 +7,8 @@ class.
"""
+from xml.dom import minidom
+from locks import LockManager
from errors import *
import time
@@ -93,12 +95,36 @@ class dav_interface:
def put(self,uri,data):
""" write an object to the repository
- return a result code or raise an exception
+ return the location uri or raise an exception
"""
raise DAV_Forbidden
###
+ ### LOCKing information
+ ###
+ def _get_dav_supportedlock(self, uri):
+
+ txt = ('<main xmlns:D="http://dummy/d" xmlns:ns1="http://webdav.de/ns1"><D:lockentry>\n'
+ '<D:lockscope><D:exclusive></D:exclusive></D:lockscope>\n'
+ '<D:locktype><D:write></D:write></D:locktype>\n'
+ '</D:lockentry></main>\n')
+ xml = minidom.parseString(txt)
+ return xml.firstChild.firstChild
+
+ def _get_dav_lockdiscovery(self, uri):
+ lcm = LockManager()
+ if lcm._l_isLocked(uri):
+ lock = lcm._l_getLockForUri(uri)
+ txt = lock.asXML(discover=True, namespace='D')
+
+ txtwithns = '<main xmlns:D="http://dummy/D" xmlns:n="http://webdav.de/N">%s</main>'
+ xml = minidom.parseString(txtwithns % txt)
+ return xml.firstChild.firstChild
+
+ return ''
+
+ ###
### Methods for DAV properties
###
diff --git a/DAV/locks.py b/DAV/locks.py
new file mode 100644
index 0000000..0badcc9
--- /dev/null
+++ b/DAV/locks.py
@@ -0,0 +1,240 @@
+
+import os
+import sys
+import time
+import socket
+import string
+import posixpath
+import base64
+import urlparse
+import urllib
+import random
+
+from xml.dom import minidom
+
+from utils import rfc1123_date, IfParser, tokenFinder
+from string import atoi,split
+from errors import *
+
+tokens_to_lock = {}
+uris_to_token = {}
+
+class LockManager:
+ """ Implements the locking backend and serves as MixIn for DAVRequestHandler """
+
+ def _init_locks(self):
+ return tokens_to_lock, uris_to_token
+
+ def _l_isLocked(self, uri):
+ tokens, uris = self._init_locks()
+ return uris.has_key(uri)
+
+ def _l_hasLock(self, token):
+ tokens, uris = self._init_locks()
+ return tokens.has_key(token)
+
+ def _l_getLockForUri(self, uri):
+ tokens, uris = self._init_locks()
+ return uris.get(uri, None)
+
+ def _l_getLock(self, token):
+ tokens, uris = self._init_locks()
+ return tokens.get(token, None)
+
+ def _l_delLock(self, token):
+ tokens, uris = self._init_locks()
+ if tokens.has_key(token):
+ del uris[tokens[token].uri]
+ del tokens[token]
+
+ def _l_setLock(self, lock):
+ tokens, uris = self._init_locks()
+ tokens[lock.token] = lock
+ uris[lock.uri] = lock
+
+ def _lock_unlock_parse(self, body):
+ doc = minidom.parseString(body)
+
+ data = {}
+ info = doc.getElementsByTagNameNS('DAV:', 'lockinfo')[0]
+ data['lockscope'] = info.getElementsByTagNameNS('DAV:', 'lockscope')[0]\
+ .firstChild.localName
+ data['locktype'] = info.getElementsByTagNameNS('DAV:', 'locktype')[0]\
+ .firstChild.localName
+ data['lockowner'] = info.getElementsByTagNameNS('DAV:', 'owner')
+ return data
+
+ def _lock_unlock_create(self, uri, creator, depth, data):
+ lock = LockItem(uri, creator, **data)
+ iscollection = uri[-1] == '/' # very dumb collection check
+
+ result = ''
+ if depth == 'infinity' and iscollection:
+ # locking of children/collections not yet supported
+ pass
+
+ if not self._l_isLocked(uri):
+ self._l_setLock(lock)
+
+ # because we do not handle children we leave result empty
+ return lock.token, result
+
+ def do_UNLOCK(self):
+ """ Unlocks given resource """
+
+ dc = self.IFACE_CLASS
+
+ if self._config.DAV.verbose is True:
+ print >>sys.stderr, 'UNLOCKing resource %s' % self.headers
+
+ uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
+ uri = urllib.unquote(uri)
+
+ token = tokenFinder(self.headers.get('Lock-Token'))
+ if self._l_isLocked(uri):
+ self._l_delLock(token)
+
+ self.send_body(None, '204', 'Ok', 'Ok')
+
+ def do_LOCK(self):
+ """ Locking is implemented via in-memory caches. No data is written to disk. """
+
+ dc = self.IFACE_CLASS
+
+ if self._config.DAV.verbose is True:
+ print >>sys.stderr, 'LOCKing resource %s' % self.headers
+
+ body = None
+ if self.headers.has_key('Content-Length'):
+ l = self.headers['Content-Length']
+ body = self.rfile.read(atoi(l))
+
+ depth = self.headers.get('Depth', 'infinity')
+
+ uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
+ uri = urllib.unquote(uri)
+
+ ifheader = self.headers.get('If')
+ alreadylocked = self._l_isLocked(uri)
+
+ if body and alreadylocked:
+ # Full LOCK request but resource already locked
+ self.responses[423] = ('Locked', 'Already locked')
+ return self.send_status(423)
+
+ elif body and not ifheader:
+ # LOCK with XML information
+ data = self._lock_unlock_parse(body)
+ token, result = self._lock_unlock_create(uri, 'unknown', depth, data)
+
+ if result:
+ self.send_body(result, '207', 'Error', 'Error',
+ 'text/xml; charset="utf-8"')
+
+ else:
+ lock = self._l_getLock(token)
+ self.send_body(lock.asXML(), '200', 'OK', 'OK',
+ 'text/xml; charset="utf-8"',
+ {'Lock-Token' : 'opaquelocktoken:%s' % token})
+
+
+ else:
+ # refresh request - refresh lock timeout
+ taglist = IfParser(ifheader)
+ found = 0
+ for tag in taglist:
+ for listitem in tag.list:
+ token = tokenFinder(listitem)
+ if token and self._l_hasLock(token):
+ lock = self._l_getLock(token)
+ timeout = self.headers.get('Timeout', 'Infinite')
+ lock.setTimeout(timeout) # automatically refreshes
+ found = 1
+
+ self.send_body(lock.asXML(),
+ '200', 'OK', 'OK', 'text/xml; encoding="utf-8"')
+ break
+ if found:
+ break
+
+ # we didn't find any of the tokens mentioned - means
+ # that table was cleared or another error
+ if not found:
+ self.send_status(412) # precondition failed
+
+class LockItem:
+ """ Lock with support for exclusive write locks. Some code taken from
+ webdav.LockItem from the Zope project. """
+
+ def __init__(self, uri, creator, lockowner, depth=0, timeout='Infinite',
+ locktype='write', lockscope='exclusive', token=None, **kw):
+
+ self.uri = uri
+ self.creator = creator
+ self.owner = lockowner
+ self.depth = depth
+ self.timeout = timeout
+ self.locktype = locktype
+ self.lockscope = lockscope
+ self.token = token and token or self.generateToken()
+ self.modified = time.time()
+
+ def getModifiedTime(self):
+ return self.modified
+
+ def refresh(self):
+ self.modified = time.time()
+
+ def isValid(self):
+ now = time.time()
+ modified = self.modified
+ timeout = self.timeout
+ return (modified + timeout) > now
+
+ def generateToken(self):
+ _randGen = random.Random(time.time())
+ return '%s-%s-00105A989226:%.03f' % \
+ (_randGen.random(),_randGen.random(),time.time())
+
+ def getTimeoutString(self):
+ t = str(self.timeout)
+ if t[-1] == 'L': t = t[:-1]
+ return 'Second-%s' % t
+
+ def setTimeout(self, timeout):
+ self.timeout = timeout
+ self.modified = time.time()
+
+ def asXML(self, namespace='d', discover=False):
+ token = self.token
+ base = ('<%(ns)s:activelock>\n'
+ ' <%(ns)s:locktype><%(ns)s:%(locktype)s/></%(ns)s:locktype>\n'
+ ' <%(ns)s:lockscope><%(ns)s:%(lockscope)s/></%(ns)s:lockscope>\n'
+ ' <%(ns)s:depth>%(depth)s</%(ns)s:depth>\n'
+ ' <%(ns)s:owner>%(owner)s</%(ns)s:owner>\n'
+ ' <%(ns)s:timeout>%(timeout)s</%(ns)s:timeout>\n'
+ ' <%(ns)s:locktoken>\n'
+ ' <%(ns)s:href>opaquelocktoken:%(locktoken)s</%(ns)s:href>\n'
+ ' </%(ns)s:locktoken>\n'
+ ' </%(ns)s:activelock>\n'
+ ) % {
+ 'ns': namespace,
+ 'locktype': self.locktype,
+ 'lockscope': self.lockscope,
+ 'depth': self.depth,
+ 'owner': self.owner and self.owner or '',
+ 'timeout': self.getTimeoutString(),
+ 'locktoken': token,
+ }
+
+ if discover is True:
+ return base
+
+ s = """<?xml version="1.0" encoding="utf-8" ?>
+<d:prop xmlns:d="DAV:">
+ <d:lockdiscovery>
+ %s
+ </d:lockdiscovery>
+</d:prop>""" % base
+
+ return s
diff --git a/DAV/propfind.py b/DAV/propfind.py
index 5796db4..4c68cd6 100644
--- a/DAV/propfind.py
+++ b/DAV/propfind.py
@@ -1,25 +1,3 @@
-#!/usr/bin/env python
-
-"""
- python davserver
- Copyright (C) 1999 Christian Scholz (ruebe at aachen.heimat.de)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public
- License along with this library; if not, write to the Free
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-"""
-
import xml.dom.minidom
domimpl = xml.dom.minidom.getDOMImplementation()
@@ -49,8 +27,7 @@ class PROPFIND:
"""
-
- def __init__(self,uri,dataclass,depth):
+ def __init__(self,uri,dataclass,depth, body):
self.request_type=None
self.nsmap={}
self.proplist={}
@@ -63,11 +40,12 @@ class PROPFIND:
if dataclass.verbose:
print >>sys.stderr, 'PROPFIND: Depth is %s, URI is %s' % (depth, uri)
- def read_propfind(self,xml_doc):
- self.request_type,self.proplist,self.namespaces=utils.parse_propfind(xml_doc)
+ if body:
+ self.request_type, self.proplist, self.namespaces = utils.parse_propfind(body)
+ self.__has_body = True
def createResponse(self):
- """ create the multistatus response
+ """ Create the multistatus response
This will be delegated to the specific method
depending on which request (allprop, propname, prop)
@@ -81,12 +59,16 @@ class PROPFIND:
"""
+ # check if resource exists
+ if not self.__dataclass.exists(self.__uri):
+ raise DAV_NotFound
+
df = None
if self.request_type==RT_ALLPROP:
df = self.create_allprop()
if self.request_type==RT_PROPNAME:
- df = self.create_propname()
+ df = self.create_propname()
if self.request_type==RT_PROP:
df = self.create_prop()
@@ -118,10 +100,10 @@ class PROPFIND:
re=self.mk_propname_response(self.__uri,pnames, doc)
ms.appendChild(re)
- for newuri in dc.get_childs(self.__uri):
- pnames=dc.get_propnames(newuri)
- re=self.mk_propname_response(newuri,pnames, doc)
- ms.appendChild(re)
+ for newuri in dc.get_childs(self.__uri):
+ pnames=dc.get_propnames(newuri)
+ re=self.mk_propname_response(newuri,pnames, doc)
+ ms.appendChild(re)
# *** depth=="infinity"
@@ -176,10 +158,10 @@ class PROPFIND:
res=self.mk_prop_response(self.__uri,gp,bp,doc)
ms.appendChild(res)
- for newuri in self.__dataclass.get_childs(self.__uri):
- gp,bp=self.get_propvalues(newuri)
- res=self.mk_prop_response(newuri,gp,bp,doc)
- ms.appendChild(res)
+ for newuri in self.__dataclass.get_childs(self.__uri):
+ gp,bp=self.get_propvalues(newuri)
+ res=self.mk_prop_response(newuri,gp,bp,doc)
+ ms.appendChild(res)
return doc.toxml(encoding="utf-8")
@@ -252,14 +234,18 @@ class PROPFIND:
for ns in good_props.keys():
ns_prefix="ns"+str(self.namespaces.index(ns))+":"
for p,v in good_props[ns].items():
+
pe=doc.createElement(ns_prefix+str(p))
- if p=="resourcetype":
- if v=="1":
- ve=doc.createElement("D:collection")
- pe.appendChild(ve)
+ if hasattr(v, '__class__') and v.__class__.__name__ == 'Element':
+ pe.appendChild(v)
else:
- ve=doc.createTextNode(str(v))
- pe.appendChild(ve)
+ if p=="resourcetype":
+ if v=="1":
+ ve=doc.createElement("D:collection")
+ pe.appendChild(ve)
+ else:
+ ve=doc.createTextNode(str(v))
+ pe.appendChild(ve)
gp.appendChild(pe)
@@ -316,7 +302,11 @@ class PROPFIND:
ec = 0
try:
r=ddc.get_prop(uri,ns,prop)
- good_props[ns][prop]=str(r)
+
+ # support for element returns
+ if hasattr(r, '__class__') and r.__class__.__name__ == 'Element':
+ good_props[ns][prop]=r
+ else: good_props[ns][prop]=str(r)
except DAV_Error, error_code:
ec=error_code[0]
diff --git a/DAV/status.py b/DAV/status.py
index 47e1b32..f719987 100644
--- a/DAV/status.py
+++ b/DAV/status.py
@@ -7,6 +7,7 @@ status codes for DAV services
STATUS_CODES={
+ 100: "Continue",
102: "Processing",
200: "Ok",
201: "Created",
diff --git a/DAV/utils.py b/DAV/utils.py
index 45fa792..8150e5b 100755
--- a/DAV/utils.py
+++ b/DAV/utils.py
@@ -1,44 +1,29 @@
-#!/usr/bin/env python
-
-"""
-
-UTILITIES
-
-- parse a propfind request body into a list of props
-
-"""
-
+import time
+import re
from xml.dom import minidom
-from string import lower, split, atoi, joinfields
import urlparse
+from string import lower, split, atoi, joinfields
from StringIO import StringIO
from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP
-from status import STATUS_CODES
+from BaseHTTPServer import BaseHTTPRequestHandler
-VERSION = '0.9-dev'
+VERSION = '0.9.2'
AUTHOR = 'Simon Pamies <s.pamies at banality.de>'
-
def gen_estring(ecode):
""" generate an error string from the given code """
ec=atoi(str(ecode))
- if STATUS_CODES.has_key(ec):
- return "HTTP/1.1 %s %s" %(ec,STATUS_CODES[ec])
+ if BaseHTTPRequestHandler.responses.has_key(ec):
+ return "HTTP/1.1 %s %s" %(ec, BaseHTTPRequestHandler.responses[ec][0])
else:
return "HTTP/1.1 %s" %(ec)
def parse_propfind(xml_doc):
- """ parse an propfind xml file and return a list of props
-
- returns:
-
- request_type -- ALLPROP, PROPNAME, PROP
- proplist -- list of properties found
- namespaces -- list of namespaces found
-
+ """
+ Parse an propfind xml file and return a list of props
"""
doc = minidom.parseString(xml_doc)
@@ -146,3 +131,97 @@ def make_xmlresponse(result):
doc.documentElement.appendChild(re)
return doc.toxml(encoding="utf-8")
+
+# taken from App.Common
+
+weekday_abbr = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+weekday_full = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
+ 'Friday', 'Saturday', 'Sunday']
+monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+
+def rfc1123_date(ts=None):
+ # Return an RFC 1123 format date string, required for
+ # use in HTTP Date headers per the HTTP 1.1 spec.
+ # 'Fri, 10 Nov 2000 16:21:09 GMT'
+ if ts is None: ts=time.time()
+ year, month, day, hh, mm, ss, wd, y, z = time.gmtime(ts)
+ return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (weekday_abbr[wd],
+ day, monthname[month],
+ year,
+ hh, mm, ss)
+def iso8601_date(ts=None):
+ # Return an ISO 8601 formatted date string, required
+ # for certain DAV properties.
+ # '2000-11-10T16:21:09-08:00
+ if ts is None: ts=time.time()
+ return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(ts))
+
+def rfc850_date(ts=None):
+ # Return an HTTP-date formatted date string.
+ # 'Friday, 10-Nov-00 16:21:09 GMT'
+ if ts is None: ts=time.time()
+ year, month, day, hh, mm, ss, wd, y, z = time.gmtime(ts)
+ return "%s, %02d-%3s-%2s %02d:%02d:%02d GMT" % (
+ weekday_full[wd],
+ day, monthname[month],
+ str(year)[2:],
+ hh, mm, ss)
+
+### If: header handling support. IfParser returns a sequence of
+### TagList objects in the order they were parsed which can then
+### be used in WebDAV methods to decide whether an operation can
+### proceed or to raise HTTP Error 412 (Precondition failed)
+IfHdr = re.compile(
+ r"(?P<resource><.+?>)?\s*\((?P<listitem>[^)]+)\)"
+ )
+
+ListItem = re.compile(
+ r"(?P<not>not)?\s*(?P<listitem><[a-zA-Z]+:[^>]*>|\[.*?\])",
+ re.I)
+
+class TagList:
+ def __init__(self):
+ self.resource = None
+ self.list = []
+ self.NOTTED = 0
+
+def IfParser(hdr):
+ out = []
+ i = 0
+ while 1:
+ m = IfHdr.search(hdr[i:])
+ if not m: break
+
+ i = i + m.end()
+ tag = TagList()
+ tag.resource = m.group('resource')
+ if tag.resource: # We need to delete < >
+ tag.resource = tag.resource[1:-1]
+ listitem = m.group('listitem')
+ tag.NOTTED, tag.list = ListParser(listitem)
+ out.append(tag)
+
+ return out
+
+def tokenFinder(token):
+ # takes a string like '<opaquelocktoken:afsdfadfadf> and returns the token
+ # part.
+ if not token: return None # An empty string was passed in
+ if token[0] == '[': return None # An Etag was passed in
+ if token[0] == '<': token = token[1:-1]
+ return token[token.find(':')+1:]
+
+def ListParser(listitem):
+ out = []
+ NOTTED = 0
+ i = 0
+ while 1:
+ m = ListItem.search(listitem[i:])
+ if not m: break
+
+ i = i + m.end()
+ out.append(m.group('listitem'))
+ if m.group('not'): NOTTED = 1
+
+ return NOTTED, out
diff --git a/DAVServer/fshandler.py b/DAVServer/fshandler.py
index b6a0f65..df6adaa 100644
--- a/DAVServer/fshandler.py
+++ b/DAVServer/fshandler.py
@@ -184,7 +184,8 @@ class FilesystemHandler(dav_interface):
def mkcol(self,uri):
""" create a new collection """
path=self.uri2local(uri)
- # remove leading slash
+
+ # remove trailing slash
if path[-1]=="/": path=path[:-1]
# test if file already exists
diff --git a/DAVServer/server.py b/DAVServer/server.py
index a018c8b..0b2801b 100755
--- a/DAVServer/server.py
+++ b/DAVServer/server.py
@@ -115,10 +115,8 @@ Parameters:
-m, --mysql Pass this parameter if you want MySQL based authentication.
If you want to use MySQL then the usage of a configuration
file is mandatory.
- -J, --lockemu Activate experimental lock and unlock emulation. Currently
- no real locking is done. It is only to satisfy clients
- needing DAV 2 compliant server for read/write access
- (Mac OS X Finder).
+ -J, --lockemu Activate experimental LOCK and UNLOCK mode (WebDAV Version 2).
+ Currently know to work but needs more tests.
-i, --icounter If you want to run multiple instances then you have to
give each instance it own number so that logfiles and such
can be identified. Default is 0
diff --git a/PKG-INFO b/PKG-INFO
index 3cf5c0e..d95eda0 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: PyWebDAV
-Version: 0.9.1
+Version: 0.9.2
Summary: WebDAV library including a standalone server for python
Home-page: http://code.google.com/p/pywebdav/
Author: Simon Pamies
@@ -8,13 +8,24 @@ Author-email: spamsch at gmail.com
License: GPL v2
Description:
WebDAV library for python. Consists of a server and the DAV package that provides WebDAV functionality.
+ Currently supports WebDAV level 1 and level 2 (LOCK, UNLOCK) making it play nice with cadaver, Mac OS X Finder, Windows Explorer or even iCal.
+
After installation of this package you will have a new script in you $PYTHON/bin directory called
*davserver*. This serves as the main entry point to the server.
- Example ::
- davserver -D /home/files -n
+ Example (using easy_install)::
+
+ easy_install PyWebDAV
+ davserver -D /tmp -n
+
+ Example (unpacking file locally)::
+
+ tar xvzf PyWebDAV-$VERSION.tar.gz
+ cd pywebdav
+ python setup.py develop
+ davserver -D /tmp -n
- For more information go to http://code.google.com/p/pywebdav/
+ For more information: http://code.google.com/p/pywebdav/
Keywords: webdav,server,dav,standalone,library,gpl,http,rfc2518,rfc 2518
Platform: Unix
diff --git a/PyWebDAV.egg-info/PKG-INFO b/PyWebDAV.egg-info/PKG-INFO
index 3cf5c0e..d95eda0 100644
--- a/PyWebDAV.egg-info/PKG-INFO
+++ b/PyWebDAV.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: PyWebDAV
-Version: 0.9.1
+Version: 0.9.2
Summary: WebDAV library including a standalone server for python
Home-page: http://code.google.com/p/pywebdav/
Author: Simon Pamies
@@ -8,13 +8,24 @@ Author-email: spamsch at gmail.com
License: GPL v2
Description:
WebDAV library for python. Consists of a server and the DAV package that provides WebDAV functionality.
+ Currently supports WebDAV level 1 and level 2 (LOCK, UNLOCK) making it play nice with cadaver, Mac OS X Finder, Windows Explorer or even iCal.
+
After installation of this package you will have a new script in you $PYTHON/bin directory called
*davserver*. This serves as the main entry point to the server.
- Example ::
- davserver -D /home/files -n
+ Example (using easy_install)::
+
+ easy_install PyWebDAV
+ davserver -D /tmp -n
+
+ Example (unpacking file locally)::
+
+ tar xvzf PyWebDAV-$VERSION.tar.gz
+ cd pywebdav
+ python setup.py develop
+ davserver -D /tmp -n
- For more information go to http://code.google.com/p/pywebdav/
+ For more information: http://code.google.com/p/pywebdav/
Keywords: webdav,server,dav,standalone,library,gpl,http,rfc2518,rfc 2518
Platform: Unix
diff --git a/PyWebDAV.egg-info/SOURCES.txt b/PyWebDAV.egg-info/SOURCES.txt
index ce7f1ae..4c55b7f 100644
--- a/PyWebDAV.egg-info/SOURCES.txt
+++ b/PyWebDAV.egg-info/SOURCES.txt
@@ -16,6 +16,7 @@ DAV/dbconn.py
DAV/delete.py
DAV/errors.py
DAV/iface.py
+DAV/locks.py
DAV/propfind.py
DAV/propfind.py.orig
DAV/status.py
diff --git a/README b/README
index 22bc1af..cfcd9be 100644
--- a/README
+++ b/README
@@ -1,129 +1,92 @@
+PyWebDAV is a standards compliant WebDAV server and library written in Python
-Python WebDAV implementation.
-A working WebDAV Server is included in the PyDAVServer package. Feel free to
-extend it to your needs. Please send suggestions and bugfixes to the author(s).
+DESCRIPTION
+-----------
-LICENSE
--------
+Python WebDAV implementation (level 1 and 2) that features a library that enables you
+to integrate WebDAV server capabilities to your application
-see doc/LICENSE file
+A fully working example on how to use the library is included. You can find the server in the DAVServer package. Upon installation a script called davserver is created in your $PYTHON/bin directory.
+If you search an easy to use WebDAV server that supports most clients (cadaver, Mac OS X Finder, Windows Explorer, ...) then try out PyWebDAV.
-AUTHOR(s)
-------
-
-Simon Pamies (also current maintainer)
-Bielefeld, Germany
-s.pamies at banality.de
-
-Christian Scholz
-Aachen, Germany
-mrtopf at webdav.de
-Vince Spicer
-Ontario, Canada
-vince at vince.ca
+INSTALLATION
+------------
+Installation and setup of server can be as easy as follows:
-REQUIREMENTS
-------------
+$ easy_install PyWebDAV
+$ davserver -D /tmp -n -J
-- Python 2.0 or higher (www.python.org)
-- PyXML 0.66 (pyxml.sourceforge.net)
+If you're living on the bleeding edge then check out the sourcecode from
+http://code.google.com/p/pywebdav/source/checkout
-OPTIONAL
---------
+After having downloaded code simply install a development egg:
-- MySQLdb (http://sourceforge.net/projects/mysql-python)
- - Mysql server 4.0+ for Mysql authentication with
- with read/write access to one database
+$ svn co svn checkout http://pywebdav.googlecode.com/svn/trunk/ pywebdav
+$ cd pywebdav
+$ python setup.py develop
+$ davserver --help
-INSTALLATION
-------------
+Send patches to http://code.google.com/p/pywebdav/issues/list
-see doc/INSTALL file
+If you want to use the library then have a look at the DAVServer package that
+holds all code for a full blown server. Also doc/ARCHITECURE has information for you.
-What is WebDAV?
----------------
-http://www.webdav.org.
-http://www.ietf.org/rfc/rfc2518.txt
+QUESTIONS?
+---------
-What is Python?
----------------
+Ask here http://groups.google.com/group/pywebdav
+or send an email to the maintainer.
-http://www.python.org.
-http://diveintopython.org/
-Contents
---------
+REQUIREMENTS
+------------
-Here is a little overview of the package:
+- Python 2.4 or higher (www.python.org)
+- PyXML 0.66 (pyxml.sourceforge.net)
-A. In the DAV/ 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.
+LICENSE
+-------
- 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.
+General Public License v2
+see doc/LICENSE
- 2. AuthHTTPServer
-
- This works on top of either the BasicHTTPServer or the
- BufferingHTTPServer and implements basic authentication.
- 3. 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).
+AUTHOR(s)
+------
-B. In the PyDAVServer directory:
+Simon Pamies [*]
+Bielefeld, Germany
+s.pamies at banality.de
- 1. server.py
- Main file for server. Serves as a start point for the WebDAV server
+Christian Scholz
+Aachen, Germany
+mrtopf at webdav.de
- 2. fshandler.py
- Backend for the DAV server. Makes him serving content from the filesystem.
- Have a deeper look at it if you want to implement backends to other data sources
+Vince Spicer
+Ontario, Canada
+vince at vince.ca
- 3. fileauth.py
- Handler for authentication. Nothing very special about it.
+[*]: Current Maintainer
- 4. dbconn.py
- Mysql Database Handler.
- 5. INI_Parse.py
- Parses the config.ini file.
+OPTIONAL
+--------
- 6. config.ini
- PyWebDav configuration file.
+- MySQLdb (http://sourceforge.net/projects/mysql-python)
+ - Mysql server 4.0+ for Mysql authentication with
+ with read/write access to one database
NOTES
-----
-Right now some parts are missing like handling of ALLPROP and PROPNAMES as
-the whole proppatch method. Also not everything is tested and the return
-codes might not be the right ones in every case.
-
-I plan to adjust things in order to work with my groupware project which
-I then will release (actually it's my thesis I am working on).
-
-Look inside the file TODO for things which needs to be done and will be done
+Look inside the file doc/TODO for things which needs to be done and will be done
in the near future.
-If you find bugs (many!) or have suggestions then please email the maintainer
-
-Thanks :)
-
-
-Windows Notes
--------------
-
-This version can not be run under windows.
-
+Have a look at doc/ARCHITECTURE to understand what's going on under the hood
diff --git a/VERSION b/VERSION
index f374f66..2003b63 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.9.1
+0.9.2
diff --git a/doc/ARCHITECTURE b/doc/ARCHITECTURE
index 39348b1..b683365 100644
--- a/doc/ARCHITECTURE
+++ b/doc/ARCHITECTURE
@@ -1,3 +1,52 @@
+
+OVERVIEW
+--------
+
+Here is a little overview of the package:
+
+A. In the DAV/ 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
+
+ This works on top of either the BasicHTTPServer or the
+ BufferingHTTPServer and implements basic authentication.
+
+ 3. 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:
+
+ 1. server.py
+ Main file for server. Serves as a start point for the WebDAV server
+
+ 2. fshandler.py
+ Backend for the DAV server. Makes him serving content from the filesystem.
+ Have a deeper look at it if you want to implement backends to other data sources
+
+ 3. fileauth.py
+ Handler for authentication. Nothing very special about it.
+
+ 4. dbconn.py
+ Mysql Database Handler.
+
+ 5. INI_Parse.py
+ Parses the config.ini file.
+
+ 6. config.ini
+ PyWebDav configuration file.
+
+
Class Tree
----------
diff --git a/doc/Changes b/doc/Changes
index 119ab9f..0eb5f39 100644
--- a/doc/Changes
+++ b/doc/Changes
@@ -1,4 +1,42 @@
+- Fixed COPY, MOVE, DELETE to support locked
+ resources
+ [Simon Pamies]
+
+- Fixed PROPFIND to return 404 for non existing
+ objects and also reduce property bloat
+ [Simon Pamies]
+
+- Implemented fully working LOCK and UNLOCK based
+ on in memory lock/token database. Now fully supports
+ cadaver and Mac OS X Finder.
+ [Simon Pamies]
+
+- Fixed MKCOL answer to 201
+ [Jesus Cea]
+
+- Fixed MSIE webdav headers
+ [Jesus Cea]
+
+- Make propfind respect the depth from queries
+ [Cédric Krier]
+
+- Add ETag in the header of GET. This is needed to implement
+ GroupDAV, CardDAV and CalDAV.
+ [Cédric Krier]
+
+- Handle the "Expect 100-continue" header
+ [Cédric Krier]
+
+- Remove debug statements and remove logging
+ [Cédric Krier]
+
+- Use the Host header in baseuri if set.
+ [Cédric Krier]
+
+- Adding If-Match on PUT and DELETE
+ [Cédric Krier]
+
0.9.1 (May 4th 2009)
--
diff --git a/doc/INSTALL b/doc/INSTALL
index ecce054..9e167e1 100644
--- a/doc/INSTALL
+++ b/doc/INSTALL
@@ -4,14 +4,14 @@ How to install python WebDAV server
1. Check prerequisites
+ *nix OS (including Mac OS X)
- Will not run on Windows!
+ Windows seems to work
- + Python 2.x
+ + Python >=2.4.x
+ PyXML
2. Run setup.py
- > sudo python setup.py install
+ $ python setup.py install
4. Change to the PyDAVServer directory and start server with
diff --git a/doc/TODO b/doc/TODO
index 8fcde44..fa66e2a 100644
--- a/doc/TODO
+++ b/doc/TODO
@@ -25,13 +25,7 @@ PROPFIND
GET
---
-- guessing type?
-
-
-HEAD
-----
-
-to be done
+- guessing mimetype?
PROPPATCH
@@ -40,10 +34,3 @@ PROPPATCH
to be done (not that important as only DAV props are supported right now which
you cannot change)
-data.py
--------
-
-- implement proppatch? As example we might use some sort of database for
- storing.. (to much trouble for a simple example? Better show it with
- the groupware stuff?)
-
diff --git a/setup.py b/setup.py
index abc91bb..cef371b 100644
--- a/setup.py
+++ b/setup.py
@@ -13,13 +13,24 @@ VERSION = VERSION.replace('\n', '')
DOC = """
WebDAV library for python. Consists of a server and the DAV package that provides WebDAV functionality.
+Currently supports WebDAV level 1 and level 2 (LOCK, UNLOCK) making it play nice with cadaver, Mac OS X Finder, Windows Explorer or even iCal.
+
After installation of this package you will have a new script in you $PYTHON/bin directory called
*davserver*. This serves as the main entry point to the server.
-Example ::
- davserver -D /home/files -n
+Example (using easy_install)::
+
+ easy_install PyWebDAV
+ davserver -D /tmp -n
+
+Example (unpacking file locally)::
+
+ tar xvzf PyWebDAV-$VERSION.tar.gz
+ cd pywebdav
+ python setup.py develop
+ davserver -D /tmp -n
-For more information go to http://code.google.com/p/pywebdav/
+For more information: http://code.google.com/p/pywebdav/
"""
from distutils.core import setup
commit 3c58ac269eb29a220955396df6f319b8281b7c31
Author: Daniel Baumann <daniel at debian.org>
Date: Tue May 12 21:53:53 2009 +0200
Adding upstream version 0.9.1.
diff --git a/DAV/AuthServer.py b/DAV/AuthServer.py
old mode 100755
new mode 100644
index 131da36..ba771e2
--- a/DAV/AuthServer.py
+++ b/DAV/AuthServer.py
@@ -121,7 +121,9 @@ class AuthRequestHandler:
m,up=string.split(a)
up2=base64.decodestring(up)
user,pw=string.split(up2,":")
- if not self.get_userinfo(user,pw):
+
+ # 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")
@@ -209,9 +211,9 @@ class AuthRequestHandler:
for l in lines:
self._append("%s\r\n" %l)
- def get_userinfo(self,user):
- """ return the password of the user
- Override this class to return the right password
+ def get_userinfo(self,user, password, command):
+ """Checks if the given user and the given
+ password are allowed to access.
"""
# Always reject
diff --git a/DAV/INI_Parse.py b/DAV/INI_Parse.py
new file mode 100644
index 0000000..03cdff8
--- /dev/null
+++ b/DAV/INI_Parse.py
@@ -0,0 +1,39 @@
+#!/usr/bin/python
+
+from ConfigParser import SafeConfigParser
+
+class Configuration:
+ def __init__ (self, fileName):
+ cp = SafeConfigParser()
+ cp.read(fileName)
+ self.__parser = cp
+ self.fileName = fileName
+
+ def __getattr__ (self, name):
+ if name in self.__parser.sections():
+ return Section(name, self.__parser)
+ else:
+ return None
+
+ def __str__ (self):
+ p = self.__parser
+ result = []
+ result.append('<Configuration from %s>' % self.fileName)
+ for s in p.sections():
+ result.append('[%s]' % s)
+ for o in p.options(s):
+ result.append('%s=%s' % (o, p.get(s, o)))
+ return '\n'.join(result)
+
+class Section:
+ def __init__ (self, name, parser):
+ self.name = name
+ self.__parser = parser
+ def __getattr__ (self, name):
+ return self.__parser.get(self.name, name)
+
+# Test
+if __name__ == '__main__':
+ c = Configuration('Importador.ini')
+ print c.Origem.host, c.Origem.port
+
\ No newline at end of file
diff --git a/DAV/WebDAVServer.py b/DAV/WebDAVServer.py
index 6d89916..57da08f 100644
--- a/DAV/WebDAVServer.py
+++ b/DAV/WebDAVServer.py
@@ -40,6 +40,7 @@ import base64
import AuthServer
import urlparse
import urllib
+import random
from propfind import PROPFIND
from delete import DELETE
@@ -61,6 +62,10 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler):
- PROPPATCH
- MKCOL
+ experimental
+ - LOCK
+ - UNLOCK
+
It uses the resource/collection classes for serving and
storing content.
@@ -107,16 +112,56 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler):
self._append("0\r\n")
self._append("\r\n")
- ### HTTP METHODS
+ ### HTTP METHODS called by the server
def do_OPTIONS(self):
"""return the list of capabilities """
self.send_response(200)
- self.send_header("Allow", "GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE")
self.send_header("Content-Type", "text/plain")
- self.send_header("DAV", "1")
+
+ if self._config.DAV.lockemulation is True:
+ if self._config.DAV.verbose is True:
+ print >>sys.stderr, 'Activated LOCK,UNLOCK emulation for this connection (NOT known to work currently)'
+
+ self.send_header('Allow', 'GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE, LOCK, UNLOCK')
+ self.send_header('DAV', '1,2')
+
+ else:
+ self.send_header("Allow", "GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE")
+ self.send_header('DAV', '1')
+
+ self.send_header('MS-Author-Via', 'DAV') # this is for M$
self.end_headers()
+ def _init_locks(self):
+ if not hasattr(self, '_lock_table'):
+ self._lock_table = {}
+
+ return self._lock_table
+
+ def is_locked(self, uri):
+ table = self._init_locks()
+ return table.has_key(uri)
+
+ def do_LOCK(self):
+ """ Locking is implemented via in-memory caches. No data is written. """
+
+ if self._config.DAV.verbose is True:
+ print >>sys.stderr, 'LOCKing resource %s' % self.headers
+
+ # XXX this is not finished and'll be replaced soon
+ token = str(random.randint(123, 12345678))
+ header = {'Lock-Token' : token}
+ return self.send_status(body=str(token))
+
+ def do_UNLOCK(self):
+ """ Always send OK with no content = Status 204 """
+
+ if self.DAV._config.DAV.verbose is True:
+ print >>sys.stderr, 'UNLOCKing resource %s' % self.headers
+
+ return self.send_status(204)
+
def do_PROPFIND(self):
dc=self.IFACE_CLASS
@@ -141,13 +186,23 @@ class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler):
if body:
pf.read_propfind(body)
+
try:
DATA=pf.createResponse()
DATA=DATA+"\n"
except DAV_Error, (ec,dd):
return self.send_status(ec)
- self.send_body_chunks(DATA,"207","Multi-Status","Multiple responses")
+ # work around MSIE DAV bug for creation and modified date
+ # taken from Resource.py @ Zope webdav
+ if (self.headers.get('User-Agent') ==
+ 'Microsoft Data Access Internet Publishing Provider DAV 1.1'):
+ result = result.replace('<ns0:getlastmodified xmlns:ns0="DAV:">',
+ '<ns0:getlastmodified xmlns:n="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.rfc1123">')
+ result = result.replace('<ns0:creationdate xmlns:ns0="DAV:">',
+ '<ns0:creationdate xmlns:n="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.tz">')
+
+ self.send_body_chunks(DATA,'207','Multi-Status','Multiple responses')
def do_GET(self):
"""Serve a GET request."""
diff --git a/DAV/davcmd.py b/DAV/davcmd.py
index 5c6be3f..249b88a 100644
--- a/DAV/davcmd.py
+++ b/DAV/davcmd.py
@@ -37,32 +37,32 @@ def deltree(dc,uri,exclude={}):
problem_uris=result.keys()
element=tlist[i-1]
- # test here, if an element is a prefix of an uri which
- # generated an error before.
- # note that we walk here from childs to parents, thus
- # we cannot delete a parent if a child made a problem.
- # (see example in 8.6.2.1)
- ok=1
- for p in problem_uris:
- if is_prefix(element,p):
- ok=None
- break
+ # test here, if an element is a prefix of an uri which
+ # generated an error before.
+ # note that we walk here from childs to parents, thus
+ # we cannot delete a parent if a child made a problem.
+ # (see example in 8.6.2.1)
+ ok=1
+ for p in problem_uris:
+ if is_prefix(element,p):
+ ok=None
+ break
- if not ok: continue
+ if not ok: continue
- # here we test for the exclude list which is the other way round!
- for p in exclude.keys():
- if is_prefix(p,element):
- ok=None
- break
+ # here we test for the exclude list which is the other way round!
+ for p in exclude.keys():
+ if is_prefix(p,element):
+ ok=None
+ break
- if not ok: continue
+ if not ok: continue
- # now delete stuff
- try:
- delone(dc,element)
- except DAV_Error, (ec,dd):
- result[element]=ec
+ # now delete stuff
+ try:
+ delone(dc,element)
+ except DAV_Error, (ec,dd):
+ result[element]=ec
return result
@@ -130,7 +130,6 @@ def copytree(dc,src,dst,overwrite=None):
"""
-
# first delete the destination resource
if overwrite and dc.exists(dst):
delres=deltree(dc,dst)
@@ -149,32 +148,33 @@ def copytree(dc,src,dst,overwrite=None):
for element in tlist:
problem_uris=result.keys()
-
- # now URIs get longer and longer thus we have
- # to test if we had a parent URI which we were not
- # able to copy in problem_uris which is the prefix
- # of the actual element. If it is, then we cannot
- # copy this as well but do not generate another error.
- ok=1
- for p in problem_uris:
- if is_prefix(p,element):
- ok=None
- break
+
+ # now URIs get longer and longer thus we have
+ # to test if we had a parent URI which we were not
+ # able to copy in problem_uris which is the prefix
+ # of the actual element. If it is, then we cannot
+ # copy this as well but do not generate another error.
+ ok=1
+ for p in problem_uris:
+ if is_prefix(p,element):
+ ok=None
+ break
if not ok: continue
- # now create the destination URI which corresponds to
- # the actual source URI. -> actual_dst
- # ("subtract" the base src from the URI and prepend the
- # dst prefix to it.)
- esrc=replace(element,src,"")
- actual_dst=dpath+esrc
-
- # now copy stuff
- try:
- copy(dc,element,actual_dst)
- except DAV_Error, (ec,dd):
- result[element]=ec
+ # now create the destination URI which corresponds to
+ # the actual source URI. -> actual_dst
+ # ("subtract" the base src from the URI and prepend the
+ # dst prefix to it.)
+ esrc=replace(element,src,"")
+ actual_dst=dpath+esrc
+
+ print actual_dst
+ # now copy stuff
+ try:
+ copy(dc,element,actual_dst)
+ except DAV_Error, (ec,dd):
+ result[element]=ec
return result
diff --git a/DAV/davcopy.py b/DAV/davcopy.py
old mode 100755
new mode 100644
index ec44931..d9e819d
--- a/DAV/davcopy.py
+++ b/DAV/davcopy.py
@@ -22,8 +22,8 @@
"""
-from xml.dom import ext
-from xml.dom.Document import Document
+import xml.dom.minidom
+domimpl = xml.dom.minidom.getDOMImplementation()
import sys
import string
@@ -108,10 +108,8 @@ class COPY:
### we might make a common method out of it)
###
- doc = Document(None)
- ms=doc.createElement("D:multistatus")
- ms.setAttribute("xmlns:D","DAV:")
- doc.appendChild(ms)
+ doc = domimpl.createDocument(None, "D:multistatus", None)
+ doc.documentElement.setAttribute("xmlns:D","DAV:")
for el,ec in result.items():
re=doc.createElement("D:response")
@@ -124,10 +122,5 @@ class COPY:
re.appendChild(hr)
re.appendChild(st)
ms.appendChild(re)
-
- sfile=StringIO()
- ext.PrettyPrint(doc,stream=sfile)
- s=sfile.getvalue()
- sfile.close()
- return s
+ return doc.toxml(encoding="utf-8")
diff --git a/DAV/davmove.py b/DAV/davmove.py
old mode 100755
new mode 100644
diff --git a/DAV/dbconn.py b/DAV/dbconn.py
new file mode 100644
index 0000000..6805b7f
--- /dev/null
+++ b/DAV/dbconn.py
@@ -0,0 +1,66 @@
+try:
+ import MySQLdb
+except ImportError:
+ pass
+
+import sys
+
+class Mconn:
+ def connect(self,username,userpasswd,host,port,db):
+ try: connection = MySQLdb.connect(host=host, port=int(port), user=username, passwd=userpasswd,db=db)
+ except MySQLdb.OperationalError, message:
+ print >>sys.stderr,"Error %d:\n%s" % (message[ 0 ], message[ 1 ] )
+ return 0
+ else:
+ self.db = connection.cursor()
+
+ return 1
+
+ def execute(self,qry):
+ if self.db:
+ try: res=self.db.execute(qry)
+ except MySQLdb.OperationalError, message:
+ print >>sys.stderr, "Error %d:\n%s" % (message[ 0 ], message[ 1 ] )
+ return 0
+
+ except MySQLdb.ProgrammingError, message:
+ print >>sys.stderr,"Error %d:\n%s" % (message[ 0 ], message[ 1 ] )
+ return 0
+
+ else:
+ print >>sys.stderr,'Query Returned '+str(res)+' results'
+ return self.db.fetchall()
+
+ def create_user(self,user,passwd):
+ qry="select * from Users where User='%s'"%(user)
+ res=self.execute(qry)
+ if not res or len(res) ==0:
+ qry="insert into Users (User,Pass) values('%s','%s')"%(user,passwd)
+ res=self.execute(qry)
+ else:
+ print >>sys.stderr, "Username already in use"
+
+ def create_table(self):
+ qry="""CREATE TABLE `Users` (
+ `uid` int(10) NOT NULL auto_increment,
+ `User` varchar(60) default NULL,
+ `Pass` varchar(60) default NULL,
+ `Write` tinyint(1) default '0',
+ PRIMARY KEY (`uid`)
+ ) ENGINE=MyISAM DEFAULT CHARSET=latin1"""
+ self.execute(qry)
+
+
+ def first_run(self,user,passwd):
+ res= self.execute('select * from Users')
+ if res or type(res)==type(()) :
+ pass
+ else:
+ self.create_table()
+ self.create_user(user,passwd)
+
+
+ def __init__(self,user,password,host,port,db):
+ self.db=0
+ self.connect(user,password,host,port,db)
+
diff --git a/DAV/delete.py b/DAV/delete.py
old mode 100755
new mode 100644
diff --git a/DAV/errors.py b/DAV/errors.py
old mode 100755
new mode 100644
diff --git a/DAV/iface.py b/DAV/iface.py
index dbe8e10..1d06f16 100644
--- a/DAV/iface.py
+++ b/DAV/iface.py
@@ -106,7 +106,7 @@ class dav_interface:
""" return the creationdate of a resource """
d=self.get_creationdate(uri)
# format it
- return time.strftime("%Y-%m-%dT%H-%M-%SZ",time.localtime(d))
+ return time.strftime("%Y-%m-%dT%H:%M:%SZ",time.localtime(d))
def _get_dav_getlastmodified(self,uri):
""" return the last modified date of a resource """
diff --git a/DAV/propfind.py b/DAV/propfind.py
old mode 100755
new mode 100644
index 9fdd981..5796db4
--- a/DAV/propfind.py
+++ b/DAV/propfind.py
@@ -21,8 +21,8 @@
"""
-from xml.dom import ext
-from xml.dom.Document import Document
+import xml.dom.minidom
+domimpl = xml.dom.minidom.getDOMImplementation()
import sys
import string
@@ -60,6 +60,9 @@ class PROPFIND:
self.__uri=uri
self.__has_body=None # did we parse a body?
+ if dataclass.verbose:
+ print >>sys.stderr, 'PROPFIND: Depth is %s, URI is %s' % (depth, uri)
+
def read_propfind(self,xml_doc):
self.request_type,self.proplist,self.namespaces=utils.parse_propfind(xml_doc)
@@ -78,49 +81,51 @@ class PROPFIND:
"""
+ df = None
if self.request_type==RT_ALLPROP:
- return self.create_allprop()
+ df = self.create_allprop()
if self.request_type==RT_PROPNAME:
- return self.create_propname()
+ df = self.create_propname()
if self.request_type==RT_PROP:
- return self.create_prop()
-
+ df = self.create_prop()
+
+ if df != None:
+ return df
+
# no body means ALLPROP!
- return self.create_allprop()
+ df = self.create_allprop()
+ return df
def create_propname(self):
""" create a multistatus response for the prop names """
dc=self.__dataclass
# create the document generator
- doc = Document(None)
- ms=doc.createElement("D:multistatus")
- ms.setAttribute("xmlns:D","DAV:")
- doc.appendChild(ms)
+ doc = domimpl.createDocument(None, "multistatus", None)
+ ms = doc.documentElement
+ ms.setAttribute("xmlns:D", "DAV:")
+ ms.tagName = 'D:multistatus'
if self.__depth=="0":
pnames=dc.get_propnames(self.__uri)
- re=self.mk_propname_response(self.__uri,pnames)
+ re=self.mk_propname_response(self.__uri,pnames, doc)
ms.appendChild(re)
elif self.__depth=="1":
pnames=dc.get_propnames(self.__uri)
- re=self.mk_propname_response(self.__uri,pnames)
+ re=self.mk_propname_response(self.__uri,pnames, doc)
ms.appendChild(re)
for newuri in dc.get_childs(self.__uri):
pnames=dc.get_propnames(newuri)
- re=self.mk_propname_response(newuri,pnames)
+ re=self.mk_propname_response(newuri,pnames, doc)
ms.appendChild(re)
+
# *** depth=="infinity"
- sfile=StringIO()
- ext.PrettyPrint(doc,stream=sfile)
- s=sfile.getvalue()
- sfile.close()
- return s
+ return doc.toxml(encoding="utf-8")
def create_allprop(self):
""" return a list of all properties """
@@ -156,10 +161,10 @@ class PROPFIND:
# create the document generator
- doc = Document(None)
- ms=doc.createElement("D:multistatus")
- ms.setAttribute("xmlns:D","DAV:")
- doc.appendChild(ms)
+ doc = domimpl.createDocument(None, "multistatus", None)
+ ms = doc.documentElement
+ ms.setAttribute("xmlns:D", "DAV:")
+ ms.tagName = 'D:multistatus'
if self.__depth=="0":
gp,bp=self.get_propvalues(self.__uri)
@@ -175,13 +180,8 @@ class PROPFIND:
gp,bp=self.get_propvalues(newuri)
res=self.mk_prop_response(newuri,gp,bp,doc)
ms.appendChild(res)
-
- sfile=StringIO()
- ext.PrettyPrint(doc,stream=sfile)
- s=sfile.getvalue()
- sfile.close()
- return s
+ return doc.toxml(encoding="utf-8")
def mk_propname_response(self,uri,propnames,doc):
""" make a new <prop> result element for a PROPNAME request
@@ -196,7 +196,7 @@ class PROPFIND:
uparts=urlparse.urlparse(uri)
fileloc=uparts[2]
href=doc.createElement("D:href")
- huri=doc.createTextNode(urllib.quote(fileloc))
+ huri=doc.createTextNode(uparts[0]+'://'+'/'.join(uparts[1:2]) + urllib.quote(fileloc))
href.appendChild(huri)
re.appendChild(href)
@@ -239,13 +239,13 @@ class PROPFIND:
uparts=urlparse.urlparse(uri)
fileloc=uparts[2]
href=doc.createElement("D:href")
- huri=doc.createTextNode(urllib.quote(fileloc))
+ huri=doc.createTextNode(uparts[0]+'://'+'/'.join(uparts[1:2]) + urllib.quote(fileloc))
href.appendChild(huri)
re.appendChild(href)
# write good properties
+ ps=doc.createElement("D:propstat")
if good_props:
- ps=doc.createElement("D:propstat")
re.appendChild(ps)
gp=doc.createElement("D:prop")
@@ -308,13 +308,14 @@ class PROPFIND:
good_props={}
bad_props={}
+ ddc = self.__dataclass
for (ns,plist) in self.proplist.items():
good_props[ns]={}
bad_props={}
- ec = 0
for prop in plist:
+ ec = 0
try:
- r=self.__dataclass.get_prop(uri,ns,prop)
+ r=ddc.get_prop(uri,ns,prop)
good_props[ns][prop]=str(r)
except DAV_Error, error_code:
ec=error_code[0]
diff --git a/DAV/propfind.py b/DAV/propfind.py.orig
old mode 100755
new mode 100644
similarity index 94%
copy from DAV/propfind.py
copy to DAV/propfind.py.orig
index 9fdd981..371d012
--- a/DAV/propfind.py
+++ b/DAV/propfind.py.orig
@@ -78,17 +78,22 @@ class PROPFIND:
"""
+ df = None
if self.request_type==RT_ALLPROP:
- return self.create_allprop()
+ df = self.create_allprop()
if self.request_type==RT_PROPNAME:
- return self.create_propname()
+ df = self.create_propname()
if self.request_type==RT_PROP:
- return self.create_prop()
+ df = self.create_prop()
+ if df != None:
+ return df
+
# no body means ALLPROP!
- return self.create_allprop()
+ df = self.create_allprop()
+ return df
def create_propname(self):
""" create a multistatus response for the prop names """
@@ -102,17 +107,17 @@ class PROPFIND:
if self.__depth=="0":
pnames=dc.get_propnames(self.__uri)
- re=self.mk_propname_response(self.__uri,pnames)
+ re=self.mk_propname_response(self.__uri,pnames, doc)
ms.appendChild(re)
elif self.__depth=="1":
pnames=dc.get_propnames(self.__uri)
- re=self.mk_propname_response(self.__uri,pnames)
+ re=self.mk_propname_response(self.__uri,pnames, doc)
ms.appendChild(re)
for newuri in dc.get_childs(self.__uri):
pnames=dc.get_propnames(newuri)
- re=self.mk_propname_response(newuri,pnames)
+ re=self.mk_propname_response(newuri,pnames, doc)
ms.appendChild(re)
# *** depth=="infinity"
@@ -244,8 +249,8 @@ class PROPFIND:
re.appendChild(href)
# write good properties
+ ps=doc.createElement("D:propstat")
if good_props:
- ps=doc.createElement("D:propstat")
re.appendChild(ps)
gp=doc.createElement("D:prop")
@@ -308,13 +313,14 @@ class PROPFIND:
good_props={}
bad_props={}
+ ddc = self.__dataclass
for (ns,plist) in self.proplist.items():
good_props[ns]={}
bad_props={}
- ec = 0
for prop in plist:
+ ec = 0
try:
- r=self.__dataclass.get_prop(uri,ns,prop)
+ r=ddc.get_prop(uri,ns,prop)
good_props[ns][prop]=str(r)
except DAV_Error, error_code:
ec=error_code[0]
diff --git a/DAV/utils.py b/DAV/utils.py
index 910619a..45fa792 100755
--- a/DAV/utils.py
+++ b/DAV/utils.py
@@ -8,11 +8,8 @@ UTILITIES
"""
-from xml.dom import ext
-from xml.dom.Document import Document
-from xml.dom.ext.reader import PyExpat
-from xml.dom import Node
-from xml.dom import NodeIterator, NodeFilter
+
+from xml.dom import minidom
from string import lower, split, atoi, joinfields
import urlparse
@@ -21,7 +18,7 @@ from StringIO import StringIO
from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP
from status import STATUS_CODES
-VERSION = '0.6'
+VERSION = '0.9-dev'
AUTHOR = 'Simon Pamies <s.pamies at banality.de>'
@@ -43,32 +40,30 @@ def parse_propfind(xml_doc):
namespaces -- list of namespaces found
"""
- doc = PyExpat.Reader().fromString(xml_doc)
- snit = doc.createNodeIterator(doc, NodeFilter.NodeFilter.SHOW_ELEMENT, None, None)
+
+ doc = minidom.parseString(xml_doc)
request_type=None
props={}
namespaces=[]
- while 1:
- curr_elem = snit.nextNode()
- if not curr_elem: break
- ename=fname=lower(curr_elem.nodeName)
- if ":" in fname:
- ename=split(fname,":")[1]
- if ename=="prop": request_type=RT_PROP; continue
- if ename=="propfind": continue
- if ename=="allprop": request_type=RT_ALLPROP; continue
- if ename=="propname": request_type=RT_PROPNAME; continue
-
- # rest should be names of attributes
-
- ns = curr_elem.namespaceURI
- if props.has_key(ns):
- props[ns].append(ename)
- else:
- props[ns]=[ename]
- namespaces.append(ns)
+ if doc.getElementsByTagNameNS("DAV:", "allprop"):
+ request_type = RT_ALLPROP
+ elif doc.getElementsByTagNameNS("DAV:", "propname"):
+ request_type = RT_PROPNAME
+ else:
+ request_type = RT_PROP
+ e = doc.getElementsByTagNameNS("DAV:", "prop")
+ for e in e[0].childNodes:
+ if e.nodeType != minidom.Node.ELEMENT_NODE:
+ continue
+ ns = e.namespaceURI
+ ename = e.localName
+ if props.has_key(ns):
+ props[ns].append(ename)
+ else:
+ props[ns]=[ename]
+ namespaces.append(ns)
return request_type,props,namespaces
@@ -135,26 +130,19 @@ def get_parenturi(uri):
def make_xmlresponse(result):
""" construct a response from a dict of uri:error_code elements """
- doc = Document.Document(None)
- ms=doc.createElement("D:multistatus")
- ms.setAttribute("xmlns:D","DAV:")
- doc.appendChild(ms)
+ doc = minidom.getDOMImplementation().createDocument(None, "D:multistatus", None)
+ doc.documentElement.setAttribute("xmlns:D","DAV:")
for el,ec in result.items():
- re=doc.createElement("D:response")
- hr=doc.createElement("D:href")
- st=doc.createElement("D:status")
+ re=doc.createElementNS("DAV:","response")
+ hr=doc.createElementNS("DAV:","href")
+ st=doc.createElementNS("DAV:","status")
huri=doc.createTextNode(quote_uri(el))
t=doc.createTextNode(gen_estring(ec))
st.appendChild(t)
hr.appendChild(huri)
re.appendChild(hr)
re.appendChild(st)
- ms.appendChild(re)
-
- sfile=StringIO()
- ext.PrettyPrint(doc,stream=sfile)
- s=sfile.getvalue()
- sfile.close()
- return s
+ doc.documentElement.appendChild(re)
+ return doc.toxml(encoding="utf-8")
diff --git a/DAVServer/__init__.py b/DAVServer/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/DAVServer/config.ini b/DAVServer/config.ini
new file mode 100644
index 0000000..47b0d60
--- /dev/null
+++ b/DAVServer/config.ini
@@ -0,0 +1,50 @@
+
+# PyWebDAV config.ini
+# Read documents before editing this file
+
+# Created 11:48 10-08-2006 By Vince Spicer
+
+
+[MySQL]
+
+# Mysql server information
+host=localhost
+port=3306
+user=root
+passwd=rootpw
+
+# Auth Database Table, Must exists in database prior to firstrun
+dbtable=webDav
+
+# Create User Database Table and Insert system user
+# Disable after the Table is created; for performance reasons
+firstrun=0
+
+[DAV]
+
+# Verbose?
+verbose = 1
+
+# main directory
+directory = /home/spamies/tmp
+
+# Server address
+port = 8081
+host = localhost
+
+# disable auth
+noauth = 0
+
+# Enable mysql auth
+mysql_auth=0
+
+# admin user
+user = test
+password = test00
+
+# daemonize?
+daemonize = 0
+daemonaction = start
+
+# instance counter
+counter = 0
diff --git a/PyDAVServer/daemonize.py b/DAVServer/daemonize.py
old mode 100755
new mode 100644
similarity index 98%
rename from PyDAVServer/daemonize.py
rename to DAVServer/daemonize.py
index 0c59417..84e527d
--- a/PyDAVServer/daemonize.py
+++ b/DAVServer/daemonize.py
@@ -112,7 +112,7 @@ def startstop(stdout='/dev/null', stderr=None, stdin='/dev/null',
if 'start' == action:
if pid:
- mess = "Start aborded since pid file '%s' exists.\n"
+ mess = "Start aborted since pid file '%s' exists.\n"
sys.stderr.write(mess % pidfile)
sys.exit(1)
diff --git a/PyDAVServer/fileauth.py b/DAVServer/fileauth.py
old mode 100755
new mode 100644
similarity index 80%
rename from PyDAVServer/fileauth.py
rename to DAVServer/fileauth.py
index 002612e..5668e50
--- a/PyDAVServer/fileauth.py
+++ b/DAVServer/fileauth.py
@@ -26,28 +26,30 @@ This is an example implementation of a DAVserver using the DAV package.
from DAV.WebDAVServer import DAVRequestHandler
from fshandler import FilesystemHandler
import sys
+from DAV.dbconn import Mconn
class DAVAuthHandler(DAVRequestHandler):
"""
- Provide real auth check based on filesystem files
+ Provides authentication based on parameters. The calling
+ class has to inject password and username into this.
+ (Variables: auth_user and auth_pass)
"""
# Do not forget to set IFACE_CLASS by caller
# ex.: IFACE_CLASS = FilesystemHandler('/tmp', 'http://localhost/')
-
verbose = False
-
+
def _log(self, message):
if self.verbose:
print >>sys.stderr, '>> (DAVAuthHandler) %s' % message
- def get_userinfo(self,user,pw):
- """ authenticate user """
+ def get_userinfo(self,user,pw,command):
+ """ authenticate user """
- if user == self.auth_user and pw == self.auth_pass:
+ if user == self._config.DAV.user and pw == self._config.DAV.password:
self._log('Successfully authenticated user %s' % user)
return 1
self._log('Authentication failed for user %s' % user)
- return None
+ return 0
diff --git a/PyDAVServer/fshandler.py b/DAVServer/fshandler.py
old mode 100755
new mode 100644
similarity index 97%
rename from PyDAVServer/fshandler.py
rename to DAVServer/fshandler.py
index f7a1d8a..b6a0f65
--- a/PyDAVServer/fshandler.py
+++ b/DAVServer/fshandler.py
@@ -78,11 +78,8 @@ class FilesystemHandler(dav_interface):
if os.path.isdir(fileloc):
try:
files=os.listdir(fileloc)
-
- # silently ignore directories
- # not allowed.
except:
- raise DAV_Forbidden
+ raise DAV_NotFound
for file in files:
newloc=os.path.join(fileloc,file)
@@ -118,10 +115,14 @@ class FilesystemHandler(dav_interface):
path=self.uri2local(uri)
if os.path.isfile(path):
return OBJECT
- else:
+
+ elif os.path.isdir(path):
return COLLECTION
+
+ raise DAV_NotFound
def _get_dav_displayname(self,uri):
+ #return uri
raise DAV_Secret # do not show
def _get_dav_getcontentlength(self,uri):
@@ -161,11 +162,10 @@ class FilesystemHandler(dav_interface):
if os.path.exists(path):
if os.path.isfile(path):
return "application/octet-stream"
- else:
+ elif os.path.isdir(path):
return "httpd/unix-directory"
- else:
- raise DAV_NotFound, 'Could not find %s' % path
+ raise DAV_NotFound, 'Could not find %s' % path
def put(self,uri,data,content_type=None):
""" put the object into the filesystem """
@@ -227,6 +227,7 @@ class FilesystemHandler(dav_interface):
if not os.system("rm -f '%s'" %path):
return 204
else:
+ self._log('rm: Forbidden')
raise DAV_Forbidden # forbidden
###
@@ -355,6 +356,7 @@ class FilesystemHandler(dav_interface):
try:
os.system("cp '%s' '%s'" %(srcfile,dstfile))
except:
+ self._log('copy: forbidden')
raise DAV_Error, Forbidden
def copycol(self,src,dst):
diff --git a/DAVServer/mysqlauth.py b/DAVServer/mysqlauth.py
new file mode 100644
index 0000000..da6249c
--- /dev/null
+++ b/DAVServer/mysqlauth.py
@@ -0,0 +1,36 @@
+from fileauth import DAVAuthHandler
+
+class MySQLAuthHandler(DAVAuthHandler):
+ """
+ Provides authentication based on a mysql table
+ """
+
+ def get_userinfo(self,user,pw,command):
+ """ authenticate user """
+
+ # Commands that need write access
+ nowrite=['OPTIONS','PROPFIND','GET']
+
+ Mysql=self._config.MySQL
+ DB=Mconn(Mysql.user,Mysql.passwd,Mysql.host,Mysql.port,Mysql.dbtable)
+ if self.verbose:
+ print >>sys.stderr,user,command
+
+ qry="select * from %s.Users where User='%s' and Pass='%s'"%(Mysql.dbtable,user,pw)
+ Auth=DB.execute(qry)
+
+ if len(Auth) == 1:
+ can_write=Auth[0][3]
+ if not can_write and not command in nowrite:
+ self._log('Authentication failed for user %s using command %s' %(user,command))
+ return 0
+ else:
+ self._log('Successfully authenticated user %s writable=%s' % (user,can_write))
+ return 1
+ else:
+ self._log('Authentication failed for user %s' % user)
+ return 0
+
+ self._log('Authentication failed for user %s' % user)
+ return 0
+
diff --git a/PyDAVServer/server.py b/DAVServer/server.py
similarity index 57%
rename from PyDAVServer/server.py
rename to DAVServer/server.py
index 552e90e..a018c8b 100755
--- a/PyDAVServer/server.py
+++ b/DAVServer/server.py
@@ -31,19 +31,15 @@ except ImportError:
print 'DAV package not found! Please install into site-packages or set PYTHONPATH!'
sys.exit(2)
-try:
- from xml.dom import ext
-except ImportError:
- print 'PyXML not found! Get it from http://pyxml.sourceforge.net/'
- sys.exit(2)
-
from DAV.utils import VERSION, AUTHOR
__version__ = VERSION
__author__ = AUTHOR
from fileauth import DAVAuthHandler
+from mysqlauth import MySQLAuthHandler
from fshandler import FilesystemHandler
from daemonize import startstop
+from DAV.INI_Parse import Configuration
def runserver(
port = 8008, host='localhost',
@@ -73,6 +69,7 @@ def runserver(
sys.exit(233)
# dispatch directory and host to the filesystem handler
+ # This handler is responsible from where to take the data
handler.IFACE_CLASS = FilesystemHandler(directory, 'http://%s:%s/' % (host, port), verbose )
# put some extra vars
@@ -81,10 +78,6 @@ def runserver(
print >>sys.stderr, '>> ATTENTION: Authentication disabled!'
handler.DO_AUTH = False
- else:
- handler.auth_user = user
- handler.auth_pass = password
-
print >>sys.stderr, '>> Serving data from %s' % directory
# initialize server on specified port
@@ -106,18 +99,31 @@ Standalone WebDAV server based on python
Usage: ./server.py [OPTIONS]
Parameters:
+ -c, --config Specify a file where configuration is specified. In this
+ file you can specify options for a running server.
+ For an example look at the config.ini in this directory.
-D, --directory Directory where to serve data from
The user that runs this server must have permissions
on that directory. NEVER run as root!
Default directory is /tmp
-
-H, --host Host where to listen on (default: localhost)
-P, --port Port to bind server to (default: 8008)
-u, --user Username for authentication
-p, --password Password for given user
- -n, --noauth Pass parameter if server should not ask for authentication
+ -n, --noauth Pass parameter if server should not ask for authentication
+ This means that every user has access
+ -m, --mysql Pass this parameter if you want MySQL based authentication.
+ If you want to use MySQL then the usage of a configuration
+ file is mandatory.
+ -J, --lockemu Activate experimental lock and unlock emulation. Currently
+ no real locking is done. It is only to satisfy clients
+ needing DAV 2 compliant server for read/write access
+ (Mac OS X Finder).
+ -i, --icounter If you want to run multiple instances then you have to
+ give each instance it own number so that logfiles and such
+ can be identified. Default is 0
-d, --daemon Make server act like a daemon. That means that it is going
- to the background mode. All messages are redirected to
+ to background mode. All messages are redirected to
logfiles (default: /tmp/pydav.log and /tmp/pydav.err).
You need to pass one of the following values to this parameter
start - Start daemon
@@ -131,8 +137,18 @@ Parameters:
Please send bug reports and feature requests to %s
""" % (__version__, __author__)
-if __name__ == '__main__':
+def setupDummyConfig(**kw):
+
+ class DummyConfigDAV:
+ def __init__(self, **kw):
+ self.__dict__.update(**kw)
+
+ class DummyConfig:
+ DAV = DummyConfigDAV(**kw)
+
+ return DummyConfig()
+def run():
verbose = False
directory = '/tmp'
port = 8008
@@ -142,17 +158,34 @@ if __name__ == '__main__':
password = ''
daemonize = False
daemonaction = 'start'
+ counter = 0
+ mysql = False
+ lockemulation = False
+ configfile = ''
# parse commandline
try:
- opts, args = getopt.getopt(sys.argv[1:], 'P:D:H:d:u:p:nvh',
- ['host=', 'port=', 'directory=', 'user=', 'password=','daemon=', 'noauth', 'help', 'verbose'])
+ opts, args = getopt.getopt(sys.argv[1:], 'P:D:H:d:u:p:nvhmJi:c:',
+ ['host=', 'port=', 'directory=', 'user=', 'password=','daemon=', 'noauth', 'help', 'verbose',
+ 'mysql', 'icounter=', 'config=', 'lockemu'])
except getopt.GetoptError, e:
print usage
print '>>>> ERROR: %s' % str(e)
sys.exit(2)
for o,a in opts:
+ if o in ['-i', '--icounter']:
+ counter = int(str(a).strip())
+
+ if o in ['-m', '--mysql']:
+ mysql = True
+
+ if o in ['-J', '--lockemu']:
+ lockemulation = True
+
+ if o in ['-c', '--config']:
+ configfile = a
+
if o in ['-D', '--directory']:
directory = a
@@ -182,24 +215,85 @@ if __name__ == '__main__':
daemonize = True
daemonaction = a
- print >>sys.stderr, 'Starting up PyWebDAV server (version %s)' % __version__
- if not noauth and daemonaction != 'status':
+ conf = None
+ if configfile != '':
+ print >>sys.stderr, 'Reading configuration from %s' % configfile
+ conf = Configuration(configfile)
+
+ dv = conf.DAV
+ verbose = bool(int(dv.verbose))
+ directory = dv.directory
+ port = dv.port
+ host = dv.host
+ noauth = bool(int(dv.noauth))
+ user = dv.user
+ password = dv.password
+ daemonize = bool(int(dv.daemonize))
+ if daemonaction != 'stop':
+ daemonaction = dv.daemonaction
+ counter = int(dv.counter)
+
+ else:
+
+ _dc = { 'verbose' : verbose,
+ 'directory' : directory,
+ 'port' : port,
+ 'host' : host,
+ 'noauth' : noauth,
+ 'user' : user,
+ 'password' : password,
+ 'daemonize' : daemonize,
+ 'daemonaction' : daemonaction,
+ 'counter' : counter,
+ 'lockemulation' : lockemulation }
+
+ conf = setupDummyConfig(**_dc)
+
+ if mysql == True and configfile == '':
+ print >>sys.stderr, '>> ERROR: You can only use MySQL with configuration file!'
+ sys.exit(3)
+
+ if daemonaction != 'stop':
+ print >>sys.stderr, 'Starting up PyWebDAV server (version %s)' % __version__
+ else:
+ print >>sys.stderr, 'Stopping PyWebDAV server (version %s)' % __version__
+
+ if not noauth and daemonaction not in ['status', 'stop']:
if not user:
- print >>sys.stderr, '>> ERROR: Please specify an username or pass parameter --noauth'
+ print usage
+ print >>sys.stderr, '>> ERROR: No usable parameter specified!'
+ print >>sys.stderr, '>> Example: davserver -D /home/files -n'
sys.exit(3)
if daemonaction == 'status':
- print >>sys.stdout, 'Checking for status...'
+ print >>sys.stdout, 'Checking for state...'
if type(port) == type(''):
port = int(port.strip())
if daemonize:
- startstop(stdout='/tmp/pydav.log',
- stderr='/tmp/pydav.err',
- pidfile='/tmp/pydav.pid',
+
+ # check if pid file exists
+ if os.path.exists('/tmp/pydav%s.pid' % counter) and daemonaction != 'stop':
+ print >>sys.stderr, \
+ '>> ERROR: Found another instance! Either use -i to specifiy another instance number or remove /tmp/pydav%s.pid!' % counter
+ sys.exit(3)
+
+ startstop(stdout='/tmp/pydav%s.log' % counter,
+ stderr='/tmp/pydav%s.err' % counter,
+ pidfile='/tmp/pydav%s.pid' % counter,
startmsg='>> Started PyWebDAV (PID: %s)',
action=daemonaction)
# start now
- runserver(port, host, directory, verbose, noauth, user, password)
+ handler = DAVAuthHandler
+ if mysql == True:
+ handler = MySQLAuthHandler
+
+ # injecting options
+ handler._config = conf
+
+ runserver(port, host, directory, verbose, noauth, user, password, handler=handler)
+
+if __name__ == '__main__':
+ run()
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..22c139a
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,3 @@
+include setup.py README VERSION
+include doc/*
+include DAVServer/*.py DAVServer/*.ini
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..3cf5c0e
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,31 @@
+Metadata-Version: 1.0
+Name: PyWebDAV
+Version: 0.9.1
+Summary: WebDAV library including a standalone server for python
+Home-page: http://code.google.com/p/pywebdav/
+Author: Simon Pamies
+Author-email: spamsch at gmail.com
+License: GPL v2
+Description:
+ WebDAV library for python. Consists of a server and the DAV package that provides WebDAV functionality.
+ After installation of this package you will have a new script in you $PYTHON/bin directory called
+ *davserver*. This serves as the main entry point to the server.
+
+ Example ::
+ davserver -D /home/files -n
+
+ For more information go to http://code.google.com/p/pywebdav/
+
+Keywords: webdav,server,dav,standalone,library,gpl,http,rfc2518,rfc 2518
+Platform: Unix
+Platform: Windows
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Console
+Classifier: Environment :: Web Environment
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: System Administrators
+Classifier: License :: OSI Approved :: GNU General Public License (GPL)
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Operating System :: POSIX
+Classifier: Programming Language :: Python
+Classifier: Topic :: Software Development :: Libraries
diff --git a/PyWebDAV.egg-info/PKG-INFO b/PyWebDAV.egg-info/PKG-INFO
new file mode 100644
index 0000000..3cf5c0e
--- /dev/null
+++ b/PyWebDAV.egg-info/PKG-INFO
@@ -0,0 +1,31 @@
+Metadata-Version: 1.0
+Name: PyWebDAV
+Version: 0.9.1
+Summary: WebDAV library including a standalone server for python
+Home-page: http://code.google.com/p/pywebdav/
+Author: Simon Pamies
+Author-email: spamsch at gmail.com
+License: GPL v2
+Description:
+ WebDAV library for python. Consists of a server and the DAV package that provides WebDAV functionality.
+ After installation of this package you will have a new script in you $PYTHON/bin directory called
+ *davserver*. This serves as the main entry point to the server.
+
+ Example ::
+ davserver -D /home/files -n
+
+ For more information go to http://code.google.com/p/pywebdav/
+
+Keywords: webdav,server,dav,standalone,library,gpl,http,rfc2518,rfc 2518
+Platform: Unix
+Platform: Windows
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Console
+Classifier: Environment :: Web Environment
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: System Administrators
+Classifier: License :: OSI Approved :: GNU General Public License (GPL)
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Operating System :: POSIX
+Classifier: Programming Language :: Python
+Classifier: Topic :: Software Development :: Libraries
diff --git a/PyWebDAV.egg-info/SOURCES.txt b/PyWebDAV.egg-info/SOURCES.txt
new file mode 100644
index 0000000..ce7f1ae
--- /dev/null
+++ b/PyWebDAV.egg-info/SOURCES.txt
@@ -0,0 +1,42 @@
+MANIFEST.in
+README
+VERSION
+ez_setup.py
+setup.py
+DAV/AuthServer.py
+DAV/BufferingHTTPServer.py
+DAV/INI_Parse.py
+DAV/WebDAVServer.py
+DAV/__init__.py
+DAV/constants.py
+DAV/davcmd.py
+DAV/davcopy.py
+DAV/davmove.py
+DAV/dbconn.py
+DAV/delete.py
+DAV/errors.py
+DAV/iface.py
+DAV/propfind.py
+DAV/propfind.py.orig
+DAV/status.py
+DAV/utils.py
+DAVServer/__init__.py
+DAVServer/config.ini
+DAVServer/daemonize.py
+DAVServer/fileauth.py
+DAVServer/fshandler.py
+DAVServer/mysqlauth.py
+DAVServer/server.py
+PyWebDAV.egg-info/PKG-INFO
+PyWebDAV.egg-info/SOURCES.txt
+PyWebDAV.egg-info/dependency_links.txt
+PyWebDAV.egg-info/entry_points.txt
+PyWebDAV.egg-info/not-zip-safe
+PyWebDAV.egg-info/top_level.txt
+doc/ARCHITECTURE
+doc/Changes
+doc/INSTALL
+doc/LICENSE
+doc/TODO
+doc/interface_class
+doc/walker
\ No newline at end of file
diff --git a/PyWebDAV.egg-info/dependency_links.txt b/PyWebDAV.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/PyWebDAV.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/PyWebDAV.egg-info/entry_points.txt b/PyWebDAV.egg-info/entry_points.txt
new file mode 100644
index 0000000..5a11fa1
--- /dev/null
+++ b/PyWebDAV.egg-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+davserver = DAVServer.server:run
+
diff --git a/PyWebDAV.egg-info/not-zip-safe b/PyWebDAV.egg-info/not-zip-safe
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/PyWebDAV.egg-info/not-zip-safe
@@ -0,0 +1 @@
+
diff --git a/PyWebDAV.egg-info/top_level.txt b/PyWebDAV.egg-info/top_level.txt
new file mode 100644
index 0000000..e863022
--- /dev/null
+++ b/PyWebDAV.egg-info/top_level.txt
@@ -0,0 +1,2 @@
+DAVServer
+DAV
diff --git a/README b/README
index aa10c2b..22bc1af 100644
--- a/README
+++ b/README
@@ -1,17 +1,19 @@
-I want to introduce you to my webdav server implementation in Python. Actually
-it's supposed to be a generic class which can be used for all sorts of servers.
-Thus the actual webdav part and the data interface part is implemented in two
-modules. All you have to do in order to create a wevdav server for you
-application is to write an interface class.
+Python WebDAV implementation.
A working WebDAV Server is included in the PyDAVServer package. Feel free to
extend it to your needs. Please send suggestions and bugfixes to the author(s).
+LICENSE
+-------
+
+see doc/LICENSE file
+
+
AUTHOR(s)
------
-Simon Pamies (Current maintainer)
+Simon Pamies (also current maintainer)
Bielefeld, Germany
s.pamies at banality.de
@@ -19,16 +21,28 @@ Christian Scholz
Aachen, Germany
mrtopf at webdav.de
+Vince Spicer
+Ontario, Canada
+vince at vince.ca
+
+
REQUIREMENTS
------------
- Python 2.0 or higher (www.python.org)
- PyXML 0.66 (pyxml.sourceforge.net)
+OPTIONAL
+--------
+
+- MySQLdb (http://sourceforge.net/projects/mysql-python)
+ - Mysql server 4.0+ for Mysql authentication with
+ with read/write access to one database
+
INSTALLATION
------------
-see INSTALL file
+see doc/INSTALL file
What is WebDAV?
---------------
@@ -80,6 +94,15 @@ B. In the PyDAVServer directory:
3. fileauth.py
Handler for authentication. Nothing very special about it.
+ 4. dbconn.py
+ Mysql Database Handler.
+
+ 5. INI_Parse.py
+ Parses the config.ini file.
+
+ 6. config.ini
+ PyWebDav configuration file.
+
NOTES
-----
@@ -98,10 +121,9 @@ If you find bugs (many!) or have suggestions then please email the maintainer
Thanks :)
-LICENSE
--------
-see LICENSE file
+Windows Notes
+-------------
-That's all there is to it!
+This version can not be run under windows.
diff --git a/VERSION b/VERSION
index 5a2a580..f374f66 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.6
+0.9.1
diff --git a/ARCHITECTURE b/doc/ARCHITECTURE
similarity index 100%
rename from ARCHITECTURE
rename to doc/ARCHITECTURE
diff --git a/Changes b/doc/Changes
similarity index 60%
rename from Changes
rename to doc/Changes
index 888ce23..119ab9f 100644
--- a/Changes
+++ b/doc/Changes
@@ -1,5 +1,57 @@
-Changes since 0.5.2
--------------------
+
+0.9.1 (May 4th 2009)
+--
+
+- Restructured the structure a bit: Made server package
+ a real python package. Adapted error messages. Prepared
+ egg distribution.
+ [Simon Pamies]
+
+- Fix for time formatting bug. Thanks to Ian Kallen
+ [Simon Pamies]
+
+- Small fixes for WebDavServer (status not handled correctly) and
+ propfind (children are returned from a PROPFIND with "Depth: 0")
+ [Kjetil Ørbekk]
+
+0.8 (Jul 15th 2008)
+---
+
+- First try of an implementation of the LOCK and UNLOCK features.
+ Still very incomplete (read: very incomplete) and not working
+ in this version.
+ [Simon Pamies]
+
+- Some code cleanups to prepare restructuring
+ [Simon Pamies]
+
+- Port to minidom because PyXML isn't longer maintained
+ [Martin v. Loewis]
+
+- utils.py: Makes use of DOMImplementation class to create a new xml document
+ Uses dom namespace features to create elements within DAV: namespace
+ [Stephane Bonhomme]
+
+- davcmd.py: Missing an indent in loop on remove and copy operations on trees, the
+ effect was that only the last object was removed/copied : always leads
+ to a failure when copying collections.
+ [Stephane Bonhomme]
+
+- propfind.py: missing a return at the end of the createResponse method (case of a
+ propfind without xml body, should act as a allprops).
+ [Stephane Bonhomme]
+
+0.7
+---
+
+- Added MySQL auth support brought by Vince Spicer
+- Added INI file support also introduced by Vince
+- Some minor bugfixes and integration changes.
+- Added instance counter to make multiple instances possible
+- Extended --help text a bit
+
+0.6
+---
- Added bugfixes for buggy Mac OS X Finder implementation
Finder tries to stat .DS_Store without checking if it exists
@@ -13,7 +65,7 @@ Changes since 0.5.2
- Added logging facilities
- Added extended arguments
-- some more things I can't remember
+- some more things I can't remember (spamsch)
Changes since 0.5.1
-------------------
diff --git a/INSTALL b/doc/INSTALL
similarity index 100%
rename from INSTALL
rename to doc/INSTALL
diff --git a/LICENSE b/doc/LICENSE
similarity index 100%
rename from LICENSE
rename to doc/LICENSE
diff --git a/TODO b/doc/TODO
similarity index 100%
rename from TODO
rename to doc/TODO
diff --git a/ez_setup.py b/ez_setup.py
new file mode 100644
index 0000000..d24e845
--- /dev/null
+++ b/ez_setup.py
@@ -0,0 +1,276 @@
+#!python
+"""Bootstrap setuptools installation
+
+If you want to use setuptools in your package's setup.py, just include this
+file in the same directory with it, and add this to the top of your setup.py::
+
+ from ez_setup import use_setuptools
+ use_setuptools()
+
+If you want to require a specific version of setuptools, set a download
+mirror, or use an alternate download directory, you can do so by supplying
+the appropriate options to ``use_setuptools()``.
+
+This file can also be run as a script to install or upgrade setuptools.
+"""
+import sys
+DEFAULT_VERSION = "0.6c9"
+DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
+
+md5_data = {
+ 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
+ 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
+ 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
+ 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
+ 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
+ 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
+ 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
+ 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
+ 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
+ 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
+ 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
+ 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
+ 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
+ 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
+ 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
+ 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
+ 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
+ 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
+ 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
+ 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
+ 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
+ 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
+ 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
+ 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
+ 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
+ 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
+ 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
+ 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902',
+ 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de',
+ 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b',
+ 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03',
+ 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a',
+ 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6',
+ 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a',
+}
+
+import sys, os
+try: from hashlib import md5
+except ImportError: from md5 import md5
+
+def _validate_md5(egg_name, data):
+ if egg_name in md5_data:
+ digest = md5(data).hexdigest()
+ if digest != md5_data[egg_name]:
+ print >>sys.stderr, (
+ "md5 validation of %s failed! (Possible download problem?)"
+ % egg_name
+ )
+ sys.exit(2)
+ return data
+
+def use_setuptools(
+ version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+ download_delay=15
+):
+ """Automatically find/download setuptools and make it available on sys.path
+
+ `version` should be a valid setuptools version number that is available
+ as an egg for download under the `download_base` URL (which should end with
+ a '/'). `to_dir` is the directory where setuptools will be downloaded, if
+ it is not already available. If `download_delay` is specified, it should
+ be the number of seconds that will be paused before initiating a download,
+ should one be required. If an older version of setuptools is installed,
+ this routine will print a message to ``sys.stderr`` and raise SystemExit in
+ an attempt to abort the calling script.
+ """
+ was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules
+ def do_download():
+ egg = download_setuptools(version, download_base, to_dir, download_delay)
+ sys.path.insert(0, egg)
+ import setuptools; setuptools.bootstrap_install_from = egg
+ try:
+ import pkg_resources
+ except ImportError:
+ return do_download()
+ try:
+ pkg_resources.require("setuptools>="+version); return
+ except pkg_resources.VersionConflict, e:
+ if was_imported:
+ print >>sys.stderr, (
+ "The required version of setuptools (>=%s) is not available, and\n"
+ "can't be installed while this script is running. Please install\n"
+ " a more recent version first, using 'easy_install -U setuptools'."
+ "\n\n(Currently using %r)"
+ ) % (version, e.args[0])
+ sys.exit(2)
+ else:
+ del pkg_resources, sys.modules['pkg_resources'] # reload ok
+ return do_download()
+ except pkg_resources.DistributionNotFound:
+ return do_download()
+
+def download_setuptools(
+ version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+ delay = 15
+):
+ """Download setuptools from a specified location and return its filename
+
+ `version` should be a valid setuptools version number that is available
+ as an egg for download under the `download_base` URL (which should end
+ with a '/'). `to_dir` is the directory where the egg will be downloaded.
+ `delay` is the number of seconds to pause before an actual download attempt.
+ """
+ import urllib2, shutil
+ egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
+ url = download_base + egg_name
+ saveto = os.path.join(to_dir, egg_name)
+ src = dst = None
+ if not os.path.exists(saveto): # Avoid repeated downloads
+ try:
+ from distutils import log
+ if delay:
+ log.warn("""
+---------------------------------------------------------------------------
+This script requires setuptools version %s to run (even to display
+help). I will attempt to download it for you (from
+%s), but
+you may need to enable firewall access for this script first.
+I will start the download in %d seconds.
+
+(Note: if this machine does not have network access, please obtain the file
+
+ %s
+
+and place it in this directory before rerunning this script.)
+---------------------------------------------------------------------------""",
+ version, download_base, delay, url
+ ); from time import sleep; sleep(delay)
+ log.warn("Downloading %s", url)
+ src = urllib2.urlopen(url)
+ # Read/write all in one block, so we don't create a corrupt file
+ # if the download is interrupted.
+ data = _validate_md5(egg_name, src.read())
+ dst = open(saveto,"wb"); dst.write(data)
+ finally:
+ if src: src.close()
+ if dst: dst.close()
+ return os.path.realpath(saveto)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def main(argv, version=DEFAULT_VERSION):
+ """Install or upgrade setuptools and EasyInstall"""
+ try:
+ import setuptools
+ except ImportError:
+ egg = None
+ try:
+ egg = download_setuptools(version, delay=0)
+ sys.path.insert(0,egg)
+ from setuptools.command.easy_install import main
+ return main(list(argv)+[egg]) # we're done here
+ finally:
+ if egg and os.path.exists(egg):
+ os.unlink(egg)
+ else:
+ if setuptools.__version__ == '0.0.1':
+ print >>sys.stderr, (
+ "You have an obsolete version of setuptools installed. Please\n"
+ "remove it from your system entirely before rerunning this script."
+ )
+ sys.exit(2)
+
+ req = "setuptools>="+version
+ import pkg_resources
+ try:
+ pkg_resources.require(req)
+ except pkg_resources.VersionConflict:
+ try:
+ from setuptools.command.easy_install import main
+ except ImportError:
+ from easy_install import main
+ main(list(argv)+[download_setuptools(delay=0)])
+ sys.exit(0) # try to force an exit
+ else:
+ if argv:
+ from setuptools.command.easy_install import main
+ main(argv)
+ else:
+ print "Setuptools version",version,"or greater has been installed."
+ print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
+
+def update_md5(filenames):
+ """Update our built-in md5 registry"""
+
+ import re
+
+ for name in filenames:
+ base = os.path.basename(name)
+ f = open(name,'rb')
+ md5_data[base] = md5(f.read()).hexdigest()
+ f.close()
+
+ data = [" %r: %r,\n" % it for it in md5_data.items()]
+ data.sort()
+ repl = "".join(data)
+
+ import inspect
+ srcfile = inspect.getsourcefile(sys.modules[__name__])
+ f = open(srcfile, 'rb'); src = f.read(); f.close()
+
+ match = re.search("\nmd5_data = {\n([^}]+)}", src)
+ if not match:
+ print >>sys.stderr, "Internal error!"
+ sys.exit(2)
+
+ src = src[:match.start(1)] + repl + src[match.end(1):]
+ f = open(srcfile,'w')
+ f.write(src)
+ f.close()
+
+
+if __name__=='__main__':
+ if len(sys.argv)>2 and sys.argv[1]=='--md5update':
+ update_md5(sys.argv[2:])
+ else:
+ main(sys.argv[1:])
+
+
+
+
+
+
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..861a9f5
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build =
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/setup.py b/setup.py
index ed160b8..abc91bb 100644
--- a/setup.py
+++ b/setup.py
@@ -1,25 +1,50 @@
#!/usr/bin/env python
+try:
+ from ez_setup import use_setuptools
+ use_setuptools()
+except ImportError:
+ pass
+
+from setuptools import setup
+
+VERSION = open('VERSION', 'r').read()
+VERSION = VERSION.replace('\n', '')
+
+DOC = """
+WebDAV library for python. Consists of a server and the DAV package that provides WebDAV functionality.
+After installation of this package you will have a new script in you $PYTHON/bin directory called
+*davserver*. This serves as the main entry point to the server.
+
+Example ::
+ davserver -D /home/files -n
+
+For more information go to http://code.google.com/p/pywebdav/
+"""
+
from distutils.core import setup
-setup(name='PyDAV',
- description='WebDAV library and server for python',
- author='Christian Scholz',
- author_email='cs at comlounge.net',
+setup(name='PyWebDAV',
+ description='WebDAV library including a standalone server for python',
+ author='Simon Pamies',
+ author_email='spamsch at gmail.com',
maintainer='Simon Pamies',
- maintainer_email='s.pamies at banality.de',
- url='http://www.webdav.de',
- version='0.6',
+ maintainer_email='spamsch at gmail.com',
+ url='http://code.google.com/p/pywebdav/',
+ platforms=['Unix', 'Windows'],
+ license='GPL v2',
+ version=VERSION,
+ long_description=DOC,
classifiers = [
- 'Development Status :: 0.6',
+ 'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Environment :: Web Environment',
- 'Intended Audience :: End Users',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
- 'License :: GPL',
+ 'License :: OSI Approved :: GNU General Public License (GPL)',
'Operating System :: MacOS :: MacOS X',
'Operating System :: POSIX',
'Programming Language :: Python',
+ 'Topic :: Software Development :: Libraries'
],
keywords = ['webdav',
'server',
@@ -30,5 +55,9 @@ setup(name='PyDAV',
'http',
'rfc2518',
'rfc 2518'],
- packages=['DAV', ],
+ packages=['DAV', 'DAVServer'],
+ zip_safe=False,
+ entry_points={
+ 'console_scripts' : ['davserver = DAVServer.server:run']
+ }
)
commit b24c8be644a6641c80ca130bbe3f15c121047ffe
Author: Daniel Baumann <daniel at debian.org>
Date: Wed Feb 4 22:52:22 2009 +0100
Adding upstream version 0.6.
diff --git a/ARCHITECTURE b/ARCHITECTURE
new file mode 100644
index 0000000..39348b1
--- /dev/null
+++ b/ARCHITECTURE
@@ -0,0 +1,84 @@
+Class Tree
+----------
+
+PyWebDAVServer.fileauth.DAVAuthHandler
+<Very simple handler for authentication. Must have its data
+injected (look at server.py). Overwrites get_userinfo from AuthRequestHandler>
+
+ |
+ |
+ |
+
+DAV.WebDAVServer.DAVRequestHandler
+<Provides methods for DAV commands. These methods are triggered
+by AuthRequestHandler.handle().>
+
+ |
+ |
+ |
+
+DAV.AuthServer.BufferedAuthRequestHandler
+<Calls the right methods to buffer request data>
+
+ | |
+ | |
+ | |
+
+DAV.BufferingHTTPServer DAV.AuthServer.AuthRequestHandler
+<Saves the complete result <Overwrites handle() method in order
+in one file and returns to provide authentication and to pass
+only if request is control to the do_ methods handling
+complete> 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 AuthServer class implements Basic Authentication in order to make
+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.
+
+Thus the WebDAVServer class needs an interface
+to this data which is implemented via an interface class (in our
+example stored in fshandler.py). This class will be instantiated by the
+WebDAVServer class and be used when needed (e.g. retrieving properties,
+creating new resources, obtaining existing resources etc.).
+
+When it comes to parsing XML (like in the PROPFIND and PROPPATCH
+methods) the WebDAVServer class uses for each method another extra class
+stored e.g. in propfind.py. This class parses the XML body and
+createsan XML response while obtaining data from the interface
+class. Thus all the XML parsing is factored out into the specific
+method classes.
+
+In order to create your own davserver for your own purposes you have to do the
+following:
+
+- subclass the WebDAVServer class and write your own get_userinfo() method for
+ identifying users.
+
+- create your own interface class for interfacing with your actual data.
+ 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.
+
+===
+* describe the methods which need to be implemented.
+
+
diff --git a/Changes b/Changes
new file mode 100644
index 0000000..888ce23
--- /dev/null
+++ b/Changes
@@ -0,0 +1,70 @@
+Changes since 0.5.2
+-------------------
+
+- Added bugfixes for buggy Mac OS X Finder implementation
+ Finder tries to stat .DS_Store without checking if it exists
+- Cleaned up readme and install files
+- Moved license to extra file
+- Added distutils support
+- Refactored module layout
+- Refactored class and module names
+- Added commandline support
+- Added daemonize support
+- Added logging facilities
+- Added extended arguments
+
+- some more things I can't remember
+
+Changes since 0.5.1
+-------------------
+Updated to work with latest 4Suite
+
+Changes since 0.5
+-----------------
+
+- added constants.py
+- data.py must now return COLLECTION or OBJECT when getting asked for
+ resourcetype. propfind.py will automatically generate the right xml
+ element.
+- <href> now only contains the path
+- changed HTTP/1.0 header to HTTP/1.1 which makes it work with WebFolders
+- added DO_AUTH constant to AuthServer.py to control whether authentication
+ should be done or not.
+- added chunked responses in davserver.py
+ One step in order to get a server with keep-alive one day.
+- we now use 4DOM instead if PyDOM
+- the URI in a href is quoted
+- complete rewrite of the PROPFIND stuff:
+ - error responses are now generated when a property if not found
+ or not accessible
+ - namespace handling is now better. We forget any prefix and
+ create them ourselves later in the response.
+- added superclass iface.py in DAV/ in order to make implementing
+ interface classes easier. See data.py for how to use it.
+ Also note that the way data.py handles things might have changed from
+ the previous release (if you don't like it wait for 1.0!)
+- added functions to iface.py which format creationdate and lastmodified
+- implemented HEAD
+
+- lots of bugfixes
+
+
+Changes since 0.3
+-----------------
+
+- removed hard coded base uri from davserver.py and replaced by
+ a reference to the dataclass. Added this to iface.py where you
+ have to define it in your subclass.
+- added davcmd.py which contains utility functions for copy and move
+- reimplemented DELETE and removed dependencies to pydom. move actual
+ delete method to davcmd.
+- implemented COPY
+- implemented MOVE
+- fixed bugs in errors.py, needs revisiting anyway..
+- URIs are now unquoted in davserver.py before being used
+- paths in data.py are quoted in system calls in order to support
+ blanks in pathnames (e.g. mkdir '%s' )
+- switched to exceptions when catching errors from the interface class
+- added exists() method to data.py
+- added more uri utility functions to utils.py
+- millenium bugfixes ;-)
diff --git a/DAV/AuthServer.py b/DAV/AuthServer.py
new file mode 100755
index 0000000..131da36
--- /dev/null
+++ b/DAV/AuthServer.py
@@ -0,0 +1,232 @@
+#!/usr/bin/env python
+
+"""
+ Authenticating HTTP Server
+
+ This module builds on BaseHTTPServer and implements
+ basic authentication
+
+"""
+
+from DAV.utils import VERSION, AUTHOR
+__version__ = VERSION
+__author__ = AUTHOR
+
+import os
+import sys
+import time
+import socket
+import string
+import posixpath
+import SocketServer
+import BufferingHTTPServer
+import BaseHTTPServer
+import base64
+
+from string import atoi,split
+
+AUTH_ERROR_MSG="""<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
+<HTML><HEAD>
+<TITLE>401 Authorization Required</TITLE>
+</HEAD><BODY>
+<H1>Authorization Required</H1>
+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.<P>
+</BODY></HTML>"""
+
+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.
+
+ In your subclass you have to define the method get_userinfo(user,pw)
+ 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="""<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
+ <HTML><HEAD>
+ <TITLE>401 Authorization Required</TITLE>
+ </HEAD><BODY>
+ <H1>Authorization Required</H1>
+ 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.<P>
+ </BODY></HTML>"""
+
+ server_version = "AuthHTTP/" + __version__
+
+ def _log(self, message):
+ pass
+
+ def handle(self):
+ """
+ Special handle method with buffering and authentication
+ """
+
+ self.infp=open("/tmp/in.%s" %self.__class__, "a+")
+
+ self.infp.write("------------------------------------------------------------------------------\n")
+ self.raw_requestline = self.rfile.readline()
+ self.infp.write(self.raw_requestline)
+ self.request_version = version = "HTTP/0.9" # Default
+ requestline = self.raw_requestline
+
+ # needed by send_error
+ self.command = requestline
+
+ 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)
+ self.infp.write(str(self.headers))
+
+ # 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,":")
+ if not self.get_userinfo(user,pw):
+ 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.infp.flush()
+ self.infp.close()
+ self._flush()
+
+ def write_infp(self,s):
+ self.infp.write(str(s))
+ self.infp.flush()
+
+ def send_response(self,code, message=None):
+ """Override send_response to use the correct http version
+ in the response."""
+
+ 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.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')
+
+ def send_head(self):
+ """Common code for GET and HEAD commands.
+
+ 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.
+
+ """
+ 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:
+ message = short
+ explain = long
+
+ emsg=self.AUTH_ERROR_MSG
+ self.log_error("code %d, message %s", code, message)
+ self.send_response(code, message)
+ self.send_header("WWW-Authenticate","Basic realm=\"PyWebDAV\"")
+ self.send_header("Content-Type", 'text/html')
+ self.end_headers()
+
+ lines=split(emsg,"\n")
+ for l in lines:
+ self._append("%s\r\n" %l)
+
+ def get_userinfo(self,user):
+ """ return the password of the user
+ Override this class to return the right password
+ """
+
+ # 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/DAV/BufferingHTTPServer.py b/DAV/BufferingHTTPServer.py
new file mode 100644
index 0000000..5984d35
--- /dev/null
+++ b/DAV/BufferingHTTPServer.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+
+"""
+ Buffering HTTP Server
+ Copyright (C) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+
+
+from DAV.utils import VERSION, AUTHOR
+__version__ = VERSION
+__author__ = AUTHOR
+
+from BaseHTTPServer import BaseHTTPRequestHandler
+
+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)
+
+ """
+
+
+ def _init_buffer(self):
+ """initialize the buffer.
+
+ If you override the handle() method remember to call
+ this (see below)
+ """
+ self.__buffer=""
+ self.__outfp=open("/tmp/out.%s" %self.__class__,"a+")
+
+ 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.__outfp.write(self.__buffer)
+ self.__outfp.flush()
+ self.wfile.flush()
+ self.__buffer=""
+
+ 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':
+ 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.send_header('Date', self.date_time_string())
+
+ protocol_version="HTTP/1.1"
+
diff --git a/DAV/WebDAVServer.py b/DAV/WebDAVServer.py
new file mode 100644
index 0000000..6d89916
--- /dev/null
+++ b/DAV/WebDAVServer.py
@@ -0,0 +1,345 @@
+"""
+ Python WebDAV Server.
+ Copyright (C) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+This module builds on AuthServer by implementing the standard DAV
+methods.
+
+Subclass this class and specify an IFACE_CLASS. See example.
+
+"""
+
+DEBUG=None
+
+from DAV.utils import VERSION, AUTHOR
+__version__ = VERSION
+__author__ = AUTHOR
+
+
+import os
+import sys
+import time
+import socket
+import string
+import posixpath
+import base64
+import AuthServer
+import urlparse
+import urllib
+
+from propfind import PROPFIND
+from delete import DELETE
+from davcopy import COPY
+from davmove import MOVE
+
+from string import atoi,split
+from status import STATUS_CODES
+from errors import *
+
+class DAVRequestHandler(AuthServer.BufferedAuthRequestHandler):
+ """Simple DAV request handler with
+
+ - GET
+ - HEAD
+ - PUT
+ - OPTIONS
+ - PROPFIND
+ - PROPPATCH
+ - MKCOL
+
+ It uses the resource/collection classes for serving and
+ storing content.
+
+ """
+
+ server_version = "DAV/" + __version__
+
+ ### utility functions
+ def _log(self, message):
+ pass
+
+ def send_body(self,DATA,code,msg,desc,ctype='application/octet-stream',headers={}):
+ """ send a body in one part """
+
+ self.send_response(code,message=msg)
+ self.send_header("Connection", "close")
+ self.send_header("Accept-Ranges", "bytes")
+
+ for a,v in headers.items():
+ self.send_header(a,v)
+
+ if DATA:
+ self.send_header("Content-Length", str(len(DATA)))
+ self.send_header("Content-Type", ctype)
+ else:
+ self.send_header("Content-Length", "0")
+
+ self.end_headers()
+ if DATA:
+ self._append(DATA)
+
+ def send_body_chunks(self,DATA,code,msg,desc,ctype='text/xml; encoding="utf-8"'):
+ """ send a body in chunks """
+
+ self.responses[207]=(msg,desc)
+ self.send_response(code,message=msg)
+ self.send_header("Content-type", ctype)
+ self.send_header("Connection", "close")
+ self.send_header("Transfer-Encoding", "chunked")
+ self.end_headers()
+ self._append(hex(len(DATA))[2:]+"\r\n")
+ self._append(DATA)
+ self._append("\r\n")
+ self._append("0\r\n")
+ self._append("\r\n")
+
+ ### HTTP METHODS
+
+ def do_OPTIONS(self):
+ """return the list of capabilities """
+ self.send_response(200)
+ self.send_header("Allow", "GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE")
+ self.send_header("Content-Type", "text/plain")
+ self.send_header("DAV", "1")
+ self.end_headers()
+
+ def do_PROPFIND(self):
+
+ dc=self.IFACE_CLASS
+
+ # read the body
+ body=None
+ if self.headers.has_key("Content-Length"):
+ l=self.headers['Content-Length']
+ body=self.rfile.read(atoi(l))
+ self.write_infp(body)
+
+ # which Depth?
+ if self.headers.has_key('Depth'):
+ d=self.headers['Depth']
+ else:
+ d="infinity"
+
+ uri=urlparse.urljoin(dc.baseuri,self.path)
+ uri=urllib.unquote(uri)
+ pf=PROPFIND(uri,dc,d)
+
+ if body:
+ pf.read_propfind(body)
+
+ try:
+ DATA=pf.createResponse()
+ DATA=DATA+"\n"
+ except DAV_Error, (ec,dd):
+ return self.send_status(ec)
+
+ self.send_body_chunks(DATA,"207","Multi-Status","Multiple responses")
+
+ def do_GET(self):
+ """Serve a GET request."""
+
+ dc=self.IFACE_CLASS
+ uri=urlparse.urljoin(dc.baseuri,self.path)
+ uri=urllib.unquote(uri)
+
+ # get the last modified date
+ try:
+ lm=dc.get_prop(uri,"DAV:","getlastmodified")
+ except:
+ lm="Sun, 01 Dec 2014 00:00:00 GMT" # dummy!
+ headers={"Last-Modified":lm}
+
+ # get the content type
+ try:
+ ct=dc.get_prop(uri,"DAV:","getcontenttype")
+ except:
+ ct="application/octet-stream"
+
+ # get the data
+ try:
+ data=dc.get_data(uri)
+ except DAV_Error, (ec,dd):
+ self.send_status(ec)
+ return
+
+ # send the data
+ self.send_body(data,"200","OK","OK",ct,headers)
+
+ def do_HEAD(self):
+ """ Send a HEAD response """
+
+ dc=self.IFACE_CLASS
+ uri=urlparse.urljoin(dc.baseuri,self.path)
+ uri=urllib.unquote(uri)
+
+ # get the last modified date
+ try:
+ lm=dc.get_prop(uri,"DAV:","getlastmodified")
+ except:
+ lm="Sun, 01 Dec 2014 00:00:00 GMT" # dummy!
+
+ headers={"Last-Modified":lm}
+
+ # get the content type
+ try:
+ ct=dc.get_prop(uri,"DAV:","getcontenttype")
+ except:
+ ct="application/octet-stream"
+
+ try:
+ data=dc.get_data(uri)
+ headers["Content-Length"]=str(len(data))
+ except DAV_NotFound:
+ self.send_body(None,"404","Not Found","")
+ return
+
+ self.send_body(None,"200","OK","OK",ct,headers)
+
+ def do_POST(self):
+ self.send_error(404,"File not found")
+
+ def do_MKCOL(self):
+ """ create a new collection """
+
+ dc=self.IFACE_CLASS
+ uri=urlparse.urljoin(dc.baseuri,self.path)
+ uri=urllib.unquote(uri)
+ try:
+ dc.mkcol(uri)
+ self.send_status(200)
+ except DAV_Error, (ec,dd):
+ self.send_status(ec)
+
+ def do_DELETE(self):
+ """ delete an resource """
+ dc=self.IFACE_CLASS
+ uri=urlparse.urljoin(dc.baseuri,self.path)
+ uri=urllib.unquote(uri)
+ dl=DELETE(uri,dc)
+ if dc.is_collection(uri):
+ res=dl.delcol()
+ else:
+ res=dl.delone()
+
+ if res:
+ self.send_status(207,body=res)
+ else:
+ self.send_status(204)
+
+ def do_PUT(self):
+ dc=self.IFACE_CLASS
+
+ # read the body
+ body=None
+ if self.headers.has_key("Content-Length"):
+ l=self.headers['Content-Length']
+ body=self.rfile.read(atoi(l))
+ uri=urlparse.urljoin(dc.baseuri,self.path)
+ uri=urllib.unquote(uri)
+
+ ct=None
+ if self.headers.has_key("Content-Type"):
+ ct=self.headers['Content-Type']
+ try:
+ dc.put(uri,body,ct)
+ except DAV_Error, (ec,dd):
+ self.send_status(ec)
+ return
+ self.send_status(201)
+
+ def do_COPY(self):
+ """ copy one resource to another """
+ try:
+ self.copymove(COPY)
+ except DAV_Error, (ec,dd):
+ self.send_status(ec)
+
+ def do_MOVE(self):
+ """ move one resource to another """
+ try:
+ self.copymove(MOVE)
+ except DAV_Error, (ec,dd):
+ self.send_status(ec)
+
+ def copymove(self,CLASS):
+ """ common method for copying or moving objects """
+ dc=self.IFACE_CLASS
+
+ # get the source URI
+ source_uri=urlparse.urljoin(dc.baseuri,self.path)
+ source_uri=urllib.unquote(source_uri)
+
+ # get the destination URI
+ dest_uri=self.headers['Destination']
+ dest_uri=urllib.unquote(dest_uri)
+
+ # Overwrite?
+ overwrite=1
+ result_code=204
+ if self.headers.has_key("Overwrite"):
+ if self.headers['Overwrite']=="F":
+ overwrite=None
+ result_code=201
+
+ # instanciate ACTION class
+ cp=CLASS(dc,source_uri,dest_uri,overwrite)
+
+ # Depth?
+ d="infinity"
+ if self.headers.has_key("Depth"):
+ d=self.headers['Depth']
+
+ if d!="0" and d!="infinity":
+ self.send_status(400)
+ return
+
+ if d=="0":
+ res=cp.single_action()
+ self.send_status(res)
+ return
+
+ # 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):
+ self.send_status(ec)
+ return
+ else:
+ try:
+ res=cp.single_action()
+ except DAV_Error, (ec,dd):
+ self.send_status(ec)
+ return
+
+ if res:
+ self.send_body_chunks(res,207,STATUS_CODES[207],STATUS_CODES[207],
+ ctype='text/xml; charset="utf-8"')
+ else:
+ self.send_status(result_code)
+
+ 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):
+
+ if not msg: msg=STATUS_CODES[code]
+ self.send_body(body,code,STATUS_CODES[code],msg,mediatype)
+
diff --git a/DAV/__init__.py b/DAV/__init__.py
new file mode 100644
index 0000000..10abdb4
--- /dev/null
+++ b/DAV/__init__.py
@@ -0,0 +1,20 @@
+"""
+ python davserver
+ Copyright (C) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""
+
+
diff --git a/DAV/constants.py b/DAV/constants.py
new file mode 100644
index 0000000..e858ac5
--- /dev/null
+++ b/DAV/constants.py
@@ -0,0 +1,18 @@
+"""
+
+constants definition
+
+
+"""
+
+# definition for resourcetype
+COLLECTION=1
+OBJECT=None
+DAV_PROPS=['creationdate', 'displayname', 'getcontentlanguage', 'getcontentlength', 'getcontenttype', 'getetag', 'getlastmodified', 'lockdiscovery', 'resourcetype', 'source', 'supportedlock']
+
+
+# Request classes in propfind
+
+RT_ALLPROP=1
+RT_PROPNAME=2
+RT_PROP=3
diff --git a/DAV/davcmd.py b/DAV/davcmd.py
new file mode 100644
index 0000000..5c6be3f
--- /dev/null
+++ b/DAV/davcmd.py
@@ -0,0 +1,218 @@
+"""
+
+davcmd.py
+---------
+
+containts commands like copy, move, delete for normal
+resources and collections
+
+"""
+
+from string import split,replace,joinfields
+import urlparse
+
+from utils import create_treelist, is_prefix
+from errors import *
+
+def deltree(dc,uri,exclude={}):
+ """ delete a tree of resources
+
+ dc -- dataclass to use
+ uri -- root uri to delete
+ exclude -- an optional list of uri:error_code pairs which should not
+ be deleted.
+
+ returns dict of uri:error_code tuples from which
+ another method can create a multistatus xml element.
+
+ Also note that we only know Depth=infinity thus we don't have
+ to test for it.
+
+ """
+
+ tlist=create_treelist(dc,uri)
+ result={}
+
+ for i in range(len(tlist),0,-1):
+ problem_uris=result.keys()
+ element=tlist[i-1]
+
+ # test here, if an element is a prefix of an uri which
+ # generated an error before.
+ # note that we walk here from childs to parents, thus
+ # we cannot delete a parent if a child made a problem.
+ # (see example in 8.6.2.1)
+ ok=1
+ for p in problem_uris:
+ if is_prefix(element,p):
+ ok=None
+ break
+
+ if not ok: continue
+
+ # here we test for the exclude list which is the other way round!
+ for p in exclude.keys():
+ if is_prefix(p,element):
+ ok=None
+ break
+
+ if not ok: continue
+
+ # now delete stuff
+ try:
+ delone(dc,element)
+ except DAV_Error, (ec,dd):
+ result[element]=ec
+
+ return result
+
+def delone(dc,uri):
+ """ delete a single object """
+ if dc.is_collection(uri):
+ dc.rmcol(uri) # should be empty
+ else:
+ dc.rm(uri)
+
+###
+### COPY
+###
+
+# helper function
+
+def copy(dc,src,dst):
+ """ only copy the element
+
+ This is just a helper method factored out from copy and
+ copytree. It will not handle the overwrite or depth header.
+
+ """
+
+ # destination should have been deleted before
+ if dc.exists(dst): raise DAV_Error, 412
+
+ # source should exist also
+ if not dc.exists(src): raise DAV_NotFound
+
+ if dc.is_collection(src):
+ dc.copycol(src,dst) # an exception will be passed thru
+ else:
+ dc.copy(src,dst) # an exception will be passed thru
+
+
+# the main functions
+
+def copyone(dc,src,dst,overwrite=None):
+ """ copy one resource to a new destination """
+
+ if overwrite and dc.exists(dst):
+ delres=deltree(dc,dst)
+ else:
+ delres={}
+
+ # if we cannot delete everything, then do not copy!
+ if delres: return delres
+
+ try:
+ copy(dc,src,dst) # pass thru exceptions
+ except DAV_Error, (ec,dd):
+ return ec
+
+def copytree(dc,src,dst,overwrite=None):
+ """ copy a tree of resources to another location
+
+ dc -- dataclass to use
+ src -- src uri from where to copy
+ dst -- dst uri
+ overwrite -- if 1 then delete dst uri before
+
+ returns dict of uri:error_code tuples from which
+ another method can create a multistatus xml element.
+
+ """
+
+
+ # first delete the destination resource
+ if overwrite and dc.exists(dst):
+ delres=deltree(dc,dst)
+ else:
+ delres={}
+
+ # if we cannot delete everything, then do not copy!
+ if delres: return delres
+
+ # get the tree we have to copy
+ tlist=create_treelist(dc,src)
+ result={}
+
+ # prepare destination URIs (get the prefix)
+ dpath=urlparse.urlparse(dst)[2]
+
+ for element in tlist:
+ problem_uris=result.keys()
+
+ # now URIs get longer and longer thus we have
+ # to test if we had a parent URI which we were not
+ # able to copy in problem_uris which is the prefix
+ # of the actual element. If it is, then we cannot
+ # copy this as well but do not generate another error.
+ ok=1
+ for p in problem_uris:
+ if is_prefix(p,element):
+ ok=None
+ break
+
+ if not ok: continue
+
+ # now create the destination URI which corresponds to
+ # the actual source URI. -> actual_dst
+ # ("subtract" the base src from the URI and prepend the
+ # dst prefix to it.)
+ esrc=replace(element,src,"")
+ actual_dst=dpath+esrc
+
+ # now copy stuff
+ try:
+ copy(dc,element,actual_dst)
+ except DAV_Error, (ec,dd):
+ result[element]=ec
+
+ return result
+
+
+
+###
+### MOVE
+###
+
+
+def moveone(dc,src,dst,overwrite=None):
+ """ move a single resource
+
+ This is done by first copying it and then deleting
+ the original.
+ """
+
+ # first copy it
+ copyone(dc,src,dst,overwrite)
+
+ # then delete it
+ dc.rm(src)
+
+def movetree(dc,src,dst,overwrite=None):
+ """ move a collection
+
+ This is done by first copying it and then deleting
+ the original.
+
+ PROBLEM: if something did not copy then we have a problem
+ when deleting as the original might get deleted!
+ """
+
+ # first copy it
+ res=copytree(dc,src,dst,overwrite)
+
+ # then delete it
+ res=deltree(dc,src,exclude=res)
+
+ return res
+
diff --git a/DAV/davcopy.py b/DAV/davcopy.py
new file mode 100755
index 0000000..ec44931
--- /dev/null
+++ b/DAV/davcopy.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+
+"""
+ python davserver
+ Copyright (C) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+"""
+
+
+from xml.dom import ext
+from xml.dom.Document import Document
+
+import sys
+import string
+import urlparse
+import urllib
+from StringIO import StringIO
+
+import utils
+from constants import COLLECTION, OBJECT, DAV_PROPS, RT_ALLPROP, RT_PROPNAME, RT_PROP
+from errors import *
+from utils import create_treelist, quote_uri, gen_estring
+
+class COPY:
+ """ copy resources and eventually create multistatus responses
+
+ This module implements the COPY class which is responsible for
+ copying resources. Usually the normal copy work is done in the
+ interface class. This class only creates error messages if error
+ occur.
+
+ """
+
+
+ def __init__(self,dataclass,src_uri,dst_uri,overwrite):
+ self.__dataclass=dataclass
+ self.__src=src_uri
+ self.__dst=dst_uri
+ self.__overwrite=overwrite
+
+
+ def single_action(self):
+ """ copy a normal resources.
+
+ We try to copy it and return the result code.
+ This is for Depth==0
+
+ """
+
+ dc=self.__dataclass
+ base=self.__src
+
+ ### some basic tests
+ # test if dest exists and overwrite is false
+ if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
+ # test if src and dst are the same
+ # (we assume that both uris are on the same server!)
+ ps=urlparse.urlparse(self.__src)[2]
+ pd=urlparse.urlparse(self.__dst)[2]
+ if ps==pd: raise DAV_Error, 403
+
+ return dc.copyone(self.__src,self.__dst,self.__overwrite)
+
+ #return copyone(dc,self.__src,self.__dst,self.__overwrite)
+
+ def tree_action(self):
+ """ copy a tree of resources (a collection)
+
+ Here we return a multistatus xml element.
+
+ """
+ dc=self.__dataclass
+ base=self.__src
+
+ ### some basic tests
+ # test if dest exists and overwrite is false
+ if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
+ # test if src and dst are the same
+ # (we assume that both uris are on the same server!)
+ ps=urlparse.urlparse(self.__src)[2]
+ pd=urlparse.urlparse(self.__dst)[2]
+ if ps==pd: raise DAV_Error, 403
+
+
+ result=dc.copytree(self.__src,self.__dst,self.__overwrite)
+ #result=copytree(dc,self.__src,self.__dst,self.__overwrite)
+
+ if not result: return None
+
+ ###
+ ### create the multistatus XML element
+ ### (this is also the same as in delete.py.
+ ### we might make a common method out of it)
+ ###
+
+ doc = Document(None)
+ ms=doc.createElement("D:multistatus")
+ ms.setAttribute("xmlns:D","DAV:")
+ doc.appendChild(ms)
+
+ for el,ec in result.items():
+ re=doc.createElement("D:response")
+ hr=doc.createElement("D:href")
+ st=doc.createElement("D:status")
+ huri=doc.createTextNode(quote_uri(el))
+ t=doc.createTextNode(gen_estring(ec))
+ st.appendChild(t)
+ hr.appendChild(huri)
+ re.appendChild(hr)
+ re.appendChild(st)
+ ms.appendChild(re)
+
+ sfile=StringIO()
+ ext.PrettyPrint(doc,stream=sfile)
+ s=sfile.getvalue()
+ sfile.close()
+ return s
+
diff --git a/DAV/davmove.py b/DAV/davmove.py
new file mode 100755
index 0000000..78c1e64
--- /dev/null
+++ b/DAV/davmove.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+
+"""
+ python davserver
+ Copyright (C) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+"""
+
+
+import sys
+import string
+import urlparse
+import urllib
+from StringIO import StringIO
+
+import utils
+from constants import COLLECTION, OBJECT, DAV_PROPS
+from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP
+from errors import *
+from utils import create_treelist, quote_uri, gen_estring, make_xmlresponse
+from davcmd import moveone, movetree
+
+class MOVE:
+ """ move resources and eventually create multistatus responses
+
+ This module implements the MOVE class which is responsible for
+ moving resources.
+
+ MOVE is implemented by a COPY followed by a DELETE of the old
+ resource.
+
+ """
+
+
+ def __init__(self,dataclass,src_uri,dst_uri,overwrite):
+ self.__dataclass=dataclass
+ self.__src=src_uri
+ self.__dst=dst_uri
+ self.__overwrite=overwrite
+
+
+ def single_action(self):
+ """ move a normal resources.
+
+ We try to move it and return the result code.
+ This is for Depth==0
+
+ """
+
+ dc=self.__dataclass
+ base=self.__src
+
+ ### some basic tests
+ # test if dest exists and overwrite is false
+ if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
+ # test if src and dst are the same
+ # (we assume that both uris are on the same server!)
+ ps=urlparse.urlparse(self.__src)[2]
+ pd=urlparse.urlparse(self.__dst)[2]
+ if ps==pd: raise DAV_Error, 403
+
+ return dc.moveone(self.__src,self.__dst,self.__overwrite)
+
+ def tree_action(self):
+ """ move a tree of resources (a collection)
+
+ Here we return a multistatus xml element.
+
+ """
+ dc=self.__dataclass
+ base=self.__src
+
+ ### some basic tests
+ # test if dest exists and overwrite is false
+ if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
+ # test if src and dst are the same
+ # (we assume that both uris are on the same server!)
+ ps=urlparse.urlparse(self.__src)[2]
+ pd=urlparse.urlparse(self.__dst)[2]
+ if ps==pd: raise DAV_Error, 403
+
+ result=dc.movetree(self.__src,self.__dst,self.__overwrite)
+ if not result: return None
+
+ # create the multistatus XML element
+ return make_xmlresponse(result)
+
diff --git a/DAV/delete.py b/DAV/delete.py
new file mode 100755
index 0000000..ed017e5
--- /dev/null
+++ b/DAV/delete.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+
+"""
+
+ python davserver
+ Copyright (C) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+"""
+import os
+import string
+import urllib
+from StringIO import StringIO
+
+from status import STATUS_CODES
+from utils import gen_estring, quote_uri, make_xmlresponse
+from davcmd import deltree
+
+class DELETE:
+
+ def __init__(self,uri,dataclass):
+ self.__dataclass=dataclass
+ self.__uri=uri
+
+ def delcol(self):
+ """ delete a collection """
+
+ dc=self.__dataclass
+ result=dc.deltree(self.__uri)
+
+ if not len(result.items()):
+ return None # everything ok
+
+ # create the result element
+ return make_xmlresponse(result)
+
+ def delone(self):
+ """ delete a resource """
+
+ dc=self.__dataclass
+ result=dc.delone(self.__uri)
+
+ if not result: return None
+ if not len(result.items()):
+ return None # everything ok
+
+ # create the result element
+ return make_xmlresponse(result)
+
diff --git a/DAV/errors.py b/DAV/errors.py
new file mode 100755
index 0000000..c2b12d1
--- /dev/null
+++ b/DAV/errors.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+"""
+
+ Exceptions for the DAVserver implementation
+
+"""
+
+class DAV_Error(Exception):
+ """ in general we can have the following arguments:
+
+ 1. the error code
+ 2. the error result element, e.g. a <multistatus> element
+ """
+
+ def __init__(self,*args):
+ if len(args)==1:
+ self.args=(args[0],"")
+ else:
+ self.args=args
+
+class DAV_Secret(DAV_Error):
+ """ the user is not allowed to know anything about it
+
+ returning this for a property value means to exclude it
+ from the response xml element.
+ """
+
+ def __init__(self):
+ DAV_Error.__init__(self,0)
+ pass
+
+class DAV_NotFound(DAV_Error):
+ """ a requested property was not found for a resource """
+
+ def __init__(self,*args):
+ if len(args):
+ DAV_Error.__init__(self,404,args[0])
+ else:
+ DAV_Error.__init__(self,404)
+
+ pass
+
+class DAV_Forbidden(DAV_Error):
+ """ a method on a resource is not allowed """
+
+ def __init__(self,*args):
+ if len(args):
+ DAV_Error.__init__(self,403,args[0])
+ else:
+ DAV_Error.__init__(self,403)
+ pass
+
diff --git a/DAV/iface.py b/DAV/iface.py
new file mode 100644
index 0000000..dbe8e10
--- /dev/null
+++ b/DAV/iface.py
@@ -0,0 +1,255 @@
+"""
+
+basic interface class
+
+use this for subclassing when writing your own interface
+class.
+
+"""
+
+from errors import *
+
+import time
+from string import lower
+
+class dav_interface:
+ """ interface class for implementing DAV servers """
+
+ ### defined properties (modify this but let the DAV stuff there!)
+ ### the format is namespace: [list of properties]
+
+ PROPS={"DAV:" : ('creationdate',
+ 'displayname',
+ 'getcontentlanguage',
+ 'getcontentlength',
+ 'getcontenttype',
+ 'getetag',
+ 'getlastmodified',
+ 'lockdiscovery',
+ 'resourcetype',
+ 'source',
+ 'supportedlock'),
+ "NS2" : ("p1","p2")
+ }
+
+ # here we define which methods handle which namespace
+ # the first item is the namespace URI and the second one
+ # the method prefix
+ # e.g. for DAV:getcontenttype we call dav_getcontenttype()
+ M_NS={"DAV:" : "_get_dav",
+ "NS2" : "ns2" }
+
+ def get_propnames(self,uri):
+ """ return the property names allowed for the given URI
+
+ In this method we simply return the above defined properties
+ assuming that they are valid for any resource.
+ You can override this in order to return a different set
+ of property names for each resource.
+
+ """
+ return self.PROPS
+
+ def get_prop2(self,uri,ns,pname):
+ """ return the value of a property
+
+
+ """
+ if lower(ns)=="dav:": return self.get_dav(uri,pname)
+
+ raise DAV_NotFound
+
+ def get_prop(self,uri,ns,propname):
+ """ return the value of a given property
+
+ uri -- uri of the object to get the property of
+ ns -- namespace of the property
+ pname -- name of the property
+ """
+ if self.M_NS.has_key(ns):
+ prefix=self.M_NS[ns]
+ else:
+ raise DAV_NotFound
+ mname=prefix+"_"+propname
+ try:
+ m=getattr(self,mname)
+ r=m(uri)
+ return r
+ except AttributeError:
+ raise DAV_NotFound
+
+ ###
+ ### DATA methods (for GET and PUT)
+ ###
+
+ def get_data(self,uri):
+ """ return the content of an object
+
+ return data or raise an exception
+
+ """
+ raise DAV_NotFound
+
+ def put(self,uri,data):
+ """ write an object to the repository
+
+ return a result code or raise an exception
+ """
+
+ raise DAV_Forbidden
+
+ ###
+ ### Methods for DAV properties
+ ###
+
+ def _get_dav_creationdate(self,uri):
+ """ return the creationdate of a resource """
+ d=self.get_creationdate(uri)
+ # format it
+ return time.strftime("%Y-%m-%dT%H-%M-%SZ",time.localtime(d))
+
+ def _get_dav_getlastmodified(self,uri):
+ """ return the last modified date of a resource """
+ d=self.get_lastmodified(uri)
+ # format it
+ return time.strftime("%a, %d %b %Y %H:%M:%S %Z",time.localtime(d))
+
+
+ ###
+ ### OVERRIDE THESE!
+ ###
+
+ def get_creationdate(self,uri):
+ """ return the creationdate of the resource """
+ return time.time()
+
+ def get_lastmodified(self,uri):
+ """ return the last modification date of the resource """
+ return time.time()
+
+
+ ###
+ ### COPY MOVE DELETE
+ ###
+
+ ### methods for deleting a resource
+
+ def rmcol(self,uri):
+ """ delete a collection
+
+ This should not delete any children! This is automatically done
+ before by the DELETE class in DAV/delete.py
+
+ return a success code or raise an exception
+
+ """
+ raise DAV_NotFound
+
+ def rm(self,uri):
+ """ delete a single resource
+
+ return a success code or raise an exception
+
+ """
+ raise DAV_NotFound
+
+ """
+
+ COPY/MOVE HANDLER
+
+ These handler are called when a COPY or MOVE method is invoked by
+ a client. In the default implementation it works as follows:
+
+ - the davserver receives a COPY/MOVE method
+ - the davcopy or davmove module will be loaded and the corresponding
+ class will be initialized
+ - this class parses the query and decides which method of the interface class
+ to call:
+
+ copyone for a single resource to copy
+ copytree for a tree to copy (collection)
+ (the same goes for move of course).
+
+ - the interface class has now two options:
+ 1. to handle the action directly (e.g. cp or mv on filesystems)
+ 2. to let it handle via the copy/move methods in davcmd.
+
+ ad 1) The first approach can be used when we know that no error can
+ happen inside a tree or when the action can exactly tell which
+ element made which error. We have to collect these and return
+ it in a dict of the form {uri: error_code, ...}
+
+ ad 2) The copytree/movetree/... methods of davcmd.py will do the recursion
+ themselves and call for each resource the copy/move method of the
+ interface class. Thus method will then only act on a single resource.
+ (Thus a copycol on a normal unix filesystem actually only needs to do
+ an mkdir as the content will be copied by the davcmd.py function.
+ The davcmd.py method will also automatically collect all errors and
+ return the dictionary described above.
+ When you use 2) you also have to implement the copy() and copycol()
+ methods in your interface class. See the example for details.
+
+ To decide which approach is the best you have to decide if your application
+ is able to generate errors inside a tree. E.g. a function which completely
+ fails on a tree if one of the tree's childs fail is not what we need. Then
+ 2) would be your way of doing it.
+ Actually usually 2) is the better solution and should only be replaced by
+ 1) if you really need it.
+
+ The remaining question is if we should do the same for the DELETE method.
+
+ """
+
+ ### MOVE handlers
+
+ def moveone(self,src,dst,overwrite):
+ """ move one resource with Depth=0 """
+ return moveone(self,src,dst,overwrite)
+
+ def movetree(self,src,dst,overwrite):
+ """ move a collection with Depth=infinity """
+ return movetree(self,src,dst,overwrite)
+
+ ### COPY handlers
+
+ def copyone(self,src,dst,overwrite):
+ """ copy one resource with Depth=0 """
+ return copyone(self,src,dst,overwrite)
+
+ def copytree(self,src,dst,overwrite):
+ """ copy a collection with Depth=infinity """
+ return copytree(self,src,dst,overwrite)
+
+
+ ### low level copy methods (you only need these for method 2)
+ def copy(self,src,dst):
+ """ copy a resource with depth==0
+
+ You don't need to bother about overwrite or not.
+ This has been done already.
+
+ return a success code or raise an exception if something fails
+ """
+ return 201
+
+
+ def copycol(self,src,dst):
+ """ copy a resource with depth==infinity
+
+ You don't need to bother about overwrite or not.
+ This has been done already.
+
+ return a success code or raise an exception if something fails
+ """
+ return 201
+
+ ### some utility functions you need to implement
+
+ def exists(self,uri):
+ """ return 1 or None depending on if a resource exists """
+ return None # no
+
+ def is_collection(self,uri):
+ """ return 1 or None depending on if a resource is a collection """
+ return None # no
+
diff --git a/DAV/propfind.py b/DAV/propfind.py
new file mode 100755
index 0000000..9fdd981
--- /dev/null
+++ b/DAV/propfind.py
@@ -0,0 +1,334 @@
+#!/usr/bin/env python
+
+"""
+ python davserver
+ Copyright (C) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+
+
+from xml.dom import ext
+from xml.dom.Document import Document
+
+import sys
+import string
+import urlparse
+import urllib
+from StringIO import StringIO
+
+import utils
+from constants import COLLECTION, OBJECT, DAV_PROPS, RT_ALLPROP, RT_PROPNAME, RT_PROP
+from errors import *
+
+class PROPFIND:
+ """ parse a propfind xml element and extract props
+
+ It will set the following instance vars:
+
+ request_class : ALLPROP | PROPNAME | PROP
+ proplist : list of properties
+ nsmap : map of namespaces
+
+ The list of properties will contain tuples of the form
+ (element name, ns_prefix, ns_uri)
+
+
+ """
+
+
+ def __init__(self,uri,dataclass,depth):
+ self.request_type=None
+ self.nsmap={}
+ self.proplist={}
+ self.default_ns=None
+ self.__dataclass=dataclass
+ self.__depth=str(depth)
+ self.__uri=uri
+ self.__has_body=None # did we parse a body?
+
+ def read_propfind(self,xml_doc):
+ self.request_type,self.proplist,self.namespaces=utils.parse_propfind(xml_doc)
+
+ def createResponse(self):
+ """ create the multistatus response
+
+ This will be delegated to the specific method
+ depending on which request (allprop, propname, prop)
+ was found.
+
+ If we get a PROPNAME then we simply return the list with empty
+ values which we get from the interface class
+
+ If we get an ALLPROP we first get the list of properties and then
+ we do the same as with a PROP method.
+
+ """
+
+ if self.request_type==RT_ALLPROP:
+ return self.create_allprop()
+
+ if self.request_type==RT_PROPNAME:
+ return self.create_propname()
+
+ if self.request_type==RT_PROP:
+ return self.create_prop()
+
+ # no body means ALLPROP!
+ return self.create_allprop()
+
+ def create_propname(self):
+ """ create a multistatus response for the prop names """
+
+ dc=self.__dataclass
+ # create the document generator
+ doc = Document(None)
+ ms=doc.createElement("D:multistatus")
+ ms.setAttribute("xmlns:D","DAV:")
+ doc.appendChild(ms)
+
+ if self.__depth=="0":
+ pnames=dc.get_propnames(self.__uri)
+ re=self.mk_propname_response(self.__uri,pnames)
+ ms.appendChild(re)
+
+ elif self.__depth=="1":
+ pnames=dc.get_propnames(self.__uri)
+ re=self.mk_propname_response(self.__uri,pnames)
+ ms.appendChild(re)
+
+ for newuri in dc.get_childs(self.__uri):
+ pnames=dc.get_propnames(newuri)
+ re=self.mk_propname_response(newuri,pnames)
+ ms.appendChild(re)
+ # *** depth=="infinity"
+
+ sfile=StringIO()
+ ext.PrettyPrint(doc,stream=sfile)
+ s=sfile.getvalue()
+ sfile.close()
+ return s
+
+ def create_allprop(self):
+ """ return a list of all properties """
+ self.proplist={}
+ self.namespaces=[]
+ for ns,plist in self.__dataclass.get_propnames(self.__uri).items():
+ self.proplist[ns]=plist
+ self.namespaces.append(ns)
+
+ return self.create_prop()
+
+ def create_prop(self):
+ """ handle a <prop> request
+
+ This will
+
+ 1. set up the <multistatus>-Framework
+
+ 2. read the property values for each URI
+ (which is dependant on the Depth header)
+ This is done by the get_propvalues() method.
+
+ 3. For each URI call the append_result() method
+ to append the actual <result>-Tag to the result
+ document.
+
+ We differ between "good" properties, which have been
+ assigned a value by the interface class and "bad"
+ properties, which resulted in an error, either 404
+ (Not Found) or 403 (Forbidden).
+
+ """
+
+
+ # create the document generator
+ doc = Document(None)
+ ms=doc.createElement("D:multistatus")
+ ms.setAttribute("xmlns:D","DAV:")
+ doc.appendChild(ms)
+
+ if self.__depth=="0":
+ gp,bp=self.get_propvalues(self.__uri)
+ res=self.mk_prop_response(self.__uri,gp,bp,doc)
+ ms.appendChild(res)
+
+ elif self.__depth=="1":
+ gp,bp=self.get_propvalues(self.__uri)
+ res=self.mk_prop_response(self.__uri,gp,bp,doc)
+ ms.appendChild(res)
+
+ for newuri in self.__dataclass.get_childs(self.__uri):
+ gp,bp=self.get_propvalues(newuri)
+ res=self.mk_prop_response(newuri,gp,bp,doc)
+ ms.appendChild(res)
+
+ sfile=StringIO()
+ ext.PrettyPrint(doc,stream=sfile)
+ s=sfile.getvalue()
+ sfile.close()
+ return s
+
+
+ def mk_propname_response(self,uri,propnames,doc):
+ """ make a new <prop> result element for a PROPNAME request
+
+ This will simply format the propnames list.
+ propnames should have the format {NS1 : [prop1, prop2, ...], NS2: ...}
+
+ """
+ re=doc.createElement("D:response")
+
+ # write href information
+ uparts=urlparse.urlparse(uri)
+ fileloc=uparts[2]
+ href=doc.createElement("D:href")
+ huri=doc.createTextNode(urllib.quote(fileloc))
+ href.appendChild(huri)
+ re.appendChild(href)
+
+ ps=doc.createElement("D:propstat")
+ nsnum=0
+
+ for ns,plist in propnames.items():
+ # write prop element
+ pr=doc.createElement("D:prop")
+ nsp="ns"+str(nsnum)
+ pr.setAttribute("xmlns:"+nsp,ns)
+ nsnum=nsnum+1
+
+ # write propertynames
+ for p in plist:
+ pe=doc.createElement(nsp+":"+p)
+ pr.appendChild(pe)
+
+ ps.appendChild(pr)
+ re.appendChild(ps)
+
+ return re
+
+ def mk_prop_response(self,uri,good_props,bad_props,doc):
+ """ make a new <prop> result element
+
+ We differ between the good props and the bad ones for
+ each generating an extra <propstat>-Node (for each error
+ one, that means).
+
+ """
+ re=doc.createElement("D:response")
+ # append namespaces to response
+ nsnum=0
+ for nsname in self.namespaces:
+ re.setAttribute("xmlns:ns"+str(nsnum),nsname)
+ nsnum=nsnum+1
+
+ # write href information
+ uparts=urlparse.urlparse(uri)
+ fileloc=uparts[2]
+ href=doc.createElement("D:href")
+ huri=doc.createTextNode(urllib.quote(fileloc))
+ href.appendChild(huri)
+ re.appendChild(href)
+
+ # write good properties
+ if good_props:
+ ps=doc.createElement("D:propstat")
+ re.appendChild(ps)
+
+ gp=doc.createElement("D:prop")
+ for ns in good_props.keys():
+ ns_prefix="ns"+str(self.namespaces.index(ns))+":"
+ for p,v in good_props[ns].items():
+ pe=doc.createElement(ns_prefix+str(p))
+ if p=="resourcetype":
+ if v=="1":
+ ve=doc.createElement("D:collection")
+ pe.appendChild(ve)
+ else:
+ ve=doc.createTextNode(str(v))
+ pe.appendChild(ve)
+
+ gp.appendChild(pe)
+
+ ps.appendChild(gp)
+ s=doc.createElement("D:status")
+ t=doc.createTextNode("HTTP/1.1 200 OK")
+ s.appendChild(t)
+ ps.appendChild(s)
+ re.appendChild(ps)
+
+ # now write the errors!
+ if len(bad_props.items()):
+
+ # write a propstat for each error code
+ for ecode in bad_props.keys():
+ ps=doc.createElement("D:propstat")
+ re.appendChild(ps)
+ bp=doc.createElement("D:prop")
+ ps.appendChild(bp)
+
+ for ns in bad_props[ecode].keys():
+ ns_prefix="ns"+str(self.namespaces.index(ns))+":"
+
+ for p in bad_props[ecode][ns]:
+ pe=doc.createElement(ns_prefix+str(p))
+ bp.appendChild(pe)
+
+ s=doc.createElement("D:status")
+ t=doc.createTextNode(utils.gen_estring(ecode))
+ s.appendChild(t)
+ ps.appendChild(s)
+ re.appendChild(ps)
+
+ # return the new response element
+ return re
+
+ def get_propvalues(self,uri):
+ """ create lists of property values for an URI
+
+ We create two lists for an URI: the properties for
+ which we found a value and the ones for which we
+ only got an error, either because they haven't been
+ found or the user is not allowed to read them.
+
+ """
+ good_props={}
+ bad_props={}
+
+ for (ns,plist) in self.proplist.items():
+ good_props[ns]={}
+ bad_props={}
+ ec = 0
+ for prop in plist:
+ try:
+ r=self.__dataclass.get_prop(uri,ns,prop)
+ good_props[ns][prop]=str(r)
+ except DAV_Error, error_code:
+ ec=error_code[0]
+
+ # ignore props with error_code if 0 (invisible)
+ if ec==0: continue
+
+ if bad_props.has_key(ec):
+ if bad_props[ec].has_key(ns):
+ bad_props[ec][ns].append(prop)
+ else:
+ bad_props[ec][ns]=[prop]
+ else:
+ bad_props[ec]={ns:[prop]}
+
+ return good_props, bad_props
+
diff --git a/DAV/status.py b/DAV/status.py
new file mode 100644
index 0000000..47e1b32
--- /dev/null
+++ b/DAV/status.py
@@ -0,0 +1,30 @@
+"""
+
+status codes for DAV services
+
+
+"""
+
+
+STATUS_CODES={
+ 102: "Processing",
+ 200: "Ok",
+ 201: "Created",
+ 204: "No Content",
+ 207: "Multi-Status",
+ 201: "Created",
+ 400: "Bad Request",
+ 403: "Forbidden",
+ 404: "Not Found",
+ 405: "Method Not Allowed",
+ 409: "Conflict",
+ 412: "Precondition failed",
+ 423: "Locked",
+ 415: "Unsupported Media Type",
+ 507: "Insufficient Storage",
+ 422: "Unprocessable Entity",
+ 423: "Locked",
+ 424: "Failed Dependency",
+ 502: "Bad Gateway",
+ 507: "Insufficient Storage"
+}
diff --git a/DAV/utils.py b/DAV/utils.py
new file mode 100755
index 0000000..910619a
--- /dev/null
+++ b/DAV/utils.py
@@ -0,0 +1,160 @@
+#!/usr/bin/env python
+
+"""
+
+UTILITIES
+
+- parse a propfind request body into a list of props
+
+"""
+
+from xml.dom import ext
+from xml.dom.Document import Document
+from xml.dom.ext.reader import PyExpat
+from xml.dom import Node
+from xml.dom import NodeIterator, NodeFilter
+
+from string import lower, split, atoi, joinfields
+import urlparse
+from StringIO import StringIO
+
+from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP
+from status import STATUS_CODES
+
+VERSION = '0.6'
+AUTHOR = 'Simon Pamies <s.pamies at banality.de>'
+
+
+def gen_estring(ecode):
+ """ generate an error string from the given code """
+ ec=atoi(str(ecode))
+ if STATUS_CODES.has_key(ec):
+ return "HTTP/1.1 %s %s" %(ec,STATUS_CODES[ec])
+ else:
+ return "HTTP/1.1 %s" %(ec)
+
+def parse_propfind(xml_doc):
+ """ parse an propfind xml file and return a list of props
+
+ returns:
+
+ request_type -- ALLPROP, PROPNAME, PROP
+ proplist -- list of properties found
+ namespaces -- list of namespaces found
+
+ """
+ doc = PyExpat.Reader().fromString(xml_doc)
+ snit = doc.createNodeIterator(doc, NodeFilter.NodeFilter.SHOW_ELEMENT, None, None)
+
+ request_type=None
+ props={}
+ namespaces=[]
+
+ while 1:
+ curr_elem = snit.nextNode()
+ if not curr_elem: break
+ ename=fname=lower(curr_elem.nodeName)
+ if ":" in fname:
+ ename=split(fname,":")[1]
+ if ename=="prop": request_type=RT_PROP; continue
+ if ename=="propfind": continue
+ if ename=="allprop": request_type=RT_ALLPROP; continue
+ if ename=="propname": request_type=RT_PROPNAME; continue
+
+ # rest should be names of attributes
+
+ ns = curr_elem.namespaceURI
+ if props.has_key(ns):
+ props[ns].append(ename)
+ else:
+ props[ns]=[ename]
+ namespaces.append(ns)
+
+ return request_type,props,namespaces
+
+
+def create_treelist(dataclass,uri):
+ """ create a list of resources out of a tree
+
+ This function is used for the COPY, MOVE and DELETE methods
+
+ uri - the root of the subtree to flatten
+
+ It will return the flattened tree as list
+
+ """
+ queue=[uri]
+ list=[uri]
+ while len(queue):
+ element=queue[-1]
+ if dataclass.is_collection(element):
+ childs=dataclass.get_childs(element)
+ else:
+ childs=[]
+ if len(childs):
+ list=list+childs
+ # update queue
+ del queue[-1]
+ if len(childs):
+ queue=queue+childs
+ return list
+
+def is_prefix(uri1,uri2):
+ """ returns 1 of uri1 is a prefix of uri2 """
+ if uri2[:len(uri1)]==uri1:
+ return 1
+ else:
+ return None
+
+def quote_uri(uri):
+ """ quote an URL but not the protocol part """
+ import urlparse
+ import urllib
+
+ up=urlparse.urlparse(uri)
+ np=urllib.quote(up[2])
+ return urlparse.urlunparse((up[0],up[1],np,up[3],up[4],up[5]))
+
+def get_uriparentpath(uri):
+ """ extract the uri path and remove the last element """
+ up=urlparse.urlparse(uri)
+ return joinfields(split(up[2],"/")[:-1],"/")
+
+def get_urifilename(uri):
+ """ extract the uri path and return the last element """
+ up=urlparse.urlparse(uri)
+ return split(up[2],"/")[-1]
+
+def get_parenturi(uri):
+ """ return the parent of the given resource"""
+ up=urlparse.urlparse(uri)
+ np=joinfields(split(up[2],"/")[:-1],"/")
+ return urlparse.urlunparse((up[0],up[1],np,up[3],up[4],up[5]))
+
+### XML utilities
+
+def make_xmlresponse(result):
+ """ construct a response from a dict of uri:error_code elements """
+ doc = Document.Document(None)
+ ms=doc.createElement("D:multistatus")
+ ms.setAttribute("xmlns:D","DAV:")
+ doc.appendChild(ms)
+
+ for el,ec in result.items():
+ re=doc.createElement("D:response")
+ hr=doc.createElement("D:href")
+ st=doc.createElement("D:status")
+ huri=doc.createTextNode(quote_uri(el))
+ t=doc.createTextNode(gen_estring(ec))
+ st.appendChild(t)
+ hr.appendChild(huri)
+ re.appendChild(hr)
+ re.appendChild(st)
+ ms.appendChild(re)
+
+ sfile=StringIO()
+ ext.PrettyPrint(doc,stream=sfile)
+ s=sfile.getvalue()
+ sfile.close()
+ return s
+
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..ecce054
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,23 @@
+How to install python WebDAV server
+-------------------------------
+
+1. Check prerequisites
+
+ + *nix OS (including Mac OS X)
+ Will not run on Windows!
+
+ + Python 2.x
+ + PyXML
+
+2. Run setup.py
+
+ > sudo python setup.py install
+
+4. Change to the PyDAVServer directory and start server with
+
+ > ./server.py -h
+
+5. Enjoy
+
+Please send bugs and feature requests to
+ s.pamies at banality.de
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..40f4b8a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,480 @@
+
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the library GPL. It is
+ numbered 2 because it goes with version 2 of the ordinary GPL.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Library General Public License, applies to some
+specially designated Free Software Foundation software, and to any
+other libraries whose authors decide to use it. You can use it for
+your libraries, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if
+you distribute copies of the library, or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link a program with the library, you must provide
+complete object files to the recipients so that they can relink them
+with the library, after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ Our method of protecting your rights has two steps: (1) copyright
+the library, and (2) offer you this license which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ Also, for each distributor's protection, we want to make certain
+that everyone understands that there is no warranty for this free
+library. If the library is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original
+version, so that any problems introduced by others will not reflect on
+the original authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that companies distributing free
+software will individually obtain patent licenses, thus in effect
+transforming the program into proprietary software. To prevent this,
+we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+ Most GNU software, including some libraries, is covered by the ordinary
+GNU General Public License, which was designed for utility programs. This
+license, the GNU Library General Public License, applies to certain
+designated libraries. This license is quite different from the ordinary
+one; be sure to read it in full, and don't assume that anything in it is
+the same as in the ordinary license.
+
+ The reason we have a separate public license for some libraries is that
+they blur the distinction we usually make between modifying or adding to a
+program and simply using it. Linking a program with a library, without
+changing the library, is in some sense simply using the library, and is
+analogous to running a utility program or application program. However, in
+a textual and legal sense, the linked executable is a combined work, a
+derivative of the original library, and the ordinary General Public License
+treats it as such.
+
+ Because of this blurred distinction, using the ordinary General
+Public License for libraries did not effectively promote software
+sharing, because most developers did not use the libraries. We
+concluded that weaker conditions might promote sharing better.
+
+ However, unrestricted linking of non-free programs would deprive the
+users of those programs of all benefit from the free status of the
+libraries themselves. This Library General Public License is intended to
+permit developers of non-free programs to use free libraries, while
+preserving your freedom as a user of such programs to change the free
+libraries that are incorporated in them. (We have not seen how to achieve
+this as regards changes in header files, but we have achieved it as regards
+changes in the actual functions of the Library.) The hope is that this
+will lead to faster development of free libraries.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, while the latter only
+works together with the library.
+
+ Note that it is possible for a library to be covered by the ordinary
+General Public License rather than by this special one.
+
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library which
+contains a notice placed by the copyright holder or other authorized
+party saying it may be distributed under the terms of this Library
+General Public License (also called "this License"). Each licensee is
+addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also compile or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ c) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ d) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the source code distributed need not include anything that is normally
+distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Library General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
diff --git a/PyDAVServer/daemonize.py b/PyDAVServer/daemonize.py
new file mode 100755
index 0000000..0c59417
--- /dev/null
+++ b/PyDAVServer/daemonize.py
@@ -0,0 +1,128 @@
+'''
+ This module is used to fork the current process into a daemon.
+ Almost none of this is necessary (or advisable) if your daemon
+ is being started by inetd. In that case, stdin, stdout and stderr are
+ all set up for you to refer to the network connection, and the fork()s
+ and session manipulation should not be done (to avoid confusing inetd).
+ Only the chdir() and umask() steps remain as useful.
+ References:
+ UNIX Programming FAQ
+ 1.7 How do I get my program to act like a daemon?
+ http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
+ Advanced Programming in the Unix Environment
+ W. Richard Stevens, 1992, Addison-Wesley, ISBN 0-201-56317-7.
+
+ History:
+ 2005/06/23 by Simon Pamies
+ 2001/07/10 by Juergen Hermann
+ 2002/08/28 by Noah Spurrier
+ 2003/02/24 by Clark Evans
+
+ http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012
+'''
+import sys, os, time
+from signal import SIGTERM
+
+def deamonize(stdout='/dev/null', stderr=None, stdin='/dev/null',
+ pidfile=None, startmsg = 'started with pid %s' ):
+ '''
+ This forks the current process into a daemon.
+ The stdin, stdout, and stderr arguments are file names that
+ will be opened and be used to replace the standard file descriptors
+ in sys.stdin, sys.stdout, and sys.stderr.
+ These arguments are optional and default to /dev/null.
+ Note that stderr is opened unbuffered, so
+ if it shares a file with stdout then interleaved output
+ may not appear in the order that you expect.
+ '''
+ # Do first fork.
+ try:
+ pid = os.fork()
+ if pid > 0: sys.exit(0) # Exit first parent.
+ except OSError, e:
+ sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
+ sys.exit(1)
+
+ # Decouple from parent environment.
+ os.chdir("/")
+ os.umask(0)
+ os.setsid()
+
+ # Do second fork.
+ try:
+ pid = os.fork()
+ if pid > 0: sys.exit(0) # Exit second parent.
+ except OSError, e:
+ sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
+ sys.exit(1)
+
+ # Open file descriptors and print start message
+ if not stderr: stderr = stdout
+ si = file(stdin, 'r')
+ so = file(stdout, 'a+')
+ se = file(stderr, 'a+', 0)
+ pid = str(os.getpid())
+ sys.stderr.write("\n%s\n" % startmsg % pid)
+ sys.stderr.flush()
+ if pidfile: file(pidfile,'w+').write("%s\n" % pid)
+
+ if sys.stdin.closed: sys.stdin = open('/dev/null', 'r')
+ if sys.stdout.closed: sys.stdout = open('/dev/null', 'a+')
+ if sys.stderr.closed: sys.stderr = open('/dev/null', 'a+')
+
+ # Redirect standard file descriptors.
+ os.dup2(si.fileno(), sys.stdin.fileno())
+ os.dup2(so.fileno(), sys.stdout.fileno())
+ os.dup2(se.fileno(), sys.stderr.fileno())
+
+def startstop(stdout='/dev/null', stderr=None, stdin='/dev/null',
+ pidfile='pid.txt', startmsg = 'started with pid %s', action='start' ):
+ if action:
+ try:
+ pf = file(pidfile,'r')
+ pid = int(pf.read().strip())
+ pf.close()
+ except IOError:
+ pid = None
+
+ if 'stop' == action or 'restart' == action:
+ if not pid:
+ mess = "Could not stop, pid file '%s' missing.\n"
+ sys.stderr.write(mess % pidfile)
+ if 'stop' == action:
+ sys.exit(1)
+ action = 'start'
+ pid = None
+ else:
+ try:
+ while 1:
+ os.kill(pid,SIGTERM)
+ time.sleep(1)
+ except OSError, err:
+ err = str(err)
+ if err.find("No such process") > 0:
+ os.remove(pidfile)
+ if 'stop' == action:
+ sys.exit(0)
+ action = 'start'
+ pid = None
+ else:
+ print str(err)
+ sys.exit(1)
+
+ if 'start' == action:
+ if pid:
+ mess = "Start aborded since pid file '%s' exists.\n"
+ sys.stderr.write(mess % pidfile)
+ sys.exit(1)
+
+ deamonize(stdout,stderr,stdin,pidfile,startmsg)
+ return
+
+ if 'status' == action:
+ if not pid:
+ sys.stderr.write('Status: Stopped\n')
+
+ else: sys.stderr.write('Status: Running\n')
+ sys.exit(0)
+
diff --git a/PyDAVServer/fileauth.py b/PyDAVServer/fileauth.py
new file mode 100755
index 0000000..002612e
--- /dev/null
+++ b/PyDAVServer/fileauth.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+
+"""
+Python WebDAV Server.
+Copyright (C) 1999 Christian Scholz (ruebe at aachen.heimat.de)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+This is an example implementation of a DAVserver using the DAV package.
+
+"""
+
+
+from DAV.WebDAVServer import DAVRequestHandler
+from fshandler import FilesystemHandler
+import sys
+
+class DAVAuthHandler(DAVRequestHandler):
+ """
+ Provide real auth check based on filesystem files
+ """
+
+ # Do not forget to set IFACE_CLASS by caller
+ # ex.: IFACE_CLASS = FilesystemHandler('/tmp', 'http://localhost/')
+
+ verbose = False
+
+ def _log(self, message):
+ if self.verbose:
+ print >>sys.stderr, '>> (DAVAuthHandler) %s' % message
+
+ def get_userinfo(self,user,pw):
+ """ authenticate user """
+
+ if user == self.auth_user and pw == self.auth_pass:
+ self._log('Successfully authenticated user %s' % user)
+ return 1
+
+ self._log('Authentication failed for user %s' % user)
+ return None
+
diff --git a/PyDAVServer/fshandler.py b/PyDAVServer/fshandler.py
new file mode 100755
index 0000000..f7a1d8a
--- /dev/null
+++ b/PyDAVServer/fshandler.py
@@ -0,0 +1,386 @@
+
+import sys
+import urlparse
+import os
+import time
+from string import joinfields, split, lower
+
+from DAV.constants import COLLECTION, OBJECT
+from DAV.errors import *
+from DAV.iface import *
+
+from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree
+
+class FilesystemHandler(dav_interface):
+ """
+ Model a filesystem for DAV
+
+ This class models a regular filesystem for the DAV server
+
+ The basic URL will be http://localhost/
+ And the underlying filesystem will be /tmp
+
+ Thus http://localhost/gfx/pix will lead
+ to /tmp/gfx/pix
+
+ """
+
+ def __init__(self, directory, uri, verbose=False):
+ self.setDirectory(directory)
+ self.setBaseURI(uri)
+
+ # should we be verbose?
+ self.verbose = verbose
+ self._log('Initialized with %s-%s' % (directory, uri))
+
+ def _log(self, message):
+ if self.verbose:
+ print >>sys.stderr, '>> (FilesystemHandler) %s' % message
+
+ def setDirectory(self, path):
+ """ Sets the directory """
+
+ if not os.path.isdir(path):
+ raise Exception, '%s not must be a directory!' % path
+
+ self.directory = path
+
+ def setBaseURI(self, uri):
+ """ Sets the base uri """
+
+ self.baseuri = uri
+
+ def uri2local(self,uri):
+ """ map uri in baseuri and local part """
+
+ uparts=urlparse.urlparse(uri)
+ fileloc=uparts[2][1:]
+ filename=os.path.join(self.directory,fileloc)
+ return filename
+
+ def local2uri(self,filename):
+ """ map local filename to self.baseuri """
+
+ pnum=len(split(self.directory,"/"))
+ parts=split(filename,"/")[pnum:]
+ sparts="/"+joinfields(parts,"/")
+ uri=urlparse.urljoin(self.baseuri,sparts)
+ return uri
+
+
+ def get_childs(self,uri):
+ """ return the child objects as self.baseuris for the given URI """
+
+ fileloc=self.uri2local(uri)
+ filelist=[]
+
+ if os.path.exists(fileloc):
+ if os.path.isdir(fileloc):
+ try:
+ files=os.listdir(fileloc)
+
+ # silently ignore directories
+ # not allowed.
+ except:
+ raise DAV_Forbidden
+
+ for file in files:
+ newloc=os.path.join(fileloc,file)
+ filelist.append(self.local2uri(newloc))
+
+ self._log('get_childs: Childs %s' % filelist)
+
+ return filelist
+
+ def get_data(self,uri):
+ """ return the content of an object """
+ path=self.uri2local(uri)
+ if os.path.exists(path):
+ if os.path.isfile(path):
+ s=""
+ fp=open(path,"r")
+ while 1:
+ a=fp.read()
+ if not a: break
+ s=s+a
+ fp.close()
+ self._log('Serving content of %s' % uri)
+ return s
+ else:
+ # also raise an error for collections
+ # don't know what should happen then..
+ self._log('get_data: %s not found' % path)
+
+ raise DAV_NotFound
+
+ def _get_dav_resourcetype(self,uri):
+ """ return type of object """
+ path=self.uri2local(uri)
+ if os.path.isfile(path):
+ return OBJECT
+ else:
+ return COLLECTION
+
+ def _get_dav_displayname(self,uri):
+ raise DAV_Secret # do not show
+
+ def _get_dav_getcontentlength(self,uri):
+ """ return the content length of an object """
+ path=self.uri2local(uri)
+ if os.path.exists(path):
+ if os.path.isfile(path):
+ s=os.stat(path)
+ return str(s[6])
+
+ return '0'
+
+ def get_lastmodified(self,uri):
+ """ return the last modified date of the object """
+ path=self.uri2local(uri)
+ if os.path.exists(path):
+ s=os.stat(path)
+ date=s[8]
+ return date
+
+ raise DAV_NotFound
+
+ def get_creationdate(self,uri):
+ """ return the last modified date of the object """
+ path=self.uri2local(uri)
+ if os.path.exists(path):
+ s=os.stat(path)
+ date=s[9]
+ return date
+
+ raise DAV_NotFound
+
+ def _get_dav_getcontenttype(self,uri):
+ """ find out yourself! """
+
+ path=self.uri2local(uri)
+ if os.path.exists(path):
+ if os.path.isfile(path):
+ return "application/octet-stream"
+ else:
+ return "httpd/unix-directory"
+
+ else:
+ raise DAV_NotFound, 'Could not find %s' % path
+
+ def put(self,uri,data,content_type=None):
+ """ put the object into the filesystem """
+ path=self.uri2local(uri)
+ try:
+ fp=open(path,"w+")
+ fp.write(data)
+ fp.close()
+ self._log('put: Created %s' % uri)
+ except:
+ self._log('put: Could not create %s' % uri)
+ raise DAV_Error, 424
+
+ return None
+
+ def mkcol(self,uri):
+ """ create a new collection """
+ path=self.uri2local(uri)
+ # remove leading slash
+ if path[-1]=="/": path=path[:-1]
+
+ # test if file already exists
+ if os.path.exists(path):
+ raise DAV_Error,405
+
+ # test if parent exists
+ h,t=os.path.split(path)
+ if not os.path.exists(h):
+ raise DAV_Error, 409
+
+ # test, if we are allowed to create it
+ try:
+ os.system("mkdir '%s'" % path)
+ self._log('mkcol: Created new collection %s' % path)
+ return 201
+ except:
+ self._log('mkcol: Creation of %s denied' % path)
+ raise DAV_Forbidden
+
+ ### ?? should we do the handler stuff for DELETE, too ?
+ ### (see below)
+
+ def rmcol(self,uri):
+ """ delete a collection """
+ path=self.uri2local(uri)
+ if not os.path.exists(path):
+ raise DAV_NotFound
+
+ if not os.system("rmdir '%s'" %path):
+ return 204
+ else:
+ raise DAV_Forbidden # forbidden
+
+ def rm(self,uri):
+ """ delete a normal resource """
+ path=self.uri2local(uri)
+ if not os.path.exists(path):
+ raise DAV_NotFound
+ if not os.system("rm -f '%s'" %path):
+ return 204
+ else:
+ raise DAV_Forbidden # forbidden
+
+ ###
+ ### DELETE handlers (examples)
+ ### (we use the predefined methods in davcmd instead of doing
+ ### a rm directly
+ ###
+
+ def delone(self,uri):
+ """ delete a single resource
+
+ You have to return a result dict of the form
+ uri:error_code
+ or None if everything's ok
+
+ """
+ return delone(self,uri)
+
+ def deltree(self,uri):
+ """ delete a collection
+
+ You have to return a result dict of the form
+ uri:error_code
+ or None if everything's ok
+ """
+
+ return deltree(self,uri)
+
+
+ ###
+ ### MOVE handlers (examples)
+ ###
+
+ def moveone(self,src,dst,overwrite):
+ """ move one resource with Depth=0
+
+ an alternative implementation would be
+
+ result_code=201
+ if overwrite:
+ result_code=204
+ r=os.system("rm -f '%s'" %dst)
+ if r: return 412
+ r=os.system("mv '%s' '%s'" %(src,dst))
+ if r: return 412
+ return result_code
+
+ (untested!). This would not use the davcmd functions
+ and thus can only detect errors directly on the root node.
+ """
+ return moveone(self,src,dst,overwrite)
+
+ def movetree(self,src,dst,overwrite):
+ """ move a collection with Depth=infinity
+
+ an alternative implementation would be
+
+ result_code=201
+ if overwrite:
+ result_code=204
+ r=os.system("rm -rf '%s'" %dst)
+ if r: return 412
+ r=os.system("mv '%s' '%s'" %(src,dst))
+ if r: return 412
+ return result_code
+
+ (untested!). This would not use the davcmd functions
+ and thus can only detect errors directly on the root node"""
+
+ return movetree(self,src,dst,overwrite)
+
+ ###
+ ### COPY handlers
+ ###
+
+ def copyone(self,src,dst,overwrite):
+ """ copy one resource with Depth=0
+
+ an alternative implementation would be
+
+ result_code=201
+ if overwrite:
+ result_code=204
+ r=os.system("rm -f '%s'" %dst)
+ if r: return 412
+ r=os.system("cp '%s' '%s'" %(src,dst))
+ if r: return 412
+ return result_code
+
+ (untested!). This would not use the davcmd functions
+ and thus can only detect errors directly on the root node.
+ """
+ return copyone(self,src,dst,overwrite)
+
+ def copytree(self,src,dst,overwrite):
+ """ copy a collection with Depth=infinity
+
+ an alternative implementation would be
+
+ result_code=201
+ if overwrite:
+ result_code=204
+ r=os.system("rm -rf '%s'" %dst)
+ if r: return 412
+ r=os.system("cp -r '%s' '%s'" %(src,dst))
+ if r: return 412
+ return result_code
+
+ (untested!). This would not use the davcmd functions
+ and thus can only detect errors directly on the root node"""
+
+ return copytree(self,src,dst,overwrite)
+
+ ###
+ ### copy methods.
+ ### This methods actually copy something. low-level
+ ### They are called by the davcmd utility functions
+ ### copytree and copyone (not the above!)
+ ### Look in davcmd.py for further details.
+ ###
+
+ def copy(self,src,dst):
+ """ copy a resource from src to dst """
+ srcfile=self.uri2local(src)
+ dstfile=self.uri2local(dst)
+ try:
+ os.system("cp '%s' '%s'" %(srcfile,dstfile))
+ except:
+ raise DAV_Error, Forbidden
+
+ def copycol(self,src,dst):
+ """ copy a collection.
+
+ As this is not recursive (the davserver recurses itself)
+ we will only create a new directory here. For some more
+ advanced systems we might also have to copy properties from
+ the source to the destination.
+ """
+
+ return self.mkcol(dst)
+
+
+ def exists(self,uri):
+ """ test if a resource exists """
+ path=self.uri2local(uri)
+ if os.path.exists(path):
+ return 1
+ return None
+
+ def is_collection(self,uri):
+ """ test if the given uri is a collection """
+ path=self.uri2local(uri)
+ if os.path.isdir(path):
+ return 1
+ else:
+ return 0
+
diff --git a/PyDAVServer/server.py b/PyDAVServer/server.py
new file mode 100755
index 0000000..552e90e
--- /dev/null
+++ b/PyDAVServer/server.py
@@ -0,0 +1,205 @@
+#!/usr/bin/env python
+
+"""
+Python WebDAV Server.
+Copyright (C) 1999-2005 Christian Scholz (cs at comlounge.net)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+This is an example implementation of a DAVserver using the DAV package.
+
+"""
+
+import getopt, sys, os
+import BaseHTTPServer
+
+try:
+ import DAV
+except ImportError:
+ print 'DAV package not found! Please install into site-packages or set PYTHONPATH!'
+ sys.exit(2)
+
+try:
+ from xml.dom import ext
+except ImportError:
+ print 'PyXML not found! Get it from http://pyxml.sourceforge.net/'
+ sys.exit(2)
+
+from DAV.utils import VERSION, AUTHOR
+__version__ = VERSION
+__author__ = AUTHOR
+
+from fileauth import DAVAuthHandler
+from fshandler import FilesystemHandler
+from daemonize import startstop
+
+def runserver(
+ port = 8008, host='localhost',
+ directory='/tmp',
+ verbose = False,
+ noauth = False,
+ user = '',
+ password = '',
+ handler = DAVAuthHandler,
+ server = BaseHTTPServer.HTTPServer):
+
+ directory = directory.strip()
+ host = host.strip()
+
+ if not os.path.isdir(directory):
+ print >>sys.stderr, '>> ERROR: %s is not a valid directory!' % directory
+ return sys.exit(233)
+
+ # basic checks against wrong hosts
+ if host.find('/') != -1 or host.find(':') != -1:
+ print >>sys.stderr, '>> ERROR: Malformed host %s' % host
+ return sys.exit(233)
+
+ # no root directory
+ if directory == '/':
+ print >>sys.stderr, '>> ERROR: Root directory not allowed!'
+ sys.exit(233)
+
+ # dispatch directory and host to the filesystem handler
+ handler.IFACE_CLASS = FilesystemHandler(directory, 'http://%s:%s/' % (host, port), verbose )
+
+ # put some extra vars
+ handler.verbose = verbose
+ if noauth:
+ print >>sys.stderr, '>> ATTENTION: Authentication disabled!'
+ handler.DO_AUTH = False
+
+ else:
+ handler.auth_user = user
+ handler.auth_pass = password
+
+ print >>sys.stderr, '>> Serving data from %s' % directory
+
+ # initialize server on specified port
+ runner = server( (host, port), handler )
+ print >>sys.stderr, '>> Listening on %s (%i)' % (host, port)
+
+ if verbose:
+ print >>sys.stderr, '>> Verbose mode ON'
+
+ print ''
+
+ try:
+ runner.serve_forever()
+ except KeyboardInterrupt:
+ print >>sys.stderr, '\n>> Killed by user'
+
+usage = """PyWebDAV server (version %s)
+Standalone WebDAV server based on python
+
+Usage: ./server.py [OPTIONS]
+Parameters:
+ -D, --directory Directory where to serve data from
+ The user that runs this server must have permissions
+ on that directory. NEVER run as root!
+ Default directory is /tmp
+
+ -H, --host Host where to listen on (default: localhost)
+ -P, --port Port to bind server to (default: 8008)
+ -u, --user Username for authentication
+ -p, --password Password for given user
+ -n, --noauth Pass parameter if server should not ask for authentication
+ -d, --daemon Make server act like a daemon. That means that it is going
+ to the background mode. All messages are redirected to
+ logfiles (default: /tmp/pydav.log and /tmp/pydav.err).
+ You need to pass one of the following values to this parameter
+ start - Start daemon
+ stop - Stop daemon
+ restart - Restart complete server
+ status - Returns status of server
+
+ -v, --verbose Be verbose
+ -h, --help Show this screen
+
+Please send bug reports and feature requests to %s
+""" % (__version__, __author__)
+
+if __name__ == '__main__':
+
+ verbose = False
+ directory = '/tmp'
+ port = 8008
+ host = 'localhost'
+ noauth = False
+ user = ''
+ password = ''
+ daemonize = False
+ daemonaction = 'start'
+
+ # parse commandline
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], 'P:D:H:d:u:p:nvh',
+ ['host=', 'port=', 'directory=', 'user=', 'password=','daemon=', 'noauth', 'help', 'verbose'])
+ except getopt.GetoptError, e:
+ print usage
+ print '>>>> ERROR: %s' % str(e)
+ sys.exit(2)
+
+ for o,a in opts:
+ if o in ['-D', '--directory']:
+ directory = a
+
+ if o in ['-H', '--host']:
+ host = a
+
+ if o in ['-P', '--p']:
+ port = a
+
+ if o in ['-v', '--verbose']:
+ verbose = True
+
+ if o in ['-h', '--help']:
+ print usage
+ sys.exit(2)
+
+ if o in ['-n', '--noauth']:
+ noauth = True
+
+ if o in ['-u', '--user']:
+ user = a
+
+ if o in ['-p', '--password']:
+ password = a
+
+ if o in ['-d', '--daemon']:
+ daemonize = True
+ daemonaction = a
+
+ print >>sys.stderr, 'Starting up PyWebDAV server (version %s)' % __version__
+ if not noauth and daemonaction != 'status':
+ if not user:
+ print >>sys.stderr, '>> ERROR: Please specify an username or pass parameter --noauth'
+ sys.exit(3)
+
+ if daemonaction == 'status':
+ print >>sys.stdout, 'Checking for status...'
+
+ if type(port) == type(''):
+ port = int(port.strip())
+
+ if daemonize:
+ startstop(stdout='/tmp/pydav.log',
+ stderr='/tmp/pydav.err',
+ pidfile='/tmp/pydav.pid',
+ startmsg='>> Started PyWebDAV (PID: %s)',
+ action=daemonaction)
+
+ # start now
+ runserver(port, host, directory, verbose, noauth, user, password)
diff --git a/README b/README
new file mode 100644
index 0000000..aa10c2b
--- /dev/null
+++ b/README
@@ -0,0 +1,107 @@
+
+I want to introduce you to my webdav server implementation in Python. Actually
+it's supposed to be a generic class which can be used for all sorts of servers.
+Thus the actual webdav part and the data interface part is implemented in two
+modules. All you have to do in order to create a wevdav server for you
+application is to write an interface class.
+
+A working WebDAV Server is included in the PyDAVServer package. Feel free to
+extend it to your needs. Please send suggestions and bugfixes to the author(s).
+
+AUTHOR(s)
+------
+
+Simon Pamies (Current maintainer)
+Bielefeld, Germany
+s.pamies at banality.de
+
+Christian Scholz
+Aachen, Germany
+mrtopf at webdav.de
+
+REQUIREMENTS
+------------
+
+- Python 2.0 or higher (www.python.org)
+- PyXML 0.66 (pyxml.sourceforge.net)
+
+INSTALLATION
+------------
+
+see INSTALL file
+
+What is WebDAV?
+---------------
+
+http://www.webdav.org.
+http://www.ietf.org/rfc/rfc2518.txt
+
+What is Python?
+---------------
+
+http://www.python.org.
+http://diveintopython.org/
+
+Contents
+--------
+
+Here is a little overview of the package:
+
+A. In the DAV/ 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
+
+ This works on top of either the BasicHTTPServer or the
+ BufferingHTTPServer and implements basic authentication.
+
+ 3. 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:
+
+ 1. server.py
+ Main file for server. Serves as a start point for the WebDAV server
+
+ 2. fshandler.py
+ Backend for the DAV server. Makes him serving content from the filesystem.
+ Have a deeper look at it if you want to implement backends to other data sources
+
+ 3. fileauth.py
+ Handler for authentication. Nothing very special about it.
+
+
+NOTES
+-----
+
+Right now some parts are missing like handling of ALLPROP and PROPNAMES as
+the whole proppatch method. Also not everything is tested and the return
+codes might not be the right ones in every case.
+
+I plan to adjust things in order to work with my groupware project which
+I then will release (actually it's my thesis I am working on).
+
+Look inside the file TODO for things which needs to be done and will be done
+in the near future.
+
+If you find bugs (many!) or have suggestions then please email the maintainer
+
+Thanks :)
+
+LICENSE
+-------
+
+see LICENSE file
+
+That's all there is to it!
+
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..8fcde44
--- /dev/null
+++ b/TODO
@@ -0,0 +1,49 @@
+GENERAL
+-------
+
+- web page needs to get done:
+ - Download
+ - News
+ - TODO list
+ - Changes
+ - Name
+
+MOVE
+----
+
+needs to get implemented (will get used for renaming files and dirs)
+
+
+PROPFIND
+--------
+
+- PROPNAMES need to get implemented
+- only DAV properties are possible right now
+- Depth=infinity should be implemented
+
+
+GET
+---
+
+- guessing type?
+
+
+HEAD
+----
+
+to be done
+
+
+PROPPATCH
+---------
+
+to be done (not that important as only DAV props are supported right now which
+you cannot change)
+
+data.py
+-------
+
+- implement proppatch? As example we might use some sort of database for
+ storing.. (to much trouble for a simple example? Better show it with
+ the groupware stuff?)
+
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..5a2a580
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.6
diff --git a/doc/interface_class b/doc/interface_class
new file mode 100644
index 0000000..74946b4
--- /dev/null
+++ b/doc/interface_class
@@ -0,0 +1,133 @@
+How to write an interface class
+-------------------------------
+
+(this information might be a little out of date. See data.py for more
+details).
+
+The interface class of davserver is the interface between the actual data
+and the davserver. The davserver will ask this class every time it needs
+information about the underlying data (e.g. a filesystem or a database).
+
+So how do you write such a class?
+
+Simply take the existing class which models a normal unix filesystem
+and change it. You actually have implement the following methods:
+
+
+
+get_childs(self,uri)
+
+ This method should return a list of all childs for the
+ object specified by the given uri.
+
+ The childs should be specified as normal URIs.
+
+
+get_props(self,uri,values=None,all=None,proplist=[])
+
+ This method will be called when the davserver needs information
+ 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
+
+ The result of this method should be a dictionary of the form
+
+ props[propname]=propvalue
+
+ Note that in the example class this one is simply a dummy class
+ as only DAV properties are handled which have their own methods
+ (see below).
+
+
+get_data(self,uri)
+
+ This method will be called when the content of an object is needed.
+ Thus this method should return a data string.
+
+
+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_<propname> method. You maybe should
+ handle it the same way.
+
+
+_get_dav_<propname>(uri)
+
+ These methods will be called by get_dav() when the value of a DAV
+ property is needed. The defined properties are:
+
+ - resourcetype (empty or <collection> if the object is a collection)
+ - getcontentlength
+ - getcontenttype
+ - getlastmodified
+ - creationdate
+
+
+
+put(self,uri,data)
+
+ This method will write data into the given object.
+ It should return the result code (e.g. 424 if an error occured and
+ None if everythin was ok).
+
+
+mkcol(self,uri)
+
+ This method will be called when the MKCOL WEBDAV method was received
+ by the server. The interface class has to test
+ - if the parents of the uri all exists. If not, return 409
+ - if the object already exists. If so, return 405
+ - if it is allowed to create the collection. If not, return 403
+ If everything is ok, then create the new collection (aka directory)
+ and return 201.
+
+
+rmcol(self,uri)
+
+ This method is called when a collection needs to be removed.
+ Only the collection should be removed, no children as the davserver
+ is automatically iterating over all children and calling rm/rmcol
+ for each of them (because it needs a result code for every deleted
+ object).
+ If the user is not allowed to delete the collection then an
+ 403 should be returned
+
+
+rm(self,uri)
+
+ This is the same for single objects, the same as above applies.
+
+
+is_collection(self,uri)
+
+ This one simply returns 1 if the object specified by the uri
+ is an object or 0 if it isn't.
+
+
+
+So these are basically the methods which need to get implemented. While writing
+this I also noticed some problems:
+
+- the actual user is not know to the interface class. This should be changed as
+ it might be important when testing if an action is allowed or not. Also some
+ implementations might need a user in order to decide what to return (e.g.
+ GROUP.lounge will need this.)
+
+- the return of result codes is not standardized throughout the interface class.
+ This should be changed.
+
+- The should be a super interface class to derive from in order to handle some
+ common things like get_dav() or property handling in general. Some things
+ then also might me moved from propfind.py/devserver.py into this class.
+
+
+
+As the changes above might break existing code you have been warned with this
+message :)
+
diff --git a/doc/walker b/doc/walker
new file mode 100644
index 0000000..cb33a27
--- /dev/null
+++ b/doc/walker
@@ -0,0 +1,50 @@
+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.
+
+The difference between all these walks is only that we perform
+a different action on the resources we visit. Thus it might be
+the simplest solution to provide a walker class or method to
+do that work and give it a function to perform before starting.
+
+
+Way of walking
+--------------
+
+When we delete things we should do it bottom up but when we copy
+or move things we should create resources top down. Thus we actually
+need 2 methods.
+
+But the following method might work: We create a list of all the nodes
+in the tree in tree order (means top down, left to right). When
+we walk over this list from begin to end we can copy and when we
+move backwards we can delete.
+
+Thus we need an indicator for the direction and the method to
+perform on it.
+
+
+Here the iterative approach (in order to save memory):
+dc=dataclass
+queue=list=[start_uri]
+while len(queue):
+ element=queue[-1]
+ childs=dc.get_childs(element)
+ if childs:
+ list=list+childs
+ # update queue
+ del queue[-1]
+ if childs:
+ queue=queue+childs
+
+
+(first try..)
+
+
+
+
+
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..ed160b8
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+
+from distutils.core import setup
+setup(name='PyDAV',
+ description='WebDAV library and server for python',
+ author='Christian Scholz',
+ author_email='cs at comlounge.net',
+ maintainer='Simon Pamies',
+ maintainer_email='s.pamies at banality.de',
+ url='http://www.webdav.de',
+ version='0.6',
+ classifiers = [
+ 'Development Status :: 0.6',
+ 'Environment :: Console',
+ 'Environment :: Web Environment',
+ 'Intended Audience :: End Users',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: System Administrators',
+ 'License :: GPL',
+ 'Operating System :: MacOS :: MacOS X',
+ 'Operating System :: POSIX',
+ 'Programming Language :: Python',
+ ],
+ keywords = ['webdav',
+ 'server',
+ 'dav',
+ 'standalone',
+ 'library',
+ 'gpl',
+ 'http',
+ 'rfc2518',
+ 'rfc 2518'],
+ packages=['DAV', ],
+ )
--
pywebdav
More information about the tryton-debian-vcs
mailing list