// Copyright 2014 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.util; import com.google.common.base.CharMatcher; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.Iterables; import com.google.common.escape.CharEscaperBuilder; import com.google.common.escape.Escaper; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import java.io.IOException; /** * Utility class to escape strings for use with shell commands. * *
Escaped strings may safely be inserted into shell commands. Escaping is * only done if necessary. Strings containing only shell-neutral characters * will not be escaped. * *
This is a replacement for {@code ShellUtils.shellEscape(String)} and * {@code ShellUtils.prettyPrintArgv(java.util.List)} (see * {@link com.google.devtools.build.lib.shell.ShellUtils}). Its advantage is the use * of standard building blocks from the {@code com.google.common.base} * package, such as {@link Joiner} and {@link CharMatcher}, making this class * more efficient and reliable than {@code ShellUtils}. * *
The behavior is slightly different though: this implementation will
* defensively escape non-ASCII letters and digits, whereas
* {@code shellEscape} does not.
*/
@Immutable
public final class ShellEscaper extends Escaper {
// Note: extending Escaper may seem desirable, but is in fact harmful.
// The class would then need to implement escape(Appendable), returning an Appendable
// that escapes everything it receives. In case of shell escaping, we most often join
// string parts on spaces, using a Joiner. Spaces are escaped characters. Using the
// Appendable returned by escape(Appendable) would escape these spaces too, which
// is unwanted.
public static final ShellEscaper INSTANCE = new ShellEscaper();
private static final Function A string is not escaped iff it only contains safe characters.
* The following characters are safe:
* A string is escaped iff it contains at least one non-safe character.
* Escaped strings are created by replacing every occurrence of single
* quotes with the string '\'' and enclosing the result in a pair of
* single quotes.
*
* Examples:
* This method works as if by invoking
* {@link #escapeJoinAll(Appendable, Iterable, Joiner)} with
* {@code Joiner.on(' ')}.
*
* @param out what the result will be appended to
* @param argv the strings to escape and join
* @return the same reference as {@code out}, now containing the the
* joined, escaped fragments
* @throws IOException if an I/O error occurs while appending
*/
public static Appendable escapeJoinAll(Appendable out, Iterable extends String> argv)
throws IOException {
return SPACE_JOINER.appendTo(out, escapeAll(argv));
}
/**
* Escapes all strings in {@code argv} individually and joins them into
* {@code out} using the specified {@link Joiner}. The result is appended
* directly into {@code out}, without adding a separator.
*
* The resulting strings are the same as if escaped one by one using
* {@link #escapeString(String)}.
*
* Example: if the joiner is {@code Joiner.on('|')}, then the input
* {@code ["abc", "de'f"]} will be escaped as "{@code abc|'de'\''f'}".
* If {@code out} initially contains "{@code 123}", then the returned
* {@code Appendable} will contain "{@code 123abc|'de'\''f'}".
*
* @param out what the result will be appended to
* @param argv the strings to escape and join
* @param joiner the {@link Joiner} to use to join the escaped strings
* @return the same reference as {@code out}, now containing the the
* joined, escaped fragments
* @throws IOException if an I/O error occurs while appending
*/
public static Appendable escapeJoinAll(Appendable out, Iterable extends String> argv,
Joiner joiner) throws IOException {
return joiner.appendTo(out, escapeAll(argv));
}
/**
* Escapes all strings in {@code argv} individually and joins them on
* single spaces, then returns the resulting string.
*
* This method works as if by invoking
* {@link #escapeJoinAll(Iterable, Joiner)} with {@code Joiner.on(' ')}.
*
* Example: {@code ["abc", "de'f"]} will be escaped and joined as
* "abc 'de'\''f'".
*
* @param argv the strings to escape and join
* @return the string of escaped and joined input elements
*/
public static String escapeJoinAll(Iterable extends String> argv) {
return SPACE_JOINER.join(escapeAll(argv));
}
/**
* Escapes all strings in {@code argv} individually and joins them using
* the specified {@link Joiner}, then returns the resulting string.
*
* The resulting strings are the same as if escaped one by one using
* {@link #escapeString(String)}.
*
* Example: if the joiner is {@code Joiner.on('|')}, then the input
* {@code ["abc", "de'f"]} will be escaped and joined as "abc|'de'\''f'".
*
* @param argv the strings to escape and join
* @param joiner the {@link Joiner} to use to join the escaped strings
* @return the string of escaped and joined input elements
*/
public static String escapeJoinAll(Iterable extends String> argv, Joiner joiner) {
return joiner.join(escapeAll(argv));
}
private ShellEscaper() {
// Utility class - do not instantiate.
}
}
*
*
*
*
*/
@Override
public String escape(String unescaped) {
final String s = unescaped.toString();
if (s.isEmpty()) {
// Empty string is a special case: needs to be quoted to ensure that it
// gets treated as a separate argument.
return "''";
} else {
return SAFECHAR_MATCHER.matchesAllOf(s)
? s
: "'" + STRONGQUOTE_ESCAPER.escape(s) + "'";
}
}
public static String escapeString(String unescaped) {
return INSTANCE.escape(unescaped);
}
/**
* Transforms the input {@code Iterable} of unescaped strings to an
* {@code Iterable} of escaped ones. The escaping is done lazily.
*/
public static Iterable