1 /*
   2  * Copyright (c) 2010, 2015, 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 package jdk.nashorn.internal.runtime;
  27 
  28 import static jdk.nashorn.internal.lookup.Lookup.MH;
  29 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
  30 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
  31 
  32 import java.io.BufferedReader;
  33 import java.io.File;
  34 import java.io.IOException;
  35 import java.io.InputStream;
  36 import java.io.InputStreamReader;
  37 import java.io.OutputStream;
  38 import java.lang.invoke.MethodHandle;
  39 import java.lang.invoke.MethodHandles;
  40 import java.util.ArrayList;
  41 import java.util.Arrays;
  42 import java.util.HashMap;
  43 import java.util.List;
  44 import java.util.Map;
  45 import jdk.nashorn.internal.objects.NativeArray;
  46 import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError;
  47 
  48 /**
  49  * Global functions supported only in scripting mode.
  50  */
  51 public final class ScriptingFunctions {
  52 
  53     /** Handle to implementation of {@link ScriptingFunctions#readLine} - Nashorn extension */
  54     public static final MethodHandle READLINE = findOwnMH("readLine", Object.class, Object.class, Object.class);
  55 
  56     /** Handle to implementation of {@link ScriptingFunctions#readFully} - Nashorn extension */
  57     public static final MethodHandle READFULLY = findOwnMH("readFully",     Object.class, Object.class, Object.class);
  58 
  59     /** Handle to implementation of {@link ScriptingFunctions#exec} - Nashorn extension */
  60     public static final MethodHandle EXEC = findOwnMH("exec",     Object.class, Object.class, Object[].class);
  61 
  62     /** EXEC name - special property used by $EXEC API. */
  63     public static final String EXEC_NAME = "$EXEC";
  64 
  65     /** OUT name - special property used by $EXEC API. */
  66     public static final String OUT_NAME  = "$OUT";
  67 
  68     /** ERR name - special property used by $EXEC API. */
  69     public static final String ERR_NAME  = "$ERR";
  70 
  71     /** EXIT name - special property used by $EXEC API. */
  72     public static final String EXIT_NAME = "$EXIT";
  73 
  74     /** Names of special properties used by $ENV API. */
  75     public  static final String ENV_NAME  = "$ENV";
  76 
  77     /** Name of the environment variable for the current working directory. */
  78     public static final String PWD_NAME  = "PWD";
  79 
  80     private ScriptingFunctions() {
  81     }
  82 
  83     /**
  84      * Nashorn extension: global.readLine (scripting-mode-only)
  85      * Read one line of input from the standard input.
  86      *
  87      * @param self   self reference
  88      * @param prompt String used as input prompt
  89      *
  90      * @return line that was read
  91      *
  92      * @throws IOException if an exception occurs
  93      */
  94     public static Object readLine(final Object self, final Object prompt) throws IOException {
  95         if (prompt != UNDEFINED) {
  96             System.out.print(JSType.toString(prompt));
  97         }
  98         final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
  99         return reader.readLine();
 100     }
 101 
 102     /**
 103      * Nashorn extension: Read the entire contents of a text file and return as String.
 104      *
 105      * @param self self reference
 106      * @param file The input file whose content is read.
 107      *
 108      * @return String content of the input file.
 109      *
 110      * @throws IOException if an exception occurs
 111      */
 112     public static Object readFully(final Object self, final Object file) throws IOException {
 113         File f = null;
 114 
 115         if (file instanceof File) {
 116             f = (File)file;
 117         } else if (JSType.isString(file)) {
 118             f = new java.io.File(((CharSequence)file).toString());
 119         }
 120 
 121         if (f == null || !f.isFile()) {
 122             throw typeError("not.a.file", ScriptRuntime.safeToString(file));
 123         }
 124 
 125         return new String(Source.readFully(f));
 126     }
 127 
 128     /**
 129      * Nashorn extension: exec a string in a separate process.
 130      *
 131      * @param self   self reference
 132      * @param args   In one of four forms
 133      *               1. String script, String input
 134      *               2. String script, InputStream input, OutputStream output, OutputStream error
 135      *               3. Array scriptTokens, String input
 136      *               4. Array scriptTokens, InputStream input, OutputStream output, OutputStream error
 137      *
 138      * @return output string from the request if in form of 1. or 3., empty string otherwise
 139      */
 140     public static Object exec(final Object self, final Object... args) {
 141         final Object arg0 = args.length > 0 ? args[0] : UNDEFINED;
 142         final Object arg1 = args.length > 1 ? args[1] : UNDEFINED;
 143         final Object arg2 = args.length > 2 ? args[2] : UNDEFINED;
 144         final Object arg3 = args.length > 3 ? args[3] : UNDEFINED;
 145 
 146         InputStream inputStream = null;
 147         OutputStream outputStream = null;
 148         OutputStream errorStream = null;
 149         String script = null;
 150         List<String> tokens = null;
 151         String inputString = null;
 152 
 153         if (arg0 instanceof NativeArray) {
 154             String[] array = (String[])JSType.toJavaArray(arg0, String.class);
 155             tokens = new ArrayList<>();
 156             tokens.addAll(Arrays.asList(array));
 157         } else {
 158             script = JSType.toString(arg0);
 159         }
 160 
 161         if (arg1 instanceof InputStream) {
 162             inputStream = (InputStream)arg1;
 163         } else {
 164             inputString = JSType.toString(arg1);
 165         }
 166 
 167         if (arg2 instanceof OutputStream) {
 168             outputStream = (OutputStream)arg2;
 169         }
 170 
 171         if (arg3 instanceof OutputStream) {
 172             errorStream = (OutputStream)arg3;
 173         }
 174 
 175         // Current global is need to fetch additional inputs and for additional results.
 176         final ScriptObject global = Context.getGlobal();
 177 
 178         // Capture ENV property state.
 179         final Map<String, String> environment = new HashMap<>();
 180         final Object env = global.get(ENV_NAME);
 181 
 182         if (env instanceof ScriptObject) {
 183             final ScriptObject envProperties = (ScriptObject)env;
 184 
 185             // Copy ENV variables.
 186             envProperties.entrySet().stream().forEach((entry) -> {
 187                 environment.put(JSType.toString(entry.getKey()), JSType.toString(entry.getValue()));
 188             });
 189         }
 190 
 191         // get the $EXEC function object from the global object
 192         final Object exec = global.get(EXEC_NAME);
 193         assert exec instanceof ScriptObject : EXEC_NAME + " is not a script object!";
 194 
 195         // Execute the commands
 196         final CommandExecutor executor = new CommandExecutor();
 197         executor.setInputString(inputString);
 198         executor.setInputStream(inputStream);
 199         executor.setOutputStream(outputStream);
 200         executor.setErrorStream(errorStream);
 201         executor.setEnvironment(environment);
 202 
 203         if (tokens != null) {
 204             executor.process(tokens);
 205         } else {
 206             executor.process(script);
 207         }
 208 
 209         final String outString = executor.getOutputString();
 210         final String errString = executor.getErrorString();
 211         int exitCode = executor.getExitCode();
 212 
 213         // Set globals for secondary results.
 214         global.set(OUT_NAME, outString, 0);
 215         global.set(ERR_NAME, errString, 0);
 216         global.set(EXIT_NAME, exitCode, 0);
 217 
 218         // Return the result from stdout.
 219         return outString;
 220     }
 221 
 222     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
 223         return MH.findStatic(MethodHandles.lookup(), ScriptingFunctions.class, name, MH.type(rtype, types));
 224     }
 225 }