Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8307424

FileNotFoundException for an existing file that contains Ω in its name

    XMLWordPrintable

Details

    • generic
    • windows

    Description

      ADDITIONAL SYSTEM INFORMATION :
      OS: Windows
      java -version
      java version "17.0.7" 2023-04-18 LTS
      Java(TM) SE Runtime Environment (build 17.0.7+8-LTS-224)
      Java HotSpot(TM) 64-Bit Server VM (build 17.0.7+8-LTS-224, mixed mode, sharing)

      A DESCRIPTION OF THE PROBLEM :
      When a file name contains certain characters, like the omega sign Ω, the FileInputStream fails to open the file, when used by the file URL handler in special cases.

      String path = "c:\temp\42 Ω.txt"
      File f = new File(path);
      (1) new FileInputStream(path); --> succeeds
      (2) f.toURI().toURL().openConnection().getInputStream(); --> succeeds
      (3) new URL(f.toURI().toASCIIString()).openConnection().getInputStream(); --> FAILS

      Case (1) creates the FileInputStream with a String, which is wrapped into a File by the FileInputStream constructor.
      Case (2) creates a URL with toURI().toURL(), which has this format "file:/c:/temp/42%20Ω.txt"
      Case (3) also creates a URL, but uses File.toURI().toASCIIString() and the URL constructor. The resulting URL has the format "file:/c:/temp/42%20%CE%A9.txt"

      The interesting part is that in both URL cases the URL is converted back by sun.net.www.protocol.file.Handler.openConnection() to the exact same File with the abstract path "c:\temp\42 Ω.txt" and in both cases the FileInputStream constructor is called with that path. See FileURLConnection.connect().

      So it is unclear why (2) succeeds, but (3) fails.

      You might wonder why someone should use the case (3). The issue is that the JDK itself uses case (3) in the following locations that parse XML files:
      * javax.xml.parsers.DocumentBuilder.parse(File)
      * javax.xml.parsers.SAXParser.parse(File, DefaultHandler)
      * javax.xml.parsers.SAXParser.parse(File, HandlerBase)
      * javax.xml.transform.stream.StreamSource.StreamSource(File)

      All those methods contain a comment that refers to RFC2396
              //convert file to appropriate URI, f.toURI().toASCIIString()
              //converts the URI to string as per rule specified in
              //RFC 2396,

      There are more characters that succeed in case (1) and (2) but fail in case (3), but most characters do not cause the issue. Even Unicode smileys or gothik characters (that are not in the Basic Multi-language Plane) do not cause the issue.




      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1) Create a file on disk that contains Ω in its name (e.g. C:\temp\Ω.XML)
      2) Call: new URL(new File("C:\\temp\\Ω.XML").toURI().toASCIIString()).openConnection().getInputStream()

      Note that step two above is minimalist. The same error occurs if you try to parse the file with a DocumentBuilder.parse(File) and it might also occur for SAXParser.parse(File, ..) or when using StreamSource(File)

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      No error should occur. DocumentBuilder.parse(File) should succeed for all local files where DocumentBuilder.parse(new FileInputStream(file)) succeeds.
      ACTUAL -
      A FileNotFoundException occurs for a file that exists

      ---------- BEGIN SOURCE ----------

      import java.io.File;
      import java.io.FileInputStream;
      import java.io.FileNotFoundException;
      import java.io.IOException;
      import java.net.MalformedURLException;
      import java.net.URL;
      import java.nio.file.Files;
      import java.nio.file.Path;

      import javax.xml.parsers.DocumentBuilder;
      import javax.xml.parsers.DocumentBuilderFactory;

      import org.w3c.dom.Document;

      public class FileInputStreamIssue
      {
        public static void main(String[] args) throws MalformedURLException, IOException {
          tryPath("c:\\temp\\42 Ohm.xml");
          tryPath("c:\\temp\\Thumbs up 👍.xml");
          tryPath("c:\\temp\\Hello 你好.xml");
          tryPath("c:\\temp\\Gothic 𐌾𐍈𐍄𐌷𐌹𐌾.xml");
          tryPath("c:\\temp\\24 ℧ (upside-down).xml");
          tryPath("c:\\temp\\42 Ω.xml");

          // To search for more characters that cause the same issue:
      // for (int i = 32; i < Math.pow(2, 32); ++i) {
      // var path = "c:\\temp\\" + i + " " + new String(Character.toChars(i)) + ".xml";
      // tryPath(path);
      // }
        }

        private static void tryPath(String path) throws IOException, FileNotFoundException, MalformedURLException {
          System.out.println("Trying " + path);
          try {
            // Create the file
            File f = new File(path);
            if (!f.exists()) {
              Files.createFile(f.toPath());
              Files.writeString(f.toPath(), "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><empty />");
            }

            try (var s = new FileInputStream(path)) {
              System.out.println("Successfully opened FileInputStream for " + path);
            } catch (IOException e) {
              System.out.println("Failed to open FileInputStream for " + path);
            }

            try (var s = f.toURI().toURL().openConnection().getInputStream()) {
              System.out.println("Successfully opened f.toURI().toURL().openConnection().getInputStream() for " + path);
            } catch (IOException e) {
              System.out.println("Failed to open f.toURI().toURL().openConnection().getInputStream() for " + path);
            }

            try (var s = new URL(f.toURI().toASCIIString()).openConnection().getInputStream()) {
              System.out.println("Successfully opened f.toURI().toASCIIString()).openConnection().getInputStream() for " + path);
            } catch (IOException e) {
              System.out.println("Failed to open f.toURI().toASCIIString()).openConnection().getInputStream() for " + path);
            }

            try {
              DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
              DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
              Document doc = dBuilder.parse(f);

              System.out.println("Successfully used DocumentBuilder.parse(File) for " + path);
            } catch (IOException e) {
              System.out.println("Failed to use DocumentBuilder.parse(File) for " + path);
            }


            try (var in = new FileInputStream(f)) {
              DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
              DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
              Document doc = dBuilder.parse(in);

              System.out.println("Successfully used DocumentBuilder.parse(FileInputStream) for " + path);
            } catch (IOException e) {
              System.out.println("Failed to use DocumentBuilder.parse(FileInputStream) for " + path);
            }

            // Cleanup - delete the file
            Files.deleteIfExists(Path.of(path));
          } catch (Exception e) {
            System.out.println("Failed to test path " + path + " " + e.getMessage());
          }
        }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Do not use DocumentBuilder.parse(File). Instead use DocumentBuilder.parse(InputStream)


      FREQUENCY : always


      Attachments

        Activity

          People

            bpb Brian Burkhalter
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

            Dates

              Created:
              Updated: