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