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 5d7b0a4bef..6d0194995c 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 @@ -451,7 +451,7 @@ private[http] object HttpHeaderParser { // TODO: optimize by running the header value parser directly on the input ByteString (rather than an extracted String) val (headerValue, endIx) = scanHeaderValue(input, valueStart, valueStart + maxHeaderValueLength)() val trimmedHeaderValue = headerValue.trim - val header = HeaderParser.dispatch(new HeaderParser(trimmedHeaderValue), headerName) match { + val header = HeaderParser.parseFull(headerName, trimmedHeaderValue) match { case Right(h) ⇒ h case Left(error) ⇒ onIllegalHeader(error.withSummaryPrepended(s"Illegal '$headerName' header")) diff --git a/akka-http-core/src/main/scala/akka/http/impl/model/parser/HeaderParser.scala b/akka-http-core/src/main/scala/akka/http/impl/model/parser/HeaderParser.scala index b509523a1c..c40cacb515 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/model/parser/HeaderParser.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/model/parser/HeaderParser.scala @@ -67,6 +67,19 @@ private[http] class HeaderParser(val input: ParserInput) extends Parser with Dyn private[http] object HeaderParser { object RuleNotFoundException extends SingletonException + def parseFull(headerName: String, value: String): HeaderParser#Result = { + import akka.parboiled2.EOI + val v = value + EOI // this makes sure the parser isn't broken even if there's no trailing garbage in this value + val parser = new HeaderParser(v) + dispatch(parser, headerName) match { + case r @ Right(_) if parser.cursor == v.length ⇒ r + case r @ Right(_) ⇒ + Left(ErrorInfo("Header parsing error", + s"Rule for $headerName accepted trailing garbage. Is the parser missing a trailing EOI?")) + case Left(e) ⇒ Left(e.copy(summary = e.summary.filterNot(_ == EOI), detail = e.detail.filterNot(_ == EOI))) + } + } + val (dispatch, ruleNames) = DynamicRuleDispatch[HeaderParser, HttpHeader :: HNil]( "accept", "accept-charset", diff --git a/akka-http-core/src/main/scala/akka/http/impl/model/parser/SimpleHeaders.scala b/akka-http-core/src/main/scala/akka/http/impl/model/parser/SimpleHeaders.scala index bbf9912606..f8dd2f8ee7 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/model/parser/SimpleHeaders.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/model/parser/SimpleHeaders.scala @@ -37,7 +37,7 @@ private[parser] trait SimpleHeaders { this: Parser with CommonRules with CommonA // http://www.w3.org/TR/cors/#access-control-allow-origin-response-header def `access-control-allow-origin` = rule( - ws('*') ~ push(`Access-Control-Allow-Origin`.`*`) + ws('*') ~ EOI ~ push(`Access-Control-Allow-Origin`.`*`) | `origin-list-or-null` ~ EOI ~> (origins ⇒ `Access-Control-Allow-Origin`.forRange(HttpOriginRange(origins: _*)))) // http://www.w3.org/TR/cors/#access-control-expose-headers-response-header @@ -108,7 +108,7 @@ private[parser] trait SimpleHeaders { this: Parser with CommonRules with CommonA // http://tools.ietf.org/html/rfc7231#section-5.1.1 def `expect` = rule { - ignoreCase("100-continue") ~ OWS ~ push(Expect.`100-continue`) + ignoreCase("100-continue") ~ OWS ~ EOI ~ push(Expect.`100-continue`) } // http://tools.ietf.org/html/rfc7234#section-5.3 @@ -126,7 +126,7 @@ private[parser] trait SimpleHeaders { this: Parser with CommonRules with CommonA // http://tools.ietf.org/html/rfc7232#section-3.1 def `if-match` = rule( - ws('*') ~ push(`If-Match`.`*`) + ws('*') ~ EOI ~ push(`If-Match`.`*`) | oneOrMore(`entity-tag`).separatedBy(listSep) ~ EOI ~> (tags ⇒ `If-Match`(EntityTagRange(tags: _*)))) // http://tools.ietf.org/html/rfc7232#section-3.3 @@ -134,7 +134,7 @@ private[parser] trait SimpleHeaders { this: Parser with CommonRules with CommonA // http://tools.ietf.org/html/rfc7232#section-3.2 def `if-none-match` = rule { - ws('*') ~ push(`If-None-Match`.`*`) | + ws('*') ~ EOI ~ push(`If-None-Match`.`*`) | oneOrMore(`entity-tag`).separatedBy(listSep) ~ EOI ~> (tags ⇒ `If-None-Match`(EntityTagRange(tags: _*))) } @@ -175,7 +175,7 @@ private[parser] trait SimpleHeaders { this: Parser with CommonRules with CommonA } // http://tools.ietf.org/html/rfc7231#section-7.4.2 - def server = rule { products ~> (Server(_)) } + def server = rule { products ~ EOI ~> (Server(_)) } // http://tools.ietf.org/html/rfc7230#section-3.3.1 def `transfer-encoding` = rule { @@ -189,7 +189,7 @@ private[parser] trait SimpleHeaders { this: Parser with CommonRules with CommonA // http://tools.ietf.org/html/rfc7230#section-6.7 def upgrade = rule { - oneOrMore(protocol).separatedBy(listSep) ~> (Upgrade(_)) + oneOrMore(protocol).separatedBy(listSep) ~ EOI ~> (Upgrade(_)) } def protocol = rule { @@ -197,7 +197,7 @@ private[parser] trait SimpleHeaders { this: Parser with CommonRules with CommonA } // http://tools.ietf.org/html/rfc7231#section-5.5.3 - def `user-agent` = rule { products ~> (`User-Agent`(_)) } + def `user-agent` = rule { products ~ EOI ~> (`User-Agent`(_)) } // http://tools.ietf.org/html/rfc7235#section-4.1 def `www-authenticate` = rule { diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpHeader.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpHeader.scala index 0ffc81ecbe..79b71db1ca 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpHeader.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/HttpHeader.scala @@ -60,7 +60,7 @@ object HttpHeader { parser.`header-field-value`.run() match { case Success(preProcessedValue) ⇒ try { - HeaderParser.dispatch(new HeaderParser(preProcessedValue), name.toLowerCase) match { + HeaderParser.parseFull(name.toLowerCase, preProcessedValue) match { case Right(header) ⇒ ParsingResult.Ok(header, Nil) case Left(info) ⇒ val errors = info.withSummaryPrepended(s"Illegal HTTP header '$name'") :: Nil diff --git a/akka-http-core/src/test/scala/akka/http/impl/model/parser/HttpHeaderSpec.scala b/akka-http-core/src/test/scala/akka/http/impl/model/parser/HttpHeaderSpec.scala index 5de9aaf4a7..3f3e9dd77c 100644 --- a/akka-http-core/src/test/scala/akka/http/impl/model/parser/HttpHeaderSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/impl/model/parser/HttpHeaderSpec.scala @@ -4,6 +4,7 @@ package akka.http.impl.model.parser +import akka.http.scaladsl.model.HttpHeader.ParsingResult import org.scalatest.{ Matchers, FreeSpec } import org.scalatest.matchers.{ Matcher, MatchResult } import akka.http.impl.util._