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