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