xref: /aosp_15_r20/build/blueprint/proptools/escape.go (revision 1fa6dee971e1612fa5cc0aa5ca2d35a22e2c34a3)
1*1fa6dee9SAndroid Build Coastguard Worker// Copyright 2016 Google Inc. All rights reserved.
2*1fa6dee9SAndroid Build Coastguard Worker//
3*1fa6dee9SAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*1fa6dee9SAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*1fa6dee9SAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*1fa6dee9SAndroid Build Coastguard Worker//
7*1fa6dee9SAndroid Build Coastguard Worker//     http://www.apache.org/licenses/LICENSE-2.0
8*1fa6dee9SAndroid Build Coastguard Worker//
9*1fa6dee9SAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*1fa6dee9SAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*1fa6dee9SAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*1fa6dee9SAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*1fa6dee9SAndroid Build Coastguard Worker// limitations under the License.
14*1fa6dee9SAndroid Build Coastguard Worker
15*1fa6dee9SAndroid Build Coastguard Workerpackage proptools
16*1fa6dee9SAndroid Build Coastguard Worker
17*1fa6dee9SAndroid Build Coastguard Workerimport (
18*1fa6dee9SAndroid Build Coastguard Worker	"slices"
19*1fa6dee9SAndroid Build Coastguard Worker	"strings"
20*1fa6dee9SAndroid Build Coastguard Worker	"unsafe"
21*1fa6dee9SAndroid Build Coastguard Worker)
22*1fa6dee9SAndroid Build Coastguard Worker
23*1fa6dee9SAndroid Build Coastguard Worker// NinjaEscapeList takes a slice of strings that may contain characters that are meaningful to ninja
24*1fa6dee9SAndroid Build Coastguard Worker// ($), and escapes each string so they will be passed to bash.  It is not necessary on input,
25*1fa6dee9SAndroid Build Coastguard Worker// output, or dependency names, those are handled by ModuleContext.Build.  It is generally required
26*1fa6dee9SAndroid Build Coastguard Worker// on strings from properties in Blueprint files that are used as Args to ModuleContext.Build.  If
27*1fa6dee9SAndroid Build Coastguard Worker// escaping modified any of the strings then a new slice containing the escaped strings is returned,
28*1fa6dee9SAndroid Build Coastguard Worker// otherwise the original slice is returned.
29*1fa6dee9SAndroid Build Coastguard Workerfunc NinjaEscapeList(slice []string) []string {
30*1fa6dee9SAndroid Build Coastguard Worker	sliceCopied := false
31*1fa6dee9SAndroid Build Coastguard Worker	for i, s := range slice {
32*1fa6dee9SAndroid Build Coastguard Worker		escaped := NinjaEscape(s)
33*1fa6dee9SAndroid Build Coastguard Worker		if unsafe.StringData(s) != unsafe.StringData(escaped) {
34*1fa6dee9SAndroid Build Coastguard Worker			if !sliceCopied {
35*1fa6dee9SAndroid Build Coastguard Worker				// If this was the first string that was modified by escaping then make a copy of the
36*1fa6dee9SAndroid Build Coastguard Worker				// input slice to use as the output slice.
37*1fa6dee9SAndroid Build Coastguard Worker				slice = slices.Clone(slice)
38*1fa6dee9SAndroid Build Coastguard Worker				sliceCopied = true
39*1fa6dee9SAndroid Build Coastguard Worker			}
40*1fa6dee9SAndroid Build Coastguard Worker			slice[i] = escaped
41*1fa6dee9SAndroid Build Coastguard Worker		}
42*1fa6dee9SAndroid Build Coastguard Worker	}
43*1fa6dee9SAndroid Build Coastguard Worker	return slice
44*1fa6dee9SAndroid Build Coastguard Worker}
45*1fa6dee9SAndroid Build Coastguard Worker
46*1fa6dee9SAndroid Build Coastguard Worker// NinjaEscape takes a string that may contain characters that are meaningful to ninja
47*1fa6dee9SAndroid Build Coastguard Worker// ($), and escapes it so it will be passed to bash.  It is not necessary on input,
48*1fa6dee9SAndroid Build Coastguard Worker// output, or dependency names, those are handled by ModuleContext.Build.  It is generally required
49*1fa6dee9SAndroid Build Coastguard Worker// on strings from properties in Blueprint files that are used as Args to ModuleContext.Build.
50*1fa6dee9SAndroid Build Coastguard Workerfunc NinjaEscape(s string) string {
51*1fa6dee9SAndroid Build Coastguard Worker	return ninjaEscaper.Replace(s)
52*1fa6dee9SAndroid Build Coastguard Worker}
53*1fa6dee9SAndroid Build Coastguard Worker
54*1fa6dee9SAndroid Build Coastguard Workervar ninjaEscaper = strings.NewReplacer(
55*1fa6dee9SAndroid Build Coastguard Worker	"$", "$$")
56*1fa6dee9SAndroid Build Coastguard Worker
57*1fa6dee9SAndroid Build Coastguard Worker// ShellEscapeList takes a slice of strings that may contain characters that are meaningful to bash and
58*1fa6dee9SAndroid Build Coastguard Worker// escapes them if necessary by wrapping them in single quotes, and replacing internal single quotes with
59*1fa6dee9SAndroid Build Coastguard Worker// one single quote to end the quoting, a shell-escaped single quote to insert a real single
60*1fa6dee9SAndroid Build Coastguard Worker// quote, and then a single quote to restarting quoting.  If escaping modified any of the strings then a
61*1fa6dee9SAndroid Build Coastguard Worker// new slice containing the escaped strings is returned, otherwise the original slice is returned.
62*1fa6dee9SAndroid Build Coastguard Workerfunc ShellEscapeList(slice []string) []string {
63*1fa6dee9SAndroid Build Coastguard Worker	sliceCopied := false
64*1fa6dee9SAndroid Build Coastguard Worker	for i, s := range slice {
65*1fa6dee9SAndroid Build Coastguard Worker		escaped := ShellEscape(s)
66*1fa6dee9SAndroid Build Coastguard Worker		if unsafe.StringData(s) != unsafe.StringData(escaped) {
67*1fa6dee9SAndroid Build Coastguard Worker			if !sliceCopied {
68*1fa6dee9SAndroid Build Coastguard Worker				// If this was the first string that was modified by escaping then make a copy of the
69*1fa6dee9SAndroid Build Coastguard Worker				// input slice to use as the output slice.
70*1fa6dee9SAndroid Build Coastguard Worker				slice = slices.Clone(slice)
71*1fa6dee9SAndroid Build Coastguard Worker				sliceCopied = true
72*1fa6dee9SAndroid Build Coastguard Worker			}
73*1fa6dee9SAndroid Build Coastguard Worker			slice[i] = escaped
74*1fa6dee9SAndroid Build Coastguard Worker		}
75*1fa6dee9SAndroid Build Coastguard Worker	}
76*1fa6dee9SAndroid Build Coastguard Worker	return slice
77*1fa6dee9SAndroid Build Coastguard Worker}
78*1fa6dee9SAndroid Build Coastguard Worker
79*1fa6dee9SAndroid Build Coastguard Workerfunc ShellEscapeListIncludingSpaces(slice []string) []string {
80*1fa6dee9SAndroid Build Coastguard Worker	sliceCopied := false
81*1fa6dee9SAndroid Build Coastguard Worker	for i, s := range slice {
82*1fa6dee9SAndroid Build Coastguard Worker		escaped := ShellEscapeIncludingSpaces(s)
83*1fa6dee9SAndroid Build Coastguard Worker		if unsafe.StringData(s) != unsafe.StringData(escaped) {
84*1fa6dee9SAndroid Build Coastguard Worker			if !sliceCopied {
85*1fa6dee9SAndroid Build Coastguard Worker				// If this was the first string that was modified by escaping then make a copy of the
86*1fa6dee9SAndroid Build Coastguard Worker				// input slice to use as the output slice.
87*1fa6dee9SAndroid Build Coastguard Worker				slice = slices.Clone(slice)
88*1fa6dee9SAndroid Build Coastguard Worker				sliceCopied = true
89*1fa6dee9SAndroid Build Coastguard Worker			}
90*1fa6dee9SAndroid Build Coastguard Worker			slice[i] = escaped
91*1fa6dee9SAndroid Build Coastguard Worker		}
92*1fa6dee9SAndroid Build Coastguard Worker	}
93*1fa6dee9SAndroid Build Coastguard Worker	return slice
94*1fa6dee9SAndroid Build Coastguard Worker}
95*1fa6dee9SAndroid Build Coastguard Worker
96*1fa6dee9SAndroid Build Coastguard Workerfunc shellUnsafeChar(r rune) bool {
97*1fa6dee9SAndroid Build Coastguard Worker	switch {
98*1fa6dee9SAndroid Build Coastguard Worker	case 'A' <= r && r <= 'Z',
99*1fa6dee9SAndroid Build Coastguard Worker		'a' <= r && r <= 'z',
100*1fa6dee9SAndroid Build Coastguard Worker		'0' <= r && r <= '9',
101*1fa6dee9SAndroid Build Coastguard Worker		r == '_',
102*1fa6dee9SAndroid Build Coastguard Worker		r == '+',
103*1fa6dee9SAndroid Build Coastguard Worker		r == '-',
104*1fa6dee9SAndroid Build Coastguard Worker		r == '=',
105*1fa6dee9SAndroid Build Coastguard Worker		r == '.',
106*1fa6dee9SAndroid Build Coastguard Worker		r == ',',
107*1fa6dee9SAndroid Build Coastguard Worker		r == '/':
108*1fa6dee9SAndroid Build Coastguard Worker		return false
109*1fa6dee9SAndroid Build Coastguard Worker	default:
110*1fa6dee9SAndroid Build Coastguard Worker		return true
111*1fa6dee9SAndroid Build Coastguard Worker	}
112*1fa6dee9SAndroid Build Coastguard Worker}
113*1fa6dee9SAndroid Build Coastguard Worker
114*1fa6dee9SAndroid Build Coastguard Worker// ShellEscape takes string that may contain characters that are meaningful to bash and
115*1fa6dee9SAndroid Build Coastguard Worker// escapes it if necessary by wrapping it in single quotes, and replacing internal single quotes with
116*1fa6dee9SAndroid Build Coastguard Worker// one single quote to end the quoting, a shell-escaped single quote to insert a real single
117*1fa6dee9SAndroid Build Coastguard Worker// quote, and then a single quote to restarting quoting.
118*1fa6dee9SAndroid Build Coastguard Workerfunc ShellEscape(s string) string {
119*1fa6dee9SAndroid Build Coastguard Worker	shellUnsafeCharNotSpace := func(r rune) bool {
120*1fa6dee9SAndroid Build Coastguard Worker		return r != ' ' && shellUnsafeChar(r)
121*1fa6dee9SAndroid Build Coastguard Worker	}
122*1fa6dee9SAndroid Build Coastguard Worker
123*1fa6dee9SAndroid Build Coastguard Worker	if strings.IndexFunc(s, shellUnsafeCharNotSpace) == -1 {
124*1fa6dee9SAndroid Build Coastguard Worker		// No escaping necessary
125*1fa6dee9SAndroid Build Coastguard Worker		return s
126*1fa6dee9SAndroid Build Coastguard Worker	}
127*1fa6dee9SAndroid Build Coastguard Worker
128*1fa6dee9SAndroid Build Coastguard Worker	return `'` + singleQuoteReplacer.Replace(s) + `'`
129*1fa6dee9SAndroid Build Coastguard Worker}
130*1fa6dee9SAndroid Build Coastguard Worker
131*1fa6dee9SAndroid Build Coastguard Worker// ShellEscapeIncludingSpaces escapes the input `s` in a similar way to ShellEscape except that
132*1fa6dee9SAndroid Build Coastguard Worker// this treats spaces as meaningful characters.
133*1fa6dee9SAndroid Build Coastguard Workerfunc ShellEscapeIncludingSpaces(s string) string {
134*1fa6dee9SAndroid Build Coastguard Worker	if strings.IndexFunc(s, shellUnsafeChar) == -1 {
135*1fa6dee9SAndroid Build Coastguard Worker		// No escaping necessary
136*1fa6dee9SAndroid Build Coastguard Worker		return s
137*1fa6dee9SAndroid Build Coastguard Worker	}
138*1fa6dee9SAndroid Build Coastguard Worker
139*1fa6dee9SAndroid Build Coastguard Worker	return `'` + singleQuoteReplacer.Replace(s) + `'`
140*1fa6dee9SAndroid Build Coastguard Worker}
141*1fa6dee9SAndroid Build Coastguard Worker
142*1fa6dee9SAndroid Build Coastguard Workerfunc NinjaAndShellEscapeList(slice []string) []string {
143*1fa6dee9SAndroid Build Coastguard Worker	return ShellEscapeList(NinjaEscapeList(slice))
144*1fa6dee9SAndroid Build Coastguard Worker}
145*1fa6dee9SAndroid Build Coastguard Worker
146*1fa6dee9SAndroid Build Coastguard Workerfunc NinjaAndShellEscapeListIncludingSpaces(slice []string) []string {
147*1fa6dee9SAndroid Build Coastguard Worker	return ShellEscapeListIncludingSpaces(NinjaEscapeList(slice))
148*1fa6dee9SAndroid Build Coastguard Worker}
149*1fa6dee9SAndroid Build Coastguard Worker
150*1fa6dee9SAndroid Build Coastguard Workerfunc NinjaAndShellEscape(s string) string {
151*1fa6dee9SAndroid Build Coastguard Worker	return ShellEscape(NinjaEscape(s))
152*1fa6dee9SAndroid Build Coastguard Worker}
153*1fa6dee9SAndroid Build Coastguard Worker
154*1fa6dee9SAndroid Build Coastguard Workerfunc NinjaAndShellEscapeIncludingSpaces(s string) string {
155*1fa6dee9SAndroid Build Coastguard Worker	return ShellEscapeIncludingSpaces(NinjaEscape(s))
156*1fa6dee9SAndroid Build Coastguard Worker}
157*1fa6dee9SAndroid Build Coastguard Worker
158*1fa6dee9SAndroid Build Coastguard Workervar singleQuoteReplacer = strings.NewReplacer(`'`, `'\''`)
159