Add proptools functions to escape strings
Blueprint properties that end up as command line arguments need to be both ninja and shell escaped. Provide helpers that primary builders can use to appropriately escape them. Change-Id: Ifd697d87edb1c6f0a910377835c391bbe8f95b42
This commit is contained in:
parent
f5909d9cb5
commit
41ca49ff91
4 changed files with 212 additions and 5 deletions
|
@ -69,12 +69,14 @@ bootstrap_go_package(
|
|||
pkgPath = "github.com/google/blueprint/proptools",
|
||||
srcs = [
|
||||
"proptools/clone.go",
|
||||
"proptools/escape.go",
|
||||
"proptools/extend.go",
|
||||
"proptools/proptools.go",
|
||||
"proptools/typeequal.go",
|
||||
],
|
||||
testSrcs = [
|
||||
"proptools/clone_test.go",
|
||||
"proptools/escape_test.go",
|
||||
"proptools/extend_test.go",
|
||||
"proptools/typeequal_test.go",
|
||||
],
|
||||
|
|
|
@ -73,7 +73,7 @@ default $
|
|||
# Variant:
|
||||
# Type: bootstrap_go_package
|
||||
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
|
||||
# Defined: Blueprints:83:1
|
||||
# Defined: Blueprints:85:1
|
||||
|
||||
build $
|
||||
${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a $
|
||||
|
@ -100,7 +100,7 @@ default $
|
|||
# Variant:
|
||||
# Type: bootstrap_go_package
|
||||
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
|
||||
# Defined: Blueprints:102:1
|
||||
# Defined: Blueprints:104:1
|
||||
|
||||
build $
|
||||
${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a $
|
||||
|
@ -173,6 +173,7 @@ default $
|
|||
build $
|
||||
${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
|
||||
: g.bootstrap.compile ${g.bootstrap.srcDir}/proptools/clone.go $
|
||||
${g.bootstrap.srcDir}/proptools/escape.go $
|
||||
${g.bootstrap.srcDir}/proptools/extend.go $
|
||||
${g.bootstrap.srcDir}/proptools/proptools.go $
|
||||
${g.bootstrap.srcDir}/proptools/typeequal.go | $
|
||||
|
@ -186,7 +187,7 @@ default $
|
|||
# Variant:
|
||||
# Type: bootstrap_core_go_binary
|
||||
# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1
|
||||
# Defined: Blueprints:135:1
|
||||
# Defined: Blueprints:137:1
|
||||
|
||||
build ${g.bootstrap.buildDir}/.bootstrap/gotestmain/obj/gotestmain.a: $
|
||||
g.bootstrap.compile ${g.bootstrap.srcDir}/gotestmain/gotestmain.go | $
|
||||
|
@ -209,7 +210,7 @@ default ${g.bootstrap.BinDir}/gotestmain
|
|||
# Variant:
|
||||
# Type: bootstrap_core_go_binary
|
||||
# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1
|
||||
# Defined: Blueprints:140:1
|
||||
# Defined: Blueprints:142:1
|
||||
|
||||
build ${g.bootstrap.buildDir}/.bootstrap/gotestrunner/obj/gotestrunner.a: $
|
||||
g.bootstrap.compile ${g.bootstrap.srcDir}/gotestrunner/gotestrunner.go $
|
||||
|
@ -232,7 +233,7 @@ default ${g.bootstrap.BinDir}/gotestrunner
|
|||
# Variant:
|
||||
# Type: bootstrap_core_go_binary
|
||||
# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1
|
||||
# Defined: Blueprints:114:1
|
||||
# Defined: Blueprints:116:1
|
||||
|
||||
build ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/minibp.a: $
|
||||
g.bootstrap.compile ${g.bootstrap.srcDir}/bootstrap/minibp/main.go | $
|
||||
|
|
78
proptools/escape.go
Normal file
78
proptools/escape.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package proptools
|
||||
|
||||
import "strings"
|
||||
|
||||
// NinjaEscape takes a slice of strings that may contain characters that are meaningful to ninja
|
||||
// ($), and escapes each string so they will be passed to bash. It is not necessary on input,
|
||||
// output, or dependency names, those are handled by ModuleContext.Build. It is generally required
|
||||
// on strings from properties in Blueprint files that are used as Args to ModuleContext.Build. A
|
||||
// new slice containing the escaped strings is returned.
|
||||
func NinjaEscape(slice []string) []string {
|
||||
slice = append([]string(nil), slice...)
|
||||
for i, s := range slice {
|
||||
slice[i] = ninjaEscaper.Replace(s)
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
var ninjaEscaper = strings.NewReplacer(
|
||||
"$", "$$")
|
||||
|
||||
// ShellEscape takes a slice of strings that may contain characters that are meaningful to bash and
|
||||
// escapes if necessary by wrapping them in single quotes, and replacing internal single quotes with
|
||||
// '\'' (one single quote to end the quoting, a shell-escaped single quote to insert a real single
|
||||
// quote, and then a single quote to restarting quoting. A new slice containing the escaped strings
|
||||
// is returned.
|
||||
func ShellEscape(slice []string) []string {
|
||||
shellUnsafeChar := func(r rune) bool {
|
||||
switch {
|
||||
case 'A' <= r && r <= 'Z',
|
||||
'a' <= r && r <= 'z',
|
||||
'0' <= r && r <= '9',
|
||||
r == '_',
|
||||
r == '+',
|
||||
r == '-',
|
||||
r == '=',
|
||||
r == '.',
|
||||
r == ',',
|
||||
r == '/',
|
||||
r == ' ':
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
slice = append([]string(nil), slice...)
|
||||
|
||||
for i, s := range slice {
|
||||
if strings.IndexFunc(s, shellUnsafeChar) == -1 {
|
||||
// No escaping necessary
|
||||
continue
|
||||
}
|
||||
|
||||
slice[i] = `'` + singleQuoteReplacer.Replace(s) + `'`
|
||||
}
|
||||
return slice
|
||||
|
||||
}
|
||||
|
||||
func NinjaAndShellEscape(slice []string) []string {
|
||||
return ShellEscape(NinjaEscape(slice))
|
||||
}
|
||||
|
||||
var singleQuoteReplacer = strings.NewReplacer(`'`, `'\''`)
|
126
proptools/escape_test.go
Normal file
126
proptools/escape_test.go
Normal file
|
@ -0,0 +1,126 @@
|
|||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package proptools
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type escapeTestCase struct {
|
||||
name string
|
||||
in string
|
||||
out string
|
||||
}
|
||||
|
||||
var ninjaEscapeTestCase = []escapeTestCase{
|
||||
{
|
||||
name: "no escaping",
|
||||
in: `test`,
|
||||
out: `test`,
|
||||
},
|
||||
{
|
||||
name: "leading $",
|
||||
in: `$test`,
|
||||
out: `$$test`,
|
||||
},
|
||||
{
|
||||
name: "trailing $",
|
||||
in: `test$`,
|
||||
out: `test$$`,
|
||||
},
|
||||
{
|
||||
name: "leading and trailing $",
|
||||
in: `$test$`,
|
||||
out: `$$test$$`,
|
||||
},
|
||||
}
|
||||
|
||||
var shellEscapeTestCase = []escapeTestCase{
|
||||
{
|
||||
name: "no escaping",
|
||||
in: `test`,
|
||||
out: `test`,
|
||||
},
|
||||
{
|
||||
name: "leading $",
|
||||
in: `$test`,
|
||||
out: `'$test'`,
|
||||
},
|
||||
{
|
||||
name: "trailing $",
|
||||
in: `test$`,
|
||||
out: `'test$'`,
|
||||
},
|
||||
{
|
||||
name: "leading and trailing $",
|
||||
in: `$test$`,
|
||||
out: `'$test$'`,
|
||||
},
|
||||
{
|
||||
name: "single quote",
|
||||
in: `'`,
|
||||
out: `''\'''`,
|
||||
},
|
||||
{
|
||||
name: "multiple single quote",
|
||||
in: `''`,
|
||||
out: `''\'''\'''`,
|
||||
},
|
||||
{
|
||||
name: "double quote",
|
||||
in: `""`,
|
||||
out: `'""'`,
|
||||
},
|
||||
{
|
||||
name: "ORIGIN",
|
||||
in: `-Wl,--rpath,${ORIGIN}/../bionic-loader-test-libs`,
|
||||
out: `'-Wl,--rpath,${ORIGIN}/../bionic-loader-test-libs'`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestNinjaEscaping(t *testing.T) {
|
||||
for _, testCase := range ninjaEscapeTestCase {
|
||||
got := NinjaEscape([]string{testCase.in})[0]
|
||||
if got != testCase.out {
|
||||
t.Errorf("%s: expected `%s` got `%s`", testCase.name, testCase.out, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestShellEscaping(t *testing.T) {
|
||||
for _, testCase := range shellEscapeTestCase {
|
||||
got := ShellEscape([]string{testCase.in})[0]
|
||||
if got != testCase.out {
|
||||
t.Errorf("%s: expected `%s` got `%s`", testCase.name, testCase.out, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExternalShellEscaping(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
for _, testCase := range shellEscapeTestCase {
|
||||
cmd := "echo -n " + ShellEscape([]string{testCase.in})[0]
|
||||
got, err := exec.Command("/bin/sh", "-c", cmd).Output()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if string(got) != testCase.in {
|
||||
t.Errorf("%s: expected `%s` got `%s`", testCase.name, testCase.in, got)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue