def __call__(self, environ, start_response): path = environ["PATH_INFO"] # We want to answer OPTIONS(*), even if no handler was registered for # the top-level realm (e.g. required to map drive letters). provider = environ["wsgidav.provider"] # Hotfix for WinXP / Vista: accept '/' for a '*' if environ["REQUEST_METHOD"] == "OPTIONS" and path in ("/", "*"): # Answer HTTP 'OPTIONS' method on server-level. # From RFC 2616: # If the Request-URI is an asterisk ("*"), the OPTIONS request is # intended to apply to the server in general rather than to a specific # resource. Since a server's communication options typically depend on # the resource, the "*" request is only useful as a "ping" or "no-op" # type of method; it does nothing beyond allowing the client to test the # capabilities of the server. For example, this can be used to test a # proxy for HTTP/1.1 compliance (or lack thereof). dav_compliance_level = "1,2" if provider is None or provider.isReadOnly( ) or provider.lockManager is None: dav_compliance_level = "1" headers = [ ("Content-Type", "text/html; charset=UTF-8"), ("Content-Length", "0"), ("DAV", dav_compliance_level), ("Date", util.getRfc1123Time()), ] if environ["wsgidav.config"].get("add_header_MS_Author_Via", False): headers.append(("MS-Author-Via", "DAV")) start_response("200 OK", headers) yield "" return if provider is None: raise DAVError(HTTP_NOT_FOUND, "Could not find resource provider for '%s'" % path) # Let the appropriate resource provider for the realm handle the request app = RequestServer(provider) app_iter = app(environ, start_response) for v in app_iter: yield v if hasattr(app_iter, "close"): app_iter.close() return
def __call__(self, environ, start_response): path = environ["PATH_INFO"] # We want to answer OPTIONS(*), even if no handler was registered for # the top-level realm (e.g. required to map drive letters). # Hotfix for WinXP / Vista: accept '/' for a '*' if environ["REQUEST_METHOD"] == "OPTIONS" and path in ("/", "*"): # Answer HTTP 'OPTIONS' method on server-level. # From RFC 2616: # If the Request-URI is an asterisk ("*"), the OPTIONS request is # intended to apply to the server in general rather than to a specific # resource. Since a server's communication options typically depend on # the resource, the "*" request is only useful as a "ping" or "no-op" # type of method; it does nothing beyond allowing the client to test the # capabilities of the server. For example, this can be used to test a # proxy for HTTP/1.1 compliance (or lack thereof). start_response("200 OK", [("Content-Type", "text/html"), ("Content-Length", "0"), ("DAV", "1,2"), ("Server", "DAV/2"), ("Date", util.getRfc1123Time()), ]) yield "" return provider = environ["wsgidav.provider"] if provider is None: raise DAVError(HTTP_NOT_FOUND, "Could not find resource provider for '%s'" % path) # Let the appropriate resource provider for the realm handle the request app = RequestServer(provider) for v in app(environ, start_response): yield v 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
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