Commit 88f4bed7 authored by 's avatar

cleanup

parent 5edd9bec
...@@ -85,10 +85,11 @@ ...@@ -85,10 +85,11 @@
"""WebDAV support - collection objects.""" """WebDAV support - collection objects."""
__version__='$Revision: 1.1 $'[11:-2] __version__='$Revision: 1.2 $'[11:-2]
import sys, os, string, mimetypes import sys, os, string
from Resource import Resource from Resource import Resource
from common import urlfix
class Collection(Resource): class Collection(Resource):
...@@ -96,8 +97,9 @@ class Collection(Resource): ...@@ -96,8 +97,9 @@ class Collection(Resource):
collection objects. It provides default implementations collection objects. It provides default implementations
for all supported WebDAV HTTP methods. The behaviors of some for all supported WebDAV HTTP methods. The behaviors of some
WebDAV HTTP methods for collections are slightly different WebDAV HTTP methods for collections are slightly different
than those for non-collection resources. than those for non-collection resources."""
"""
__dav_collection__=1
def redirect_check(self, req, rsp): def redirect_check(self, req, rsp):
# By the spec, we are not supposed to accept /foo for a # By the spec, we are not supposed to accept /foo for a
...@@ -126,14 +128,13 @@ class Collection(Resource): ...@@ -126,14 +128,13 @@ class Collection(Resource):
"""Delete a collection resource. For collection resources, DELETE """Delete a collection resource. For collection resources, DELETE
may return either 200 (OK) or 204 (No Content) to indicate total may return either 200 (OK) or 204 (No Content) to indicate total
success, or may return 207 (Multistatus) to indicate partial success, or may return 207 (Multistatus) to indicate partial
success.""" success. Note that in Zope a DELETE never returns 207."""
self.init_headers(RESPONSE) self.init_headers(RESPONSE)
self.redirect_check(REQUEST, RESPONSE) self.redirect_check(REQUEST, RESPONSE)
if self.dav__is_acquired(): url=urlfix(REQUEST['URL'], 'DELETE')
raise 'Not Found', 'The requested resource does not exist.' name=filter(None, string.split(url, '/'))[-1]
path=filter(None, string.split(REQUEST['PATH_INFO'], '/')) # TODO: add lock checking here
name=path[-1]
# TODO: add lock check here
self.aq_parent._delObject(name) self.aq_parent._delObject(name)
RESPONSE.setStatus(204) RESPONSE.setStatus(204)
return RESPONSE return RESPONSE
...@@ -85,22 +85,25 @@ ...@@ -85,22 +85,25 @@
"""WebDAV support - null resource objects.""" """WebDAV support - null resource objects."""
__version__='$Revision: 1.1 $'[11:-2] __version__='$Revision: 1.2 $'[11:-2]
import sys, os, string, mimetypes import sys, os, string, mimetypes
import Acquisition, OFS.content_types import Acquisition, OFS.content_types
from Resource import Resource, aq_base from common import absattr, aq_base, urlfix
from Resource import Resource
from Globals import Persistent
class NullResource(Acquisition.Implicit, Resource): class NullResource(Persistent, Acquisition.Implicit, Resource):
"""Null resources are used to handle HTTP method calls on """Null resources are used to handle HTTP method calls on
objects which do not yet exist in the url namespace.""" objects which do not yet exist in the url namespace."""
_isNullResource=1
__dav_null__=1
def __init__(self, parent, id): def __init__(self, parent, id):
self.id=id self.id=id
self.__parent__=parent self.__parent__=parent
self.__roles__=None # fix this!! self.__roles__=parent.__roles__
def HEAD(self, REQUEST, RESPONSE): def HEAD(self, REQUEST, RESPONSE):
"""Retrieve resource information without a response message body.""" """Retrieve resource information without a response message body."""
...@@ -143,8 +146,7 @@ class NullResource(Acquisition.Implicit, Resource): ...@@ -143,8 +146,7 @@ class NullResource(Acquisition.Implicit, Resource):
parent=self.__parent__ parent=self.__parent__
if hasattr(aq_base(parent), self.id): if hasattr(aq_base(parent), self.id):
raise 'Method Not Allowed', 'The name %s is in use.' % self.id raise 'Method Not Allowed', 'The name %s is in use.' % self.id
if (not hasattr(parent.aq_base, 'isAnObjectManager')) or \ if not hasattr(parent, '__dav_collection__'):
(not parent.isAnObjectManager):
raise 'Forbidden', 'Unable to create collection resource.' raise 'Forbidden', 'Unable to create collection resource.'
# This should probably do self.__class__(id, ...), except Folder # This should probably do self.__class__(id, ...), except Folder
# doesn't currently have a constructor. # doesn't currently have a constructor.
...@@ -161,5 +163,4 @@ class NullResource(Acquisition.Implicit, Resource): ...@@ -161,5 +163,4 @@ class NullResource(Acquisition.Implicit, Resource):
def UNLOCK(self): def UNLOCK(self):
"""Remove a lock-null resource.""" """Remove a lock-null resource."""
self.init_headers(RESPONSE) self.init_headers(RESPONSE)
raise 'Method Not Allowed', 'Method not supported for this resource.' raise 'Method Not Allowed', 'Method not supported for this resource.'
...@@ -85,13 +85,12 @@ ...@@ -85,13 +85,12 @@
"""WebDAV support - resource objects.""" """WebDAV support - resource objects."""
__version__='$Revision: 1.1 $'[11:-2] __version__='$Revision: 1.2 $'[11:-2]
import sys, os, string, time import sys, os, string, time
import mimetypes, xmlcmds import mimetypes, xmlcmds
from common import absattr, aq_base
zpns='http://www.zope.org/propertysets/default/' from common import urlfix, rfc1123_date
class Resource: class Resource:
...@@ -101,13 +100,12 @@ class Resource: ...@@ -101,13 +100,12 @@ class Resource:
such as PUT should be overridden to ensure correct behavior in such as PUT should be overridden to ensure correct behavior in
the context of the object type.""" the context of the object type."""
__dav_resource__=1
__http_methods__=('GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS', __http_methods__=('GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS',
'TRACE', 'PROPFIND', 'PROPPATCH', 'MKCOL', 'COPY', 'TRACE', 'PROPFIND', 'PROPPATCH', 'MKCOL', 'COPY',
'MOVE', 'MOVE',
) )
__dav_resource__=1
def init_headers(self, r): def init_headers(self, r):
# Init expected HTTP 1.1 / WebDAV headers which are not # Init expected HTTP 1.1 / WebDAV headers which are not
# currently set by the response object automagically. # currently set by the response object automagically.
...@@ -130,35 +128,16 @@ class Resource: ...@@ -130,35 +128,16 @@ class Resource:
lock=Lock('xxxx', 'xxxx') lock=Lock('xxxx', 'xxxx')
return self.dav__locks + (lock,) return self.dav__locks + (lock,)
def dav__is_acquired(self, ob=None):
# Return true if this object is not a direct
# subobject of its aq_parent object.
if ob is None: ob=self
if not hasattr(ob, 'aq_parent'):
return 0
if hasattr(aq_base(ob.aq_parent), absattr(ob.id)):
return 0
if hasattr(aq_base(ob), 'isTopLevelPrincipiaApplicationObject'):
return 0
return 1
# WebDAV class 1 support # WebDAV class 1 support
def HEAD(self, REQUEST, RESPONSE): def HEAD(self, REQUEST, RESPONSE):
"""Retrieve resource information without a response message """Retrieve resource information without a response body."""
body. It would be great if we had a standard way to ask an
arbitrary object for its headers -- that would allow the
default HEAD implementation to handle most needs."""
self.init_headers(RESPONSE) self.init_headers(RESPONSE)
raise 'Method Not Allowed', 'Method not supported for this resource.' raise 'Method Not Allowed', 'Method not supported for this resource.'
def PUT(self, REQUEST, RESPONSE): def PUT(self, REQUEST, RESPONSE):
"""Replace the GET response entity of an existing resource. """Replace the GET response entity of an existing resource.
Because this is often object-dependent, objects which handle Because this is often object-dependent, objects which handle
PUT should override the default PUT implementation with an PUT should override the default PUT implementation with an
object-specific implementation. By default, PUT requests object-specific implementation. By default, PUT requests
...@@ -176,11 +155,11 @@ class Resource: ...@@ -176,11 +155,11 @@ class Resource:
def TRACE(self, REQUEST, RESPONSE): def TRACE(self, REQUEST, RESPONSE):
"""Return the HTTP message received back to the client as the """Return the HTTP message received back to the client as the
entity-body of a 200 (OK) response. This will often actually entity-body of a 200 (OK) response. This will often usually
be intercepted by the web server in use. If not, the TRACE be intercepted by the web server in use. If not, the TRACE
request will fail with a 405 (Method Not Allowed), since it request will fail with a 405 (Method Not Allowed), since it
is not often possible to reproduce the HTTP request verbatim is not often possible to reproduce the HTTP request verbatim
from within the Zope environment.""" from within the Zope environment."""
self.init_headers(RESPONSE) self.init_headers(RESPONSE)
raise 'Method Not Allowed', 'Method not supported for this resource.' raise 'Method Not Allowed', 'Method not supported for this resource.'
...@@ -188,12 +167,8 @@ class Resource: ...@@ -188,12 +167,8 @@ class Resource:
"""Delete a resource. For non-collection resources, DELETE may """Delete a resource. For non-collection resources, DELETE may
return either 200 or 204 (No Content) to indicate success.""" return either 200 or 204 (No Content) to indicate success."""
self.init_headers(RESPONSE) self.init_headers(RESPONSE)
if self.dav__is_acquired(): url=urlfix(REQUEST['URL'], 'DELETE')
raise 'Not Found', 'The requested resource does not exist.' name=filter(None, string.split(url, '/'))[-1]
path=filter(None, string.split(REQUEST['URL'], '/'))
if path[-1]=='DELETE':
del path[-1]
name=path[-1]
# TODO: add lock checking here # TODO: add lock checking here
self.aq_parent._delObject(name) self.aq_parent._delObject(name)
RESPONSE.setStatus(204) RESPONSE.setStatus(204)
...@@ -202,11 +177,9 @@ class Resource: ...@@ -202,11 +177,9 @@ class Resource:
def PROPFIND(self, REQUEST, RESPONSE): def PROPFIND(self, REQUEST, RESPONSE):
"""Retrieve properties defined on the resource.""" """Retrieve properties defined on the resource."""
self.init_headers(RESPONSE) self.init_headers(RESPONSE)
if self.dav__is_acquired(): try: cmd=xmlcmds.PropFind(REQUEST)
raise 'Not Found', 'The requested resource does not exist.'
try: request=xmlcmds.PropFind(REQUEST)
except: raise 'Bad Request', 'Invalid xml request.' except: raise 'Bad Request', 'Invalid xml request.'
result=request.apply(self) result=cmd.apply(self)
RESPONSE.setStatus(207) RESPONSE.setStatus(207)
RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"') RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"')
RESPONSE.setBody(result) RESPONSE.setBody(result)
...@@ -215,15 +188,13 @@ class Resource: ...@@ -215,15 +188,13 @@ class Resource:
def PROPPATCH(self, REQUEST, RESPONSE): def PROPPATCH(self, REQUEST, RESPONSE):
"""Set and/or remove properties defined on the resource.""" """Set and/or remove properties defined on the resource."""
self.init_headers(RESPONSE) self.init_headers(RESPONSE)
if self.dav__is_acquired():
raise 'Not Found', 'The requested resource does not exist.'
if not hasattr(self, '__propsets__'): if not hasattr(self, '__propsets__'):
raise 'Method Not Allowed', ( raise 'Method Not Allowed', (
'Method not supported for this resource.') 'Method not supported for this resource.')
# TODO: add lock checking here # TODO: add lock checking here
try: request=xmlcmds.PropPatch(REQUEST) try: cmd=xmlcmds.PropPatch(REQUEST)
except: raise 'Bad Request', 'Invalid xml request.' except: raise 'Bad Request', 'Invalid xml request.'
result=request.apply(self) result=cmd.apply(self)
RESPONSE.setStatus(207) RESPONSE.setStatus(207)
RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"') RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"')
RESPONSE.setBody(result) RESPONSE.setBody(result)
...@@ -245,8 +216,6 @@ class Resource: ...@@ -245,8 +216,6 @@ class Resource:
if not hasattr(aq_base(self), 'cb_isCopyable') or \ if not hasattr(aq_base(self), 'cb_isCopyable') or \
not self.cb_isCopyable(): not self.cb_isCopyable():
raise 'Method Not Allowed', 'This object may not be copied.' raise 'Method Not Allowed', 'This object may not be copied.'
if self.dav__is_acquired():
raise 'Not Found', 'The requested resource does not exist.'
depth=REQUEST.get_header('Depth', 'infinity') depth=REQUEST.get_header('Depth', 'infinity')
dest=REQUEST.get_header('Destination', '') dest=REQUEST.get_header('Destination', '')
if not dest: raise 'Bad Request', 'No destination given' if not dest: raise 'Bad Request', 'No destination given'
...@@ -261,7 +230,7 @@ class Resource: ...@@ -261,7 +230,7 @@ class Resource:
except 'Not Found': except 'Not Found':
raise 'Conflict', 'The resource %s must exist.' % path raise 'Conflict', 'The resource %s must exist.' % path
except: raise sys.exc_type, sys.exc_value except: raise sys.exc_type, sys.exc_value
if hasattr(parent, '_isNullResource'): if hasattr(parent, '__dav_null__'):
raise 'Conflict', 'The resource %s must exist.' % path raise 'Conflict', 'The resource %s must exist.' % path
if self.dav__is_acquired(parent): if self.dav__is_acquired(parent):
raise 'Conflict', 'The resource %s must exist.' % path raise 'Conflict', 'The resource %s must exist.' % path
...@@ -296,8 +265,6 @@ class Resource: ...@@ -296,8 +265,6 @@ class Resource:
if not hasattr(aq_base(self), 'cb_isMoveable') or \ if not hasattr(aq_base(self), 'cb_isMoveable') or \
not self.cb_isMoveable(): not self.cb_isMoveable():
raise 'Method Not Allowed', 'This object may not be moved.' raise 'Method Not Allowed', 'This object may not be moved.'
if self.dav__is_acquired():
raise 'Not Found', 'The requested resource does not exist.'
dest=REQUEST.get_header('Destination', '') dest=REQUEST.get_header('Destination', '')
if not dest: raise 'Bad Request', 'No destination given' if not dest: raise 'Bad Request', 'No destination given'
flag=REQUEST.get_header('Overwrite', 'F') flag=REQUEST.get_header('Overwrite', 'F')
...@@ -311,7 +278,7 @@ class Resource: ...@@ -311,7 +278,7 @@ class Resource:
except 'Not Found': except 'Not Found':
raise 'Conflict', 'The resource %s must exist.' % path raise 'Conflict', 'The resource %s must exist.' % path
except: raise sys.exc_type, sys.exc_value except: raise sys.exc_type, sys.exc_value
if hasattr(parent, '_isNullResource'): if hasattr(parent, '__dav_null__'):
raise 'Conflict', 'The resource %s must exist.' % path raise 'Conflict', 'The resource %s must exist.' % path
if self.dav__is_acquired(parent): if self.dav__is_acquired(parent):
raise 'Conflict', 'The resource %s must exist.' % path raise 'Conflict', 'The resource %s must exist.' % path
...@@ -377,25 +344,3 @@ class Lock: ...@@ -377,25 +344,3 @@ class Lock:
'<d:href>opaquelocktoken:%(token)s</d:href>\n' \ '<d:href>opaquelocktoken:%(token)s</d:href>\n' \
'</d:locktoken>\n' \ '</d:locktoken>\n' \
'</d:activelock>\n' % self.__dict__ '</d:activelock>\n' % self.__dict__
def absattr(attr):
if callable(attr):
return attr()
return attr
def aq_base(ob):
if hasattr(ob, 'aq_base'):
return ob.aq_base
return ob
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.
if ts is None: ts=time.time()
ts=time.asctime(time.gmtime(ts))
ts=string.split(ts)
return '%s, %s %s %s %s GMT' % (ts[0],ts[2],ts[1],ts[3],ts[4])
...@@ -84,7 +84,10 @@ ...@@ -84,7 +84,10 @@
############################################################################## ##############################################################################
"""The webdav package provides WebDAV class 1 functionality within """The webdav package provides WebDAV class 1 functionality within
the Zope environment. Based on RFC 2518.""" the Zope environment. Based on:
__version__='$Revision: 1.2 $'[11:-2] [WebDAV] Y. Y. Goland, E. J. Whitehead, Jr., A. Faizi, S. R. Carter, D.
Jensen, "HTTP Extensions for Distributed Authoring - WebDAV." RFC 2518.
Microsoft, U.C. Irvine, Netscape, Novell. February, 1999."""
__version__='$Revision: 1.3 $'[11:-2]
"""HTTP 1.1 / WebDAV client library."""
__version__='$Revision: 1.1 $'[11:-2]
import sys, os, string, regex, time, types
import socket, httplib, mimetools
from types import FileType
from mimetypes import guess_type
from base64 import encodestring
from cStringIO import StringIO
from whrandom import random
from urllib import quote
from regsub import gsub
class HTTP(httplib.HTTP):
# A revised version of the HTTP class that can do basic
# HTTP 1.1 connections, and also compensates for a bug
# that occurs on some platforms in 1.5 and 1.5.1 with
# socket.makefile().read()
read_bug=sys.version[:5] in ('1.5 (','1.5.1')
def putrequest(self, request, selector, ver='1.1'):
selector=selector or '/'
str = '%s %s HTTP/%s\r\n' % (request, selector, ver)
self.send(str)
def getreply(self):
file=self.sock.makefile('rb')
data=string.join(file.readlines(), '')
file.close()
self.file=StringIO(data)
line = self.file.readline()
try:
[ver, code, msg] = string.split(line, None, 2)
except ValueError:
try:
[ver, code] = string.split(line, None, 1)
msg = ""
except ValueError:
return -1, line, None
if ver[:5] != 'HTTP/':
return -1, line, None
code=string.atoi(code)
msg =string.strip(msg)
headers =mimetools.Message(self.file, 0)
return ver, code, msg, headers
class Resource:
"""An object representing a web resource."""
def __init__(self, url, username=None, password=None):
self.username=username
self.password=password
self.url=url
if urlregex.match(url) >= 0:
host,port,uri=urlregex.group(1,2,3)
self.host=host
self.port=port and string.atoi(port[1:]) or 80
self.uri=uri or '/'
else: raise ValueError, url
def __getattr__(self, name):
url=os.path.join(self.url, name)
return self.__class__(url, username=self.username,
password=self.password)
def __get_headers(self, kw={}):
headers={}
headers=self.__set_authtoken(headers)
headers['User-Agent']='WebDAV.client %s' % __version__
headers['Host']=self.host
headers['Connection']='close'
headers['Accept']='*/*'
if kw.has_key('headers'):
for name, val in kw['headers'].items():
headers[name]=val
del kw['headers']
return headers
def __set_authtoken(self, headers, atype='Basic'):
if not (self.username and self.password):
return headers
if headers.has_key('Authorization'):
return headers
if atype=='Basic':
headers['Authorization']=(
"Basic %s" %
gsub('\012','',encodestring('%s:%s' % (
self.username,self.password))))
return headers
raise ValueError, 'Unknown authentication scheme: %s' % atype
def __enc_formdata(self, args={}):
formdata=[]
for key, val in args.items():
n=string.rfind(key, '__')
if n > 0:
tag=key[n+2:]
key=key[:n]
else: tag='string'
func=varfuncs.get(tag, marshal_string)
formdata.append(func(key, val))
return string.join(formdata, '&')
def __enc_multipart(self, args={}):
return MultiPart(args).render()
def __snd_request(self, method, uri, headers={}, body='', eh=1):
try:
h=HTTP()
h.connect(self.host, self.port)
h.putrequest(method, uri)
for n, v in headers.items():
h.putheader(n, v)
if eh: h.endheaders()
if body: h.send(body)
ver, code, msg, hdrs=h.getreply()
data=h.getfile().read()
h.close()
except:
raise 'NotAvailable', sys.exc_value
return http_response(ver, code, msg, hdrs, data)
# HTTP methods
def get(self, **kw):
headers=self.__get_headers(kw)
query=self.__enc_formdata(kw)
uri=query and '%s?%s' % (self.uri, query) or self.uri
return self.__snd_request('GET', uri, headers)
def head(self, **kw):
headers=self.__get_headers(kw)
query=self.__enc_formdata(kw)
uri=query and '%s?%s' % (self.uri, query) or self.uri
return self.__snd_request('HEAD', uri, headers)
def post(self, **kw):
headers=self.__get_headers(kw)
content_type=None
for key, val in kw.items():
if (key[-6:]=='__file') or hasattr(val, 'read'):
content_type='multipart/form-data'
break
if content_type=='multipart/form-data':
body=self.__enc_multipart(kw)
return self.__snd_request('POST', self.uri, headers, body, eh=0)
else:
body=self.__enc_formdata(kw)
headers['Content-Type']='application/x-www-form-urlencoded'
headers['Content-Length']=str(len(body))
return self.__snd_request('POST', self.uri, headers, body)
def put(self, file='', content_type='', content_enc='', **kw):
headers=self.__get_headers(kw)
if type(file) is type('') and os.path.exists(file):
ob=open(file, 'rb')
body=ob.read()
ob.close()
c_type, c_enc=guess_type(file)
elif type(file) is FileType:
body=file.read()
c_type, c_enc=guess_type(file.name)
elif type(file) is type(''):
body=file
c_type, c_enc=guess_type(self.url)
else:
raise ValueError, 'File must be a filename, file or string.'
content_type=content_type or c_type
content_enc =content_enc or c_enc
if content_type: headers['Content-Type']=content_type
if content_enc: headers['Content-Encoding']=content_enc
headers['Content-Length']=str(len(body))
return self.__snd_request('PUT', self.uri, headers, body)
def options(self, **kw):
headers=self.__get_headers(kw)
return self.__snd_request('OPTIONS', self.uri, headers)
def trace(self, **kw):
headers=self.__get_headers(kw)
return self.__snd_request('TRACE', self.uri, headers)
def delete(self, **kw):
headers=self.__get_headers(kw)
return self.__snd_request('DELETE', self.uri, headers)
def propfind(self, body='', depth=0, **kw):
headers=self.__get_headers(kw)
headers['Depth']=str(depth)
headers['Content-Type']='text/xml; charset="utf-8"'
headers['Content-Length']=str(len(body))
return self.__snd_request('PROPFIND', self.uri, headers, body)
def proppatch(self, body, **kw):
headers=self.__get_headers(kw)
headers['Content-Type']='text/xml; charset="utf-8"'
headers['Content-Length']=str(len(body))
return self.__snd_request('PROPPATCH', self.uri, headers, body)
def copy(self, dest, depth='infinity', overwrite=0, **kw):
"""Copy a resource to the specified destination."""
headers=self.__get_headers(kw)
headers['Overwrite']=overwrite and 'T' or 'F'
headers['Destination']=dest
headers['Depth']=depth
return self.__snd_request('COPY', self.uri, headers)
def move(self, dest, depth='infinity', overwrite=0, **kw):
"""Move a resource to the specified destination."""
headers=self.__get_headers(kw)
headers['Overwrite']=overwrite and 'T' or 'F'
headers['Destination']=dest
headers['Depth']=depth
return self.__snd_request('MOVE', self.uri, headers)
def mkcol(self, **kw):
headers=self.__get_headers(kw)
return self.__snd_request('MKCOL', self.uri, headers)
# class 2 support
def lock(self, scope='exclusive', type='write', owner='',
depth='infinity', timeout='Infinite', **kw):
"""Create a lock with the specified scope, type, owner, depth
and timeout on the resource. A locked resource prevents a principal
without the lock from executing a PUT, POST, PROPPATCH, LOCK, UNLOCK,
MOVE, DELETE, or MKCOL on the locked resource."""
if not scope in ('shared', 'exclusive'):
raise ValueError, 'Invalid lock scope.'
if not type in ('write',):
raise ValueError, 'Invalid lock type.'
if not depth in ('0', 'infinity'):
raise ValueError, 'Invalid depth.'
headers=self.__get_headers(kw)
body='<?xml version="1.0" encoding="utf-8"?>\n' \
'<d:lockinfo xmlns:d="DAV:">\n' \
' <d:lockscope><d:%s/></d:lockscope>\n' \
' <d:locktype><d:%s/></d:locktype>\n' \
' <d:owner>\n' \
' <d:href>%s</d:href>\n' \
' </d:owner>\n' \
'</d:lockinfo>' % (scope, type, owner)
headers['Content-Type']='text/xml; charset="utf-8"'
headers['Content-Length']=str(len(body))
headers['Timeout']=timeout
headers['Depth']=depth
return self.__snd_request('LOCK', self.uri, headers, body)
def unlock(self, token, **kw):
"""Remove the lock identified by token from the resource and all
other resources included in the lock. If all resources which have
been locked under the submitted lock token can not be unlocked the
unlock method will fail."""
headers=self.__get_headers(kw)
token='<opaquelocktoken:%s>' % str(token)
headers['Lock-Token']=token
return self.__snd_request('UNLOCK', self.uri, headers, body)
def allprops(self, depth=0):
return self.propfind('', depth)
def propnames(self, depth=0):
body='<?xml version="1.0" encoding="utf-8"?>\n' \
'<d:propfind xmlns:d="DAV:">\n' \
' <d:propname/>\n' \
'</d:propfind>'
return self.propfind(body, depth)
def getprops(self, *names):
if not names: return self.propfind()
tags=string.join(names, '/>\n <')
body='<?xml version="1.0" encoding="utf-8"?>\n' \
'<d:propfind xmlns:d="DAV:">\n' \
' <d:prop>\n' \
' <%s>\n' \
' </d:prop>\n' \
'</d:propfind>' % tags
return self.propfind(body, 0)
def setprops(self, **props):
if not props:
raise ValueError, 'No properties specified.'
tags=[]
for key, val in props.items():
tags.append(' <%s>%s</%s>' % (key, val, key))
tags=string.join(tags, '\n')
body='<?xml version="1.0" encoding="utf-8"?>\n' \
'<d:propertyupdate xmlns:d="DAV:">\n' \
'<d:set>\n' \
' <d:prop>\n' \
' %s\n' \
' </d:prop>\n' \
'</d:set>\n' \
'</d:propertyupdate>' % tags
return self.proppatch(body)
def delprops(self, *names):
if not names:
raise ValueError, 'No property names specified.'
tags=string.join(names, '/>\n <')
body='<?xml version="1.0" encoding="utf-8"?>\n' \
'<d:propertyupdate xmlns:d="DAV:">\n' \
'<d:remove>\n' \
' <d:prop>\n' \
' <%s>\n' \
' </d:prop>\n' \
'</d:remove>\n' \
'</d:propfind>' % tags
return self.proppatch(body)
def __str__(self):
return '<HTTP resource %s>' % self.url
__repr__=__str__
class http_response:
def __init__(self, ver, code, msg, headers, body):
self.version=ver
self.code=code
self.msg=msg
self.headers=headers
self.body=body
def get_status(self):
return '%s %s' % (self.code, self.msg)
def get_header(self, name, val=None):
return self.headers.dict.get(string.lower(name), val)
def get_headers(self):
return self.headers.dict
def get_body(self):
return self.body
def __str__(self):
data=[]
data.append('%s %s %s\r\n' % (self.version, self.code, self.msg))
map(data.append, self.headers.headers)
data.append('\r\n')
data.append(self.body)
return string.join(data, '')
set_xml="""<?xml version="1.0" encoding="utf-8"?>
<d:propertyupdate xmlns:d="DAV:"
xmlns:z="http://www.zope.org/propsets/default">
<d:set>
<d:prop>
<z:author>Brian Lloyd</z:author>
<z:title>My New Title</z:title>
</d:prop>
</d:set>
</d:propertyupdate>
"""
rem_xml="""<?xml version="1.0" encoding="utf-8"?>
<d:propertyupdate xmlns:d="DAV:"
xmlns:z="http://www.zope.org/propsets/default">
<d:remove>
<d:prop>
<z:author/>
<z:title/>
</d:prop>
</d:remove>
</d:propertyupdate>
"""
find_xml="""<?xml version="1.0" encoding="utf-8" ?>
<D:propfind xmlns:D="DAV:">
<D:prop xmlns:z="http://www.zope.org/propsets/default">
<z:title/>
<z:author/>
<z:content_type/>
</D:prop>
</D:propfind>
"""
##############################################################################
# Implementation details below here
urlregex=regex.compile('http://\([^:/]+\)\(:[0-9]+\)?\(/.+\)?', regex.casefold)
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.
if ts is None: ts=time.time()
ts=time.asctime(time.gmtime(ts))
ts=string.split(ts)
return '%s, %s %s %s %s GMT' % (ts[0],ts[2],ts[1],ts[3],ts[4])
def marshal_string(name, val):
return '%s=%s' % (name, quote(str(val)))
def marshal_float(name, val):
return '%s:float=%s' % (name, val)
def marshal_int(name, val):
return '%s:int=%s' % (name, val)
def marshal_long(name, val):
return ('%s:long=%s' % (name, val))[:-1]
def marshal_list(name, seq, tname='list', lt=type([]), tt=type(())):
result=[]
for v in seq:
tp=type(v)
if tp in (lt, tt):
raise TypeError, 'Invalid recursion in data to be marshaled.'
result.append(marshal_whatever("%s:%s" % (name, tname), v))
return string.join(result, '&')
def marshal_tuple(name, seq):
return marshal_list(name, seq, 'tuple')
varfuncs={}
vartypes=(('int', type(1), marshal_int),
('float', type(1.0), marshal_float),
('long', type(1L), marshal_long),
('list', type([]), marshal_list),
('tuple', type(()), marshal_tuple),
('string', type(''), marshal_string),
('file', types.FileType, None),
)
for name, tp, func in vartypes:
varfuncs[name]=func
varfuncs[tp]=func
def marshal_var(name, val):
return varfuncs.get(type(val), marshal_string)(name, val)
class MultiPart:
def __init__(self,*args):
c=len(args)
if c==1: name,val=None,args[0]
elif c==2: name,val=args[0],args[1]
else: raise ValueError, 'Invalid arguments'
h={'Content-Type': {'_v':''},
'Content-Transfer-Encoding': {'_v':''},
'Content-Disposition': {'_v':''},
}
dt=type(val)
b=t=None
if dt==types.DictType:
t=1
b=self.boundary()
d=[]
h['Content-Type']['_v']='multipart/form-data; boundary=%s' % b
for n,v in val.items():
d.append(MultiPart(n,v))
elif (dt==types.ListType) or (dt==types.TupleType):
raise ValueError, 'Sorry, nested multipart is not done yet!'
elif dt==types.FileType or hasattr(val,'read'):
if hasattr(val,'name'):
ct, enc=guess_type(val.name)
if not ct: ct='application/octet-stream'
fn=gsub('\\\\','/',val.name)
fn=fn[(string.rfind(fn,'/')+1):]
else:
ct='application/octet-stream'
enc=''
fn=''
enc=enc or (ct[:6] in ('image/', 'applic') and 'binary' or '')
h['Content-Disposition']['_v'] ='form-data'
h['Content-Disposition']['name'] ='"%s"' % name
h['Content-Disposition']['filename']='"%s"' % fn
h['Content-Transfer-Encoding']['_v']=enc
h['Content-Type']['_v'] =ct
d=[]
l=val.read(8192)
while l:
d.append(l)
l=val.read(8192)
else:
n=string.rfind(name, '__')
if n > 0: name='%s:%s' % (name[:n], name[n+2:])
h['Content-Disposition']['_v']='form-data'
h['Content-Disposition']['name']='"%s"' % name
d=[str(val)]
self._headers =h
self._data =d
self._boundary=b
self._top =t
def boundary(self):
return '%s_%s_%s' % (int(time.time()), os.getpid(), random())
def render(self):
join=string.join
h=self._headers
s=[]
if self._top:
for n,v in h.items():
if v['_v']:
s.append('%s: %s' % (n,v['_v']))
for k in v.keys():
if k != '_v': s.append('; %s=%s' % (k, v[k]))
s.append('\n')
p=[]
t=[]
b=self._boundary
for d in self._data:
p.append(d.render())
t.append('--%s\n' % b)
t.append(join(p,'\n--%s\n' % b))
t.append('\n--%s--\n' % b)
t=join(t,'')
s.append('Content-Length: %s\n\n' % len(t))
s.append(t)
return join(s,'')
else:
for n,v in h.items():
if v['_v']:
s.append('%s: %s' % (n,v['_v']))
for k in v.keys():
if k != '_v': s.append('; %s=%s' % (k, v[k]))
s.append('\n')
s.append('\n')
if self._boundary:
p=[]
b=self._boundary
for d in self._data:
p.append(d.render())
s.append('--%s\n' % b)
s.append(join(p,'\n--%s\n' % b))
s.append('\n--%s--\n' % b)
return join(s,'')
else:
return join(s+self._data,'')
_extmap={'': 'text/plain',
'rdb': 'text/plain',
'html': 'text/html',
'dtml': 'text/html',
'htm': 'text/html',
'dtm': 'text/html',
'gif': 'image/gif',
'jpg': 'image/jpeg',
'exe': 'application/octet-stream',
None : 'application/octet-stream',
}
_encmap={'image/gif': 'binary',
'image/jpg': 'binary',
'application/octet-stream': 'binary',
}
a=Resource('http://tarzan.digicool.com/dev/brian3')
b=Resource('http://tarzan.digicool.com/dev/brian3',
username='brian',
password='123')
z=Resource('http://www.zope.org/')
x=Resource('http://sandbox.xerox.com:8080/')
t=Resource('http://tarzan.digicool.com/')
f=Resource('http://www.fwi.com:8080/',
username='BrianL',
password='999999')
##############################################################################
#
# Zope Public License (ZPL) Version 0.9.4
# ---------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions in source code must retain the above
# copyright notice, this list of conditions, and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions, and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# 3. Any use, including use of the Zope software to operate a
# website, must either comply with the terms described below
# under "Attribution" or alternatively secure a separate
# license from Digital Creations.
#
# 4. All advertising materials, documentation, or technical papers
# mentioning features derived from or use of this software must
# display the following acknowledgement:
#
# "This product includes software developed by Digital
# Creations for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# 5. Names associated with Zope or Digital Creations must not be
# used to endorse or promote products derived from this
# software without prior written permission from Digital
# Creations.
#
# 6. Redistributions of any form whatsoever must retain the
# following acknowledgment:
#
# "This product includes software developed by Digital
# Creations for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# 7. Modifications are encouraged but must be packaged separately
# as patches to official Zope releases. Distributions that do
# not clearly separate the patches from the original work must
# be clearly labeled as unofficial distributions.
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND
# ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
# SHALL DIGITAL CREATIONS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
#
# Attribution
#
# Individuals or organizations using this software as a web site
# must provide attribution by placing the accompanying "button"
# and a link to the accompanying "credits page" on the website's
# main entry point. In cases where this placement of
# attribution is not feasible, a separate arrangment must be
# concluded with Digital Creations. Those using the software
# for purposes other than web sites must provide a corresponding
# attribution in locations that include a copyright using a
# manner best suited to the application environment.
#
# This software consists of contributions made by Digital
# Creations and many individuals on behalf of Digital Creations.
# Specific attributions are listed in the accompanying credits
# file.
#
##############################################################################
"""Commonly used functions for WebDAV support modules."""
__version__='$Revision: 1.1 $'[11:-2]
import string, time
def absattr(attr):
if callable(attr):
return attr()
return attr
def aq_base(ob):
if hasattr(ob, 'aq_base'):
return ob.aq_base
return ob
def urlfix(url, s):
n=len(s)
if url[-n:]==s: url=url[:-n]
if len(url) > 1 and url[-1]=='/':
url=url[:-1]
return url
def is_acquired(ob):
# Return true if this object is not a direct
# subobject of its aq_parent object.
if not hasattr(ob, 'aq_parent'):
return 0
if hasattr(aq_base(ob.aq_parent), absattr(ob.id)):
return 0
if hasattr(aq_base(ob), 'isTopLevelPrincipiaApplicationObject'):
return 0
return 1
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.
if ts is None: ts=time.time()
ts=time.asctime(time.gmtime(ts))
ts=string.split(ts)
return '%s, %s %s %s %s GMT' % (ts[0],ts[2],ts[1],ts[3],ts[4])
...@@ -83,24 +83,20 @@ ...@@ -83,24 +83,20 @@
# #
############################################################################## ##############################################################################
"""WebDAV XML request objects.""" """WebDAV xml request objects."""
__version__='$Revision: 1.1 $'[11:-2]
import sys, os, string, xmllib __version__='$Revision: 1.2 $'[11:-2]
import sys, os, string
from common import absattr, aq_base, urlfix
from xmltools import XmlParser from xmltools import XmlParser
from cStringIO import StringIO from cStringIO import StringIO
zope_id='http://www.zope.org/propsets/default'
dav_id='DAV:'
def compact(self, data):
root=XmlParser().parse(data)
class PropFind: class PropFind:
"""Model a PROPFIND request."""
def __init__(self, request): def __init__(self, request):
self.request=request self.request=request
data=request.get('BODY', '') data=request.get('BODY', '')
...@@ -110,17 +106,17 @@ class PropFind: ...@@ -110,17 +106,17 @@ class PropFind:
self.propnames=[] self.propnames=[]
self.parse(data) self.parse(data)
def parse(self, data): def parse(self, data, dav='DAV:'):
if not data: return if not data: return
root=XmlParser().parse(data) root=XmlParser().parse(data)
e=root.elements('propfind', ns=dav_id)[0] e=root.elements('propfind', ns=dav)[0]
if e.elements('allprop', ns=dav_id): if e.elements('allprop', ns=dav):
self.allprop=1 self.allprop=1
return return
if e.elements('propname', ns=dav_id): if e.elements('propname', ns=dav):
self.propname=1 self.propname=1
return return
prop=e.elements('prop', ns=dav_id)[0] prop=e.elements('prop', ns=dav)[0]
for val in prop.elements(): for val in prop.elements():
self.propnames.append((val.name(), val.namespace())) self.propnames.append((val.name(), val.namespace()))
return return
...@@ -129,19 +125,15 @@ class PropFind: ...@@ -129,19 +125,15 @@ class PropFind:
if result is None: if result is None:
result=StringIO() result=StringIO()
depth=self.depth depth=self.depth
url=self.request['URL'] url=urlfix(self.request['URL'], 'PROPFIND')
if url[-9:]=='/PROPFIND':
url=url[:-9]
result.write('<?xml version="1.0" encoding="utf-8"?>\n' \ result.write('<?xml version="1.0" encoding="utf-8"?>\n' \
'<d:multistatus xmlns:d="DAV:" ' \ '<d:multistatus xmlns:d="DAV:">\n'
'xmlns:z="%s">\n' % zope_id)
iscol=hasattr(aq_base(obj), 'isAnObjectManager') and \ iscol=hasattr(aq_base(obj), 'isAnObjectManager') and \
obj.isAnObjectManager obj.isAnObjectManager
if iscol and url[-1] != '/': url=url+'/' if iscol and url[-1] != '/': url=url+'/'
result.write('<d:response>\n<d:href>%s</d:href>\n' % url) result.write('<d:response>\n<d:href>%s</d:href>\n' % url)
if hasattr(obj, '__propsets__'): if hasattr(obj, '__propsets__'):
for ps in obj.propertysheets.items(): for ps in obj.propertysheets.values():
if hasattr(aq_base(ps), 'dav__propstat'): if hasattr(aq_base(ps), 'dav__propstat'):
stat=ps.dav__propstat(self.allprop, self.propnames) stat=ps.dav__propstat(self.allprop, self.propnames)
result.write(stat) result.write(stat)
...@@ -160,18 +152,19 @@ class PropFind: ...@@ -160,18 +152,19 @@ class PropFind:
class PropPatch: class PropPatch:
"""Model a PROPPATCH request."""
def __init__(self, request): def __init__(self, request):
self.request=request self.request=request
data=request.get('BODY', '') data=request.get('BODY', '')
self.values=[] self.values=[]
self.parse(data) self.parse(data)
def parse(self, data): def parse(self, data, dav='DAV:'):
root=XmlParser().parse(data) root=XmlParser().parse(data)
e=root.elements('propertyupdate', ns=dav_id)[0] e=root.elements('propertyupdate', ns=dav)[0]
for ob in e.elements(): for ob in e.elements():
if ob.name()=='set' and ob.namespace()==dav_id: if ob.name()=='set' and ob.namespace()==dav:
prop=ob.elements('prop', ns=dav_id)[0] prop=ob.elements('prop', ns=dav)[0]
for val in prop.elements(): for val in prop.elements():
# We have to ensure that all tag attrs (including # We have to ensure that all tag attrs (including
# an xmlns attr for all xml namespaces used by the # an xmlns attr for all xml namespaces used by the
...@@ -183,25 +176,23 @@ class PropPatch: ...@@ -183,25 +176,23 @@ class PropPatch:
md={'attrs':attrs, 'nsid': val.__nskey__} md={'attrs':attrs, 'nsid': val.__nskey__}
item=(val.name(), val.namespace(), val.strval(), md) item=(val.name(), val.namespace(), val.strval(), md)
self.values.append(item) self.values.append(item)
if ob.name()=='remove' and ob.namespace()==dav_id: if ob.name()=='remove' and ob.namespace()==dav:
prop=ob.elements('prop', ns=dav_id)[0] prop=ob.elements('prop', ns=dav)[0]
for val in prop.elements(): for val in prop.elements():
item=(val.name(), val.namespace()) item=(val.name(), val.namespace())
self.values.append(item) self.values.append(item)
def apply(self, obj): def apply(self, obj):
url=self.request['URL'] url=urlfix(self.request['URL'], 'PROPPATCH')
if url[-10:]=='/PROPPATCH':
url=url[:-10]
if hasattr(aq_base(obj), 'isAnObjectManager') and \ if hasattr(aq_base(obj), 'isAnObjectManager') and \
obj.isAnObjectManager and url[-1] != '/': obj.isAnObjectManager and url[-1] != '/':
url=url+'/' url=url+'/'
result=StringIO() result=StringIO()
errors=[] errors=[]
result.write('<?xml version="1.0" encoding="utf-8"?>\n' \ result.write('<?xml version="1.0" encoding="utf-8"?>\n' \
'<d:multistatus xmlns:d="DAV:" xmlns:z="%s">\n' \ '<d:multistatus xmlns:d="DAV:">\n' \
'<d:response>\n' \ '<d:response>\n' \
'<d:href>%s</d:href>\n' % (zope_id, url)) '<d:href>%s</d:href>\n' % url)
propsets=obj.propertysheets propsets=obj.propertysheets
for value in self.values: for value in self.values:
status='200 OK' status='200 OK'
...@@ -210,7 +201,7 @@ class PropPatch: ...@@ -210,7 +201,7 @@ class PropPatch:
propset=propsets.get(ns, None) propset=propsets.get(ns, None)
if propset is None: if propset is None:
obj.propertysheets.manage_addPropertySheet('', ns) obj.propertysheets.manage_addPropertySheet('', ns)
propsets=obj.propertysheets.items() propsets=obj.propertysheets.values()
propset=propsets.get(ns) propset=propsets.get(ns)
if propset.hasProperty(name): if propset.hasProperty(name):
try: propset._updateProperty(name, val, meta=md) try: propset._updateProperty(name, val, meta=md)
...@@ -234,9 +225,9 @@ class PropPatch: ...@@ -234,9 +225,9 @@ class PropPatch:
errors.append('%s cannot be deleted.' % name) errors.append('%s cannot be deleted.' % name)
status='409 Conflict' status='409 Conflict'
if result != '200 OK': abort=1 if result != '200 OK': abort=1
result.write('<d:propstat xmlns:ps="%s">\n' \ result.write('<d:propstat xmlns:n="%s">\n' \
' <d:prop>\n' \ ' <d:prop>\n' \
' <ps:%s/>\n' \ ' <n:%s/>\n' \
' </d:prop>\n' \ ' </d:prop>\n' \
' <d:status>HTTP/1.1 %s</d:status>\n' \ ' <d:status>HTTP/1.1 %s</d:status>\n' \
'</d:propstat>\n' % (ns, name, status)) '</d:propstat>\n' % (ns, name, status))
...@@ -256,117 +247,21 @@ class PropPatch: ...@@ -256,117 +247,21 @@ class PropPatch:
class Lock: class Lock:
def __init__(self, data): """Model a LOCK request."""
def __init__(self, request):
self.request=request
data=request.get('BODY', '')
self.scope='exclusive' self.scope='exclusive'
self.type='write' self.type='write'
self.owner='' self.owner=''
self.parse(data) self.parse(data)
def parse(self, data): def parse(self, data, dav='DAV:'):
root=XmlParser().parse(data) root=XmlParser().parse(data)
info=root.elements('lockinfo', ns=dav_id)[0] info=root.elements('lockinfo', ns=dav)[0]
ls=info.elements('lockscope', ns=dav_id)[0] ls=info.elements('lockscope', ns=dav)[0]
self.scope=ls.elements()[0].name() self.scope=ls.elements()[0].name()
lt=info.elements('locktype', ns=dav_id)[0] lt=info.elements('locktype', ns=dav)[0]
self.type=lt.elements()[0].name() self.type=lt.elements()[0].name()
lo=info.elements('owner', ns=dav_id) lo=info.elements('owner', ns=dav)
if lo: self.owner=lo[0].toxml() if lo: self.owner=lo[0].toxml()
def absattr(attr):
if callable(attr):
return attr()
return attr
def aq_base(ob):
if hasattr(ob, 'aq_base'):
return ob.aq_base
return ob
propfind_xml="""<?xml version="1.0" encoding="utf-8" ?>
<d:propfind xmlns:d="DAV:">
<d:prop xmlns:z="http://www.zope.org/propsets/default">
<z:title/>
<z:author/>
<z:content_type/>
</d:prop>
</d:propfind>
"""
rem_xml="""<?xml version="1.0" encoding="utf-8"?>
<d:propertyupdate xmlns:d="DAV:"
xmlns:z="http://www.zope.org/propsets/default">
<d:remove>
<d:prop>
<z:author/>
<z:title/>
</d:prop>
</d:remove>
</d:propertyupdate>
"""
proppatch_xml="""<?xml version="1.0" encoding="utf-8" ?>
<d:propertyupdate xmlns:d="DAV:"
xmlns:z="http://www.w3.com/standards/z39.50/">
<d:set>
<d:prop>
<z:authors>
<z:Author>Jim Whitehead</z:Author>
<z:Author>Roy Fielding</z:Author>
</z:authors>
</d:prop>
</d:set>
<d:remove>
<d:prop><z:Copyright-Owner/></d:prop>
</d:remove>
</d:propertyupdate>
"""
lock_xml="""<?xml version="1.0" encoding="utf-8" ?>
<D:lockinfo xmlns:D='DAV:'>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
<D:owner>
<D:href>http://www.ics.uci.edu/~ejw/contact.html</D:href>
</D:owner>
</D:lockinfo>
"""
multistatus_xml="""<?xml version="1.0" encoding="utf-8" ?>
<multistatus xmlns="DAV:">
<response xmlns:z="http://www.zope.org/dav/">
<href>http://www.foo.bar/container/</href>
<propstat>
<prop xmlns:R="http://www.foo.bar/boxschema/">
<R:bigbox z:type="int"/>
<R:author/>
<creationdate/>
<displayname/>
<resourcetype/>
<supportedlock/>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>http://www.foo.bar/container/front.html</href>
<propstat>
<prop xmlns:R="http://www.foo.bar/boxschema/">
<R:bigbox/>
<creationdate/>
<displayname/>
<getcontentlength/>
<getcontenttype/>
<getetag/>
<getlastmodified/>
<resourcetype/>
<supportedlock/>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
</multistatus>
"""
...@@ -84,7 +84,8 @@ ...@@ -84,7 +84,8 @@
############################################################################## ##############################################################################
"""WebDAV XML parsing tools.""" """WebDAV XML parsing tools."""
__version__='$Revision: 1.1 $'[11:-2]
__version__='$Revision: 1.2 $'[11:-2]
import sys, os, string, xmllib import sys, os, string, xmllib
from Acquisition import Implicit from Acquisition import Implicit
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment