if err != nil {
return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
}
+ var ok bool
for i, arg := range args {
- arg = expandSrcDir(arg, di.Dir)
- if !safeCgoName(arg) {
+ if arg, ok = expandSrcDir(arg, di.Dir); !ok {
return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg)
}
args[i] = arg
return nil
}
-func expandSrcDir(str string, srcdir string) string {
+// expandSrcDir expands any occurrence of ${SRCDIR}, making sure
+// the result is safe for the shell.
+func expandSrcDir(str string, srcdir string) (string, bool) {
// "\" delimited paths cause safeCgoName to fail
// so convert native paths with a different delimeter
- // to "/" before starting (eg: on windows)
+ // to "/" before starting (eg: on windows).
srcdir = filepath.ToSlash(srcdir)
- return strings.Replace(str, "${SRCDIR}", srcdir, -1)
+
+ // Spaces are tolerated in ${SRCDIR}, but not anywhere else.
+ chunks := strings.Split(str, "${SRCDIR}")
+ if len(chunks) < 2 {
+ return str, safeCgoName(str, false)
+ }
+ ok := true
+ for _, chunk := range chunks {
+ ok = ok && (chunk == "" || safeCgoName(chunk, false))
+ }
+ ok = ok && (srcdir == "" || safeCgoName(srcdir, true))
+ res := strings.Join(chunks, srcdir)
+ return res, ok && res != ""
}
// NOTE: $ is not safe for the shell, but it is allowed here because of linker options like -Wl,$ORIGIN.
// We never pass these arguments to a shell (just to programs we construct argv for), so this should be okay.
// See golang.org/issue/6038.
-var safeBytes = []byte("+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:$")
+const safeString = "+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:$"
+const safeSpaces = " "
-func safeCgoName(s string) bool {
+var safeBytes = []byte(safeSpaces + safeString)
+
+func safeCgoName(s string, spaces bool) bool {
if s == "" {
return false
}
+ safe := safeBytes
+ if !spaces {
+ safe = safe[len(safeSpaces):]
+ }
for i := 0; i < len(s); i++ {
- if c := s[i]; c < 0x80 && bytes.IndexByte(safeBytes, c) < 0 {
+ if c := s[i]; c < 0x80 && bytes.IndexByte(safe, c) < 0 {
return false
}
}
func TestExpandSrcDir(t *testing.T) {
for _, test := range expandSrcDirTests {
- output := expandSrcDir(test.input, expandSrcDirPath)
+ output, _ := expandSrcDir(test.input, expandSrcDirPath)
if output != test.expected {
t.Errorf("%q expands to %q with SRCDIR=%q when %q is expected", test.input, output, expandSrcDirPath, test.expected)
} else {
}
}
}
+
+func TestShellSafety(t *testing.T) {
+ tests := []struct {
+ input, srcdir, expected string
+ result bool
+ }{
+ {"-I${SRCDIR}/../include", "/projects/src/issue 11868", "-I/projects/src/issue 11868/../include", true},
+ {"-X${SRCDIR}/1,${SRCDIR}/2", "/projects/src/issue 11868", "-X/projects/src/issue 11868/1,/projects/src/issue 11868/2", true},
+ {"-I/tmp -I/tmp", "/tmp2", "-I/tmp -I/tmp", false},
+ {"-I/tmp", "/tmp/[0]", "-I/tmp", true},
+ {"-I${SRCDIR}/dir", "/tmp/[0]", "-I/tmp/[0]/dir", false},
+ }
+ for _, test := range tests {
+ output, ok := expandSrcDir(test.input, test.srcdir)
+ if ok != test.result {
+ t.Errorf("Expected %t while %q expands to %q with SRCDIR=%q; got %t", test.result, test.input, output, test.srcdir, ok)
+ }
+ if output != test.expected {
+ t.Errorf("Expected %q while %q expands with SRCDIR=%q; got %q", test.expected, test.input, test.srcdir, output)
+ }
+ }
+}