3
3
import java .io .BufferedReader ;
4
4
import java .io .IOException ;
5
5
import java .io .InputStreamReader ;
6
+ import java .lang .reflect .Method ;
6
7
import java .nio .charset .Charset ;
7
8
import java .util .Locale ;
8
9
@@ -12,6 +13,7 @@ private Util() {
12
13
throw new Error ();
13
14
}
14
15
16
+
15
17
static final String operatingSystemName = System .getProperty ("os.name" );
16
18
static final char operatingSystem ;
17
19
static final char LIN = 'l' ;
@@ -36,6 +38,29 @@ else if (os.contains("sunos"))
36
38
throw new UnsupportedOperatingSystemException ("directories are not supported on " + operatingSystemName );
37
39
}
38
40
41
+ private static Object base64Encoder = null ;
42
+ private static Method base64EncodeMethod = null ;
43
+ // This string needs to end up being a multiple of 3 bytes after conversion to UTF-16. (It is currently 1200 bytes.)
44
+ // This is because Base64 converts 3 bytes to 4 letters; other numbers of bytes would introduce padding, which
45
+ // would make it harder to simply concatenate this precomputed string with whatever directories the user requests.
46
+ static final String SCRIPT_START_BASE64 = operatingSystem == 'w' ? toUTF16LEBase64 ("& {\n " +
47
+ "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\n " +
48
+ "Add-Type @\" \n " +
49
+ "using System;\n " +
50
+ "using System.Runtime.InteropServices;\n " +
51
+ "public class Dir {\n " +
52
+ " [DllImport(\" shell32.dll\" )]\n " +
53
+ " private static extern int SHGetKnownFolderPath([MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken, out IntPtr pszPath);\n " +
54
+ " public static string GetKnownFolderPath(string rfid) {\n " +
55
+ " IntPtr pszPath;\n " +
56
+ " if (SHGetKnownFolderPath(new Guid(rfid), 0, IntPtr.Zero, out pszPath) != 0) return \" \" ;\n " +
57
+ " string path = Marshal.PtrToStringUni(pszPath);\n " +
58
+ " Marshal.FreeCoTaskMem(pszPath);\n " +
59
+ " return path;\n " +
60
+ " }\n " +
61
+ "}\n " +
62
+ "\" @\n " ) : null ;
63
+
39
64
static void requireNonNull (Object value ) {
40
65
if (value == null )
41
66
throw new NullPointerException ();
@@ -112,63 +137,50 @@ static String[] getXDGUserDirs(String... dirs) {
112
137
113
138
static String [] getWinDirs (String ... guids ) {
114
139
115
- // See https://www.oracle.com/technetwork/java/javase/8u231-relnotes-5592812.html#JDK-8221858
116
- // Vague attempt at replicating the logic followed by ProcessBuilder, just so that the first
117
- // attempt succeeds and only one command needs to be run.
118
- final String prop = System .getProperty ("jdk.lang.Process.allowAmbiguousCommands" );
119
- final boolean doubleEscapeQuotes = prop == null ? System .getSecurityManager () == null : !"false" .equalsIgnoreCase (prop );
120
- final String [] initialResult = getWinDirs (doubleEscapeQuotes , guids );
121
-
122
- for (String s : initialResult ) {
123
- if (s != null )
124
- return initialResult ;
125
- }
126
-
127
- // First attempt failed, let's try to escape differently.
128
- return getWinDirs (!doubleEscapeQuotes , guids );
129
- }
130
-
131
- static String [] getWinDirs (boolean doubleEscapeQuotes , String ... guids ) {
132
-
133
- // Deal with legacy or safe handling of quotes by the JDK.
134
- // Safe handling may be enabled for JDKs >= 1.8.0_231, under some conditions.
135
- // See https://www.oracle.com/technetwork/java/javase/8u231-relnotes-5592812.html#JDK-8221858
136
- // or https://github.com/AdoptOpenJDK/openjdk-jdk8u/commit/048eb42afa11ac217dcdb690d5b266fcb910771f
137
- final String q = doubleEscapeQuotes ? "\\ \" " : "\" " ;
138
-
139
140
int guidsLength = guids .length ;
140
141
StringBuilder buf = new StringBuilder (guidsLength * 68 );
141
142
for (int i = 0 ; i < guidsLength ; i ++) {
142
- buf .append ("[Dir]::GetKnownFolderPath(" + q );
143
+ buf .append ("[Dir]::GetKnownFolderPath(\" " );
143
144
buf .append (guids [i ]);
144
- buf .append (q + ")\n " );
145
+ buf .append (" \ " )\n " );
145
146
}
146
147
148
+ String encodedCommand = SCRIPT_START_BASE64 + toUTF16LEBase64 (buf .toString () + "}" );
149
+
147
150
return runCommands (guidsLength , Charset .forName ("UTF-8" ),
148
151
"powershell.exe" ,
149
152
"-Command" ,
150
- "& {\n " +
151
- "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\n " +
152
- "Add-Type @" + q + "\n " +
153
- "using System;\n " +
154
- "using System.Runtime.InteropServices;\n " +
155
- "public class Dir {\n " +
156
- " [DllImport(" + q + "shell32.dll" + q + ")]\n " +
157
- " private static extern int SHGetKnownFolderPath([MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken, out IntPtr pszPath);\n " +
158
- " public static string GetKnownFolderPath(string rfid) {\n " +
159
- " IntPtr pszPath;\n " +
160
- " if (SHGetKnownFolderPath(new Guid(rfid), 0, IntPtr.Zero, out pszPath) != 0) return " + q + q + ";\n " +
161
- " string path = Marshal.PtrToStringUni(pszPath);\n " +
162
- " Marshal.FreeCoTaskMem(pszPath);\n " +
163
- " return path;\n " +
164
- " }\n " +
165
- "}\n " +
166
- q + "@\n " +
167
- buf .toString () +
168
- "}"
153
+ "-EncodedCommand" ,
154
+ encodedCommand
169
155
);
170
156
}
171
157
158
+ private static String toUTF16LEBase64 (String script ) {
159
+ byte [] scriptInUtf16LEBytes = script .getBytes (Charset .forName ("UTF-16LE" ));
160
+ if (base64EncodeMethod == null ) {
161
+ initBase64Encoding ();
162
+ }
163
+ try {
164
+ return (String ) base64EncodeMethod .invoke (base64Encoder , scriptInUtf16LEBytes );
165
+ } catch (Exception e ) {
166
+ throw new RuntimeException ("Base64 encoding failed!" , e );
167
+ }
168
+ }
169
+
170
+ private static void initBase64Encoding () {
171
+ try {
172
+ base64Encoder = Class .forName ("java.util.Base64" ).getMethod ("getEncoder" ).invoke (null );
173
+ base64EncodeMethod = base64Encoder .getClass ().getMethod ("encodeToString" , byte [].class );
174
+ } catch (Exception e1 ) {
175
+ try {
176
+ base64EncodeMethod = Class .forName ("sun.misc.BASE64Encoder" ).getMethod ("encode" , byte [].class );
177
+ } catch (Exception e2 ) {
178
+ throw new RuntimeException (
179
+ "Could not find any viable Base64 encoder! (java.util.Base64 failed with: " + e1 .getMessage () + ")" , e2 );
180
+ }
181
+ }
182
+ }
183
+
172
184
private static String [] runCommands (int expectedResultLines , Charset charset , String ... commands ) {
173
185
final ProcessBuilder processBuilder = new ProcessBuilder (commands );
174
186
Process process ;
0 commit comments