| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- package param
- import (
- "bufio"
- "fmt"
- "io"
- "strings"
- )
- // Param is a key/value header parameter
- type Param struct {
- Key string
- Value string
- Quote bool
- }
- // String returns the formatted parameter
- func (p Param) String() string {
- if p.Quote {
- return fmt.Sprintf("%s=%q", p.Key, p.Value)
- }
- return fmt.Sprintf("%s=%s", p.Key, p.Value)
- }
- // Format formats the parameters to be included in the header
- func Format(pp ...Param) string {
- var b strings.Builder
- for i, p := range pp {
- if i > 0 {
- b.WriteString(", ")
- }
- b.WriteString(p.String())
- }
- return b.String()
- }
- // Parse parses the header parameters
- func Parse(s string) ([]Param, error) {
- var pp []Param
- br := bufio.NewReader(strings.NewReader(s))
- for i := 0; true; i++ {
- // skip whitespace
- if err := skipWhite(br); err != nil {
- return nil, err
- }
- // see if there's more to read
- if _, err := br.Peek(1); err == io.EOF {
- break
- }
- // read key/value pair
- p, err := parseParam(br, i == 0)
- if err != nil {
- return nil, fmt.Errorf("param: %w", err)
- }
- pp = append(pp, p)
- }
- return pp, nil
- }
- func parseIdent(br *bufio.Reader) (string, error) {
- var ident []byte
- for {
- b, err := br.ReadByte()
- if err == io.EOF {
- break
- }
- if err != nil {
- return "", err
- }
- if !(('a' <= b && b <= 'z') || ('A' <= b && b <= 'Z') || '0' <= b && b <= '9' || b == '-') {
- if err := br.UnreadByte(); err != nil {
- return "", err
- }
- break
- }
- ident = append(ident, b)
- }
- return string(ident), nil
- }
- func parseByte(br *bufio.Reader, expect byte) error {
- b, err := br.ReadByte()
- if err != nil {
- if err == io.EOF {
- return fmt.Errorf("expected '%c', got EOF", expect)
- }
- return err
- }
- if b != expect {
- return fmt.Errorf("expected '%c', got '%c'", expect, b)
- }
- return nil
- }
- func parseString(br *bufio.Reader) (string, error) {
- var s []rune
- // read the open quote
- if err := parseByte(br, '"'); err != nil {
- return "", err
- }
- // read the string
- var escaped bool
- for {
- r, _, err := br.ReadRune()
- if err != nil {
- return "", err
- }
- if escaped {
- s = append(s, r)
- escaped = false
- continue
- }
- if r == '\\' {
- escaped = true
- continue
- }
- // closing quote
- if r == '"' {
- break
- }
- s = append(s, r)
- }
- return string(s), nil
- }
- func skipWhite(br *bufio.Reader) error {
- for {
- b, err := br.ReadByte()
- if err != nil {
- if err == io.EOF {
- return nil
- }
- return err
- }
- if b != ' ' {
- return br.UnreadByte()
- }
- }
- }
- func parseParam(br *bufio.Reader, first bool) (Param, error) {
- // skip whitespace
- if err := skipWhite(br); err != nil {
- return Param{}, err
- }
- if !first {
- // read the comma separator
- if err := parseByte(br, ','); err != nil {
- return Param{}, err
- }
- // skip whitespace
- if err := skipWhite(br); err != nil {
- return Param{}, err
- }
- }
- // read the key
- key, err := parseIdent(br)
- if err != nil {
- return Param{}, err
- }
- // skip whitespace
- if err := skipWhite(br); err != nil {
- return Param{}, err
- }
- // read the equals sign
- if err := parseByte(br, '='); err != nil {
- return Param{}, err
- }
- // skip whitespace
- if err := skipWhite(br); err != nil {
- return Param{}, err
- }
- // read the value
- var value string
- var quote bool
- if b, _ := br.Peek(1); len(b) == 1 && b[0] == '"' {
- quote = true
- value, err = parseString(br)
- } else {
- value, err = parseIdent(br)
- }
- if err != nil {
- return Param{}, err
- }
- return Param{Key: key, Value: value, Quote: quote}, nil
- }
|