NekoX/TMessagesProj/src/main/java/com/github/shadowsocks/utils/Commandline.kt

160 lines
6.7 KiB
Kotlin

/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.utils
import java.util.*
/**
* Commandline objects help handling command lines specifying processes to
* execute.
*
* The class can be used to define a command line as nested elements or as a
* helper to define a command line by an application.
*
*
* `
* <someelement><br></br>
* &nbsp;&nbsp;<acommandline executable="/executable/to/run"><br></br>
* &nbsp;&nbsp;&nbsp;&nbsp;<argument value="argument 1" /><br></br>
* &nbsp;&nbsp;&nbsp;&nbsp;<argument line="argument_1 argument_2 argument_3" /><br></br>
* &nbsp;&nbsp;&nbsp;&nbsp;<argument value="argument 4" /><br></br>
* &nbsp;&nbsp;</acommandline><br></br>
* </someelement><br></br>
` *
*
* Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java
*
* Adds support for escape character '\'.
*/
object Commandline {
/**
* Quote the parts of the given array in way that makes them
* usable as command line arguments.
* @param args the list of arguments to quote.
* @return empty string for null or no command, else every argument split
* by spaces and quoted by quoting rules.
*/
fun toString(args: Iterable<String>?): String {
// empty path return empty string
args ?: return ""
// path containing one or more elements
val result = StringBuilder()
for (arg in args) {
if (result.isNotEmpty()) result.append(' ')
arg.indices.map { arg[it] }.forEach {
when (it) {
' ', '\\', '"', '\'' -> {
result.append('\\') // intentionally no break
result.append(it)
}
else -> result.append(it)
}
}
}
return result.toString()
}
/**
* Quote the parts of the given array in way that makes them
* usable as command line arguments.
* @param args the list of arguments to quote.
* @return empty string for null or no command, else every argument split
* by spaces and quoted by quoting rules.
*/
fun toString(args: Array<String>) = toString(args.asIterable()) // thanks to Java, arrays aren't iterable
/**
* Crack a command line.
* @param toProcess the command line to process.
* @return the command line broken into strings.
* An empty or null toProcess parameter results in a zero sized array.
*/
fun translateCommandline(toProcess: String?): Array<String> {
if (toProcess == null || toProcess.isEmpty()) {
//no command? no string
return arrayOf()
}
// parse with a simple finite state machine
val normal = 0
val inQuote = 1
val inDoubleQuote = 2
var state = normal
val tok = StringTokenizer(toProcess, "\\\"\' ", true)
val result = ArrayList<String>()
val current = StringBuilder()
var lastTokenHasBeenQuoted = false
var lastTokenIsSlash = false
while (tok.hasMoreTokens()) {
val nextTok = tok.nextToken()
when (state) {
inQuote -> if ("\'" == nextTok) {
lastTokenHasBeenQuoted = true
state = normal
} else current.append(nextTok)
inDoubleQuote -> when (nextTok) {
"\"" -> if (lastTokenIsSlash) {
current.append(nextTok)
lastTokenIsSlash = false
} else {
lastTokenHasBeenQuoted = true
state = normal
}
"\\" -> lastTokenIsSlash = if (lastTokenIsSlash) {
current.append(nextTok)
false
} else true
else -> {
if (lastTokenIsSlash) {
current.append("\\") // unescaped
lastTokenIsSlash = false
}
current.append(nextTok)
}
}
else -> {
when {
lastTokenIsSlash -> {
current.append(nextTok)
lastTokenIsSlash = false
}
"\\" == nextTok -> lastTokenIsSlash = true
"\'" == nextTok -> state = inQuote
"\"" == nextTok -> state = inDoubleQuote
" " == nextTok -> if (lastTokenHasBeenQuoted || current.isNotEmpty()) {
result.add(current.toString())
current.setLength(0)
}
else -> current.append(nextTok)
}
lastTokenHasBeenQuoted = false
}
}
}
if (lastTokenHasBeenQuoted || current.isNotEmpty()) result.add(current.toString())
require(state != inQuote && state != inDoubleQuote) { "unbalanced quotes in $toProcess" }
require(!lastTokenIsSlash) { "escape character following nothing in $toProcess" }
return result.toTypedArray()
}
}