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
64package java.lang;
65
66import java.io.*;
67import java.util.*;
68
69final class ProcessEnvironment extends HashMap<String,String>
70{
71
72    private static final long serialVersionUID = -8017839552603542824L;
73
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
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
90    private static String nonNullString(Object o) {
91        if (o == null)
92            throw new NullPointerException();
93        return (String) o;
94    }
95
96    public String put(String key, String value) {
97        return super.put(validateName(key), validateValue(value));
98    }
99
100    public String get(Object key) {
101        return super.get(nonNullString(key));
102    }
103
104    public boolean containsKey(Object key) {
105        return super.containsKey(nonNullString(key));
106    }
107
108    public boolean containsValue(Object value) {
109        return super.containsValue(nonNullString(value));
110    }
111
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;
120        public CheckedEntry(Map.Entry<String,String> e) {this.e = e;}
121        public String getKey()   { return e.getKey();}
122        public String getValue() { return e.getValue();}
123        public String setValue(String value) {
124            return e.setValue(validateValue(value));
125        }
126        public String toString() { return getKey() + "=" + getValue();}
127        public boolean equals(Object o) {return e.equals(o);}
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;
135        public CheckedEntrySet(Set<Map.Entry<String,String>> s) {this.s = s;}
136        public int size()        {return s.size();}
137        public boolean isEmpty() {return s.isEmpty();}
138        public void clear()      {       s.clear();}
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        }
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        }
156        public boolean contains(Object o) {return s.contains(checkedEntry(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;
162        public CheckedValues(Collection<String> c) {this.c = c;}
163        public int size()                  {return c.size();}
164        public boolean isEmpty()           {return c.isEmpty();}
165        public void clear()                {       c.clear();}
166        public Iterator<String> iterator() {return c.iterator();}
167        public boolean contains(Object o)  {return c.contains(nonNullString(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;
173        public CheckedKeySet(Set<String> s) {this.s = s;}
174        public int size()                  {return s.size();}
175        public boolean isEmpty()           {return s.isEmpty();}
176        public void clear()                {       s.clear();}
177        public Iterator<String> iterator() {return s.iterator();}
178        public boolean contains(Object o)  {return s.contains(nonNullString(o));}
179        public boolean remove(Object o)    {return s.remove(nonNullString(o));}
180    }
181
182    public Set<String> keySet() {
183        return new CheckedKeySet(super.keySet());
184    }
185
186    public Collection<String> values() {
187        return new CheckedValues(super.values());
188    }
189
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> {
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>> {
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)
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
261    private ProcessEnvironment() {
262        super();
263    }
264
265    private ProcessEnvironment(int capacity) {
266        super(capacity);
267    }
268
269    // Only for use by System.getenv(String)
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()
283    static Map<String,String> getenv() {
284        return theUnmodifiableEnvironment;
285    }
286
287    // Only for use by ProcessBuilder.environment()
288    @SuppressWarnings("unchecked")
289    static Map<String,String> environment() {
290        return (Map<String,String>) theEnvironment.clone();
291    }
292
293    // Only for use by ProcessBuilder.environment(String[] envp)
294    static Map<String,String> emptyEnvironment(int capacity) {
295        return new ProcessEnvironment(capacity);
296    }
297
298    private static native String environmentBlock();
299
300    // Only for use by ProcessImpl.start()
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
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
343    private static void addToEnv(StringBuilder sb, String name, String val) {
344        sb.append(name).append('=').append(val).append('\u0000');
345    }
346
347    static String toEnvironmentBlock(Map<String,String> map) {
348        return map == null ? null :
349            ((ProcessEnvironment)map).toEnvironmentBlock();
350    }
351}
352