332 lines
7.7 KiB
Go
332 lines
7.7 KiB
Go
|
// Package httphead contains utils for parsing HTTP and HTTP-grammar compatible
|
||
|
// text protocols headers.
|
||
|
//
|
||
|
// That is, this package first aim is to bring ability to easily parse
|
||
|
// constructions, described here https://tools.ietf.org/html/rfc2616#section-2
|
||
|
package httphead
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// ScanTokens parses data in this form:
|
||
|
//
|
||
|
// list = 1#token
|
||
|
//
|
||
|
// It returns false if data is malformed.
|
||
|
func ScanTokens(data []byte, it func([]byte) bool) bool {
|
||
|
lexer := &Scanner{data: data}
|
||
|
|
||
|
var ok bool
|
||
|
for lexer.Next() {
|
||
|
switch lexer.Type() {
|
||
|
case ItemToken:
|
||
|
ok = true
|
||
|
if !it(lexer.Bytes()) {
|
||
|
return true
|
||
|
}
|
||
|
case ItemSeparator:
|
||
|
if !isComma(lexer.Bytes()) {
|
||
|
return false
|
||
|
}
|
||
|
default:
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ok && !lexer.err
|
||
|
}
|
||
|
|
||
|
// ParseOptions parses all header options and appends it to given slice of
|
||
|
// Option. It returns flag of successful (wellformed input) parsing.
|
||
|
//
|
||
|
// Note that appended options are all consist of subslices of data. That is,
|
||
|
// mutation of data will mutate appended options.
|
||
|
func ParseOptions(data []byte, options []Option) ([]Option, bool) {
|
||
|
var i int
|
||
|
index := -1
|
||
|
return options, ScanOptions(data, func(idx int, name, attr, val []byte) Control {
|
||
|
if idx != index {
|
||
|
index = idx
|
||
|
i = len(options)
|
||
|
options = append(options, Option{Name: name})
|
||
|
}
|
||
|
if attr != nil {
|
||
|
options[i].Parameters.Set(attr, val)
|
||
|
}
|
||
|
return ControlContinue
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// SelectFlag encodes way of options selection.
|
||
|
type SelectFlag byte
|
||
|
|
||
|
// String represetns flag as string.
|
||
|
func (f SelectFlag) String() string {
|
||
|
var flags [2]string
|
||
|
var n int
|
||
|
if f&SelectCopy != 0 {
|
||
|
flags[n] = "copy"
|
||
|
n++
|
||
|
}
|
||
|
if f&SelectUnique != 0 {
|
||
|
flags[n] = "unique"
|
||
|
n++
|
||
|
}
|
||
|
return "[" + strings.Join(flags[:n], "|") + "]"
|
||
|
}
|
||
|
|
||
|
const (
|
||
|
// SelectCopy causes selector to copy selected option before appending it
|
||
|
// to resulting slice.
|
||
|
// If SelectCopy flag is not passed to selector, then appended options will
|
||
|
// contain sub-slices of the initial data.
|
||
|
SelectCopy SelectFlag = 1 << iota
|
||
|
|
||
|
// SelectUnique causes selector to append only not yet existing option to
|
||
|
// resulting slice. Unique is checked by comparing option names.
|
||
|
SelectUnique
|
||
|
)
|
||
|
|
||
|
// OptionSelector contains configuration for selecting Options from header value.
|
||
|
type OptionSelector struct {
|
||
|
// Check is a filter function that applied to every Option that possibly
|
||
|
// could be selected.
|
||
|
// If Check is nil all options will be selected.
|
||
|
Check func(Option) bool
|
||
|
|
||
|
// Flags contains flags for options selection.
|
||
|
Flags SelectFlag
|
||
|
|
||
|
// Alloc used to allocate slice of bytes when selector is configured with
|
||
|
// SelectCopy flag. It will be called with number of bytes needed for copy
|
||
|
// of single Option.
|
||
|
// If Alloc is nil make is used.
|
||
|
Alloc func(n int) []byte
|
||
|
}
|
||
|
|
||
|
// Select parses header data and appends it to given slice of Option.
|
||
|
// It also returns flag of successful (wellformed input) parsing.
|
||
|
func (s OptionSelector) Select(data []byte, options []Option) ([]Option, bool) {
|
||
|
var current Option
|
||
|
var has bool
|
||
|
index := -1
|
||
|
|
||
|
alloc := s.Alloc
|
||
|
if alloc == nil {
|
||
|
alloc = defaultAlloc
|
||
|
}
|
||
|
check := s.Check
|
||
|
if check == nil {
|
||
|
check = defaultCheck
|
||
|
}
|
||
|
|
||
|
ok := ScanOptions(data, func(idx int, name, attr, val []byte) Control {
|
||
|
if idx != index {
|
||
|
if has && check(current) {
|
||
|
if s.Flags&SelectCopy != 0 {
|
||
|
current = current.Copy(alloc(current.Size()))
|
||
|
}
|
||
|
options = append(options, current)
|
||
|
has = false
|
||
|
}
|
||
|
if s.Flags&SelectUnique != 0 {
|
||
|
for i := len(options) - 1; i >= 0; i-- {
|
||
|
if bytes.Equal(options[i].Name, name) {
|
||
|
return ControlSkip
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
index = idx
|
||
|
current = Option{Name: name}
|
||
|
has = true
|
||
|
}
|
||
|
if attr != nil {
|
||
|
current.Parameters.Set(attr, val)
|
||
|
}
|
||
|
|
||
|
return ControlContinue
|
||
|
})
|
||
|
if has && check(current) {
|
||
|
if s.Flags&SelectCopy != 0 {
|
||
|
current = current.Copy(alloc(current.Size()))
|
||
|
}
|
||
|
options = append(options, current)
|
||
|
}
|
||
|
|
||
|
return options, ok
|
||
|
}
|
||
|
|
||
|
func defaultAlloc(n int) []byte { return make([]byte, n) }
|
||
|
func defaultCheck(Option) bool { return true }
|
||
|
|
||
|
// Control represents operation that scanner should perform.
|
||
|
type Control byte
|
||
|
|
||
|
const (
|
||
|
// ControlContinue causes scanner to continue scan tokens.
|
||
|
ControlContinue Control = iota
|
||
|
// ControlBreak causes scanner to stop scan tokens.
|
||
|
ControlBreak
|
||
|
// ControlSkip causes scanner to skip current entity.
|
||
|
ControlSkip
|
||
|
)
|
||
|
|
||
|
// ScanOptions parses data in this form:
|
||
|
//
|
||
|
// values = 1#value
|
||
|
// value = token *( ";" param )
|
||
|
// param = token [ "=" (token | quoted-string) ]
|
||
|
//
|
||
|
// It calls given callback with the index of the option, option itself and its
|
||
|
// parameter (attribute and its value, both could be nil). Index is useful when
|
||
|
// header contains multiple choises for the same named option.
|
||
|
//
|
||
|
// Given callback should return one of the defined Control* values.
|
||
|
// ControlSkip means that passed key is not in caller's interest. That is, all
|
||
|
// parameters of that key will be skipped.
|
||
|
// ControlBreak means that no more keys and parameters should be parsed. That
|
||
|
// is, it must break parsing immediately.
|
||
|
// ControlContinue means that caller want to receive next parameter and its
|
||
|
// value or the next key.
|
||
|
//
|
||
|
// It returns false if data is malformed.
|
||
|
func ScanOptions(data []byte, it func(index int, option, attribute, value []byte) Control) bool {
|
||
|
lexer := &Scanner{data: data}
|
||
|
|
||
|
var ok bool
|
||
|
var state int
|
||
|
const (
|
||
|
stateKey = iota
|
||
|
stateParamBeforeName
|
||
|
stateParamName
|
||
|
stateParamBeforeValue
|
||
|
stateParamValue
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
index int
|
||
|
key, param, value []byte
|
||
|
mustCall bool
|
||
|
)
|
||
|
for lexer.Next() {
|
||
|
var (
|
||
|
call bool
|
||
|
growIndex int
|
||
|
)
|
||
|
|
||
|
t := lexer.Type()
|
||
|
v := lexer.Bytes()
|
||
|
|
||
|
switch t {
|
||
|
case ItemToken:
|
||
|
switch state {
|
||
|
case stateKey, stateParamBeforeName:
|
||
|
key = v
|
||
|
state = stateParamBeforeName
|
||
|
mustCall = true
|
||
|
case stateParamName:
|
||
|
param = v
|
||
|
state = stateParamBeforeValue
|
||
|
mustCall = true
|
||
|
case stateParamValue:
|
||
|
value = v
|
||
|
state = stateParamBeforeName
|
||
|
call = true
|
||
|
default:
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
case ItemString:
|
||
|
if state != stateParamValue {
|
||
|
return false
|
||
|
}
|
||
|
value = v
|
||
|
state = stateParamBeforeName
|
||
|
call = true
|
||
|
|
||
|
case ItemSeparator:
|
||
|
switch {
|
||
|
case isComma(v) && state == stateKey:
|
||
|
// Nothing to do.
|
||
|
|
||
|
case isComma(v) && state == stateParamBeforeName:
|
||
|
state = stateKey
|
||
|
// Make call only if we have not called this key yet.
|
||
|
call = mustCall
|
||
|
if !call {
|
||
|
// If we have already called callback with the key
|
||
|
// that just ended.
|
||
|
index++
|
||
|
} else {
|
||
|
// Else grow the index after calling callback.
|
||
|
growIndex = 1
|
||
|
}
|
||
|
|
||
|
case isComma(v) && state == stateParamBeforeValue:
|
||
|
state = stateKey
|
||
|
growIndex = 1
|
||
|
call = true
|
||
|
|
||
|
case isSemicolon(v) && state == stateParamBeforeName:
|
||
|
state = stateParamName
|
||
|
|
||
|
case isSemicolon(v) && state == stateParamBeforeValue:
|
||
|
state = stateParamName
|
||
|
call = true
|
||
|
|
||
|
case isEquality(v) && state == stateParamBeforeValue:
|
||
|
state = stateParamValue
|
||
|
|
||
|
default:
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
if call {
|
||
|
switch it(index, key, param, value) {
|
||
|
case ControlBreak:
|
||
|
// User want to stop to parsing parameters.
|
||
|
return true
|
||
|
|
||
|
case ControlSkip:
|
||
|
// User want to skip current param.
|
||
|
state = stateKey
|
||
|
lexer.SkipEscaped(',')
|
||
|
|
||
|
case ControlContinue:
|
||
|
// User is interested in rest of parameters.
|
||
|
// Nothing to do.
|
||
|
|
||
|
default:
|
||
|
panic("unexpected control value")
|
||
|
}
|
||
|
ok = true
|
||
|
param = nil
|
||
|
value = nil
|
||
|
mustCall = false
|
||
|
index += growIndex
|
||
|
}
|
||
|
}
|
||
|
if mustCall {
|
||
|
ok = true
|
||
|
it(index, key, param, value)
|
||
|
}
|
||
|
|
||
|
return ok && !lexer.err
|
||
|
}
|
||
|
|
||
|
func isComma(b []byte) bool {
|
||
|
return len(b) == 1 && b[0] == ','
|
||
|
}
|
||
|
func isSemicolon(b []byte) bool {
|
||
|
return len(b) == 1 && b[0] == ';'
|
||
|
}
|
||
|
func isEquality(b []byte) bool {
|
||
|
return len(b) == 1 && b[0] == '='
|
||
|
}
|