David K. Bainbridge | 528b318 | 2017-01-23 08:51:59 -0800 | [diff] [blame] | 1 | // Copyright 2012, 2013 Canonical Ltd. |
| 2 | // Licensed under the LGPLv3, see LICENCE file for details. |
| 3 | |
| 4 | package utils |
| 5 | |
| 6 | import ( |
| 7 | "bytes" |
| 8 | "compress/gzip" |
| 9 | "crypto/sha256" |
| 10 | "encoding/hex" |
| 11 | "io" |
| 12 | "io/ioutil" |
| 13 | "os" |
| 14 | "strings" |
| 15 | "unicode" |
| 16 | ) |
| 17 | |
| 18 | // TODO(ericsnow) Move the quoting helpers into the shell package? |
| 19 | |
| 20 | // ShQuote quotes s so that when read by bash, no metacharacters |
| 21 | // within s will be interpreted as such. |
| 22 | func ShQuote(s string) string { |
| 23 | // single-quote becomes single-quote, double-quote, single-quote, double-quote, single-quote |
| 24 | return `'` + strings.Replace(s, `'`, `'"'"'`, -1) + `'` |
| 25 | } |
| 26 | |
| 27 | // WinPSQuote quotes s so that when read by powershell, no metacharacters |
| 28 | // within s will be interpreted as such. |
| 29 | func WinPSQuote(s string) string { |
| 30 | // See http://ss64.com/ps/syntax-esc.html#quotes. |
| 31 | // Double quotes inside single quotes are fine, double single quotes inside |
| 32 | // single quotes, not so much so. Having double quoted strings inside single |
| 33 | // quoted strings, ensure no expansion happens. |
| 34 | return `'` + strings.Replace(s, `'`, `"`, -1) + `'` |
| 35 | } |
| 36 | |
| 37 | // WinCmdQuote quotes s so that when read by cmd.exe, no metacharacters |
| 38 | // within s will be interpreted as such. |
| 39 | func WinCmdQuote(s string) string { |
| 40 | // See http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx. |
| 41 | quoted := winCmdQuote(s) |
| 42 | return winCmdEscapeMeta(quoted) |
| 43 | } |
| 44 | |
| 45 | func winCmdQuote(s string) string { |
| 46 | var escaped string |
| 47 | for _, c := range s { |
| 48 | switch c { |
| 49 | case '\\', '"': |
| 50 | escaped += `\` |
| 51 | } |
| 52 | escaped += string(c) |
| 53 | } |
| 54 | return `"` + escaped + `"` |
| 55 | } |
| 56 | |
| 57 | func winCmdEscapeMeta(str string) string { |
| 58 | const meta = `()%!^"<>&|` |
| 59 | var newStr string |
| 60 | for _, c := range str { |
| 61 | if strings.Contains(meta, string(c)) { |
| 62 | newStr += "^" |
| 63 | } |
| 64 | newStr += string(c) |
| 65 | } |
| 66 | return newStr |
| 67 | } |
| 68 | |
| 69 | // CommandString flattens a sequence of command arguments into a |
| 70 | // string suitable for executing in a shell, escaping slashes, |
| 71 | // variables and quotes as necessary; each argument is double-quoted |
| 72 | // if and only if necessary. |
| 73 | func CommandString(args ...string) string { |
| 74 | var buf bytes.Buffer |
| 75 | for i, arg := range args { |
| 76 | needsQuotes := false |
| 77 | var argBuf bytes.Buffer |
| 78 | for _, r := range arg { |
| 79 | if unicode.IsSpace(r) { |
| 80 | needsQuotes = true |
| 81 | } else if r == '"' || r == '$' || r == '\\' { |
| 82 | needsQuotes = true |
| 83 | argBuf.WriteByte('\\') |
| 84 | } |
| 85 | argBuf.WriteRune(r) |
| 86 | } |
| 87 | if i > 0 { |
| 88 | buf.WriteByte(' ') |
| 89 | } |
| 90 | if needsQuotes { |
| 91 | buf.WriteByte('"') |
| 92 | argBuf.WriteTo(&buf) |
| 93 | buf.WriteByte('"') |
| 94 | } else { |
| 95 | argBuf.WriteTo(&buf) |
| 96 | } |
| 97 | } |
| 98 | return buf.String() |
| 99 | } |
| 100 | |
| 101 | // Gzip compresses the given data. |
| 102 | func Gzip(data []byte) []byte { |
| 103 | var buf bytes.Buffer |
| 104 | w := gzip.NewWriter(&buf) |
| 105 | if _, err := w.Write(data); err != nil { |
| 106 | // Compression should never fail unless it fails |
| 107 | // to write to the underlying writer, which is a bytes.Buffer |
| 108 | // that never fails. |
| 109 | panic(err) |
| 110 | } |
| 111 | if err := w.Close(); err != nil { |
| 112 | panic(err) |
| 113 | } |
| 114 | return buf.Bytes() |
| 115 | } |
| 116 | |
| 117 | // Gunzip uncompresses the given data. |
| 118 | func Gunzip(data []byte) ([]byte, error) { |
| 119 | r, err := gzip.NewReader(bytes.NewReader(data)) |
| 120 | if err != nil { |
| 121 | return nil, err |
| 122 | } |
| 123 | return ioutil.ReadAll(r) |
| 124 | } |
| 125 | |
| 126 | // ReadSHA256 returns the SHA256 hash of the contents read from source |
| 127 | // (hex encoded) and the size of the source in bytes. |
| 128 | func ReadSHA256(source io.Reader) (string, int64, error) { |
| 129 | hash := sha256.New() |
| 130 | size, err := io.Copy(hash, source) |
| 131 | if err != nil { |
| 132 | return "", 0, err |
| 133 | } |
| 134 | digest := hex.EncodeToString(hash.Sum(nil)) |
| 135 | return digest, size, nil |
| 136 | } |
| 137 | |
| 138 | // ReadFileSHA256 is like ReadSHA256 but reads the contents of the |
| 139 | // given file. |
| 140 | func ReadFileSHA256(filename string) (string, int64, error) { |
| 141 | f, err := os.Open(filename) |
| 142 | if err != nil { |
| 143 | return "", 0, err |
| 144 | } |
| 145 | defer f.Close() |
| 146 | return ReadSHA256(f) |
| 147 | } |