Commit 88f4bed7 authored by 's avatar

cleanup

parent 5edd9bec
......@@ -85,10 +85,11 @@
"""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 common import urlfix
class Collection(Resource):
......@@ -96,8 +97,9 @@ class Collection(Resource):
collection objects. It provides default implementations
for all supported WebDAV HTTP methods. The behaviors of some
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):
# By the spec, we are not supposed to accept /foo for a
......@@ -126,14 +128,13 @@ class Collection(Resource):
"""Delete a collection resource. For collection resources, DELETE
may return either 200 (OK) or 204 (No Content) to indicate total
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.redirect_check(REQUEST, RESPONSE)
if self.dav__is_acquired():
raise 'Not Found', 'The requested resource does not exist.'
path=filter(None, string.split(REQUEST['PATH_INFO'], '/'))
name=path[-1]
# TODO: add lock check here
url=urlfix(REQUEST['URL'], 'DELETE')
name=filter(None, string.split(url, '/'))[-1]
# TODO: add lock checking here
self.aq_parent._delObject(name)
RESPONSE.setStatus(204)
return RESPONSE
......@@ -85,22 +85,25 @@
"""WebDAV support - null resource objects."""
__version__='$Revision: 1.1 $'[11:-2]
__version__='$Revision: 1.2 $'[11:-2]
import sys, os, string, mimetypes
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
objects which do not yet exist in the url namespace."""
_isNullResource=1
__dav_null__=1
def __init__(self, parent, id):
self.id=id
self.__parent__=parent
self.__roles__=None # fix this!!
self.__roles__=parent.__roles__
def HEAD(self, REQUEST, RESPONSE):
"""Retrieve resource information without a response message body."""
......@@ -143,8 +146,7 @@ class NullResource(Acquisition.Implicit, Resource):
parent=self.__parent__
if hasattr(aq_base(parent), self.id):
raise 'Method Not Allowed', 'The name %s is in use.' % self.id
if (not hasattr(parent.aq_base, 'isAnObjectManager')) or \
(not parent.isAnObjectManager):
if not hasattr(parent, '__dav_collection__'):
raise 'Forbidden', 'Unable to create collection resource.'
# This should probably do self.__class__(id, ...), except Folder
# doesn't currently have a constructor.
......@@ -161,5 +163,4 @@ class NullResource(Acquisition.Implicit, Resource):
def UNLOCK(self):
"""Remove a lock-null resource."""
self.init_headers(RESPONSE)
raise 'Method Not Allowed', 'Method not supported for this resource.'
......@@ -85,13 +85,12 @@
"""WebDAV support - resource objects."""
__version__='$Revision: 1.1 $'[11:-2]
__version__='$Revision: 1.2 $'[11:-2]
import sys, os, string, time
import mimetypes, xmlcmds
zpns='http://www.zope.org/propertysets/default/'
from common import absattr, aq_base
from common import urlfix, rfc1123_date
class Resource:
......@@ -101,13 +100,12 @@ class Resource:
such as PUT should be overridden to ensure correct behavior in
the context of the object type."""
__dav_resource__=1
__http_methods__=('GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS',
'TRACE', 'PROPFIND', 'PROPPATCH', 'MKCOL', 'COPY',
'MOVE',
)
__dav_resource__=1
def init_headers(self, r):
# Init expected HTTP 1.1 / WebDAV headers which are not
# currently set by the response object automagically.
......@@ -130,35 +128,16 @@ class Resource:
lock=Lock('xxxx', 'xxxx')
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
def HEAD(self, REQUEST, RESPONSE):
"""Retrieve resource information without a response message
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."""
"""Retrieve resource information without a response body."""
self.init_headers(RESPONSE)
raise 'Method Not Allowed', 'Method not supported for this resource.'
def PUT(self, REQUEST, RESPONSE):
"""Replace the GET response entity of an existing resource.
Because this is often object-dependent, objects which handle
PUT should override the default PUT implementation with an
object-specific implementation. By default, PUT requests
......@@ -176,7 +155,7 @@ class Resource:
def TRACE(self, REQUEST, RESPONSE):
"""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
request will fail with a 405 (Method Not Allowed), since it
is not often possible to reproduce the HTTP request verbatim
......@@ -188,12 +167,8 @@ class Resource:
"""Delete a resource. For non-collection resources, DELETE may
return either 200 or 204 (No Content) to indicate success."""
self.init_headers(RESPONSE)
if self.dav__is_acquired():
raise 'Not Found', 'The requested resource does not exist.'
path=filter(None, string.split(REQUEST['URL'], '/'))
if path[-1]=='DELETE':
del path[-1]
name=path[-1]
url=urlfix(REQUEST['URL'], 'DELETE')
name=filter(None, string.split(url, '/'))[-1]
# TODO: add lock checking here
self.aq_parent._delObject(name)
RESPONSE.setStatus(204)
......@@ -202,11 +177,9 @@ class Resource:
def PROPFIND(self, REQUEST, RESPONSE):
"""Retrieve properties defined on the resource."""
self.init_headers(RESPONSE)
if self.dav__is_acquired():
raise 'Not Found', 'The requested resource does not exist.'
try: request=xmlcmds.PropFind(REQUEST)
try: cmd=xmlcmds.PropFind(REQUEST)
except: raise 'Bad Request', 'Invalid xml request.'
result=request.apply(self)
result=cmd.apply(self)
RESPONSE.setStatus(207)
RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"')
RESPONSE.setBody(result)
......@@ -215,15 +188,13 @@ class Resource:
def PROPPATCH(self, REQUEST, RESPONSE):
"""Set and/or remove properties defined on the resource."""
self.init_headers(RESPONSE)
if self.dav__is_acquired():
raise 'Not Found', 'The requested resource does not exist.'
if not hasattr(self, '__propsets__'):
raise 'Method Not Allowed', (
'Method not supported for this resource.')
# TODO: add lock checking here
try: request=xmlcmds.PropPatch(REQUEST)
try: cmd=xmlcmds.PropPatch(REQUEST)
except: raise 'Bad Request', 'Invalid xml request.'
result=request.apply(self)
result=cmd.apply(self)
RESPONSE.setStatus(207)
RESPONSE.setHeader('Content-Type', 'text/xml; charset="utf-8"')
RESPONSE.setBody(result)
......@@ -245,8 +216,6 @@ class Resource:
if not hasattr(aq_base(self), 'cb_isCopyable') or \
not self.cb_isCopyable():
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')
dest=REQUEST.get_header('Destination', '')
if not dest: raise 'Bad Request', 'No destination given'
......@@ -261,7 +230,7 @@ class Resource:
except 'Not Found':
raise 'Conflict', 'The resource %s must exist.' % path
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
if self.dav__is_acquired(parent):
raise 'Conflict', 'The resource %s must exist.' % path
......@@ -296,8 +265,6 @@ class Resource:
if not hasattr(aq_base(self), 'cb_isMoveable') or \
not self.cb_isMoveable():
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', '')
if not dest: raise 'Bad Request', 'No destination given'
flag=REQUEST.get_header('Overwrite', 'F')
......@@ -311,7 +278,7 @@ class Resource:
except 'Not Found':
raise 'Conflict', 'The resource %s must exist.' % path
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
if self.dav__is_acquired(parent):
raise 'Conflict', 'The resource %s must exist.' % path
......@@ -377,25 +344,3 @@ class Lock:
'<d:href>opaquelocktoken:%(token)s</d:href>\n' \
'</d:locktoken>\n' \
'</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 @@
##############################################################################
"""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 @@
#
##############################################################################
"""WebDAV XML request objects."""
__version__='$Revision: 1.1 $'[11:-2]
"""WebDAV xml request objects."""
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 cStringIO import StringIO
zope_id='http://www.zope.org/propsets/default'
dav_id='DAV:'
def compact(self, data):
root=XmlParser().parse(data)
class PropFind:
"""Model a PROPFIND request."""
def __init__(self, request):
self.request=request
data=request.get('BODY', '')
......@@ -110,17 +106,17 @@ class PropFind:
self.propnames=[]
self.parse(data)
def parse(self, data):
def parse(self, data, dav='DAV:'):
if not data: return
root=XmlParser().parse(data)
e=root.elements('propfind', ns=dav_id)[0]
if e.elements('allprop', ns=dav_id):
e=root.elements('propfind', ns=dav)[0]
if e.elements('allprop', ns=dav):
self.allprop=1
return
if e.elements('propname', ns=dav_id):
if e.elements('propname', ns=dav):
self.propname=1
return
prop=e.elements('prop', ns=dav_id)[0]
prop=e.elements('prop', ns=dav)[0]
for val in prop.elements():
self.propnames.append((val.name(), val.namespace()))
return
......@@ -129,19 +125,15 @@ class PropFind:
if result is None:
result=StringIO()
depth=self.depth
url=self.request['URL']
if url[-9:]=='/PROPFIND':
url=url[:-9]
url=urlfix(self.request['URL'], 'PROPFIND')
result.write('<?xml version="1.0" encoding="utf-8"?>\n' \
'<d:multistatus xmlns:d="DAV:" ' \
'xmlns:z="%s">\n' % zope_id)
'<d:multistatus xmlns:d="DAV:">\n'
iscol=hasattr(aq_base(obj), 'isAnObjectManager') and \
obj.isAnObjectManager
if iscol and url[-1] != '/': url=url+'/'
result.write('<d:response>\n<d:href>%s</d:href>\n' % url)
if hasattr(obj, '__propsets__'):
for ps in obj.propertysheets.items():
for ps in obj.propertysheets.values():
if hasattr(aq_base(ps), 'dav__propstat'):
stat=ps.dav__propstat(self.allprop, self.propnames)
result.write(stat)
......@@ -160,18 +152,19 @@ class PropFind:
class PropPatch:
"""Model a PROPPATCH request."""
def __init__(self, request):
self.request=request
data=request.get('BODY', '')
self.values=[]
self.parse(data)
def parse(self, data):
def parse(self, data, dav='DAV:'):
root=XmlParser().parse(data)
e=root.elements('propertyupdate', ns=dav_id)[0]
e=root.elements('propertyupdate', ns=dav)[0]
for ob in e.elements():
if ob.name()=='set' and ob.namespace()==dav_id:
prop=ob.elements('prop', ns=dav_id)[0]
if ob.name()=='set' and ob.namespace()==dav:
prop=ob.elements('prop', ns=dav)[0]
for val in prop.elements():
# We have to ensure that all tag attrs (including
# an xmlns attr for all xml namespaces used by the
......@@ -183,25 +176,23 @@ class PropPatch:
md={'attrs':attrs, 'nsid': val.__nskey__}
item=(val.name(), val.namespace(), val.strval(), md)
self.values.append(item)
if ob.name()=='remove' and ob.namespace()==dav_id:
prop=ob.elements('prop', ns=dav_id)[0]
if ob.name()=='remove' and ob.namespace()==dav:
prop=ob.elements('prop', ns=dav)[0]
for val in prop.elements():
item=(val.name(), val.namespace())
self.values.append(item)
def apply(self, obj):
url=self.request['URL']
if url[-10:]=='/PROPPATCH':
url=url[:-10]
url=urlfix(self.request['URL'], 'PROPPATCH')
if hasattr(aq_base(obj), 'isAnObjectManager') and \
obj.isAnObjectManager and url[-1] != '/':
url=url+'/'
result=StringIO()
errors=[]
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:href>%s</d:href>\n' % (zope_id, url))
'<d:href>%s</d:href>\n' % url)
propsets=obj.propertysheets
for value in self.values:
status='200 OK'
......@@ -210,7 +201,7 @@ class PropPatch:
propset=propsets.get(ns, None)
if propset is None:
obj.propertysheets.manage_addPropertySheet('', ns)
propsets=obj.propertysheets.items()
propsets=obj.propertysheets.values()
propset=propsets.get(ns)
if propset.hasProperty(name):
try: propset._updateProperty(name, val, meta=md)
......@@ -234,9 +225,9 @@ class PropPatch:
errors.append('%s cannot be deleted.' % name)
status='409 Conflict'
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' \
' <ps:%s/>\n' \
' <n:%s/>\n' \
' </d:prop>\n' \
' <d:status>HTTP/1.1 %s</d:status>\n' \
'</d:propstat>\n' % (ns, name, status))
......@@ -256,117 +247,21 @@ class PropPatch:
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.type='write'
self.owner=''
self.parse(data)
def parse(self, data):
def parse(self, data, dav='DAV:'):
root=XmlParser().parse(data)
info=root.elements('lockinfo', ns=dav_id)[0]
ls=info.elements('lockscope', ns=dav_id)[0]
info=root.elements('lockinfo', ns=dav)[0]
ls=info.elements('lockscope', ns=dav)[0]
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()
lo=info.elements('owner', ns=dav_id)
lo=info.elements('owner', ns=dav)
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 @@
##############################################################################
"""WebDAV XML parsing tools."""
__version__='$Revision: 1.1 $'[11:-2]
__version__='$Revision: 1.2 $'[11:-2]
import sys, os, string, xmllib
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