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

Expose listing UNC server shares in public API

XMLWordPrintable

    • x86_64
    • windows

      ADDITIONAL SYSTEM INFORMATION :
      Tested on Windows 10 / 11
      Tested with Java 8 / 11 / 17 / 21 / 24

      A DESCRIPTION OF THE PROBLEM :
      The JRE is technically able to list available shares on a UNC server.
      But this functionality is not available through public API
      You can only do it using `sun.awt.shell` classes via reflection.

      Here some experiments I made where `\\wsl$\Ubuntu-20.04` is an available share, and `\\wsl$\dummy` is a non-existing share. Except for the second `dummy` case using `\\wsl$` and `\\fileserver.company.com` behave exactly the same.

      This works to list the contents of a given share.
      Tthis is a baseline.
      All further experiments try to or do list the shares on the server.
      ```java
      FileSystemView fsv = FileSystemView.getFileSystemView();
      File[] shares = fsv.getFiles(new File("\\\\wsl$\\Ubuntu-20.04"), false);
      System.out.println(Arrays.toString(shares));
      ```

      On Java 8 this produces an empty result.
      On Java 11+ this produces `InvalidPathException: UNC path is missing sharename: \\wsl$` due to `Paths.get` call.
      ```java
      FileSystemView fsv = FileSystemView.getFileSystemView();
      File[] shares = fsv.getFiles(new File("\\\\wsl$\\Ubuntu-20.04").getParentFile(), false);
      System.out.println(Arrays.toString(shares));
      ```

      On Java 8 this produces an empty result.
      On Java 11+ this produces `InvalidPathException: UNC path is missing sharename: \\wsl$` due to `Paths.get` call.
      ```java
      FileSystemView fsv = FileSystemView.getFileSystemView();
      File[] shares = fsv.getFiles(new File("\\\\wsl$"), false);
      System.out.println(Arrays.toString(shares));
      ```

      This correctly lists the shares on all versions.
      But it requires to know an existing share from which to start.
      ```java
      FileSystemView fsv = FileSystemView.getFileSystemView();
      Class<?> shellFolder = Class.forName("sun.awt.shell.ShellFolder");
      Method getShellFolder = shellFolder.getMethod("getShellFolder", File.class);
      File[] shares = fsv.getFiles(((File) getShellFolder.invoke(null, new File("\\\\wsl$\\Ubuntu-20.04"))).getParentFile(), false);
      System.out.println(Arrays.toString(shares));
      ```

      On Java 8 this produces `java.io.FileNotFoundException` due to `file.exists()` returning `false`.
      On Java 11+ this produces `InvalidPathException: UNC path is missing sharename: \\wsl$` due to `Paths.get`.
      ```java
      Class<?> shellFolder = Class.forName("sun.awt.shell.ShellFolder");
      Method getShellFolder = shellFolder.getMethod("getShellFolder", File.class);
      getShellFolder.invoke(null, new File("\\\\wsl$"));
      ```

      On all versions this produces `java.io.FileNotFoundException` due to `file.exists()` returning `false`.
      ```java
      Class<?> shellFolder = Class.forName("sun.awt.shell.ShellFolder");
      Method getShellFolder = shellFolder.getMethod("getShellFolder", File.class);
      getShellFolder.invoke(null, new File("\\\\wsl$\\dummy"));
      ```

      On all versions for `\\wsl$` this works.
      On all versions for `\\fileserver.company.com` this produces `java.io.FileNotFoundException: File \\fileserver.company.com\dummy not found` due to `Win32ShellFolder2.parseDisplayName0` throwing `java.io.IOException: Could not parse name`.
      ```java
      FileSystemView fsv = FileSystemView.getFileSystemView();
      Class<?> win32ShellFolderManager2 = Class.forName("sun.awt.shell.Win32ShellFolderManager2");
      Method createShellFolder = win32ShellFolderManager2.getMethod("createShellFolder", File.class);
      createShellFolder.setAccessible(true);
      Constructor<?> win32ShellFolderManager2Constructor = win32ShellFolderManager2.getDeclaredConstructor();
      win32ShellFolderManager2Constructor.setAccessible(true);
      File[] shares = fsv.getFiles(((File) createShellFolder.invoke(win32ShellFolderManager2Constructor.newInstance(), new File("\\\\wsl$\\dummy"))).getParentFile(), false);
      System.out.println(Arrays.toString(shares));
      ```

      On all versions this produces `java.io.FileNotFoundException: File \\wsl$ not found` due to `file.getCanonicalPath()` throwing `java.io.IOException`.
      ```java
      Class<?> win32ShellFolderManager2 = Class.forName("sun.awt.shell.Win32ShellFolderManager2");
      Method createShellFolder = win32ShellFolderManager2.getMethod("createShellFolder", File.class);
      createShellFolder.setAccessible(true);
      Constructor<?> win32ShellFolderManager2Constructor = win32ShellFolderManager2.getDeclaredConstructor();
      win32ShellFolderManager2Constructor.setAccessible(true);
      createShellFolder.invoke(win32ShellFolderManager2Constructor.newInstance(), new File("\\\\wsl$"));
      ```

      This works for all cases on all versions and is the "best" work-around I found so far, besides heavily using internal classes and methods.
      ```java
      Class<?> win32ShellFolderManager2 = Class.forName("sun.awt.shell.Win32ShellFolderManager2");
      Method getDesktop = win32ShellFolderManager2.getDeclaredMethod("getDesktop");
      getDesktop.setAccessible(true);
      Object desktop = getDesktop.invoke(null);
      Class<?> win32ShellFolder2 = Class.forName("sun.awt.shell.Win32ShellFolder2");
      Method parseDisplayName = win32ShellFolder2.getDeclaredMethod("parseDisplayName", String.class);
      parseDisplayName.setAccessible(true);
      Long pidl = (Long) parseDisplayName.invoke(desktop, "\\\\wsl$");
      if (pidl != 0)
          try {
              Method createShellFolderFromRelativePIDL = win32ShellFolderManager2.getDeclaredMethod("createShellFolderFromRelativePIDL", win32ShellFolder2, long.class);
              createShellFolderFromRelativePIDL.setAccessible(true);
              File server = (File) createShellFolderFromRelativePIDL.invoke(null, desktop, pidl);
              FileSystemView fsv = FileSystemView.getFileSystemView();
              File[] shares = fsv.getFiles(server, false);
              System.out.println(Arrays.toString(shares));
          } finally {
              Method releasePIDL = win32ShellFolder2.getDeclaredMethod("releasePIDL", long.class);
              releasePIDL.setAccessible(true);
              releasePIDL.invoke(null, pidl);
          }
      ```


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      - It should be possible to list available shares on a UNC server using public API.
      - Path API like `Paths.get`, `Path.of`, ... should work for UNC paths without share and return proper results for exists checks and alike
      - File API like `exists`, `getCanonicalPath`, ... should work for UNC paths witout share
      ACTUAL -
      Reflection and usage of internal classes paired with `--add-opens java.desktop/sun.awt.shell=ALL-UNNAMED` is necessary to get the desired result currently.

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

              Created:
              Updated: