From 9907bdad19b7b564f9e8626c1e9e657a0601c93e Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Tue, 4 Aug 2015 11:51:36 +0200 Subject: [PATCH] +htc #16834 apply uri-parsing-mode setting to header parsing as well --- .../impl/engine/parsing/BodyPartParser.scala | 6 ++++-- .../engine/parsing/HttpHeaderParser.scala | 10 +++++----- .../http/impl/model/parser/CommonRules.scala | 3 +++ .../http/impl/model/parser/HeaderParser.scala | 19 ++++++++++++++++--- .../http/impl/model/parser/LinkHeader.scala | 2 +- .../impl/model/parser/SimpleHeaders.scala | 6 +++--- .../impl/model/parser/HttpHeaderSpec.scala | 6 ++++++ .../scaladsl/model/headers/HeaderSpec.scala | 3 ++- 8 files changed, 40 insertions(+), 15 deletions(-) diff --git a/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/BodyPartParser.scala b/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/BodyPartParser.scala index 36d874d90a..f90c82ce9c 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/BodyPartParser.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/engine/parsing/BodyPartParser.scala @@ -270,7 +270,8 @@ private[http] object BodyPartParser { maxHeaderValueLength: Int, maxHeaderCount: Int, illegalHeaderWarnings: Boolean, - headerValueCacheLimit: Int) extends HttpHeaderParser.Settings { + headerValueCacheLimit: Int, + uriParsingMode: Uri.ParsingMode) extends HttpHeaderParser.Settings { require(maxHeaderNameLength > 0, "maxHeaderNameLength must be > 0") require(maxHeaderValueLength > 0, "maxHeaderValueLength must be > 0") require(maxHeaderCount > 0, "maxHeaderCount must be > 0") @@ -284,5 +285,6 @@ private[http] object BodyPartParser { maxHeaderValueLength = 8192, maxHeaderCount = 64, illegalHeaderWarnings = true, - headerValueCacheLimit = 8) + headerValueCacheLimit = 8, + uriParsingMode = Uri.ParsingMode.Relaxed) } 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 6d0194995c..de93b81495 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 @@ -10,7 +10,7 @@ import scala.annotation.tailrec import akka.parboiled2.CharUtils import akka.util.ByteString import akka.http.impl.util._ -import akka.http.scaladsl.model.{ IllegalHeaderException, StatusCodes, HttpHeader, ErrorInfo } +import akka.http.scaladsl.model.{ IllegalHeaderException, StatusCodes, HttpHeader, ErrorInfo, Uri } import akka.http.scaladsl.model.headers.RawHeader import akka.http.impl.model.parser.HeaderParser import akka.http.impl.model.parser.CharacterClasses._ @@ -376,7 +376,7 @@ private[engine] final class HttpHeaderParser private ( private[http] object HttpHeaderParser { import SpecializedHeaderValueParsers._ - trait Settings { + trait Settings extends HeaderParser.Settings { def maxHeaderNameLength: Int def maxHeaderValueLength: Int def headerValueCacheLimit(headerName: String): Int @@ -410,7 +410,7 @@ private[http] object HttpHeaderParser { def prime(parser: HttpHeaderParser): HttpHeaderParser = { val valueParsers: Seq[HeaderValueParser] = HeaderParser.ruleNames.map { name ⇒ - new ModelledHeaderValueParser(name, parser.settings.maxHeaderValueLength, parser.settings.headerValueCacheLimit(name)) + new ModelledHeaderValueParser(name, parser.settings.maxHeaderValueLength, parser.settings.headerValueCacheLimit(name), parser.settings) }(collection.breakOut) def insertInGoodOrder(items: Seq[Any])(startIx: Int = 0, endIx: Int = items.size): Unit = if (endIx - startIx > 0) { @@ -445,13 +445,13 @@ private[http] object HttpHeaderParser { def cachingEnabled = maxValueCount > 0 } - class ModelledHeaderValueParser(headerName: String, maxHeaderValueLength: Int, maxValueCount: Int) + class ModelledHeaderValueParser(headerName: String, maxHeaderValueLength: Int, maxValueCount: Int, settings: HeaderParser.Settings) extends HeaderValueParser(headerName, maxValueCount) { def apply(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) val (headerValue, endIx) = scanHeaderValue(input, valueStart, valueStart + maxHeaderValueLength)() val trimmedHeaderValue = headerValue.trim - val header = HeaderParser.parseFull(headerName, trimmedHeaderValue) match { + val header = HeaderParser.parseFull(headerName, trimmedHeaderValue, settings) 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/CommonRules.scala b/akka-http-core/src/main/scala/akka/http/impl/model/parser/CommonRules.scala index 890a1ca03a..1368cdb201 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/model/parser/CommonRules.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/model/parser/CommonRules.scala @@ -420,5 +420,8 @@ private[parser] trait CommonRules { this: Parser with StringBuilding ⇒ } } } + + def newUriParser(input: ParserInput): UriParser + def uriReference: Rule1[Uri] = rule { runSubParser(newUriParser(_).`URI-reference-pushed`) } } 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 c40cacb515..e45a8f0cea 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 @@ -13,7 +13,7 @@ import akka.http.scaladsl.model._ /** * INTERNAL API. */ -private[http] class HeaderParser(val input: ParserInput) extends Parser with DynamicRuleHandler[HeaderParser, HttpHeader :: HNil] +private[http] class HeaderParser(val input: ParserInput, settings: HeaderParser.Settings = HeaderParser.DefaultSettings) extends Parser with DynamicRuleHandler[HeaderParser, HttpHeader :: HNil] with CommonRules with AcceptCharsetHeader with AcceptEncodingHeader @@ -59,6 +59,8 @@ private[http] class HeaderParser(val input: ParserInput) extends Parser with Dyn case NonFatal(e) ⇒ Left(ErrorInfo.fromCompoundString(e.getMessage)) } def ruleNotFound(ruleName: String): Result = throw HeaderParser.RuleNotFoundException + + def newUriParser(input: ParserInput): UriParser = new UriParser(input, uriParsingMode = settings.uriParsingMode) } /** @@ -67,10 +69,10 @@ 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 = { + def parseFull(headerName: String, value: String, settings: Settings = DefaultSettings): 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) + val parser = new HeaderParser(v, settings) dispatch(parser, headerName) match { case r @ Right(_) if parser.cursor == v.length ⇒ r case r @ Right(_) ⇒ @@ -136,4 +138,15 @@ private[http] object HeaderParser { "user-agent", "www-authenticate", "x-forwarded-for") + + trait Settings { + def uriParsingMode: Uri.ParsingMode + } + def Settings(uriParsingMode: Uri.ParsingMode): Settings = { + val _uriParsingMode = uriParsingMode + new Settings { + def uriParsingMode: Uri.ParsingMode = _uriParsingMode + } + } + val DefaultSettings: Settings = Settings(Uri.ParsingMode.Relaxed) } \ No newline at end of file diff --git a/akka-http-core/src/main/scala/akka/http/impl/model/parser/LinkHeader.scala b/akka-http-core/src/main/scala/akka/http/impl/model/parser/LinkHeader.scala index 2c9a41f880..a629e0bb5d 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/model/parser/LinkHeader.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/model/parser/LinkHeader.scala @@ -49,7 +49,7 @@ private[parser] trait LinkHeader { this: Parser with CommonRules with CommonActi ////////////////////////////// helpers /////////////////////////////////// def UriReference(terminationChar: Char) = rule { - capture(oneOrMore(!terminationChar ~ VCHAR)) ~> (new UriParser(_).parseUriReference()) + capture(oneOrMore(!terminationChar ~ VCHAR)) ~> (newUriParser(_).parseUriReference()) } def URI = rule { 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 f8dd2f8ee7..b62155fa9d 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 @@ -121,7 +121,7 @@ private[parser] trait SimpleHeaders { this: Parser with CommonRules with CommonA // Also: an empty hostnames with a non-empty port value (as in `Host: :8080`) are *allowed*, // see http://trac.tools.ietf.org/wg/httpbis/trac/ticket/92 def host = rule { - runSubParser(new UriParser(_).`hostAndPort-pushed`) ~ EOI ~> (Host(_, _)) + runSubParser(newUriParser(_).`hostAndPort-pushed`) ~ EOI ~> (Host(_, _)) } // http://tools.ietf.org/html/rfc7232#section-3.1 @@ -150,7 +150,7 @@ private[parser] trait SimpleHeaders { this: Parser with CommonRules with CommonA // http://tools.ietf.org/html/rfc7231#section-7.1.2 def location = rule { - runSubParser(new UriParser(_).`URI-reference-pushed`) ~ EOI ~> (Location(_)) + uriReference ~ EOI ~> (Location(_)) } // http://tools.ietf.org/html/rfc6454#section-7 @@ -171,7 +171,7 @@ private[parser] trait SimpleHeaders { this: Parser with CommonRules with CommonA def referer = rule { // we are bit more relaxed than the spec here by also parsing a potential fragment // but catch it in the `Referer` instance validation (with a `require` in the constructor) - runSubParser(new UriParser(_).`URI-reference-pushed`) ~ EOI ~> (Referer(_)) + uriReference ~ EOI ~> (Referer(_)) } // http://tools.ietf.org/html/rfc7231#section-7.4.2 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 8d67fb7132..bf1a3a872f 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 @@ -574,6 +574,12 @@ class HttpHeaderSpec extends FreeSpec with Matchers { "resolve obs-fold occurrences" in { parse("Foo", "b\r\n\ta \r\n r") shouldEqual ParsingResult.Ok(RawHeader("Foo", "b a r"), Nil) } + + "parse with custom uri parsing mode" in { + val targetUri = Uri("http://example.org/?abc=def=ghi", Uri.ParsingMode.RelaxedWithRawQuery) + HeaderParser.parseFull("location", "http://example.org/?abc=def=ghi", HeaderParser.Settings(uriParsingMode = Uri.ParsingMode.RelaxedWithRawQuery)) shouldEqual + Right(Location(targetUri)) + } } implicit class TestLine(line: 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 1535c3a136..f802f2eab4 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 @@ -4,9 +4,10 @@ package akka.http.scaladsl.model.headers -import akka.http.scaladsl.model._ import org.scalatest.{ FreeSpec, MustMatchers } +import akka.http.scaladsl.model._ + class HeaderSpec extends FreeSpec with MustMatchers { "ModeledCompanion should" - { "provide parseFromValueString method" - {