def prepare_copy_move(self): req = self.req ctx = req.context dest_url = req.headers.get('Destination') if not dest_url: raise dav.DAVBadRequestError('Destination URL must be supplied via Destination: HTTP header.') root = dav.DAVRoot(req) try: tr = root.resolve_uri(dest_url) except ValueError: raise dav.DAVBadRequestError('Bad destination URL.') if len(tr['subpath']) > 0: raise dav.DAVConflictError('Unable to locate destination node.') over = req.headers.get('Overwrite', 'T').upper() if over == 'T': over = True elif over == 'F': over = False else: raise dav.DAVBadRequestError('You must supply either "T" or "F" as Overwrite: HTTP header.') if (not tr['view_name']) and (not over): raise dav.DAVPreconditionError( 'Destination node exists and overwrite was disabled via HTTP Overwrite: header.', header='Overwrite' ) if tr['view_name']: parent = tr['context'] node = None node_name = tr['view_name'] else: node = tr['context'] parent = node.__parent__ node_name = node.__name__ return (parent, node, node_name)
def patch(self): # Overwrite a region of an existing file req = self.req obj = req.context req.dav.user_acl(req, obj, dprops.ACL_WRITE_CONTENT) self.conditional_request(obj) ctype = req.content_type if ctype == 'application/x-sabredav-partialupdate': if 'Content-Length' not in req.headers: raise dav.DAVLengthRequiredError('SabreDAV PATCH method requires Content-Length: HTTP header.') if 'X-Update-Range' not in req.headers: raise dav.DAVBadRequestError('Patch range must be specified via X-Update-Range: HTTP header.') range_hdr = req.headers.get('X-Update-Range') if ',' in range_hdr: raise dav.DAVBadRequestError('Malformed patch range or multiple ranges supplied via X-Update-Range: HTTP header.') r_start = r_end = None obj_sz = getattr(obj, 'size', None) if callable(obj_sz): obj_sz = obj_sz(req) if range_hdr == 'append': if obj_sz is None: raise dav.DAVNotImplementedError('Node does not support appending data.') r_start = obj_sz r_end = r_start + req.content_length else: patch_range = parse_range(range_hdr) if patch_range is None: raise dav.DAVBadRequestError('Malformed patch range supplied via X-Update-Range: HTTP header.') r_start = patch_range.start r_end = patch_range.end if (r_start is not None) and (r_end is not None): if (r_end - r_start) != req.content_length: raise dav.DAVUnsatisfiableRangeError('Content length and range supplied via X-Update-Range: HTTP header are not consistent.') if r_start < 0: if obj_sz is None: raise dav.DAVNotImplementedError('Node does not support relative file offsets.') r_start = obj_sz + r_start if r_start < 0: raise dav.DAVUnsatisfiableRangeError('Relative offset (supplied via X-Update-Range: HTTP header) is before file start.') r_end = r_start + req.content_length if r_end is None: r_end = r_start + req.content_length putter = getattr(obj, 'dav_put', None) if putter is None: raise dav.DAVNotImplementedError('Unable to patch node.') modified = putter(req, req.body_file_seekable, r_start, r_end - r_start) # TODO: handle IOErrors, handle non-seekable request body if modified: etag = None else: etag = getattr(obj, 'etag', None) if callable(etag): etag = etag(req) resp = dav.DAVOverwriteResponse(request=req, etag=etag) req.dav.set_features(resp, node=obj) resp.headers.add('MS-Author-Via', 'DAV') return resp raise dav.DAVUnsupportedMediaTypeError('Unknown content type specified in PATCH request.')
def lock(self): req = self.req ctx = req.context req.dav.user_acl(req, ctx, dprops.ACL_WRITE_CONTENT) path = req.dav.ctx_path(ctx) str_path = '/'.join(path) locks = req.dav.get_locks(path) lock = None if req.body: # New lock oldlock = None for oldlock in locks: if oldlock.scope == DAVLock.SCOPE_EXCLUSIVE: raise dav.DAVConflictingLockError(lock=oldlock) lreq = dav.DAVLockRequest(req) lock = lreq.get_lock(DAVLock) if oldlock and (lock.scope != DAVLock.SCOPE_SHARED): raise dav.DAVConflictingLockError(lock=oldlock) lock.uri = str_path lock.depth = req.dav.get_http_depth(req) if isinstance(ctx, File): lock.file = ctx else: # Refreshing old lock ifh = self.get_if() if not ifh: raise dav.DAVBadRequestError('No If: headers supplied in LOCK refresh request.') try: for oldlock in locks: for ifobj in ifh: for token in ifobj['tokens']: if oldlock.test_token(token['tok']): lock = oldlock raise StopIteration except StopIteration: pass if lock is None: if len(locks): raise dav.DAVLockedError('Resource is locked.', lock=locks[0]) else: raise dav.DAVBadRequestError('New LOCK request must be accompanied by a request body.') lock.refresh(req.dav.get_http_timeout(req)) if req.body: sess = DBSession() sess.add(lock) resp = dav.DAVLockResponse(lock=lock, request=req, new_file=False) resp.make_body() return resp
def report(self): req = self.req rreq = dav.DAVReportRequest(req) rname = rreq.get_name() if rname is None: raise dav.DAVBadRequestError('Need to supply report name.') r = req.dav.report(rname, rreq) return r(req)
def unlock(self): req = self.req ctx = req.context req.dav.user_acl(req, ctx, dprops.ACL_WRITE_CONTENT) token = req.headers.get('Lock-Token') if not token: raise dav.DAVBadRequestError('UNLOCK request must be accompanied by a valid lock token header.') path = req.dav.ctx_path(ctx) if token[0] != '<': token = '<%s>' % (token,) locks = req.dav.get_locks(path) for lock in locks: token_str = '<opaquelocktoken:%s>' % (lock.token,) if token == token_str: sess = DBSession() sess.delete(lock) return dav.DAVUnlockResponse(request=req) raise dav.DAVLockTokenMatchError('Invalid lock token supplied.')
def notfound_lock(self): # TODO: DRY, unify this with normal lock # TODO: ACL req = self.req path = req.dav.path(req.url) str_path = '/'.join(path) locks = req.dav.get_locks(path) lock = None if req.body: # New lock oldlock = None for oldlock in locks: if oldlock.scope == DAVLock.SCOPE_EXCLUSIVE: raise dav.DAVConflictingLockError(lock=oldlock) lreq = dav.DAVLockRequest(req) lock = lreq.get_lock(DAVLock) if oldlock and (lock.scope != DAVLock.SCOPE_SHARED): raise dav.DAVConflictingLockError(lock=oldlock) lock.uri = str_path lock.depth = req.dav.get_http_depth(req) else: # Refreshing old lock ifh = self.get_if() if not ifh: raise dav.DAVBadRequestError('No If: headers supplied in LOCK refresh request.') try: for oldlock in locks: for ifobj in ifh: for token in ifobj['tokens']: if oldlock.test_token(token['tok']): lock = oldlock raise StopIteration except StopIteration: pass if lock is None: if len(locks): raise dav.DAVLockedError('Resource is locked.', lock=locks[0]) else: raise dav.DAVBadRequestError('New LOCK request must be accompanied by a request body.') if not req.view_name: # TODO: find proper DAV error to put here raise Exception('Invalid file name (empty file names are not allowed).') if len(req.subpath) > 0: # TODO: find proper DAV error to put here raise Exception('Invalid file name (slashes are not allowed).') req.dav.user_acl(req, req.context, dprops.ACL_BIND) creator = getattr(req.context, 'dav_create', None) if creator is None: raise dav.DAVNotImplementedError('Unable to create child node.') with io.BytesIO(b'') as bio: obj, modified = creator(req, req.view_name, None, None, bio) # TODO: handle IOErrors, handle non-seekable request body if isinstance(obj, File): lock.file = obj lock.refresh(req.dav.get_http_timeout(req)) if req.body: sess = DBSession() sess.add(lock) resp = dav.DAVLockResponse(lock=lock, request=req, new_file=True) resp.make_body() return resp