def __init__(self, **kwargs): super(WebDAV, self).__init__(**kwargs) if self.storage is None: self.storage = FSStorage()
class WebDAV(View): http_method_names = ['get', 'put', 'propfind', 'delete', 'head', 'options', 'mkcol', 'proppatch', 'copy', 'move'] collection_type = '{DAV:}collection' subcollection_type = None # add your OPTIONS Dav header extensions here dav_extensions = [] root = None storage = None def __init__(self, **kwargs): super(WebDAV, self).__init__(**kwargs) if self.storage is None: self.storage = FSStorage() @csrf_exempt def dispatch(self, request, username, *args, **kwargs): user = None # REMOTE_USER should be always honored if 'REMOTE_USER' in request.META: user = User.objects.get(username=request.META['REMOTE_USER']) elif 'HTTP_AUTHORIZATION' in request.META: auth = request.META['HTTP_AUTHORIZATION'].split() if len(auth) == 2: if auth[0].lower() == "basic": uname, passwd = base64.b64decode(auth[1]).split(':') user = authenticate(username=uname, password=passwd) def _check_group_sharing(user, sharing_user): try: sharing_user = User.objects.get(username=username) return sharing_user.groups.all() & user.groups.all() except ObjectDoesNotExist: return None if (user and user.is_active) and ( user.username == username or _check_group_sharing(user, username)): login(request, user) request.user = user try: response = super(WebDAV, self).dispatch( request, username, *args, **kwargs ) dav_base = ['1'] dav_base += getattr(settings, 'DAVVY_EXTENSIONS', []) response['Dav'] = ','.join(dav_base + self.dav_extensions) except davvy.exceptions.DavException as e: code, phrase = e.status.split(' ', 1) response = HttpResponse(phrase, content_type='text/plain') response.status_code = int(code) response.reason_phrase = phrase else: response = HttpResponse('Unathorized', content_type='text/plain') response.status_code = 401 response['WWW-Authenticate'] = 'Basic realm="davvy"' return response def options(self, request, user, resource_name): response = HttpResponse() response['Allow'] = ','.join( [method.upper() for method in self.http_method_names] ) return response def head(self, request, user, resource_name): resource = self.get_resource(request, user, resource_name) if resource.collection: return HttpResponseForbidden() response = HttpResponse(content_type=resource.content_type) response['Content-Length'] = resource.size response[ 'Content-Disposition'] = "attachment; filename=%s" % resource.name return response def get(self, request, user, resource_name): resource = self.get_resource(request, user, resource_name) if resource.collection: return HttpResponseForbidden() response = StreamingHttpResponse( self.storage.retrieve( self, request, resource ), content_type=resource.content_type ) response['Content-Length'] = resource.size response[ 'Content-Disposition'] = "attachment; filename=%s" % resource.name return response def delete(self, request, user, resource_name): resource = self.get_resource(request, user, resource_name) # you can't delete protected resources if resource.protected: return HttpResponseForbidden() depth = request.META.get('HTTP_DEPTH', 'infinity') # return forbidden if there are still items in the collection and # Depth is not 'infinity' # this is not standard-compliant, but should increase security if resource.collection and depth != 'infinity': if resource.resource_set.count() > 0: return HttpResponseForbidden() resource.delete() return HttpResponse(status=204) def _get_destination(self, request, user, resource_name): destination = request.META['HTTP_DESTINATION'] # ignore http(s) schema destination = sub(r"^http[s]*://", "", destination) base = request.META['HTTP_HOST'] + request.path[:-len(resource_name)] try: # destination user could be different destination_user = user_regexp.search( destination[:-len(resource_name)] ).group('user') # remove source user from base base = user_regexp.sub("/", base) if not destination.startswith(base): raise davvy.exceptions.BadGateway() except AttributeError: # if something goes wrong while catching the user destination_user = user finally: # return destination resource and related user return destination[len(base) + len(destination_user) + 1:].rstrip('/'), destination_user def move(self, request, user, resource_name): resource = self.get_resource(request, user, resource_name) # depth = request.META.get('HTTP_DEPTH', 'infinity') overwrite = request.META.get('HTTP_OVERWRITE', 'T') destination, destination_user = self._get_destination( request, user, resource_name) result = davvy.created try: resource2 = self.get_resource( request, destination_user, destination) if overwrite == 'F': raise davvy.exceptions.PreconditionFailed() elif overwrite == 'T': result = davvy.nocontent except davvy.exceptions.NotFound: resource2 = self.get_resource( request, destination_user, destination, create=True) # copy the resource resource2.collection = resource.collection resource2.uuid = resource.uuid resource2.size = resource.size resource2.content_type = resource.content_type resource2.created_at = resource.created_at resource2.save() # move properties for prop in resource.prop_set.all(): prop.resource = resource2 prop.save() # move children if resource.collection: for child in resource.resource_set.all(): # first check for another child with the same attributes try: twin = Resource.objects.get( parent=resource2, name=child.name) if overwrite == 'T': twin.delete() raise Resource.DoesNotExist() else: raise davvy.exceptions.PreconditionFailed() except Resource.DoesNotExist: child.parent = resource2 child.save() # destroy the old resource resource.delete() return result(request) def _copy_resource(self, request, resource, destination, overwrite): result = davvy.created try: resource2 = self.get_resource(request, resource.user, destination) if overwrite == 'F': raise davvy.exceptions.PreconditionFailed() elif overwrite == 'T': result = davvy.nocontent except davvy.exceptions.NotFound: resource2 = self.get_resource( request, resource.user, destination, create=True) # copy the resource resource2.collection = resource.collection resource2.uuid = resource.uuid resource2.size = resource.size resource2.content_type = resource.content_type resource2.created_at = resource.created_at resource2.save() # copy properties for prop in resource.prop_set.all(): prop.pk = None prop.parent = resource2 prop.save() return result def _copy_coll(self, request, resource, destination, overwrite): result = self._copy_resource(request, resource, destination, overwrite) if resource.collection: for child in resource.resource_set.all(): self._copy_coll( request, child, destination + '/' + child.name, overwrite) return result def copy(self, request, user, resource_name): resource = self.get_resource(request, user, resource_name) overwrite = request.META.get('HTTP_OVERWRITE', 'T') depth = request.META.get('HTTP_DEPTH', 'infinity') destination = self._get_destination(request, resource_name) if resource.collection and depth == 'infinity': result = self._copy_coll(request, resource, destination, overwrite) else: result = self._copy_resource( request, resource, destination, overwrite) return result(request) def put(self, request, user, resource_name): resource = self.get_resource(request, user, resource_name, create=True) resource.content_type = request.META.get( 'CONTENT_TYPE', 'application/octet-stream' ) resource.size = request.META['CONTENT_LENGTH'] resource.save() self.storage.store(self, request, resource) return davvy.created(request) def mkcol(self, request, user, resource_name): cl = int(request.META.get('CONTENT_LENGTH', '0')) if cl > 0: raise davvy.exceptions.UnsupportedMediaType() self.get_resource( request, user, resource_name, create=True, collection=True, strict=True ) return davvy.created(request) def _propfind_response(self, request, href, resource, requested_props): response_props = resource.properties(self, request, requested_props) multistatus_response = etree.Element('{DAV:}response') multistatus_response_href = etree.Element('{DAV:}href') if resource.collection: href = href.rstrip('/') + '/' try: scheme = request.scheme except: scheme = request.META['wsgi.url_scheme'] multistatus_response_href.text = scheme + \ '://' + request.META['HTTP_HOST'] + href multistatus_response.append(multistatus_response_href) for prop in response_props: propstat = etree.Element('{DAV:}propstat') multistatus_response.append(propstat) tag, value, status = prop prop_element = etree.Element('{DAV:}prop') prop_element_item = etree.Element(tag) if isinstance(value, etree._Element): prop_element_item.append(value) elif isinstance(value, list) or isinstance(value, types.GeneratorType): for item in value: prop_element_item.append(item) else: if value != '': prop_element_item.text = value prop_element.append(prop_element_item) propstat.append(prop_element) propstat_status = etree.Element('{DAV:}status') propstat_status.text = request.META[ 'SERVER_PROTOCOL'] + ' ' + status propstat.append(propstat_status) return multistatus_response def _proppatch_response(self, request, href, resource, requested_props): multistatus_response = etree.Element('{DAV:}response') multistatus_response_href = etree.Element('{DAV:}href') if resource.collection: href = href.rstrip('/') + '/' multistatus_response_href.text = href multistatus_response.append(multistatus_response_href) for prop in requested_props: propstat = etree.Element('{DAV:}propstat') multistatus_response.append(propstat) tag, status = prop prop_element = etree.Element('{DAV:}prop') prop_element.append(etree.Element(tag)) propstat.append(prop_element) propstat_status = etree.Element('{DAV:}status') propstat_status.text = request.META[ 'SERVER_PROTOCOL'] + ' ' + status propstat.append(propstat_status) return multistatus_response def propfind(self, request, user, resource_name): return self._propfinder(request, user, resource_name) def _propfinder(self, request, user, resource_name, shared=False): resource = self.get_resource(request, user, resource_name) try: dom = etree.fromstring(request.read()) except: raise davvy.exceptions.BadRequest() logger.debug(etree.tostring(dom, pretty_print=True)) props = dom.find('{DAV:}prop') requested_props = [prop.tag for prop in props] depth = request.META.get('HTTP_DEPTH', 'infinity') doc = etree.Element('{DAV:}multistatus') multistatus_response = self._propfind_response( request, request.path, resource, requested_props ) doc.append(multistatus_response) if depth == '1': resources = Resource.objects.filter(parent=resource) if shared: # we skip it if unnecessary # add shared resources from groups shared_resources = Resource.objects.filter( groups=request.user.groups.all() ) # consider only shared resources having the same progenitor # so, if resource is a calendar, only calendars, and so on... resource_progenitor = resource.progenitor.name if resource.progenitor else self.root shared_resources_id = [r.id for r in shared_resources if r.progenitor.name == resource_progenitor ] resources |= shared_resources.filter( id__in=shared_resources_id) for resource in resources: multistatus_response = self._propfind_response( request, sub( r"%s$" % (user), "%s" % (resource.user), request.path.rstrip("/") ) + "/" + resource.name, resource, requested_props ) doc.append(multistatus_response) logger.debug("%s", etree.tostring(doc, pretty_print=True)) response = HttpResponse( etree.tostring(doc, pretty_print=True), content_type='text/xml; charset=utf-8' ) response.status_code = 207 response.reason_phrase = 'Multi-Status' return response def proppatch(self, request, user, resource_name): resource = self.get_resource(request, user, resource_name) try: dom = etree.fromstring(request.read()) except: raise davvy.exceptions.BadRequest() # print etree.tostring(dom, pretty_print=True) requested_props = [] for setremove_item in dom: props = setremove_item.find('{DAV:}prop') if props is None: props = [] # top-down must be respected for prop in props: if setremove_item.tag == '{DAV:}set': try: resource.set_prop(self, request, prop.tag, prop) requested_props.append((prop.tag, '200 OK')) except davvy.exceptions.DavException as e: requested_props.append((prop.tag, e.status)) elif setremove_item.tag == '{DAV:}remove': try: resource.del_prop(self, request, prop.tag) requested_props.append((prop.tag, '200 OK')) except davvy.exceptions.DavException as e: requested_props.append((prop.tag, e.status)) doc = etree.Element('{DAV:}multistatus') multistatus_response = self._proppatch_response( request, request.path, resource, requested_props) doc.append(multistatus_response) # print etree.tostring(doc, pretty_print=True) response = HttpResponse( etree.tostring(doc, pretty_print=True), content_type='text/xml; charset=utf-8') response.status_code = 207 response.reason_phrase = 'Multi-Status' return response def _get_root(self, user): try: resource = Resource.objects.get( name=self.root, user=user, parent=None, collection=True) except: resource = Resource.objects.create( name=self.root, user=user, parent=None, collection=True) return resource def get_resource(self, request, user, name, create=False, collection=False, strict=False): resource_user = User.objects.get(username=user) # remove final slashes name = name.rstrip('/') parent = self._get_root(resource_user) if not name: return parent # split the name parts = name.split('/') # skip the last item # on error, returns conflict # returns root in case of '/' for part in parts[:-1]: try: resource_part = Resource.objects.get( user=resource_user, parent=parent, name=part ) if not resource_part.collection: raise Resource.DoesNotExist() except Resource.DoesNotExist: raise davvy.exceptions.Conflict() parent = resource_part # now check for the requested item try: resource = Resource.objects.get( user=resource_user, parent=parent, name=parts[-1] ) if strict and create: raise davvy.exceptions.AlreadyExists() except Resource.DoesNotExist: if create: resource = Resource.objects.create( user=resource_user, parent=parent, name=parts[ -1], collection=collection ) else: raise davvy.exceptions.NotFound() return resource