xref: /openbsd-src/gnu/llvm/clang/tools/scan-build-py/lib/libscanbuild/shell.py (revision a9ac8606c53d55cee9c3a39778b249c51df111ef)
1*a9ac8606Spatrick# -*- coding: utf-8 -*-
2*a9ac8606Spatrick# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
3*a9ac8606Spatrick# See https://llvm.org/LICENSE.txt for license information.
4*a9ac8606Spatrick# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5*a9ac8606Spatrick""" This module implements basic shell escaping/unescaping methods. """
6*a9ac8606Spatrick
7*a9ac8606Spatrickimport re
8*a9ac8606Spatrickimport shlex
9*a9ac8606Spatrick
10*a9ac8606Spatrick__all__ = ['encode', 'decode']
11*a9ac8606Spatrick
12*a9ac8606Spatrick
13*a9ac8606Spatrickdef encode(command):
14*a9ac8606Spatrick    """ Takes a command as list and returns a string. """
15*a9ac8606Spatrick
16*a9ac8606Spatrick    def needs_quote(word):
17*a9ac8606Spatrick        """ Returns true if arguments needs to be protected by quotes.
18*a9ac8606Spatrick
19*a9ac8606Spatrick        Previous implementation was shlex.split method, but that's not good
20*a9ac8606Spatrick        for this job. Currently is running through the string with a basic
21*a9ac8606Spatrick        state checking. """
22*a9ac8606Spatrick
23*a9ac8606Spatrick        reserved = {' ', '$', '%', '&', '(', ')', '[', ']', '{', '}', '*', '|',
24*a9ac8606Spatrick                    '<', '>', '@', '?', '!'}
25*a9ac8606Spatrick        state = 0
26*a9ac8606Spatrick        for current in word:
27*a9ac8606Spatrick            if state == 0 and current in reserved:
28*a9ac8606Spatrick                return True
29*a9ac8606Spatrick            elif state == 0 and current == '\\':
30*a9ac8606Spatrick                state = 1
31*a9ac8606Spatrick            elif state == 1 and current in reserved | {'\\'}:
32*a9ac8606Spatrick                state = 0
33*a9ac8606Spatrick            elif state == 0 and current == '"':
34*a9ac8606Spatrick                state = 2
35*a9ac8606Spatrick            elif state == 2 and current == '"':
36*a9ac8606Spatrick                state = 0
37*a9ac8606Spatrick            elif state == 0 and current == "'":
38*a9ac8606Spatrick                state = 3
39*a9ac8606Spatrick            elif state == 3 and current == "'":
40*a9ac8606Spatrick                state = 0
41*a9ac8606Spatrick        return state != 0
42*a9ac8606Spatrick
43*a9ac8606Spatrick    def escape(word):
44*a9ac8606Spatrick        """ Do protect argument if that's needed. """
45*a9ac8606Spatrick
46*a9ac8606Spatrick        table = {'\\': '\\\\', '"': '\\"'}
47*a9ac8606Spatrick        escaped = ''.join([table.get(c, c) for c in word])
48*a9ac8606Spatrick
49*a9ac8606Spatrick        return '"' + escaped + '"' if needs_quote(word) else escaped
50*a9ac8606Spatrick
51*a9ac8606Spatrick    return " ".join([escape(arg) for arg in command])
52*a9ac8606Spatrick
53*a9ac8606Spatrick
54*a9ac8606Spatrickdef decode(string):
55*a9ac8606Spatrick    """ Takes a command string and returns as a list. """
56*a9ac8606Spatrick
57*a9ac8606Spatrick    def unescape(arg):
58*a9ac8606Spatrick        """ Gets rid of the escaping characters. """
59*a9ac8606Spatrick
60*a9ac8606Spatrick        if len(arg) >= 2 and arg[0] == arg[-1] and arg[0] == '"':
61*a9ac8606Spatrick            arg = arg[1:-1]
62*a9ac8606Spatrick            return re.sub(r'\\(["\\])', r'\1', arg)
63*a9ac8606Spatrick        return re.sub(r'\\([\\ $%&\(\)\[\]\{\}\*|<>@?!])', r'\1', arg)
64*a9ac8606Spatrick
65*a9ac8606Spatrick    return [unescape(arg) for arg in shlex.split(string)]
66