@@ -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