diff --git a/launch/src/main/scala/xsbt/boot/Configuration.scala b/launch/src/main/scala/xsbt/boot/Configuration.scala index 5233b14e3..651d80033 100644 --- a/launch/src/main/scala/xsbt/boot/Configuration.scala +++ b/launch/src/main/scala/xsbt/boot/Configuration.scala @@ -47,9 +47,10 @@ object Configuration } def configurationFromFile(path: String, baseDirectory: File): URL = { + val pathURI = filePathURI(path) def resolve(against: URI): Option[URL] = { - val resolved = against.resolve(path) + val resolved = against.resolve(pathURI) // variant that accepts String doesn't properly escape (#725) val exists = try { (new File(resolved)).exists } catch { case _: IllegalArgumentException => false } if(exists) Some(resolved.toURL) else None } @@ -111,8 +112,11 @@ object Configuration Option(props.getProperty(SbtVersionProperty)) } - def resolveAgainst(baseDirectory: File): List[URI] = (baseDirectory toURI) :: (new File(System.getProperty("user.home")) toURI) :: - toDirectory(classLocation(getClass).toURI) :: Nil + def resolveAgainst(baseDirectory: File): List[URI] = + directoryURI(baseDirectory) :: + directoryURI(new File(System.getProperty("user.home"))) :: + toDirectory(classLocation(getClass).toURI) :: + Nil def classLocation(cl: Class[_]): URL = { @@ -120,12 +124,26 @@ object Configuration if(codeSource == null) error("No class location for " + cl) else codeSource.getLocation } + // single-arg constructor doesn't properly escape + def filePathURI(path: String): URI = { + val f = new File(path) + new URI(if(f.isAbsolute) "file" else null, path, null) + } + def directoryURI(dir: File): URI = directoryURI(dir.toURI) + def directoryURI(uri: URI): URI = + { + assert(uri.isAbsolute) + val str = uri.toASCIIString + val dirStr = if(str.endsWith("/")) str else str + "/" + (new URI(dirStr)).normalize + } + def toDirectory(uri: URI): URI = try { val file = new File(uri) val newFile = if(file.isFile) file.getParentFile else file - newFile.toURI + directoryURI(newFile) } catch { case _: Exception => uri } private[this] def neNull: AnyRef => Boolean = _ ne null diff --git a/launch/src/test/scala/URITests.scala b/launch/src/test/scala/URITests.scala new file mode 100644 index 000000000..75a91e6ed --- /dev/null +++ b/launch/src/test/scala/URITests.scala @@ -0,0 +1,43 @@ +package xsbt.boot + +import org.scalacheck._ +import Prop._ +import Configuration._ +import java.io.File +import java.net.URI + +object URITests extends Properties("URI Tests") +{ + val FileProtocol = "file" + property("directoryURI adds trailing slash") = secure { + val dirURI = directoryURI(new File("/a/b/c")) + val directURI = filePathURI("/a/b/c/") + dirURI == directURI + } + property("directoryURI preserves trailing slash") = secure { + directoryURI(new File("/a/b/c/")) == filePathURI("/a/b/c/") + } + + property("filePathURI encodes spaces") = secure { + val decoded = "has spaces" + val encoded = "has%20spaces" + val fpURI = filePathURI(decoded) + val directURI = new URI(encoded) + s"filePathURI: $fpURI" |: + s"direct URI: $directURI" |: + s"getPath: ${fpURI.getPath}" |: + s"getRawPath: ${fpURI.getRawPath}" |: + (fpURI == directURI) && + (fpURI.getPath == decoded) && + (fpURI.getRawPath == encoded) + } + + property("filePathURI and File.toURI agree for absolute file") = secure { + val s = "/a/b'/has spaces" + val viaPath = filePathURI(s) + val viaFile = (new File(s)).toURI + s"via path: $viaPath" |: + s"via file: $viaFile" |: + (viaPath == viaFile) + } +} \ No newline at end of file diff --git a/util/io/src/main/scala/sbt/IO.scala b/util/io/src/main/scala/sbt/IO.scala index 06947bfae..85251c520 100644 --- a/util/io/src/main/scala/sbt/IO.scala +++ b/util/io/src/main/scala/sbt/IO.scala @@ -724,7 +724,9 @@ object IO (new URI(dirStr)).normalize } /** Converts the given File to a URI. If the File is relative, the URI is relative, unlike File.toURI*/ - def toURI(f: File): URI = if(f.isAbsolute) f.toURI else new URI(normalizeName(f.getPath)) + def toURI(f: File): URI = + // need to use the three argument URI constructor because the single argument version doesn't encode + if(f.isAbsolute) f.toURI else new URI(null, normalizeName(f.getPath), null) def resolve(base: File, f: File): File = { assertAbsolute(base)