From 9af15059476743049454e39d3d41fcb5df5f3187 Mon Sep 17 00:00:00 2001 From: Shinichi Morimoto Date: Fri, 16 Mar 2018 16:25:55 +0900 Subject: [PATCH 01/17] add setting ignore-illegal-header-for #687 --- akka-http-core/src/main/resources/reference.conf | 3 +++ .../impl/engine/parsing/HttpHeaderParser.scala | 5 +++-- .../akka/http/impl/engine/parsing/package.scala | 15 ++++++++++----- .../http/impl/settings/ParserSettingsImpl.scala | 2 ++ .../http/javadsl/settings/ParserSettings.scala | 1 + .../akka/http/scaladsl/model/ErrorInfo.scala | 3 ++- .../http/scaladsl/settings/ParserSettings.scala | 2 ++ .../http/scaladsl/model/headers/HeaderSpec.scala | 14 +++++++------- 8 files changed, 30 insertions(+), 15 deletions(-) diff --git a/akka-http-core/src/main/resources/reference.conf b/akka-http-core/src/main/resources/reference.conf index 6d5d80d29e8..9329a108d1f 100644 --- a/akka-http-core/src/main/resources/reference.conf +++ b/akka-http-core/src/main/resources/reference.conf @@ -424,6 +424,9 @@ akka.http { # `error-logging-verbosity`. illegal-header-warnings = on + # TODO comment + ignore-illegal-header-for = [] + # Parse headers into typed model classes in the Akka Http core layer. # # If set to `off`, only essential headers will be parsed into their model classes. All other ones will be provided diff --git a/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/HttpHeaderParser.scala b/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/HttpHeaderParser.scala index 17dc9c361d5..2bb0be7ea1b 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/HttpHeaderParser.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/HttpHeaderParser.scala @@ -426,6 +426,7 @@ private[http] object HttpHeaderParser { def headerValueCacheLimit(headerName: String): Int def customMediaTypes: MediaTypes.FindCustom def illegalHeaderWarnings: Boolean + def ignoreIllegalHeaderFor: Set[String] def illegalResponseHeaderValueProcessingMode: IllegalResponseHeaderValueProcessingMode def errorLoggingVerbosity: ErrorLoggingVerbosity def modeledHeaderParsing: Boolean @@ -461,7 +462,7 @@ private[http] object HttpHeaderParser { def defaultIllegalHeaderHandler(settings: HttpHeaderParser.Settings, log: LoggingAdapter): ErrorInfo ⇒ Unit = if (settings.illegalHeaderWarnings) - info ⇒ logParsingError(info withSummaryPrepended "Illegal header", log, settings.errorLoggingVerbosity) + info ⇒ logParsingError(info withSummaryPrepended "Illegal header", log, settings.errorLoggingVerbosity, settings.ignoreIllegalHeaderFor) else (_: ErrorInfo) ⇒ _ // Does exactly what the label says - nothing @@ -529,7 +530,7 @@ private[http] object HttpHeaderParser { val header = parser(trimmedHeaderValue) match { case HeaderParser.Success(h) ⇒ h case HeaderParser.Failure(error) ⇒ - onIllegalHeader(error.withSummaryPrepended(s"Illegal '$headerName' header")) + onIllegalHeader(error.withSummaryPrepended(s"Illegal '$headerName' header").withHeaderNamePrepend(headerName)) RawHeader(headerName, trimmedHeaderValue) case HeaderParser.RuleNotFound ⇒ throw new IllegalStateException(s"Unexpected RuleNotFound exception for modeled header [$headerName]") diff --git a/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/package.scala b/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/package.scala index ed728bad825..5da113d9de2 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/package.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/package.scala @@ -38,11 +38,16 @@ package object parsing { } private[http] def logParsingError(info: ErrorInfo, log: LoggingAdapter, - setting: ParserSettings.ErrorLoggingVerbosity): Unit = - setting match { - case ParserSettings.ErrorLoggingVerbosity.Off ⇒ // nothing to do - case ParserSettings.ErrorLoggingVerbosity.Simple ⇒ log.warning(info.summary) - case ParserSettings.ErrorLoggingVerbosity.Full ⇒ log.warning(info.formatPretty) + settings: ParserSettings.ErrorLoggingVerbosity, + ignoreHeaderNames: Set[String] = Set.empty): Unit = + settings match { + case ParserSettings.ErrorLoggingVerbosity.Off ⇒ // nothing to do + case ParserSettings.ErrorLoggingVerbosity.Simple ⇒ + if (!ignoreHeaderNames.contains(info.errorHeaderName)) + log.warning(info.summary) + case ParserSettings.ErrorLoggingVerbosity.Full ⇒ + if (!ignoreHeaderNames.contains(info.errorHeaderName)) + log.warning(info.formatPretty) } } diff --git a/akka-http-core/src/main/scala/akka/http/impl/settings/ParserSettingsImpl.scala b/akka-http-core/src/main/scala/akka/http/impl/settings/ParserSettingsImpl.scala index 7e75648fc7a..0772cf7716a 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/settings/ParserSettingsImpl.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/settings/ParserSettingsImpl.scala @@ -28,6 +28,7 @@ private[akka] final case class ParserSettingsImpl( uriParsingMode: Uri.ParsingMode, cookieParsingMode: CookieParsingMode, illegalHeaderWarnings: Boolean, + ignoreIllegalHeaderFor: Set[String], errorLoggingVerbosity: ErrorLoggingVerbosity, illegalResponseHeaderValueProcessingMode: IllegalResponseHeaderValueProcessingMode, headerValueCacheLimits: Map[String, Int], @@ -82,6 +83,7 @@ object ParserSettingsImpl extends SettingsCompanion[ParserSettingsImpl]("akka.ht Uri.ParsingMode(c getString "uri-parsing-mode"), CookieParsingMode(c getString "cookie-parsing-mode"), c getBoolean "illegal-header-warnings", + (c getStringList "ignore-illegal-header-for").asScala.toSet, 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), diff --git a/akka-http-core/src/main/scala/akka/http/javadsl/settings/ParserSettings.scala b/akka-http-core/src/main/scala/akka/http/javadsl/settings/ParserSettings.scala index 5d8143bcaaf..c2eed8ecaaa 100644 --- a/akka-http-core/src/main/scala/akka/http/javadsl/settings/ParserSettings.scala +++ b/akka-http-core/src/main/scala/akka/http/javadsl/settings/ParserSettings.scala @@ -36,6 +36,7 @@ abstract class ParserSettings private[akka] () extends BodyPartParser.Settings { def getUriParsingMode: Uri.ParsingMode def getCookieParsingMode: ParserSettings.CookieParsingMode def getIllegalHeaderWarnings: Boolean + def getIgnoreIllegalHeaderFor: Set[String] def getErrorLoggingVerbosity: ParserSettings.ErrorLoggingVerbosity def getIllegalResponseHeaderValueProcessingMode: ParserSettings.IllegalResponseHeaderValueProcessingMode def getHeaderValueCacheLimits: ju.Map[String, Int] diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala index 0d357246da8..e50216f896f 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala @@ -12,9 +12,10 @@ import StatusCodes.ClientError * repeating anything present in the message itself (in order to not open holes for XSS attacks), * while the detail can contain additional information from any source (even the request itself). */ -final case class ErrorInfo(summary: String = "", detail: String = "") { +final case class ErrorInfo(summary: String = "", detail: String = "", errorHeaderName: String = "") { def withSummary(newSummary: String) = copy(summary = newSummary) def withSummaryPrepended(prefix: String) = withSummary(if (summary.isEmpty) prefix else prefix + ": " + summary) + def withHeaderNamePrepend(headerName: String) = copy(errorHeaderName = headerName) def withFallbackSummary(fallbackSummary: String) = if (summary.isEmpty) withSummary(fallbackSummary) else this def formatPretty = if (summary.isEmpty) detail else if (detail.isEmpty) summary else summary + ": " + detail def format(withDetail: Boolean): String = if (withDetail) formatPretty else summary diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/settings/ParserSettings.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/settings/ParserSettings.scala index 7d9c4991ed5..88184f06be3 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/settings/ParserSettings.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/settings/ParserSettings.scala @@ -36,6 +36,7 @@ abstract class ParserSettings private[akka] () extends akka.http.javadsl.setting def uriParsingMode: Uri.ParsingMode def cookieParsingMode: ParserSettings.CookieParsingMode def illegalHeaderWarnings: Boolean + def ignoreIllegalHeaderFor: Set[String] def errorLoggingVerbosity: ParserSettings.ErrorLoggingVerbosity def illegalResponseHeaderValueProcessingMode: ParserSettings.IllegalResponseHeaderValueProcessingMode def headerValueCacheLimits: Map[String, Int] @@ -55,6 +56,7 @@ abstract class ParserSettings private[akka] () extends akka.http.javadsl.setting override def getMaxHeaderValueLength = maxHeaderValueLength override def getIncludeTlsSessionInfoHeader = includeTlsSessionInfoHeader override def getIllegalHeaderWarnings = illegalHeaderWarnings + override def getIgnoreIllegalHeaderFor = ignoreIllegalHeaderFor override def getMaxHeaderNameLength = maxHeaderNameLength override def getMaxChunkSize = maxChunkSize override def getMaxResponseReasonLength = maxResponseReasonLength diff --git a/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala b/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala index 6b8b84a3c0d..c1d4bb880f3 100644 --- a/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala @@ -23,7 +23,7 @@ class HeaderSpec extends FreeSpec with Matchers { CacheDirectives.`s-maxage`(1000))) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail))) = headers.`Last-Modified`.parseFromValueString("abc") + val Left(List(ErrorInfo(summary, detail, ""))) = headers.`Last-Modified`.parseFromValueString("abc") summary shouldEqual "Illegal HTTP header 'Last-Modified': Invalid input 'a', expected IMF-fixdate, asctime-date or '0' (line 1, column 1)" detail shouldEqual """abc @@ -39,7 +39,7 @@ class HeaderSpec extends FreeSpec with Matchers { MediaType.parse("application/gnutar") shouldEqual Right(MediaTypes.`application/gnutar`) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail))) = MediaType.parse("application//gnutar") + val Left(List(ErrorInfo(summary, detail, ""))) = MediaType.parse("application//gnutar") summary shouldEqual "Illegal HTTP header 'Content-Type': Invalid input '/', expected subtype (line 1, column 13)" detail shouldEqual """application//gnutar @@ -54,7 +54,7 @@ class HeaderSpec extends FreeSpec with Matchers { ContentType.parse("text/plain; charset=UTF8") shouldEqual Right(MediaTypes.`text/plain`.withCharset(HttpCharsets.`UTF-8`)) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail))) = ContentType.parse("text/plain, charset=UTF8") + val Left(List(ErrorInfo(summary, detail, ""))) = ContentType.parse("text/plain, charset=UTF8") summary shouldEqual "Illegal HTTP header 'Content-Type': Invalid input ',', expected tchar, OWS, ws or 'EOI' (line 1, column 11)" detail shouldEqual """text/plain, charset=UTF8 @@ -71,11 +71,11 @@ class HeaderSpec extends FreeSpec with Matchers { Right(headers.`Retry-After`(DateTime(2015, 10, 21, 7, 28))) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail))) = `Retry-After`.parseFromValueString("011") + val Left(List(ErrorInfo(summary, detail, ""))) = `Retry-After`.parseFromValueString("011") summary shouldEqual "Illegal HTTP header 'Retry-After': Invalid input '1', expected OWS or 'EOI' (line 1, column 2)" - val Left(List(ErrorInfo(summary2, detail2))) = `Retry-After`.parseFromValueString("-10") + val Left(List(ErrorInfo(summary2, detail2, ""))) = `Retry-After`.parseFromValueString("-10") summary2 shouldEqual "Illegal HTTP header 'Retry-After': Invalid input '-', expected HTTP-date or delta-seconds (line 1, column 1)" - val Left(List(ErrorInfo(summary3, detail3))) = `Retry-After`.parseFromValueString("2015-10-21H07:28:00Z") + val Left(List(ErrorInfo(summary3, detail3, ""))) = `Retry-After`.parseFromValueString("2015-10-21H07:28:00Z") summary3 shouldEqual "Illegal HTTP header 'Retry-After': Invalid input '-', expected DIGIT, OWS or 'EOI' (line 1, column 5)" } } @@ -94,7 +94,7 @@ class HeaderSpec extends FreeSpec with Matchers { Right(headers.`Strict-Transport-Security`(30, true)) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail))) = `Strict-Transport-Security`.parseFromValueString("max-age=30; includeSubDomains; preload;") + val Left(List(ErrorInfo(summary, detail, ""))) = `Strict-Transport-Security`.parseFromValueString("max-age=30; includeSubDomains; preload;") summary shouldEqual "Illegal HTTP header 'Strict-Transport-Security': Invalid input 'EOI', expected OWS or token0 (line 1, column 40)" } } From 563ddc22a76c07114611abc6836756fe5489a16f Mon Sep 17 00:00:00 2001 From: Shinichi Morimoto Date: Fri, 16 Mar 2018 23:08:19 +0900 Subject: [PATCH 02/17] fix binary incompatibility #687 --- .../akka/http/scaladsl/model/ErrorInfo.scala | 35 +++++++++++++++++-- .../scaladsl/model/headers/HeaderSpec.scala | 14 ++++---- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala index e50216f896f..686be664b34 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala @@ -12,13 +12,40 @@ import StatusCodes.ClientError * repeating anything present in the message itself (in order to not open holes for XSS attacks), * while the detail can contain additional information from any source (even the request itself). */ -final case class ErrorInfo(summary: String = "", detail: String = "", errorHeaderName: String = "") { +final class ErrorInfo( + val summary: String = "", + val detail: String = "", + val errorHeaderName: String = "" +) extends scala.Product with scala.Serializable with scala.Equals with java.io.Serializable { def withSummary(newSummary: String) = copy(summary = newSummary) def withSummaryPrepended(prefix: String) = withSummary(if (summary.isEmpty) prefix else prefix + ": " + summary) - def withHeaderNamePrepend(headerName: String) = copy(errorHeaderName = headerName) + def withHeaderNamePrepend(headerName: String) = setErrorHeaderName(errorHeaderName = headerName) def withFallbackSummary(fallbackSummary: String) = if (summary.isEmpty) withSummary(fallbackSummary) else this def formatPretty = if (summary.isEmpty) detail else if (detail.isEmpty) summary else summary + ": " + detail def format(withDetail: Boolean): String = if (withDetail) formatPretty else summary + + private[akka] def copy(summary: String = summary, detail: String = detail): ErrorInfo = { + new ErrorInfo(summary, detail, errorHeaderName) + } + + private[akka] def setErrorHeaderName(errorHeaderName: String): ErrorInfo = new ErrorInfo(summary, detail, errorHeaderName) + + override def canEqual(that: Any): Boolean = that.isInstanceOf[ErrorInfo] + + override def equals(that: Any): Boolean = that match { + case that: ErrorInfo ⇒ that.canEqual(this) && that.summary == this.summary && that.detail == this.detail && that.errorHeaderName == this.errorHeaderName + case _ ⇒ false + } + + override def productElement(n: Int): Any = n match { + case 0 ⇒ summary + case 1 ⇒ detail + case 2 ⇒ errorHeaderName + } + + override def productArity: Int = 3 + + private[akka] def this(summary: String, detail: String) = this(summary, detail, "") } object ErrorInfo { @@ -27,6 +54,10 @@ object ErrorInfo { * Used for example when catching exceptions generated by the header value parser, which doesn't provide * summary/details information but structures its exception messages accordingly. */ + private[akka] def apply(summary: String = "", detail: String = ""): ErrorInfo = new ErrorInfo(summary, detail, "") + + private[akka] def unapply(arg: ErrorInfo): Option[(String, String)] = Some((arg.summary, arg.detail)) + def fromCompoundString(message: String): ErrorInfo = message.split(": ", 2) match { case Array(summary, detail) ⇒ apply(summary, detail) case _ ⇒ ErrorInfo("", message) diff --git a/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala b/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala index c1d4bb880f3..6b8b84a3c0d 100644 --- a/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala @@ -23,7 +23,7 @@ class HeaderSpec extends FreeSpec with Matchers { CacheDirectives.`s-maxage`(1000))) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail, ""))) = headers.`Last-Modified`.parseFromValueString("abc") + val Left(List(ErrorInfo(summary, detail))) = headers.`Last-Modified`.parseFromValueString("abc") summary shouldEqual "Illegal HTTP header 'Last-Modified': Invalid input 'a', expected IMF-fixdate, asctime-date or '0' (line 1, column 1)" detail shouldEqual """abc @@ -39,7 +39,7 @@ class HeaderSpec extends FreeSpec with Matchers { MediaType.parse("application/gnutar") shouldEqual Right(MediaTypes.`application/gnutar`) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail, ""))) = MediaType.parse("application//gnutar") + val Left(List(ErrorInfo(summary, detail))) = MediaType.parse("application//gnutar") summary shouldEqual "Illegal HTTP header 'Content-Type': Invalid input '/', expected subtype (line 1, column 13)" detail shouldEqual """application//gnutar @@ -54,7 +54,7 @@ class HeaderSpec extends FreeSpec with Matchers { ContentType.parse("text/plain; charset=UTF8") shouldEqual Right(MediaTypes.`text/plain`.withCharset(HttpCharsets.`UTF-8`)) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail, ""))) = ContentType.parse("text/plain, charset=UTF8") + val Left(List(ErrorInfo(summary, detail))) = ContentType.parse("text/plain, charset=UTF8") summary shouldEqual "Illegal HTTP header 'Content-Type': Invalid input ',', expected tchar, OWS, ws or 'EOI' (line 1, column 11)" detail shouldEqual """text/plain, charset=UTF8 @@ -71,11 +71,11 @@ class HeaderSpec extends FreeSpec with Matchers { Right(headers.`Retry-After`(DateTime(2015, 10, 21, 7, 28))) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail, ""))) = `Retry-After`.parseFromValueString("011") + val Left(List(ErrorInfo(summary, detail))) = `Retry-After`.parseFromValueString("011") summary shouldEqual "Illegal HTTP header 'Retry-After': Invalid input '1', expected OWS or 'EOI' (line 1, column 2)" - val Left(List(ErrorInfo(summary2, detail2, ""))) = `Retry-After`.parseFromValueString("-10") + val Left(List(ErrorInfo(summary2, detail2))) = `Retry-After`.parseFromValueString("-10") summary2 shouldEqual "Illegal HTTP header 'Retry-After': Invalid input '-', expected HTTP-date or delta-seconds (line 1, column 1)" - val Left(List(ErrorInfo(summary3, detail3, ""))) = `Retry-After`.parseFromValueString("2015-10-21H07:28:00Z") + val Left(List(ErrorInfo(summary3, detail3))) = `Retry-After`.parseFromValueString("2015-10-21H07:28:00Z") summary3 shouldEqual "Illegal HTTP header 'Retry-After': Invalid input '-', expected DIGIT, OWS or 'EOI' (line 1, column 5)" } } @@ -94,7 +94,7 @@ class HeaderSpec extends FreeSpec with Matchers { Right(headers.`Strict-Transport-Security`(30, true)) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail, ""))) = `Strict-Transport-Security`.parseFromValueString("max-age=30; includeSubDomains; preload;") + val Left(List(ErrorInfo(summary, detail))) = `Strict-Transport-Security`.parseFromValueString("max-age=30; includeSubDomains; preload;") summary shouldEqual "Illegal HTTP header 'Strict-Transport-Security': Invalid input 'EOI', expected OWS or token0 (line 1, column 40)" } } From b9b45d71e7c1bb8337170ab5a85629a2d537c317 Mon Sep 17 00:00:00 2001 From: Shinichi Morimoto Date: Sat, 17 Mar 2018 20:54:27 +0900 Subject: [PATCH 03/17] move binary compatibility warning configuration to 10.1.0 #687 --- .../src/main/mima-filters/10.1.0.backwards.excludes | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes b/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes index 998c01f9cb6..c6d58f03f39 100644 --- a/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes +++ b/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes @@ -8,3 +8,7 @@ ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.model.Ur ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.settings.ServerSettings.getWebsocketSettings") ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.settings.ClientConnectionSettings.websocketSettings") ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.settings.ServerSettings.websocketSettings") + +# New ignoreIllegalHeaderFor setting +ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.settings.ParserSettings.getIgnoreIllegalHeaderFor") +ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.settings.ParserSettings.ignoreIllegalHeaderFor") From 2185245c14028c1764b152988df24cc5f26daca4 Mon Sep 17 00:00:00 2001 From: Shinichi Morimoto Date: Sat, 17 Mar 2018 20:56:02 +0900 Subject: [PATCH 04/17] add setting's comment #687 --- akka-http-core/src/main/resources/reference.conf | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/akka-http-core/src/main/resources/reference.conf b/akka-http-core/src/main/resources/reference.conf index 9329a108d1f..3fcea3c3f69 100644 --- a/akka-http-core/src/main/resources/reference.conf +++ b/akka-http-core/src/main/resources/reference.conf @@ -424,7 +424,11 @@ akka.http { # `error-logging-verbosity`. illegal-header-warnings = on - # TODO comment + # Sets the whitelist for illegal header + # + # When adding a header name to this setting. + # Disables the logging of warning messages in case an incoming message + # contains an HTTP header which cannot be parsed into its high-level model class due to incompatible syntax. ignore-illegal-header-for = [] # Parse headers into typed model classes in the Akka Http core layer. From 0e970a3b6c2876e2073a9201f4d2b4b52f75f338 Mon Sep 17 00:00:00 2001 From: Shinichi Morimoto Date: Sat, 17 Mar 2018 21:04:37 +0900 Subject: [PATCH 05/17] rename method #687 --- .../akka/http/impl/engine/parsing/HttpHeaderParser.scala | 2 +- .../src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/HttpHeaderParser.scala b/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/HttpHeaderParser.scala index 2bb0be7ea1b..30512090cf8 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/HttpHeaderParser.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/HttpHeaderParser.scala @@ -530,7 +530,7 @@ private[http] object HttpHeaderParser { val header = parser(trimmedHeaderValue) match { case HeaderParser.Success(h) ⇒ h case HeaderParser.Failure(error) ⇒ - onIllegalHeader(error.withSummaryPrepended(s"Illegal '$headerName' header").withHeaderNamePrepend(headerName)) + onIllegalHeader(error.withSummaryPrepended(s"Illegal '$headerName' header").withErrorHeaderName(headerName)) RawHeader(headerName, trimmedHeaderValue) case HeaderParser.RuleNotFound ⇒ throw new IllegalStateException(s"Unexpected RuleNotFound exception for modeled header [$headerName]") diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala index 686be664b34..f43a98bca97 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala @@ -19,7 +19,7 @@ final class ErrorInfo( ) extends scala.Product with scala.Serializable with scala.Equals with java.io.Serializable { def withSummary(newSummary: String) = copy(summary = newSummary) def withSummaryPrepended(prefix: String) = withSummary(if (summary.isEmpty) prefix else prefix + ": " + summary) - def withHeaderNamePrepend(headerName: String) = setErrorHeaderName(errorHeaderName = headerName) + def withErrorHeaderName(headerName: String) = new ErrorInfo(summary, detail, headerName) def withFallbackSummary(fallbackSummary: String) = if (summary.isEmpty) withSummary(fallbackSummary) else this def formatPretty = if (summary.isEmpty) detail else if (detail.isEmpty) summary else summary + ": " + detail def format(withDetail: Boolean): String = if (withDetail) formatPretty else summary @@ -28,8 +28,6 @@ final class ErrorInfo( new ErrorInfo(summary, detail, errorHeaderName) } - private[akka] def setErrorHeaderName(errorHeaderName: String): ErrorInfo = new ErrorInfo(summary, detail, errorHeaderName) - override def canEqual(that: Any): Boolean = that.isInstanceOf[ErrorInfo] override def equals(that: Any): Boolean = that match { From 2aba588f96164baf1d53b8c0817298bcdad95d7c Mon Sep 17 00:00:00 2001 From: Shinichi Morimoto Date: Sat, 17 Mar 2018 21:05:19 +0900 Subject: [PATCH 06/17] reorder method #687 --- .../main/scala/akka/http/scaladsl/model/ErrorInfo.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala index f43a98bca97..a44e775008e 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala @@ -47,15 +47,15 @@ final class ErrorInfo( } object ErrorInfo { + private[akka] def apply(summary: String = "", detail: String = ""): ErrorInfo = new ErrorInfo(summary, detail, "") + + private[akka] def unapply(arg: ErrorInfo): Option[(String, String)] = Some((arg.summary, arg.detail)) + /** * Allows constructing an `ErrorInfo` from a single string. * Used for example when catching exceptions generated by the header value parser, which doesn't provide * summary/details information but structures its exception messages accordingly. */ - private[akka] def apply(summary: String = "", detail: String = ""): ErrorInfo = new ErrorInfo(summary, detail, "") - - private[akka] def unapply(arg: ErrorInfo): Option[(String, String)] = Some((arg.summary, arg.detail)) - def fromCompoundString(message: String): ErrorInfo = message.split(": ", 2) match { case Array(summary, detail) ⇒ apply(summary, detail) case _ ⇒ ErrorInfo("", message) From 9ec538b9a4ff909905960f61d95d03d6fc3a8a55 Mon Sep 17 00:00:00 2001 From: Shinichi Morimoto Date: Sat, 17 Mar 2018 21:44:41 +0900 Subject: [PATCH 07/17] check ignore header name insensitive #687 --- .../main/scala/akka/http/impl/settings/ParserSettingsImpl.scala | 2 +- .../src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/akka-http-core/src/main/scala/akka/http/impl/settings/ParserSettingsImpl.scala b/akka-http-core/src/main/scala/akka/http/impl/settings/ParserSettingsImpl.scala index 0772cf7716a..421c6852aed 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/settings/ParserSettingsImpl.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/settings/ParserSettingsImpl.scala @@ -83,7 +83,7 @@ object ParserSettingsImpl extends SettingsCompanion[ParserSettingsImpl]("akka.ht Uri.ParsingMode(c getString "uri-parsing-mode"), CookieParsingMode(c getString "cookie-parsing-mode"), c getBoolean "illegal-header-warnings", - (c getStringList "ignore-illegal-header-for").asScala.toSet, + (c getStringList "ignore-illegal-header-for").asScala.map(_.toLowerCase).toSet, 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), diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala index a44e775008e..5ef10ddc7a0 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala @@ -19,7 +19,7 @@ final class ErrorInfo( ) extends scala.Product with scala.Serializable with scala.Equals with java.io.Serializable { def withSummary(newSummary: String) = copy(summary = newSummary) def withSummaryPrepended(prefix: String) = withSummary(if (summary.isEmpty) prefix else prefix + ": " + summary) - def withErrorHeaderName(headerName: String) = new ErrorInfo(summary, detail, headerName) + def withErrorHeaderName(headerName: String) = new ErrorInfo(summary, detail, headerName.toLowerCase) def withFallbackSummary(fallbackSummary: String) = if (summary.isEmpty) withSummary(fallbackSummary) else this def formatPretty = if (summary.isEmpty) detail else if (detail.isEmpty) summary else summary + ": " + detail def format(withDetail: Boolean): String = if (withDetail) formatPretty else summary From bc2e09e5d487342631ac8b37e76be399700c600a Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 19 Mar 2018 10:16:42 +0900 Subject: [PATCH 08/17] Update 10.1.0.backwards.excludes --- akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes b/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes index c6d58f03f39..c42625208bd 100644 --- a/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes +++ b/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes @@ -9,6 +9,6 @@ ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.settings ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.settings.ClientConnectionSettings.websocketSettings") ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.settings.ServerSettings.websocketSettings") -# New ignoreIllegalHeaderFor setting +# #1942 New ignoreIllegalHeaderFor setting ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.settings.ParserSettings.getIgnoreIllegalHeaderFor") ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.settings.ParserSettings.ignoreIllegalHeaderFor") From 8b14a9240d2cb54139ed0065350dcae79a98a8cf Mon Sep 17 00:00:00 2001 From: Shinichi Morimoto Date: Tue, 20 Mar 2018 12:24:03 +0900 Subject: [PATCH 09/17] put case class back for ErrorInfo. and add annotation @InternalAPI #687 --- .../mima-filters/10.1.0.backwards.excludes | 5 +++ .../akka/http/scaladsl/model/ErrorInfo.scala | 38 +++---------------- 2 files changed, 11 insertions(+), 32 deletions(-) diff --git a/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes b/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes index c42625208bd..90ddd7be97b 100644 --- a/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes +++ b/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes @@ -12,3 +12,8 @@ ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.setting # #1942 New ignoreIllegalHeaderFor setting ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.settings.ParserSettings.getIgnoreIllegalHeaderFor") ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.settings.ParserSettings.ignoreIllegalHeaderFor") + +# #1942 Internal class +ProblemFilters.exclude[DirectMissingMethodProblem]("akka.http.scaladsl.model.ErrorInfo.copy") +ProblemFilters.exclude[DirectMissingMethodProblem]("akka.http.scaladsl.model.ErrorInfo.this") +ProblemFilters.exclude[DirectMissingMethodProblem]("akka.http.scaladsl.model.ErrorInfo.apply") diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala index 5ef10ddc7a0..ef036161dd0 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala @@ -5,52 +5,26 @@ package akka.http.scaladsl.model import StatusCodes.ClientError +import akka.annotation.InternalApi /** + * INTERNAL API * Two-level model of error information. * The summary should explain what is wrong with the request or response *without* directly * repeating anything present in the message itself (in order to not open holes for XSS attacks), * while the detail can contain additional information from any source (even the request itself). */ -final class ErrorInfo( - val summary: String = "", - val detail: String = "", - val errorHeaderName: String = "" -) extends scala.Product with scala.Serializable with scala.Equals with java.io.Serializable { +@InternalApi private[akka] final case class ErrorInfo(summary: String = "", detail: String = "", errorHeaderName: String = "") { def withSummary(newSummary: String) = copy(summary = newSummary) def withSummaryPrepended(prefix: String) = withSummary(if (summary.isEmpty) prefix else prefix + ": " + summary) - def withErrorHeaderName(headerName: String) = new ErrorInfo(summary, detail, headerName.toLowerCase) + def withErrorHeaderName(headerName: String) = copy(errorHeaderName = headerName.toLowerCase) def withFallbackSummary(fallbackSummary: String) = if (summary.isEmpty) withSummary(fallbackSummary) else this def formatPretty = if (summary.isEmpty) detail else if (detail.isEmpty) summary else summary + ": " + detail def format(withDetail: Boolean): String = if (withDetail) formatPretty else summary - - private[akka] def copy(summary: String = summary, detail: String = detail): ErrorInfo = { - new ErrorInfo(summary, detail, errorHeaderName) - } - - override def canEqual(that: Any): Boolean = that.isInstanceOf[ErrorInfo] - - override def equals(that: Any): Boolean = that match { - case that: ErrorInfo ⇒ that.canEqual(this) && that.summary == this.summary && that.detail == this.detail && that.errorHeaderName == this.errorHeaderName - case _ ⇒ false - } - - override def productElement(n: Int): Any = n match { - case 0 ⇒ summary - case 1 ⇒ detail - case 2 ⇒ errorHeaderName - } - - override def productArity: Int = 3 - - private[akka] def this(summary: String, detail: String) = this(summary, detail, "") } -object ErrorInfo { - private[akka] def apply(summary: String = "", detail: String = ""): ErrorInfo = new ErrorInfo(summary, detail, "") - - private[akka] def unapply(arg: ErrorInfo): Option[(String, String)] = Some((arg.summary, arg.detail)) - +/** INTERNAL API */ +@InternalApi private[akka] object ErrorInfo { /** * Allows constructing an `ErrorInfo` from a single string. * Used for example when catching exceptions generated by the header value parser, which doesn't provide From 1e5a3255d04734fa77dfc2d6c4e1655f7e8765cd Mon Sep 17 00:00:00 2001 From: Shinichi Morimoto Date: Tue, 20 Mar 2018 13:04:53 +0900 Subject: [PATCH 10/17] fix test #687 --- .../http/scaladsl/model/headers/HeaderSpec.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala b/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala index 6b8b84a3c0d..c1d4bb880f3 100644 --- a/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala @@ -23,7 +23,7 @@ class HeaderSpec extends FreeSpec with Matchers { CacheDirectives.`s-maxage`(1000))) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail))) = headers.`Last-Modified`.parseFromValueString("abc") + val Left(List(ErrorInfo(summary, detail, ""))) = headers.`Last-Modified`.parseFromValueString("abc") summary shouldEqual "Illegal HTTP header 'Last-Modified': Invalid input 'a', expected IMF-fixdate, asctime-date or '0' (line 1, column 1)" detail shouldEqual """abc @@ -39,7 +39,7 @@ class HeaderSpec extends FreeSpec with Matchers { MediaType.parse("application/gnutar") shouldEqual Right(MediaTypes.`application/gnutar`) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail))) = MediaType.parse("application//gnutar") + val Left(List(ErrorInfo(summary, detail, ""))) = MediaType.parse("application//gnutar") summary shouldEqual "Illegal HTTP header 'Content-Type': Invalid input '/', expected subtype (line 1, column 13)" detail shouldEqual """application//gnutar @@ -54,7 +54,7 @@ class HeaderSpec extends FreeSpec with Matchers { ContentType.parse("text/plain; charset=UTF8") shouldEqual Right(MediaTypes.`text/plain`.withCharset(HttpCharsets.`UTF-8`)) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail))) = ContentType.parse("text/plain, charset=UTF8") + val Left(List(ErrorInfo(summary, detail, ""))) = ContentType.parse("text/plain, charset=UTF8") summary shouldEqual "Illegal HTTP header 'Content-Type': Invalid input ',', expected tchar, OWS, ws or 'EOI' (line 1, column 11)" detail shouldEqual """text/plain, charset=UTF8 @@ -71,11 +71,11 @@ class HeaderSpec extends FreeSpec with Matchers { Right(headers.`Retry-After`(DateTime(2015, 10, 21, 7, 28))) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail))) = `Retry-After`.parseFromValueString("011") + val Left(List(ErrorInfo(summary, detail, ""))) = `Retry-After`.parseFromValueString("011") summary shouldEqual "Illegal HTTP header 'Retry-After': Invalid input '1', expected OWS or 'EOI' (line 1, column 2)" - val Left(List(ErrorInfo(summary2, detail2))) = `Retry-After`.parseFromValueString("-10") + val Left(List(ErrorInfo(summary2, detail2, ""))) = `Retry-After`.parseFromValueString("-10") summary2 shouldEqual "Illegal HTTP header 'Retry-After': Invalid input '-', expected HTTP-date or delta-seconds (line 1, column 1)" - val Left(List(ErrorInfo(summary3, detail3))) = `Retry-After`.parseFromValueString("2015-10-21H07:28:00Z") + val Left(List(ErrorInfo(summary3, detail3, ""))) = `Retry-After`.parseFromValueString("2015-10-21H07:28:00Z") summary3 shouldEqual "Illegal HTTP header 'Retry-After': Invalid input '-', expected DIGIT, OWS or 'EOI' (line 1, column 5)" } } @@ -94,7 +94,7 @@ class HeaderSpec extends FreeSpec with Matchers { Right(headers.`Strict-Transport-Security`(30, true)) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail))) = `Strict-Transport-Security`.parseFromValueString("max-age=30; includeSubDomains; preload;") + val Left(List(ErrorInfo(summary, detail, ""))) = `Strict-Transport-Security`.parseFromValueString("max-age=30; includeSubDomains; preload;") summary shouldEqual "Illegal HTTP header 'Strict-Transport-Security': Invalid input 'EOI', expected OWS or token0 (line 1, column 40)" } } From f577046798aaa72b120b5c4abb9aa72f69b5a104 Mon Sep 17 00:00:00 2001 From: Shinichi Morimoto Date: Thu, 29 Mar 2018 02:19:48 +0900 Subject: [PATCH 11/17] delete InternalApi annnotation from ErrorInfo #687 --- .../main/mima-filters/10.1.0.backwards.excludes | 4 +--- .../akka/http/scaladsl/model/ErrorInfo.scala | 15 +++++++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes b/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes index 90ddd7be97b..9c2d4c877fc 100644 --- a/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes +++ b/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes @@ -13,7 +13,5 @@ ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.setting ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.settings.ParserSettings.getIgnoreIllegalHeaderFor") ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.settings.ParserSettings.ignoreIllegalHeaderFor") -# #1942 Internal class +# #1942 changes to Internal API ProblemFilters.exclude[DirectMissingMethodProblem]("akka.http.scaladsl.model.ErrorInfo.copy") -ProblemFilters.exclude[DirectMissingMethodProblem]("akka.http.scaladsl.model.ErrorInfo.this") -ProblemFilters.exclude[DirectMissingMethodProblem]("akka.http.scaladsl.model.ErrorInfo.apply") diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala index ef036161dd0..b23aa6ceff1 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala @@ -8,23 +8,30 @@ import StatusCodes.ClientError import akka.annotation.InternalApi /** - * INTERNAL API * Two-level model of error information. * The summary should explain what is wrong with the request or response *without* directly * repeating anything present in the message itself (in order to not open holes for XSS attacks), * while the detail can contain additional information from any source (even the request itself). */ -@InternalApi private[akka] final case class ErrorInfo(summary: String = "", detail: String = "", errorHeaderName: String = "") { +final case class ErrorInfo(summary: String = "", detail: String = "", errorHeaderName: String = "") { def withSummary(newSummary: String) = copy(summary = newSummary) def withSummaryPrepended(prefix: String) = withSummary(if (summary.isEmpty) prefix else prefix + ": " + summary) def withErrorHeaderName(headerName: String) = copy(errorHeaderName = headerName.toLowerCase) def withFallbackSummary(fallbackSummary: String) = if (summary.isEmpty) withSummary(fallbackSummary) else this def formatPretty = if (summary.isEmpty) detail else if (detail.isEmpty) summary else summary + ": " + detail def format(withDetail: Boolean): String = if (withDetail) formatPretty else summary + /** INTERNAL API */ + @InternalApi private[akka] def this(summary: String, detail: String) = { + this(summary, detail, "") + } } -/** INTERNAL API */ -@InternalApi private[akka] object ErrorInfo { +object ErrorInfo { + /** INTERNAL API */ + @InternalApi private[akka] def apply(summary: String, detail: String): ErrorInfo = { + ErrorInfo(summary, detail, "") + } + /** * Allows constructing an `ErrorInfo` from a single string. * Used for example when catching exceptions generated by the header value parser, which doesn't provide From a9585d487c82e9d04591d76352659a0421c3b635 Mon Sep 17 00:00:00 2001 From: Shinichi Morimoto Date: Tue, 3 Apr 2018 10:03:40 +0900 Subject: [PATCH 12/17] create 10.1.2.backwards.excludes #687 --- .../src/main/mima-filters/10.1.0.backwards.excludes | 7 ------- .../src/main/mima-filters/10.1.2.backwards.excludes | 9 +++++++++ 2 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 akka-http-core/src/main/mima-filters/10.1.2.backwards.excludes diff --git a/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes b/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes index 9c2d4c877fc..998c01f9cb6 100644 --- a/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes +++ b/akka-http-core/src/main/mima-filters/10.1.0.backwards.excludes @@ -8,10 +8,3 @@ ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.model.Ur ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.settings.ServerSettings.getWebsocketSettings") ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.settings.ClientConnectionSettings.websocketSettings") ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.settings.ServerSettings.websocketSettings") - -# #1942 New ignoreIllegalHeaderFor setting -ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.settings.ParserSettings.getIgnoreIllegalHeaderFor") -ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.settings.ParserSettings.ignoreIllegalHeaderFor") - -# #1942 changes to Internal API -ProblemFilters.exclude[DirectMissingMethodProblem]("akka.http.scaladsl.model.ErrorInfo.copy") diff --git a/akka-http-core/src/main/mima-filters/10.1.2.backwards.excludes b/akka-http-core/src/main/mima-filters/10.1.2.backwards.excludes new file mode 100644 index 00000000000..b25c4da443b --- /dev/null +++ b/akka-http-core/src/main/mima-filters/10.1.2.backwards.excludes @@ -0,0 +1,9 @@ +# Don't monitor changes to internal API +ProblemFilters.exclude[Problem]("akka.http.impl.*") + +# #1942 New ignoreIllegalHeaderFor setting +ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.settings.ParserSettings.getIgnoreIllegalHeaderFor") +ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.settings.ParserSettings.ignoreIllegalHeaderFor") + +# #1942 changes to Internal API +ProblemFilters.exclude[DirectMissingMethodProblem]("akka.http.scaladsl.model.ErrorInfo.copy") \ No newline at end of file From 48954f0ec8f6d1099f12a5403eb6edf8ef76306b Mon Sep 17 00:00:00 2001 From: Shinichi Morimoto Date: Wed, 4 Apr 2018 18:27:58 +0900 Subject: [PATCH 13/17] Chainge ErrorInfo from caseclass to class for bin incomp #687 --- .../mima-filters/10.1.2.backwards.excludes | 5 +-- .../akka/http/scaladsl/model/ErrorInfo.scala | 37 +++++++++++++++---- .../scaladsl/model/headers/HeaderSpec.scala | 14 +++---- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/akka-http-core/src/main/mima-filters/10.1.2.backwards.excludes b/akka-http-core/src/main/mima-filters/10.1.2.backwards.excludes index b25c4da443b..da31efee967 100644 --- a/akka-http-core/src/main/mima-filters/10.1.2.backwards.excludes +++ b/akka-http-core/src/main/mima-filters/10.1.2.backwards.excludes @@ -3,7 +3,4 @@ ProblemFilters.exclude[Problem]("akka.http.impl.*") # #1942 New ignoreIllegalHeaderFor setting ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.settings.ParserSettings.getIgnoreIllegalHeaderFor") -ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.settings.ParserSettings.ignoreIllegalHeaderFor") - -# #1942 changes to Internal API -ProblemFilters.exclude[DirectMissingMethodProblem]("akka.http.scaladsl.model.ErrorInfo.copy") \ No newline at end of file +ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.settings.ParserSettings.ignoreIllegalHeaderFor") \ No newline at end of file diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala index b23aa6ceff1..693ada58652 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/ErrorInfo.scala @@ -13,24 +13,47 @@ import akka.annotation.InternalApi * repeating anything present in the message itself (in order to not open holes for XSS attacks), * while the detail can contain additional information from any source (even the request itself). */ -final case class ErrorInfo(summary: String = "", detail: String = "", errorHeaderName: String = "") { +final class ErrorInfo( + val summary: String = "", + val detail: String = "", + val errorHeaderName: String = "" +) extends scala.Product with scala.Serializable with scala.Equals with java.io.Serializable { def withSummary(newSummary: String) = copy(summary = newSummary) def withSummaryPrepended(prefix: String) = withSummary(if (summary.isEmpty) prefix else prefix + ": " + summary) - def withErrorHeaderName(headerName: String) = copy(errorHeaderName = headerName.toLowerCase) + def withErrorHeaderName(headerName: String) = new ErrorInfo(summary, detail, headerName.toLowerCase) def withFallbackSummary(fallbackSummary: String) = if (summary.isEmpty) withSummary(fallbackSummary) else this def formatPretty = if (summary.isEmpty) detail else if (detail.isEmpty) summary else summary + ": " + detail def format(withDetail: Boolean): String = if (withDetail) formatPretty else summary + /** INTERNAL API */ - @InternalApi private[akka] def this(summary: String, detail: String) = { - this(summary, detail, "") + @InternalApi private[akka] def copy(summary: String = summary, detail: String = detail): ErrorInfo = { + new ErrorInfo(summary, detail, errorHeaderName) + } + + override def canEqual(that: Any): Boolean = that.isInstanceOf[ErrorInfo] + + override def equals(that: Any): Boolean = that match { + case that: ErrorInfo ⇒ that.canEqual(this) && that.summary == this.summary && that.detail == this.detail && that.errorHeaderName == this.errorHeaderName + case _ ⇒ false + } + + override def productElement(n: Int): Any = n match { + case 0 ⇒ summary + case 1 ⇒ detail + case 2 ⇒ errorHeaderName } + + override def productArity: Int = 3 + + /** INTERNAL API */ + @InternalApi private[akka] def this(summary: String, detail: String) = this(summary, detail, "") } object ErrorInfo { /** INTERNAL API */ - @InternalApi private[akka] def apply(summary: String, detail: String): ErrorInfo = { - ErrorInfo(summary, detail, "") - } + @InternalApi private[akka] def apply(summary: String = "", detail: String = ""): ErrorInfo = new ErrorInfo(summary, detail, "") + + def unapply(arg: ErrorInfo): Option[(String, String)] = Some((arg.summary, arg.detail)) /** * Allows constructing an `ErrorInfo` from a single string. diff --git a/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala b/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala index c1d4bb880f3..6b8b84a3c0d 100644 --- a/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/scaladsl/model/headers/HeaderSpec.scala @@ -23,7 +23,7 @@ class HeaderSpec extends FreeSpec with Matchers { CacheDirectives.`s-maxage`(1000))) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail, ""))) = headers.`Last-Modified`.parseFromValueString("abc") + val Left(List(ErrorInfo(summary, detail))) = headers.`Last-Modified`.parseFromValueString("abc") summary shouldEqual "Illegal HTTP header 'Last-Modified': Invalid input 'a', expected IMF-fixdate, asctime-date or '0' (line 1, column 1)" detail shouldEqual """abc @@ -39,7 +39,7 @@ class HeaderSpec extends FreeSpec with Matchers { MediaType.parse("application/gnutar") shouldEqual Right(MediaTypes.`application/gnutar`) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail, ""))) = MediaType.parse("application//gnutar") + val Left(List(ErrorInfo(summary, detail))) = MediaType.parse("application//gnutar") summary shouldEqual "Illegal HTTP header 'Content-Type': Invalid input '/', expected subtype (line 1, column 13)" detail shouldEqual """application//gnutar @@ -54,7 +54,7 @@ class HeaderSpec extends FreeSpec with Matchers { ContentType.parse("text/plain; charset=UTF8") shouldEqual Right(MediaTypes.`text/plain`.withCharset(HttpCharsets.`UTF-8`)) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail, ""))) = ContentType.parse("text/plain, charset=UTF8") + val Left(List(ErrorInfo(summary, detail))) = ContentType.parse("text/plain, charset=UTF8") summary shouldEqual "Illegal HTTP header 'Content-Type': Invalid input ',', expected tchar, OWS, ws or 'EOI' (line 1, column 11)" detail shouldEqual """text/plain, charset=UTF8 @@ -71,11 +71,11 @@ class HeaderSpec extends FreeSpec with Matchers { Right(headers.`Retry-After`(DateTime(2015, 10, 21, 7, 28))) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail, ""))) = `Retry-After`.parseFromValueString("011") + val Left(List(ErrorInfo(summary, detail))) = `Retry-After`.parseFromValueString("011") summary shouldEqual "Illegal HTTP header 'Retry-After': Invalid input '1', expected OWS or 'EOI' (line 1, column 2)" - val Left(List(ErrorInfo(summary2, detail2, ""))) = `Retry-After`.parseFromValueString("-10") + val Left(List(ErrorInfo(summary2, detail2))) = `Retry-After`.parseFromValueString("-10") summary2 shouldEqual "Illegal HTTP header 'Retry-After': Invalid input '-', expected HTTP-date or delta-seconds (line 1, column 1)" - val Left(List(ErrorInfo(summary3, detail3, ""))) = `Retry-After`.parseFromValueString("2015-10-21H07:28:00Z") + val Left(List(ErrorInfo(summary3, detail3))) = `Retry-After`.parseFromValueString("2015-10-21H07:28:00Z") summary3 shouldEqual "Illegal HTTP header 'Retry-After': Invalid input '-', expected DIGIT, OWS or 'EOI' (line 1, column 5)" } } @@ -94,7 +94,7 @@ class HeaderSpec extends FreeSpec with Matchers { Right(headers.`Strict-Transport-Security`(30, true)) } "failing parse run" in { - val Left(List(ErrorInfo(summary, detail, ""))) = `Strict-Transport-Security`.parseFromValueString("max-age=30; includeSubDomains; preload;") + val Left(List(ErrorInfo(summary, detail))) = `Strict-Transport-Security`.parseFromValueString("max-age=30; includeSubDomains; preload;") summary shouldEqual "Illegal HTTP header 'Strict-Transport-Security': Invalid input 'EOI', expected OWS or token0 (line 1, column 40)" } } From 8c04d0c3a81bb8165dd2803d492a92f0ebf408c5 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 5 Apr 2018 00:02:03 +0900 Subject: [PATCH 14/17] Update reference.conf --- akka-http-core/src/main/resources/reference.conf | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/akka-http-core/src/main/resources/reference.conf b/akka-http-core/src/main/resources/reference.conf index 3fcea3c3f69..fc7e8b3c11d 100644 --- a/akka-http-core/src/main/resources/reference.conf +++ b/akka-http-core/src/main/resources/reference.conf @@ -424,10 +424,9 @@ akka.http { # `error-logging-verbosity`. illegal-header-warnings = on - # Sets the whitelist for illegal header + # Sets the whitelist for illegal headers; # - # When adding a header name to this setting. - # Disables the logging of warning messages in case an incoming message + # Adding a header name to this setting list disables the logging of warning messages in case an incoming message # contains an HTTP header which cannot be parsed into its high-level model class due to incompatible syntax. ignore-illegal-header-for = [] From 0d01b924464ef76c5afdb5c38ed03013c589a5b5 Mon Sep 17 00:00:00 2001 From: Shinichi Morimoto Date: Sat, 14 Apr 2018 23:14:07 +0900 Subject: [PATCH 15/17] add tests #687 --- .../javadsl/settings/ParserSettings.scala | 1 + .../scaladsl/settings/ParserSettings.scala | 1 + .../engine/parsing/HttpHeaderParserSpec.scala | 21 +++++++++++++++---- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/akka-http-core/src/main/scala/akka/http/javadsl/settings/ParserSettings.scala b/akka-http-core/src/main/scala/akka/http/javadsl/settings/ParserSettings.scala index c2eed8ecaaa..2593aac634b 100644 --- a/akka-http-core/src/main/scala/akka/http/javadsl/settings/ParserSettings.scala +++ b/akka-http-core/src/main/scala/akka/http/javadsl/settings/ParserSettings.scala @@ -65,6 +65,7 @@ abstract class ParserSettings private[akka] () extends BodyPartParser.Settings { def withHeaderValueCacheLimits(newValue: ju.Map[String, Int]): ParserSettings = self.copy(headerValueCacheLimits = newValue.asScala.toMap) def withIncludeTlsSessionInfoHeader(newValue: Boolean): ParserSettings = self.copy(includeTlsSessionInfoHeader = newValue) def withModeledHeaderParsing(newValue: Boolean): ParserSettings = self.copy(modeledHeaderParsing = newValue) + def withIgnoreIllegalHeaderFor(newValue: List[String]): ParserSettings = self.copy(ignoreIllegalHeaderFor = newValue.map(_.toLowerCase).toSet) // special --- diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/settings/ParserSettings.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/settings/ParserSettings.scala index 88184f06be3..dec9130c363 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/settings/ParserSettings.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/settings/ParserSettings.scala @@ -90,6 +90,7 @@ abstract class ParserSettings private[akka] () extends akka.http.javadsl.setting override def withIllegalHeaderWarnings(newValue: Boolean): ParserSettings = self.copy(illegalHeaderWarnings = newValue) override def withIncludeTlsSessionInfoHeader(newValue: Boolean): ParserSettings = self.copy(includeTlsSessionInfoHeader = newValue) override def withModeledHeaderParsing(newValue: Boolean): ParserSettings = self.copy(modeledHeaderParsing = newValue) + override def withIgnoreIllegalHeaderFor(newValue: List[String]): ParserSettings = self.copy(ignoreIllegalHeaderFor = newValue.map(_.toLowerCase).toSet) // overloads for idiomatic Scala use def withUriParsingMode(newValue: Uri.ParsingMode): ParserSettings = self.copy(uriParsingMode = newValue) diff --git a/akka-http-core/src/test/scala/akka/http/impl/engine/parsing/HttpHeaderParserSpec.scala b/akka-http-core/src/test/scala/akka/http/impl/engine/parsing/HttpHeaderParserSpec.scala index 2a2b0a931b7..9e8ff15805e 100644 --- a/akka-http-core/src/test/scala/akka/http/impl/engine/parsing/HttpHeaderParserSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/impl/engine/parsing/HttpHeaderParserSpec.scala @@ -19,13 +19,13 @@ import akka.http.scaladsl.model.headers._ import akka.http.impl.model.parser.CharacterClasses import akka.http.impl.util._ import akka.http.scaladsl.settings.ParserSettings.IllegalResponseHeaderValueProcessingMode -import akka.testkit.TestKit +import akka.testkit.{ EventFilter, TestKit } abstract class HttpHeaderParserSpec(mode: String, newLine: String) extends WordSpec with Matchers with BeforeAndAfterAll { val testConf: Config = ConfigFactory.parseString(""" akka.event-handlers = ["akka.testkit.TestEventListener"] - akka.loglevel = ERROR + akka.loglevel = WARNING akka.http.parsing.max-header-name-length = 60 akka.http.parsing.max-header-value-length = 1000 akka.http.parsing.header-cache.Host = 300""") @@ -254,6 +254,19 @@ abstract class HttpHeaderParserSpec(mode: String, newLine: String) extends WordS parseAndCache(s"User-Agent: hmpf${newLine}x")(s"USER-AGENT: hmpf${newLine}x") shouldEqual RawHeader("User-Agent", "hmpf") parseAndCache(s"X-Forwarded-Host: localhost:8888${newLine}x")(s"X-FORWARDED-Host: localhost:8888${newLine}x") shouldEqual RawHeader("X-Forwarded-Host", "localhost:8888") } + "disables the logging of warning message when set the whitelist for illegal headers" in new TestSetup( + testSetupMode = TestSetupMode.Default, + parserSettings = createParserSettings(system).withIgnoreIllegalHeaderFor(List("Content-Type"))) { + //Illegal header is `Retry-After`. So logged warning message + EventFilter.warning(occurrences = 1).intercept { + parser.parseHeaderLine(ByteString(s"Retry-After: -10${newLine}x"))() + }(system) + + //Illegal header is `Content-Type` and it is in the whitelist. So not logged warning message + EventFilter.warning(occurrences = 0).intercept { + parser.parseHeaderLine(ByteString(s"Content-Type: abc:123${newLine}x"))() + }(system) + } } override def afterAll() = TestKit.shutdownActorSystem(system) @@ -284,13 +297,13 @@ abstract class HttpHeaderParserSpec(mode: String, newLine: String) extends WordS case TestSetupMode.Default ⇒ HttpHeaderParser(parserSettings, system.log) } - private def defaultIllegalHeaderHandler = (info: ErrorInfo) ⇒ system.log.warning(info.formatPretty) + private def defaultIllegalHeaderHandler = (info: ErrorInfo) ⇒ system.log.debug(info.formatPretty) def insert(line: String, value: AnyRef): Unit = if (parser.isEmpty) HttpHeaderParser.insertRemainingCharsAsNewNodes(parser, ByteString(line), value) else HttpHeaderParser.insert(parser, ByteString(line), value) - def parseLine(line: String) = parser.parseHeaderLine(ByteString(line))() → { system.log.warning(parser.resultHeader.getClass.getSimpleName); parser.resultHeader } + def parseLine(line: String) = parser.parseHeaderLine(ByteString(line))() → { system.log.debug(parser.resultHeader.getClass.getSimpleName); parser.resultHeader } def parseAndCache(lineA: String)(lineB: String = lineA): HttpHeader = { val (ixA, headerA) = parseLine(lineA) From 8e82d9bf204dbf7269431ed37b513d2a966dbe31 Mon Sep 17 00:00:00 2001 From: Shinichi Morimoto Date: Mon, 16 Apr 2018 23:57:04 +0900 Subject: [PATCH 16/17] use parseLine #687 --- .../impl/engine/parsing/HttpHeaderParserSpec.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/akka-http-core/src/test/scala/akka/http/impl/engine/parsing/HttpHeaderParserSpec.scala b/akka-http-core/src/test/scala/akka/http/impl/engine/parsing/HttpHeaderParserSpec.scala index 9e8ff15805e..1484efbc572 100644 --- a/akka-http-core/src/test/scala/akka/http/impl/engine/parsing/HttpHeaderParserSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/impl/engine/parsing/HttpHeaderParserSpec.scala @@ -29,7 +29,7 @@ abstract class HttpHeaderParserSpec(mode: String, newLine: String) extends WordS akka.http.parsing.max-header-name-length = 60 akka.http.parsing.max-header-value-length = 1000 akka.http.parsing.header-cache.Host = 300""") - val system = ActorSystem(getClass.getSimpleName, testConf) + implicit val system = ActorSystem(getClass.getSimpleName, testConf) s"The HttpHeaderParser (mode: $mode)" should { "insert the 1st value" in new TestSetup(testSetupMode = TestSetupMode.Unprimed) { @@ -259,13 +259,13 @@ abstract class HttpHeaderParserSpec(mode: String, newLine: String) extends WordS parserSettings = createParserSettings(system).withIgnoreIllegalHeaderFor(List("Content-Type"))) { //Illegal header is `Retry-After`. So logged warning message EventFilter.warning(occurrences = 1).intercept { - parser.parseHeaderLine(ByteString(s"Retry-After: -10${newLine}x"))() - }(system) + parseLine(s"Retry-After: -10${newLine}x") + } //Illegal header is `Content-Type` and it is in the whitelist. So not logged warning message EventFilter.warning(occurrences = 0).intercept { - parser.parseHeaderLine(ByteString(s"Content-Type: abc:123${newLine}x"))() - }(system) + parseLine(s"Content-Type: abc:123${newLine}x") + } } } From 1579427c275842af200c21691fda4089beec4d07 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 7 May 2018 10:13:56 +0900 Subject: [PATCH 17/17] Update reference.conf --- akka-http-core/src/main/resources/reference.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-http-core/src/main/resources/reference.conf b/akka-http-core/src/main/resources/reference.conf index fc7e8b3c11d..5784f91b584 100644 --- a/akka-http-core/src/main/resources/reference.conf +++ b/akka-http-core/src/main/resources/reference.conf @@ -424,7 +424,7 @@ akka.http { # `error-logging-verbosity`. illegal-header-warnings = on - # Sets the whitelist for illegal headers; + # Sets the list of headers for which illegal values will *not* cause warning logs to be emitted; # # Adding a header name to this setting list disables the logging of warning messages in case an incoming message # contains an HTTP header which cannot be parsed into its high-level model class due to incompatible syntax.