!htt #19388 Chanring x-real-ip from raw to real modeled header

* Allows for parsing rules to be applied to vet the ip
* Removed last cases of code where the String variant of apply on RemoteAddress is used
This commit is contained in:
Chris Baxter 2016-02-10 12:08:09 -05:00
parent 7850ad36a5
commit 6d3bb94d09
10 changed files with 84 additions and 10 deletions

View file

@ -0,0 +1,16 @@
/**
* Copyright (C) 2009-2016 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.model.headers;
/**
* Model for the `X-Real-Ip` header.
*/
public abstract class XRealIp extends akka.http.scaladsl.model.HttpHeader {
public abstract akka.http.javadsl.model.RemoteAddress address();
public static XRealIp create(akka.http.javadsl.model.RemoteAddress address) {
return new akka.http.scaladsl.model.headers.X$minusReal$minusIp(((akka.http.scaladsl.model.RemoteAddress) address));
}
}

View file

@ -154,7 +154,8 @@ private[http] object HeaderParser {
"upgrade", "upgrade",
"user-agent", "user-agent",
"www-authenticate", "www-authenticate",
"x-forwarded-for") "x-forwarded-for",
"x-real-ip")
abstract class Settings { abstract class Settings {
def uriParsingMode: Uri.ParsingMode def uriParsingMode: Uri.ParsingMode

View file

@ -221,4 +221,9 @@ private[parser] trait SimpleHeaders { this: Parser with CommonRules with CommonA
def addr = rule { (`ip-v4-address` | `ip-v6-address`) ~> (RemoteAddress(_)) | "unknown" ~ push(RemoteAddress.Unknown) } def addr = rule { (`ip-v4-address` | `ip-v6-address`) ~> (RemoteAddress(_)) | "unknown" ~ push(RemoteAddress.Unknown) }
rule { oneOrMore(addr).separatedBy(listSep) ~ EOI ~> (`X-Forwarded-For`(_)) } rule { oneOrMore(addr).separatedBy(listSep) ~ EOI ~> (`X-Forwarded-For`(_)) }
} }
def `x-real-ip` = rule {
(`ip-v4-address` | `ip-v6-address`) ~ EOI ~> (b `X-Real-Ip`(RemoteAddress(b)))
}
} }

View file

@ -910,3 +910,10 @@ final case class `X-Forwarded-For`(addresses: immutable.Seq[RemoteAddress]) exte
/** Java API */ /** Java API */
def getAddresses: Iterable[jm.RemoteAddress] = addresses.asJava def getAddresses: Iterable[jm.RemoteAddress] = addresses.asJava
} }
object `X-Real-Ip` extends ModeledCompanion[`X-Real-Ip`]
final case class `X-Real-Ip`(address:RemoteAddress) extends jm.headers.XRealIp
with RequestHeader {
def renderValue[R <: Rendering](r: R): r.type = r ~~ address
protected def companion = `X-Real-Ip`
}

View file

@ -132,6 +132,10 @@ class HttpHeaderParserSpec extends WordSpec with Matchers with BeforeAndAfterAll
parseAndCache("X-Forwarded-For: 1.2.3.4, akka.io\r\nx")() shouldEqual RawHeader("x-forwarded-for", "1.2.3.4, akka.io") parseAndCache("X-Forwarded-For: 1.2.3.4, akka.io\r\nx")() shouldEqual RawHeader("x-forwarded-for", "1.2.3.4, akka.io")
} }
"parse and cache an X-Real-Ip with a hostname as it's value as a RawHeader" in new TestSetup() {
parseAndCache("X-Real-Ip: akka.io\r\nx")() shouldEqual RawHeader("x-real-ip", "akka.io")
}
"parse and cache a raw header" in new TestSetup(primed = false) { "parse and cache a raw header" in new TestSetup(primed = false) {
insert("hello: bob", 'Hello) insert("hello: bob", 'Hello)
val (ixA, headerA) = parseLine("Fancy-Pants: foo\r\nx") val (ixA, headerA) = parseLine("Fancy-Pants: foo\r\nx")

View file

@ -581,6 +581,40 @@ class HttpHeaderSpec extends FreeSpec with Matchers {
"1.2.3.4, akka.io\n ^") "1.2.3.4, akka.io\n ^")
} }
"X-Real-Ip" in {
"X-Real-Ip: 1.2.3.4" =!= `X-Real-Ip`(remoteAddress("1.2.3.4"))
"X-Real-Ip: 2001:db8:cafe:0:0:0:0:17" =!= `X-Real-Ip`(remoteAddress("2001:db8:cafe:0:0:0:0:17"))
"X-Real-Ip: 1234:5678:9abc:def1:2345:6789:abcd:ef00" =!= `X-Real-Ip`(remoteAddress("1234:5678:9abc:def1:2345:6789:abcd:ef00"))
"X-Real-Ip: 1234:567:9a:d:2:67:abc:ef00" =!= `X-Real-Ip`(remoteAddress("1234:567:9a:d:2:67:abc:ef00"))
"X-Real-Ip: 2001:db8:85a3::8a2e:370:7334" =!=> "2001:db8:85a3:0:0:8a2e:370:7334"
"X-Real-Ip: 1:2:3:4:5:6:7:8" =!= `X-Real-Ip`(remoteAddress("1:2:3:4:5:6:7:8"))
"X-Real-Ip: ::2:3:4:5:6:7:8" =!=> "0:2:3:4:5:6:7:8"
"X-Real-Ip: ::3:4:5:6:7:8" =!=> "0:0:3:4:5:6:7:8"
"X-Real-Ip: ::4:5:6:7:8" =!=> "0:0:0:4:5:6:7:8"
"X-Real-Ip: ::5:6:7:8" =!=> "0:0:0:0:5:6:7:8"
"X-Real-Ip: ::6:7:8" =!=> "0:0:0:0:0:6:7:8"
"X-Real-Ip: ::7:8" =!=> "0:0:0:0:0:0:7:8"
"X-Real-Ip: ::8" =!=> "0:0:0:0:0:0:0:8"
"X-Real-Ip: 1:2:3:4:5:6:7::" =!=> "1:2:3:4:5:6:7:0"
"X-Real-Ip: 1:2:3:4:5:6::" =!=> "1:2:3:4:5:6:0:0"
"X-Real-Ip: 1:2:3:4:5::" =!=> "1:2:3:4:5:0:0:0"
"X-Real-Ip: 1:2:3:4::" =!=> "1:2:3:4:0:0:0:0"
"X-Real-Ip: 1:2:3::" =!=> "1:2:3:0:0:0:0:0"
"X-Real-Ip: 1:2::" =!=> "1:2:0:0:0:0:0:0"
"X-Real-Ip: 1::" =!=> "1:0:0:0:0:0:0:0"
"X-Real-Ip: 1::3:4:5:6:7:8" =!=> "1:0:3:4:5:6:7:8"
"X-Real-Ip: 1:2::4:5:6:7:8" =!=> "1:2:0:4:5:6:7:8"
"X-Real-Ip: 1:2:3::5:6:7:8" =!=> "1:2:3:0:5:6:7:8"
"X-Real-Ip: 1:2:3:4::6:7:8" =!=> "1:2:3:4:0:6:7:8"
"X-Real-Ip: 1:2:3:4:5::7:8" =!=> "1:2:3:4:5:0:7:8"
"X-Real-Ip: 1:2:3:4:5:6::8" =!=> "1:2:3:4:5:6:0:8"
"X-Real-Ip: ::" =!=> "0:0:0:0:0:0:0:0"
"X-Real-Ip: akka.io" =!=
ErrorInfo(
"Illegal HTTP header 'X-Real-Ip': Invalid input 'k', expected HEXDIG, h8, ':', ch16o or cc (line 1, column 2)",
"akka.io\n ^")
}
"RawHeader" in { "RawHeader" in {
"X-Space-Ranger: no, this rock!" =!= RawHeader("X-Space-Ranger", "no, this rock!") "X-Space-Ranger: no, this rock!" =!= RawHeader("X-Space-Ranger", "no, this rock!")
} }

View file

@ -6,7 +6,7 @@ package akka.http.scaladsl.model.headers
import akka.http.impl.util._ import akka.http.impl.util._
import org.scalatest._ import org.scalatest._
import java.net.InetAddress
import akka.http.scaladsl.model._ import akka.http.scaladsl.model._
class HeaderSpec extends FreeSpec with Matchers { class HeaderSpec extends FreeSpec with Matchers {
@ -99,7 +99,8 @@ class HeaderSpec extends FreeSpec with Matchers {
`Transfer-Encoding`(TransferEncodings.chunked), `Transfer-Encoding`(TransferEncodings.chunked),
Upgrade(Vector(UpgradeProtocol("HTTP", Some("2.0")))), Upgrade(Vector(UpgradeProtocol("HTTP", Some("2.0")))),
`User-Agent`("Akka HTTP Client 2.4"), `User-Agent`("Akka HTTP Client 2.4"),
`X-Forwarded-For`(RemoteAddress("192.168.0.1"))) `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("192.168.0.1"))),
`X-Real-Ip`(RemoteAddress(InetAddress.getByName("192.168.1.1"))))
requestHeaders.foreach { header requestHeaders.foreach { header
header shouldBe 'renderInRequests header shouldBe 'renderInRequests

View file

@ -13,8 +13,11 @@ import akka.http.javadsl.server.RequestVals;
import akka.http.javadsl.server.Unmarshallers; import akka.http.javadsl.server.Unmarshallers;
import akka.http.javadsl.testkit.JUnitRouteTest; import akka.http.javadsl.testkit.JUnitRouteTest;
import akka.http.javadsl.testkit.TestRoute; import akka.http.javadsl.testkit.TestRoute;
import org.junit.Test; import org.junit.Test;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class RequestValTest extends JUnitRouteTest { public class RequestValTest extends JUnitRouteTest {
@ -62,16 +65,16 @@ public class RequestValTest extends JUnitRouteTest {
} }
@Test @Test
public void testClientIpExtraction() { public void testClientIpExtraction() throws UnknownHostException{
TestRoute route = testRoute(completeWithValueToString(RequestVals.clientIP())); TestRoute route = testRoute(completeWithValueToString(RequestVals.clientIP()));
route route
.run(HttpRequest.create().addHeader(XForwardedFor.create(RemoteAddress.create("127.0.0.2")))) .run(HttpRequest.create().addHeader(XForwardedFor.create(RemoteAddress.create(InetAddress.getByName("127.0.0.2")))))
.assertStatusCode(200) .assertStatusCode(200)
.assertEntity("127.0.0.2"); .assertEntity("127.0.0.2");
route route
.run(HttpRequest.create().addHeader(akka.http.javadsl.model.headers.RemoteAddress.create(RemoteAddress.create("127.0.0.3")))) .run(HttpRequest.create().addHeader(akka.http.javadsl.model.headers.RemoteAddress.create(RemoteAddress.create(InetAddress.getByName("127.0.0.3")))))
.assertStatusCode(200) .assertStatusCode(200)
.assertEntity("127.0.0.3"); .assertEntity("127.0.0.3");

View file

@ -10,22 +10,23 @@ import scala.concurrent.duration._
import scala.util.Try import scala.util.Try
import akka.http.scaladsl.model._ import akka.http.scaladsl.model._
import headers._ import headers._
import java.net.InetAddress
class MiscDirectivesSpec extends RoutingSpec { class MiscDirectivesSpec extends RoutingSpec {
"the extractClientIP directive" should { "the extractClientIP directive" should {
"extract from a X-Forwarded-For header" in { "extract from a X-Forwarded-For header" in {
Get() ~> addHeaders(`X-Forwarded-For`("2.3.4.5"), RawHeader("x-real-ip", "1.2.3.4")) ~> { Get() ~> addHeaders(`X-Forwarded-For`(remoteAddress("2.3.4.5")), RawHeader("x-real-ip", "1.2.3.4")) ~> {
extractClientIP { echoComplete } extractClientIP { echoComplete }
} ~> check { responseAs[String] shouldEqual "2.3.4.5" } } ~> check { responseAs[String] shouldEqual "2.3.4.5" }
} }
"extract from a Remote-Address header" in { "extract from a Remote-Address header" in {
Get() ~> addHeaders(RawHeader("x-real-ip", "1.2.3.4"), `Remote-Address`(RemoteAddress("5.6.7.8"))) ~> { Get() ~> addHeaders(`X-Real-Ip`(remoteAddress("1.2.3.4")), `Remote-Address`(remoteAddress("5.6.7.8"))) ~> {
extractClientIP { echoComplete } extractClientIP { echoComplete }
} ~> check { responseAs[String] shouldEqual "5.6.7.8" } } ~> check { responseAs[String] shouldEqual "5.6.7.8" }
} }
"extract from a X-Real-IP header" in { "extract from a X-Real-IP header" in {
Get() ~> addHeader(RawHeader("x-real-ip", "1.2.3.4")) ~> { Get() ~> addHeader(`X-Real-Ip`(remoteAddress("1.2.3.4"))) ~> {
extractClientIP { echoComplete } extractClientIP { echoComplete }
} ~> check { responseAs[String] shouldEqual "1.2.3.4" } } ~> check { responseAs[String] shouldEqual "1.2.3.4" }
} }
@ -85,4 +86,6 @@ class MiscDirectivesSpec extends RoutingSpec {
} }
} }
} }
def remoteAddress(ip: String) = RemoteAddress(InetAddress.getByName(ip))
} }

View file

@ -66,7 +66,7 @@ object MiscDirectives extends MiscDirectives {
private val _extractClientIP: Directive1[RemoteAddress] = private val _extractClientIP: Directive1[RemoteAddress] =
headerValuePF { case `X-Forwarded-For`(Seq(address, _*)) address } | headerValuePF { case `X-Forwarded-For`(Seq(address, _*)) address } |
headerValuePF { case `Remote-Address`(address) address } | headerValuePF { case `Remote-Address`(address) address } |
headerValuePF { case h if h.is("x-real-ip") RemoteAddress(h.value) } headerValuePF { case `X-Real-Ip`(address) address }
private val _requestEntityEmpty: Directive0 = private val _requestEntityEmpty: Directive0 =
extract(_.request.entity.isKnownEmpty).flatMap(if (_) pass else reject) extract(_.request.entity.isKnownEmpty).flatMap(if (_) pass else reject)