Skip to content

Commit 7f9113f

Browse files
committed
JCL-417: Validate LDP containment data in SolidContainer::getResources method
1 parent 4b37b94 commit 7f9113f

File tree

4 files changed

+88
-8
lines changed

4 files changed

+88
-8
lines changed

solid/src/main/java/com/inrupt/client/solid/SolidContainer.java

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,33 @@ public SolidContainer(final URI identifier, final Dataset dataset, final Metadat
7777
* @return the contained resources
7878
*/
7979
public Set<SolidResource> getResources() {
80-
final Node node = new Node(rdf.createIRI(getIdentifier().toString()), getGraph());
81-
try (final Stream<Node.TypedNode> stream = node.getResources()) {
82-
return stream.map(child -> {
83-
final Metadata.Builder builder = Metadata.newBuilder();
84-
getMetadata().getStorage().ifPresent(builder::storage);
85-
child.getTypes().forEach(builder::type);
86-
return new SolidResourceReference(URI.create(child.getIRIString()), builder.build());
87-
}).collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet));
80+
final String container = getIdentifier().toString();
81+
// As defined by the Solid Protocol, containers always end with a slash.
82+
if (container.endsWith("/")) {
83+
final Node node = new Node(rdf.createIRI(getIdentifier().toString()), getGraph());
84+
try (final Stream<Node.TypedNode> stream = node.getResources()) {
85+
return stream.flatMap(child -> {
86+
final URI childLocation = URI.create(child.getIRIString()).normalize();
87+
// Solid containment is based on URI path hierarchy,
88+
// so all child resources must start with the URL of the parent
89+
if (childLocation.toString().startsWith(container)) {
90+
final String relativePath = childLocation.toString().substring(container.length());
91+
final String normalizedPath = relativePath.endsWith("/") ?
92+
relativePath.substring(0, relativePath.length() - 1) : relativePath;
93+
// Solid containment occurs via direct decent,
94+
// so any recursively contained resources must not be included
95+
if (!normalizedPath.isEmpty() && !normalizedPath.contains("/")) {
96+
final Metadata.Builder builder = Metadata.newBuilder();
97+
getMetadata().getStorage().ifPresent(builder::storage);
98+
child.getTypes().forEach(builder::type);
99+
return Stream.of(new SolidResourceReference(childLocation, builder.build()));
100+
}
101+
}
102+
return Stream.empty();
103+
}).collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet));
104+
}
88105
}
106+
return Collections.emptySet();
89107
}
90108

91109
/**

solid/src/test/java/com/inrupt/client/solid/SolidClientTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.inrupt.client.Response;
2929
import com.inrupt.client.auth.Session;
3030
import com.inrupt.client.spi.RDFFactory;
31+
import com.inrupt.client.util.URIBuilder;
3132

3233
import java.io.ByteArrayInputStream;
3334
import java.io.IOException;
@@ -36,6 +37,7 @@
3637
import java.util.*;
3738
import java.util.concurrent.CompletableFuture;
3839
import java.util.concurrent.CompletionException;
40+
import java.util.stream.Collectors;
3941
import java.util.stream.Stream;
4042

4143
import org.apache.commons.rdf.api.RDF;
@@ -216,6 +218,22 @@ void testGetBinaryCreate() {
216218
}).toCompletableFuture().join();
217219
}
218220

221+
@Test
222+
void testSolidContainer() {
223+
final URI uri = URI.create(config.get("solid_resource_uri") + "/container/");
224+
final Set<URI> expected = new HashSet<>();
225+
expected.add(URIBuilder.newBuilder(uri).path("newContainer/").build());
226+
expected.add(URIBuilder.newBuilder(uri).path("test.txt").build());
227+
expected.add(URIBuilder.newBuilder(uri).path("test2.txt").build());
228+
229+
client.read(uri, SolidContainer.class).thenAccept(container -> {
230+
try (final SolidContainer c = container) {
231+
assertEquals(expected,
232+
c.getResources().stream().map(SolidResource::getIdentifier).collect(Collectors.toSet()));
233+
}
234+
}).toCompletableFuture().join();
235+
}
236+
219237
@Test
220238
void testBinaryCreate() throws IOException {
221239
final URI uri = URI.create(config.get("solid_resource_uri") + "/binary");

solid/src/test/java/com/inrupt/client/solid/SolidMockHttpService.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,25 @@ private void setupMocks() {
8080
)
8181
);
8282

83+
wireMockServer.stubFor(get(urlEqualTo("/container/"))
84+
.withHeader("User-Agent", equalTo(USER_AGENT))
85+
.willReturn(aResponse()
86+
.withStatus(200)
87+
.withHeader("Content-Type", "text/turtle")
88+
.withHeader("Link", Link.of(LDP.BasicContainer, "type").toString())
89+
.withHeader("Link", Link.of(URI.create("http://acl.example/solid/"), "acl").toString())
90+
.withHeader("Link", Link.of(URI.create("http://storage.example/"),
91+
PIM.storage).toString())
92+
.withHeader("Link", Link.of(URI.create("https://history.test/"), "timegate").toString())
93+
.withHeader("WAC-Allow", "user=\"read write\",public=\"read\"")
94+
.withHeader("Allow", "POST, PUT, PATCH")
95+
.withHeader("Accept-Post", "application/ld+json, text/turtle")
96+
.withHeader("Accept-Put", "application/ld+json, text/turtle")
97+
.withHeader("Accept-Patch", "application/sparql-update, text/n3")
98+
.withBodyFile("container.ttl")
99+
)
100+
);
101+
83102
wireMockServer.stubFor(get(urlEqualTo("/recipe"))
84103
.withHeader("User-Agent", equalTo(USER_AGENT))
85104
.willReturn(aResponse()
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
@prefix dct: <http://purl.org/dc/terms/>.
2+
@prefix ldp: <http://www.w3.org/ns/ldp#>.
3+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
4+
@prefix pl: <http://www.w3.org/ns/iana/media-types/text/plain#>.
5+
6+
<>
7+
a ldp:BasicContainer ;
8+
dct:modified "2022-11-25T10:36:36Z"^^xsd:dateTime;
9+
ldp:contains <newContainer/>, <test.txt>, <test2.txt> .
10+
<newContainer/>
11+
a ldp:BasicContainer ;
12+
dct:modified "2022-11-25T10:36:36Z"^^xsd:dateTime .
13+
<test.txt>
14+
a pl:Resource, ldp:NonRDFSource;
15+
dct:modified "2022-11-25T10:34:14Z"^^xsd:dateTime .
16+
<test2.txt>
17+
a pl:Resource, ldp:NonRDFSource;
18+
dct:modified "2022-11-25T10:37:06Z"^^xsd:dateTime .
19+
20+
# These containment triples should not be included in a getResources response
21+
<>
22+
ldp:contains <https://example.com/other> , <newContainer/child> , <> , <./> .
23+
<https://example.test/container/>
24+
a ldp:BasicContainer ;
25+
ldp:contains <https://example.test/container/external> .

0 commit comments

Comments
 (0)