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