Merge pull request #16954 from spray/wip-16953-mathias

=htc #16953 Fix handling of empty hosts in Uris
This commit is contained in:
Roland Kuhn 2015-03-09 13:09:06 +01:00
commit c383ce46e2
3 changed files with 27 additions and 16 deletions

View file

@ -292,7 +292,8 @@ object Uri {
def httpScheme(securedConnection: Boolean = false) = if (securedConnection) "https" else "http"
final case class Authority(host: Host, port: Int = 0, userinfo: String = "") {
def isEmpty = host.isEmpty
def isEmpty = equals(Authority.Empty)
def nonEmpty = !isEmpty
def normalizedForHttp(encrypted: Boolean = false) =
normalizedFor(httpScheme(encrypted))
def normalizedFor(scheme: String): Authority = {
@ -791,22 +792,19 @@ object UriRendering {
if (isAbsolute) r ~~ scheme ~~ ':'
renderAuthority(r, authority, scheme, charset)
renderPath(r, path, charset, encodeFirstSegmentColons = isRelative)
if (!query.isEmpty) renderQuery(r ~~ '?', query, charset)
r
if (query.nonEmpty) renderQuery(r ~~ '?', query, charset) else r
}
def renderAuthority[R <: Rendering](r: R, authority: Authority, scheme: String, charset: Charset): r.type =
if (authority.isEmpty) r else {
if (authority.nonEmpty) {
import authority._
r ~~ '/' ~~ '/'
if (!userinfo.isEmpty) encode(r, userinfo, charset, `userinfo-char`) ~~ '@'
r ~~ host
if (port != 0) normalizePort(port, scheme) match {
case 0 r
case x r ~~ ':' ~~ port
}
else r
if (port != 0) r ~~ ':' ~~ port else r
} else scheme match {
case "" | "mailto" r
case _ r ~~ '/' ~~ '/'
}
def renderPath[R <: Rendering](r: R, path: Path, charset: Charset, encodeFirstSegmentColons: Boolean = false): r.type =

View file

@ -139,7 +139,7 @@ private[http] class UriParser(val input: ParserInput,
def `reg-name` = rule(
clearSBForDecoding() ~ oneOrMore(`lower-reg-name-char` ~ appendSB() | UPPER_ALPHA ~ appendLowered() | `pct-encoded`) ~
run(_host = NamedHost(getDecodedStringAndLowerIfEncoded(UTF8)))
| run(_host = NamedHost("")))
| run(_host = Host.Empty))
def `path-abempty` = rule { clearSB() ~ slashSegments ~ savePath() }
def `path-absolute` = rule { clearSB() ~ '/' ~ appendSB('/') ~ optional(`segment-nz` ~ slashSegments) ~ savePath() }

View file

@ -16,6 +16,10 @@ class UriSpec extends WordSpec with Matchers {
"Uri.Host instances" should {
"correctly parse empty hosts" in {
Host("") shouldEqual Host.Empty
}
"parse correctly from IPv4 literals" in {
Host("192.0.2.16") shouldEqual IPv4Host("192.0.2.16")
Host("255.0.0.0") shouldEqual IPv4Host("255.0.0.0")
@ -25,6 +29,7 @@ class UriSpec extends WordSpec with Matchers {
Host("3.0.0.0") shouldEqual IPv4Host("3.0.0.0")
Host("30.0.0.0") shouldEqual IPv4Host("30.0.0.0")
}
"support inetAddresses round-trip for Inet4Addresses" in {
def roundTrip(ip: String): Unit = {
val inetAddr = InetAddress.getByName(ip)
@ -354,7 +359,7 @@ class UriSpec extends WordSpec with Matchers {
Uri.from(scheme = "urn", path = "oasis:names:specification:docbook:dtd:xml:4.1.2")
// more examples
Uri("http://") shouldEqual Uri(scheme = "http", authority = Authority(host = NamedHost("")))
Uri("http://") shouldEqual Uri(scheme = "http", authority = Authority(Host.Empty))
Uri("http:?") shouldEqual Uri.from(scheme = "http", query = Query(""))
Uri("?a+b=c%2Bd") shouldEqual Uri.from(query = ("a b", "c+d") +: Query.Empty)
@ -372,6 +377,10 @@ class UriSpec extends WordSpec with Matchers {
// render
Uri("https://server.com/path/to/here?st=12345").toString shouldEqual "https://server.com/path/to/here?st=12345"
Uri("/foo/?a#b").toString shouldEqual "/foo/?a#b"
// empty host
Uri("http://:8000/foo") shouldEqual Uri("http", Authority(Host.Empty, 8000), Path / "foo")
Uri("http://:80/foo") shouldEqual Uri("http", Authority.Empty, Path / "foo")
}
"properly complete a normalization cycle" in {
@ -388,8 +397,12 @@ class UriSpec extends WordSpec with Matchers {
normalize("Http://Localhost") shouldEqual "http://localhost"
normalize("hTtP://localHost") shouldEqual "http://localhost"
normalize("https://:443") shouldEqual "https://"
normalize("https://:444") shouldEqual "https://:444"
normalize("http://:80/foo") shouldEqual "http:///foo"
normalize("http://:8080/foo") shouldEqual "http://:8080/foo"
normalize("ftp://example.com:21") shouldEqual "ftp://example.com"
normalize("example.com:21") shouldEqual "example.com:21"
normalize("example.com:21") shouldEqual "example.com://21" // example.com is parsed as the SCHEME (which is correct)
normalize("//example.com:21") shouldEqual "//example.com:21"
normalize("ftp://example.com:22") shouldEqual "ftp://example.com:22"
normalize("//user:pass@[::1]:80/segment/index.html?query#frag") shouldEqual "//user:pass@[::1]:80/segment/index.html?query#frag"
@ -406,7 +419,7 @@ class UriSpec extends WordSpec with Matchers {
normalize("http://sourceforge.net/projects/uriparser/") shouldEqual "http://sourceforge.net/projects/uriparser/"
normalize("http://sourceforge.net/project/platformdownload.php?group_id=182840") shouldEqual "http://sourceforge.net/project/platformdownload.php?group_id=182840"
normalize("mailto:test@example.com") shouldEqual "mailto:test@example.com"
normalize("file:///bin/bash") shouldEqual "file:///bin/bash"
normalize("file:/bin/bash") shouldEqual "file:///bin/bash"
normalize("http://www.example.com/name%20with%20spaces/") shouldEqual "http://www.example.com/name%20with%20spaces/"
normalize("http://examp%4Ce.com/") shouldEqual "http://example.com/"
normalize("http://example.com/a/b/%2E%2E/") shouldEqual "http://example.com/a/"
@ -499,7 +512,7 @@ class UriSpec extends WordSpec with Matchers {
def resolve(uri: String) = parseAndResolve(uri, base).toString
"normal examples" in {
resolve("g:h") shouldEqual "g:h"
resolve("g:h") shouldEqual "g://h"
resolve("g") shouldEqual "http://a/b/c/g"
resolve("./g") shouldEqual "http://a/b/c/g"
resolve("g/") shouldEqual "http://a/b/c/g/"
@ -547,7 +560,7 @@ class UriSpec extends WordSpec with Matchers {
resolve("g#s/./x") shouldEqual "http://a/b/c/g#s/./x"
resolve("g#s/../x") shouldEqual "http://a/b/c/g#s/../x"
resolve("http:g") shouldEqual "http:g"
resolve("http:g") shouldEqual "http://g"
}
}