From d6bc3ec4c5720c76bc55387b04371ad572c42940 Mon Sep 17 00:00:00 2001 From: Mathias Date: Tue, 17 Feb 2015 17:08:44 +0100 Subject: [PATCH] +hco #16822 add model for `Referer` header --- .../akka/http/model/japi/headers/Referer.java | 19 +++++++++++++++++++ .../akka/http/model/headers/headers.scala | 13 +++++++++++++ .../akka/http/model/parser/HeaderParser.scala | 2 +- .../http/model/parser/SimpleHeaders.scala | 7 +++++++ .../engine/parsing/HttpHeaderParserSpec.scala | 11 ++++++----- .../http/model/parser/HttpHeaderSpec.scala | 7 +++++++ 6 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 akka-http-core/src/main/java/akka/http/model/japi/headers/Referer.java diff --git a/akka-http-core/src/main/java/akka/http/model/japi/headers/Referer.java b/akka-http-core/src/main/java/akka/http/model/japi/headers/Referer.java new file mode 100644 index 0000000000..f8095a12d6 --- /dev/null +++ b/akka-http-core/src/main/java/akka/http/model/japi/headers/Referer.java @@ -0,0 +1,19 @@ +/** + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.http.model.japi.headers; + +import akka.http.model.japi.Uri; + +/** + * Model for the `Referer` header. + * Specification: http://tools.ietf.org/html/rfc7231#section-5.5.2 + */ +public abstract class Referer extends akka.http.model.HttpHeader { + public abstract Uri getUri(); + + public static Referer create(Uri uri) { + return new akka.http.model.headers.Referer(akka.http.model.japi.Util.convertUriToScala(uri)); + } +} diff --git a/akka-http-core/src/main/scala/akka/http/model/headers/headers.scala b/akka-http-core/src/main/scala/akka/http/model/headers/headers.scala index 1e5204b18c..3c51b4545d 100644 --- a/akka-http-core/src/main/scala/akka/http/model/headers/headers.scala +++ b/akka-http-core/src/main/scala/akka/http/model/headers/headers.scala @@ -537,6 +537,19 @@ final case class `Remote-Address`(address: RemoteAddress) extends japi.headers.R protected def companion = `Remote-Address` } +// http://tools.ietf.org/html/rfc7231#section-5.5.2 +object Referer extends ModeledCompanion +final case class Referer(uri: Uri) extends japi.headers.Referer with ModeledHeader { + require(uri.fragment == None, "Referer header URI must not contain a fragment") + require(uri.authority.userinfo.isEmpty, "Referer header URI must not contain a userinfo component") + + def renderValue[R <: Rendering](r: R): r.type = { import UriRendering.UriRenderer; r ~~ uri } + protected def companion = Referer + + /** Java API */ + def getUri: akka.http.model.japi.Uri = uri.asJava +} + // http://tools.ietf.org/html/rfc7231#section-7.4.2 object Server extends ModeledCompanion { def apply(products: String): Server = apply(ProductVersion.parseMultiple(products)) diff --git a/akka-http-core/src/main/scala/akka/http/model/parser/HeaderParser.scala b/akka-http-core/src/main/scala/akka/http/model/parser/HeaderParser.scala index 38073c510a..992fc6d7aa 100644 --- a/akka-http-core/src/main/scala/akka/http/model/parser/HeaderParser.scala +++ b/akka-http-core/src/main/scala/akka/http/model/parser/HeaderParser.scala @@ -96,10 +96,10 @@ object HeaderParser { "link", "location", "origin", - "range", "proxy-authenticate", "proxy-authorization", "range", + "referer", "server", "set-cookie", "transfer-encoding", diff --git a/akka-http-core/src/main/scala/akka/http/model/parser/SimpleHeaders.scala b/akka-http-core/src/main/scala/akka/http/model/parser/SimpleHeaders.scala index 8aa73022d5..67fc467c44 100644 --- a/akka-http-core/src/main/scala/akka/http/model/parser/SimpleHeaders.scala +++ b/akka-http-core/src/main/scala/akka/http/model/parser/SimpleHeaders.scala @@ -168,6 +168,13 @@ private[parser] trait SimpleHeaders { this: Parser with CommonRules with CommonA // http://tools.ietf.org/html/rfc7233#section-3.1 def `range` = rule { `byte-ranges-specifier` /*| `other-ranges-specifier` */ ~ EOI ~> (Range(_, _)) } + // http://tools.ietf.org/html/rfc7231#section-5.5.2 + 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(_)) + } + // http://tools.ietf.org/html/rfc7231#section-7.4.2 def server = rule { products ~> (Server(_)) } diff --git a/akka-http-core/src/test/scala/akka/http/engine/parsing/HttpHeaderParserSpec.scala b/akka-http-core/src/test/scala/akka/http/engine/parsing/HttpHeaderParserSpec.scala index 85551512bd..91992dde19 100644 --- a/akka-http-core/src/test/scala/akka/http/engine/parsing/HttpHeaderParserSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/engine/parsing/HttpHeaderParserSpec.scala @@ -153,7 +153,8 @@ class HttpHeaderParserSpec extends WordSpec with Matchers with BeforeAndAfterAll | | ┌─o-r-i-g-i-n-:- (origin) | | | ┌─e-n-t-i-c-a-t-e-:- (proxy-authenticate) | | ┌─p-r-o-x-y---a-u-t-h-o-r-i-z-a-t-i-o-n-:- (proxy-authorization) - | | | └─r-a-n-g-e-:- (range) + | | | | ┌─a-n-g-e-:- (range) + | | | └─r-e-f-e-r-e-r-:- (referer) | └─s-e-r-v-e-r-:- (server) | | └─t---c-o-o-k-i-e-:- (set-cookie) | | ┌─t-r-a-n-s-f-e-r---e-n-c-o-d-i-n-g-:- (transfer-encoding) @@ -162,7 +163,7 @@ class HttpHeaderParserSpec extends WordSpec with Matchers with BeforeAndAfterAll | └─x---f-o-r-w-a-r-d-e-d---f-o-r-:- (x-forwarded-for) |""" -> parser.formatTrie } - parser.formatSizes shouldEqual "602 nodes, 42 branchData rows, 57 values" + parser.formatSizes shouldEqual "610 nodes, 43 branchData rows, 58 values" parser.contentHistogram shouldEqual Map("connection" -> 3, "Content-Length" -> 1, "accept" -> 2, "cache-control" -> 2, "expect" -> 1) } @@ -234,8 +235,8 @@ class HttpHeaderParserSpec extends WordSpec with Matchers with BeforeAndAfterAll } randomHeaders.take(300).foldLeft(0) { case (acc, rawHeader) ⇒ acc + parseAndCache(rawHeader.toString + "\r\nx", rawHeader) - } shouldEqual 99 // number of cache hits - parser.formatSizes shouldEqual "3040 nodes, 115 branchData rows, 255 values" + } shouldEqual 98 // number of cache hits + parser.formatSizes shouldEqual "3036 nodes, 115 branchData rows, 255 values" } "continue parsing modelled headers even if the overall cache capacity is reached" in new TestSetup() { @@ -247,7 +248,7 @@ class HttpHeaderParserSpec extends WordSpec with Matchers with BeforeAndAfterAll randomHostHeaders.take(300).foldLeft(0) { case (acc, header) ⇒ acc + parseAndCache(header.toString + "\r\nx", header) } shouldEqual 12 // number of cache hits - parser.formatSizes shouldEqual "766 nodes, 51 branchData rows, 69 values" + parser.formatSizes shouldEqual "774 nodes, 52 branchData rows, 70 values" } "continue parsing raw headers even if the header-specific cache capacity is reached" in new TestSetup() { diff --git a/akka-http-core/src/test/scala/akka/http/model/parser/HttpHeaderSpec.scala b/akka-http-core/src/test/scala/akka/http/model/parser/HttpHeaderSpec.scala index b863cd0071..2e44417058 100644 --- a/akka-http-core/src/test/scala/akka/http/model/parser/HttpHeaderSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/model/parser/HttpHeaderSpec.scala @@ -339,6 +339,13 @@ class HttpHeaderSpec extends FreeSpec with Matchers { `Proxy-Authorization`(GenericHttpCredentials("Fancy", Map("yes" -> "no", "nonce" -> """4\2"""))) } + "Referer" in { + "Referer: https://spray.io/secure" =!= Referer(Uri("https://spray.io/secure")) + "Referer: /en-us/default.aspx?foo=bar" =!= Referer(Uri("/en-us/default.aspx?foo=bar")) + "Referer: https://akka.io/#sec" =!= ErrorInfo("Illegal HTTP header 'Referer': requirement failed", + "Referer header URI must not contain a fragment") + } + "Server" in { "Server: as fghf.fdf/xx" =!= `Server`(Vector(ProductVersion("as"), ProductVersion("fghf.fdf", "xx"))) }