Ejemplo n.º 1
0
	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)
Ejemplo n.º 2
0
	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.')
Ejemplo n.º 3
0
	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
Ejemplo n.º 4
0
	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)
Ejemplo n.º 5
0
	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.')
Ejemplo n.º 6
0
	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