This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git


The following commit(s) were added to refs/heads/master by this push:
     new ebf10df  JAMES-3662 Accept CORS headers without the JMAP API 
restriction on "Accept" headers (#699)
ebf10df is described below

commit ebf10dfe499b79b6156a3e19adfb9968fa67c69a
Author: Benoit TELLIER <[email protected]>
AuthorDate: Tue Oct 19 08:25:38 2021 +0700

    JAMES-3662 Accept CORS headers without the JMAP API restriction on "Accept" 
headers (#699)
---
 .../java/org/apache/james/jmap/JMAPServer.java     | 29 ++++++++--
 .../java/org/apache/james/jmap/JMAPServerTest.java | 63 ++++++++++++++++++++--
 2 files changed, 82 insertions(+), 10 deletions(-)

diff --git 
a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java 
b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
index e4a7938..d549cc3 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServer.java
@@ -22,8 +22,10 @@ package org.apache.james.jmap;
 import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
 import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
 
+import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Stream;
 
 import javax.annotation.PreDestroy;
 import javax.inject.Inject;
@@ -33,9 +35,11 @@ import org.apache.james.lifecycle.api.Startable;
 import org.apache.james.util.Port;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.Multimap;
 
+import io.netty.handler.codec.http.HttpMethod;
 import reactor.netty.DisposableServer;
 import reactor.netty.http.server.HttpServer;
 import reactor.netty.http.server.HttpServerRequest;
@@ -46,6 +50,7 @@ public class JMAPServer implements Startable {
     private final JMAPConfiguration configuration;
     private final VersionParser versionParser;
     private final Multimap<Version, JMAPRoute> routes;
+    private final List<JMAPRoute> corsRoutes;
     private Optional<DisposableServer> server;
 
     @Inject
@@ -59,9 +64,16 @@ public class JMAPServer implements Startable {
             .flatMap(version -> jmapRoutesHandlers.stream()
                 .flatMap(handler -> handler.routes(version)
                     .map(route -> Pair.of(version, route))))
+            .filter(route -> 
!route.getRight().getEndpoint().getMethod().equals(HttpMethod.OPTIONS))
             .collect(ImmutableListMultimap.toImmutableListMultimap(
                 Pair::getKey,
                 Pair::getValue));
+        this.corsRoutes = versionParser.getSupportedVersions()
+            .stream()
+            .flatMap(version -> jmapRoutesHandlers.stream()
+                .flatMap(handler -> handler.routes(version)))
+            .filter(route -> 
route.getEndpoint().getMethod().equals(HttpMethod.OPTIONS))
+            .collect(ImmutableList.toImmutableList());
     }
 
     public Port getPort() {
@@ -87,19 +99,26 @@ public class JMAPServer implements Startable {
     }
 
     private JMAPRoute.Action handleVersionRoute(HttpServerRequest request) {
+        if (request.method().equals(HttpMethod.OPTIONS)) {
+            return retrieveMatchingAction(request, corsRoutes.stream());
+        }
         try {
             Version version = versionParser.parseRequestVersionHeader(request);
 
-            return routes.get(version).stream()
-                .filter(jmapRoute -> jmapRoute.matches(request))
-                .map(JMAPRoute::getAction)
-                .findFirst()
-                .orElse((req, res) -> res.status(NOT_FOUND).send());
+            return retrieveMatchingAction(request, 
routes.get(version).stream());
         } catch (IllegalArgumentException e) {
             return (req, res) -> res.status(BAD_REQUEST).send();
         }
     }
 
+    private JMAPRoute.Action retrieveMatchingAction(HttpServerRequest request, 
Stream<JMAPRoute> routeStream) {
+        return routeStream
+            .filter(jmapRoute -> jmapRoute.matches(request))
+            .map(JMAPRoute::getAction)
+            .findFirst()
+            .orElse((req, res) -> res.status(NOT_FOUND).send());
+    }
+
     @PreDestroy
     public void stop() {
         server.ifPresent(DisposableServer::disposeNow);
diff --git 
a/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java 
b/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
index 207e12a..7748951 100644
--- 
a/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
+++ 
b/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
@@ -22,6 +22,7 @@ package org.apache.james.jmap;
 import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT;
 import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
 import static io.restassured.RestAssured.given;
+import static io.restassured.RestAssured.when;
 import static io.restassured.config.EncoderConfig.encoderConfig;
 import static io.restassured.config.RestAssuredConfig.newConfig;
 import static org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE_UTF8;
@@ -37,8 +38,6 @@ import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.google.common.collect.ImmutableSet;
 
@@ -81,6 +80,14 @@ class JMAPServerTest {
             new FakeJMAPRoutes(AUTHENTICATION_ENDPOINTS, Version.RFC8621))
     );
 
+    private static final ImmutableSet<JMAPRoutesHandler> CORS_ROUTES = 
ImmutableSet.of(
+        new JMAPRoutesHandler(
+            Version.DRAFT,
+            new FakeJMAPRoutes(ImmutableSet.of(new 
Endpoint(HttpMethod.OPTIONS, "/a")), Version.DRAFT)),
+        new JMAPRoutesHandler(
+            Version.RFC8621,
+            new FakeJMAPRoutes(ImmutableSet.of(new 
Endpoint(HttpMethod.OPTIONS, "/b")), Version.RFC8621)));
+
     private static final ImmutableSet<Version> SUPPORTED_VERSIONS = 
ImmutableSet.of(
         Version.DRAFT,
         Version.RFC8621
@@ -222,9 +229,50 @@ class JMAPServerTest {
         }
     }
 
-    private static class FakeJMAPRoutes implements JMAPRoutes {
-        private static final Logger LOGGER = 
LoggerFactory.getLogger(FakeJMAPRoutes.class);
+    @Nested
+    class CorsRouteVersioningTest {
+        JMAPServer server;
+
+        @BeforeEach
+        void setUp() {
+            VersionParser versionParser = new 
VersionParser(SUPPORTED_VERSIONS, JMAPConfiguration.DEFAULT);
+            server = new JMAPServer(TEST_CONFIGURATION, CORS_ROUTES, 
versionParser);
+            server.start();
+
+            RestAssured.requestSpecification = new RequestSpecBuilder()
+                .setContentType(ContentType.JSON)
+                .setAccept(ContentType.JSON)
+                
.setConfig(newConfig().encoderConfig(encoderConfig().defaultContentCharset(StandardCharsets.UTF_8)))
+                .setPort(server.getPort().getValue())
+                .build();
+        }
+
+        @AfterEach
+        void tearDown() {
+            server.stop();
+        }
 
+        @Test
+        void corsRoutesOfAllVersionsShouldBeExposed() {
+            when()
+                .options("/b")
+            .then()
+                .statusCode(200)
+                .header("Access-Control-Allow-Origin", "*")
+                .header("Access-Control-Allow-Methods", "GET, POST, DELETE, 
PUT")
+                .header("Access-Control-Allow-Headers", "Content-Type, 
Authorization, Accept");
+
+            when()
+                .options("/a")
+            .then()
+                .statusCode(200)
+                .header("Access-Control-Allow-Origin", "*")
+                .header("Access-Control-Allow-Methods", "GET, POST, DELETE, 
PUT")
+                .header("Access-Control-Allow-Headers", "Content-Type, 
Authorization, Accept");
+        }
+    }
+
+    private static class FakeJMAPRoutes implements JMAPRoutes {
         private final Set<Endpoint> endpoints;
         private final Version version;
 
@@ -238,7 +286,12 @@ class JMAPServerTest {
             return endpoints.stream()
                 .map(endpoint -> JMAPRoute.builder()
                     .endpoint(endpoint)
-                    .action((request, response) -> 
sendVersionResponse(response))
+                    .action((request, response) -> {
+                        if (endpoint.getMethod().equals(HttpMethod.OPTIONS)) {
+                            return 
JMAPRoutes.CORS_CONTROL.handleRequest(request, response);
+                        }
+                        return sendVersionResponse(response);
+                    })
                     .noCorsHeaders());
         }
 

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to