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         boolean foundSysRoot = false;
 303         for (Map.Entry<String,String> e : list) {
 304             String key = e.getKey();
 305             String val = e.getValue();
 306             if (!foundSysRoot) {
 307                 int cmp = key.compareToIgnoreCase("systemroot");
 308                 if (cmp == 0) {
 309                     foundSysRoot = true;
 310                 } else if (cmp > 0) {
 311                     // SystemRoot not set, so add it here
 312                     // must use lowercase name, to preserve sort position
 313                     addEnv(sb, "systemroot");
 314                     foundSysRoot = true;
 315                 }
 316             }
 317             sb.append(key)
 318               .append('=')
 319               .append(val)
 320               .append('\u0000');
 321         }
 322         if (!foundSysRoot) {
 323             // must use lowercase name, to preserve sort position
 324             addEnv(sb, "systemroot");
 325         }
 326         // Ensure double NUL termination,
 327         // even if environment is empty.
 328         if (sb.length() == 0)
 329             sb.append('\u0000');
 330         sb.append('\u0000');
 331         return sb.toString();
 332     }
 333 
 334     // add the environment variable to the child, if it exists in parent
 335     private static void addEnv(StringBuilder sb, String envName) {
 336         String s = getenv(envName);
 337         if (s != null) {
 338             sb.append(envName)
 339               .append("=")
 340               .append(s)
 341               .append('\u0000');
 342         }
 343     }
 344 
 345     static String toEnvironmentBlock(Map<String,String> map) {
 346         return map == null ? null :
 347             ((ProcessEnvironment)map).toEnvironmentBlock();
 348     }
 349 }