فهرست منبع

Merge pull request #118 from scossu/issues/115

Issues/115
Stefano Cossu 4 سال پیش
والد
کامیت
6562722496
4فایلهای تغییر یافته به همراه115 افزوده شده و 55 حذف شده
  1. 41 41
      lakesuperior/endpoints/ldp.py
  2. 14 1
      lakesuperior/exceptions.py
  3. 14 13
      lakesuperior/model/ldp/ldp_factory.py
  4. 46 0
      tests/3_endpoints/test_3_0_ldp.py

+ 41 - 41
lakesuperior/endpoints/ldp.py

@@ -17,14 +17,10 @@ from rdflib import Graph, plugin, parser#, serializer
 from werkzeug.http import parse_date
 
 from lakesuperior import env
+from lakesuperior import exceptions as exc
 from lakesuperior.api import resource as rsrc_api
 from lakesuperior.dictionaries.namespaces import ns_collection as nsc
 from lakesuperior.dictionaries.namespaces import ns_mgr as nsm
-from lakesuperior.exceptions import (
-        ChecksumValidationError, ResourceNotExistsError, TombstoneError,
-        ServerManagedTermError, InvalidResourceError, SingleSubjectError,
-        ResourceExistsError, IncompatibleLdpTypeError,
-        IndigestibleError)
 from lakesuperior.globals import RES_CREATED
 from lakesuperior.model.ldp.ldp_factory import LdpFactory
 from lakesuperior.model.ldp.ldp_nr import LdpNr
@@ -152,7 +148,7 @@ def get_resource(uid, out_fmt=None):
     try:
         if not rsrc_api.exists(uid):
             return '', 404
-    except TombstoneError as e:
+    except exc.TombstoneError as e:
         return _tombstone_response(e, uid)
 
     # Then process the condition headers.
@@ -229,11 +225,11 @@ def get_version_info(uid):
     rdf_mimetype = _best_rdf_mimetype() or DEFAULT_RDF_MIMETYPE
     try:
         imr = rsrc_api.get_version_info(uid)
-    except ResourceNotExistsError as e:
+    except exc.ResourceNotExistsError as e:
         return str(e), 404
-    except InvalidResourceError as e:
+    except exc.InvalidResourceError as e:
         return str(e), 409
-    except TombstoneError as e:
+    except exc.TombstoneError as e:
         return _tombstone_response(e, uid)
     else:
         with store.txn_ctx():
@@ -251,11 +247,11 @@ def get_version(uid, ver_uid):
     rdf_mimetype = _best_rdf_mimetype() or DEFAULT_RDF_MIMETYPE
     try:
         imr = rsrc_api.get_version(uid, ver_uid)
-    except ResourceNotExistsError as e:
+    except exc.ResourceNotExistsError as e:
         return str(e), 404
-    except InvalidResourceError as e:
+    except exc.InvalidResourceError as e:
         return str(e), 409
-    except TombstoneError as e:
+    except exc.TombstoneError as e:
         return _tombstone_response(e, uid)
     else:
         with store.txn_ctx():
@@ -277,17 +273,19 @@ def post_resource(parent_uid):
     try:
         kwargs = _create_args_from_req(slug)
         rsrc = rsrc_api.create(parent_uid, slug, **kwargs)
-    except IndigestibleError:
+    except exc.RdfParsingError as e:
+        return str(e), 400
+    except exc.IndigestibleError:
         return (
             f'Unable to parse digest header: {request.headers["digest"]}'
         ), 400
-    except ResourceNotExistsError as e:
+    except exc.ResourceNotExistsError as e:
         return str(e), 404
-    except (InvalidResourceError, ChecksumValidationError) as e:
+    except (exc.InvalidResourceError, exc.ChecksumValidationError) as e:
         return str(e), 409
-    except TombstoneError as e:
+    except exc.TombstoneError as e:
         return _tombstone_response(e, uid)
