diff --git a/app/src/main/java/chat/vojo/proxy/core/upstream/HttpUpstream.kt b/app/src/main/java/chat/vojo/proxy/core/upstream/HttpUpstream.kt index ed7681f..7a5bb83 100644 --- a/app/src/main/java/chat/vojo/proxy/core/upstream/HttpUpstream.kt +++ b/app/src/main/java/chat/vojo/proxy/core/upstream/HttpUpstream.kt @@ -67,17 +67,20 @@ class HttpUpstream( /** Reads the response head up to the blank line and returns the status code. */ private fun readStatusLine(inp: InputStream): Int { val head = StringBuilder() - var last3 = 0 // rolling match for "\r\n\r\n" + // End of head = two consecutive line terminators. Count consecutive LFs and + // ignore CRs, so CRLFCRLF, bare LFLF, and mixed CRLF/LF proxies all terminate + // (a CR-only build would otherwise spin to the 16 KB cap and fail every CONNECT). + var consecutiveLf = 0 while (true) { val c = inp.read() if (c < 0) throw IOException("eof during http proxy response") head.append(c.toChar()) - last3 = when { - c == '\r'.code -> if (last3 == 2) 3 else 1 - c == '\n'.code -> if (last3 == 1) 2 else if (last3 == 3) 4 else 0 - else -> 0 + when (c) { + '\n'.code -> consecutiveLf++ + '\r'.code -> { /* part of CRLF; don't reset the LF run */ } + else -> consecutiveLf = 0 } - if (last3 == 4) break // saw \r\n\r\n + if (consecutiveLf == 2) break if (head.length > 16 * 1024) throw IOException("http proxy response head too large") } val firstLine = head.lineSequence().firstOrNull()?.trim().orEmpty() diff --git a/app/src/main/java/chat/vojo/proxy/core/upstream/Upstream.kt b/app/src/main/java/chat/vojo/proxy/core/upstream/Upstream.kt index bbf31cd..2cf9804 100644 --- a/app/src/main/java/chat/vojo/proxy/core/upstream/Upstream.kt +++ b/app/src/main/java/chat/vojo/proxy/core/upstream/Upstream.kt @@ -65,7 +65,16 @@ interface Upstream { const val CONNECT_TIMEOUT_MS = 10_000 const val HANDSHAKE_TIMEOUT_MS = 15_000 -/** Creates an unconnected, VPN-protected TCP socket and connects it to the proxy server. */ +/** + * Creates a VPN-protected TCP socket and connects it to the proxy server. + * + * NOTE: protect()'s return value is intentionally ignored. The app's own sockets are + * already kept out of the tun by the VpnService app filter (blacklist disallows self; + * whitelist simply doesn't include us), so protect() is redundant belt-and-suspenders + * here — and on this platform it returns false for a freshly-created unconnected socket + * even though traffic is correctly excluded. Treating that false as fatal breaks every + * upstream connection (verified on-device), so we don't. + */ internal fun SocketProtector.connectedSocket(host: String, port: Int): Socket { val socket = Socket() protect(socket)