github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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  }