Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package okhttp3.internal.http1

import java.io.EOFException
import java.net.ProtocolException
import okhttp3.Headers
import okhttp3.internal.HEADER_LIMIT
import okio.BufferedSource
Expand All @@ -29,7 +31,23 @@ class HeadersReader(

/** Read a single line counted against the header size limit. */
fun readLine(): String {
val line = source.readUtf8LineStrict(headerLimit)
val line =
try {
source.readUtf8LineStrict(headerLimit)
} catch (e: EOFException) {
// readUtf8LineStrict() throws EOFException both when the line exceeds the byte limit
// and when the stream ends before a line terminator. Only the former means the response
// head is too large; a genuinely truncated response must still surface as an EOFException.
// A line of exactly headerLimit bytes is legal (okio accepts headerLimit bytes plus a
// terminator), so buffer.size == headerLimit is a truncated legal line, not an oversized
// one; only a strictly larger buffer proves the limit was exceeded.
if (source.buffer.size > headerLimit) {
throw ProtocolException(
"response headers exceed the ${HEADER_LIMIT / 1024} KiB limit",
).initCause(e)
}
throw e
}
headerLimit -= line.length.toLong()
return line
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (C) 2026 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.internal.http1

import assertk.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.isInstanceOf
import assertk.assertions.isNotNull
import java.io.EOFException
import java.net.ProtocolException
import kotlin.test.assertFailsWith
import okhttp3.internal.HEADER_LIMIT
import okio.Buffer
import org.junit.jupiter.api.Test

class HeadersReaderTest {
@Test
fun readsNormalHeaders() {
val source =
Buffer().writeUtf8(
"Content-Type: text/plain\r\n" +
"Content-Length: 0\r\n" +
"\r\n",
)
val headers = HeadersReader(source).readHeaders()
assertThat(headers.size).isEqualTo(2)
assertThat(headers["Content-Type"]).isEqualTo("text/plain")
assertThat(headers["Content-Length"]).isEqualTo("0")
}

/** A header line larger than the limit reports the limit clearly instead of a raw okio dump. */
@Test
fun headerLineExceedingLimitReportsLimit() {
val source = Buffer().writeUtf8("a".repeat((HEADER_LIMIT + 1).toInt()))
val exception =
assertFailsWith<ProtocolException> {
HeadersReader(source).readHeaders()
}
assertThat(exception.message).isEqualTo("response headers exceed the ${HEADER_LIMIT / 1024} KiB limit")
assertThat(exception.cause).isNotNull().isInstanceOf(EOFException::class.java)
}

/**
* A response that is truncated before the limit is genuine end-of-stream, not an oversized
* header, so it must still surface as an [EOFException] rather than the limit message.
*/
@Test
fun truncatedHeadersBeforeLimitStillThrowEof() {
val source = Buffer().writeUtf8("Content-Type: text/pl")
assertFailsWith<EOFException> {
HeadersReader(source).readHeaders()
}
}

/**
* A line of exactly [HEADER_LIMIT] bytes is a legal header line, so a stream that ends after that
* many bytes with no terminator is a truncated legal line, not an oversized header. It must still
* surface as an [EOFException]; the limit message is reserved for lines strictly over the limit.
*/
@Test
fun truncatedHeadersAtLimitStillThrowEof() {
val source = Buffer().writeUtf8("a".repeat(HEADER_LIMIT.toInt()))
assertFailsWith<EOFException> {
HeadersReader(source).readHeaders()
}
}
}
Loading