gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/asserts/headers.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package asserts 21 22 import ( 23 "bytes" 24 "fmt" 25 "regexp" 26 "sort" 27 "strings" 28 "unicode/utf8" 29 ) 30 31 var ( 32 nl = []byte("\n") 33 nlnl = []byte("\n\n") 34 35 // for basic sanity checking of header names 36 headerNameSanity = regexp.MustCompile("^[a-z](?:-?[a-z0-9])*$") 37 ) 38 39 func parseHeaders(head []byte) (map[string]interface{}, error) { 40 if !utf8.Valid(head) { 41 return nil, fmt.Errorf("header is not utf8") 42 } 43 headers := make(map[string]interface{}) 44 lines := strings.Split(string(head), "\n") 45 for i := 0; i < len(lines); { 46 entry := lines[i] 47 nameValueSplit := strings.Index(entry, ":") 48 if nameValueSplit == -1 { 49 return nil, fmt.Errorf("header entry missing ':' separator: %q", entry) 50 } 51 name := entry[:nameValueSplit] 52 if !headerNameSanity.MatchString(name) { 53 return nil, fmt.Errorf("invalid header name: %q", name) 54 } 55 56 consumed := nameValueSplit + 1 57 var value interface{} 58 var err error 59 value, i, err = parseEntry(consumed, i, lines, 0) 60 if err != nil { 61 return nil, err 62 } 63 64 if _, ok := headers[name]; ok { 65 return nil, fmt.Errorf("repeated header: %q", name) 66 } 67 68 headers[name] = value 69 } 70 return headers, nil 71 } 72 73 const ( 74 commonPrefix = " " 75 multilinePrefix = " " 76 listChar = "-" 77 listPrefix = commonPrefix + listChar 78 ) 79 80 func nestingPrefix(baseIndent int, prefix string) string { 81 return strings.Repeat(" ", baseIndent) + prefix 82 } 83 84 func parseEntry(consumedByIntro int, first int, lines []string, baseIndent int) (value interface{}, firstAfter int, err error) { 85 entry := lines[first] 86 i := first + 1 87 if consumedByIntro == len(entry) { 88 // multiline values 89 basePrefix := nestingPrefix(baseIndent, commonPrefix) 90 if i < len(lines) && strings.HasPrefix(lines[i], basePrefix) { 91 rest := lines[i][len(basePrefix):] 92 if strings.HasPrefix(rest, listChar) { 93 // list 94 return parseList(i, lines, baseIndent) 95 } 96 if len(rest) > 0 && rest[0] != ' ' { 97 // map 98 return parseMap(i, lines, baseIndent) 99 } 100 } 101 102 return parseMultilineText(i, lines, baseIndent) 103 } 104 105 // simple one-line value 106 if entry[consumedByIntro] != ' ' { 107 return nil, -1, fmt.Errorf("header entry should have a space or newline (for multiline) before value: %q", entry) 108 } 109 110 return entry[consumedByIntro+1:], i, nil 111 } 112 113 func parseMultilineText(first int, lines []string, baseIndent int) (value interface{}, firstAfter int, err error) { 114 size := 0 115 i := first 116 j := i 117 prefix := nestingPrefix(baseIndent, multilinePrefix) 118 for j < len(lines) { 119 iline := lines[j] 120 if !strings.HasPrefix(iline, prefix) { 121 break 122 } 123 size += len(iline) - len(prefix) + 1 124 j++ 125 } 126 if j == i { 127 var cur string 128 if i == len(lines) { 129 cur = "EOF" 130 } else { 131 cur = fmt.Sprintf("%q", lines[i]) 132 } 133 return nil, -1, fmt.Errorf("expected %d chars nesting prefix after multiline introduction %q: %s", len(prefix), lines[i-1], cur) 134 } 135 136 valueBuf := bytes.NewBuffer(make([]byte, 0, size-1)) 137 valueBuf.WriteString(lines[i][len(prefix):]) 138 i++ 139 for i < j { 140 valueBuf.WriteByte('\n') 141 valueBuf.WriteString(lines[i][len(prefix):]) 142 i++ 143 } 144 145 return valueBuf.String(), i, nil 146 } 147 148 func parseList(first int, lines []string, baseIndent int) (value interface{}, firstAfter int, err error) { 149 lst := []interface{}(nil) 150 j := first 151 prefix := nestingPrefix(baseIndent, listPrefix) 152 for j < len(lines) { 153 if !strings.HasPrefix(lines[j], prefix) { 154 return lst, j, nil 155 } 156 var v interface{} 157 var err error 158 v, j, err = parseEntry(len(prefix), j, lines, baseIndent+len(listPrefix)-1) 159 if err != nil { 160 return nil, -1, err 161 } 162 lst = append(lst, v) 163 } 164 return lst, j, nil 165 } 166 167 func parseMap(first int, lines []string, baseIndent int) (value interface{}, firstAfter int, err error) { 168 m := make(map[string]interface{}) 169 j := first 170 prefix := nestingPrefix(baseIndent, commonPrefix) 171 for j < len(lines) { 172 if !strings.HasPrefix(lines[j], prefix) { 173 return m, j, nil 174 } 175 176 entry := lines[j][len(prefix):] 177 keyValueSplit := strings.Index(entry, ":") 178 if keyValueSplit == -1 { 179 return nil, -1, fmt.Errorf("map entry missing ':' separator: %q", entry) 180 } 181 key := entry[:keyValueSplit] 182 if !headerNameSanity.MatchString(key) { 183 return nil, -1, fmt.Errorf("invalid map entry key: %q", key) 184 } 185 186 consumed := keyValueSplit + 1 187 var value interface{} 188 var err error 189 value, j, err = parseEntry(len(prefix)+consumed, j, lines, len(prefix)) 190 if err != nil { 191 return nil, -1, err 192 } 193 194 if _, ok := m[key]; ok { 195 return nil, -1, fmt.Errorf("repeated map entry: %q", key) 196 } 197 198 m[key] = value 199 } 200 return m, j, nil 201 } 202 203 // checkHeader checks that the header values are strings, or nested lists or maps with strings as the only scalars 204 func checkHeader(v interface{}) error { 205 switch x := v.(type) { 206 case string: 207 return nil 208 case []interface{}: 209 for _, elem := range x { 210 err := checkHeader(elem) 211 if err != nil { 212 return err 213 } 214 } 215 return nil 216 case map[string]interface{}: 217 for _, elem := range x { 218 err := checkHeader(elem) 219 if err != nil { 220 return err 221 } 222 } 223 return nil 224 default: 225 return fmt.Errorf("header values must be strings or nested lists or maps with strings as the only scalars: %v", v) 226 } 227 } 228 229 // checkHeaders checks that headers are of expected types 230 func checkHeaders(headers map[string]interface{}) error { 231 for name, value := range headers { 232 err := checkHeader(value) 233 if err != nil { 234 return fmt.Errorf("header %q: %v", name, err) 235 } 236 } 237 return nil 238 } 239 240 // copyHeader helps deep copying header values to defend against external mutations 241 func copyHeader(v interface{}) interface{} { 242 switch x := v.(type) { 243 case string: 244 return x 245 case []interface{}: 246 res := make([]interface{}, len(x)) 247 for i, elem := range x { 248 res[i] = copyHeader(elem) 249 } 250 return res 251 case map[string]interface{}: 252 res := make(map[string]interface{}, len(x)) 253 for name, value := range x { 254 if value == nil { 255 continue // normalize nils out 256 } 257 res[name] = copyHeader(value) 258 } 259 return res 260 default: 261 panic(fmt.Sprintf("internal error: encountered unexpected value type copying headers: %v", v)) 262 } 263 } 264 265 // copyHeader helps deep copying headers to defend against external mutations 266 func copyHeaders(headers map[string]interface{}) map[string]interface{} { 267 return copyHeader(headers).(map[string]interface{}) 268 } 269 270 func appendEntry(buf *bytes.Buffer, intro string, v interface{}, baseIndent int) { 271 switch x := v.(type) { 272 case nil: 273 return // omit 274 case string: 275 buf.WriteByte('\n') 276 buf.WriteString(intro) 277 if strings.ContainsRune(x, '\n') { 278 // multiline value => quote by 4-space indenting 279 buf.WriteByte('\n') 280 pfx := nestingPrefix(baseIndent, multilinePrefix) 281 buf.WriteString(pfx) 282 x = strings.Replace(x, "\n", "\n"+pfx, -1) 283 } else { 284 buf.WriteByte(' ') 285 } 286 buf.WriteString(x) 287 case []interface{}: 288 if len(x) == 0 { 289 return // simply omit 290 } 291 buf.WriteByte('\n') 292 buf.WriteString(intro) 293 pfx := nestingPrefix(baseIndent, listPrefix) 294 for _, elem := range x { 295 appendEntry(buf, pfx, elem, baseIndent+len(listPrefix)-1) 296 } 297 case map[string]interface{}: 298 if len(x) == 0 { 299 return // simply omit 300 } 301 buf.WriteByte('\n') 302 buf.WriteString(intro) 303 // emit entries sorted by key 304 keys := make([]string, len(x)) 305 i := 0 306 for key := range x { 307 keys[i] = key 308 i++ 309 } 310 sort.Strings(keys) 311 pfx := nestingPrefix(baseIndent, commonPrefix) 312 for _, key := range keys { 313 appendEntry(buf, pfx+key+":", x[key], len(pfx)) 314 } 315 default: 316 panic(fmt.Sprintf("internal error: encountered unexpected value type formatting headers: %v", v)) 317 } 318 }