1*d68f33bcSAndroid Build Coastguard Worker# Copyright 2016 The Android Open Source Project 2*d68f33bcSAndroid Build Coastguard Worker# 3*d68f33bcSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 4*d68f33bcSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 5*d68f33bcSAndroid Build Coastguard Worker# You may obtain a copy of the License at 6*d68f33bcSAndroid Build Coastguard Worker# 7*d68f33bcSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 8*d68f33bcSAndroid Build Coastguard Worker# 9*d68f33bcSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 10*d68f33bcSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 11*d68f33bcSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*d68f33bcSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 13*d68f33bcSAndroid Build Coastguard Worker# limitations under the License. 14*d68f33bcSAndroid Build Coastguard Worker 15*d68f33bcSAndroid Build Coastguard Worker"""Functions for working with shell code.""" 16*d68f33bcSAndroid Build Coastguard Worker 17*d68f33bcSAndroid Build Coastguard Workerimport os 18*d68f33bcSAndroid Build Coastguard Workerimport pathlib 19*d68f33bcSAndroid Build Coastguard Workerimport sys 20*d68f33bcSAndroid Build Coastguard Worker 21*d68f33bcSAndroid Build Coastguard Worker_path = os.path.realpath(__file__ + '/../..') 22*d68f33bcSAndroid Build Coastguard Workerif sys.path[0] != _path: 23*d68f33bcSAndroid Build Coastguard Worker sys.path.insert(0, _path) 24*d68f33bcSAndroid Build Coastguard Workerdel _path 25*d68f33bcSAndroid Build Coastguard Worker 26*d68f33bcSAndroid Build Coastguard Worker 27*d68f33bcSAndroid Build Coastguard Worker# For use by ShellQuote. Match all characters that the shell might treat 28*d68f33bcSAndroid Build Coastguard Worker# specially. This means a number of things: 29*d68f33bcSAndroid Build Coastguard Worker# - Reserved characters. 30*d68f33bcSAndroid Build Coastguard Worker# - Characters used in expansions (brace, variable, path, globs, etc...). 31*d68f33bcSAndroid Build Coastguard Worker# - Characters that an interactive shell might use (like !). 32*d68f33bcSAndroid Build Coastguard Worker# - Whitespace so that one arg turns into multiple. 33*d68f33bcSAndroid Build Coastguard Worker# See the bash man page as well as the POSIX shell documentation for more info: 34*d68f33bcSAndroid Build Coastguard Worker# http://www.gnu.org/software/bash/manual/bashref.html 35*d68f33bcSAndroid Build Coastguard Worker# http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html 36*d68f33bcSAndroid Build Coastguard Worker_SHELL_QUOTABLE_CHARS = frozenset('[|&;()<> \t!{}[]=*?~$"\'\\#^') 37*d68f33bcSAndroid Build Coastguard Worker# The chars that, when used inside of double quotes, need escaping. 38*d68f33bcSAndroid Build Coastguard Worker# Order here matters as we need to escape backslashes first. 39*d68f33bcSAndroid Build Coastguard Worker_SHELL_ESCAPE_CHARS = r'\"`$' 40*d68f33bcSAndroid Build Coastguard Worker 41*d68f33bcSAndroid Build Coastguard Worker 42*d68f33bcSAndroid Build Coastguard Workerdef quote(s): 43*d68f33bcSAndroid Build Coastguard Worker """Quote |s| in a way that is safe for use in a shell. 44*d68f33bcSAndroid Build Coastguard Worker 45*d68f33bcSAndroid Build Coastguard Worker We aim to be safe, but also to produce "nice" output. That means we don't 46*d68f33bcSAndroid Build Coastguard Worker use quotes when we don't need to, and we prefer to use less quotes (like 47*d68f33bcSAndroid Build Coastguard Worker putting it all in single quotes) than more (using double quotes and escaping 48*d68f33bcSAndroid Build Coastguard Worker a bunch of stuff, or mixing the quotes). 49*d68f33bcSAndroid Build Coastguard Worker 50*d68f33bcSAndroid Build Coastguard Worker While python does provide a number of alternatives like: 51*d68f33bcSAndroid Build Coastguard Worker - pipes.quote 52*d68f33bcSAndroid Build Coastguard Worker - shlex.quote 53*d68f33bcSAndroid Build Coastguard Worker They suffer from various problems like: 54*d68f33bcSAndroid Build Coastguard Worker - Not widely available in different python versions. 55*d68f33bcSAndroid Build Coastguard Worker - Do not produce pretty output in many cases. 56*d68f33bcSAndroid Build Coastguard Worker - Are in modules that rarely otherwise get used. 57*d68f33bcSAndroid Build Coastguard Worker 58*d68f33bcSAndroid Build Coastguard Worker Note: We don't handle reserved shell words like "for" or "case". This is 59*d68f33bcSAndroid Build Coastguard Worker because those only matter when they're the first element in a command, and 60*d68f33bcSAndroid Build Coastguard Worker there is no use case for that. When we want to run commands, we tend to 61*d68f33bcSAndroid Build Coastguard Worker run real programs and not shell ones. 62*d68f33bcSAndroid Build Coastguard Worker 63*d68f33bcSAndroid Build Coastguard Worker Args: 64*d68f33bcSAndroid Build Coastguard Worker s: The string to quote. 65*d68f33bcSAndroid Build Coastguard Worker 66*d68f33bcSAndroid Build Coastguard Worker Returns: 67*d68f33bcSAndroid Build Coastguard Worker A safely (possibly quoted) string. 68*d68f33bcSAndroid Build Coastguard Worker """ 69*d68f33bcSAndroid Build Coastguard Worker # If callers pass down bad types, don't blow up. 70*d68f33bcSAndroid Build Coastguard Worker if isinstance(s, bytes): 71*d68f33bcSAndroid Build Coastguard Worker s = s.encode('utf-8') 72*d68f33bcSAndroid Build Coastguard Worker elif isinstance(s, pathlib.PurePath): 73*d68f33bcSAndroid Build Coastguard Worker return str(s) 74*d68f33bcSAndroid Build Coastguard Worker elif not isinstance(s, str): 75*d68f33bcSAndroid Build Coastguard Worker return repr(s) 76*d68f33bcSAndroid Build Coastguard Worker 77*d68f33bcSAndroid Build Coastguard Worker # See if no quoting is needed so we can return the string as-is. 78*d68f33bcSAndroid Build Coastguard Worker for c in s: 79*d68f33bcSAndroid Build Coastguard Worker if c in _SHELL_QUOTABLE_CHARS: 80*d68f33bcSAndroid Build Coastguard Worker break 81*d68f33bcSAndroid Build Coastguard Worker else: 82*d68f33bcSAndroid Build Coastguard Worker return s if s else "''" 83*d68f33bcSAndroid Build Coastguard Worker 84*d68f33bcSAndroid Build Coastguard Worker # See if we can use single quotes first. Output is nicer. 85*d68f33bcSAndroid Build Coastguard Worker if "'" not in s: 86*d68f33bcSAndroid Build Coastguard Worker return f"'{s}'" 87*d68f33bcSAndroid Build Coastguard Worker 88*d68f33bcSAndroid Build Coastguard Worker # Have to use double quotes. Escape the few chars that still expand when 89*d68f33bcSAndroid Build Coastguard Worker # used inside of double quotes. 90*d68f33bcSAndroid Build Coastguard Worker for c in _SHELL_ESCAPE_CHARS: 91*d68f33bcSAndroid Build Coastguard Worker if c in s: 92*d68f33bcSAndroid Build Coastguard Worker s = s.replace(c, fr'\{c}') 93*d68f33bcSAndroid Build Coastguard Worker return f'"{s}"' 94*d68f33bcSAndroid Build Coastguard Worker 95*d68f33bcSAndroid Build Coastguard Worker 96*d68f33bcSAndroid Build Coastguard Workerdef unquote(s): 97*d68f33bcSAndroid Build Coastguard Worker """Do the opposite of ShellQuote. 98*d68f33bcSAndroid Build Coastguard Worker 99*d68f33bcSAndroid Build Coastguard Worker This function assumes that the input is a valid escaped string. 100*d68f33bcSAndroid Build Coastguard Worker The behaviour is undefined on malformed strings. 101*d68f33bcSAndroid Build Coastguard Worker 102*d68f33bcSAndroid Build Coastguard Worker Args: 103*d68f33bcSAndroid Build Coastguard Worker s: An escaped string. 104*d68f33bcSAndroid Build Coastguard Worker 105*d68f33bcSAndroid Build Coastguard Worker Returns: 106*d68f33bcSAndroid Build Coastguard Worker The unescaped version of the string. 107*d68f33bcSAndroid Build Coastguard Worker """ 108*d68f33bcSAndroid Build Coastguard Worker if not s: 109*d68f33bcSAndroid Build Coastguard Worker return '' 110*d68f33bcSAndroid Build Coastguard Worker 111*d68f33bcSAndroid Build Coastguard Worker if s[0] == "'": 112*d68f33bcSAndroid Build Coastguard Worker return s[1:-1] 113*d68f33bcSAndroid Build Coastguard Worker 114*d68f33bcSAndroid Build Coastguard Worker if s[0] != '"': 115*d68f33bcSAndroid Build Coastguard Worker return s 116*d68f33bcSAndroid Build Coastguard Worker 117*d68f33bcSAndroid Build Coastguard Worker s = s[1:-1] 118*d68f33bcSAndroid Build Coastguard Worker output = '' 119*d68f33bcSAndroid Build Coastguard Worker i = 0 120*d68f33bcSAndroid Build Coastguard Worker while i < len(s) - 1: 121*d68f33bcSAndroid Build Coastguard Worker # Skip the backslash when it makes sense. 122*d68f33bcSAndroid Build Coastguard Worker if s[i] == '\\' and s[i + 1] in _SHELL_ESCAPE_CHARS: 123*d68f33bcSAndroid Build Coastguard Worker i += 1 124*d68f33bcSAndroid Build Coastguard Worker output += s[i] 125*d68f33bcSAndroid Build Coastguard Worker i += 1 126*d68f33bcSAndroid Build Coastguard Worker return output + s[i] if i < len(s) else output 127*d68f33bcSAndroid Build Coastguard Worker 128*d68f33bcSAndroid Build Coastguard Worker 129*d68f33bcSAndroid Build Coastguard Workerdef cmd_to_str(cmd): 130*d68f33bcSAndroid Build Coastguard Worker """Translate a command list into a space-separated string. 131*d68f33bcSAndroid Build Coastguard Worker 132*d68f33bcSAndroid Build Coastguard Worker The resulting string should be suitable for logging messages and for 133*d68f33bcSAndroid Build Coastguard Worker pasting into a terminal to run. Command arguments are surrounded by 134*d68f33bcSAndroid Build Coastguard Worker quotes to keep them grouped, even if an argument has spaces in it. 135*d68f33bcSAndroid Build Coastguard Worker 136*d68f33bcSAndroid Build Coastguard Worker Examples: 137*d68f33bcSAndroid Build Coastguard Worker ['a', 'b'] ==> "'a' 'b'" 138*d68f33bcSAndroid Build Coastguard Worker ['a b', 'c'] ==> "'a b' 'c'" 139*d68f33bcSAndroid Build Coastguard Worker ['a', 'b\'c'] ==> '\'a\' "b\'c"' 140*d68f33bcSAndroid Build Coastguard Worker [u'a', "/'$b"] ==> '\'a\' "/\'$b"' 141*d68f33bcSAndroid Build Coastguard Worker [] ==> '' 142*d68f33bcSAndroid Build Coastguard Worker See unittest for additional (tested) examples. 143*d68f33bcSAndroid Build Coastguard Worker 144*d68f33bcSAndroid Build Coastguard Worker Args: 145*d68f33bcSAndroid Build Coastguard Worker cmd: List of command arguments. 146*d68f33bcSAndroid Build Coastguard Worker 147*d68f33bcSAndroid Build Coastguard Worker Returns: 148*d68f33bcSAndroid Build Coastguard Worker String representing full command. 149*d68f33bcSAndroid Build Coastguard Worker """ 150*d68f33bcSAndroid Build Coastguard Worker # Use str before repr to translate unicode strings to regular strings. 151*d68f33bcSAndroid Build Coastguard Worker return ' '.join(quote(arg) for arg in cmd) 152*d68f33bcSAndroid Build Coastguard Worker 153*d68f33bcSAndroid Build Coastguard Worker 154*d68f33bcSAndroid Build Coastguard Workerdef boolean_shell_value(sval, default): 155*d68f33bcSAndroid Build Coastguard Worker """See if |sval| is a value users typically consider as boolean.""" 156*d68f33bcSAndroid Build Coastguard Worker if sval is None: 157*d68f33bcSAndroid Build Coastguard Worker return default 158*d68f33bcSAndroid Build Coastguard Worker 159*d68f33bcSAndroid Build Coastguard Worker if isinstance(sval, str): 160*d68f33bcSAndroid Build Coastguard Worker s = sval.lower() 161*d68f33bcSAndroid Build Coastguard Worker if s in ('yes', 'y', '1', 'true'): 162*d68f33bcSAndroid Build Coastguard Worker return True 163*d68f33bcSAndroid Build Coastguard Worker if s in ('no', 'n', '0', 'false'): 164*d68f33bcSAndroid Build Coastguard Worker return False 165*d68f33bcSAndroid Build Coastguard Worker 166*d68f33bcSAndroid Build Coastguard Worker raise ValueError(f'Could not decode as a boolean value: {sval!r}') 167