fix(upstream): parse bare-LF HTTP proxy headers; document benign protect() failure
This commit is contained in:
parent
6b75c65503
commit
1b06c3c01f
2 changed files with 19 additions and 7 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue