Skip to content

Commit 89470c1

Browse files
committed
rework code to scan for header name
Update HttpHeaderParser.scala Update HttpHeaderParser.scala
1 parent 3bb2985 commit 89470c1

File tree

1 file changed

+67
-27
lines changed

1 file changed

+67
-27
lines changed

http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpHeaderParser.scala

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -165,37 +165,77 @@ private[engine] final class HttpHeaderParser private (
165165
}
166166

167167
private def parseRawHeader(input: ByteString, lineStart: Int, cursor: Int, nodeIx: Int): Int = {
168-
val colonIx = scanHeaderNameAndReturnIndexOfColon(input, lineStart, lineStart + 1 + maxHeaderNameLength)(cursor)
169-
val headerName = asciiString(input, lineStart, colonIx)
170-
try {
171-
val valueParser = new RawHeaderValueParser(headerName, maxHeaderValueLength,
172-
headerValueCacheLimit(headerName), log, illegalResponseHeaderValueProcessingMode)
173-
insert(input, valueParser)(cursor, colonIx + 1, nodeIx, colonIx)
174-
parseHeaderLine(input, lineStart)(cursor, nodeIx)
175-
} catch {
176-
case OutOfTrieSpaceException => // if we cannot insert we drop back to simply creating new header instances
177-
val (headerValue, endIx) = scanHeaderValue(this, input, colonIx + 1, colonIx + maxHeaderValueLength + 3,
178-
log, settings.illegalResponseHeaderValueProcessingMode)()
179-
resultHeader = RawHeader(headerName, headerValue.trim)
180-
endIx
168+
val colonIx = input.indexOf(':', cursor, lineStart + 1 + maxHeaderNameLength)
169+
if (colonIx == -1) {
170+
scanIllegalHeaderNameCharacters(input, cursor, lineStart + 1 + maxHeaderNameLength)
171+
fail(s"HTTP header name exceeds the configured limit of $maxHeaderNameLength characters",
172+
StatusCodes.RequestHeaderFieldsTooLarge)
173+
} else {
174+
val headerName = scanAsciiString(input, lineStart, colonIx)
175+
try {
176+
val valueParser = new RawHeaderValueParser(headerName, maxHeaderValueLength,
177+
headerValueCacheLimit(headerName), log, illegalResponseHeaderValueProcessingMode)
178+
insert(input, valueParser)(cursor, colonIx + 1, nodeIx, colonIx)
179+
parseHeaderLine(input, lineStart)(cursor, nodeIx)
180+
} catch {
181+
case OutOfTrieSpaceException => // if we cannot insert we drop back to simply creating new header instances
182+
val (headerValue, endIx) = scanHeaderValue(this, input, colonIx + 1, colonIx + maxHeaderValueLength + 3,
183+
log, settings.illegalResponseHeaderValueProcessingMode)()
184+
resultHeader = RawHeader(headerName, headerValue.trim)
185+
endIx
186+
}
181187
}
182188
}
183189

184-
@tailrec private def scanHeaderNameAndReturnIndexOfColon(input: ByteString, start: Int, limit: Int)(ix: Int): Int =
185-
if (ix < limit)
186-
(byteChar(input, ix), settings.illegalResponseHeaderNameProcessingMode) match {
187-
case (':', _) => ix
188-
case (c, _) if tchar(c) => scanHeaderNameAndReturnIndexOfColon(input, start, limit)(ix + 1)
189-
case (c, IllegalResponseHeaderNameProcessingMode.Error) =>
190-
fail(s"Illegal character '${escape(c)}' in header name")
191-
case (c, IllegalResponseHeaderNameProcessingMode.Warn) =>
192-
log.warning(s"Header key contains illegal character '${escape(c)}'")
193-
scanHeaderNameAndReturnIndexOfColon(input, start, limit)(ix + 1)
194-
case (c, IllegalResponseHeaderNameProcessingMode.Ignore) =>
195-
scanHeaderNameAndReturnIndexOfColon(input, start, limit)(ix + 1)
190+
// similar to asciiString function but it checks for illegal characters
191+
private def scanAsciiString(input: ByteString, start: Int, end: Int): String = {
192+
@tailrec def build(ix: Int = start, sb: JStringBuilder = new JStringBuilder(end - start)): String =
193+
if (ix == end) {
194+
sb.toString
195+
} else {
196+
val c = byteChar(input, ix)
197+
if (tchar(c)) {
198+
build(ix + 1, sb.append(c))
199+
} else {
200+
settings.illegalResponseHeaderNameProcessingMode match {
201+
case IllegalResponseHeaderNameProcessingMode.Error =>
202+
fail(s"Illegal character '${escape(c)}' in header name")
203+
case IllegalResponseHeaderNameProcessingMode.Warn =>
204+
log.warning(s"Header key contains illegal character '${escape(c)}'")
205+
build(ix + 1, sb.append(c))
206+
case IllegalResponseHeaderNameProcessingMode.Ignore =>
207+
build(ix + 1, sb.append(c))
208+
}
209+
}
196210
}
197-
else fail(s"HTTP header name exceeds the configured limit of ${limit - start - 1} characters",
198-
StatusCodes.RequestHeaderFieldsTooLarge)
211+
if (start == end) "" else build()
212+
}
213+
214+
// similar to scanAsciiString but only scans for illegal characters and fails or warns if it finds one
215+
private def scanIllegalHeaderNameCharacters(input: ByteString, start: Int, end: Int): Unit = {
216+
@tailrec def check(ix: Int = start): Unit =
217+
if (ix == end) {
218+
()
219+
} else {
220+
val c = byteChar(input, ix)
221+
if (tchar(c)) {
222+
check(ix + 1)
223+
} else {
224+
settings.illegalResponseHeaderNameProcessingMode match {
225+
case IllegalResponseHeaderNameProcessingMode.Error =>
226+
fail(s"Illegal character '${escape(c)}' in header name")
227+
case IllegalResponseHeaderNameProcessingMode.Warn =>
228+
log.warning(s"Header key contains illegal character '${escape(c)}'")
229+
check(ix + 1)
230+
case IllegalResponseHeaderNameProcessingMode.Ignore =>
231+
()
232+
}
233+
}
234+
}
235+
if (start == end || settings.illegalResponseHeaderNameProcessingMode == IllegalResponseHeaderNameProcessingMode.Ignore)
236+
()
237+
else check()
238+
}
199239

200240
@tailrec
201241
private def parseHeaderValue(input: ByteString, valueStart: Int, branch: ValueBranch)(cursor: Int = valueStart,

0 commit comments

Comments
 (0)