+htc #16834 apply uri-parsing-mode setting to header parsing as well

This commit is contained in:
Johannes Rudolph 2015-08-04 11:51:36 +02:00
parent 880a6b64d0
commit 9907bdad19
8 changed files with 40 additions and 15 deletions

View file

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

View file

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

View file

@ -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`) }
}

View file

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

View file

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

View file

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

View file

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

View file

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