URI SyntaxException: Illegal character in authority at index 7

(same question as on discord Discord)

Can someone help me understand and fix a windows-only bug which I cannot reproduce. I have some code which tries to use java.awt.Desktop and java.net.URI api(s) to display a file, a png file in the case. One of my students claims it fails on windows, and I have no way of testing this as I don’t have access to windows. It works on MacOS.

The error message is

Exception in thread "main" java.net.URISyntaxException: Illegal character in authority at index 7: file://C:\Users\axelg\AppData\Local\Temp\thompson-13248086568388762202.png

Here is the code; the error is on the line where new URI is used.

  def openGraphicalFile(fileName:String):String = {
    import java.awt.Desktop
    import java.net.URI

    if (Desktop.isDesktopSupported) {
      Desktop.getDesktop.browse(new URI("file://" + fileName))
    }
    else {
      // I don't know all the cases where this ELSE branch is taken,
      // but at least it is taken on Linux while running the CI/CD pipeline
      // in gitlab.
      println(s"cannot open $fileName in OS = ${System.getProperty("os.name")} because desktop not supported")
    }
    fileName
  }

There are no back slashes in file:// URLs, Windows paths are represented using slashes.

For safe, cross-platform representation, don’t do string manipulation but use

import java.nio.file._
val filePath: String = ???
val uri = Paths.get(filePath).toUri

(…or use a Scala native library for URIs, e.g. scala-uri.)

3 Likes

The “file” URI scheme RFC contains a section about Windows paths.

I like this solution, as I don’t have to do anything withs classes forward nor backward.
I tested your solution and it works on MacOS. Not sure how I can test it on windows, because the student who reported it has already converted to a linux VM.

Looking at that documentation section it seems file::///c:/path is supported and file:c:/path, but not file://c:/path :-(,
Will Paths.get(fileName) convert the slashes to the correct direction?

I asked my student to go back and test it, and he claims it works.

  def openGraphicalFile(fileName:String):String = {
    import java.awt.Desktop

    if (Desktop.isDesktopSupported) {
      // See: https://users.scala-lang.org/t/uri-syntaxexception-illegal-character-in-authority-at-index-7/8777
      Desktop.getDesktop.browse(java.nio.file.Paths.get(fileName).toUri)
    }
    else {
      // I don't know all the cases where this ELSE branch is taken,
      // but at least it is taken on Linux while running the CI/CD pipeline
      // in gitlab.
      println(s"cannot open $fileName in OS = ${System.getProperty("os.name")} because desktop not supported")
    }
    fileName
  }

Yes, but this feels like it’s not quite the right question to ask. :slight_smile: The NIO API is supposed to abstract away file system specifics, in particular the structure of string-y path representations. If you check the API docs (Paths, Path, FileSystemProvider, etc.), you’ll find

  • Paths#get() will give you a Path instance for the default file system on the local system (or an exception if the argument is an illegal path representation for this file system).
  • Each FileSystemProvider is associated with a URL scheme. In particular, the default provider is tied to the file:// scheme.
  • Thus, Path#toUri for a Path instance retrieved from the default file system will give you a proper file:// URI (barring I/O or security exceptions).
  • If a URI instance created this way is converted to a Path, it is guaranteed to represent the same absolute path as the original Path instance (“round trip guarantee”).

IOW, once you have a proper Path instance, you shouldn’t worry about string representations at all. Just let the system do its thing.

1 Like

Re-reading, just to clarify: This is probably wrong - and it doesn’t matter. :slight_smile:

Paths#get() will parse the argument to whatever Path implementation there is for the Windows default file system provider. This may have an internal representation as a String, a String array, a Byte array or whatever (and some way to render this correctly on #toString()). Similarly, Path#toUri() will produce an instance of URI, which happens to represent the full URI (and the path, redundantly) as a String, likely with proper slashes, but that’s an internal detail, too. All that matters is that the result of URI#toString() will produce the correct String representation - everything in between is (and should be) opaque.

Consider using GitHub Actions to do your Windows testing.

Can GitHub test on windows? Currently I am using my school’s gitLab installation as repo. the CI/CD is always run on linux as our administrators have installed it.

Yes.

Here’s an example GitHub Actions config for testing on both Linux and Windows: