sing/common/param/param.go
Tommy Wu 0a3c811e42 add Digest authentication for http proxy server
https://datatracker.ietf.org/doc/html/rfc2617
server will send both Basic and Digest header to client
client can use either Basic or Digest for authentication

Change-Id: Iaa6629c143551770c836af3ead823bd148b244c6
2025-02-12 15:00:40 +08:00

189 lines
3.5 KiB
Go

package param
// code retrieve from https://github.com/icholy/digest/tree/master/internal/param
import (
"bufio"
"fmt"
"io"
"strconv"
"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 p.Key + "=" + strconv.Quote(p.Value)
}
return 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
}