Merge pull request #124 from colincross/escape

Add proptools functions to escape strings
This commit is contained in:
colincross 2016-09-29 15:49:14 -07:00 committed by GitHub
commit 888b21f87b
4 changed files with 212 additions and 5 deletions

View file

@ -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",
],

View file

@ -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
View 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
View 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)
}
}
}