def getIndirectUrlLockList(self, url, principal=None): """Return a list of valid lockDicts, that protect <path> directly or indirectly. If a principal is given, only locks owned by this principal are returned. Side effect: expired locks for this path and all parents are purged. """ url = normalizeLockRoot(url) lockList = [] u = url while u: ll = self.storage.getLockList(u, includeRoot=True, includeChildren=False, tokenOnly=False) for l in ll: if u != url and l["depth"] != "infinity": continue # We only consider parents with Depth: infinity # TODO: handle shared locks in some way? # if l["scope"] == "shared" and lockscope == "shared" and principal != l["principal"]: # continue # Only compatible with shared locks by other users if principal is None or principal == l["principal"]: lockList.append(l) u = util.getUriParent(u) return lockList
def _listDirectory(self, davres, environ, start_response): """ @see: http://www.webdav.org/specs/rfc4918.html#rfc.section.9.4 """ assert davres.isCollection dirConfig = environ["wsgidav.config"].get("dir_browser", {}) displaypath = urllib.unquote(davres.getHref()) isReadOnly = environ["wsgidav.provider"].isReadOnly() trailer = dirConfig.get("response_trailer") if trailer: trailer = trailer.replace("${version}", "<a href='https://github.com/mar10/wsgidav/'>WsgiDAV/%s</a>" % __version__) trailer = trailer.replace("${time}", util.getRfc1123Time()) else: trailer = ("Seafile WebDAV Server, based on <a href='https://github.com/mar10/wsgidav/'>WsgiDAV/%s</a> - %s" % (__version__, util.getRfc1123Time())) html = [] html.append("<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' 'http://www.w3.org/TR/html4/strict.dtd'>"); html.append("<html>") html.append("<head>") html.append("<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>") html.append("<meta name='generator' content='WsgiDAV %s'>" % __version__) html.append("<title>WsgiDAV - Index of %s </title>" % displaypath) html.append("<script type='text/javascript'>%s</script>" % PAGE_SCRIPT) html.append("<style type='text/css'>%s</style>" % PAGE_CSS) # Special CSS to enable MS Internet Explorer behaviour if dirConfig.get("ms_mount"): html.append("<style type='text/css'> A {behavior: url(#default#AnchorClick);} </style>") if dirConfig.get("ms_sharepoint_plugin"): html.append("<object id='winFirefoxPlugin' type='application/x-sharepoint' width='0' height='0' style=''visibility: hidden;'></object>") html.append("</head>") html.append("<body onload='onLoad()'>") # Title html.append("<h1>Index of %s</h1>" % displaypath) # Add DAV-Mount link and Web-Folder link links = [] if dirConfig.get("davmount"): links.append("<a title='Open this folder in a WebDAV client.' href='%s?davmount'>Mount</a>" % util.makeCompleteUrl(environ)) if dirConfig.get("ms_mount"): links.append("<a title='Open as Web Folder (requires Microsoft Internet Explorer)' href='' FOLDER='%s'>Open as Web Folder</a>" % util.makeCompleteUrl(environ)) # html.append("<a href='' FOLDER='%ssetup.py'>Open setup.py as WebDAV</a>" % util.makeCompleteUrl(environ)) if links: html.append("<p>%s</p>" % " – ".join(links)) html.append("<hr>") # Listing html.append("<table onclick='return onClickTable(event)'>") html.append("<thead>") html.append("<tr><th>Name</th> <th>Type</th> <th class='right'>Size</th> <th class='right'>Last modified</th> </tr>") html.append("</thead>") html.append("<tbody>") if davres.path in ("", "/"): html.append("<tr><td>Top level share</td> <td></td> <td></td> <td></td> </tr>") else: parentUrl = util.getUriParent(davres.getHref()) html.append("<tr><td><a href='" + parentUrl + "'>Parent Directory</a></td> <td></td> <td></td> <td></td> </tr>") # Ask collection for member info list dirInfoList = davres.getDirectoryInfo() if dirInfoList is None: # No pre-build info: traverse members dirInfoList = [] childList = davres.getDescendants(depth="1", addSelf=False) for res in childList: di = res.getDisplayInfo() href = res.getHref() infoDict = {"href": href, "class": "", "displayName": escapeName(res.getDisplayName()), "lastModified": res.getLastModified(), "isCollection": res.isCollection, "contentLength": res.getContentLength(), "displayType": di.get("type"), "displayTypeComment": di.get("typeComment"), } if not isReadOnly and not res.isCollection: ext = os.path.splitext(href)[1].lstrip(".").lower() officeType = msOfficeExtToTypeMap.get(ext) if officeType: # print "OT", officeType # print "OT", dirConfig if dirConfig.get("ms_sharepoint_plugin"): infoDict["class"] = "msoffice" elif dirConfig.get("ms_sharepoint_urls"): infoDict["href"] = "ms-%s:ofe|u|%s" % (officeType, href) dirInfoList.append(infoDict) # for infoDict in dirInfoList: lastModified = infoDict.get("lastModified") if lastModified is None: infoDict["strModified"] = "" else: infoDict["strModified"] = util.getRfc1123Time(lastModified) infoDict["strSize"] = "-" if not infoDict.get("isCollection"): contentLength = infoDict.get("contentLength") if contentLength is not None: infoDict["strSize"] = util.byteNumberString(contentLength) html.append("""\ <tr><td><a href="%(href)s" class="%(class)s">%(displayName)s</a></td> <td>%(displayType)s</td> <td class='right'>%(strSize)s</td> <td class='right'>%(strModified)s</td></tr>""" % infoDict) html.append("</tbody>") html.append("</table>") html.append("<hr>") if "http_authenticator.username" in environ: if environ.get("http_authenticator.username"): html.append("<p>Authenticated user: '******', realm: '%s'.</p>" % (environ.get("http_authenticator.username"), environ.get("http_authenticator.realm"))) # else: # html.append("<p>Anonymous</p>") if trailer: html.append("<p class='trailer'>%s</p>" % trailer) html.append("</body></html>") body = "\n".join(html) start_response("200 OK", [("Content-Type", "text/html"), ("Content-Length", str(len(body))), ("Date", util.getRfc1123Time()), ]) return [ body ]
def _listDirectory(self, davres, environ, start_response): """ @see: http://www.webdav.org/specs/rfc4918.html#rfc.section.9.4 """ assert davres.isCollection dirConfig = environ["wsgidav.config"].get("dir_browser", {}) displaypath = urllib.unquote(davres.getHref()) trailer = dirConfig.get("response_trailer") if trailer: trailer = trailer.replace( "${version}", "<a href='http://wsgidav.googlecode.com/'>WsgiDAV/%s</a>" % __version__) trailer = trailer.replace("${time}", util.getRfc1123Time()) else: trailer = ( "<a href='http://wsgidav.googlecode.com/'>WsgiDAV/%s</a> - %s" % (__version__, util.getRfc1123Time())) html = [] html.append( "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' 'http://www.w3.org/TR/html4/strict.dtd'>" ) html.append("<html>") html.append("<head>") html.append( "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>" ) html.append("<meta name='generator' content='WsgiDAV %s'>" % __version__) html.append("<title>WsgiDAV - Index of %s </title>" % displaypath) html.append("""\ <style type="text/css"> img { border: 0; padding: 0 2px; vertical-align: text-bottom; } th, td { padding: 2px 20px 2px 2px; } th { text-align: left; } th.right { text-align: right; } td { font-family: monospace; vertical-align: bottom; white-space: pre; } td.right { text-align: right; } table { border: 0; } a.symlink { font-style: italic; } p.trailer { font-size: smaller; } </style>""") # Special CSS to enable MS Internet Explorer behaviour if dirConfig.get("msmount"): html.append("""\ <style type="text/css"> A {behavior: url(#default#AnchorClick);} </style>""") html.append("</head><body>") # Title html.append("<h1>Index of %s</h1>" % displaypath) # Add DAV-Mount link and Web-Folder link links = [] if dirConfig.get("davmount"): links.append( "<a title='Open this folder in a WebDAV client.' href='%s?davmount'>Mount</a>" % util.makeCompleteUrl(environ)) if dirConfig.get("msmount"): links.append( "<a title='Open as Web Folder (requires Microsoft Internet Explorer)' href='' FOLDER='%s'>Open as Web Folder</a>" % util.makeCompleteUrl(environ)) # html.append("<a href='' FOLDER='%ssetup.py'>Open setup.py as WebDAV</a>" % util.makeCompleteUrl(environ)) if links: html.append("<p>%s</p>" % " – ".join(links)) html.append("<hr>") # Listing html.append("<table>") html.append("<thead>") html.append( "<tr><th>Name</th> <th>Type</th> <th class='right'>Size</th> <th class='right'>Last modified</th> </tr>" ) html.append("</thead>") html.append("<tbody>") if davres.path in ("", "/"): html.append( "<tr><td>Top level share</td> <td></td> <td></td> <td></td> </tr>" ) else: parentUrl = util.getUriParent(davres.getHref()) html.append( "<tr><td><a href='" + parentUrl + "'>Parent Directory</a></td> <td></td> <td></td> <td></td> </tr>" ) # Ask collection for member info list dirInfoList = davres.getDirectoryInfo() if dirInfoList is None: # No pre-build info: traverse members dirInfoList = [] childList = davres.getDescendants(depth="1", addSelf=False) for res in childList: di = res.getDisplayInfo() infoDict = { "href": res.getHref(), "displayName": res.getDisplayName(), "lastModified": res.getLastModified(), "isCollection": res.isCollection, "contentLength": res.getContentLength(), "displayType": di.get("type"), "displayTypeComment": di.get("typeComment"), } dirInfoList.append(infoDict) # for infoDict in dirInfoList: lastModified = infoDict.get("lastModified") if lastModified is None: infoDict["strModified"] = "" else: infoDict["strModified"] = util.getRfc1123Time(lastModified) infoDict["strSize"] = "-" if not infoDict.get("isCollection"): contentLength = infoDict.get("contentLength") if contentLength is not None: infoDict["strSize"] = util.byteNumberString(contentLength) html.append("""\ <tr><td><a href="%(href)s">%(displayName)s</a></td> <td>%(displayType)s</td> <td class='right'>%(strSize)s</td> <td class='right'>%(strModified)s</td></tr>""" % infoDict) html.append("</tbody>") html.append("</table>") html.append("<hr>") if "http_authenticator.username" in environ: if environ.get("http_authenticator.username"): html.append("<p>Authenticated user: '******', realm: '%s'.</p>" % (environ.get("http_authenticator.username"), environ.get("http_authenticator.realm"))) # else: # html.append("<p>Anonymous</p>") if trailer: html.append("<p class='trailer'>%s</p>" % trailer) # html.append("<p class='trailer'><a href='http://wsgidav.googlecode.com/'>WsgiDAV/%s</a> - %s</p>" # % (__version__, util.getRfc1123Time())) html.append("</body></html>") body = "\n".join(html) start_response("200 OK", [ ("Content-Type", "text/html"), ("Content-Length", str(len(body))), ("Date", util.getRfc1123Time()), ]) return [body]
def _listDirectory(self, davres, environ, start_response): """ @see: http://www.webdav.org/specs/rfc4918.html#rfc.section.9.4 """ assert davres.isCollection dirConfig = environ["wsgidav.config"].get("dir_browser", {}) displaypath = urllib.unquote(davres.getHref()) trailer = dirConfig.get("response_trailer") if trailer: trailer = trailer.replace("${version}", "<a href='http://wsgidav.googlecode.com/'>WsgiDAV/%s</a>" % __version__) trailer = trailer.replace("${time}", util.getRfc1123Time()) else: trailer = ("<a href='http://wsgidav.googlecode.com/'>WsgiDAV/%s</a> - %s" % (__version__, util.getRfc1123Time())) html = [] html.append("<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' 'http://www.w3.org/TR/html4/strict.dtd'>"); html.append("<html>") html.append("<head>") html.append("<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>") html.append("<meta name='generator' content='WsgiDAV %s'>" % __version__) html.append("<title>WsgiDAV - Index of %s </title>" % displaypath) html.append("""\ <style type="text/css"> img { border: 0; padding: 0 2px; vertical-align: text-bottom; } th, td { padding: 2px 20px 2px 2px; } th { text-align: left; } th.right { text-align: right; } td { font-family: monospace; vertical-align: bottom; white-space: pre; } td.right { text-align: right; } table { border: 0; } a.symlink { font-style: italic; } p.trailer { font-size: smaller; } </style>""") # Special CSS to enable MS Internet Explorer behaviour if dirConfig.get("msmount"): html.append("""\ <style type="text/css"> A {behavior: url(#default#AnchorClick);} </style>""") html.append("</head><body>") # Title html.append("<h1>Index of %s</h1>" % displaypath) # Add DAV-Mount link and Web-Folder link links = [] if dirConfig.get("davmount"): links.append("<a title='Open this folder in a WebDAV client.' href='%s?davmount'>Mount</a>" % util.makeCompleteUrl(environ)) if dirConfig.get("msmount"): links.append("<a title='Open as Web Folder (requires Microsoft Internet Explorer)' href='' FOLDER='%s'>Open as Web Folder</a>" % util.makeCompleteUrl(environ)) # html.append("<a href='' FOLDER='%ssetup.py'>Open setup.py as WebDAV</a>" % util.makeCompleteUrl(environ)) if links: html.append("<p>%s</p>" % " – ".join(links)) html.append("<hr>") # Listing html.append("<table>") html.append("<thead>") html.append("<tr><th>Name</th> <th>Type</th> <th class='right'>Size</th> <th class='right'>Last modified</th> </tr>") html.append("</thead>") html.append("<tbody>") if davres.path in ("", "/"): html.append("<tr><td>Top level share</td> <td></td> <td></td> <td></td> </tr>") else: parentUrl = util.getUriParent(davres.getHref()) html.append("<tr><td><a href='" + parentUrl + "'>Parent Directory</a></td> <td></td> <td></td> <td></td> </tr>") # Ask collection for member info list dirInfoList = davres.getDirectoryInfo() if dirInfoList is None: # No pre-build info: traverse members dirInfoList = [] childList = davres.getDescendants(depth="1", addSelf=False) for res in childList: di = res.getDisplayInfo() infoDict = {"href": res.getHref(), "displayName": res.getDisplayName(), "lastModified": res.getLastModified(), "isCollection": res.isCollection, "contentLength": res.getContentLength(), "displayType": di.get("type"), "displayTypeComment": di.get("typeComment"), } dirInfoList.append(infoDict) # for infoDict in dirInfoList: lastModified = infoDict.get("lastModified") if lastModified is None: infoDict["strModified"] = "" else: infoDict["strModified"] = util.getRfc1123Time(lastModified) infoDict["strSize"] = "-" if not infoDict.get("isCollection"): contentLength = infoDict.get("contentLength") if contentLength is not None: infoDict["strSize"] = util.byteNumberString(contentLength) html.append("""\ <tr><td><a href="%(href)s">%(displayName)s</a></td> <td>%(displayType)s</td> <td class='right'>%(strSize)s</td> <td class='right'>%(strModified)s</td></tr>""" % infoDict) html.append("</tbody>") html.append("</table>") html.append("<hr>") if "http_authenticator.username" in environ: if environ.get("http_authenticator.username"): html.append("<p>Authenticated user: '******', realm: '%s'.</p>" % (environ.get("http_authenticator.username"), environ.get("http_authenticator.realm"))) # else: # html.append("<p>Anonymous</p>") if trailer: html.append("<p class='trailer'>%s</p>" % trailer) # html.append("<p class='trailer'><a href='http://wsgidav.googlecode.com/'>WsgiDAV/%s</a> - %s</p>" # % (__version__, util.getRfc1123Time())) html.append("</body></html>") body = "\n".join(html) start_response("200 OK", [("Content-Type", "text/html"), ("Content-Length", str(len(body))), ("Date", util.getRfc1123Time()), ]) return [ body ]
def _listDirectory(self, davres, environ, start_response): """ @see: http://www.webdav.org/specs/rfc4918.html#rfc.section.9.4 """ assert davres.isCollection dirConfig = environ["wsgidav.config"].get("dir_browser", {}) displaypath = urllib.unquote(davres.getHref()) isReadOnly = environ["wsgidav.provider"].isReadOnly() trailer = dirConfig.get("response_trailer") if trailer: trailer = trailer.replace("${version}", "<a href='https://github.com/mar10/wsgidav/'>WsgiDAV/%s</a>" % __version__) trailer = trailer.replace("${time}", util.getRfc1123Time()) else: trailer = ("<a href='https://github.com/mar10/wsgidav/'>WsgiDAV/%s</a> - %s" % (__version__, util.getRfc1123Time())) html = [] html.append("<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' 'http://www.w3.org/TR/html4/strict.dtd'>"); html.append("<html>") html.append("<head>") html.append("<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>") html.append("<meta name='generator' content='WsgiDAV %s'>" % __version__) html.append("<title>WsgiDAV - Index of %s </title>" % displaypath) html.append("<script type='text/javascript'>%s</script>" % PAGE_SCRIPT) html.append("<style type='text/css'>%s</style>" % PAGE_CSS) # Special CSS to enable MS Internet Explorer behaviour if dirConfig.get("ms_mount"): html.append("<style type='text/css'> A {behavior: url(#default#AnchorClick);} </style>") if dirConfig.get("ms_sharepoint_plugin"): html.append("<object id='winFirefoxPlugin' type='application/x-sharepoint' width='0' height='0' style=''visibility: hidden;'></object>") html.append("</head>") html.append("<body onload='onLoad()'>") # Title html.append("<h1>Index of %s</h1>" % displaypath) # Add DAV-Mount link and Web-Folder link links = [] if dirConfig.get("davmount"): links.append("<a title='Open this folder in a WebDAV client.' href='%s?davmount'>Mount</a>" % util.makeCompleteUrl(environ)) if dirConfig.get("ms_mount"): links.append("<a title='Open as Web Folder (requires Microsoft Internet Explorer)' href='' FOLDER='%s'>Open as Web Folder</a>" % util.makeCompleteUrl(environ)) # html.append("<a href='' FOLDER='%ssetup.py'>Open setup.py as WebDAV</a>" % util.makeCompleteUrl(environ)) if links: html.append("<p>%s</p>" % " – ".join(links)) html.append("<hr>") # Listing html.append("<table onclick='return onClickTable(event)'>") html.append("<thead>") html.append("<tr><th>Name</th> <th>Type</th> <th class='right'>Size</th> <th class='right'>Last modified</th> </tr>") html.append("</thead>") html.append("<tbody>") if davres.path in ("", "/"): html.append("<tr><td>Top level share</td> <td></td> <td></td> <td></td> </tr>") else: parentUrl = util.getUriParent(davres.getHref()) html.append("<tr><td><a href='" + parentUrl + "'>Parent Directory</a></td> <td></td> <td></td> <td></td> </tr>") # Ask collection for member info list dirInfoList = davres.getDirectoryInfo() if dirInfoList is None: # No pre-build info: traverse members dirInfoList = [] childList = davres.getDescendants(depth="1", addSelf=False) for res in childList: di = res.getDisplayInfo() href = res.getHref() infoDict = {"href": href, "class": "", "displayName": res.getDisplayName(), "lastModified": res.getLastModified(), "isCollection": res.isCollection, "contentLength": res.getContentLength(), "displayType": di.get("type"), "displayTypeComment": di.get("typeComment"), } if not isReadOnly and not res.isCollection: ext = os.path.splitext(href)[1].lstrip(".").lower() officeType = msOfficeExtToTypeMap.get(ext) if officeType: print "OT", officeType print "OT", dirConfig if dirConfig.get("ms_sharepoint_plugin"): infoDict["class"] = "msoffice" elif dirConfig.get("ms_sharepoint_urls"): infoDict["href"] = "ms-%s:ofe|u|%s" % (officeType, href) dirInfoList.append(infoDict) # for infoDict in dirInfoList: lastModified = infoDict.get("lastModified") if lastModified is None: infoDict["strModified"] = "" else: infoDict["strModified"] = util.getRfc1123Time(lastModified) infoDict["strSize"] = "-" if not infoDict.get("isCollection"): contentLength = infoDict.get("contentLength") if contentLength is not None: infoDict["strSize"] = util.byteNumberString(contentLength) html.append("""\ <tr><td><a href="%(href)s" class="%(class)s">%(displayName)s</a></td> <td>%(displayType)s</td> <td class='right'>%(strSize)s</td> <td class='right'>%(strModified)s</td></tr>""" % infoDict) html.append("</tbody>") html.append("</table>") html.append("<hr>") if "http_authenticator.username" in environ: if environ.get("http_authenticator.username"): html.append("<p>Authenticated user: '******', realm: '%s'.</p>" % (environ.get("http_authenticator.username"), environ.get("http_authenticator.realm"))) # else: # html.append("<p>Anonymous</p>") if trailer: html.append("<p class='trailer'>%s</p>" % trailer) html.append("</body></html>") body = "\n".join(html) start_response("200 OK", [("Content-Type", "text/html"), ("Content-Length", str(len(body))), ("Date", util.getRfc1123Time()), ]) return [ body ]
def checkWritePermission(self, url, depth, tokenList, principal): """Check, if <principal> can modify <url>, otherwise raise HTTP_LOCKED. If modifying <url> is prevented by a lock, DAVError(HTTP_LOCKED) is raised. An embedded DAVErrorCondition contains the conflicting locks. <url> may be modified by <principal>, if it is not currently locked directly or indirectly (i.e. by a locked parent). For depth-infinity operations, <url> also must not have locked children. It is not enough to check whether a lock is owned by <principal>, but also the token must be passed with the request. Because <principal> may run two different applications. See http://www.webdav.org/specs/rfc4918.html#lock-model http://www.webdav.org/specs/rfc4918.html#rfc.section.7.4 TODO: verify assumptions: - Parent locks WILL NOT be conflicting, if they are depth-0. - Exclusive child locks WILL be conflicting, even if they are owned by <principal>. @param url: URL that shall be modified, created, moved, or deleted @param depth: "0"|"infinity" @param tokenList: list of lock tokens, that the principal submitted in If: header @param principal: name of the principal requesting a lock @return: None or raise error """ assert depth in ("0", "infinity") _logger.debug("checkWritePermission(%s, %s, %s, %s)" % (url, depth, tokenList, principal)) # Error precondition to collect conflicting URLs errcond = DAVErrorCondition(PRECONDITION_CODE_LockConflict) self._lock.acquireRead() try: # Check url and all parents for conflicting locks u = url while u: ll = self.getUrlLockList(u) _logger.debug(" checking %s" % u) for l in ll: _logger.debug(" l=%s" % lockString(l)) if u != url and l["depth"] != "infinity": # We only consider parents with Depth: inifinity continue elif principal == l["principal"] and l[ "token"] in tokenList: # User owns this lock continue else: # Token is owned by principal, but not passed with lock list _logger.debug(" -> DENIED due to locked parent %s" % lockString(l)) errcond.add_href(l["root"]) u = util.getUriParent(u) if depth == "infinity": # Check child URLs for conflicting locks childLocks = self.storage.getLockList(url, includeRoot=False, includeChildren=True, tokenOnly=False) for l in childLocks: assert util.isChildUri(url, l["root"]) # if util.isChildUri(url, l["root"]): _logger.debug(" -> DENIED due to locked child %s" % lockString(l)) errcond.add_href(l["root"]) finally: self._lock.release() # If there were conflicts, raise HTTP_LOCKED for <url>, and pass # conflicting resource with 'no-conflicting-lock' precondition if len(errcond.hrefs) > 0: raise DAVError(HTTP_LOCKED, errcondition=errcond) return
def _checkLockPermission(self, url, locktype, lockscope, lockdepth, tokenList, principal): """Check, if <principal> can lock <url>, otherwise raise an error. If locking <url> would create a conflict, DAVError(HTTP_LOCKED) is raised. An embedded DAVErrorCondition contains the conflicting resource. @see http://www.webdav.org/specs/rfc4918.html#lock-model - Parent locks WILL NOT be conflicting, if they are depth-0. - Exclusive depth-infinity parent locks WILL be conflicting, even if they are owned by <principal>. - Child locks WILL NOT be conflicting, if we request a depth-0 lock. - Exclusive child locks WILL be conflicting, even if they are owned by <principal>. (7.7) - It is not enough to check whether a lock is owned by <principal>, but also the token must be passed with the request. (Because <principal> may run two different applications on his client.) - <principal> cannot lock-exclusive, if he holds a parent shared-lock. (This would only make sense, if he was the only shared-lock holder.) - TODO: litmus tries to acquire a shared lock on one resource twice (locks: 27 'double_sharedlock') and fails, when we return HTTP_LOCKED. So we allow multi shared locks on a resource even for the same principal. @param url: URL that shall be locked @param locktype: "write" @param lockscope: "shared"|"exclusive" @param lockdepth: "0"|"infinity" @param tokenList: list of lock tokens, that the user submitted in If: header @param principal: name of the principal requesting a lock @return: None (or raise) """ assert locktype == "write" assert lockscope in ("shared", "exclusive") assert lockdepth in ("0", "infinity") _logger.debug("checkLockPermission(%s, %s, %s, %s)" % (url, lockscope, lockdepth, principal)) # Error precondition to collect conflicting URLs errcond = DAVErrorCondition(PRECONDITION_CODE_LockConflict) self._lock.acquireRead() try: # Check url and all parents for conflicting locks u = url while u: ll = self.getUrlLockList(u) for l in ll: _logger.debug(" check parent %s, %s" % (u, lockString(l))) if u != url and l["depth"] != "infinity": # We only consider parents with Depth: infinity continue elif l["scope"] == "shared" and lockscope == "shared": # Only compatible with shared locks (even by same principal) continue # Lock conflict _logger.debug(" -> DENIED due to locked parent %s" % lockString(l)) errcond.add_href(l["root"]) u = util.getUriParent(u) if lockdepth == "infinity": # Check child URLs for conflicting locks childLocks = self.storage.getLockList(url, includeRoot=False, includeChildren=True, tokenOnly=False) for l in childLocks: assert util.isChildUri(url, l["root"]) # if util.isChildUri(url, l["root"]): _logger.debug(" -> DENIED due to locked child %s" % lockString(l)) errcond.add_href(l["root"]) finally: self._lock.release() # If there were conflicts, raise HTTP_LOCKED for <url>, and pass # conflicting resource with 'no-conflicting-lock' precondition if len(errcond.hrefs) > 0: raise DAVError(HTTP_LOCKED, errcondition=errcond) return
def checkWritePermission(self, url, depth, tokenList, principal): """Check, if <principal> can modify <url>, otherwise raise HTTP_LOCKED. If modifying <url> is prevented by a lock, DAVError(HTTP_LOCKED) is raised. An embedded DAVErrorCondition contains the conflicting locks. <url> may be modified by <principal>, if it is not currently locked directly or indirectly (i.e. by a locked parent). For depth-infinity operations, <url> also must not have locked children. It is not enough to check whether a lock is owned by <principal>, but also the token must be passed with the request. Because <principal> may run two different applications. See http://www.webdav.org/specs/rfc4918.html#lock-model http://www.webdav.org/specs/rfc4918.html#rfc.section.7.4 TODO: verify assumptions: - Parent locks WILL NOT be conflicting, if they are depth-0. - Exclusive child locks WILL be conflicting, even if they are owned by <principal>. @param url: URL that shall be modified, created, moved, or deleted @param depth: "0"|"infinity" @param tokenList: list of lock tokens, that the principal submitted in If: header @param principal: name of the principal requesting a lock @return: None or raise error """ assert depth in ("0", "infinity") _logger.debug("checkWritePermission(%s, %s, %s, %s)" % (url, depth, tokenList, principal)) # Error precondition to collect conflicting URLs errcond = DAVErrorCondition(PRECONDITION_CODE_LockConflict) self._lock.acquireRead() try: # Check url and all parents for conflicting locks u = url while u: ll = self.getUrlLockList(u) _logger.debug(" checking %s" % u) for l in ll: _logger.debug(" l=%s" % lockString(l)) if u != url and l["depth"] != "infinity": # We only consider parents with Depth: inifinity continue elif principal == l["principal"] and l["token"] in tokenList: # User owns this lock continue else: # Token is owned by principal, but not passed with lock list _logger.debug(" -> DENIED due to locked parent %s" % lockString(l)) errcond.add_href(l["root"]) u = util.getUriParent(u) if depth == "infinity": # Check child URLs for conflicting locks childLocks = self.storage.getLockList(url, includeRoot=False, includeChildren=True, tokenOnly=False) for l in childLocks: assert util.isChildUri(url, l["root"]) # if util.isChildUri(url, l["root"]): _logger.debug(" -> DENIED due to locked child %s" % lockString(l)) errcond.add_href(l["root"]) finally: self._lock.release() # If there were conflicts, raise HTTP_LOCKED for <url>, and pass # conflicting resource with 'no-conflicting-lock' precondition if len(errcond.hrefs) > 0: raise DAVError(HTTP_LOCKED, errcondition=errcond) return