mirror of
https://github.com/SagerNet/sing.git
synced 2025-04-04 20:37:40 +03:00
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
189 lines
3.5 KiB
Go
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
|
|
}
|