| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| Expand Up | @@ -20,21 +20,26 @@ | |
| */ | ||
| package com.inrupt.client.solid; | ||
|
|
||
| import com.inrupt.client.ValidationResult; | ||
| import com.inrupt.client.vocabulary.LDP; | ||
| import com.inrupt.client.vocabulary.RDF; | ||
| import com.inrupt.rdf.wrapping.commons.ValueMappings; | ||
| import com.inrupt.rdf.wrapping.commons.WrapperIRI; | ||
|
|
||
| import java.net.URI; | ||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| import java.util.function.Predicate; | ||
| import java.util.stream.Collectors; | ||
| import java.util.stream.Stream; | ||
|
|
||
| import org.apache.commons.rdf.api.Dataset; | ||
| import org.apache.commons.rdf.api.Graph; | ||
| import org.apache.commons.rdf.api.IRI; | ||
| import org.apache.commons.rdf.api.RDFTerm; | ||
| import org.apache.commons.rdf.api.Triple; | ||
|
|
||
| /** | ||
| * A Solid Container Object. | ||
| Expand Down Expand Up | @@ -77,15 +82,43 @@ public SolidContainer(final URI identifier, final Dataset dataset, final Metadat | |
| * @return the contained resources | ||
| */ | ||
| public Set<SolidResource> getResources() { | ||
| final Node node = new Node(rdf.createIRI(getIdentifier().toString()), getGraph()); | ||
| try (final Stream<Node.TypedNode> stream = node.getResources()) { | ||
| return stream.map(child -> { | ||
| final Metadata.Builder builder = Metadata.newBuilder(); | ||
| getMetadata().getStorage().ifPresent(builder::storage); | ||
| child.getTypes().forEach(builder::type); | ||
| return new SolidResourceReference(URI.create(child.getIRIString()), builder.build()); | ||
| }).collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet)); | ||
| final String container = normalize(getIdentifier()); | ||
| // As defined by the Solid Protocol, containers always end with a slash. | ||
| if (container.endsWith("/")) { | ||
|
Comment thread
Copy link
Copy Markdown
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityI wonder if we could have a dedicated error thrown here and upon creation too, if the URI does not end in a slash.
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityI would not be inclined to throw an exception here. Generally, I'd prefer the client libraries to be generous in the data they accept. In this particular case, if the resource is a SolidContainer, then the getResources() method must follow the rules of Solid, but if the resource isn't strictly a SolidContainer, it shouldn't cause the consuming application to throw an exception (and thus the getResources method would just return an empty collection). This condition is specifically to prevent against such cases as this: A resource is https://example.com/resource and it includes the triple: <> ldp:contains <https://example.com/resourcechild> . That triple should not cause the getResources method to include the sibling resource from being included in that list (since it doesn't conform to the Solid definition of containment)
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityOne thing to note is that these checks are put in place to prevent a malicious or misbehaving server from causing an application to perform unexpected operations on non-contained resources. For well-behaved servers, this will never be an issue and there is no need to add these constraints, but we can't expect all servers to be well-behaved
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityInterestingly, your last comment was the reason for which we decided to throw an error in the JS implementation of this feature: it may be desirable to let the consuming app know that there is something wrong with the target resource so that it takes action and removes the offending triple, so that other clients who don't implement this check don't perform the unintended operations we are preventing against here. I would prefer the DX to be convergent between JS and Java, so I'm happy to filter out the resources that do not follow slash semantics from the children list, but isn't there value in communicating this inconsistency to the app/user, especially when they can act on it?
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityMy rationale is basically the "robustness principle" https://en.wikipedia.org/wiki/Robustness_principle Be conservative in what you do and liberal in what you accept from others. There may be cases where an RDFSource is used to model an example Container's RDF or where an app is intending to write certain triples or where a server is simply doing things incorrectly. There will be a lot of "slightly off" data in the wild, and I think it is important that client libraries don't just blow up in those cases. As long as the data is syntactically correct, the semantics may vary from domain to domain, and it would be unfortunate if we imposed constraints that prevented certain interesting use cases.
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityHere is a compromise (actually, I think it's a better solution overall)
Sorry, something went wrong.
timea-solid reacted with thumbs up emoji
All reactions
Copy link
Copy Markdown
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityI see both sides of the coin but I like the validate() option a lot! Validate should by default already have this check in then.
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityHaving a distinction between a read method that just ignores triples we deem invalid and a validation method where the intent is to be aware of any issues sounds like a good pattern. I'll think about other cases when it would be applicable.
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality@timea-solid and @NSeydoux I'll wait on merging this until you have a chance to see my latest additions to this in d3fd371
Sorry, something went wrong.
All reactions
|
||
| final Node node = new Node(rdf.createIRI(getIdentifier().toString()), getGraph()); | ||
| try (final Stream<Node.TypedNode> stream = node.getResources()) { | ||
| return stream.filter(child -> verifyContainmentIri(container, child)).map(child -> { | ||
| final Metadata.Builder builder = Metadata.newBuilder(); | ||
| getMetadata().getStorage().ifPresent(builder::storage); | ||
| child.getTypes().forEach(builder::type); | ||
| return new SolidResourceReference(URI.create(child.getIRIString()), builder.build()); | ||
| }).collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet)); | ||
| } | ||
| } | ||
| return Collections.emptySet(); | ||
| } | ||
|
|
||
| @Override | ||
| public ValidationResult validate() { | ||
| // Get the normalized container URI | ||
| final String container = normalize(getIdentifier()); | ||
| final List<String> messages = new ArrayList<>(); | ||
| // Verify that the container URI path ends with a slash | ||
| if (!container.endsWith("/")) { | ||
| messages.add("Container URI does not end in a slash"); | ||
| } | ||
|
|
||
| // Verify that all ldp:contains triples align with Solid expectations | ||
| getGraph().stream(null, rdf.createIRI(LDP.contains.toString()), null) | ||
| .collect(Collectors.partitioningBy(verifyContainmentTriple(container))) | ||
| .get(false) // we are only concerned with the invalid triples | ||
| .forEach(triple -> messages.add("Invalid containment triple: " + triple.getSubject().ntriplesString() + | ||
| " ldp:contains " + triple.getObject().ntriplesString() + " .")); | ||
|
|
||
| if (messages.isEmpty()) { | ||
| return new ValidationResult(true); | ||
| } | ||
| return new ValidationResult(false, messages); | ||
| } | ||
|
|
||
| /** | ||
| Expand All | @@ -99,6 +132,49 @@ public Stream<SolidResource> getContainedResources() { | |
| return getResources().stream(); | ||
| } | ||
|
|
||
| static String normalize(final IRI iri) { | ||
| return normalize(URI.create(iri.getIRIString())); | ||
| } | ||
|
|
||
| static String normalize(final URI uri) { | ||
| return uri.normalize().toString().split("#")[0].split("\\?")[0]; | ||
| } | ||
|
|
||
| static Predicate<Triple> verifyContainmentTriple(final String container) { | ||
| final IRI subject = rdf.createIRI(container); | ||
| return triple -> { | ||
| if (!triple.getSubject().equals(subject)) { | ||
| // Out-of-domain containment triple subject | ||
| return false; | ||
| } | ||
| if (triple.getObject() instanceof IRI) { | ||
| return verifyContainmentIri(container, (IRI) triple.getObject()); | ||
| } | ||
| // Non-URI containment triple object | ||
| return false; | ||
| }; | ||
| } | ||
|
|
||
| static boolean verifyContainmentIri(final String container, final IRI object) { | ||
| if (!object.getIRIString().startsWith(container)) { | ||
| // Out-of-domain containment triple object | ||
| return false; | ||
| } else { | ||
| final String relativePath = object.getIRIString().substring(container.length()); | ||
| final String normalizedPath = relativePath.endsWith("/") ? | ||
| relativePath.substring(0, relativePath.length() - 1) : relativePath; | ||
| if (normalizedPath.isEmpty()) { | ||
| // Containment triple subject and object cannot be the same | ||
| return false; | ||
| } | ||
| if (normalizedPath.contains("/")) { | ||
| // Containment cannot skip intermediate nodes | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| @SuppressWarnings("java:S2160") // Wrapper equality is correctly delegated to underlying node | ||
| static final class Node extends WrapperIRI { | ||
| private final IRI ldpContains = rdf.createIRI(LDP.contains.toString()); | ||
| Expand Down | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| @prefix dct: <http://purl.org/dc/terms/>. | ||
| @prefix ldp: <http://www.w3.org/ns/ldp#>. | ||
| @prefix xsd: <http://www.w3.org/2001/XMLSchema#>. | ||
| @prefix pl: <http://www.w3.org/ns/iana/media-types/text/plain#>. | ||
|
|
||
| <> | ||
| a ldp:BasicContainer ; | ||
| dct:modified "2022-11-25T10:36:36Z"^^xsd:dateTime; | ||
| ldp:contains <newContainer/>, <test.txt>, <test2.txt> . | ||
| <newContainer/> | ||
| a ldp:BasicContainer ; | ||
| dct:modified "2022-11-25T10:36:36Z"^^xsd:dateTime . | ||
| <test.txt> | ||
| a pl:Resource, ldp:NonRDFSource; | ||
| dct:modified "2022-11-25T10:34:14Z"^^xsd:dateTime . | ||
| <test2.txt> | ||
| a pl:Resource, ldp:NonRDFSource; | ||
| dct:modified "2022-11-25T10:37:06Z"^^xsd:dateTime . | ||
|
|
||
| # These containment triples should not be included in a getResources response | ||
| <> | ||
| ldp:contains <https://example.com/other> , <newContainer/child> , <> , <./> . | ||
| <https://example.test/container/> | ||
| a ldp:BasicContainer ; | ||
| ldp:contains <https://example.test/container/external> . |
| Back | FazBrowse Home | New Git URL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityI believe now you can call validate() here.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityNot validate() directly, as that performs a number of checks that are irrelevant here. You will see that in https://github.com/inrupt/solid-client-java/pull/571/files#diff-2c9b192110a924fa828ba14eaa2e7359afa8b3b811c8ad12fd6563e9161d7ba0R90, we re-use some of the same code as validate in this pipeline
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.