add vendor data
All checks were successful
Create and publish a Docker image 🚀 / build-and-push-image (push) Successful in 1m49s
All checks were successful
Create and publish a Docker image 🚀 / build-and-push-image (push) Successful in 1m49s
This commit is contained in:
3
vendor/github.com/dunglas/httpsfv/.gitmodules
generated
vendored
Normal file
3
vendor/github.com/dunglas/httpsfv/.gitmodules
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "structured-field-tests"]
|
||||
path = structured-field-tests
|
||||
url = https://github.com/httpwg/structured-field-tests
|
||||
27
vendor/github.com/dunglas/httpsfv/LICENSE
generated
vendored
Normal file
27
vendor/github.com/dunglas/httpsfv/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2020 Kévin Dunglas. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
25
vendor/github.com/dunglas/httpsfv/README.md
generated
vendored
Normal file
25
vendor/github.com/dunglas/httpsfv/README.md
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# httpsfv: Structured Field Values for HTTP in Go
|
||||
|
||||
This [Go (golang)](https://golang.org) library implements parsing and serialization for [Structured Field Values for HTTP (RFC 9651 and 8941)](https://httpwg.org/specs/rfc9651.html).
|
||||
|
||||
[](https://pkg.go.dev/github.com/dunglas/httpsfv)
|
||||

|
||||
[](https://coveralls.io/github/dunglas/httpsfv?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/dunglas/httpsfv)
|
||||
|
||||
## Features
|
||||
|
||||
* Fully implementing the RFC
|
||||
* Compliant with [the official test suite](https://github.com/httpwg/structured-field-tests)
|
||||
* Unit and fuzz tested
|
||||
* Strongly-typed
|
||||
* Fast (see [the benchmark](httpwg_test.go))
|
||||
* No dependencies
|
||||
|
||||
## Docs
|
||||
|
||||
[Browse the documentation on Go.dev](https://pkg.go.dev/github.com/dunglas/httpsfv).
|
||||
|
||||
## Credits
|
||||
|
||||
Created by [Kévin Dunglas](https://dunglas.fr). Sponsored by [Les-Tilleuls.coop](https://les-tilleuls.coop).
|
||||
115
vendor/github.com/dunglas/httpsfv/bareitem.go
generated
vendored
Normal file
115
vendor/github.com/dunglas/httpsfv/bareitem.go
generated
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrInvalidBareItem is returned when a bare item is invalid.
|
||||
var ErrInvalidBareItem = errors.New(
|
||||
"invalid bare item type (allowed types are bool, string, int64, float64, []byte, time.Time and Token)",
|
||||
)
|
||||
|
||||
// assertBareItem asserts that v is a valid bare item
|
||||
// according to https://httpwg.org/specs/rfc9651.html#item.
|
||||
//
|
||||
// v can be either:
|
||||
//
|
||||
// * an integer (Section 3.3.1.)
|
||||
// * a decimal (Section 3.3.2.)
|
||||
// * a string (Section 3.3.3.)
|
||||
// * a token (Section 3.3.4.)
|
||||
// * a byte sequence (Section 3.3.5.)
|
||||
// * a boolean (Section 3.3.6.)
|
||||
// * a date (Section 3.3.7.)
|
||||
// * a display string (Section 3.3.8.)
|
||||
func assertBareItem(v interface{}) {
|
||||
switch v.(type) {
|
||||
case bool,
|
||||
string,
|
||||
int,
|
||||
int8,
|
||||
int16,
|
||||
int32,
|
||||
int64,
|
||||
uint,
|
||||
uint8,
|
||||
uint16,
|
||||
uint32,
|
||||
uint64,
|
||||
float32,
|
||||
float64,
|
||||
[]byte,
|
||||
time.Time,
|
||||
Token,
|
||||
DisplayString:
|
||||
return
|
||||
default:
|
||||
panic(fmt.Errorf("%w: got %s", ErrInvalidBareItem, reflect.TypeOf(v)))
|
||||
}
|
||||
}
|
||||
|
||||
// marshalBareItem serializes as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#ser-bare-item.
|
||||
func marshalBareItem(b *strings.Builder, v interface{}) error {
|
||||
switch v := v.(type) {
|
||||
case bool:
|
||||
return marshalBoolean(b, v)
|
||||
case string:
|
||||
return marshalString(b, v)
|
||||
case int64:
|
||||
return marshalInteger(b, v)
|
||||
case int, int8, int16, int32:
|
||||
return marshalInteger(b, reflect.ValueOf(v).Int())
|
||||
case uint, uint8, uint16, uint32, uint64:
|
||||
// Casting an uint64 to an int64 is possible because the maximum allowed value is 999,999,999,999,999
|
||||
return marshalInteger(b, int64(reflect.ValueOf(v).Uint()))
|
||||
case float32, float64:
|
||||
return marshalDecimal(b, v.(float64))
|
||||
case []byte:
|
||||
return marshalBinary(b, v)
|
||||
case time.Time:
|
||||
return marshalDate(b, v)
|
||||
case Token:
|
||||
return v.marshalSFV(b)
|
||||
case DisplayString:
|
||||
return v.marshalSFV(b)
|
||||
default:
|
||||
panic(ErrInvalidBareItem)
|
||||
}
|
||||
}
|
||||
|
||||
// parseBareItem parses as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#parse-bare-item.
|
||||
func parseBareItem(s *scanner) (interface{}, error) {
|
||||
if s.eof() {
|
||||
return nil, &UnmarshalError{s.off, ErrUnexpectedEndOfString}
|
||||
}
|
||||
|
||||
c := s.data[s.off]
|
||||
switch c {
|
||||
case '"':
|
||||
return parseString(s)
|
||||
case '?':
|
||||
return parseBoolean(s)
|
||||
case '*':
|
||||
return parseToken(s)
|
||||
case ':':
|
||||
return parseBinary(s)
|
||||
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
return parseNumber(s)
|
||||
case '@':
|
||||
return parseDate(s)
|
||||
case '%':
|
||||
return parseDisplayString(s)
|
||||
default:
|
||||
if isAlpha(c) {
|
||||
return parseToken(s)
|
||||
}
|
||||
|
||||
return nil, &UnmarshalError{s.off, ErrUnrecognizedCharacter}
|
||||
}
|
||||
}
|
||||
59
vendor/github.com/dunglas/httpsfv/binary.go
generated
vendored
Normal file
59
vendor/github.com/dunglas/httpsfv/binary.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrInvalidBinaryFormat is returned when the binary format is invalid.
|
||||
var ErrInvalidBinaryFormat = errors.New("invalid binary format")
|
||||
|
||||
// marshalBinary serializes as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#ser-binary.
|
||||
func marshalBinary(b *strings.Builder, bs []byte) error {
|
||||
if err := b.WriteByte(':'); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf := make([]byte, base64.StdEncoding.EncodedLen(len(bs)))
|
||||
base64.StdEncoding.Encode(buf, bs)
|
||||
|
||||
if _, err := b.Write(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.WriteByte(':')
|
||||
}
|
||||
|
||||
// parseBinary parses as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#parse-binary.
|
||||
func parseBinary(s *scanner) ([]byte, error) {
|
||||
if s.eof() || s.data[s.off] != ':' {
|
||||
return nil, &UnmarshalError{s.off, ErrInvalidBinaryFormat}
|
||||
}
|
||||
s.off++
|
||||
|
||||
start := s.off
|
||||
|
||||
for !s.eof() {
|
||||
c := s.data[s.off]
|
||||
if c == ':' {
|
||||
// base64decode
|
||||
decoded, err := base64.StdEncoding.DecodeString(s.data[start:s.off])
|
||||
if err != nil {
|
||||
return nil, &UnmarshalError{s.off, err}
|
||||
}
|
||||
s.off++
|
||||
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
if !isAlpha(c) && !isDigit(c) && c != '+' && c != '/' && c != '=' {
|
||||
return nil, &UnmarshalError{s.off, ErrInvalidBinaryFormat}
|
||||
}
|
||||
s.off++
|
||||
}
|
||||
|
||||
return nil, &UnmarshalError{s.off, ErrInvalidBinaryFormat}
|
||||
}
|
||||
49
vendor/github.com/dunglas/httpsfv/boolean.go
generated
vendored
Normal file
49
vendor/github.com/dunglas/httpsfv/boolean.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// ErrInvalidBooleanFormat is returned when a boolean format is invalid.
|
||||
var ErrInvalidBooleanFormat = errors.New("invalid boolean format")
|
||||
|
||||
// marshalBoolean serializes as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#ser-boolean.
|
||||
func marshalBoolean(bd io.StringWriter, b bool) error {
|
||||
if b {
|
||||
_, err := bd.WriteString("?1")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := bd.WriteString("?0")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// parseBoolean parses as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#parse-boolean.
|
||||
func parseBoolean(s *scanner) (bool, error) {
|
||||
if s.eof() || s.data[s.off] != '?' {
|
||||
return false, &UnmarshalError{s.off, ErrInvalidBooleanFormat}
|
||||
}
|
||||
s.off++
|
||||
|
||||
if s.eof() {
|
||||
return false, &UnmarshalError{s.off, ErrInvalidBooleanFormat}
|
||||
}
|
||||
|
||||
switch s.data[s.off] {
|
||||
case '0':
|
||||
s.off++
|
||||
|
||||
return false, nil
|
||||
case '1':
|
||||
s.off++
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, &UnmarshalError{s.off, ErrInvalidBooleanFormat}
|
||||
}
|
||||
41
vendor/github.com/dunglas/httpsfv/date.go
generated
vendored
Normal file
41
vendor/github.com/dunglas/httpsfv/date.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrInvalidDateFormat = errors.New("invalid date format")
|
||||
|
||||
// marshalDate serializes as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#ser-date.
|
||||
func marshalDate(b io.StringWriter, i time.Time) error {
|
||||
_, err := b.WriteString("@")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return marshalInteger(b, i.Unix())
|
||||
}
|
||||
|
||||
// parseDate parses as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#parse-date.
|
||||
func parseDate(s *scanner) (time.Time, error) {
|
||||
if s.eof() || s.data[s.off] != '@' {
|
||||
return time.Time{}, &UnmarshalError{s.off, ErrInvalidDateFormat}
|
||||
}
|
||||
s.off++
|
||||
|
||||
n, err := parseNumber(s)
|
||||
if err != nil {
|
||||
return time.Time{}, &UnmarshalError{s.off, ErrInvalidDateFormat}
|
||||
}
|
||||
|
||||
i, ok := n.(int64)
|
||||
if !ok {
|
||||
return time.Time{}, &UnmarshalError{s.off, ErrInvalidDateFormat}
|
||||
}
|
||||
|
||||
return time.Unix(i, 0), nil
|
||||
}
|
||||
64
vendor/github.com/dunglas/httpsfv/decimal.go
generated
vendored
Normal file
64
vendor/github.com/dunglas/httpsfv/decimal.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const maxDecDigit = 3
|
||||
|
||||
// ErrInvalidDecimal is returned when a decimal is invalid.
|
||||
var ErrInvalidDecimal = errors.New("the integer portion is larger than 12 digits: invalid decimal")
|
||||
|
||||
// marshalDecimal serializes as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#ser-decimal.
|
||||
//
|
||||
// TODO(dunglas): add support for decimal float type when one will be available
|
||||
// (https://github.com/golang/go/issues/19787)
|
||||
func marshalDecimal(b io.StringWriter, d float64) error {
|
||||
const TH = 0.001
|
||||
|
||||
rounded := math.RoundToEven(d/TH) * TH
|
||||
i, frac := math.Modf(rounded)
|
||||
|
||||
if i < -999999999999 || i > 999999999999 {
|
||||
return ErrInvalidDecimal
|
||||
}
|
||||
|
||||
if _, err := b.WriteString(strings.TrimRight(strconv.FormatFloat(rounded, 'f', 3, 64), "0")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if frac == 0 {
|
||||
_, err := b.WriteString("0")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseDecimal(s *scanner, decSepOff int, str string, neg bool) (float64, error) {
|
||||
if decSepOff == s.off-1 {
|
||||
return 0, &UnmarshalError{s.off, ErrInvalidDecimalFormat}
|
||||
}
|
||||
|
||||
if len(s.data[decSepOff+1:s.off]) > maxDecDigit {
|
||||
return 0, &UnmarshalError{s.off, ErrNumberOutOfRange}
|
||||
}
|
||||
|
||||
i, err := strconv.ParseFloat(str, 64)
|
||||
if err != nil {
|
||||
// Should never happen
|
||||
return 0, &UnmarshalError{s.off, err}
|
||||
}
|
||||
|
||||
if neg {
|
||||
i = -i
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
63
vendor/github.com/dunglas/httpsfv/decode.go
generated
vendored
Normal file
63
vendor/github.com/dunglas/httpsfv/decode.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrUnexpectedEndOfString is returned when the end of string is unexpected.
|
||||
var ErrUnexpectedEndOfString = errors.New("unexpected end of string")
|
||||
|
||||
// ErrUnrecognizedCharacter is returned when an unrecognized character in encountered.
|
||||
var ErrUnrecognizedCharacter = errors.New("unrecognized character")
|
||||
|
||||
// UnmarshalError contains the underlying parsing error and the position at which it occurred.
|
||||
type UnmarshalError struct {
|
||||
off int
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *UnmarshalError) Error() string {
|
||||
if e.err != nil {
|
||||
return fmt.Sprintf("%s: character %d", e.err, e.off)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("unmarshal error: character %d", e.off)
|
||||
}
|
||||
|
||||
func (e *UnmarshalError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
type scanner struct {
|
||||
data string
|
||||
off int
|
||||
}
|
||||
|
||||
// scanWhileSp consumes spaces.
|
||||
func (s *scanner) scanWhileSp() {
|
||||
for !s.eof() {
|
||||
if s.data[s.off] != ' ' {
|
||||
return
|
||||
}
|
||||
|
||||
s.off++
|
||||
}
|
||||
}
|
||||
|
||||
// scanWhileOWS consumes optional white space (OWS) characters.
|
||||
func (s *scanner) scanWhileOWS() {
|
||||
for !s.eof() {
|
||||
c := s.data[s.off]
|
||||
if c != ' ' && c != '\t' {
|
||||
return
|
||||
}
|
||||
|
||||
s.off++
|
||||
}
|
||||
}
|
||||
|
||||
// eof returns true if the parser consumed all available characters.
|
||||
func (s *scanner) eof() bool {
|
||||
return s.off == len(s.data)
|
||||
}
|
||||
167
vendor/github.com/dunglas/httpsfv/dictionary.go
generated
vendored
Normal file
167
vendor/github.com/dunglas/httpsfv/dictionary.go
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Dictionary is an ordered map of name-value pairs.
|
||||
// See https://httpwg.org/specs/rfc9651.html#dictionary
|
||||
// Values can be:
|
||||
// * Item (Section 3.3.)
|
||||
// * Inner List (Section 3.1.1.)
|
||||
type Dictionary struct {
|
||||
names []string
|
||||
values map[string]Member
|
||||
}
|
||||
|
||||
// ErrInvalidDictionaryFormat is returned when a dictionary value is invalid.
|
||||
var ErrInvalidDictionaryFormat = errors.New("invalid dictionary format")
|
||||
|
||||
// NewDictionary creates a new ordered map.
|
||||
func NewDictionary() *Dictionary {
|
||||
d := Dictionary{}
|
||||
d.names = []string{}
|
||||
d.values = map[string]Member{}
|
||||
|
||||
return &d
|
||||
}
|
||||
|
||||
// Get retrieves a member.
|
||||
func (d *Dictionary) Get(k string) (Member, bool) {
|
||||
v, ok := d.values[k]
|
||||
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Add appends a new member to the ordered list.
|
||||
func (d *Dictionary) Add(k string, v Member) {
|
||||
if _, exists := d.values[k]; !exists {
|
||||
d.names = append(d.names, k)
|
||||
}
|
||||
|
||||
d.values[k] = v
|
||||
}
|
||||
|
||||
// Del removes a member from the ordered list.
|
||||
func (d *Dictionary) Del(key string) bool {
|
||||
if _, ok := d.values[key]; !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, k := range d.names {
|
||||
if k == key {
|
||||
d.names = append(d.names[:i], d.names[i+1:]...)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
delete(d.values, key)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Names retrieves the list of member names in the appropriate order.
|
||||
func (d *Dictionary) Names() []string {
|
||||
return d.names
|
||||
}
|
||||
|
||||
func (d *Dictionary) marshalSFV(b *strings.Builder) error {
|
||||
last := len(d.names) - 1
|
||||
|
||||
for m, k := range d.names {
|
||||
if err := marshalKey(b, k); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v := d.values[k]
|
||||
|
||||
if item, ok := v.(Item); ok && item.Value == true {
|
||||
if err := item.Params.marshalSFV(b); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := b.WriteByte('='); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := v.marshalSFV(b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if m != last {
|
||||
if _, err := b.WriteString(", "); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalDictionary parses a dictionary as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#parse-dictionary.
|
||||
func UnmarshalDictionary(v []string) (*Dictionary, error) {
|
||||
s := &scanner{
|
||||
data: strings.Join(v, ","),
|
||||
}
|
||||
|
||||
s.scanWhileSp()
|
||||
|
||||
sfv, err := parseDictionary(s)
|
||||
if err != nil {
|
||||
return sfv, err
|
||||
}
|
||||
|
||||
return sfv, nil
|
||||
}
|
||||
|
||||
func parseDictionary(s *scanner) (*Dictionary, error) {
|
||||
d := NewDictionary()
|
||||
|
||||
for !s.eof() {
|
||||
k, err := parseKey(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var m Member
|
||||
|
||||
if !s.eof() && s.data[s.off] == '=' {
|
||||
s.off++
|
||||
m, err = parseItemOrInnerList(s)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
p, err := parseParams(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m = Item{true, p}
|
||||
}
|
||||
|
||||
d.Add(k, m)
|
||||
s.scanWhileOWS()
|
||||
|
||||
if s.eof() {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
if s.data[s.off] != ',' {
|
||||
return nil, &UnmarshalError{s.off, ErrInvalidDictionaryFormat}
|
||||
}
|
||||
s.off++
|
||||
|
||||
s.scanWhileOWS()
|
||||
|
||||
if s.eof() {
|
||||
// there is a trailing comma
|
||||
return nil, &UnmarshalError{s.off, ErrInvalidDictionaryFormat}
|
||||
}
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
105
vendor/github.com/dunglas/httpsfv/displaystring.go
generated
vendored
Normal file
105
vendor/github.com/dunglas/httpsfv/displaystring.go
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type DisplayString string
|
||||
|
||||
var ErrInvalidDisplayString = errors.New("invalid display string type")
|
||||
|
||||
var notVcharOrSp = &unicode.RangeTable{
|
||||
R16: []unicode.Range16{
|
||||
{0x0000, 0x001f, 1},
|
||||
{0x007f, 0x00ff, 1},
|
||||
},
|
||||
LatinOffset: 2,
|
||||
}
|
||||
|
||||
// marshalSFV serializes as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#ser-string.
|
||||
func (s DisplayString) marshalSFV(b *strings.Builder) error {
|
||||
if _, err := b.WriteString(`%"`); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == '%' || s[i] == '"' || unicode.Is(notVcharOrSp, rune(s[i])) {
|
||||
b.WriteRune('%')
|
||||
b.WriteString(hex.EncodeToString([]byte{s[i]}))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
b.WriteByte(s[i])
|
||||
}
|
||||
|
||||
b.WriteByte('"')
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseDisplayString parses as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#parse-display.
|
||||
func parseDisplayString(s *scanner) (DisplayString, error) {
|
||||
if s.eof() || len(s.data[s.off:]) < 2 || s.data[s.off:2] != `%"` {
|
||||
return "", &UnmarshalError{s.off, ErrInvalidDisplayString}
|
||||
}
|
||||
s.off += 2
|
||||
|
||||
var b strings.Builder
|
||||
for !s.eof() {
|
||||
c := s.data[s.off]
|
||||
s.off++
|
||||
|
||||
switch c {
|
||||
case '%':
|
||||
if len(s.data[s.off:]) < 2 {
|
||||
return "", &UnmarshalError{s.off, ErrInvalidDisplayString}
|
||||
}
|
||||
c0 := unhex(s.data[s.off])
|
||||
if c0 == 0 {
|
||||
return "", &UnmarshalError{s.off, ErrInvalidDisplayString}
|
||||
}
|
||||
|
||||
c1 := unhex(s.data[s.off+1])
|
||||
if c1 == 0 {
|
||||
return "", &UnmarshalError{s.off, ErrInvalidDisplayString}
|
||||
}
|
||||
|
||||
b.WriteByte(c0<<4 | c1)
|
||||
s.off += 2
|
||||
case '"':
|
||||
r := b.String()
|
||||
if !utf8.ValidString(r) {
|
||||
return "", ErrInvalidDisplayString
|
||||
}
|
||||
|
||||
return DisplayString(r), nil
|
||||
|
||||
default:
|
||||
if unicode.Is(notVcharOrSp, rune(c)) {
|
||||
return "", &UnmarshalError{s.off, ErrInvalidDisplayString}
|
||||
}
|
||||
|
||||
b.WriteByte(c)
|
||||
}
|
||||
}
|
||||
|
||||
return "", &UnmarshalError{s.off, ErrInvalidDisplayString}
|
||||
}
|
||||
|
||||
func unhex(c byte) byte {
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
return c - '0'
|
||||
case 'a' <= c && c <= 'f':
|
||||
return c - 'a' + 10
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
42
vendor/github.com/dunglas/httpsfv/encode.go
generated
vendored
Normal file
42
vendor/github.com/dunglas/httpsfv/encode.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// Package httpsfv implements serializing and parsing
|
||||
// of Structured Field Values for HTTP as defined in RFC 9651.
|
||||
//
|
||||
// Structured Field Values are either lists, dictionaries or items. Dedicated types are provided for all of them.
|
||||
// Dedicated types are also used for tokens, parameters and inner lists.
|
||||
// Other values are stored in native types:
|
||||
//
|
||||
// int64, for integers
|
||||
// float64, for decimals
|
||||
// string, for strings
|
||||
// byte[], for byte sequences
|
||||
// bool, for booleans
|
||||
//
|
||||
// The specification is available at https://httpwg.org/specs/rfc9651.html.
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// marshaler is the interface implemented by types that can marshal themselves into valid SFV.
|
||||
type marshaler interface {
|
||||
marshalSFV(b *strings.Builder) error
|
||||
}
|
||||
|
||||
// StructuredFieldValue represents a List, a Dictionary or an Item.
|
||||
type StructuredFieldValue interface {
|
||||
marshaler
|
||||
}
|
||||
|
||||
// Marshal returns the HTTP Structured Value serialization of v
|
||||
// as defined in https://httpwg.org/specs/rfc9651.html#text-serialize.
|
||||
//
|
||||
// v must be a List, a Dictionary, an Item or an InnerList.
|
||||
func Marshal(v StructuredFieldValue) (string, error) {
|
||||
var b strings.Builder
|
||||
if err := v.marshalSFV(&b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return b.String(), nil
|
||||
}
|
||||
91
vendor/github.com/dunglas/httpsfv/innerlist.go
generated
vendored
Normal file
91
vendor/github.com/dunglas/httpsfv/innerlist.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrInvalidInnerListFormat is returned when an inner list format is invalid.
|
||||
var ErrInvalidInnerListFormat = errors.New("invalid inner list format")
|
||||
|
||||
// InnerList represents an inner list as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#inner-list.
|
||||
type InnerList struct {
|
||||
Items []Item
|
||||
Params *Params
|
||||
}
|
||||
|
||||
func (il InnerList) member() {
|
||||
}
|
||||
|
||||
// marshalSFV serializes as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#ser-innerlist.
|
||||
func (il InnerList) marshalSFV(b *strings.Builder) error {
|
||||
if err := b.WriteByte('('); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l := len(il.Items)
|
||||
for i := 0; i < l; i++ {
|
||||
if err := il.Items[i].marshalSFV(b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if i != l-1 {
|
||||
if err := b.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.WriteByte(')'); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return il.Params.marshalSFV(b)
|
||||
}
|
||||
|
||||
// parseInnerList parses as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#parse-item-or-list.
|
||||
func parseInnerList(s *scanner) (InnerList, error) {
|
||||
if s.eof() || s.data[s.off] != '(' {
|
||||
return InnerList{}, &UnmarshalError{s.off, ErrInvalidInnerListFormat}
|
||||
}
|
||||
s.off++
|
||||
|
||||
il := InnerList{nil, nil}
|
||||
|
||||
for !s.eof() {
|
||||
s.scanWhileSp()
|
||||
|
||||
if s.eof() {
|
||||
return InnerList{}, &UnmarshalError{s.off, ErrInvalidInnerListFormat}
|
||||
}
|
||||
|
||||
if s.data[s.off] == ')' {
|
||||
s.off++
|
||||
|
||||
p, err := parseParams(s)
|
||||
if err != nil {
|
||||
return InnerList{}, err
|
||||
}
|
||||
|
||||
il.Params = p
|
||||
|
||||
return il, nil
|
||||
}
|
||||
|
||||
i, err := parseItem(s)
|
||||
if err != nil {
|
||||
return InnerList{}, err
|
||||
}
|
||||
|
||||
if s.eof() || (s.data[s.off] != ')' && s.data[s.off] != ' ') {
|
||||
return InnerList{}, &UnmarshalError{s.off, ErrInvalidInnerListFormat}
|
||||
}
|
||||
|
||||
il.Items = append(il.Items, i)
|
||||
}
|
||||
|
||||
return InnerList{}, &UnmarshalError{s.off, ErrInvalidInnerListFormat}
|
||||
}
|
||||
120
vendor/github.com/dunglas/httpsfv/integer.go
generated
vendored
Normal file
120
vendor/github.com/dunglas/httpsfv/integer.go
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const maxDigit = 12
|
||||
|
||||
// ErrNotDigit is returned when a character should be a digit but isn't.
|
||||
var ErrNotDigit = errors.New("character is not a digit")
|
||||
|
||||
// ErrNumberOutOfRange is returned when the number is too large according to the specification.
|
||||
var ErrNumberOutOfRange = errors.New("integer or decimal out of range")
|
||||
|
||||
// ErrInvalidDecimalFormat is returned when the decimal format is invalid.
|
||||
var ErrInvalidDecimalFormat = errors.New("invalid decimal format")
|
||||
|
||||
const (
|
||||
typeInteger = iota
|
||||
typeDecimal
|
||||
)
|
||||
|
||||
// marshalInteger serializes as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#integer.
|
||||
func marshalInteger(b io.StringWriter, i int64) error {
|
||||
if i < -999999999999999 || i > 999999999999999 {
|
||||
return ErrNumberOutOfRange
|
||||
}
|
||||
|
||||
_, err := b.WriteString(strconv.FormatInt(i, 10))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// parseNumber parses as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#parse-number.
|
||||
func parseNumber(s *scanner) (interface{}, error) {
|
||||
neg := isNeg(s)
|
||||
if neg && s.eof() {
|
||||
return 0, &UnmarshalError{s.off, ErrUnexpectedEndOfString}
|
||||
}
|
||||
|
||||
if !isDigit(s.data[s.off]) {
|
||||
return 0, &UnmarshalError{s.off, ErrNotDigit}
|
||||
}
|
||||
|
||||
start := s.off
|
||||
s.off++
|
||||
|
||||
var (
|
||||
decSepOff int
|
||||
t = typeInteger
|
||||
)
|
||||
|
||||
for s.off < len(s.data) {
|
||||
size := s.off - start
|
||||
if (t == typeInteger && (size >= 15)) || size >= 16 {
|
||||
return 0, &UnmarshalError{s.off, ErrNumberOutOfRange}
|
||||
}
|
||||
|
||||
c := s.data[s.off]
|
||||
if isDigit(c) {
|
||||
s.off++
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if t == typeInteger && c == '.' {
|
||||
if size > maxDigit {
|
||||
return 0, &UnmarshalError{s.off, ErrNumberOutOfRange}
|
||||
}
|
||||
|
||||
t = typeDecimal
|
||||
decSepOff = s.off
|
||||
s.off++
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
str := s.data[start:s.off]
|
||||
|
||||
if t == typeInteger {
|
||||
return parseInteger(str, neg, s.off)
|
||||
}
|
||||
|
||||
return parseDecimal(s, decSepOff, str, neg)
|
||||
}
|
||||
|
||||
func isNeg(s *scanner) bool {
|
||||
if s.data[s.off] == '-' {
|
||||
s.off++
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func parseInteger(str string, neg bool, off int) (int64, error) {
|
||||
i, err := strconv.ParseInt(str, 10, 64)
|
||||
if err != nil {
|
||||
// Should never happen
|
||||
return 0, &UnmarshalError{off, err}
|
||||
}
|
||||
|
||||
if neg {
|
||||
i = -i
|
||||
}
|
||||
|
||||
if i < -999999999999999 || i > 999999999999999 {
|
||||
return 0, &UnmarshalError{off, ErrNumberOutOfRange}
|
||||
}
|
||||
|
||||
return i, err
|
||||
}
|
||||
73
vendor/github.com/dunglas/httpsfv/item.go
generated
vendored
Normal file
73
vendor/github.com/dunglas/httpsfv/item.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Item is a bare value and associated parameters.
|
||||
// See https://httpwg.org/specs/rfc9651.html#item.
|
||||
type Item struct {
|
||||
Value interface{}
|
||||
Params *Params
|
||||
}
|
||||
|
||||
// NewItem returns a new Item.
|
||||
func NewItem(v interface{}) Item {
|
||||
assertBareItem(v)
|
||||
|
||||
return Item{v, NewParams()}
|
||||
}
|
||||
|
||||
func (i Item) member() {
|
||||
}
|
||||
|
||||
// marshalSFV serializes as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#ser-item.
|
||||
func (i Item) marshalSFV(b *strings.Builder) error {
|
||||
if i.Value == nil {
|
||||
return ErrInvalidBareItem
|
||||
}
|
||||
|
||||
if err := marshalBareItem(b, i.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return i.Params.marshalSFV(b)
|
||||
}
|
||||
|
||||
// UnmarshalItem parses an item as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#parse-item.
|
||||
func UnmarshalItem(v []string) (Item, error) {
|
||||
s := &scanner{
|
||||
data: strings.Join(v, ","),
|
||||
}
|
||||
|
||||
s.scanWhileSp()
|
||||
|
||||
sfv, err := parseItem(s)
|
||||
if err != nil {
|
||||
return Item{}, err
|
||||
}
|
||||
|
||||
s.scanWhileSp()
|
||||
|
||||
if !s.eof() {
|
||||
return Item{}, &UnmarshalError{off: s.off}
|
||||
}
|
||||
|
||||
return sfv, nil
|
||||
}
|
||||
|
||||
func parseItem(s *scanner) (Item, error) {
|
||||
bi, err := parseBareItem(s)
|
||||
if err != nil {
|
||||
return Item{}, err
|
||||
}
|
||||
|
||||
p, err := parseParams(s)
|
||||
if err != nil {
|
||||
return Item{}, err
|
||||
}
|
||||
|
||||
return Item{bi, p}, nil
|
||||
}
|
||||
81
vendor/github.com/dunglas/httpsfv/key.go
generated
vendored
Normal file
81
vendor/github.com/dunglas/httpsfv/key.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// ErrInvalidKeyFormat is returned when the format of a parameter or dictionary key is invalid.
|
||||
var ErrInvalidKeyFormat = errors.New("invalid key format")
|
||||
|
||||
// isKeyChar checks if c is a valid key characters.
|
||||
func isKeyChar(c byte) bool {
|
||||
if isLowerCaseAlpha(c) || isDigit(c) {
|
||||
return true
|
||||
}
|
||||
|
||||
switch c {
|
||||
case '_', '-', '.', '*':
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// checkKey checks if the given value is a valid parameter key according to
|
||||
// https://httpwg.org/specs/rfc9651.html#param.
|
||||
func checkKey(k string) error {
|
||||
if len(k) == 0 {
|
||||
return fmt.Errorf("a key cannot be empty: %w", ErrInvalidKeyFormat)
|
||||
}
|
||||
|
||||
if !isLowerCaseAlpha(k[0]) && k[0] != '*' {
|
||||
return fmt.Errorf("a key must start with a lower case alpha character or *: %w", ErrInvalidKeyFormat)
|
||||
}
|
||||
|
||||
for i := 1; i < len(k); i++ {
|
||||
if !isKeyChar(k[i]) {
|
||||
return fmt.Errorf("the character %c isn't allowed in a key: %w", k[i], ErrInvalidKeyFormat)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// marshalKey serializes as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#ser-key.
|
||||
func marshalKey(b io.StringWriter, k string) error {
|
||||
if err := checkKey(k); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := b.WriteString(k)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// parseKey parses as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#parse-key.
|
||||
func parseKey(s *scanner) (string, error) {
|
||||
if s.eof() {
|
||||
return "", &UnmarshalError{s.off, ErrInvalidKeyFormat}
|
||||
}
|
||||
|
||||
c := s.data[s.off]
|
||||
if !isLowerCaseAlpha(c) && c != '*' {
|
||||
return "", &UnmarshalError{s.off, ErrInvalidKeyFormat}
|
||||
}
|
||||
|
||||
start := s.off
|
||||
s.off++
|
||||
|
||||
for !s.eof() {
|
||||
if !isKeyChar(s.data[s.off]) {
|
||||
break
|
||||
}
|
||||
s.off++
|
||||
}
|
||||
|
||||
return s.data[start:s.off], nil
|
||||
}
|
||||
99
vendor/github.com/dunglas/httpsfv/list.go
generated
vendored
Normal file
99
vendor/github.com/dunglas/httpsfv/list.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrInvalidListFormat is returned when the format of a list is invalid.
|
||||
var ErrInvalidListFormat = errors.New("invalid list format")
|
||||
|
||||
// List contains items an inner lists.
|
||||
//
|
||||
// See https://httpwg.org/specs/rfc9651.html#list
|
||||
type List []Member
|
||||
|
||||
// marshalSFV serializes as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#ser-list.
|
||||
func (l List) marshalSFV(b *strings.Builder) error {
|
||||
s := len(l)
|
||||
for i := 0; i < s; i++ {
|
||||
if err := l[i].marshalSFV(b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if i != s-1 {
|
||||
if _, err := b.WriteString(", "); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalList parses a list as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#parse-list.
|
||||
func UnmarshalList(v []string) (List, error) {
|
||||
s := &scanner{
|
||||
data: strings.Join(v, ","),
|
||||
}
|
||||
|
||||
s.scanWhileSp()
|
||||
|
||||
sfv, err := parseList(s)
|
||||
if err != nil {
|
||||
return List{}, err
|
||||
}
|
||||
|
||||
return sfv, nil
|
||||
}
|
||||
|
||||
// parseList parses as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#parse-list.
|
||||
func parseList(s *scanner) (List, error) {
|
||||
var l List
|
||||
|
||||
for !s.eof() {
|
||||
m, err := parseItemOrInnerList(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l = append(l, m)
|
||||
|
||||
s.scanWhileOWS()
|
||||
|
||||
if s.eof() {
|
||||
return l, nil
|
||||
}
|
||||
|
||||
if s.data[s.off] != ',' {
|
||||
return nil, &UnmarshalError{s.off, ErrInvalidListFormat}
|
||||
}
|
||||
s.off++
|
||||
|
||||
s.scanWhileOWS()
|
||||
|
||||
if s.eof() {
|
||||
// there is a trailing comma
|
||||
return nil, &UnmarshalError{s.off, ErrInvalidListFormat}
|
||||
}
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// parseItemOrInnerList parses as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#parse-item-or-list.
|
||||
func parseItemOrInnerList(s *scanner) (Member, error) {
|
||||
if s.eof() {
|
||||
return nil, &UnmarshalError{s.off, ErrInvalidInnerListFormat}
|
||||
}
|
||||
|
||||
if s.data[s.off] == '(' {
|
||||
return parseInnerList(s)
|
||||
}
|
||||
|
||||
return parseItem(s)
|
||||
}
|
||||
9
vendor/github.com/dunglas/httpsfv/member.go
generated
vendored
Normal file
9
vendor/github.com/dunglas/httpsfv/member.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package httpsfv
|
||||
|
||||
// Member is a marker interface for members of dictionaries and lists.
|
||||
//
|
||||
// See https://httpwg.org/specs/rfc9651.html#list.
|
||||
type Member interface {
|
||||
member()
|
||||
marshaler
|
||||
}
|
||||
143
vendor/github.com/dunglas/httpsfv/params.go
generated
vendored
Normal file
143
vendor/github.com/dunglas/httpsfv/params.go
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Params are an ordered map of key-value pairs that are associated with an item or an inner list.
|
||||
//
|
||||
// See https://httpwg.org/specs/rfc9651.html#param.
|
||||
type Params struct {
|
||||
names []string
|
||||
values map[string]interface{}
|
||||
}
|
||||
|
||||
// ErrInvalidParameterFormat is returned when the format of a parameter is invalid.
|
||||
var ErrInvalidParameterFormat = errors.New("invalid parameter format")
|
||||
|
||||
// ErrInvalidParameterValue is returned when a parameter key is invalid.
|
||||
var ErrInvalidParameterValue = errors.New("invalid parameter value")
|
||||
|
||||
// ErrMissingParameters is returned when the Params structure is missing from the element.
|
||||
var ErrMissingParameters = errors.New("missing parameters")
|
||||
|
||||
// NewParams creates a new ordered map.
|
||||
func NewParams() *Params {
|
||||
p := Params{}
|
||||
p.names = []string{}
|
||||
p.values = map[string]interface{}{}
|
||||
|
||||
return &p
|
||||
}
|
||||
|
||||
// Get retrieves a parameter.
|
||||
func (p *Params) Get(k string) (interface{}, bool) {
|
||||
v, ok := p.values[k]
|
||||
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Add appends a new parameter to the ordered list.
|
||||
// If the key already exists, overwrite its value.
|
||||
func (p *Params) Add(k string, v interface{}) {
|
||||
assertBareItem(v)
|
||||
|
||||
if _, exists := p.values[k]; !exists {
|
||||
p.names = append(p.names, k)
|
||||
}
|
||||
|
||||
p.values[k] = v
|
||||
}
|
||||
|
||||
// Del removes a parameter from the ordered list.
|
||||
func (p *Params) Del(key string) bool {
|
||||
if _, ok := p.values[key]; !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, k := range p.names {
|
||||
if k == key {
|
||||
p.names = append(p.names[:i], p.names[i+1:]...)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
delete(p.values, key)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Names retrieves the list of parameter names in the appropriate order.
|
||||
func (p *Params) Names() []string {
|
||||
return p.names
|
||||
}
|
||||
|
||||
// marshalSFV serializes as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#ser-params.
|
||||
func (p *Params) marshalSFV(b *strings.Builder) error {
|
||||
if p == nil {
|
||||
return ErrMissingParameters
|
||||
}
|
||||
for _, k := range p.names {
|
||||
if err := b.WriteByte(';'); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := marshalKey(b, k); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v := p.values[k]
|
||||
if v == true {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := b.WriteByte('='); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := marshalBareItem(b, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseParams parses as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#parse-param.
|
||||
func parseParams(s *scanner) (*Params, error) {
|
||||
p := NewParams()
|
||||
|
||||
for !s.eof() {
|
||||
if s.data[s.off] != ';' {
|
||||
break
|
||||
}
|
||||
s.off++
|
||||
s.scanWhileSp()
|
||||
|
||||
k, err := parseKey(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var i interface{}
|
||||
|
||||
if !s.eof() && s.data[s.off] == '=' {
|
||||
s.off++
|
||||
|
||||
i, err = parseBareItem(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
i = true
|
||||
}
|
||||
|
||||
p.Add(k, i)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
88
vendor/github.com/dunglas/httpsfv/string.go
generated
vendored
Normal file
88
vendor/github.com/dunglas/httpsfv/string.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// ErrInvalidStringFormat is returned when a string format is invalid.
|
||||
var ErrInvalidStringFormat = errors.New("invalid string format")
|
||||
|
||||
// marshalString serializes as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#ser-string.
|
||||
func marshalString(b *strings.Builder, s string) error {
|
||||
if err := b.WriteByte('"'); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] <= '\u001F' || s[i] >= unicode.MaxASCII {
|
||||
return ErrInvalidStringFormat
|
||||
}
|
||||
|
||||
switch s[i] {
|
||||
case '"', '\\':
|
||||
if err := b.WriteByte('\\'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.WriteByte(s[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.WriteByte('"'); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseString parses as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#parse-string.
|
||||
func parseString(s *scanner) (string, error) {
|
||||
if s.eof() || s.data[s.off] != '"' {
|
||||
return "", &UnmarshalError{s.off, ErrInvalidStringFormat}
|
||||
}
|
||||
s.off++
|
||||
|
||||
var b strings.Builder
|
||||
|
||||
for !s.eof() {
|
||||
c := s.data[s.off]
|
||||
s.off++
|
||||
|
||||
switch c {
|
||||
case '\\':
|
||||
if s.eof() {
|
||||
return "", &UnmarshalError{s.off, ErrInvalidStringFormat}
|
||||
}
|
||||
|
||||
n := s.data[s.off]
|
||||
if n != '"' && n != '\\' {
|
||||
return "", &UnmarshalError{s.off, ErrInvalidStringFormat}
|
||||
}
|
||||
s.off++
|
||||
|
||||
if err := b.WriteByte(n); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
continue
|
||||
case '"':
|
||||
return b.String(), nil
|
||||
default:
|
||||
if c <= '\u001F' || c >= unicode.MaxASCII {
|
||||
return "", &UnmarshalError{s.off, ErrInvalidStringFormat}
|
||||
}
|
||||
|
||||
if err := b.WriteByte(c); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", &UnmarshalError{s.off, ErrInvalidStringFormat}
|
||||
}
|
||||
71
vendor/github.com/dunglas/httpsfv/token.go
generated
vendored
Normal file
71
vendor/github.com/dunglas/httpsfv/token.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
package httpsfv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// isExtendedTchar checks if c is a valid token character as defined in the spec.
|
||||
func isExtendedTchar(c byte) bool {
|
||||
if isAlpha(c) || isDigit(c) {
|
||||
return true
|
||||
}
|
||||
|
||||
switch c {
|
||||
case '!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~', ':', '/':
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ErrInvalidTokenFormat is returned when a token format is invalid.
|
||||
var ErrInvalidTokenFormat = errors.New("invalid token format")
|
||||
|
||||
// Token represents a token as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#token.
|
||||
// A specific type is used to distinguish tokens from strings.
|
||||
type Token string
|
||||
|
||||
// marshalSFV serializes as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#ser-token.
|
||||
func (t Token) marshalSFV(b io.StringWriter) error {
|
||||
if len(t) == 0 {
|
||||
return fmt.Errorf("a token cannot be empty: %w", ErrInvalidTokenFormat)
|
||||
}
|
||||
|
||||
if !isAlpha(t[0]) && t[0] != '*' {
|
||||
return fmt.Errorf("a token must start with an alpha character or *: %w", ErrInvalidTokenFormat)
|
||||
}
|
||||
|
||||
for i := 1; i < len(t); i++ {
|
||||
if !isExtendedTchar(t[i]) {
|
||||
return fmt.Errorf("the character %c isn't allowed in a token: %w", t[i], ErrInvalidTokenFormat)
|
||||
}
|
||||
}
|
||||
|
||||
_, err := b.WriteString(string(t))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// parseToken parses as defined in
|
||||
// https://httpwg.org/specs/rfc9651.html#parse-token.
|
||||
func parseToken(s *scanner) (Token, error) {
|
||||
if s.eof() || (!isAlpha(s.data[s.off]) && s.data[s.off] != '*') {
|
||||
return "", &UnmarshalError{s.off, ErrInvalidTokenFormat}
|
||||
}
|
||||
|
||||
start := s.off
|
||||
s.off++
|
||||
|
||||
for !s.eof() {
|
||||
if !isExtendedTchar(s.data[s.off]) {
|
||||
break
|
||||
}
|
||||
s.off++
|
||||
}
|
||||
|
||||
return Token(s.data[start:s.off]), nil
|
||||
}
|
||||
16
vendor/github.com/dunglas/httpsfv/utils.go
generated
vendored
Normal file
16
vendor/github.com/dunglas/httpsfv/utils.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package httpsfv
|
||||
|
||||
// isLowerCaseAlpha checks if c is a lower cased alpha character.
|
||||
func isLowerCaseAlpha(c byte) bool {
|
||||
return 'a' <= c && c <= 'z'
|
||||
}
|
||||
|
||||
// isAlpha checks if c is an alpha character.
|
||||
func isAlpha(c byte) bool {
|
||||
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')
|
||||
}
|
||||
|
||||
// isDigit checks if c is a digit.
|
||||
func isDigit(c byte) bool {
|
||||
return '0' <= c && c <= '9'
|
||||
}
|
||||
Reference in New Issue
Block a user