diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApi.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApi.java index 73760208e..2c4577603 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApi.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApi.java @@ -8,6 +8,7 @@ import org.briarproject.bramble.api.mailbox.MailboxProperties; import java.io.File; import java.io.IOException; import java.util.Collection; +import java.util.List; import javax.annotation.concurrent.Immutable; @@ -68,6 +69,14 @@ interface MailboxApi { void addFile(MailboxProperties properties, String folderId, File file) throws IOException, ApiException; + /** + * Used by owner and contacts to list their files to retrieve. + *

+ * Returns 200 OK with the list of files in JSON. + */ + List getFiles(MailboxProperties properties, String folderId) + throws IOException, ApiException; + @Immutable @JsonSerialize class MailboxContact { @@ -85,6 +94,16 @@ interface MailboxApi { } } + class MailboxFile { + public final String name; + public final long time; + + public MailboxFile(String name, long time) { + this.name = name; + this.time = time; + } + } + @Immutable class ApiException extends Exception { } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApiImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApiImpl.java index 53b2e5836..83efd7ed5 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApiImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxApiImpl.java @@ -3,6 +3,7 @@ package org.briarproject.bramble.mailbox; import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.briarproject.bramble.api.WeakSingletonProvider; import org.briarproject.bramble.api.contact.ContactId; @@ -165,6 +166,46 @@ class MailboxApiImpl implements MailboxApi { Response response = sendPostRequest(properties, path, body); if (response.code() != 200) throw new ApiException(); } + + @Override + public List getFiles(MailboxProperties properties, + String folderId) throws IOException, ApiException { + String path = "/files/" + folderId; + Response response = sendGetRequest(properties, path); + if (response.code() != 200) throw new ApiException(); + + ResponseBody body = response.body(); + if (body == null) throw new ApiException(); + try { + JsonNode node = mapper.readTree(body.string()); + JsonNode filesNode = node.get("files"); + if (filesNode == null || !filesNode.isArray()) { + throw new ApiException(); + } + List list = new ArrayList<>(); + for (JsonNode fileNode : filesNode) { + if (!fileNode.isObject()) throw new ApiException(); + ObjectNode objectNode = (ObjectNode) fileNode; + JsonNode nameNode = objectNode.get("name"); + JsonNode timeNode = objectNode.get("time"); + if (nameNode == null || !nameNode.isTextual()) { + throw new ApiException(); + } + if (timeNode == null || !timeNode.isNumber()) { + throw new ApiException(); + } + String name = nameNode.asText(); + long time = timeNode.asLong(); + if (!isValidToken(name)) throw new ApiException(); + if (time < 1) throw new ApiException(); + list.add(new MailboxFile(name, time)); + } + return list; + } catch (JacksonException e) { + throw new ApiException(); + } + } + /* Helper Functions */ private Response sendGetRequest(MailboxProperties properties, String path) diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxApiTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxApiTest.java index 3e88c2d8d..87707df7f 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxApiTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxApiTest.java @@ -1,10 +1,13 @@ package org.briarproject.bramble.mailbox; +import com.fasterxml.jackson.databind.ObjectMapper; + import org.briarproject.bramble.api.WeakSingletonProvider; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.mailbox.MailboxApi.ApiException; import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact; +import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile; import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException; import org.briarproject.bramble.test.BrambleTestCase; import org.junit.Rule; @@ -419,6 +422,117 @@ public class MailboxApiTest extends BrambleTestCase { assertToken(request3, token); } + @Test + public void testGetFiles() throws Exception { + MailboxFile mailboxFile1 = new MailboxFile(getMailboxSecret(), 1337); + MailboxFile mailboxFile2 = + new MailboxFile(getMailboxSecret(), System.currentTimeMillis()); + String fileResponse1 = + new ObjectMapper().writeValueAsString(mailboxFile1); + String fileResponse2 = + new ObjectMapper().writeValueAsString(mailboxFile2); + String validResponse1 = "{\"files\": [" + fileResponse1 + "] }"; + String validResponse2 = "{\"files\": [" + fileResponse1 + ", " + + fileResponse2 + "] }"; + String invalidResponse1 = "{\"files\":\"bar\"}"; + String invalidResponse2 = "{\"files\":{\"foo\":\"bar\"}}"; + String invalidResponse3 = "{\"files\": [" + fileResponse1 + ", 1] }"; + String invalidResponse4 = "{\"contacts\": [ 1, 2 ] }"; + + MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setBody(validResponse1)); + server.enqueue(new MockResponse().setBody(validResponse2)); + server.enqueue(new MockResponse()); + server.enqueue(new MockResponse().setBody(invalidResponse1)); + server.enqueue(new MockResponse().setBody(invalidResponse2)); + server.enqueue(new MockResponse().setBody(invalidResponse3)); + server.enqueue(new MockResponse().setBody(invalidResponse4)); + server.enqueue(new MockResponse().setResponseCode(401)); + server.enqueue(new MockResponse().setResponseCode(500)); + server.start(); + String baseUrl = getBaseUrl(server); + MailboxProperties properties = + new MailboxProperties(baseUrl, token, true); + + // valid response with one file + List received1 = api.getFiles(properties, contactInboxId); + assertEquals(1, received1.size()); + assertEquals(mailboxFile1.name, received1.get(0).name); + assertEquals(mailboxFile1.time, received1.get(0).time); + RecordedRequest request1 = server.takeRequest(); + assertEquals("/files/" + contactInboxId, request1.getPath()); + assertEquals("GET", request1.getMethod()); + assertToken(request1, token); + + // valid response with two files + List received2 = api.getFiles(properties, contactInboxId); + assertEquals(2, received2.size()); + assertEquals(mailboxFile1.name, received2.get(0).name); + assertEquals(mailboxFile1.time, received2.get(0).time); + assertEquals(mailboxFile2.name, received2.get(1).name); + assertEquals(mailboxFile2.time, received2.get(1).time); + RecordedRequest request2 = server.takeRequest(); + assertEquals("/files/" + contactInboxId, request1.getPath()); + assertEquals("GET", request2.getMethod()); + assertToken(request2, token); + + // empty body + assertThrows(ApiException.class, () -> + api.getFiles(properties, contactInboxId)); + RecordedRequest request3 = server.takeRequest(); + assertEquals("/files/" + contactInboxId, request3.getPath()); + assertEquals("GET", request3.getMethod()); + assertToken(request3, token); + + // invalid response: string instead of list + assertThrows(ApiException.class, () -> + api.getFiles(properties, contactInboxId)); + RecordedRequest request4 = server.takeRequest(); + assertEquals("/files/" + contactInboxId, request4.getPath()); + assertEquals("GET", request4.getMethod()); + assertToken(request4, token); + + // invalid response: object instead of list + assertThrows(ApiException.class, () -> + api.getFiles(properties, contactInboxId)); + RecordedRequest request5 = server.takeRequest(); + assertEquals("/files/" + contactInboxId, request5.getPath()); + assertEquals("GET", request5.getMethod()); + assertToken(request5, token); + + // invalid response: list with non-objects + assertThrows(ApiException.class, () -> + api.getFiles(properties, contactInboxId)); + RecordedRequest request6 = server.takeRequest(); + assertEquals("/files/" + contactInboxId, request6.getPath()); + assertEquals("GET", request6.getMethod()); + assertToken(request6, token); + + // no files key in root object + assertThrows(ApiException.class, () -> + api.getFiles(properties, contactInboxId)); + RecordedRequest request7 = server.takeRequest(); + assertEquals("/files/" + contactInboxId, request7.getPath()); + assertEquals("GET", request7.getMethod()); + assertToken(request7, token); + + // 401 not authorized + assertThrows(ApiException.class, () -> + api.getFiles(properties, contactInboxId)); + RecordedRequest request8 = server.takeRequest(); + assertEquals("/files/" + contactInboxId, request8.getPath()); + assertEquals("GET", request8.getMethod()); + assertToken(request8, token); + + // 500 internal server error + assertThrows(ApiException.class, + () -> api.getFiles(properties, contactInboxId)); + RecordedRequest request9 = server.takeRequest(); + assertEquals("/files/" + contactInboxId, request9.getPath()); + assertEquals("GET", request9.getMethod()); + assertToken(request9, token); + } + private String getBaseUrl(MockWebServer server) { String baseUrl = server.url("").toString(); return baseUrl.substring(0, baseUrl.length() - 1);