diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.kt index 175772f3232a..fc8a38700b1e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.kt @@ -427,7 +427,7 @@ public class WebSocketModule(context: ReactApplicationContext) : */ private fun getCookie(uri: String): String? { try { - val origin = URI(getDefaultOrigin(uri)) + val origin = getCookieLookupUri(uri) val cookieMap = cookieHandler.get(origin, HashMap>()) val cookieList = cookieMap["Cookie"] if (cookieList.isNullOrEmpty()) { @@ -459,6 +459,16 @@ public class WebSocketModule(context: ReactApplicationContext) : customClientBuilder?.apply(builder) } + /** Map a WebSocket URI's scheme to its HTTP(S) equivalent, e.g. "wss" -> "https". */ + private fun httpSchemeFor(requestURI: URI): String = + when (requestURI.scheme) { + "wss" -> "https" + "ws" -> "http" + "http", + "https" -> requestURI.scheme + else -> "" + } + /** * Get the default HTTP(S) origin for a specific WebSocket URI * @@ -468,14 +478,7 @@ public class WebSocketModule(context: ReactApplicationContext) : private fun getDefaultOrigin(uri: String): String { try { val requestURI = URI(uri) - val scheme = - when (requestURI.scheme) { - "wss" -> "https" - "ws" -> "http" - "http", - "https" -> requestURI.scheme - else -> "" - } + val scheme = httpSchemeFor(requestURI) val defaultOrigin = if (requestURI.port != -1) { @@ -489,5 +492,31 @@ public class WebSocketModule(context: ReactApplicationContext) : throw IllegalArgumentException("Unable to set $uri as default origin header") } } + + /** + * Get the URI used to look up cookies for a specific WebSocket URI, keeping its path so that + * path-scoped cookies are matched correctly. Query and fragment are dropped since cookies are + * scoped by path, not by query or fragment (RFC 6265). userInfo is also dropped so that + * credentials embedded in the URL are never forwarded to the cookie store. + * + * @param uri + * @return A URI with the endpoint converted to HTTP protocol (http[s]://host[:port]/path) + */ + private fun getCookieLookupUri(uri: String): URI { + try { + val requestURI = URI(uri) + return URI( + httpSchemeFor(requestURI), + null, + requestURI.host, + requestURI.port, + requestURI.path, + null, + null, + ) + } catch (e: URISyntaxException) { + throw IllegalArgumentException("Unable to get cookie lookup URI from $uri") + } + } } } diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/websocket/WebSocketModuleTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/websocket/WebSocketModuleTest.kt new file mode 100644 index 000000000000..bf92c30bd68e --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/websocket/WebSocketModuleTest.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.modules.websocket + +import java.net.URI +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test + +class WebSocketModuleTest { + + private fun getCookieLookupUri(uri: String): URI { + val method = + WebSocketModule.Companion::class.java.getDeclaredMethod( + "getCookieLookupUri", String::class.java) + method.isAccessible = true + return method.invoke(WebSocketModule.Companion, uri) as URI + } + + @Test + fun getCookieLookupUri_keepsPathForCookieMatching() { + val uri = getCookieLookupUri("wss://my.domain/signal-r/hubs/messages") + + assertThat(uri.toString()).isEqualTo("https://my.domain/signal-r/hubs/messages") + } + + @Test + fun getCookieLookupUri_keepsPortAndQuery() { + val uri = getCookieLookupUri("ws://my.domain:8080/path?token=abc") + + assertThat(uri.toString()).isEqualTo("http://my.domain:8080/path?token=abc") + } +}