-    except ServerManagedTermError as e:
+    except exc.ServerManagedTermError as e:
         rsp_headers['Link'] = (
             f'<{uri}>; rel="{nsc["ldp"].constrainedBy}"; '
             f'{g.webroot}/info/ldp_constraints"'
@@ -326,19 +324,21 @@ def put_resource(uid):
     try:
         kwargs = _create_args_from_req(uid)
         evt, rsrc = rsrc_api.create_or_replace(uid, **kwargs)
-    except IndigestibleError:
+    except exc.RdfParsingError as e:
+        return str(e), 400
+    except exc.IndigestibleError:
         return (
-            f'Unable to parse digest header: {request.headers["digest"]}'
-        ), 400
+                f'Unable to parse digest header: {request.headers["digest"]}',
+                 400)
     except (
-            InvalidResourceError, ChecksumValidationError,
-            ResourceExistsError) as e:
+            exc.InvalidResourceError, exc.ChecksumValidationError,
+            exc.ResourceExistsError) as e:
         return str(e), 409
-    except (ServerManagedTermError, SingleSubjectError) as e:
+    except (exc.ServerManagedTermError, exc.SingleSubjectError) as e:
         return str(e), 412
-    except IncompatibleLdpTypeError as e:
+    except exc.IncompatibleLdpTypeError as e:
         return str(e), 415
-    except TombstoneError as e:
+    except exc.TombstoneError as e:
         return _tombstone_response(e, uid)
 
     with store.txn_ctx():
@@ -371,7 +371,7 @@ def patch_resource(uid, is_metadata=False):
     try:
         if not rsrc_api.exists(uid):
             return '', 404
-    except TombstoneError as e:
+    except exc.TombstoneError as e:
         return _tombstone_response(e, uid)
 
     # Then process the condition headers.
@@ -390,9 +390,9 @@ def patch_resource(uid, is_metadata=False):
     local_update_str = g.tbox.localize_ext_str(update_str, nsc['fcres'][uid])
     try:
         rsrc = rsrc_api.update(uid, local_update_str, is_metadata, handling)
-    except (ServerManagedTermError, SingleSubjectError) as e:
+    except (exc.ServerManagedTermError, exc.SingleSubjectError) as e:
         return str(e), 412
-    except InvalidResourceError as e:
+    except exc.InvalidResourceError as e:
         return str(e), 415
     else:
         with store.txn_ctx():
@@ -423,7 +423,7 @@ def delete_resource(uid):
     try:
         if not rsrc_api.exists(uid):
             return '', 404
-    except TombstoneError as e:
+    except exc.TombstoneError as e:
         return _tombstone_response(e, uid)
 
     # Then process the condition headers.
@@ -455,7 +455,7 @@ def tombstone(uid):
     """
     try:
         rsrc_api.get(uid)
-    except TombstoneError as e:
+    except exc.TombstoneError as e:
         if request.method == 'DELETE':
             if e.uid == uid:
                 rsrc_api.delete(uid, False)
@@ -471,7 +471,7 @@ def tombstone(uid):
                 return _tombstone_response(e, uid)
         else:
             return 'Method Not Allowed.', 405
-    except ResourceNotExistsError as e:
+    except exc.ResourceNotExistsError as e:
         return str(e), 404
     else:
         return '', 404
@@ -488,11 +488,11 @@ def post_version(uid):
 
     try:
         ver_uid = rsrc_api.create_version(uid, ver_uid)
-    except ResourceNotExistsError as e:
+    except exc.ResourceNotExistsError as e:
         return str(e), 404
-    except InvalidResourceError as e:
+    except exc.InvalidResourceError as e:
         return str(e), 409
-    except TombstoneError as e:
+    except exc.TombstoneError as e:
         return _tombstone_response(e, uid)
     else:
         return '', 201, {'Location': g.tbox.uid_to_uri(ver_uid)}
@@ -510,11 +510,11 @@ def patch_version(uid, ver_uid):
     """
     try:
         rsrc_api.revert_to_version(uid, ver_uid)
-    except ResourceNotExistsError as e:
+    except exc.ResourceNotExistsError as e:
         return str(e), 404
-    except InvalidResourceError as e:
+    except exc.InvalidResourceError as e:
         return str(e), 409
