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