123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- // 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] == '='
- }
|