-    except TombstoneError as e:
+    except exc.TombstoneError as e:
         return _tombstone_response(e, uid)
     else:
         return '', 204
@@ -611,7 +611,7 @@ def _create_args_from_req(uid):
                     request.headers['digest'].split('=')
                 )
             except ValueError:
-                raise IndigestibleError(uid)
+                raise exc.IndigestibleError(uid)
 
     return kwargs
 
@@ -798,7 +798,7 @@ def _condition_hdr_match(uid, headers, safe=True):
         with store.txn_ctx():
             try:
                 rsrc_meta = rsrc_api.get_metadata(uid)
-            except ResourceNotExistsError:
+            except exc.ResourceNotExistsError:
                 rsrc_meta = Graph(uri=nsc['fcres'][uid])
 
             digest_prop = rsrc_meta.value(nsc['premis'].hasMessageDigest)
@@ -820,7 +820,7 @@ def _condition_hdr_match(uid, headers, safe=True):
 
         try:
             rsrc_meta = rsrc_api.get_metadata(uid)
-        except ResourceNotExistsError:
+        except exc.ResourceNotExistsError:
             return {
                 'if-modified-since': False,
                 'if-unmodified-since': False
@@ -861,7 +861,7 @@ def _process_cond_headers(uid, headers, safe=True):
     """
     try:
         cond_match = _condition_hdr_match(uid, headers, safe)
-    except TombstoneError as e:
+    except exc.TombstoneError as e:
         return _tombstone_response(e, uid)
 
     if cond_match:

+ 14 - 1
lakesuperior/exceptions.py

@@ -128,7 +128,20 @@ class ServerManagedTermError(RuntimeError):
 
 
 
-class InvalidTripleError(RuntimeError):
+class RdfParsingError(ValueError):
+    """
+    Raised when a string cannot be parsed as RDF in the given format.
+    """
+    def __init__(self, fmt, parser_msg=''):
+        self.fmt = fmt
+        self.parser_msg = parser_msg
+
+    def __str__(self):
+        return (f'Error parsing RDF in {self.fmt} format: {self.parser_msg}')
+
+
+
+class InvalidTripleError(ValueError):
     '''
     Raised when a triple in a delta is not valid.
 

+ 14 - 13
lakesuperior/model/ldp/ldp_factory.py

@@ -7,14 +7,12 @@ from rdflib.resource import Resource
 from rdflib.namespace import RDF
 
 from lakesuperior import env
+from lakesuperior import exceptions as exc
 from lakesuperior.model.ldp.ldpr import Ldpr
 from lakesuperior.model.ldp.ldp_nr import LdpNr
 from lakesuperior.model.ldp.ldp_rs import LdpRs, Ldpc, LdpDc, LdpIc
 from lakesuperior.config_parser import config
 from lakesuperior.dictionaries.namespaces import ns_collection as nsc
-from lakesuperior.exceptions import (
-        IncompatibleLdpTypeError, InvalidResourceError, ResourceExistsError,
-        ResourceNotExistsError, TombstoneError)
 from lakesuperior.model.rdf.graph import Graph, from_rdf
 from lakesuperior.util.toolbox import rel_uri_to_urn
 
@@ -34,9 +32,9 @@ class LdpFactory:
     @staticmethod
     def new_container(uid):
         if not uid.startswith('/') or uid == '/':
-            raise InvalidResourceError(uid)
+            raise exc.InvalidResourceError(uid)
         if rdfly.ask_rsrc_exists(uid):
-            raise ResourceExistsError(uid)
+            raise exc.ResourceExistsError(uid)
         rsrc = Ldpc(uid, provided_imr=Graph(uri=nsc['fcres'][uid]))
 
         return rsrc
@@ -66,7 +64,7 @@ class LdpFactory:
             logger.info('Resource is a LDP-RS.')
             cls = LdpRs
         else:
-            raise ResourceNotExistsError(uid)
+            raise exc.ResourceNotExistsError(uid)
 
         rsrc = cls(uid, repr_opts, **kwargs)
         # Sneak in the already extracted metadata to save a query.
@@ -101,10 +99,13 @@ class LdpFactory:
         """
         uri = nsc['fcres'][uid]
         if rdf_data:
-            provided_imr = from_rdf(
-                uri=uri, data=rdf_data,
-                format=rdf_fmt, publicID=nsc['fcres'][uid]
-            )
+            try:
+                provided_imr = from_rdf(
+                        uri=uri, data=rdf_data,
+                        format=rdf_fmt, publicID=nsc['fcres'][uid])
+            except Exception as e:
+                raise exc.RdfParsingError(rdf_fmt, str(e))
+
         elif graph:
             provided_imr = Graph(
                 uri=uri, data={
@@ -136,7 +137,7 @@ class LdpFactory:
 
             # Make sure we are not updating an LDP-NR with an LDP-RS.
             if inst.is_stored and LDP_NR_TYPE in inst.ldp_types:
-                raise IncompatibleLdpTypeError(uid, mimetype)
+                raise exc.IncompatibleLdpTypeError(uid, mimetype)
 
             if kwargs.get('handling', 'strict') != 'none':
                 inst.check_mgd_terms(inst.provided_imr)
@@ -151,7 +152,7 @@ class LdpFactory:
 
             # Make sure we are not updating an LDP-RS with an LDP-NR.
             if inst.is_stored and LDP_RS_TYPE in inst.ldp_types:
-                raise IncompatibleLdpTypeError(uid, mimetype)
+                raise exc.IncompatibleLdpTypeError(uid, mimetype)
 
         logger.debug('Creating resource of type: {}'.format(
                 inst.__class__.__name__))
@@ -189,7 +190,7 @@ class LdpFactory:
 
         parent = LdpFactory.from_stored(parent_uid)
         if nsc['ldp'].Container not in parent.types:
-            raise InvalidResourceError(parent_uid,
+            raise exc.InvalidResourceError(parent_uid,
                     'Parent {} is not a container.')
 
         pfx = parent_uid.rstrip('/') + '/'

+ 46 - 0
tests/3_endpoints/test_3_0_ldp.py

@@ -318,6 +318,52 @@ class TestLdp:
         assert isomorphic(gr1, gr2)
 
 
+    def test_put_ldprs_invalid_rdf(self):
+        """
+        Verify that PUTting invalid RDF body returns HTTP 400.
+
+        However, when forcing LDP-RS, invalid RDF is always accepted.
+        """
+        from lakesuperior.endpoints.ldp import rdf_serializable_mimetypes
+
+        rdfstr = b'This is valid RDF because it ends with a period.'
+        for mt in rdf_serializable_mimetypes:
+            rsp_notok = self.client.put(
+                    f'/ldp/{uuid4()}', data=rdfstr, content_type=mt)
+            assert rsp_notok.status_code == 400
+
+            rsp_ok = self.client.put(
+                f'/ldp/{uuid4()}', data=rdfstr, content_type=mt,
+                headers={
+                    'link': '<http://www.w3.org/ns/ldp#NonRDFSource>;rel="type"'
+                }
+            )
+            assert rsp_ok.status_code == 201
+
+
+    def test_post_ldprs_invalid_rdf(self):
+        """
+        Verify that POSTing invalid RDF body returns HTTP 400.
+
+        However, when forcing LDP-RS, invalid RDF is always accepted.
+        """
+        from lakesuperior.endpoints.ldp import rdf_serializable_mimetypes
+
+        rdfstr = b'This is valid RDF because it ends with a period.'
+        for mt in rdf_serializable_mimetypes:
+            rsp_notok = self.client.post(
+                    f'/ldp', data=rdfstr, content_type=mt)
+            assert rsp_notok.status_code == 400
+
+            rsp_ok = self.client.post(
+                f'/ldp', data=rdfstr, content_type=mt,
+                headers={
+                    'link': '<http://www.w3.org/ns/ldp#NonRDFSource>;rel="type"'
+                }
+            )
+            assert rsp_ok.status_code == 201
+
+
     def test_metadata_describe_header(self):
         """
         Verify that a "describe" Link header is presented for LDP-NR metadata.