mirror of
https://github.com/react/react-native.git
synced 2026-09-28 13:23:09 +08:00
Fix Android WebSocket cookie lookup stripping the URL path (#58365)
Summary: `WebSocketModule.getCookie` looks up cookies through `getDefaultOrigin(uri)`, which strips the URL down to `scheme://host[:port]` before handing it to `ForwardingCookieHandler`. Android's `CookieManager` matches cookies against the full URL (domain and path), so any cookie set with a `Path` other than `/` gets silently dropped from the WebSocket handshake, breaking auth/session cookies scoped to a sub-path (e.g. `/signal-r/hubs/messages`). `getDefaultOrigin` is also used to build the `origin` header for the handshake, where stripping the path is correct per the WebSocket protocol, so it can't just be changed in place without affecting that header too. This adds a separate `getCookieLookupUri` that does the same `ws(s)://` to `http(s)://` scheme mapping but keeps the path, query, and fragment, and uses it only for the cookie lookup. It also drops the URI's userinfo from that lookup, since it plays no role in cookie matching and shouldn't be forwarded into the CookieManager call. ## Changelog: [ANDROID] [FIXED] - Fix WebSocket cookie lookup dropping path-scoped cookies Pull Request resolved: https://github.com/react/react-native/pull/58365 Test Plan: Added `WebSocketModuleTest`, exercising `getCookieLookupUri` through reflection and asserting the path, port, and query survive the ws/wss -> http/https conversion. Couldn't run it through the repo's own Gradle/Robolectric setup in this environment (`react-native-gradle-plugin` isn't resolvable without the full monorepo build), so I compiled the real companion object with `kotlinc` standalone and ran the same reflection lookup against it directly, confirming both cases pass and that the private companion method resolves without a `NoSuchMethodException`. Fixes https://github.com/react/react-native/issues/58358 Reviewed By: cipolleschi Differential Revision: D119093059 Pulled By: javache fbshipit-source-id: c5404937c97b497d3eae8c45433785f057602c29
This commit is contained in:
committed by
meta-codesync[bot]
parent
07a6e1fc41
commit
0eb9b300b6
+38
-9
@@ -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<String, List<String>>())
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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_keepsPortAndDropsQuery() {
|
||||
val uri = getCookieLookupUri("ws://my.domain:8080/path?token=abc")
|
||||
|
||||
assertThat(uri.toString()).isEqualTo("http://my.domain:8080/path")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user