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 Map.Entry<String,String> e = (Map.Entry<String,String>) o; 148 nonNullString(e.getKey()); 149 nonNullString(e.getValue()); 150 return e; 151 } 152 public boolean contains(Object o) {return s.contains(checkedEntry(o));} 153 public boolean remove(Object o) {return s.remove(checkedEntry(o));} 154 } 155 156 private static class CheckedValues extends AbstractCollection<String> { 157 private final Collection<String> c; 158 public CheckedValues(Collection<String> c) {this.c = c;} 159 public int size() {return c.size();} 160 public boolean isEmpty() {return c.isEmpty();} 161 public void clear() { c.clear();} 162 public Iterator<String> iterator() {return c.iterator();} 163 public boolean contains(Object o) {return c.contains(nonNullString(o));} 164 public boolean remove(Object o) {return c.remove(nonNullString(o));} 165 } 166 167 private static class CheckedKeySet extends AbstractSet<String> { 168 private final Set<String> s; 169 public CheckedKeySet(Set<String> s) {this.s = s;} 170 public int size() {return s.size();} 171 public boolean isEmpty() {return s.isEmpty();} 172 public void clear() { s.clear();} 173 public Iterator<String> iterator() {return s.iterator();} 174 public boolean contains(Object o) {return s.contains(nonNullString(o));} 175 public boolean remove(Object o) {return s.remove(nonNullString(o));} 176 } 177 178 public Set<String> keySet() { 179 return new CheckedKeySet(super.keySet()); 180 } 181 182 public Collection<String> values() { 183 return new CheckedValues(super.values()); 184 } 185 186 public Set<Map.Entry<String,String>> entrySet() { 187 return new CheckedEntrySet(super.entrySet()); 188 } 189 190 191 private static final class NameComparator 192 implements Comparator<String> { 193 public int compare(String s1, String s2) { 194 // We can't use String.compareToIgnoreCase since it 195 // canonicalizes to lower case, while Windows 196 // canonicalizes to upper case! For example, "_" should 197 // sort *after* "Z", not before. 198 int n1 = s1.length(); 199 int n2 = s2.length(); 200 int min = Math.min(n1, n2); 201 for (int i = 0; i < min; i++) { 202 char c1 = s1.charAt(i); 203 char c2 = s2.charAt(i); 204 if (c1 != c2) { 205 c1 = Character.toUpperCase(c1); 206 c2 = Character.toUpperCase(c2); 207 if (c1 != c2) 208 // No overflow because of numeric promotion 209 return c1 - c2; 210 } 211 } 212 return n1 - n2; 213 } 214 } 215 216 private static final class EntryComparator 217 implements Comparator<Map.Entry<String,String>> { 218 public int compare(Map.Entry<String,String> e1, 219 Map.Entry<String,String> e2) { 220 return nameComparator.compare(e1.getKey(), e2.getKey()); 221 } 222 } 223 224 // Allow `=' as first char in name, e.g. =C:=C:\DIR 225 static final int MIN_NAME_LENGTH = 1; 226 227 private static final NameComparator nameComparator; 228 private static final EntryComparator entryComparator; 229 private static final ProcessEnvironment theEnvironment; 230 private static final Map<String,String> theUnmodifiableEnvironment; 231 private static final Map<String,String> theCaseInsensitiveEnvironment; 232 233 static { 234 nameComparator = new NameComparator(); 235 entryComparator = new EntryComparator(); 236 theEnvironment = new ProcessEnvironment(); 237 theUnmodifiableEnvironment 238 = Collections.unmodifiableMap(theEnvironment); 239 240 String envblock = environmentBlock(); 241 int beg, end, eql; 242 for (beg = 0; 243 ((end = envblock.indexOf('\u0000', beg )) != -1 && 244 // An initial `=' indicates a magic Windows variable name -- OK 245 (eql = envblock.indexOf('=' , beg+1)) != -1); 246 beg = end + 1) { 247 // Ignore corrupted environment strings. 248 if (eql < end) 249 theEnvironment.put(envblock.substring(beg, eql), 250 envblock.substring(eql+1,end)); 251 } 252 253 theCaseInsensitiveEnvironment = new TreeMap<>(nameComparator); 254 theCaseInsensitiveEnvironment.putAll(theEnvironment); 255 } 256 257 private ProcessEnvironment() { 258 super(); 259 } 260 261 private ProcessEnvironment(int capacity) { 262 super(capacity); 263 } 264 265 // Only for use by System.getenv(String) 266 static String getenv(String name) { 267 // The original implementation used a native call to _wgetenv, 268 // but it turns out that _wgetenv is only consistent with 269 // GetEnvironmentStringsW (for non-ASCII) if `wmain' is used 270 // instead of `main', even in a process created using 271 // CREATE_UNICODE_ENVIRONMENT. Instead we perform the 272 // case-insensitive comparison ourselves. At least this 273 // guarantees that System.getenv().get(String) will be 274 // consistent with System.getenv(String). 275 return theCaseInsensitiveEnvironment.get(name); 276 } 277 278 // Only for use by System.getenv() 279 static Map<String,String> getenv() { 280 return theUnmodifiableEnvironment; 281 } 282 283 // Only for use by ProcessBuilder.environment() 284 static Map<String,String> environment() { 285 return (Map<String,String>) theEnvironment.clone(); 286 } 287 288 // Only for use by Runtime.exec(...String[]envp...) 289 static Map<String,String> emptyEnvironment(int capacity) { 290 return new ProcessEnvironment(capacity); 291 } 292 293 private static native String environmentBlock(); 294 295 // Only for use by ProcessImpl.start() 296 String toEnvironmentBlock() { 297 // Sort Unicode-case-insensitively by name 298 List<Map.Entry<String,String>> list = new ArrayList<>(entrySet()); 299 Collections.sort(list, entryComparator); 300 301 StringBuilder sb = new StringBuilder(size()*30); 302 for (Map.Entry<String,String> e : list) 303 sb.append(e.getKey()) 304 .append('=') 305 .append(e.getValue()) 306 .append('\u0000'); 307 // Ensure double NUL termination, 308 // even if environment is empty. 309 if (sb.length() == 0) 310 sb.append('\u0000'); 311 sb.append('\u0000'); 312 return sb.toString(); 313 } 314 315 static String toEnvironmentBlock(Map<String,String> map) { 316 return map == null ? null : 317 ((ProcessEnvironment)map).toEnvironmentBlock(); 318 } 319 }