Skip to content
This repository has been archived by the owner on Apr 24, 2024. It is now read-only.

Commit

Permalink
! http: remodel HttpIp to RemoteAddress, fixes #638
Browse files Browse the repository at this point in the history
- 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 29, 2013
1 parent be5de26 commit 443b0d8
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 85 deletions.
Expand Up @@ -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"
}
}
}
Expand Up @@ -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
Expand Down
19 changes: 11 additions & 8 deletions spray-http/src/main/scala/spray/http/HttpHeader.scala
Expand Up @@ -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`
}

Expand Down Expand Up @@ -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`
}

Expand Down
28 changes: 0 additions & 28 deletions spray-http/src/main/scala/spray/http/HttpIp.scala

This file was deleted.

45 changes: 45 additions & 0 deletions spray-http/src/main/scala/spray/http/RemoteAddress.scala
@@ -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 }
}
}
Expand Up @@ -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 {
Expand Down
59 changes: 56 additions & 3 deletions spray-http/src/main/scala/spray/http/parser/BasicRules.scala
Expand Up @@ -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 }
}
Expand Up @@ -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`(_))
}
}

0 comments on commit 443b0d8

Please sign in to comment.