diff options
author | Jelmer Vernooij <jelmer@jelmer.uk> | 2017-03-16 04:29:35 +0000 |
---|---|---|
committer | Jelmer Vernooij <jelmer@jelmer.uk> | 2017-03-16 04:29:35 +0000 |
commit | a31e8ba9a491018e8b148340e53830cad07ebf00 (patch) | |
tree | fb5faf16d0dbcfd95edeb71bb0db8624952e7b7e | |
parent | b5e9986ee3f65b5139468b67579a6ba6404808f0 (diff) |
Check file validity.
-rw-r--r-- | xandikos/icalendar.py | 9 | ||||
-rw-r--r-- | xandikos/store.py | 17 | ||||
-rw-r--r-- | xandikos/web.py | 23 | ||||
-rw-r--r-- | xandikos/webdav.py | 31 |
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 [] |