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);