1 /* 2 * Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* We use APIs that access a so-called Windows "Environment Block", 27 * which looks like an array of jchars like this: 28 * 29 * FOO=BAR\u0000 ... GORP=QUUX\u0000\u0000 30 * 31 * This data structure has a number of peculiarities we must contend with: 32 * (see: http://windowssdk.msdn.microsoft.com/en-us/library/ms682009.aspx) 33 * - The NUL jchar separators, and a double NUL jchar terminator. 34 * It appears that the Windows implementation requires double NUL 35 * termination even if the environment is empty. We should always 36 * generate environments with double NUL termination, while accepting 37 * empty environments consisting of a single NUL. 38 * - on Windows9x, this is actually an array of 8-bit chars, not jchars, 39 * encoded in the system default encoding. 40 * - The block must be sorted by Unicode value, case-insensitively, 41 * as if folded to upper case. 42 * - There are magic environment variables maintained by Windows 43 * that start with a `=' (!) character. These are used for 44 * Windows drive current directory (e.g. "=C:=C:\WINNT") or the 45 * exit code of the last command (e.g. "=ExitCode=0000001"). 46 * 47 * Since Java and non-9x Windows speak the same character set, and 48 * even the same encoding, we don't have to deal with unreliable 49 * conversion to byte streams. Just add a few NUL terminators. 50 * 51 * System.getenv(String) is case-insensitive, while System.getenv() 52 * returns a map that is case-sensitive, which is consistent with 53 * native Windows APIs. 54 * 55 * The non-private methods in this class are not for general use even 56 * within this package. Instead, they are the system-dependent parts 57 * of the system-independent method of the same name. Don't even 58 * think of using this class unless your method's name appears below. 59 * 60 * @author Martin Buchholz 61 * @since 1.5 62 */ 63 64 package java.lang; 65 66 import java.io.*; 67 import java.util.*; 68 69 final class ProcessEnvironment extends HashMap<String,String> 70 { 71 private static String validateName(String name) { 72 // An initial `=' indicates a magic Windows variable name -- OK 73 if (name.indexOf('=', 1) != -1 || 74 name.indexOf('\u0000') != -1) 75 throw new IllegalArgumentException 76 ("Invalid environment variable name: \"" + name + "\""); 77 return name; 78 } 79 80 private static String validateValue(String value) { 81 if (value.indexOf('\u0000') != -1) 82 throw new IllegalArgumentException 83 ("Invalid environment variable value: \"" + value + "\""); 84 return value; 85 } 86 87 private static String nonNullString(Object o) { 88 if (o == null) 89 throw new NullPointerException(); 90 return (String) o; 91 } 92 93 public String put(String key, String value) { 94 return super.put(validateName(key), validateValue(value)); 95 } 96 97 public String get(Object key) { 98 return super.get(nonNullString(key)); 99 } 100 101 public boolean containsKey(Object key) { 102 return super.containsKey(nonNullString(key)); 103 } 104 105 public boolean containsValue(Object value) { 106 return super.containsValue(nonNullString(value)); 107 } 108 109 public String remove(Object key) { 110 return super.remove(nonNullString(key)); 111 } 112 113 private static class CheckedEntry 114 implements Map.Entry<String,String> 115 { 116 private final Map.Entry<String,String> e; 117 public CheckedEntry(Map.Entry<String,String> e) {this.e = e;} 118 public String getKey() { return e.getKey();} 119 public String getValue() { return e.getValue();} 120 public String setValue(String value) { 121 return e.setValue(validateValue(value)); 122 } 123 public String toString() { return getKey() + "=" + getValue();} 124 public boolean equals(Object o) {return e.equals(o);} 125 public int hashCode() {return e.hashCode();} 126 } 127 128 private static class CheckedEntrySet 129 extends AbstractSet<Map.Entry<String,String>> 130 { 131 private final Set<Map.Entry<String,String>> s; 132 public CheckedEntrySet(Set<Map.Entry<String,String>> s) {this.s = s;} 133 public int size() {return s.size();} 134 public boolean isEmpty() {return s.isEmpty();} 135 public void clear() { s.clear();} 136 public Iterator<Map.Entry<String,String>> iterator() { 137 return new Iterator<Map.Entry<String,String>>() { 138 Iterator<Map.Entry<String,String>> i = s.iterator(); 139 public boolean hasNext() { return i.hasNext();} 140 public Map.Entry<String,String> next() { 141 return new CheckedEntry(i.next()); 142 } 143 public void remove() { i.remove();} 144 }; 145 } 146 private static Map.Entry<String,String> checkedEntry (Object o) { 147 @SuppressWarnings("unchecked") 148 Map.Entry<String,String> e = (Map.Entry<String,String>) o; 149 nonNullString(e.getKey()); 150 nonNullString(e.getValue()); 151 return e; 152 } 153 public boolean contains(Object o) {return s.contains(checkedEntry(o));} 154 public boolean remove(Object o) {return s.remove(checkedEntry(o));} 155 } 156 157 private static class CheckedValues extends AbstractCollection<String> { 158 private final Collection<String> c; 159 public CheckedValues(Collection<String> c) {this.c = c;} 160 public int size() {return c.size();} 161 public boolean isEmpty() {return c.isEmpty();} 162 public void clear() { c.clear();} 163 public Iterator<String> iterator() {return c.iterator();} 164 public boolean contains(Object o) {return c.contains(nonNullString(o));} 165 public boolean remove(Object o) {return c.remove(nonNullString(o));} 166 } 167 168 private static class CheckedKeySet extends AbstractSet<String> { 169 private final Set<String> s; 170 public CheckedKeySet(Set<String> s) {this.s = s;} 171 public int size() {return s.size();} 172 public boolean isEmpty() {return s.isEmpty();} 173 public void clear() { s.clear();} 174 public Iterator<String> iterator() {return s.iterator();} 175 public boolean contains(Object o) {return s.contains(nonNullString(o));} 176 public boolean remove(Object o) {return s.remove(nonNullString(o));} 177 } 178 179 public Set<String> keySet() { 180 return new CheckedKeySet(super.keySet()); 181 } 182 183 public Collection<String> values() { 184 return new CheckedValues(super.values()); 185 } 186 187 public Set<Map.Entry<String,String>> entrySet() { 188 return new CheckedEntrySet(super.entrySet()); 189 } 190 191 192 private static final class NameComparator 193 implements Comparator<String> { 194 public int compare(String s1, String s2) { 195 // We can't use String.compareToIgnoreCase since it 196 // canonicalizes to lower case, while Windows 197 // canonicalizes to upper case! For example, "_" should 198 // sort *after* "Z", not before. 199 int n1 = s1.length(); 200 int n2 = s2.length(); 201 int min = Math.min(n1, n2); 202 for (int i = 0; i < min; i++) { 203 char c1 = s1.charAt(i); 204 char c2 = s2.charAt(i); 205 if (c1 != c2) { 206 c1 = Character.toUpperCase(c1); 207 c2 = Character.toUpperCase(c2); 208 if (c1 != c2) 209 // No overflow because of numeric promotion 210 return c1 - c2; 211 } 212 } 213 return n1 - n2; 214 } 215 } 216 217 private static final class EntryComparator 218 implements Comparator<Map.Entry<String,String>> { 219 public int compare(Map.Entry<String,String> e1, 220 Map.Entry<String,String> e2) { 221 return nameComparator.compare(e1.getKey(), e2.getKey()); 222 } 223 } 224 225 // Allow `=' as first char in name, e.g. =C:=C:\DIR 226 static final int MIN_NAME_LENGTH = 1; 227 228 private static final NameComparator nameComparator; 229 private static final EntryComparator entryComparator; 230 private static final ProcessEnvironment theEnvironment; 231 private static final Map<String,String> theUnmodifiableEnvironment; 232 private static final Map<String,String> theCaseInsensitiveEnvironment; 233 234 static { 235 nameComparator = new NameComparator(); 236 entryComparator = new EntryComparator(); 237 theEnvironment = new ProcessEnvironment(); 238 theUnmodifiableEnvironment 239 = Collections.unmodifiableMap(theEnvironment); 240 241 String envblock = environmentBlock(); 242 int beg, end, eql; 243 for (beg = 0; 244 ((end = envblock.indexOf('\u0000', beg )) != -1 && 245 // An initial `=' indicates a magic Windows variable name -- OK 246 (eql = envblock.indexOf('=' , beg+1)) != -1); 247 beg = end + 1) { 248 // Ignore corrupted environment strings. 249 if (eql < end) 250 theEnvironment.put(envblock.substring(beg, eql), 251 envblock.substring(eql+1,end)); 252 } 253 254 theCaseInsensitiveEnvironment = new TreeMap<>(nameComparator); 255 theCaseInsensitiveEnvironment.putAll(theEnvironment); 256 } 257 258 private ProcessEnvironment() { 259 super(); 260 } 261 262 private ProcessEnvironment(int capacity) { 263 super(capacity); 264 } 265 266 // Only for use by System.getenv(String) 267 static String getenv(String name) { 268 // The original implementation used a native call to _wgetenv, 269 // but it turns out that _wgetenv is only consistent with 270 // GetEnvironmentStringsW (for non-ASCII) if `wmain' is used 271 // instead of `main', even in a process created using 272 // CREATE_UNICODE_ENVIRONMENT. Instead we perform the 273 // case-insensitive comparison ourselves. At least this 274 // guarantees that System.getenv().get(String) will be 275 // consistent with System.getenv(String). 276 return theCaseInsensitiveEnvironment.get(name); 277 } 278 279 // Only for use by System.getenv() 280 static Map<String,String> getenv() { 281 return theUnmodifiableEnvironment; 282 } 283 284 // Only for use by ProcessBuilder.environment() 285 static Map<String,String> environment() { 286 return (Map<String,String>) theEnvironment.clone(); 287 } 288 289 // Only for use by Runtime.exec(...String[]envp...) 290 static Map<String,String> emptyEnvironment(int capacity) { 291 return new ProcessEnvironment(capacity); 292 } 293 294 private static native String environmentBlock(); 295 296 // Only for use by ProcessImpl.start() 297 String toEnvironmentBlock() { 298 // Sort Unicode-case-insensitively by name 299 Set<Map.Entry<String,String>> entries = entrySet(); 300 List<Map.Entry<String,String>> list = new ArrayList<>(entries); 301 302 // check for "SystemRoot". Needed by MSVCRT.DLL 303 // Environment variable names are case-insensitive. 304 // So, must do an iterative search. 305 boolean found = false; 306 for (Map.Entry<String,String> entry : entries) { 307 String key = entry.getKey(); 308 if (key.equalsIgnoreCase("SystemRoot")) { 309 found = true; 310 break; 311 } 312 } 313 if (!found) { 314 list.add(new AbstractMap.SimpleEntry<String,String>( 315 "SystemRoot", getenv("SystemRoot")) 316 ); 317 } 318 319 Collections.sort(list, entryComparator); 320 321 StringBuilder sb = new StringBuilder(size()*30); 322 for (Map.Entry<String,String> e : list) 323 sb.append(e.getKey()) 324 .append('=') 325 .append(e.getValue()) 326 .append('\u0000'); 327 // Ensure double NUL termination, 328 // even if environment is empty. 329 if (sb.length() == 0) 330 sb.append('\u0000'); 331 sb.append('\u0000'); 332 return sb.toString(); 333 } 334 335 static String toEnvironmentBlock(Map<String,String> map) { 336 return map == null ? null : 337 ((ProcessEnvironment)map).toEnvironmentBlock(); 338 } 339 }