Quellcode durchsuchen

Merge pull request #16692 from vdemeester/16690-fix-etc-osrelease-panic

Fix panic in parsing /etc/os-release
David Calavera vor 9 Jahren
Ursprung
Commit
98ec6bc14c

+ 1 - 0
hack/vendor.sh

@@ -13,6 +13,7 @@ clone git github.com/go-check/check 11d3bc7aa68e238947792f30573146a3231fc0f1
 clone git github.com/gorilla/context 14f550f51a
 clone git github.com/gorilla/mux e444e69cbd
 clone git github.com/kr/pty 5cf931ef8f
+clone git github.com/mattn/go-shellwords v1.0.0
 clone git github.com/mattn/go-sqlite3 v1.1.0
 clone git github.com/microsoft/hcsshim de43b42b5ce14dfdcbeedb0628b0032174d89caa
 clone git github.com/mistifyio/go-zfs v2.1.1

+ 30 - 6
pkg/parsers/operatingsystem/operatingsystem_linux.go

@@ -3,9 +3,14 @@
 package operatingsystem
 
 import (
+	"bufio"
 	"bytes"
-	"errors"
+	"fmt"
 	"io/ioutil"
+	"os"
+	"strings"
+
+	"github.com/mattn/go-shellwords"
 )
 
 var (
@@ -18,15 +23,34 @@ var (
 
 // GetOperatingSystem gets the name of the current operating system.
 func GetOperatingSystem() (string, error) {
-	b, err := ioutil.ReadFile(etcOsRelease)
+	osReleaseFile, err := os.Open(etcOsRelease)
 	if err != nil {
 		return "", err
 	}
-	if i := bytes.Index(b, []byte("PRETTY_NAME")); i >= 0 {
-		b = b[i+13:]
-		return string(b[:bytes.IndexByte(b, '"')]), nil
+	defer osReleaseFile.Close()
+
+	var prettyName string
+	scanner := bufio.NewScanner(osReleaseFile)
+	for scanner.Scan() {
+		line := scanner.Text()
+		if strings.HasPrefix(line, "PRETTY_NAME=") {
+			data := strings.SplitN(line, "=", 2)
+			prettyNames, err := shellwords.Parse(data[1])
+			if err != nil {
+				return "", fmt.Errorf("PRETTY_NAME is invalid: %s", err.Error())
+			}
+			if len(prettyNames) != 1 {
+				return "", fmt.Errorf("PRETTY_NAME needs to be enclosed by quotes if they have spaces: %s", data[1])
+			}
+			prettyName = prettyNames[0]
+		}
+	}
+	if prettyName != "" {
+		return prettyName, nil
 	}
-	return "", errors.New("PRETTY_NAME not found")
+	// If not set, defaults to PRETTY_NAME="Linux"
+	// c.f. http://www.freedesktop.org/software/systemd/man/os-release.html
+	return "Linux", nil
 }
 
 // IsContainerized returns true if we are running inside a container.

+ 93 - 22
pkg/parsers/operatingsystem/operatingsystem_unix_test.go

@@ -10,34 +10,103 @@ import (
 )
 
 func TestGetOperatingSystem(t *testing.T) {
-	var (
-		backup       = etcOsRelease
-		ubuntuTrusty = []byte(`NAME="Ubuntu"
+	var backup = etcOsRelease
+
+	invalids := []struct {
+		content       string
+		errorExpected string
+	}{
+		{
+			`PRETTY_NAME=Source Mage GNU/Linux
+PRETTY_NAME=Ubuntu 14.04.LTS`,
+			"PRETTY_NAME needs to be enclosed by quotes if they have spaces: Source Mage GNU/Linux",
+		},
+		{
+			`PRETTY_NAME="Ubuntu Linux
+PRETTY_NAME=Ubuntu 14.04.LTS`,
+			"PRETTY_NAME is invalid: invalid command line string",
+		},
+		{
+			`PRETTY_NAME=Ubuntu'
+PRETTY_NAME=Ubuntu 14.04.LTS`,
+			"PRETTY_NAME is invalid: invalid command line string",
+		},
+		{
+			`PRETTY_NAME'
+PRETTY_NAME=Ubuntu 14.04.LTS`,
+			"PRETTY_NAME needs to be enclosed by quotes if they have spaces: Ubuntu 14.04.LTS",
+		},
+	}
+
+	valids := []struct {
+		content  string
+		expected string
+	}{
+		{
+			`NAME="Ubuntu"
+PRETTY_NAME_AGAIN="Ubuntu 14.04.LTS"
+VERSION="14.04, Trusty Tahr"
+ID=ubuntu
+ID_LIKE=debian
+VERSION_ID="14.04"
+HOME_URL="http://www.ubuntu.com/"
+SUPPORT_URL="http://help.ubuntu.com/"
+BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`,
+			"Linux",
+		},
+		{
+			`NAME="Ubuntu"
 VERSION="14.04, Trusty Tahr"
 ID=ubuntu
 ID_LIKE=debian
-PRETTY_NAME="Ubuntu 14.04 LTS"
 VERSION_ID="14.04"
 HOME_URL="http://www.ubuntu.com/"
 SUPPORT_URL="http://help.ubuntu.com/"
-BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`)
-		gentoo = []byte(`NAME=Gentoo
+BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`,
+			"Linux",
+		},
+		{
+			`NAME=Gentoo
 ID=gentoo
 PRETTY_NAME="Gentoo/Linux"
 ANSI_COLOR="1;32"
 HOME_URL="http://www.gentoo.org/"
 SUPPORT_URL="http://www.gentoo.org/main/en/support.xml"
 BUG_REPORT_URL="https://bugs.gentoo.org/"
-`)
-		noPrettyName = []byte(`NAME="Ubuntu"
+`,
+			"Gentoo/Linux",
+		},
+		{
+			`NAME="Ubuntu"
 VERSION="14.04, Trusty Tahr"
 ID=ubuntu
 ID_LIKE=debian
+PRETTY_NAME="Ubuntu 14.04 LTS"
 VERSION_ID="14.04"
 HOME_URL="http://www.ubuntu.com/"
 SUPPORT_URL="http://help.ubuntu.com/"
-BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`)
-	)
+BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`,
+			"Ubuntu 14.04 LTS",
+		},
+		{
+			`NAME="Ubuntu"
+VERSION="14.04, Trusty Tahr"
+ID=ubuntu
+ID_LIKE=debian
+PRETTY_NAME='Ubuntu 14.04 LTS'`,
+			"Ubuntu 14.04 LTS",
+		},
+		{
+			`PRETTY_NAME=Source
+NAME="Source Mage"`,
+			"Source",
+		},
+		{
+			`PRETTY_NAME=Source
+PRETTY_NAME="Source Mage"`,
+			"Source Mage",
+		},
+	}
 
 	dir := os.TempDir()
 	etcOsRelease = filepath.Join(dir, "etcOsRelease")
@@ -47,21 +116,23 @@ BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`)
 		etcOsRelease = backup
 	}()
 
-	for expect, osRelease := range map[string][]byte{
-		"Ubuntu 14.04 LTS": ubuntuTrusty,
-		"Gentoo/Linux":     gentoo,
-		"":                 noPrettyName,
-	} {
-		if err := ioutil.WriteFile(etcOsRelease, osRelease, 0600); err != nil {
+	for _, elt := range invalids {
+		if err := ioutil.WriteFile(etcOsRelease, []byte(elt.content), 0600); err != nil {
+			t.Fatalf("failed to write to %s: %v", etcOsRelease, err)
+		}
+		s, err := GetOperatingSystem()
+		if err == nil || err.Error() != elt.errorExpected {
+			t.Fatalf("Expected an error %q, got %q (err: %v)", elt.errorExpected, s, err)
+		}
+	}
+
+	for _, elt := range valids {
+		if err := ioutil.WriteFile(etcOsRelease, []byte(elt.content), 0600); err != nil {
 			t.Fatalf("failed to write to %s: %v", etcOsRelease, err)
 		}
 		s, err := GetOperatingSystem()
-		if s != expect {
-			if expect == "" {
-				t.Fatalf("Expected error 'PRETTY_NAME not found', but got %v", err)
-			} else {
-				t.Fatalf("Expected '%s', but got '%s'. Err=%v", expect, s, err)
-			}
+		if err != nil || s != elt.expected {
+			t.Fatalf("Expected %q, got %q (err: %v)", elt.expected, s, err)
 		}
 	}
 }

+ 9 - 0
vendor/src/github.com/mattn/go-shellwords/.travis.yml

@@ -0,0 +1,9 @@
+language: go
+go:
+  - tip
+before_install:
+  - go get github.com/axw/gocov/gocov
+  - go get github.com/mattn/goveralls
+  - go get golang.org/x/tools/cmd/cover
+script:
+    - $HOME/gopath/bin/goveralls -repotoken 2FMhp57u8LcstKL9B190fLTcEnBtAAiEL

+ 47 - 0
vendor/src/github.com/mattn/go-shellwords/README.md

@@ -0,0 +1,47 @@
+# go-shellwords
+
+[![Coverage Status](https://coveralls.io/repos/mattn/go-shellwords/badge.png?branch=master)](https://coveralls.io/r/mattn/go-shellwords?branch=master)
+[![Build Status](https://travis-ci.org/mattn/go-shellwords.svg?branch=master)](https://travis-ci.org/mattn/go-shellwords)
+
+Parse line as shell words.
+
+## Usage
+
+```go
+args, err := shellwords.Parse("./foo --bar=baz")
+// args should be ["./foo", "--bar=baz"]
+```
+
+```go
+os.Setenv("FOO", "bar")
+p := shellwords.NewParser()
+p.ParseEnv = true
+args, err := p.Parse("./foo $FOO")
+// args should be ["./foo", "bar"]
+```
+
+```go
+p := shellwords.NewParser()
+p.ParseBacktick = true
+args, err := p.Parse("./foo `echo $SHELL`")
+// args should be ["./foo", "/bin/bash"]
+```
+
+```go
+shellwords.ParseBacktick = true
+p := shellwords.NewParser()
+args, err := p.Parse("./foo `echo $SHELL`")
+// args should be ["./foo", "/bin/bash"]
+```
+
+# Thanks
+
+This is based on cpan module [Parse::CommandLine](https://metacpan.org/pod/Parse::CommandLine).
+
+# License
+
+under the MIT License: http://mattn.mit-license.org/2014
+
+# Author
+
+Yasuhiro Matsumoto (a.k.a mattn)

+ 134 - 0
vendor/src/github.com/mattn/go-shellwords/shellwords.go

@@ -0,0 +1,134 @@
+package shellwords
+
+import (
+	"errors"
+	"os"
+	"regexp"
+	"strings"
+)
+
+var (
+	ParseEnv      bool = false
+	ParseBacktick bool = false
+)
+
+var envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`)
+
+func isSpace(r rune) bool {
+	switch r {
+	case ' ', '\t', '\r', '\n':
+		return true
+	}
+	return false
+}
+
+func replaceEnv(s string) string {
+	return envRe.ReplaceAllStringFunc(s, func(s string) string {
+		s = s[1:]
+		if s[0] == '{' {
+			s = s[1 : len(s)-1]
+		}
+		return os.Getenv(s)
+	})
+}
+
+type Parser struct {
+	ParseEnv      bool
+	ParseBacktick bool
+}
+
+func NewParser() *Parser {
+	return &Parser{ParseEnv, ParseBacktick}
+}
+
+func (p *Parser) Parse(line string) ([]string, error) {
+	line = strings.TrimSpace(line)
+
+	args := []string{}
+	buf := ""
+	var escaped, doubleQuoted, singleQuoted, backQuote bool
+	backtick := ""
+
+	for _, r := range line {
+		if escaped {
+			buf += string(r)
+			escaped = false
+			continue
+		}
+
+		if r == '\\' {
+			if singleQuoted {
+				buf += string(r)
+			} else {
+				escaped = true
+			}
+			continue
+		}
+
+		if isSpace(r) {
+			if singleQuoted || doubleQuoted || backQuote {
+				buf += string(r)
+				backtick += string(r)
+			} else if buf != "" {
+				if p.ParseEnv {
+					buf = replaceEnv(buf)
+				}
+				args = append(args, buf)
+				buf = ""
+			}
+			continue
+		}
+
+		switch r {
+		case '`':
+			if !singleQuoted && !doubleQuoted {
+				if p.ParseBacktick {
+					if backQuote {
+						out, err := shellRun(backtick)
+						if err != nil {
+							return nil, err
+						}
+						buf = out
+					}
+					backtick = ""
+					backQuote = !backQuote
+					continue
+				}
+				backtick = ""
+				backQuote = !backQuote
+			}
+		case '"':
+			if !singleQuoted {
+				doubleQuoted = !doubleQuoted
+				continue
+			}
+		case '\'':
+			if !doubleQuoted {
+				singleQuoted = !singleQuoted
+				continue
+			}
+		}
+
+		buf += string(r)
+		if backQuote {
+			backtick += string(r)
+		}
+	}
+
+	if buf != "" {
+		if p.ParseEnv {
+			buf = replaceEnv(buf)
+		}
+		args = append(args, buf)
+	}
+
+	if escaped || singleQuoted || doubleQuoted || backQuote {
+		return nil, errors.New("invalid command line string")
+	}
+
+	return args, nil
+}
+
+func Parse(line string) ([]string, error) {
+	return NewParser().Parse(line)
+}

+ 19 - 0
vendor/src/github.com/mattn/go-shellwords/util_posix.go

@@ -0,0 +1,19 @@
+// +build !windows
+
+package shellwords
+
+import (
+	"errors"
+	"os"
+	"os/exec"
+	"strings"
+)
+
+func shellRun(line string) (string, error) {
+	shell := os.Getenv("SHELL")
+	b, err := exec.Command(shell, "-c", line).Output()
+	if err != nil {
+		return "", errors.New(err.Error() + ":" + string(b))
+	}
+	return strings.TrimSpace(string(b)), nil
+}

+ 17 - 0
vendor/src/github.com/mattn/go-shellwords/util_windows.go

@@ -0,0 +1,17 @@
+package shellwords
+
+import (
+	"errors"
+	"os"
+	"os/exec"
+	"strings"
+)
+
+func shellRun(line string) (string, error) {
+	shell := os.Getenv("COMSPEC")
+	b, err := exec.Command(shell, "/c", line).Output()
+	if err != nil {
+		return "", errors.New(err.Error() + ":" + string(b))
+	}
+	return strings.TrimSpace(string(b)), nil
+}