1 /*
2  * Copyright (c) 2003, 2011, 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 
72     private static final long serialVersionUID = -8017839552603542824L;
73 
validateName(String name)74     private static String validateName(String name) {
75         // An initial `=' indicates a magic Windows variable name -- OK
76         if (name.indexOf('=', 1)   != -1 ||
77             name.indexOf('\u0000') != -1)
78             throw new IllegalArgumentException
79                 ("Invalid environment variable name: \"" + name + "\"");
80         return name;
81     }
82 
validateValue(String value)83     private static String validateValue(String value) {
84         if (value.indexOf('\u0000') != -1)
85             throw new IllegalArgumentException
86                 ("Invalid environment variable value: \"" + value + "\"");
87         return value;
88     }
89 
nonNullString(Object o)90     private static String nonNullString(Object o) {
91         if (o == null)
92             throw new NullPointerException();
93         return (String) o;
94     }
95 
put(String key, String value)96     public String put(String key, String value) {
97         return super.put(validateName(key), validateValue(value));
98     }
99 
get(Object key)100     public String get(Object key) {
101         return super.get(nonNullString(key));
102     }
103 
containsKey(Object key)104     public boolean containsKey(Object key) {
105         return super.containsKey(nonNullString(key));
106     }
107 
containsValue(Object value)108     public boolean containsValue(Object value) {
109         return super.containsValue(nonNullString(value));
110     }
111 
remove(Object key)112     public String remove(Object key) {
113         return super.remove(nonNullString(key));
114     }
115 
116     private static class CheckedEntry
117         implements Map.Entry<String,String>
118     {
119         private final Map.Entry<String,String> e;
CheckedEntry(Map.Entry<String,String> e)120         public CheckedEntry(Map.Entry<String,String> e) {this.e = e;}
getKey()121         public String getKey()   { return e.getKey();}
getValue()122         public String getValue() { return e.getValue();}
setValue(String value)123         public String setValue(String value) {
124             return e.setValue(validateValue(value));
125         }
toString()126         public String toString() { return getKey() + "=" + getValue();}
equals(Object o)127         public boolean equals(Object o) {return e.equals(o);}
hashCode()128         public int hashCode()    {return e.hashCode();}
129     }
130 
131     private static class CheckedEntrySet
132         extends AbstractSet<Map.Entry<String,String>>
133     {
134         private final Set<Map.Entry<String,String>> s;
CheckedEntrySet(Set<Map.Entry<String,String>> s)135         public CheckedEntrySet(Set<Map.Entry<String,String>> s) {this.s = s;}
size()136         public int size()        {return s.size();}
isEmpty()137         public boolean isEmpty() {return s.isEmpty();}
clear()138         public void clear()      {       s.clear();}
iterator()139         public Iterator<Map.Entry<String,String>> iterator() {
140             return new Iterator<Map.Entry<String,String>>() {
141                 Iterator<Map.Entry<String,String>> i = s.iterator();
142                 public boolean hasNext() { return i.hasNext();}
143                 public Map.Entry<String,String> next() {
144                     return new CheckedEntry(i.next());
145                 }
146                 public void remove() { i.remove();}
147             };
148         }
checkedEntry(Object o)149         private static Map.Entry<String,String> checkedEntry(Object o) {
150             @SuppressWarnings("unchecked")
151             Map.Entry<String,String> e = (Map.Entry<String,String>) o;
152             nonNullString(e.getKey());
153             nonNullString(e.getValue());
154             return e;
155         }
contains(Object o)156         public boolean contains(Object o) {return s.contains(checkedEntry(o));}
remove(Object o)157         public boolean remove(Object o)   {return s.remove(checkedEntry(o));}
158     }
159 
160     private static class CheckedValues extends AbstractCollection<String> {
161         private final Collection<String> c;
CheckedValues(Collection<String> c)162         public CheckedValues(Collection<String> c) {this.c = c;}
size()163         public int size()                  {return c.size();}
isEmpty()164         public boolean isEmpty()           {return c.isEmpty();}
clear()165         public void clear()                {       c.clear();}
iterator()166         public Iterator<String> iterator() {return c.iterator();}
contains(Object o)167         public boolean contains(Object o)  {return c.contains(nonNullString(o));}
remove(Object o)168         public boolean remove(Object o)    {return c.remove(nonNullString(o));}
169     }
170 
171     private static class CheckedKeySet extends AbstractSet<String> {
172         private final Set<String> s;
CheckedKeySet(Set<String> s)173         public CheckedKeySet(Set<String> s) {this.s = s;}
size()174         public int size()                  {return s.size();}
isEmpty()175         public boolean isEmpty()           {return s.isEmpty();}
clear()176         public void clear()                {       s.clear();}
iterator()177         public Iterator<String> iterator() {return s.iterator();}
contains(Object o)178         public boolean contains(Object o)  {return s.contains(nonNullString(o));}
remove(Object o)179         public boolean remove(Object o)    {return s.remove(nonNullString(o));}
180     }
181 
keySet()182     public Set<String> keySet() {
183         return new CheckedKeySet(super.keySet());
184     }
185 
values()186     public Collection<String> values() {
187         return new CheckedValues(super.values());
188     }
189 
entrySet()190     public Set<Map.Entry<String,String>> entrySet() {
191         return new CheckedEntrySet(super.entrySet());
192     }
193 
194 
195     private static final class NameComparator
196         implements Comparator<String> {
compare(String s1, String s2)197         public int compare(String s1, String s2) {
198             // We can't use String.compareToIgnoreCase since it
199             // canonicalizes to lower case, while Windows
200             // canonicalizes to upper case!  For example, "_" should
201             // sort *after* "Z", not before.
202             int n1 = s1.length();
203             int n2 = s2.length();
204             int min = Math.min(n1, n2);
205             for (int i = 0; i < min; i++) {
206                 char c1 = s1.charAt(i);
207                 char c2 = s2.charAt(i);
208                 if (c1 != c2) {
209                     c1 = Character.toUpperCase(c1);
210                     c2 = Character.toUpperCase(c2);
211                     if (c1 != c2)
212                         // No overflow because of numeric promotion
213                         return c1 - c2;
214                 }
215             }
216             return n1 - n2;
217         }
218     }
219 
220     private static final class EntryComparator
221         implements Comparator<Map.Entry<String,String>> {
compare(Map.Entry<String,String> e1, Map.Entry<String,String> e2)222         public int compare(Map.Entry<String,String> e1,
223                            Map.Entry<String,String> e2) {
224             return nameComparator.compare(e1.getKey(), e2.getKey());
225         }
226     }
227 
228     // Allow `=' as first char in name, e.g. =C:=C:\DIR
229     static final int MIN_NAME_LENGTH = 1;
230 
231     private static final NameComparator nameComparator;
232     private static final EntryComparator entryComparator;
233     private static final ProcessEnvironment theEnvironment;
234     private static final Map<String,String> theUnmodifiableEnvironment;
235     private static final Map<String,String> theCaseInsensitiveEnvironment;
236 
237     static {
238         nameComparator  = new NameComparator();
239         entryComparator = new EntryComparator();
240         theEnvironment  = new ProcessEnvironment();
241         theUnmodifiableEnvironment
242             = Collections.unmodifiableMap(theEnvironment);
243 
244         String envblock = environmentBlock();
245         int beg, end, eql;
246         for (beg = 0;
247              ((end = envblock.indexOf('\u0000', beg  )) != -1 &&
248               // An initial `=' indicates a magic Windows variable name -- OK
249               (eql = envblock.indexOf('='     , beg+1)) != -1);
250              beg = end + 1) {
251             // Ignore corrupted environment strings.
252             if (eql < end)
envblock.substring(beg, eql)253                 theEnvironment.put(envblock.substring(beg, eql),
254                                    envblock.substring(eql+1,end));
255         }
256 
257         theCaseInsensitiveEnvironment = new TreeMap<>(nameComparator);
258         theCaseInsensitiveEnvironment.putAll(theEnvironment);
259     }
260 
ProcessEnvironment()261     private ProcessEnvironment() {
262         super();
263     }
264 
ProcessEnvironment(int capacity)265     private ProcessEnvironment(int capacity) {
266         super(capacity);
267     }
268 
269     // Only for use by System.getenv(String)
getenv(String name)270     static String getenv(String name) {
271         // The original implementation used a native call to _wgetenv,
272         // but it turns out that _wgetenv is only consistent with
273         // GetEnvironmentStringsW (for non-ASCII) if `wmain' is used
274         // instead of `main', even in a process created using
275         // CREATE_UNICODE_ENVIRONMENT.  Instead we perform the
276         // case-insensitive comparison ourselves.  At least this
277         // guarantees that System.getenv().get(String) will be
278         // consistent with System.getenv(String).
279         return theCaseInsensitiveEnvironment.get(name);
280     }
281 
282     // Only for use by System.getenv()
getenv()283     static Map<String,String> getenv() {
284         return theUnmodifiableEnvironment;
285     }
286 
287     // Only for use by ProcessBuilder.environment()
288     @SuppressWarnings("unchecked")
environment()289     static Map<String,String> environment() {
290         return (Map<String,String>) theEnvironment.clone();
291     }
292 
293     // Only for use by ProcessBuilder.environment(String[] envp)
emptyEnvironment(int capacity)294     static Map<String,String> emptyEnvironment(int capacity) {
295         return new ProcessEnvironment(capacity);
296     }
297 
environmentBlock()298     private static native String environmentBlock();
299 
300     // Only for use by ProcessImpl.start()
toEnvironmentBlock()301     String toEnvironmentBlock() {
302         // Sort Unicode-case-insensitively by name
303         List<Map.Entry<String,String>> list = new ArrayList<>(entrySet());
304         Collections.sort(list, entryComparator);
305 
306         StringBuilder sb = new StringBuilder(size()*30);
307         int cmp = -1;
308 
309         // Some versions of MSVCRT.DLL require SystemRoot to be set.
310         // So, we make sure that it is always set, even if not provided
311         // by the caller.
312         final String SYSTEMROOT = "SystemRoot";
313 
314         for (Map.Entry<String,String> e : list) {
315             String key = e.getKey();
316             String value = e.getValue();
317             if (cmp < 0 && (cmp = nameComparator.compare(key, SYSTEMROOT)) > 0) {
318                 // Not set, so add it here
319                 addToEnvIfSet(sb, SYSTEMROOT);
320             }
321             addToEnv(sb, key, value);
322         }
323         if (cmp < 0) {
324             // Got to end of list and still not found
325             addToEnvIfSet(sb, SYSTEMROOT);
326         }
327         if (sb.length() == 0) {
328             // Environment was empty and SystemRoot not set in parent
329             sb.append('\u0000');
330         }
331         // Block is double NUL terminated
332         sb.append('\u0000');
333         return sb.toString();
334     }
335 
336     // add the environment variable to the child, if it exists in parent
addToEnvIfSet(StringBuilder sb, String name)337     private static void addToEnvIfSet(StringBuilder sb, String name) {
338         String s = getenv(name);
339         if (s != null)
340             addToEnv(sb, name, s);
341     }
342 
addToEnv(StringBuilder sb, String name, String val)343     private static void addToEnv(StringBuilder sb, String name, String val) {
344         sb.append(name).append('=').append(val).append('\u0000');
345     }
346 
toEnvironmentBlock(Map<String,String> map)347     static String toEnvironmentBlock(Map<String,String> map) {
348         return map == null ? null :
349             ((ProcessEnvironment)map).toEnvironmentBlock();
350     }
351 }
352