1import os 2import sys 3import time 4import socket 5import string 6import posixpath 7import base64 8import urlparse 9import urllib 10import random 11 12import logging 13 14log = logging.getLogger(__name__) 15 16import xml.dom 17from xml.dom import minidom 18 19from utils import rfc1123_date, IfParser, tokenFinder 20from string import atoi,split 21from errors import * 22 23tokens_to_lock = {} 24uris_to_token = {} 25 26class LockManager: 27 """ Implements the locking backend and serves as MixIn for DAVRequestHandler """ 28 29 def _init_locks(self): 30 return tokens_to_lock, uris_to_token 31 32 def _l_isLocked(self, uri): 33 tokens, uris = self._init_locks() 34 return uris.has_key(uri) 35 36 def _l_hasLock(self, token): 37 tokens, uris = self._init_locks() 38 return tokens.has_key(token) 39 40 def _l_getLockForUri(self, uri): 41 tokens, uris = self._init_locks() 42 return uris.get(uri, None) 43 44 def _l_getLock(self, token): 45 tokens, uris = self._init_locks() 46 return tokens.get(token, None) 47 48 def _l_delLock(self, token): 49 tokens, uris = self._init_locks() 50 if tokens.has_key(token): 51 del uris[tokens[token].uri] 52 del tokens[token] 53 54 def _l_setLock(self, lock): 55 tokens, uris = self._init_locks() 56 tokens[lock.token] = lock 57 uris[lock.uri] = lock 58 59 def _lock_unlock_parse(self, body): 60 doc = minidom.parseString(body) 61 62 data = {} 63 info = doc.getElementsByTagNameNS('DAV:', 'lockinfo')[0] 64 data['lockscope'] = info.getElementsByTagNameNS('DAV:', 'lockscope')[0]\ 65 .firstChild.localName 66 data['locktype'] = info.getElementsByTagNameNS('DAV:', 'locktype')[0]\ 67 .firstChild.localName 68 data['lockowner'] = info.getElementsByTagNameNS('DAV:', 'owner') 69 return data 70 71 def _lock_unlock_create(self, uri, creator, depth, data): 72 lock = LockItem(uri, creator, **data) 73 iscollection = uri[-1] == '/' # very dumb collection check 74 75 result = '' 76 if depth == 'infinity' and iscollection: 77 # locking of children/collections not yet supported 78 pass 79 80 if not self._l_isLocked(uri): 81 self._l_setLock(lock) 82 83 # because we do not handle children we leave result empty 84 return lock.token, result 85 86 def do_UNLOCK(self): 87 """ Unlocks given resource """ 88 89 dc = self.IFACE_CLASS 90 91 if self._config.DAV.getboolean('verbose') is True: 92 log.info('UNLOCKing resource %s' % self.headers) 93 94 uri = urlparse.urljoin(self.get_baseuri(dc), self.path) 95 uri = urllib.unquote(uri) 96 97 # check lock token - must contain a dash 98 if not self.headers.get('Lock-Token', '').find('-')>0: 99 return self.send_status(400) 100 101 token = tokenFinder(self.headers.get('Lock-Token')) 102 if self._l_isLocked(uri): 103 self._l_delLock(token) 104 105 self.send_body(None, '204', 'Ok', 'Ok') 106 107 def do_LOCK(self): 108 """ Locking is implemented via in-memory caches. No data is written to disk. """ 109 110 dc = self.IFACE_CLASS 111 112 log.info('LOCKing resource %s' % self.headers) 113 114 body = None 115 if self.headers.has_key('Content-Length'): 116 l = self.headers['Content-Length'] 117 body = self.rfile.read(atoi(l)) 118 119 depth = self.headers.get('Depth', 'infinity') 120 121 uri = urlparse.urljoin(self.get_baseuri(dc), self.path) 122 uri = urllib.unquote(uri) 123 log.info('do_LOCK: uri = %s' % uri) 124 125 ifheader = self.headers.get('If') 126 alreadylocked = self._l_isLocked(uri) 127 log.info('do_LOCK: alreadylocked = %s' % alreadylocked) 128 129 if body and alreadylocked: 130 # Full LOCK request but resource already locked 131 self.responses[423] = ('Locked', 'Already locked') 132 return self.send_status(423) 133 134 elif body and not ifheader: 135 # LOCK with XML information 136 data = self._lock_unlock_parse(body) 137 token, result = self._lock_unlock_create(uri, 'unknown', depth, data) 138 139 if result: 140 self.send_body(result, '207', 'Error', 'Error', 141 'text/xml; charset="utf-8"') 142 143 else: 144 lock = self._l_getLock(token) 145 self.send_body(lock.asXML(), '200', 'OK', 'OK', 146 'text/xml; charset="utf-8"', 147 {'Lock-Token' : '<opaquelocktoken:%s>' % token}) 148 149 150 else: 151 # refresh request - refresh lock timeout 152 taglist = IfParser(ifheader) 153 found = 0 154 for tag in taglist: 155 for listitem in tag.list: 156 token = tokenFinder(listitem) 157 if token and self._l_hasLock(token): 158 lock = self._l_getLock(token) 159 timeout = self.headers.get('Timeout', 'Infinite') 160 lock.setTimeout(timeout) # automatically refreshes 161 found = 1 162 163 self.send_body(lock.asXML(), 164 '200', 'OK', 'OK', 'text/xml; encoding="utf-8"') 165 break 166 if found: 167 break 168 169 # we didn't find any of the tokens mentioned - means 170 # that table was cleared or another error 171 if not found: 172 self.send_status(412) # precondition failed 173 174class LockItem: 175 """ Lock with support for exclusive write locks. Some code taken from 176 webdav.LockItem from the Zope project. """ 177 178 def __init__(self, uri, creator, lockowner, depth=0, timeout='Infinite', 179 locktype='write', lockscope='exclusive', token=None, **kw): 180 181 self.uri = uri 182 self.creator = creator 183 self.owner = lockowner 184 self.depth = depth 185 self.timeout = timeout 186 self.locktype = locktype 187 self.lockscope = lockscope 188 self.token = token and token or self.generateToken() 189 self.modified = time.time() 190 191 def getModifiedTime(self): 192 return self.modified 193 194 def refresh(self): 195 self.modified = time.time() 196 197 def isValid(self): 198 now = time.time() 199 modified = self.modified 200 timeout = self.timeout 201 return (modified + timeout) > now 202 203 def generateToken(self): 204 _randGen = random.Random(time.time()) 205 return '%s-%s-00105A989226:%.03f' % \ 206 (_randGen.random(),_randGen.random(),time.time()) 207 208 def getTimeoutString(self): 209 t = str(self.timeout) 210 if t[-1] == 'L': t = t[:-1] 211 return 'Second-%s' % t 212 213 def setTimeout(self, timeout): 214 self.timeout = timeout 215 self.modified = time.time() 216 217 def asXML(self, namespace='d', discover=False): 218 owner_str = '' 219 if isinstance(self.owner, str): 220 owner_str = self.owner 221 elif isinstance(self.owner, xml.dom.minicompat.NodeList): 222 owner_str = "".join([node.toxml() for node in self.owner[0].childNodes]) 223 224 token = self.token 225 base = ('<%(ns)s:activelock>\n' 226 ' <%(ns)s:locktype><%(ns)s:%(locktype)s/></%(ns)s:locktype>\n' 227 ' <%(ns)s:lockscope><%(ns)s:%(lockscope)s/></%(ns)s:lockscope>\n' 228 ' <%(ns)s:depth>%(depth)s</%(ns)s:depth>\n' 229 ' <%(ns)s:owner>%(owner)s</%(ns)s:owner>\n' 230 ' <%(ns)s:timeout>%(timeout)s</%(ns)s:timeout>\n' 231 ' <%(ns)s:locktoken>\n' 232 ' <%(ns)s:href>opaquelocktoken:%(locktoken)s</%(ns)s:href>\n' 233 ' </%(ns)s:locktoken>\n' 234 ' </%(ns)s:activelock>\n' 235 ) % { 236 'ns': namespace, 237 'locktype': self.locktype, 238 'lockscope': self.lockscope, 239 'depth': self.depth, 240 'owner': owner_str, 241 'timeout': self.getTimeoutString(), 242 'locktoken': token, 243 } 244 245 if discover is True: 246 return base 247 248 s = """<?xml version="1.0" encoding="utf-8" ?> 249<d:prop xmlns:d="DAV:"> 250 <d:lockdiscovery> 251 %s 252 </d:lockdiscovery> 253</d:prop>""" % base 254 255 return s 256