This commit is contained in:
parent
1d0837856a
commit
cf46ab887f
16 changed files with 185 additions and 56 deletions
|
|
@ -352,6 +352,15 @@ akka.http {
|
|||
# `full` : the full error details (potentially spanning several lines) are logged
|
||||
error-logging-verbosity = full
|
||||
|
||||
# Configures the processing mode when encountering illegal characters in
|
||||
# header value of response.
|
||||
#
|
||||
# Supported mode:
|
||||
# `error` : default mode, throw an ParsingException and terminate the processing
|
||||
# `warn` : ignore the illegal characters in response header value and log a warning message
|
||||
# `ignore` : just ignore the illegal characters in response header value
|
||||
illegal-response-header-value-processing-mode = error
|
||||
|
||||
# limits for the number of different values per header type that the
|
||||
# header cache will hold
|
||||
header-cache {
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ private[http] object OutgoingConnectionBlueprint {
|
|||
val responseParsingMerge = b.add {
|
||||
// the initial header parser we initially use for every connection,
|
||||
// will not be mutated, all "shared copy" parsers copy on first-write into the header cache
|
||||
val rootParser = new HttpResponseParser(parserSettings, HttpHeaderParser(parserSettings) { info ⇒
|
||||
val rootParser = new HttpResponseParser(parserSettings, HttpHeaderParser(parserSettings, log) { info ⇒
|
||||
if (parserSettings.illegalHeaderWarnings)
|
||||
logParsingError(info withSummaryPrepended "Illegal response header", log, parserSettings.errorLoggingVerbosity)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ private[http] final class BodyPartParser(
|
|||
private[this] val boyerMoore = new BoyerMoore(needle)
|
||||
|
||||
// TODO: prevent re-priming header parser from scratch
|
||||
private[this] val headerParser = HttpHeaderParser(settings) { errorInfo ⇒
|
||||
private[this] val headerParser = HttpHeaderParser(settings, log) { errorInfo ⇒
|
||||
if (illegalHeaderWarnings) log.warning(errorInfo.withSummaryPrepended("Illegal multipart header").formatPretty)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ package akka.http.impl.engine.parsing
|
|||
import java.nio.{ CharBuffer, ByteBuffer }
|
||||
import java.util.Arrays.copyOf
|
||||
import java.lang.{ StringBuilder ⇒ JStringBuilder }
|
||||
import akka.event.LoggingAdapter
|
||||
import akka.http.scaladsl.settings.ParserSettings.IllegalResponseHeaderValueProcessingMode
|
||||
import akka.http.scaladsl.settings.ParserSettings
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import akka.parboiled2.CharUtils
|
||||
import akka.util.ByteString
|
||||
|
|
@ -60,6 +64,7 @@ import akka.http.impl.model.parser.CharacterClasses._
|
|||
*/
|
||||
private[engine] final class HttpHeaderParser private (
|
||||
val settings: HttpHeaderParser.Settings,
|
||||
val log: LoggingAdapter,
|
||||
onIllegalHeader: ErrorInfo ⇒ Unit,
|
||||
private[this] var nodes: Array[Char] = new Array(512), // initial size, can grow as needed
|
||||
private[this] var nodeCount: Int = 0,
|
||||
|
|
@ -85,7 +90,7 @@ private[engine] final class HttpHeaderParser private (
|
|||
* Returns a copy of this parser that shares the trie data with this instance.
|
||||
*/
|
||||
def createShallowCopy(): HttpHeaderParser =
|
||||
new HttpHeaderParser(settings, onIllegalHeader, nodes, nodeCount, branchData, branchDataCount, values, valueCount)
|
||||
new HttpHeaderParser(settings, log, onIllegalHeader, nodes, nodeCount, branchData, branchDataCount, values, valueCount)
|
||||
|
||||
/**
|
||||
* Parses a header line and returns the line start index of the subsequent line.
|
||||
|
|
@ -145,12 +150,14 @@ private[engine] final class HttpHeaderParser private (
|
|||
val colonIx = scanHeaderNameAndReturnIndexOfColon(input, lineStart, lineStart + 1 + maxHeaderNameLength)(cursor)
|
||||
val headerName = asciiString(input, lineStart, colonIx)
|
||||
try {
|
||||
val valueParser = new RawHeaderValueParser(headerName, maxHeaderValueLength, headerValueCacheLimit(headerName))
|
||||
val valueParser = new RawHeaderValueParser(headerName, maxHeaderValueLength,
|
||||
headerValueCacheLimit(headerName), log, illegalResponseHeaderValueProcessingMode)
|
||||
insert(input, valueParser)(cursor, colonIx + 1, nodeIx, colonIx)
|
||||
parseHeaderLine(input, lineStart)(cursor, nodeIx)
|
||||
} catch {
|
||||
case OutOfTrieSpaceException ⇒ // if we cannot insert we drop back to simply creating new header instances
|
||||
val (headerValue, endIx) = scanHeaderValue(this, input, colonIx + 1, colonIx + maxHeaderValueLength + 3)()
|
||||
val (headerValue, endIx) = scanHeaderValue(this, input, colonIx + 1, colonIx + maxHeaderValueLength + 3,
|
||||
log, settings.illegalResponseHeaderValueProcessingMode)()
|
||||
resultHeader = RawHeader(headerName, headerValue.trim)
|
||||
endIx
|
||||
}
|
||||
|
|
@ -413,6 +420,7 @@ private[http] object HttpHeaderParser {
|
|||
def maxHeaderValueLength: Int
|
||||
def headerValueCacheLimit(headerName: String): Int
|
||||
def customMediaTypes: MediaTypes.FindCustom
|
||||
def illegalResponseHeaderValueProcessingMode: IllegalResponseHeaderValueProcessingMode
|
||||
}
|
||||
|
||||
private def predefinedHeaders = Seq(
|
||||
|
|
@ -426,16 +434,16 @@ private[http] object HttpHeaderParser {
|
|||
"Cache-Control: no-cache",
|
||||
"Expect: 100-continue")
|
||||
|
||||
def apply(settings: HttpHeaderParser.Settings)(onIllegalHeader: ErrorInfo ⇒ Unit = info ⇒ throw IllegalHeaderException(info)) =
|
||||
prime(unprimed(settings, onIllegalHeader))
|
||||
def apply(settings: HttpHeaderParser.Settings, log: LoggingAdapter)(onIllegalHeader: ErrorInfo ⇒ Unit = info ⇒ throw IllegalHeaderException(info)) =
|
||||
prime(unprimed(settings, log, onIllegalHeader))
|
||||
|
||||
def unprimed(settings: HttpHeaderParser.Settings, warnOnIllegalHeader: ErrorInfo ⇒ Unit) =
|
||||
new HttpHeaderParser(settings, warnOnIllegalHeader)
|
||||
def unprimed(settings: HttpHeaderParser.Settings, log: LoggingAdapter, warnOnIllegalHeader: ErrorInfo ⇒ Unit) =
|
||||
new HttpHeaderParser(settings, log, warnOnIllegalHeader)
|
||||
|
||||
def prime(parser: HttpHeaderParser): HttpHeaderParser = {
|
||||
val valueParsers: Seq[HeaderValueParser] =
|
||||
HeaderParser.ruleNames.map { name ⇒
|
||||
new ModeledHeaderValueParser(name, parser.settings.maxHeaderValueLength, parser.settings.headerValueCacheLimit(name), parser.settings)
|
||||
new ModeledHeaderValueParser(name, parser.settings.maxHeaderValueLength, parser.settings.headerValueCacheLimit(name), parser.log, parser.settings)
|
||||
}(collection.breakOut)
|
||||
def insertInGoodOrder(items: Seq[Any])(startIx: Int = 0, endIx: Int = items.size): Unit =
|
||||
if (endIx - startIx > 0) {
|
||||
|
|
@ -470,11 +478,11 @@ private[http] object HttpHeaderParser {
|
|||
def cachingEnabled = maxValueCount > 0
|
||||
}
|
||||
|
||||
private[parsing] class ModeledHeaderValueParser(headerName: String, maxHeaderValueLength: Int, maxValueCount: Int, settings: HeaderParser.Settings)
|
||||
private[parsing] class ModeledHeaderValueParser(headerName: String, maxHeaderValueLength: Int, maxValueCount: Int, log: LoggingAdapter, settings: HeaderParser.Settings)
|
||||
extends HeaderValueParser(headerName, maxValueCount) {
|
||||
def apply(hhp: HttpHeaderParser, input: ByteString, valueStart: Int, onIllegalHeader: ErrorInfo ⇒ Unit): (HttpHeader, Int) = {
|
||||
// TODO: optimize by running the header value parser directly on the input ByteString (rather than an extracted String); seems done?
|
||||
val (headerValue, endIx) = scanHeaderValue(hhp, input, valueStart, valueStart + maxHeaderValueLength + 2)()
|
||||
val (headerValue, endIx) = scanHeaderValue(hhp, input, valueStart, valueStart + maxHeaderValueLength + 2, log, settings.illegalResponseHeaderValueProcessingMode)()
|
||||
val trimmedHeaderValue = headerValue.trim
|
||||
val header = HeaderParser.parseFull(headerName, trimmedHeaderValue, settings) match {
|
||||
case Right(h) ⇒ h
|
||||
|
|
@ -486,10 +494,10 @@ private[http] object HttpHeaderParser {
|
|||
}
|
||||
}
|
||||
|
||||
private[parsing] class RawHeaderValueParser(headerName: String, maxHeaderValueLength: Int, maxValueCount: Int)
|
||||
extends HeaderValueParser(headerName, maxValueCount) {
|
||||
private[parsing] class RawHeaderValueParser(headerName: String, maxHeaderValueLength: Int, maxValueCount: Int,
|
||||
log: LoggingAdapter, mode: IllegalResponseHeaderValueProcessingMode) extends HeaderValueParser(headerName, maxValueCount) {
|
||||
def apply(hhp: HttpHeaderParser, input: ByteString, valueStart: Int, onIllegalHeader: ErrorInfo ⇒ Unit): (HttpHeader, Int) = {
|
||||
val (headerValue, endIx) = scanHeaderValue(hhp, input, valueStart, valueStart + maxHeaderValueLength + 2)()
|
||||
val (headerValue, endIx) = scanHeaderValue(hhp, input, valueStart, valueStart + maxHeaderValueLength + 2, log, mode)()
|
||||
RawHeader(headerName, headerValue.trim) → endIx
|
||||
}
|
||||
}
|
||||
|
|
@ -503,15 +511,16 @@ private[http] object HttpHeaderParser {
|
|||
}
|
||||
else fail(s"HTTP header name exceeds the configured limit of ${limit - start - 1} characters")
|
||||
|
||||
@tailrec private def scanHeaderValue(hhp: HttpHeaderParser, input: ByteString, start: Int,
|
||||
limit: Int)(sb: JStringBuilder = null, ix: Int = start): (String, Int) = {
|
||||
@tailrec private def scanHeaderValue(hhp: HttpHeaderParser, input: ByteString, start: Int, limit: Int, log: LoggingAdapter,
|
||||
mode: IllegalResponseHeaderValueProcessingMode)(sb: JStringBuilder = null, ix: Int = start): (String, Int) = {
|
||||
|
||||
def appended(c: Char) = (if (sb != null) sb else new JStringBuilder(asciiString(input, start, ix))).append(c)
|
||||
def appended2(c: Int) = if ((c >> 16) != 0) appended(c.toChar).append((c >> 16).toChar) else appended(c.toChar)
|
||||
if (ix < limit)
|
||||
byteChar(input, ix) match {
|
||||
case '\t' ⇒ scanHeaderValue(hhp, input, start, limit)(appended(' '), ix + 1)
|
||||
case '\t' ⇒ scanHeaderValue(hhp, input, start, limit, log, mode)(appended(' '), ix + 1)
|
||||
case '\r' if byteChar(input, ix + 1) == '\n' ⇒
|
||||
if (WSP(byteChar(input, ix + 2))) scanHeaderValue(hhp, input, start, limit)(appended(' '), ix + 3)
|
||||
if (WSP(byteChar(input, ix + 2))) scanHeaderValue(hhp, input, start, limit, log, mode)(appended(' '), ix + 3)
|
||||
else (if (sb != null) sb.toString else asciiString(input, start, ix), ix + 2)
|
||||
case c ⇒
|
||||
var nix = ix + 1
|
||||
|
|
@ -544,8 +553,21 @@ private[http] object HttpHeaderParser {
|
|||
case -1 ⇒ if (sb != null) sb.append(c).append(byteChar(input, ix + 1)).append(byteChar(input, ix + 2)).append(byteChar(input, ix + 3)) else null
|
||||
case cc ⇒ appended2(cc)
|
||||
}
|
||||
} else fail(s"Illegal character '${escape(c)}' in header value")
|
||||
scanHeaderValue(hhp, input, start, limit)(nsb, nix)
|
||||
} else {
|
||||
mode match {
|
||||
case ParserSettings.IllegalResponseHeaderValueProcessingMode.Error ⇒
|
||||
fail(s"Illegal character '${escape(c)}' in header value")
|
||||
case ParserSettings.IllegalResponseHeaderValueProcessingMode.Warn ⇒
|
||||
// ignore the illegal character and log a warning message
|
||||
log.warning(s"Illegal character '${escape(c)}' in header value")
|
||||
sb
|
||||
case ParserSettings.IllegalResponseHeaderValueProcessingMode.Ignore ⇒
|
||||
// just ignore the illegal character
|
||||
sb
|
||||
}
|
||||
|
||||
}
|
||||
scanHeaderValue(hhp, input, start, limit, log, mode)(nsb, nix)
|
||||
}
|
||||
else fail(s"HTTP header value exceeds the configured limit of ${limit - start - 2} characters")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ private[http] object HttpServerBluePrint {
|
|||
// the initial header parser we initially use for every connection,
|
||||
// will not be mutated, all "shared copy" parsers copy on first-write into the header cache
|
||||
val rootParser = new HttpRequestParser(parserSettings, rawRequestUriHeader,
|
||||
HttpHeaderParser(parserSettings) { info ⇒
|
||||
HttpHeaderParser(parserSettings, log) { info ⇒
|
||||
if (parserSettings.illegalHeaderWarnings)
|
||||
logParsingError(info withSummaryPrepended "Illegal request header", log, parserSettings.errorLoggingVerbosity)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ object WebSocketClientBlueprint {
|
|||
new GraphStageLogic(shape) with InHandler with OutHandler {
|
||||
// a special version of the parser which only parses one message and then reports the remaining data
|
||||
// if some is available
|
||||
val parser = new HttpResponseParser(settings.parserSettings, HttpHeaderParser(settings.parserSettings)()) {
|
||||
val parser = new HttpResponseParser(settings.parserSettings, HttpHeaderParser(settings.parserSettings, log)()) {
|
||||
var first = true
|
||||
override def handleInformationalResponses = false
|
||||
override protected def parseMessage(input: ByteString, offset: Int): StateResult = {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ package akka.http.impl.model.parser
|
|||
|
||||
import akka.http.scaladsl.settings.ParserSettings
|
||||
import akka.http.scaladsl.settings.ParserSettings.CookieParsingMode
|
||||
import akka.http.scaladsl.settings.ParserSettings.IllegalResponseHeaderValueProcessingMode
|
||||
import akka.http.scaladsl.model.headers.HttpCookiePair
|
||||
import akka.stream.impl.ConstantFun
|
||||
import scala.util.control.NonFatal
|
||||
|
|
@ -169,20 +170,26 @@ private[http] object HeaderParser {
|
|||
def uriParsingMode: Uri.ParsingMode
|
||||
def cookieParsingMode: ParserSettings.CookieParsingMode
|
||||
def customMediaTypes: MediaTypes.FindCustom
|
||||
def illegalResponseHeaderValueProcessingMode: IllegalResponseHeaderValueProcessingMode
|
||||
}
|
||||
def Settings(
|
||||
uriParsingMode: Uri.ParsingMode = Uri.ParsingMode.Relaxed,
|
||||
cookieParsingMode: ParserSettings.CookieParsingMode = ParserSettings.CookieParsingMode.RFC6265,
|
||||
customMediaTypes: MediaTypes.FindCustom = ConstantFun.scalaAnyTwoToNone): Settings = {
|
||||
uriParsingMode: Uri.ParsingMode = Uri.ParsingMode.Relaxed,
|
||||
cookieParsingMode: ParserSettings.CookieParsingMode = ParserSettings.CookieParsingMode.RFC6265,
|
||||
customMediaTypes: MediaTypes.FindCustom = ConstantFun.scalaAnyTwoToNone,
|
||||
mode: IllegalResponseHeaderValueProcessingMode = ParserSettings.IllegalResponseHeaderValueProcessingMode.Error): Settings = {
|
||||
|
||||
val _uriParsingMode = uriParsingMode
|
||||
val _cookieParsingMode = cookieParsingMode
|
||||
val _customMediaTypes = customMediaTypes
|
||||
val _illegalResponseHeaderValueProcessingMode = mode
|
||||
|
||||
new Settings {
|
||||
def uriParsingMode: Uri.ParsingMode = _uriParsingMode
|
||||
def cookieParsingMode: CookieParsingMode = _cookieParsingMode
|
||||
def customMediaTypes: MediaTypes.FindCustom = _customMediaTypes
|
||||
def illegalResponseHeaderValueProcessingMode: IllegalResponseHeaderValueProcessingMode =
|
||||
_illegalResponseHeaderValueProcessingMode
|
||||
}
|
||||
}
|
||||
val DefaultSettings: Settings = Settings()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
package akka.http.impl.settings
|
||||
|
||||
import akka.http.scaladsl.settings.ParserSettings
|
||||
import akka.http.scaladsl.settings.ParserSettings.{ ErrorLoggingVerbosity, CookieParsingMode }
|
||||
import akka.http.scaladsl.settings.ParserSettings.{ IllegalResponseHeaderValueProcessingMode, ErrorLoggingVerbosity, CookieParsingMode }
|
||||
import akka.stream.impl.ConstantFun
|
||||
import com.typesafe.config.Config
|
||||
import scala.collection.JavaConverters._
|
||||
|
|
@ -14,24 +14,25 @@ import akka.http.impl.util._
|
|||
|
||||
/** INTERNAL API */
|
||||
private[akka] final case class ParserSettingsImpl(
|
||||
maxUriLength: Int,
|
||||
maxMethodLength: Int,
|
||||
maxResponseReasonLength: Int,
|
||||
maxHeaderNameLength: Int,
|
||||
maxHeaderValueLength: Int,
|
||||
maxHeaderCount: Int,
|
||||
maxContentLength: Long,
|
||||
maxChunkExtLength: Int,
|
||||
maxChunkSize: Int,
|
||||
uriParsingMode: Uri.ParsingMode,
|
||||
cookieParsingMode: CookieParsingMode,
|
||||
illegalHeaderWarnings: Boolean,
|
||||
errorLoggingVerbosity: ParserSettings.ErrorLoggingVerbosity,
|
||||
headerValueCacheLimits: Map[String, Int],
|
||||
includeTlsSessionInfoHeader: Boolean,
|
||||
customMethods: String ⇒ Option[HttpMethod],
|
||||
customStatusCodes: Int ⇒ Option[StatusCode],
|
||||
customMediaTypes: MediaTypes.FindCustom)
|
||||
maxUriLength: Int,
|
||||
maxMethodLength: Int,
|
||||
maxResponseReasonLength: Int,
|
||||
maxHeaderNameLength: Int,
|
||||
maxHeaderValueLength: Int,
|
||||
maxHeaderCount: Int,
|
||||
maxContentLength: Long,
|
||||
maxChunkExtLength: Int,
|
||||
maxChunkSize: Int,
|
||||
uriParsingMode: Uri.ParsingMode,
|
||||
cookieParsingMode: CookieParsingMode,
|
||||
illegalHeaderWarnings: Boolean,
|
||||
errorLoggingVerbosity: ErrorLoggingVerbosity,
|
||||
illegalResponseHeaderValueProcessingMode: IllegalResponseHeaderValueProcessingMode,
|
||||
headerValueCacheLimits: Map[String, Int],
|
||||
includeTlsSessionInfoHeader: Boolean,
|
||||
customMethods: String ⇒ Option[HttpMethod],
|
||||
customStatusCodes: Int ⇒ Option[StatusCode],
|
||||
customMediaTypes: MediaTypes.FindCustom)
|
||||
extends akka.http.scaladsl.settings.ParserSettings {
|
||||
|
||||
require(maxUriLength > 0, "max-uri-length must be > 0")
|
||||
|
|
@ -76,6 +77,7 @@ object ParserSettingsImpl extends SettingsCompanion[ParserSettingsImpl]("akka.ht
|
|||
CookieParsingMode(c getString "cookie-parsing-mode"),
|
||||
c getBoolean "illegal-header-warnings",
|
||||
ErrorLoggingVerbosity(c getString "error-logging-verbosity"),
|
||||
IllegalResponseHeaderValueProcessingMode(c getString "illegal-response-header-value-processing-mode"),
|
||||
cacheConfig.entrySet.asScala.map(kvp ⇒ kvp.getKey → cacheConfig.getInt(kvp.getKey))(collection.breakOut),
|
||||
c getBoolean "tls-session-info-header",
|
||||
noCustomMethods,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ abstract class ParserSettings private[akka] () extends BodyPartParser.Settings {
|
|||
def getCookieParsingMode: ParserSettings.CookieParsingMode
|
||||
def getIllegalHeaderWarnings: Boolean
|
||||
def getErrorLoggingVerbosity: ParserSettings.ErrorLoggingVerbosity
|
||||
def getIllegalResponseHeaderValueProcessingMode: ParserSettings.IllegalResponseHeaderValueProcessingMode
|
||||
def getHeaderValueCacheLimits: ju.Map[String, Int]
|
||||
def getIncludeTlsSessionInfoHeader: Boolean
|
||||
def headerValueCacheLimits: Map[String, Int]
|
||||
|
|
@ -81,6 +82,7 @@ abstract class ParserSettings private[akka] () extends BodyPartParser.Settings {
|
|||
object ParserSettings extends SettingsCompanion[ParserSettings] {
|
||||
trait CookieParsingMode
|
||||
trait ErrorLoggingVerbosity
|
||||
trait IllegalResponseHeaderValueProcessingMode
|
||||
|
||||
override def create(config: Config): ParserSettings = ParserSettingsImpl(config)
|
||||
override def create(configOverrides: String): ParserSettings = ParserSettingsImpl(configOverrides)
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ abstract class ParserSettings private[akka] () extends akka.http.javadsl.setting
|
|||
def cookieParsingMode: ParserSettings.CookieParsingMode
|
||||
def illegalHeaderWarnings: Boolean
|
||||
def errorLoggingVerbosity: ParserSettings.ErrorLoggingVerbosity
|
||||
def illegalResponseHeaderValueProcessingMode: ParserSettings.IllegalResponseHeaderValueProcessingMode
|
||||
def headerValueCacheLimits: Map[String, Int]
|
||||
def includeTlsSessionInfoHeader: Boolean
|
||||
def customMethods: String ⇒ Option[HttpMethod]
|
||||
|
|
@ -56,6 +57,7 @@ abstract class ParserSettings private[akka] () extends akka.http.javadsl.setting
|
|||
override def getMaxUriLength = maxUriLength
|
||||
override def getMaxMethodLength = maxMethodLength
|
||||
override def getErrorLoggingVerbosity: js.ParserSettings.ErrorLoggingVerbosity = errorLoggingVerbosity
|
||||
override def getIllegalResponseHeaderValueProcessingMode = illegalResponseHeaderValueProcessingMode
|
||||
|
||||
override def getCustomMethods = new Function[String, Optional[akka.http.javadsl.model.HttpMethod]] {
|
||||
override def apply(t: String) = OptionConverters.toJava(customMethods(t))
|
||||
|
|
@ -100,10 +102,12 @@ abstract class ParserSettings private[akka] () extends akka.http.javadsl.setting
|
|||
val map = types.map(c ⇒ (c.mainType, c.subType) → c).toMap
|
||||
self.copy(customMediaTypes = (main, sub) ⇒ map.get((main, sub)))
|
||||
}
|
||||
def withIllegalResponseHeaderValueProcessingMode(newValue: ParserSettings.IllegalResponseHeaderValueProcessingMode): ParserSettings =
|
||||
self.copy(illegalResponseHeaderValueProcessingMode = newValue)
|
||||
}
|
||||
|
||||
object ParserSettings extends SettingsCompanion[ParserSettings] {
|
||||
trait CookieParsingMode extends akka.http.javadsl.settings.ParserSettings.CookieParsingMode
|
||||
sealed trait CookieParsingMode extends akka.http.javadsl.settings.ParserSettings.CookieParsingMode
|
||||
object CookieParsingMode {
|
||||
case object RFC6265 extends CookieParsingMode
|
||||
case object Raw extends CookieParsingMode
|
||||
|
|
@ -114,7 +118,7 @@ object ParserSettings extends SettingsCompanion[ParserSettings] {
|
|||
}
|
||||
}
|
||||
|
||||
trait ErrorLoggingVerbosity extends akka.http.javadsl.settings.ParserSettings.ErrorLoggingVerbosity
|
||||
sealed trait ErrorLoggingVerbosity extends akka.http.javadsl.settings.ParserSettings.ErrorLoggingVerbosity
|
||||
object ErrorLoggingVerbosity {
|
||||
case object Off extends ErrorLoggingVerbosity
|
||||
case object Simple extends ErrorLoggingVerbosity
|
||||
|
|
@ -129,6 +133,21 @@ object ParserSettings extends SettingsCompanion[ParserSettings] {
|
|||
}
|
||||
}
|
||||
|
||||
sealed trait IllegalResponseHeaderValueProcessingMode extends akka.http.javadsl.settings.ParserSettings.IllegalResponseHeaderValueProcessingMode
|
||||
object IllegalResponseHeaderValueProcessingMode {
|
||||
case object Error extends IllegalResponseHeaderValueProcessingMode
|
||||
case object Warn extends IllegalResponseHeaderValueProcessingMode
|
||||
case object Ignore extends IllegalResponseHeaderValueProcessingMode
|
||||
|
||||
def apply(string: String): IllegalResponseHeaderValueProcessingMode =
|
||||
string.toRootLowerCase match {
|
||||
case "error" ⇒ Error
|
||||
case "warn" ⇒ Warn
|
||||
case "ignore" ⇒ Ignore
|
||||
case x ⇒ throw new IllegalArgumentException(s"[$x] is not a legal `illegal-response-header-value-processing-mode` setting")
|
||||
}
|
||||
}
|
||||
|
||||
override def apply(config: Config): ParserSettings = ParserSettingsImpl(config)
|
||||
override def apply(configOverrides: String): ParserSettings = ParserSettingsImpl(configOverrides)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
package akka.http.impl.engine.client
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.reflect.ClassTag
|
||||
import org.scalatest.Inside
|
||||
|
|
@ -326,6 +328,65 @@ class LowLevelOutgoingConnectionSpec extends AkkaSpec("akka.loggers = []\n akka.
|
|||
}
|
||||
}
|
||||
|
||||
"process the illegal response header value properly" which {
|
||||
|
||||
val illegalChar = '\u0001'
|
||||
val escapeChar = "\\u%04x" format illegalChar.toInt
|
||||
|
||||
"catch illegal response header value by default" in new TestSetup {
|
||||
sendStandardRequest()
|
||||
sendWireData(
|
||||
s"""HTTP/1.1 200 OK
|
||||
|Some-Header: value1$illegalChar
|
||||
|Other-Header: value2
|
||||
|
|
||||
|""")
|
||||
|
||||
responsesSub.request(1)
|
||||
val error @ IllegalResponseException(info) = responses.expectError()
|
||||
info.summary shouldEqual s"""Illegal character '$escapeChar' in header value"""
|
||||
netOut.expectError(error)
|
||||
requestsSub.expectCancellation()
|
||||
netInSub.expectCancellation()
|
||||
}
|
||||
|
||||
val ignoreConfig =
|
||||
"""
|
||||
akka.http.parsing.illegal-response-header-value-processing-mode = ignore
|
||||
"""
|
||||
"ignore illegal response header value if setting the config to ignore" in new TestSetup(config = ignoreConfig) {
|
||||
sendStandardRequest()
|
||||
sendWireData(
|
||||
s"""HTTP/1.1 200 OK
|
||||
|Some-Header: value1$illegalChar
|
||||
|Other-Header: value2
|
||||
|
|
||||
|""")
|
||||
|
||||
val HttpResponse(_, headers, _, _) = expectResponse()
|
||||
val headerStr = headers.map(h ⇒ s"${h.name}: ${h.value}").mkString(",")
|
||||
headerStr shouldEqual "Some-Header: value1,Other-Header: value2"
|
||||
}
|
||||
|
||||
val warnConfig =
|
||||
"""
|
||||
akka.http.parsing.illegal-response-header-value-processing-mode = warn
|
||||
"""
|
||||
"ignore illegal response header value and log a warning message if setting the config to warn" in new TestSetup(config = warnConfig) {
|
||||
sendStandardRequest()
|
||||
sendWireData(
|
||||
s"""HTTP/1.1 200 OK
|
||||
|Some-Header: value1$illegalChar
|
||||
|Other-Header: value2
|
||||
|
|
||||
|""")
|
||||
|
||||
val HttpResponse(_, headers, _, _) = expectResponse()
|
||||
val headerStr = headers.map(h ⇒ s"${h.name}: ${h.value}").mkString(",")
|
||||
headerStr shouldEqual "Some-Header: value1,Other-Header: value2"
|
||||
}
|
||||
}
|
||||
|
||||
"produce proper errors" which {
|
||||
|
||||
"catch the request entity stream being shorter than the Content-Length" in new TestSetup {
|
||||
|
|
@ -808,13 +869,14 @@ class LowLevelOutgoingConnectionSpec extends AkkaSpec("akka.loggers = []\n akka.
|
|||
}
|
||||
}
|
||||
|
||||
class TestSetup(maxResponseContentLength: Int = -1) {
|
||||
class TestSetup(maxResponseContentLength: Int = -1, config: String = "") {
|
||||
val requests = TestPublisher.manualProbe[HttpRequest]()
|
||||
val responses = TestSubscriber.manualProbe[HttpResponse]()
|
||||
|
||||
def settings = {
|
||||
val s = ClientConnectionSettings(system)
|
||||
.withUserAgentHeader(Some(`User-Agent`(List(ProductVersion("akka-http", "test")))))
|
||||
val s = ClientConnectionSettings(
|
||||
ConfigFactory.parseString(config).withFallback(system.settings.config)
|
||||
).withUserAgentHeader(Some(`User-Agent`(List(ProductVersion("akka-http", "test")))))
|
||||
if (maxResponseContentLength < 0) s
|
||||
else s.withParserSettings(s.parserSettings.withMaxContentLength(maxResponseContentLength))
|
||||
}
|
||||
|
|
@ -873,5 +935,6 @@ class LowLevelOutgoingConnectionSpec extends AkkaSpec("akka.loggers = []\n akka.
|
|||
responsesSub.request(1)
|
||||
responses.expectNext()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -246,6 +246,7 @@ class HttpHeaderParserSpec extends WordSpec with Matchers with BeforeAndAfterAll
|
|||
val parser = {
|
||||
val p = HttpHeaderParser.unprimed(
|
||||
settings = ParserSettings(system),
|
||||
system.log,
|
||||
warnOnIllegalHeader = info ⇒ system.log.warning(info.formatPretty))
|
||||
if (primed) HttpHeaderParser.prime(p) else p
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ object HttpHeaderParserTestBed extends App {
|
|||
val system = ActorSystem("HttpHeaderParserTestBed", testConf)
|
||||
|
||||
val parser = HttpHeaderParser.prime {
|
||||
HttpHeaderParser.unprimed(ParserSettings(system), warnOnIllegalHeader = info ⇒ system.log.warning(info.formatPretty))
|
||||
HttpHeaderParser.unprimed(ParserSettings(system), system.log, warnOnIllegalHeader = info ⇒ system.log.warning(info.formatPretty))
|
||||
}
|
||||
|
||||
println {
|
||||
|
|
|
|||
|
|
@ -300,7 +300,7 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
|
|||
|
||||
"support `rawRequestUriHeader` setting" in new Test {
|
||||
override protected def newParser: HttpRequestParser =
|
||||
new HttpRequestParser(parserSettings, rawRequestUriHeader = true, headerParser = HttpHeaderParser(parserSettings)())
|
||||
new HttpRequestParser(parserSettings, rawRequestUriHeader = true, headerParser = HttpHeaderParser(parserSettings, system.log)())
|
||||
|
||||
"""GET /f%6f%6fbar?q=b%61z HTTP/1.1
|
||||
|Host: ping
|
||||
|
|
@ -582,7 +582,7 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
|
|||
.awaitResult(awaitAtMost)
|
||||
|
||||
protected def parserSettings: ParserSettings = ParserSettings(system)
|
||||
protected def newParser = new HttpRequestParser(parserSettings, false, HttpHeaderParser(parserSettings)())
|
||||
protected def newParser = new HttpRequestParser(parserSettings, false, HttpHeaderParser(parserSettings, system.log)())
|
||||
|
||||
private def compactEntity(entity: RequestEntity): Future[RequestEntity] =
|
||||
entity match {
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ class ResponseParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
|
|||
protected def parserSettings: ParserSettings = ParserSettings(system)
|
||||
|
||||
def newParserStage(requestMethod: HttpMethod = GET) = {
|
||||
val parser = new HttpResponseParser(parserSettings, HttpHeaderParser(parserSettings)())
|
||||
val parser = new HttpResponseParser(parserSettings, HttpHeaderParser(parserSettings, system.log)())
|
||||
parser.setContextForNextResponse(HttpResponseParser.ResponseContext(requestMethod, None))
|
||||
parser.stage
|
||||
}
|
||||
|
|
|
|||
|
|
@ -907,7 +907,11 @@ object MiMa extends AutoPlugin {
|
|||
),
|
||||
"2.4.9" -> Seq(
|
||||
// #20994 adding new decode method, since we're on JDK7+ now
|
||||
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.util.ByteString.decodeString")
|
||||
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.util.ByteString.decodeString"),
|
||||
|
||||
// #20976 provide different options to deal with the illegal response header value
|
||||
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.settings.ParserSettings.getIllegalResponseHeaderValueProcessingMode"),
|
||||
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.settings.ParserSettings.illegalResponseHeaderValueProcessingMode")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue