Permalink
Browse files

! http: remodel `HttpIp` to `RemoteAddress`, fixes #638

- The `HttpIp` type is replaced with `RemoteAddress` which provides first-class support for "unknown" addresses.
- The `X-Forwarded-For::ips` member is renamed to `addresses`
- The `Remote-Address::ip` member is renamed to `address`
  • Loading branch information...
sirthias committed Oct 28, 2013
1 parent be5de26 commit 443b0d898d3b2fc8cb00aba58fffd8dad7d32e90
@@ -5,11 +5,11 @@ import spray.http.HttpHeaders.`Remote-Address`
class MiscDirectivesExamplesSpec extends DirectivesSpec {
"clientIP-example" in {
val route = clientIP { ip =>
- complete(s"Client's ip is '${ip.ip.getHostAddress}'")
+ complete("Client's ip is " + ip.toOption.map(_.getHostAddress).getOrElse("unknown"))
}
Get("/").withHeaders(`Remote-Address`("192.168.3.12")) ~> route ~> check {
- responseAs[String] === "Client's ip is '192.168.3.12'"
+ responseAs[String] === "Client's ip is 192.168.3.12"
}
}
}
@@ -23,7 +23,7 @@ import HttpHeaders._
private object RemoteAddressHeaderSupport extends PipelineStage {
def apply(context: PipelineContext, commandPL: CPL, eventPL: EPL): Pipelines =
new Pipelines {
- val raHeader = `Remote-Address`(context.remoteAddress.getAddress)
+ val raHeader = `Remote-Address`(RemoteAddress.IP(context.remoteAddress.getAddress))
def appendHeader(request: HttpRequest): HttpRequest = request.mapHeaders(raHeader :: _)
val commandPipeline = commandPL
@@ -305,9 +305,11 @@ object HttpHeaders {
protected def companion = `Raw-Request-URI`
}
- object `Remote-Address` extends ModeledCompanion
- case class `Remote-Address`(ip: HttpIp) extends ModeledHeader {
- def renderValue[R <: Rendering](r: R): r.type = r ~~ ip
+ object `Remote-Address` extends ModeledCompanion {
+ def apply(address: String): `Remote-Address` = apply(RemoteAddress(address))
+ }
+ case class `Remote-Address`(address: RemoteAddress) extends ModeledHeader {
+ def renderValue[R <: Rendering](r: R): r.type = r ~~ address
protected def companion = `Remote-Address`
}
@@ -364,12 +366,13 @@ object HttpHeaders {
}
object `X-Forwarded-For` extends ModeledCompanion {
- def apply(first: HttpIp, more: HttpIp*): `X-Forwarded-For` = apply((first +: more).map(Some(_)))
- implicit val ipsRenderer = Renderer.defaultSeqRenderer[Option[HttpIp]](Renderer.optionRenderer("unknown"))
+ def apply(first: String, more: String*): `X-Forwarded-For` = apply((first +: more).map(RemoteAddress.apply))
+ def apply(first: RemoteAddress, more: RemoteAddress*): `X-Forwarded-For` = apply(first +: more)
+ implicit val addressesRenderer = Renderer.defaultSeqRenderer[RemoteAddress]
}
- case class `X-Forwarded-For`(ips: Seq[Option[HttpIp]]) extends ModeledHeader {
- import `X-Forwarded-For`.ipsRenderer
- def renderValue[R <: Rendering](r: R): r.type = r ~~ ips
+ case class `X-Forwarded-For`(addresses: Seq[RemoteAddress]) extends ModeledHeader {
+ import `X-Forwarded-For`.addressesRenderer
+ def renderValue[R <: Rendering](r: R): r.type = r ~~ addresses
protected def companion = `X-Forwarded-For`
}
@@ -1,28 +0,0 @@
-/*
- * Copyright © 2011-2013 the spray project <http://spray.io>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package spray.http
-
-import java.net.InetAddress
-
-case class HttpIp(ip: InetAddress) extends ValueRenderable {
- def render[R <: Rendering](r: R): r.type = r ~~ ip.getHostAddress
-}
-
-object HttpIp {
- implicit def apply(s: String): HttpIp = InetAddress.getByName(s)
- implicit def fromInetAddress(a: InetAddress): HttpIp = apply(a)
-}
@@ -0,0 +1,45 @@
+/*
+ * Copyright © 2011-2013 the spray project <http://spray.io>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package spray.http
+
+import java.net.{ UnknownHostException, InetAddress }
+
+sealed abstract class RemoteAddress extends ValueRenderable {
+ def toOption: Option[InetAddress]
+}
+
+object RemoteAddress {
+ case object Unknown extends RemoteAddress {
+ def toOption = None
+ def render[R <: Rendering](r: R): r.type = r ~~ "unknown"
+ }
+
+ case class IP(ip: InetAddress) extends RemoteAddress {
+ def toOption: Option[InetAddress] = Some(ip)
+ def render[R <: Rendering](r: R): r.type = r ~~ ip.getHostAddress
+ }
+
+ def apply(s: String): RemoteAddress =
+ try IP(InetAddress.getByName(s)) catch { case _: UnknownHostException Unknown }
+
+ def apply(a: InetAddress): IP = IP(a)
+
+ def apply(bytes: Array[Byte]): RemoteAddress = {
+ require(bytes.length == 4 || bytes.length == 16)
+ try IP(InetAddress.getByAddress(bytes)) catch { case _: UnknownHostException Unknown }
+ }
+}
@@ -24,11 +24,8 @@ import BasicRules._
private[parser] trait AdditionalRules {
this: Parser
- def Ip: Rule1[HttpIp] = rule(
- group(IpNumber ~ ch('.') ~ IpNumber ~ ch('.') ~ IpNumber ~ ch('.') ~ IpNumber) ~> (HttpIp(_)) ~ OptWS)
-
- def IpNumber = rule {
- Digit ~ optional(Digit ~ optional(Digit))
+ def Ip: Rule1[RemoteAddress] = rule {
+ IPv4Address ~~> ((a, b, c, d) RemoteAddress(Array(a, b, c, d)))
}
def Challenge = rule {
@@ -72,8 +72,61 @@ private[parser] object BasicRules extends Parser {
def ListSep = rule { oneOrMore("," ~ OptWS) }
- // we don't match scoped IPv6 addresses
- def IPv6Address = rule { oneOrMore(Hex | anyOf(":.")) }
+ def IPv4Address: Rule4[Byte, Byte, Byte, Byte] = {
+ def IpNumber = {
+ def Digit04 = rule { "0" - "4" }
+ def Digit05 = rule { "0" - "5" }
+ def Digit19 = rule { "1" - "9" }
+ rule {
+ group(
+ ch('2') ~ (Digit04 ~ Digit | ch('5') ~ Digit05)
+ | ch('1') ~ Digit ~ Digit
+ | Digit19 ~ Digit
+ | Digit) ~> (java.lang.Integer.parseInt(_).toByte)
+ }
+ }
+ rule { IpNumber ~ ch('.') ~ IpNumber ~ ch('.') ~ IpNumber ~ ch('.') ~ IpNumber ~ OptWS }
+ }
+
+ def IPv6Address: Rule1[Array[Byte]] = {
+ import org.parboiled.{ Context PC }
+ def arr(ctx: PC[Any]): Array[Byte] = ctx.getValueStack.peek().asInstanceOf[Array[Byte]]
+ def zero(ix: Int, count: Int = 1): PC[Any] Unit =
+ ctx { val a = arr(ctx); java.util.Arrays.fill(a, ix, ix + count, 0.toByte) }
+ def h4(ix: Int) = Hex ~ ((ctx: PC[Any]) arr(ctx)(ix) = CharUtils.hexValue(ctx.getFirstMatchChar).toByte)
+ def h8(ix: Int) = Hex ~ Hex ~ ((ctx: PC[Any])
+ arr(ctx)(ix) = (CharUtils.hexValue(ctx.getInputBuffer.charAt(ctx.getCurrentIndex - 2)) * 16 +
+ CharUtils.hexValue(ctx.getInputBuffer.charAt(ctx.getCurrentIndex - 1))).toByte)
+ def h16(ix: Int): Rule0 = h8(ix) ~ h8(ix + 1) | h4(ix) ~ h8(ix + 1) | zero(ix) ~ h8(ix + 1) | zero(ix) ~ h4(ix + 1)
+ def h16c(ix: Int): Rule0 = h16(ix) ~ ch(':') ~ !ch(':')
+ def ch16o(ix: Int): Rule0 = optional(ch(':') ~ !ch(':')) ~ (h16(ix) | zero(ix, 2))
+ def ls32: Rule0 = rule(
+ h16(12) ~ ch(':') ~ h16(14)
+ | IPv4Address ~~% withContext((a, b, c, d, cx) { arr(cx)(12) = a; arr(cx)(13) = b; arr(cx)(14) = c; arr(cx)(15) = d }))
+ def cc(ix: Int): Rule0 = ch(':') ~ ch(':') ~ zero(ix, 2)
+ def tail2 = rule { h16c(2) ~ tail4 }
+ def tail4 = rule { h16c(4) ~ tail6 }
+ def tail6 = rule { h16c(6) ~ tail8 }
+ def tail8 = rule { h16c(8) ~ tail10 }
+ def tail10 = rule { h16c(10) ~ ls32 }
+ rule(
+ !(ch(':') ~ Hex) ~ push(new Array[Byte](16)) ~ (
+ h16c(0) ~ tail2
+ | cc(0) ~ tail2
+ | ch16o(0) ~ (
+ cc(2) ~ tail4
+ | ch16o(2) ~ (
+ cc(4) ~ tail6
+ | ch16o(4) ~ (
+ cc(6) ~ tail8
+ | ch16o(6) ~ (
+ cc(8) ~ tail10
+ | ch16o(8) ~ (
+ cc(10) ~ ls32
+ | ch16o(10) ~ (
+ cc(12) ~ h16(14)
+ | ch16o(12) ~ cc(14)))))))))
+ }
- def IPv6Reference: Rule1[String] = rule { group("[" ~ IPv6Address ~ "]") ~> identityFunc }
+ def IPv6Reference: Rule1[String] = rule { group("[" ~ oneOrMore(Hex | anyOf(":.")) ~ "]") ~> identityFunc }
}
@@ -90,7 +90,7 @@ private[parser] trait SimpleHeaders {
// were not quoted and that's also what the "Transition" section in the draft says:
// http://tools.ietf.org/html/draft-ietf-appsawg-http-forwarded-10
def `*X-Forwarded-For` = rule {
- oneOrMore((Ip | IPv6Address ~> (HttpIp(_))) ~~> (Some(_)) | "unknown" ~ push(None), separator = ListSep) ~ EOI ~~>
+ oneOrMore(Ip | IPv6Address ~~> (RemoteAddress(_)) | "unknown" ~ push(RemoteAddress.Unknown), separator = ListSep) ~ EOI ~~>
(`X-Forwarded-For`(_))
}
}
Oops, something went wrong.

0 comments on commit 443b0d8

Please sign in to comment.