diff --git a/pywebdav/lib/AuthServer.py b/pywebdav/lib/AuthServer.py
index 26a26a1..d9092c7 100644
--- a/pywebdav/lib/AuthServer.py
+++ b/pywebdav/lib/AuthServer.py
@@ -10,9 +10,9 @@
import six.moves.BaseHTTPServer
-DEFAULT_AUTH_ERROR_MESSAGE = b"""
+DEFAULT_AUTH_ERROR_MESSAGE = """
-%(code)d - %(message)b
+%(code)d - %(message)s
Authorization Required
@@ -26,7 +26,7 @@
def _quote_html(html):
- return html.replace(b"&", b"&").replace(b"<", b"<").replace(b">", b">")
+ return html.replace("&", "&").replace("<", "<").replace(">", ">")
class AuthRequestHandler(six.moves.BaseHTTPServer.BaseHTTPRequestHandler):
@@ -48,7 +48,7 @@ def parse_request(self):
if self.DO_AUTH:
authorization = self.headers.get('Authorization', '')
if not authorization:
- self.send_autherror(401, b"Authorization Required")
+ self.send_autherror(401, "Authorization Required")
return False
scheme, credentials = authorization.split()
if scheme != 'Basic':
@@ -57,7 +57,7 @@ def parse_request(self):
credentials = base64.decodebytes(credentials.encode()).decode()
user, password = credentials.split(':')
if not self.get_userinfo(user, password, self.command):
- self.send_autherror(401, b"Authorization Required")
+ self.send_autherror(401, "Authorization Required")
return False
return True
@@ -76,7 +76,7 @@ def send_autherror(self, code, message=None):
try:
short, long = self.responses[code]
except KeyError:
- short, long = b'???', b'???'
+ short, long = '???', '???'
if message is None:
message = short
explain = long
@@ -84,14 +84,14 @@ def send_autherror(self, code, message=None):
# using _quote_html to prevent Cross Site Scripting attacks (see bug
# #1100201)
- content = (self.error_auth_message_format % {b'code': code, b'message':
- _quote_html(message), b'explain': explain})
+ 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)
+ self.wfile.write(content.encode('utf-8'))
error_auth_message_format = DEFAULT_AUTH_ERROR_MESSAGE
diff --git a/pywebdav/lib/WebDAVServer.py b/pywebdav/lib/WebDAVServer.py
index 8e72296..092e125 100644
--- a/pywebdav/lib/WebDAVServer.py
+++ b/pywebdav/lib/WebDAVServer.py
@@ -69,7 +69,6 @@ def send_body(self, DATA, code=None, msg=None, desc=None,
self._send_dav_version()
for a, v in headers.items():
- v = v.encode() if isinstance(v, six.text_type) else v
self.send_header(a, v)
if DATA:
@@ -97,7 +96,9 @@ def send_body(self, DATA, code=None, msg=None, desc=None,
self.end_headers()
if DATA:
- if isinstance(DATA, str) or isinstance(DATA, six.text_type) or isinstance(DATA, bytes):
+ if isinstance(DATA, str):
+ DATA = DATA.encode('utf-8')
+ if isinstance(DATA, six.text_type) or isinstance(DATA, bytes):
log.debug("Don't use iterator")
self.wfile.write(DATA)
else:
@@ -224,8 +225,7 @@ def _HEAD_GET(self, with_body=False):
""" Returns headers and body for given resource """
dc = self.IFACE_CLASS
- uri = urllib.parse.urljoin(self.get_baseuri(dc), self.path)
- uri = urllib.parse.unquote(uri).encode()
+ uri = urllib.parse.unquote(urllib.parse.urljoin(self.get_baseuri(dc), self.path))
headers = {}
@@ -244,7 +244,7 @@ def _HEAD_GET(self, with_body=False):
# get the content type
try:
- if uri.endswith(b'/'):
+ if uri.endswith('/'):
# we could do away with this very non-local workaround for
# _get_listing if the data could have a type attached
content_type = 'text/html;charset=utf-8'
@@ -331,8 +331,7 @@ def do_PROPFIND(self):
l = self.headers['Content-Length']
body = self.rfile.read(int(l))
- uri = urllib.parse.urljoin(self.get_baseuri(dc), self.path)
- uri = urllib.parse.unquote(uri).encode()
+ uri = urllib.parse.unquote(urllib.parse.urljoin(self.get_baseuri(dc), self.path))
try:
pf = PROPFIND(uri, dc, self.headers.get('Depth', 'infinity'), body)
@@ -341,7 +340,7 @@ def do_PROPFIND(self):
return self.send_status(400)
try:
- DATA = b'%s\n' % pf.createResponse()
+ DATA = pf.createResponse()
except DAV_Error as error:
(ec, dd) = error.args
return self.send_status(ec)
@@ -376,8 +375,7 @@ def do_REPORT(self):
l = self.headers['Content-Length']
body = self.rfile.read(int(l))
- uri = urllib.parse.urljoin(self.get_baseuri(dc), self.path)
- uri = urllib.parse.unquote(uri).encode()
+ uri = urllib.parse.unquote(urllib.parse.urljoin(self.get_baseuri(dc), self.path))
rp = REPORT(uri, dc, self.headers.get('Depth', '0'), body)
@@ -403,8 +401,7 @@ def do_MKCOL(self):
return self.send_status(415)
dc = self.IFACE_CLASS
- uri = urllib.parse.urljoin(self.get_baseuri(dc), self.path)
- uri = urllib.parse.unquote(uri).encode()
+ uri = urllib.parse.unquote(urllib.parse.urljoin(self.get_baseuri(dc), self.path))
try:
dc.mkcol(uri)
@@ -419,11 +416,10 @@ def do_DELETE(self):
""" delete an resource """
dc = self.IFACE_CLASS
- uri = urllib.parse.urljoin(self.get_baseuri(dc), self.path)
- uri = urllib.parse.unquote(uri).encode()
+ uri = urllib.parse.unquote(urllib.parse.urljoin(self.get_baseuri(dc), self.path))
# hastags not allowed
- if uri.find(b'#') >= 0:
+ if uri.find('#') >= 0:
return self.send_status(404)
# locked resources are not allowed to delete
@@ -490,8 +486,7 @@ def do_DELETE(self):
def do_PUT(self):
dc = self.IFACE_CLASS
- uri = urllib.parse.urljoin(self.get_baseuri(dc), self.path)
- uri = urllib.parse.unquote(uri).encode()
+ uri = urllib.parse.unquote(urllib.parse.urljoin(self.get_baseuri(dc), self.path))
log.debug("do_PUT: uri = %s" % uri)
log.debug('do_PUT: headers = %s' % self.headers)
@@ -677,12 +672,11 @@ def copymove(self, CLASS):
dc = self.IFACE_CLASS
# get the source URI
- source_uri = urllib.parse.urljoin(self.get_baseuri(dc), self.path)
- source_uri = urllib.parse.unquote(source_uri).encode()
+ source_uri = urllib.parse.unquote(urllib.parse.urljoin(self.get_baseuri(dc), self.path))
# get the destination URI
dest_uri = self.headers['Destination']
- dest_uri = urllib.parse.unquote(dest_uri).encode()
+ dest_uri = urllib.parse.unquote(dest_uri)
# check locks on source and dest
if self._l_isLocked(source_uri) or self._l_isLocked(dest_uri):
diff --git a/pywebdav/lib/davcmd.py b/pywebdav/lib/davcmd.py
index 094ad61..1387de0 100644
--- a/pywebdav/lib/davcmd.py
+++ b/pywebdav/lib/davcmd.py
@@ -14,6 +14,7 @@
from .utils import create_treelist, is_prefix
from .errors import *
from six.moves import range
+import os
def deltree(dc,uri,exclude={}):
""" delete a tree of resources
@@ -128,7 +129,7 @@ def copytree(dc,src,dst,overwrite=None):
dc -- dataclass to use
src -- src uri from where to copy
dst -- dst uri
- overwrite -- if 1 then delete dst uri before
+ overwrite -- if True then delete dst uri before
returns dict of uri:error_code tuples from which
another method can create a multistatus xml element.
@@ -149,8 +150,14 @@ def copytree(dc,src,dst,overwrite=None):
tlist = create_treelist(dc,src)
result = {}
- # prepare destination URIs (get the prefix)
- dpath = urllib.parse.urlparse(dst)[2]
+ # Extract the path out of the source URI.
+ src_path = urllib.parse.urlparse(src).path
+
+ # Parse the destination URI.
+ # We'll be using it to construct destination URIs,
+ # so we don't just retain the path, like we did with
+ # the source.
+ dst_parsed = urllib.parse.urlparse(dst)
for element in tlist:
problem_uris = list(result.keys())
@@ -160,24 +167,34 @@ def copytree(dc,src,dst,overwrite=None):
# 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
+ ok=True
for p in problem_uris:
if is_prefix(p,element):
- ok=None
+ ok=False
break
- if not ok: continue
+ if not ok:
+ continue
+
+ # Find the element's path relative to the source.
+ element_path = urllib.parse.urlparse(element).path
+ element_path_rel = os.path.relpath(element_path, start=src_path)
+ # Append this relative path to the destination.
+ if element_path_rel == '.':
+ # os.path.relpath("/somedir", start="/somedir") returns
+ # a result of ".", which we don't want to append to the
+ # destination path.
+ dst_path = dst_parsed.path
+ else:
+ dst_path = os.path.join(dst_parsed.path, element_path_rel)
+
+ # Generate destination URI using our derived destination path.
+ dst_uri = urllib.parse.urlunparse(dst_parsed._replace(path=os.path.join(dst_parsed.path, element_path_rel)))
- # 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=element.replace(src,b"")
- actual_dst=dpath+esrc
# now copy stuff
try:
- copy(dc,element,actual_dst)
+ copy(dc,element,dst_uri)
except DAV_Error as error:
(ec,dd) = error.args
result[element]=ec
@@ -216,6 +233,11 @@ def movetree(dc,src,dst,overwrite=None):
# first copy it
res = copytree(dc,src,dst,overwrite)
+ # TODO: shouldn't we check res for errors and bail out before
+ # the delete if we find any?
+ # TODO: or, better yet, is there anything preventing us from
+ # reimplementing this using `shutil.move()`?
+
# then delete it
res = deltree(dc,src,exclude=res)
diff --git a/pywebdav/lib/davcopy.py b/pywebdav/lib/davcopy.py
index 3ee6c6a..ceda983 100644
--- a/pywebdav/lib/davcopy.py
+++ b/pywebdav/lib/davcopy.py
@@ -99,4 +99,4 @@ def tree_action(self):
re.appendChild(st)
ms.appendChild(re)
- return doc.toxml(encoding="utf-8")
+ return doc.toxml(encoding="utf-8") + b"\n"
diff --git a/pywebdav/lib/davmove.py b/pywebdav/lib/davmove.py
index 155208e..fd5a80a 100644
--- a/pywebdav/lib/davmove.py
+++ b/pywebdav/lib/davmove.py
@@ -5,7 +5,7 @@
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 .utils import create_treelist, quote_uri, gen_estring, make_xmlresponse, is_prefix
from .davcmd import moveone, movetree
class MOVE:
@@ -65,7 +65,8 @@ def tree_action(self):
# (we assume that both uris are on the same server!)
ps=urllib.parse.urlparse(self.__src)[2]
pd=urllib.parse.urlparse(self.__dst)[2]
- if ps==pd: raise DAV_Error(403)
+ if is_prefix(ps, pd):
+ raise DAV_Error(403)
result=dc.movetree(self.__src,self.__dst,self.__overwrite)
if not result: return None
diff --git a/pywebdav/lib/locks.py b/pywebdav/lib/locks.py
index 7924b48..5988c97 100644
--- a/pywebdav/lib/locks.py
+++ b/pywebdav/lib/locks.py
@@ -1,7 +1,7 @@
from __future__ import absolute_import
import time
from six.moves import urllib
-import random
+import uuid
import logging
@@ -95,7 +95,7 @@ def do_UNLOCK(self):
if self._l_isLocked(uri):
self._l_delLock(token)
- self.send_body(None, 204, 'Ok', 'Ok')
+ self.send_body(None, 204, 'OK', 'OK')
def do_LOCK(self):
""" Locking is implemented via in-memory caches. No data is written to disk. """
@@ -194,9 +194,7 @@ def isValid(self):
return (modified + timeout) > now
def generateToken(self):
- _randGen = random.Random(time.time())
- return '%s-%s-00105A989226:%.03f' % \
- (_randGen.random(),_randGen.random(),time.time())
+ return str(uuid.uuid4())
def getTimeoutString(self):
t = str(self.timeout)
@@ -211,7 +209,7 @@ 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):
+ elif isinstance(self.owner, xml.dom.minicompat.NodeList) and len(self.owner) > 0:
owner_str = "".join([node.toxml() for node in self.owner[0].childNodes])
token = self.token
diff --git a/pywebdav/lib/propfind.py b/pywebdav/lib/propfind.py
index 5596af0..2cd08d5 100644
--- a/pywebdav/lib/propfind.py
+++ b/pywebdav/lib/propfind.py
@@ -34,7 +34,7 @@ def __init__(self, uri, dataclass, depth, body):
self.default_ns = None
self._dataclass = dataclass
self._depth = str(depth)
- self._uri = uri.rstrip(b'/')
+ self._uri = uri.rstrip('/')
self._has_body = None # did we parse a body?
if dataclass.verbose:
@@ -116,7 +116,7 @@ def create_propname(self):
if uri_childs:
uri_list.extend(uri_childs)
- return doc.toxml(encoding="utf-8")
+ return doc.toxml(encoding="utf-8") + b"\n"
def create_allprop(self):
""" return a list of all properties """
@@ -180,7 +180,7 @@ def create_prop(self):
if uri_childs:
uri_list.extend(uri_childs)
- return doc.toxml(encoding="utf-8")
+ return doc.toxml(encoding="utf-8") + b"\n"
def mk_propname_response(self, uri, propnames, doc):
""" make a new result element for a PROPNAME request
@@ -245,7 +245,7 @@ def mk_prop_response(self, uri, good_props, bad_props, doc):
uri = self._dataclass.baseurl + '/' + '/'.join(uri.split('/')[3:])
# write href information
- uparts = urllib.parse.urlparse(uri.decode())
+ uparts = urllib.parse.urlparse(uri)
fileloc = uparts[2]
href = doc.createElement("D:href")
diff --git a/pywebdav/lib/report.py b/pywebdav/lib/report.py
index db6f398..7d589d4 100644
--- a/pywebdav/lib/report.py
+++ b/pywebdav/lib/report.py
@@ -55,7 +55,7 @@ def create_propname(self):
if uri_childs:
uri_list.extend(uri_childs)
- return doc.toxml(encoding="utf-8")
+ return doc.toxml(encoding="utf-8") + b"\n"
def create_prop(self):
""" handle a request
@@ -117,5 +117,5 @@ def create_prop(self):
if uri_childs:
uri_list.extend(uri_childs)
- return doc.toxml(encoding="utf-8")
+ return doc.toxml(encoding="utf-8") + b"\n"
diff --git a/pywebdav/lib/utils.py b/pywebdav/lib/utils.py
index bbc6098..7698a9e 100755
--- a/pywebdav/lib/utils.py
+++ b/pywebdav/lib/utils.py
@@ -1,6 +1,7 @@
from __future__ import absolute_import
import time
import re
+import os
from xml.dom import minidom
from six.moves import urllib
@@ -74,11 +75,10 @@ def create_treelist(dataclass,uri):
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
+ """ returns 1 if uri1 is a prefix of uri2 """
+ path1 = urllib.parse.urlparse(uri1).path
+ path2 = urllib.parse.urlparse(uri2).path
+ return os.path.commonpath([path1, path2]) == path1
def quote_uri(uri):
""" quote an URL but not the protocol part """
@@ -122,7 +122,7 @@ def make_xmlresponse(result):
re.appendChild(st)
doc.documentElement.appendChild(re)
- return doc.toxml(encoding="utf-8")
+ return doc.toxml(encoding="utf-8") + b"\n"
# taken from App.Common
diff --git a/pywebdav/server/config.ini b/pywebdav/server/config.ini
index c46187f..fa57207 100644
--- a/pywebdav/server/config.ini
+++ b/pywebdav/server/config.ini
@@ -59,6 +59,9 @@ mimecheck = 1
# webdav level (1 = webdav level 2)
lockemulation = 1
+# dav server base url
+baseurl =
+
# internal features
#chunked_http_response = 1
#http_request_use_iterator = 0
diff --git a/pywebdav/server/fshandler.py b/pywebdav/server/fshandler.py
index 7b9b0a6..4b059dd 100644
--- a/pywebdav/server/fshandler.py
+++ b/pywebdav/server/fshandler.py
@@ -92,7 +92,7 @@ def setBaseURI(self, uri):
def uri2local(self,uri):
""" map uri in baseuri and local part """
- uparts=urllib.parse.urlparse(uri.decode())
+ uparts=urllib.parse.urlparse(uri)
fileloc=uparts[2][1:]
filename=os.path.join(self.directory, fileloc)
filename=os.path.normpath(filename)
@@ -105,7 +105,7 @@ def local2uri(self,filename):
parts=filename.replace("\\","/").split("/")[pnum:]
sparts="/"+"/".join(parts)
uri=urllib.parse.urljoin(self.baseuri,sparts)
- return uri.encode() if isinstance(uri, six.text_type) else uri
+ return uri
def get_childs(self, uri, filter=None):