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