| /* |
| * To change this template, choose Tools | Templates |
| * and open the template in the editor. |
| */ |
| package jnr.posix.util; |
| |
| import jnr.ffi.*; |
| |
| import java.io.File; |
| import java.io.UnsupportedEncodingException; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.StringTokenizer; |
| import jnr.posix.POSIX; |
| |
| /** |
| * |
| * @author enebo |
| */ |
| public class WindowsHelpers { |
| static final jnr.ffi.Runtime runtime = jnr.ffi.Runtime.getSystemRuntime(); |
| static final int WORDSIZE = jnr.ffi.Runtime.getSystemRuntime().addressSize(); |
| |
| public static byte[] toWPath(String path) { |
| return toWString(path); |
| } |
| |
| public static byte[] toWString(String string) { |
| if (string == null) return null; |
| |
| string += (char) 0; |
| |
| try { |
| return string.getBytes("UTF-16LE"); |
| } catch (UnsupportedEncodingException e) { |
| return null; // JVM mandates this encoding. Not reached |
| } |
| } |
| |
| // FIXME: This does not work and I am unsure if it is because I am violating something |
| // CreateProcess requires OR because there are weird requirements in how env needs to be |
| // setup for CreateProcess (e.g. =C:=C:/ vars). |
| public static Pointer createWideEnv(String[] envp) { |
| if (envp == null) return null; |
| byte[] marker = {0}; |
| int envLength = envp.length; |
| |
| // Allocate pointer for env pointer entries plus last \0\0 marker |
| Pointer result = Memory.allocateDirect(runtime, WORDSIZE * (envLength + 1)); |
| |
| for (int i = 0; i < envLength; i++) { |
| byte[] bytes = toWString(envp[i]); |
| Pointer envElement = Memory.allocateDirect(runtime, bytes.length + 1); |
| envElement.put(0, bytes, 0, bytes.length); |
| envElement.put(bytes.length, marker, 0, marker.length); |
| result.putPointer(i * WORDSIZE, envElement); |
| } |
| |
| Pointer nullMarker = Memory.allocateDirect(runtime, marker.length); |
| nullMarker.put(0, marker, 0, marker.length); |
| result.putPointer(WORDSIZE * envLength, nullMarker); |
| |
| |
| return result; |
| } |
| // Windows cmd strings have various escaping: |
| // 1. <>|^ can all be escaped with ^ (e.g. ^<) |
| // 2. \s\t must be quoted if not already |
| // 3. Any arguments with double quotes must be escaped with a double |
| // quote around whole cmd |
| private static void joinSingleArgv(StringBuilder buffer, String arg, |
| boolean quote, boolean escape) { |
| int backslashCount = 0; |
| int start = 0; |
| |
| if (quote) buffer.append('"'); |
| |
| for (int i = 0; i < arg.length(); i++) { |
| char c = arg.charAt(i); |
| switch(c) { |
| case '\\': |
| backslashCount++; |
| break; |
| case '"': { |
| buffer.append(arg.substring(start, i)); |
| for (int j = 0; j < backslashCount + 1; j++) { |
| buffer.append('\\'); |
| } |
| backslashCount = 0; |
| start = i; |
| } |
| case '<': case '>': case '|': case '^': { |
| if (escape && !quote) { |
| buffer.append(arg.substring(start, i)); |
| buffer.append('^'); |
| start = i; |
| break; |
| } |
| } |
| default: { |
| backslashCount = 0; |
| break; |
| } |
| } |
| } |
| buffer.append(arg.substring(start)); |
| |
| if (quote) buffer.append('"'); |
| } |
| |
| public static String joinArgv(String command, String[] argv, boolean escape) { |
| StringBuilder buffer = new StringBuilder(); |
| |
| if (command != null) { |
| buffer.append(command); |
| buffer.append(' '); |
| } |
| |
| int last_index = argv.length - 1; |
| for (int i = 0; i <= last_index; i++) { |
| joinSingleArgv(buffer, argv[i], quotable(argv[i]), escape); |
| if (i != last_index) buffer.append(' '); // Add space between arguments |
| } |
| |
| return buffer.toString(); |
| } |
| |
| |
| public static boolean quotable(String value) { |
| if (value == null) return false; |
| StringTokenizer toker = new StringTokenizer(value, " \t\"'"); |
| toker.nextToken(); // We know a string with no delimeters will return self |
| return toker.hasMoreTokens(); |
| } |
| |
| public static boolean isBatch(String value) { |
| if (value == null) return false; |
| int length = value.length(); |
| |
| if (length < 5) return false; |
| |
| String end = value.substring(length - 4); |
| |
| return end.equalsIgnoreCase(".bat") || end.equalsIgnoreCase(".cmd"); |
| } |
| |
| public static String[] processCommandLine(POSIX posix, String command, |
| String program, String path) { |
| String shell = null; |
| |
| if (program != null) { |
| String fullPath = Finder.findFileInPath(posix, program, path); |
| |
| shell = fullPath == null ? program : fullPath.replace('/', '\\'); |
| } else { |
| // Strip off leading whitespace |
| command = command.substring(firstNonWhitespaceIndex(command)); |
| |
| // FIXME: Ruby first looks for RUBYSHELL, but this only applies for |
| // JRuby (I doubt Jython wants to honor that env). We need a generic |
| // hook for other envs to look for? |
| shell = System.getenv("COMSPEC"); |
| boolean notHandledYet = true; |
| if (shell != null) { |
| boolean commandDotCom = isCommandDotCom(shell); |
| if (hasBuiltinSpecialNeeds(command) || isInternalCommand(command, commandDotCom)) { |
| String quote = commandDotCom ? "\"" : ""; |
| command = shell + " /c " + quote + command + quote; |
| notHandledYet = false; |
| } |
| } |
| |
| if (notHandledYet) { |
| char firstChar = command.charAt(0); |
| char quote = firstChar == '"' ? firstChar : (firstChar == '\'' ? firstChar : (char) 0); |
| int commandLength = command.length(); |
| |
| int i = quote == 0 ? 0 : 1; |
| |
| for(;; i++) { |
| if (i == commandLength) { |
| shell = command; |
| break; |
| } |
| |
| char c = command.charAt(i); |
| |
| if (c == quote) { |
| shell = command.substring(1, i); |
| break; |
| } |
| if (quote != 0) continue; |
| |
| if (Character.isSpaceChar(c) || isFunnyChar(c)) { |
| shell = command.substring(0, i); |
| break; |
| } |
| } |
| shell = Finder.findFileInPath(posix, shell, path); |
| |
| if (shell == null) { |
| shell = command.substring(0, i); |
| } else { |
| if (!shell.contains(" ")) quote = 0; |
| |
| shell = shell.replace('/', '\\'); |
| } |
| } |
| } |
| |
| return new String[] { command, shell }; |
| } |
| |
| public static String[] processCommandArgs(POSIX posix, String program, |
| String[] argv, String path) { |
| if (program == null || program.length() == 0) program = argv[0]; |
| |
| boolean addSlashC = false; |
| boolean isNotBuiltin = false; |
| boolean notHandledYet = true; |
| String shell = System.getenv("COMSPEC"); |
| String command = null; |
| if (shell != null) { |
| boolean commandDotCom = isCommandDotCom(shell); |
| if (isInternalCommand(program, commandDotCom)) { |
| isNotBuiltin = !commandDotCom; |
| program = shell; |
| addSlashC = true; |
| notHandledYet = false; |
| } |
| } |
| if (notHandledYet) { |
| command = Finder.findFileInPath(posix, program, path); |
| if (command != null) { |
| program = command.replace('/', '\\'); |
| } else if (program.contains("/")) { |
| command = program.replace('/', '\\'); |
| program = command; |
| } |
| } |
| |
| if (addSlashC || isBatch(program)) { |
| if (addSlashC) { |
| command = program + " /c "; |
| } else { |
| String[] newArgv = new String[argv.length - 1]; |
| System.arraycopy(argv, 1, newArgv, 0, argv.length - 1); |
| argv = newArgv; |
| } |
| |
| if (argv.length > 0) { |
| command = WindowsHelpers.joinArgv(command, argv, isNotBuiltin); |
| } |
| program = addSlashC ? shell : null; |
| } else { |
| command = WindowsHelpers.joinArgv(null, argv, false); |
| } |
| |
| return new String[] { command, program }; |
| } |
| |
| private static boolean isFunnyChar(char c) { |
| return c == '<' || c == '>' || c == '|' || c == '*' || c == '?' || |
| c == '"'; |
| } |
| |
| private static boolean hasBuiltinSpecialNeeds(String value) { |
| int length = value.length(); |
| char quote = '\0'; |
| |
| for (int i = 0; i < length; i++) { |
| char c = value.charAt(i); |
| switch (c) { |
| case '\'': case '\"': |
| if (quote == '\0') { |
| quote = c; |
| } else if (quote == c) { |
| quote = '\0'; |
| } |
| break; |
| case '>': case '<': case '|': case '\n': |
| if (quote != '\0') return true; |
| break; |
| case '%': // %FOO% check |
| if (i + 1 < length) { |
| i += 1; |
| char c2 = value.charAt(i); |
| if (c2 != ' ' && !Character.isLetter(c2)) break; |
| for (int j = i; j < length; j++) { |
| c2 = value.charAt(j); |
| if (c2 != ' ' && !Character.isLetterOrDigit(c2)) break; |
| } |
| if (c2 == '%') return true; |
| } |
| break; |
| } |
| } |
| return false; |
| } |
| |
| private static int firstNonWhitespaceIndex(String value) { |
| int length = value.length(); |
| int i = 0; |
| for (; i < length && Character.isSpaceChar(value.charAt(i)); i++) {} |
| return i; |
| } |
| |
| public static String escapePath(String path) { |
| StringBuilder buf = new StringBuilder(); |
| |
| for (int i = 0; i < path.length(); i++) { |
| char c = path.charAt(i); |
| |
| buf.append(c); |
| if (c == '\\') buf.append(c); |
| } |
| return buf.toString() + "\\\\"; |
| } |
| |
| private final static String COMMAND_DOT_COM = "command.com"; |
| private final static int CDC_LENGTH = COMMAND_DOT_COM.length(); |
| private enum InternalType { SHELL, COMMAND, BOTH }; |
| private static Map<String, InternalType> INTERNAL_COMMANDS = new HashMap<String, InternalType>() {{ |
| put("assoc", InternalType.COMMAND); |
| put("break", InternalType.BOTH); |
| put("call", InternalType.BOTH); |
| put("cd", InternalType.BOTH); |
| put("chcp", InternalType.SHELL); |
| put("chdir", InternalType.BOTH); |
| put("cls", InternalType.BOTH); |
| put("color", InternalType.COMMAND); |
| put("copy", InternalType.BOTH); |
| put("ctty", InternalType.SHELL); |
| put("date", InternalType.BOTH); |
| put("del", InternalType.BOTH); |
| put("dir", InternalType.BOTH); |
| put("echo", InternalType.BOTH); |
| put("endlocal", InternalType.COMMAND); |
| put("erase", InternalType.BOTH); |
| put("exit", InternalType.BOTH); |
| put("for", InternalType.BOTH); |
| put("ftype", InternalType.COMMAND); |
| put("goto", InternalType.BOTH); |
| put("if", InternalType.BOTH); |
| put("lfnfor", InternalType.SHELL); |
| put("lh", InternalType.SHELL); |
| put("lock", InternalType.SHELL); |
| put("md", InternalType.BOTH); |
| put("mkdir", InternalType.BOTH); |
| put("move", InternalType.COMMAND); |
| put("path", InternalType.BOTH); |
| put("pause", InternalType.BOTH); |
| put("popd", InternalType.COMMAND); |
| put("prompt", InternalType.BOTH); |
| put("pushd", InternalType.COMMAND); |
| put("rd", InternalType.BOTH); |
| put("rem", InternalType.BOTH); |
| put("ren", InternalType.BOTH); |
| put("rename", InternalType.BOTH); |
| put("rmdir", InternalType.BOTH); |
| put("set", InternalType.BOTH); |
| put("setlocal", InternalType.COMMAND); |
| put("shift", InternalType.BOTH); |
| put("start", InternalType.COMMAND); |
| put("time", InternalType.BOTH); |
| put("title", InternalType.COMMAND); |
| put("truename", InternalType.SHELL); |
| put("type", InternalType.BOTH); |
| put("unlock", InternalType.SHELL); |
| put("ver", InternalType.BOTH); |
| put("verify", InternalType.BOTH); |
| put("vol", InternalType.BOTH); |
| }}; |
| |
| private static boolean isDirectorySeparator(char value) { |
| return value == '/' || value == '\\'; |
| } |
| private static boolean isCommandDotCom(String command) { |
| int length = command.length(); |
| int i = length - CDC_LENGTH; |
| |
| return i == 0 || i > 0 && isDirectorySeparator(command.charAt(i - 1)) && |
| command.regionMatches(true, i, COMMAND_DOT_COM, 0, CDC_LENGTH); |
| } |
| |
| private static boolean isInternalCommand(String command, boolean hasCommandDotCom) { |
| assert command != null && !Character.isSpaceChar(command.charAt(0)) : "Spaces should have been stripped off already"; |
| |
| int length = command.length(); |
| |
| StringBuilder buf = new StringBuilder(); |
| int i = 0; |
| char c = 0; |
| for (; i < length; i++) { |
| c = command.charAt(i); |
| if (!Character.isLetter(c)) break; |
| buf.append(Character.toLowerCase(c)); |
| } |
| |
| if (i < length) { |
| if (c == '.' && i + 1 < length) i++; |
| |
| switch (command.charAt(i)) { |
| case '<': case '>': case '|': |
| return true; |
| case '\0': case ' ': case '\t': case '\n': |
| break; |
| default: |
| return false; |
| } |
| } |
| |
| InternalType kindOf = INTERNAL_COMMANDS.get(buf.toString()); |
| return kindOf == InternalType.BOTH || |
| (hasCommandDotCom ? kindOf == InternalType.COMMAND : kindOf == InternalType.SHELL); |
| } |
| |
| public static boolean isDriveLetterPath(String path) { |
| return path.length() >= 2 && Character.isLetter(path.charAt(0)) && path.charAt(1) == ':'; |
| } |
| |
| } |