summaryrefslogtreecommitdiff
path: root/examples/scala-js/javalib/src/main/scala/java/net/URI.scala
diff options
context:
space:
mode:
Diffstat (limited to 'examples/scala-js/javalib/src/main/scala/java/net/URI.scala')
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/net/URI.scala706
1 files changed, 706 insertions, 0 deletions
diff --git a/examples/scala-js/javalib/src/main/scala/java/net/URI.scala b/examples/scala-js/javalib/src/main/scala/java/net/URI.scala
new file mode 100644
index 0000000..c969f55
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/net/URI.scala
@@ -0,0 +1,706 @@
+package java.net
+
+import scala.scalajs.js.RegExp
+import scala.scalajs.js
+import scala.scalajs.js.{decodeURIComponent => decode}
+
+import scala.annotation.tailrec
+
+final class URI(origStr: String) extends Serializable with Comparable[URI] {
+
+ import URI.Fields._
+
+ /** The fields matched in the regular expression.
+ *
+ * This is a local val for the primary constructor. It is a val,
+ * since we'll set it to null after initializing all fields.
+ */
+ private[this] var _fld = Option(URI.uriRe.exec(origStr)).getOrElse {
+ throw new URISyntaxException(origStr, "Malformed URI")
+ }
+
+ private val _isAbsolute = fld(AbsScheme).isDefined
+ private val _isOpaque = fld(AbsOpaquePart).isDefined
+
+ @inline private def fld(idx: Int): js.UndefOr[String] = _fld(idx)
+
+ @inline private def fld(absIdx: Int, relIdx: Int): js.UndefOr[String] =
+ if (_isAbsolute) _fld(absIdx) else _fld(relIdx)
+
+ private val _scheme = fld(AbsScheme)
+
+ private val _schemeSpecificPart = {
+ if (!_isAbsolute) fld(RelSchemeSpecificPart)
+ else if (_isOpaque) fld(AbsOpaquePart)
+ else fld(AbsHierPart)
+ }.get
+
+ private val _authority = fld(AbsAuthority, RelAuthority)
+ private val _userInfo = fld(AbsUserInfo, RelUserInfo)
+ private val _host = fld(AbsHost, RelHost)
+ private val _port = fld(AbsPort, RelPort).fold(-1)(_.toInt)
+
+ private val _path = {
+ if (_isAbsolute) {
+ if (_authority.isDefined) fld(AbsNetPath)
+ else fld(AbsAbsPath)
+ } else {
+ if (_authority.isDefined) fld(RelNetPath)
+ else fld(RelAbsPath) orElse fld(RelRelPath)
+ }
+ }
+
+ private val _query = fld(AbsQuery, RelQuery)
+ private val _fragment = fld(Fragment)
+
+ // End of default ctor. Unset helper field
+ _fld = null
+
+ def this(scheme: String, ssp: String, fragment: String) =
+ this(URI.uriStr(scheme, ssp, fragment))
+
+ def this(scheme: String, userInfo: String, host: String, port: Int,
+ path: String, query: String, fragment: String) = {
+ this(URI.uriStr(scheme, userInfo, host, port, path, query, fragment))
+ parseServerAuthority()
+ }
+
+ def this(scheme: String, host: String, path: String, fragment: String) =
+ this(scheme, null, host, -1, path, null, fragment)
+
+ def this(scheme: String, authority: String, path: String, query: String,
+ fragment: String) = {
+ this(URI.uriStr(scheme, authority, path, query, fragment))
+ // JavaDoc says to invoke parseServerAuthority() here, but in practice
+ // it isn't invoked. This makes sense, since you want to be able
+ // to create URIs with registry-based authorities.
+ // parseServerAuthority()
+ }
+
+ /** Compare this URI to another URI while supplying a comparator
+ *
+ * This helper is required to account for the semantic differences
+ * between [[compareTo]] and [[equals]]. ([[equals]] does treat
+ * URI escapes specially: they are never case-sensitive).
+ */
+ @inline
+ private def internalCompare(that: URI)(cmp: (String, String) => Int): Int = {
+ @inline def cmpOpt(x: js.UndefOr[String], y: js.UndefOr[String]): Int = {
+ if (x == y) 0
+ // Undefined components are considered less than defined components
+ else x.fold(-1)(s1 => y.fold(1)(s2 => cmp(s1, s2)))
+ }
+
+ if (this._scheme != that._scheme)
+ this._scheme.fold(-1)(s1 => that._scheme.fold(1)(s1.compareToIgnoreCase))
+ else if (this._isOpaque != that._isOpaque)
+ // A hierarchical URI is less than an opaque URI
+ if (this._isOpaque) 1 else -1
+ else if (_isOpaque) {
+ val ssp = cmp(this._schemeSpecificPart, that._schemeSpecificPart)
+ if (ssp != 0) ssp
+ else cmpOpt(this._fragment, that._fragment)
+ } else if (this._authority != that._authority) {
+ if (this._host.isDefined && that._host.isDefined) {
+ val ui = cmpOpt(this._userInfo, that._userInfo)
+ if (ui != 0) ui
+ else {
+ val hst = this._host.get.compareToIgnoreCase(that._host.get)
+ if (hst != 0) hst
+ else if (this._port == that._port) 0
+ else if (this._port == -1) -1
+ else if (that._port == -1) 1
+ else this._port - that._port
+ }
+ } else
+ cmpOpt(this._authority, that._authority)
+ } else if (this._path != that._path)
+ cmpOpt(this._path, that._path)
+ else if (this._query != that._query)
+ cmpOpt(this._query, that._query)
+ else
+ cmpOpt(this._fragment, that._fragment)
+ }
+
+ def compareTo(that: URI): Int = internalCompare(that)(_.compareTo(_))
+
+ override def equals(that: Any): Boolean = that match {
+ case that: URI => internalCompare(that)(URI.escapeAwareCompare) == 0
+ case _ => false
+ }
+
+ def getAuthority(): String = _authority.map(decode).orNull
+ def getFragment(): String = _fragment.map(decode).orNull
+ def getHost(): String = _host.orNull
+ def getPath(): String = _path.map(decode).orNull
+ def getPort(): Int = _port
+ def getQuery(): String = _query.map(decode).orNull
+ def getRawAuthority(): String = _authority.orNull
+ def getRawFragment(): String = _fragment.orNull
+ def getRawPath(): String = _path.orNull
+ def getRawQuery(): String = _query.orNull
+ def getRawSchemeSpecificPart(): String = _schemeSpecificPart
+ def getRawUserInfo(): String = _userInfo.orNull
+ def getScheme(): String = _scheme.orNull
+ def getSchemeSpecificPart(): String = decode(_schemeSpecificPart)
+ def getUserInfo(): String = _userInfo.map(decode).orNull
+
+ override def hashCode(): Int = {
+ import scala.util.hashing.MurmurHash3._
+ import URI.normalizeEscapes
+
+ var acc = URI.uriSeed
+ acc = mix(acc, _scheme.##) // scheme may not contain escapes
+ acc = mix(acc, normalizeEscapes(_schemeSpecificPart).##)
+ acc = mixLast(acc, _fragment.map(normalizeEscapes).##)
+
+ finalizeHash(acc, 3)
+ }
+
+ def isAbsolute(): Boolean = _isAbsolute
+ def isOpaque(): Boolean = _isOpaque
+
+ def normalize(): URI = if (_isOpaque || _path.isEmpty) this else {
+ val origPath = _path.get
+
+ // Step 1: Remove all "." segments
+ // Step 2: Remove ".." segments preceeded by non ".." segment until no
+ // longer applicable
+
+ /** Checks whether a successive ".." may drop the head of a
+ * reversed segment list.
+ */
+ def okToDropFrom(resRev: List[String]) =
+ resRev.nonEmpty && resRev.head != ".." && resRev.head != ""
+
+ @tailrec
+ def loop(in: List[String], resRev: List[String]): List[String] = in match {
+ case "." :: Nil =>
+ // convert "." segments at end to an empty segment
+ // (consider: /a/b/. => /a/b/, not /a/b)
+ loop(Nil, "" :: resRev)
+ case ".." :: Nil if okToDropFrom(resRev) =>
+ // prevent a ".." segment at end to change a "dir" into a "file"
+ // (consider: /a/b/.. => /a/, not /a)
+ loop(Nil, "" :: resRev.tail)
+ case "." :: xs =>
+ // remove "." segments
+ loop(xs, resRev)
+ case "" :: xs if xs.nonEmpty =>
+ // remove empty segments not at end of path
+ loop(xs, resRev)
+ case ".." :: xs if okToDropFrom(resRev) =>
+ // Remove preceeding non-".." segment
+ loop(xs, resRev.tail)
+ case x :: xs =>
+ loop(xs, x :: resRev)
+ case Nil =>
+ resRev.reverse
+ }
+
+ // Split into segments. -1 since we want empty trailing ones
+ val segments0 = origPath.split("/", -1).toList
+ val isAbsPath = segments0.nonEmpty && segments0.head == ""
+ // Don't inject first empty segment into normalization loop, so we
+ // won't need to special case it.
+ val segments1 = if (isAbsPath) segments0.tail else segments0
+ val segments2 = loop(segments1, Nil)
+
+ // Step 3: If path is relative and first segment contains ":", prepend "."
+ // segment (according to JavaDoc). If it is absolute, add empty
+ // segment again to have leading "/".
+ val segments3 = {
+ if (isAbsPath)
+ "" :: segments2
+ else if (segments2.nonEmpty && segments2.head.contains(':'))
+ "." :: segments2
+ else segments2
+ }
+
+ val newPath = segments3.mkString("/")
+
+ // Only create new instance if anything changed
+ if (newPath == origPath)
+ this
+ else
+ new URI(getScheme(), getRawAuthority(), newPath, getQuery(), getFragment())
+ }
+
+ def parseServerAuthority(): URI = {
+ if (_authority.nonEmpty && _host.isEmpty)
+ throw new URISyntaxException(origStr, "No Host in URI")
+ else this
+ }
+
+ def relativize(uri: URI): URI = {
+ def authoritiesEqual = this._authority.fold(uri._authority.isEmpty) { a1 =>
+ uri._authority.fold(false)(a2 => URI.escapeAwareCompare(a1, a2) == 0)
+ }
+
+ if (this.isOpaque || uri.isOpaque ||
+ this._scheme != uri._scheme || !authoritiesEqual) uri
+ else {
+ val thisN = this.normalize()
+ val uriN = uri.normalize()
+
+ // Strangely, Java doesn't handle escapes here. So we don't
+ if (uriN.getRawPath().startsWith(thisN.getRawPath())) {
+ val newPath = uriN.getRawPath().stripPrefix(thisN.getRawPath())
+
+ new URI(scheme = null, authority = null,
+ // never produce an abs path if we relativized
+ path = newPath.stripPrefix("/"),
+ query = uri.getQuery(), fragment = uri.getFragment())
+ } else uri
+ }
+ }
+
+ def resolve(str: String): URI = resolve(URI.create(str))
+
+ def resolve(uri: URI): URI = {
+ if (uri.isAbsolute() || this.isOpaque()) uri
+ else if (uri._scheme.isEmpty && uri._authority.isEmpty &&
+ uri._path.get == "" && uri._query.isEmpty)
+ // This is a special case for URIs like: "#foo". This allows to
+ // just change the fragment in the current document.
+ new URI(
+ this.getScheme(),
+ this.getRawAuthority(),
+ this.getRawPath(),
+ this.getRawQuery(),
+ uri.getRawFragment())
+ else if (uri._authority.isDefined)
+ new URI(
+ this.getScheme(),
+ uri.getRawAuthority(),
+ uri.getRawPath(),
+ uri.getRawQuery(),
+ uri.getRawFragment())
+ else if (uri._path.get.startsWith("/"))
+ new URI(
+ this.getScheme(),
+ this.getRawAuthority(),
+ uri.getRawPath(),
+ uri.getRawQuery(),
+ uri.getRawFragment())
+ else {
+ val basePath = this._path.get
+ val relPath = uri._path.get
+ val endIdx = basePath.lastIndexOf('/')
+ val path =
+ if (endIdx == -1) relPath
+ else basePath.substring(0, endIdx+1) + relPath
+ new URI(
+ this.getScheme(),
+ this.getAuthority(),
+ path,
+ uri.getRawQuery(),
+ uri.getRawFragment()).normalize()
+ }
+ }
+
+ def toASCIIString(): String = origStr // We allow only ASCII in URIs.
+ override def toString(): String = origStr
+
+ // Not implemented:
+ // def toURL(): URL
+
+}
+
+object URI {
+
+ def create(str: String): URI = {
+ try new URI(str)
+ catch {
+ case e: URISyntaxException => throw new IllegalArgumentException(e)
+ }
+ }
+
+ // IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit
+ private final val ipv4address = "[0-9]{1,3}(?:\\.[0-9]{1,3}){3}"
+
+ private final val ipv6address = {
+ // http://stackoverflow.com/a/17871737/1149944
+ val block = "[0-9a-f]{1,4}"
+ val lelem = "(?:"+block+":)"
+ val relem = "(?::"+block+")"
+ val ipv4 = ipv4address
+
+ "(?:" +
+ lelem+"{7}"+block+"|"+ // 1:2:3:4:5:6:7:8
+ lelem+"{1,7}:|"+ // 1:: 1:2:3:4:5:6:7::
+ lelem+"{1,6}"+relem+"|"+ // 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8
+ lelem+"{1,5}"+relem+"{1,2}|"+ // 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8
+ lelem+"{1,4}"+relem+"{1,3}|"+ // 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8
+ lelem+"{1,3}"+relem+"{1,4}|"+ // 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8
+ lelem+"{1,2}"+relem+"{1,5}|"+ // 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8
+ lelem +relem+"{1,6}|"+ // 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8
+ ":(?:"+relem+"{1,7}|:)|" + // ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::
+ lelem+"{6}"+ipv4+"|"+ // 1:2:3:4:5:6:10.0.0.1
+ lelem+"{1,5}:"+ipv4+"|"+ // 1::10.0.0.1 1:2:3:4:5::10.0.0.1
+ lelem+"{1,4}"+relem+":"+ipv4+"|"+ // 1::6:10.0.0.1 1:2:3:4::6:10.0.0.1
+ lelem+"{1,3}"+relem+"{1,2}:"+ipv4+"|"+ // 1::5:6:10.0.0.1 1:2:3::5:6:10.0.0.1 1:2:3::6:10.0.0.1
+ lelem+"{1,2}"+relem+"{1,3}:"+ipv4+"|"+ // 1::4:5:6:10.0.0.1 1:2::4:5:6:10.0.0.1 1:2::6:10.0.0.1
+ lelem +relem+"{1,4}:"+ipv4+"|"+ // 1::3:4:5:6:10.0.0.1 1::3:4:5:6:10.0.0.1 1::6:10.0.0.1
+ "::"+lelem+"{1,5}"+ipv4+ // ::2:3:4:5:10.0.0.1 ::5:10.0.0.1 ::10.0.0.1
+ ")(?:%[0-9a-z]+)?"
+
+ // This was part of the original regex, but is too specific to
+ // IPv6 details.
+ // fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}| # fe80::7:8%eth0 fe80::7:8%1 (link-local IPv6 addresses with zone index)
+ // ::(ffff(:0{1,4}){0,1}:){0,1}
+ // ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}
+ // (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])| # ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses)
+ // ([0-9a-fA-F]{1,4}:){1,4}:
+ // ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}
+ // (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]) # 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 (IPv4-Embedded IPv6 Address)
+ }
+
+ private val ipv6Re = new RegExp("^"+ipv6address+"$", "i")
+
+ // URI syntax parser. Based on RFC2396, RFC2732 and adaptations according to
+ // JavaDoc.
+ // - http://www.ietf.org/rfc/rfc2396.txt (see Appendix A for complete syntax)
+ // - http://www.ietf.org/rfc/rfc2732.txt
+
+ private val uriRe = {
+ // We don't use any interpolators here to allow for constant folding
+
+ ///////////////////
+ //// Helpers ////
+ ///////////////////
+
+ // Inlined definitions
+ // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
+ // "$" | "," | "[" | "]" ; last two added by RFC2732
+ // unreserved = alphanum | mark
+ // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" |
+ // "(" | ")"
+
+ // escaped = "%" hex hex
+ val escaped = "%[a-f0-9]{2}"
+
+ // uric = reserved | unreserved | escaped
+ val uric = "(?:[;/?:@&=+$,\\[\\]a-z0-9-_.!~*'()]|"+escaped+")"
+
+ // pchar = unreserved | escaped |
+ // ":" | "@" | "&" | "=" | "+" | "$" | ","
+ val pchar = "(?:[a-z0-9-_.!~*'():@&=+$,]|"+escaped+")"
+
+ ///////////////////
+ //// Server ////
+ ///////////////////
+
+ // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
+ val domainlabel = "(?:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])"
+
+ // toplabel = alpha | alpha *( alphanum | "-" ) alphanum
+ val toplabel = "(?:[a-z]|[a-z][a-z0-9-]*[a-z0-9])"
+
+ // hostname = *( domainlabel "." ) toplabel [ "." ]
+ val hostname = "(?:"+domainlabel+"\\.)*"+toplabel+"\\.?"
+
+ // IPv6reference = "[" IPv6address "]"
+ val ipv6reference = "\\[(?:"+ipv6address+")\\]"
+
+ // host = hostname | IPv4address | IPv6reference
+ // ; IPv6reference added by RFC2732
+ val host = "("+hostname+"|"+ipv4address+"|"+ipv6reference+")" /*CAPT*/
+
+ // Inlined definition
+ // port = *digit
+
+ // hostport = host [ ":" port ]
+ val hostport = host+"(?::([0-9]*))?" /*CAPT*/
+
+ // userinfo = *( unreserved | escaped |
+ // ";" | ":" | "&" | "=" | "+" | "$" | "," )
+ val userinfo = "(?:[a-z0-9-_.!~*'();:&=+$,]|"+escaped+")*"
+
+ // server = [ [ userinfo "@" ] hostport ]
+ val server = "(?:(?:("+userinfo+")@)?"+hostport+")?" /*CAPT*/
+
+ ///////////////////
+ //// Authority ////
+ ///////////////////
+
+ // reg_name = 1*( unreserved | escaped | "$" | "," |
+ // ";" | ":" | "@" | "&" | "=" | "+" )
+ val reg_name = "(?:[a-z0-9-_.!~*'()$,;:@&=+]|"+escaped+")+"
+
+ // authority = server | reg_name
+ val authority = server+"|"+reg_name
+
+ ///////////////////
+ //// Paths ////
+ ///////////////////
+
+ // Inlined definitions
+ // param = *pchar
+
+ // segment = *pchar *( ";" param )
+ val segment = pchar+"*(?:;"+pchar+"*)*"
+
+ // path_segments = segment *( "/" segment )
+ val path_segments = segment+"(?:/"+segment+")*"
+
+ // abs_path = "/" path_segments
+ val abs_path = "/"+path_segments
+
+ // net_path = "//" authority [ abs_path ]
+ val net_path = "//("+authority+")("+abs_path+")?" /*2CAPT*/
+
+ // Inlined definition
+ // Deviation from RCF2396 according to JavaDoc: Allow empty rel_segment
+ // and hence empty rel_path
+ // rel_segment = 1*( unreserved | escaped |
+ // ";" | "@" | "&" | "=" | "+" | "$" | "," )
+
+ // rel_path = rel_segment [ abs_path ]
+ val rel_path = "(?:[a-z0-9-_.!~*'();@&=+$,]|"+escaped+")*(?:"+abs_path+")?"
+
+ ///////////////////
+ /// Query/Frag ///
+ ///////////////////
+
+ // query = *uric
+ val query = "("+uric+"*)" /*CAPT*/
+ // fragment = *uric
+ val fragment = "("+uric+"*)" /*CAPT*/
+
+ ///////////////////
+ /// Parts ///
+ ///////////////////
+
+ // hier_part = ( net_path | abs_path ) [ "?" query ]
+ val hier_part = "(?:"+net_path+"|("+abs_path+"))(?:\\?"+query+")?" /*CAPT*/
+
+ // Inlined definition
+ // uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" |
+ // "&" | "=" | "+" | "$" | ","
+
+ // opaque_part = uric_no_slash *uric
+ val opaque_part = "(?:[a-z0-9-_.!~*'();?:@&=+$,]|"+escaped+")"+uric+"*"
+
+ ///////////////////
+ /// URIs ///
+ ///////////////////
+
+ // scheme = alpha *( alpha | digit | "+" | "-" | "." )
+ val scheme = "([a-z][a-z0-9+-.]*)" /*CAPT*/
+
+ // absoluteURI = scheme ":" ( hier_part | opaque_part )
+ val absoluteURI = scheme+":(?:("+hier_part+")|("+opaque_part+"))" /*2CAPT*/
+
+ // relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ]
+ val relativeURI = /*2CAPT*/
+ "(?:"+net_path+"|("+abs_path+")|("+rel_path+"))(?:\\?"+query+")?"
+
+ // URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
+ val uriRef = "^(?:"+absoluteURI+"|"+relativeURI+")(?:#"+fragment+")?$"
+
+ new RegExp(uriRef, "i")
+ }
+
+ private object Fields {
+ final val AbsScheme = 1
+ final val AbsHierPart = AbsScheme+1
+ final val AbsAuthority = AbsHierPart+1
+ final val AbsUserInfo = AbsAuthority+1
+ final val AbsHost = AbsUserInfo+1
+ final val AbsPort = AbsHost+1
+ final val AbsNetPath = AbsPort+1 // abs_path part only
+ final val AbsAbsPath = AbsNetPath+1
+ final val AbsQuery = AbsAbsPath+1
+ final val AbsOpaquePart = AbsQuery+1
+ final val RelSchemeSpecificPart = 0 // It's the whole string
+ final val RelAuthority = AbsOpaquePart+1
+ final val RelUserInfo = RelAuthority+1
+ final val RelHost = RelUserInfo+1
+ final val RelPort = RelHost+1
+ final val RelNetPath = RelPort+1 // abs_path part only
+ final val RelAbsPath = RelNetPath+1
+ final val RelRelPath = RelAbsPath+1
+ final val RelQuery = RelRelPath+1
+ final val Fragment = RelQuery+1
+ }
+
+ // Helpers for constructors
+
+ private def uriStr(scheme: String, ssp: String, fragment: String): String = {
+ var resStr = ""
+
+ if (scheme != null)
+ resStr += scheme + ":"
+
+ if (ssp != null)
+ resStr += quoteIllegal(ssp)
+
+ if (fragment != null)
+ resStr += "#" + quoteIllegal(fragment)
+
+ resStr
+ }
+
+ private def uriStr(scheme: String, userInfo: String, host: String, port: Int,
+ path: String, query: String, fragment: String): String = {
+ var resStr = ""
+
+ if (scheme != null)
+ resStr += scheme + ":"
+
+ if (userInfo != null || host != null || port != -1)
+ resStr += "//"
+
+ if (userInfo != null)
+ resStr += quoteUserInfo(userInfo) + "@"
+
+ if (host != null) {
+ if (URI.ipv6Re.test(host))
+ resStr += "[" + host + "]"
+ else
+ resStr += host
+ }
+
+ if (port != -1)
+ resStr += ":" + port
+
+ if (path != null)
+ resStr += quotePath(path)
+
+ if (query != null)
+ resStr += "?" + quoteIllegal(query)
+
+ if (fragment != null)
+ resStr += "#" + quoteIllegal(fragment)
+
+ resStr
+ }
+
+ private def uriStr(scheme: String, authority: String, path: String,
+ query: String, fragment: String) = {
+ var resStr = ""
+
+ if (scheme != null)
+ resStr += scheme + ":"
+
+ if (authority != null)
+ resStr += "//" + quoteAuthority(authority)
+
+ if (path != null)
+ resStr += quotePath(path)
+
+ if (query != null)
+ resStr += "?" + quoteIllegal(query)
+
+ if (fragment != null)
+ resStr += "#" + quoteIllegal(fragment)
+
+ resStr
+ }
+
+ // Quote helpers
+
+ private val quoteChar: js.Function1[String, String] = { (str: String) =>
+ require(str.length == 1)
+
+ val c = str.head.toInt
+
+ if (c > 127)
+ throw new URISyntaxException(null, "Only ASCII allowed in URIs")
+ else
+ f"%%$c%02x"
+ }
+
+ /** matches any character not in unreserved, punct, escaped or other */
+ private val userInfoQuoteRe =
+ new RegExp("[^a-z0-9-_.!~*'(),;:$&+=%\\s]|%(?![0-9a-f]{2})", "ig")
+
+ /** Quote any character not in unreserved, punct, escaped or other */
+ private def quoteUserInfo(str: String) =
+ (str: js.prim.String).replace(userInfoQuoteRe, quoteChar)
+
+ /** matches any character not in unreserved, punct, escaped, other or equal
+ * to '/' or '@'
+ */
+ private val pathQuoteRe =
+ new RegExp("[^a-z0-9-_.!~*'(),;:$&+=%\\s@/]|%(?![0-9a-f]{2})", "ig")
+
+ /** Quote any character not in unreserved, punct, escaped, other or equal
+ * to '/' or '@'
+ */
+ private def quotePath(str: String) =
+ (str: js.prim.String).replace(pathQuoteRe, quoteChar)
+
+ /** matches any character not in unreserved, punct, escaped, other or equal
+ * to '@', '[' or ']'
+ * The last two are different to how JavaDoc specifies, but hopefully yield
+ * the same behavior. (We shouldn't escape [], since they may occur
+ * in IPv6 addresses, but technically speaking they are in reserved
+ * due to RFC2732).
+ */
+ private val authorityQuoteRe =
+ new RegExp("[^a-z0-9-_.!~*'(),;:$&+=%\\s@\\[\\]]|%(?![0-9a-f]{2})", "ig")
+
+ /** Quote any character not in unreserved, punct, escaped, other or equal
+ * to '@'
+ */
+ private def quoteAuthority(str: String) =
+ (str: js.prim.String).replace(authorityQuoteRe, quoteChar)
+
+ /** matches any character not in unreserved, reserved, escaped or other */
+ private val illegalQuoteRe =
+ new RegExp("[^a-z0-9-_.!~*'(),;:$&+=?/\\[\\]%\\s]|%(?![0-9a-f]{2})", "ig")
+
+ /** Quote any character not in unreserved, reserved, escaped or other */
+ private def quoteIllegal(str: String) =
+ (str: js.prim.String).replace(illegalQuoteRe, quoteChar)
+
+ /** Case-sensitive comparison that is case-insensitive inside URI
+ * escapes. Will compare `a%A0` and `a%a0` as equal, but `a%A0` and
+ * `A%A0` as different.
+ */
+ private def escapeAwareCompare(x: String, y: String): Int = {
+ @tailrec
+ def loop(i: Int): Int = {
+ if (i >= x.length || i >= y.length)
+ x.length - y.length
+ else {
+ val diff = x.charAt(i) - y.charAt(i)
+ if (diff != 0) diff
+ else if (x.charAt(i) == '%') {
+ // we need to do a CI compare for the next two characters
+ assert(x.length > i + 2, "Invalid escape in URI")
+ assert(y.length > i + 2, "Invalid escape in URI")
+ val cmp =
+ x.substring(i+1, i+3).compareToIgnoreCase(y.substring(i+1, i+3))
+ if (cmp != 0) cmp
+ else loop(i+3)
+ } else loop(i+1)
+ }
+ }
+
+ loop(0)
+ }
+
+ /** Upper-cases all URI escape sequences in `str`. Used for hashing */
+ private def normalizeEscapes(str: String): String = {
+ var i = 0
+ var res = ""
+ while (i < str.length) {
+ if (str.charAt(i) == '%') {
+ assert(str.length > i + 2, "Invalid escape in URI")
+ res += str.substring(i, i+3).toUpperCase()
+ i += 3
+ } else {
+ res += str.substring(i, i+1)
+ i += 1
+ }
+ }
+
+ res
+ }
+
+ private final val uriSeed = 53722356
+
+}