summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJelmer Vernooij <jelmer@jelmer.uk>2017-03-16 04:29:35 +0000
committerJelmer Vernooij <jelmer@jelmer.uk>2017-03-16 04:29:35 +0000
commita31e8ba9a491018e8b148340e53830cad07ebf00 (patch)
treefb5faf16d0dbcfd95edeb71bb0db8624952e7b7e
parentb5e9986ee3f65b5139468b67579a6ba6404808f0 (diff)
Check file validity.
-rw-r--r--xandikos/icalendar.py9
-rw-r--r--xandikos/store.py17
-rw-r--r--xandikos/web.py23
-rw-r--r--xandikos/webdav.py31
4 files changed, 69 insertions, 11 deletions
diff --git a/xandikos/icalendar.py b/xandikos/icalendar.py
index 77ddbed..aa631dc 100644
--- a/xandikos/icalendar.py
+++ b/xandikos/icalendar.py
@@ -24,7 +24,7 @@
import logging
from icalendar.cal import Calendar, component_factory
-from xandikos.store import File
+from xandikos.store import File, InvalidFileContents
def calendar_component_delta(old_cal, new_cal):
@@ -139,6 +139,13 @@ class ICalendarFile(File):
super(ICalendarFile, self).__init__(content, content_type)
self._calendar = None
+ def validate(self):
+ """Verify that file contents are valid."""
+ try:
+ self.calendar
+ except ValueError:
+ raise InvalidFileContents(self.content_type, self.content)
+
@property
def calendar(self):
if self._calendar is None:
diff --git a/xandikos/store.py b/xandikos/store.py
index e6245b3..8e35c80 100644
--- a/xandikos/store.py
+++ b/xandikos/store.py
@@ -58,6 +58,13 @@ class File(object):
self.content = content
self.content_type = content_type
+ def validate(self):
+ """Verify that file contents are valid.
+
+ :raise InvalidFileContents: Raised if a file is not valid
+ """
+ pass
+
def describe(self, name):
"""Describe the contents of this file.
@@ -144,6 +151,14 @@ class NotStoreError(Exception):
self.path = path
+class InvalidFileContents(Exception):
+ """Invalid file contents."""
+
+ def __init__(self, content_type, data):
+ self.content_type = content_type
+ self.data = data
+
+
class Store(object):
"""A object store."""
@@ -344,7 +359,7 @@ class GitStore(Store):
fi = open_by_content_type(data, content_type, self.extra_file_handlers)
if name is None:
name = str(uuid.uuid4()) + mimetypes.guess_extension(content_type)
- # TODO(jelmer): Verify that 'data' actually represents a valid object
+ fi.validate()
try:
uid = fi.get_uid()
except (KeyError, NotImplementedError):
diff --git a/xandikos/web.py b/xandikos/web.py
index 0138417..991fb08 100644
--- a/xandikos/web.py
+++ b/xandikos/web.py
@@ -36,6 +36,7 @@ from xandikos.icalendar import ICalendarFile
from xandikos.store import (
TreeGitStore,
GitStore,
+ InvalidFileContents,
NoSuchItem,
NotStoreError,
STORE_TYPE_ADDRESSBOOK,
@@ -102,9 +103,15 @@ class ObjectResource(webdav.Resource):
return self.file.content
def set_body(self, data, replace_etag=None):
- (name, etag) = self.store.import_one(
- self.name, self.content_type, data,
- replace_etag=extract_strong_etag(replace_etag))
+ try:
+ (name, etag) = self.store.import_one(
+ self.name, self.content_type, data,
+ replace_etag=extract_strong_etag(replace_etag))
+ except InvalidFileContents:
+ # TODO(jelmer): Not every invalid file is a calendar file..
+ raise webdav.PreconditionFailure(
+ '{%s}valid-calendar-data' % caldav.NAMESPACE,
+ 'Not a valid calendar file.')
return create_strong_etag(etag)
def get_content_language(self):
@@ -198,8 +205,14 @@ class StoreBasedCollection(object):
shutil.rmtree(os.path.join(self.store.path, name))
def create_member(self, name, contents, content_type):
- (name, etag) = self.store.import_one(name, content_type,
- contents)
+ try:
+ (name, etag) = self.store.import_one(name, content_type,
+ contents)
+ except InvalidFileContents:
+ # TODO(jelmer): Not every invalid file is a calendar file..
+ raise webdav.PreconditionFailure(
+ '{%s}valid-calendar-data' % caldav.NAMESPACE,
+ 'Not a valid calendar file.')
return (name, create_strong_etag(etag))
def iter_differences_since(self, old_token, new_token):
diff --git a/xandikos/webdav.py b/xandikos/webdav.py
index 4846753..aa126d7 100644
--- a/xandikos/webdav.py
+++ b/xandikos/webdav.py
@@ -52,6 +52,14 @@ class BadRequestError(Exception):
self.message = message
+class PreconditionFailure(Exception):
+ """A precondition failed."""
+
+ def __init__(self, precondition, description):
+ self.precondition = precondition
+ self.description = description
+
+
def etag_matches(condition, actual_etag):
"""Check if an etag matches an If-Matches condition.
@@ -1117,7 +1125,12 @@ class WebDAVApp(object):
start_response('405 Method Not Allowed', [])
return []
content_type = environ['CONTENT_TYPE'].split(';')[0]
- (name, etag) = r.create_member(None, new_contents, content_type)
+ try:
+ (name, etag) = r.create_member(None, new_contents, content_type)
+ except PreconditionFailure as e:
+ return _send_simple_dav_error(
+ environ, start_response, '412 Precondition Failed',
+ error=ET.Element(e.precondition))
href = environ['SCRIPT_NAME'] + urllib.parse.urljoin(path+'/', name)
start_response('200 OK', [
('Location', href)
@@ -1137,7 +1150,12 @@ class WebDAVApp(object):
start_response('412 Precondition Failed', [])
return []
if r is not None:
- new_etag = r.set_body(new_contents, current_etag)
+ try:
+ new_etag = r.set_body(new_contents, current_etag)
+ except PreconditionFailure as e:
+ return _send_simple_dav_error(
+ environ, start_response, '412 Precondition Failed',
+ error=ET.Element(e.precondition))
start_response('204 No Content', [
('ETag', new_etag)])
return []
@@ -1145,8 +1163,13 @@ class WebDAVApp(object):
container_path, name = posixpath.split(path)
r = self.backend.get_resource(container_path)
if r is not None:
- (new_name, new_etag) = r.create_member(
- name, new_contents, content_type)
+ try:
+ (new_name, new_etag) = r.create_member(
+ name, new_contents, content_type)
+ except PreconditionFailure as e:
+ return _send_simple_dav_error(
+ environ, start_response, '412 Precondition Failed',
+ error=ET.Element(e.precondition))
start_response('201 Created', [
('ETag', new_etag)])
return []