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 
  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     static void refreshEnv() {
 270         // TODO
 271     }
 272 
 273     // Only for use by System.getenv(String)
 274     static String getenv(String name) {
 275         // The original implementation used a native call to _wgetenv,
 276         // but it turns out that _wgetenv is only consistent with
 277         // GetEnvironmentStringsW (for non-ASCII) if `wmain' is used
 278         // instead of `main', even in a process created using
 279         // CREATE_UNICODE_ENVIRONMENT.  Instead we perform the
 280         // case-insensitive comparison ourselves.  At least this
 281         // guarantees that System.getenv().get(String) will be
 282         // consistent with System.getenv(String).
 283         return theCaseInsensitiveEnvironment.get(name);
 284     }
 285 
 286     // Only for use by System.getenv()
 287     static Map<String,String> getenv() {
 288         return theUnmodifiableEnvironment;
 289     }
 290 
 291     // Only for use by ProcessBuilder.environment()
 292     @SuppressWarnings("unchecked")
 293     static Map<String,String> environment() {
 294         return (Map<String,String>) theEnvironment.clone();
 295     }
 296 
 297     // Only for use by ProcessBuilder.environment(String[] envp)
 298     static Map<String,String> emptyEnvironment(int capacity) {
 299         return new ProcessEnvironment(capacity);
 300     }
 301 
 302     private static native String environmentBlock();
 303 
 304     // Only for use by ProcessImpl.start()
 305     String toEnvironmentBlock() {
 306         // Sort Unicode-case-insensitively by name
 307         List<Map.Entry<String,String>> list = new ArrayList<>(entrySet());
 308         Collections.sort(list, entryComparator);
 309 
 310         StringBuilder sb = new StringBuilder(size()*30);
 311         int cmp = -1;
 312 
 313         // Some versions of MSVCRT.DLL require SystemRoot to be set.
 314         // So, we make sure that it is always set, even if not provided
 315         // by the caller.
 316         final String SYSTEMROOT = "SystemRoot";
 317 
 318         for (Map.Entry<String,String> e : list) {
 319             String key = e.getKey();
 320             String value = e.getValue();
 321             if (cmp < 0 && (cmp = nameComparator.compare(key, SYSTEMROOT)) > 0) {
 322                 // Not set, so add it here
 323                 addToEnvIfSet(sb, SYSTEMROOT);
 324             }
 325             addToEnv(sb, key, value);
 326         }
 327         if (cmp < 0) {
 328             // Got to end of list and still not found
 329             addToEnvIfSet(sb, SYSTEMROOT);
 330         }
 331         if (sb.length() == 0) {
 332             // Environment was empty and SystemRoot not set in parent
 333             sb.append('\u0000');
 334         }
 335         // Block is double NUL terminated
 336         sb.append('\u0000');
 337         return sb.toString();
 338     }
 339 
 340     // add the environment variable to the child, if it exists in parent
 341     private static void addToEnvIfSet(StringBuilder sb, String name) {
 342         String s = getenv(name);
 343         if (s != null)
 344             addToEnv(sb, name, s);
 345     }
 346 
 347     private static void addToEnv(StringBuilder sb, String name, String val) {
 348         sb.append(name).append('=').append(val).append('\u0000');
 349     }
 350 
 351     static String toEnvironmentBlock(Map<String,String> map) {
 352         return map == null ? null :
 353             ((ProcessEnvironment)map).toEnvironmentBlock();
 354     }
 355 }