// Copyright 2025 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.
// Package httpsfv provides functionality for dealing with HTTP Structured// Field Values.
package httpsfvimport ()func isLCAlpha( byte) bool {return ( >= 'a' && <= 'z')}func isAlpha( byte) bool {returnisLCAlpha() || ( >= 'A' && <= 'Z')}func isDigit( byte) bool {return >= '0' && <= '9'}func isVChar( byte) bool {return >= 0x21 && <= 0x7e}func isSP( byte) bool {return == 0x20}func isTChar( byte) bool {ifisAlpha() || isDigit() {returntrue }returnslices.Contains([]byte{'!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~'}, )}func countLeftWhitespace( string) int { := 0for , := range []byte() {if != ' ' && != '\t' {break } ++ }return}// https://www.rfc-editor.org/rfc/rfc4648#section-8.func decOctetHex(, byte) ( byte, bool) { := func( byte) ( byte, bool) {if !isDigit() && !( >= 'a' && <= 'f') {return0, false }ifisDigit() {return - '0', true }return - 'a' + 10, true }if , = (); ! {return0, }if , = (); ! {return0, }return <<4 | , true}// ParseList parses a list from a given HTTP Structured Field Values.//// Given an HTTP SFV string that represents a list, it will call the given// function using each of the members and parameters contained in the list.// This allows the caller to extract information out of the list.//// This function will return once it encounters the end of the string, or// something that is not a list. If it cannot consume the entire given// string, the ok value returned will be false.//// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-list.func ( string, func(, string)) ( bool) {forlen() != 0 {var , stringiflen() != 0 && [0] == '(' {if , , = consumeBareInnerList(, nil); ! {return } } else {if , , = consumeBareItem(); ! {return } }if , , = consumeParameter(, nil); ! {return }if != nil { (, ) } = [countLeftWhitespace():]iflen() == 0 {break }if [0] != ',' {returnfalse } = [1:] = [countLeftWhitespace():]iflen() == 0 {returnfalse } }returntrue}// consumeBareInnerList consumes an inner list// (https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-inner-list),// except for the inner list's top-most parameter.// For example, given `(a;b c;d);e`, it will consume only `(a;b c;d)`.func consumeBareInnerList( string, func(, string)) (, string, bool) {iflen() == 0 || [0] != '(' {return"", , false } = [1:]forlen() != 0 {var , string = [countLeftWhitespace():]iflen() != 0 && [0] == ')' { = [1:]break }if , , = consumeBareItem(); ! {return"", , }if , , = consumeParameter(, nil); ! {return"", , }iflen() == 0 || ([0] != ')' && !isSP([0])) {return"", , false }if != nil { (, ) } }return [:len()-len()], , true}// ParseBareInnerList parses a bare inner list from a given HTTP Structured// Field Values.//// We define a bare inner list as an inner list// (https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-inner-list),// without the top-most parameter of the inner list. For example, given the// inner list `(a;b c;d);e`, the bare inner list would be `(a;b c;d)`.//// Given an HTTP SFV string that represents a bare inner list, it will call the// given function using each of the bare item and parameter within the bare// inner list. This allows the caller to extract information out of the bare// inner list.//// This function will return once it encounters the end of the bare inner list,// or something that is not a bare inner list. If it cannot consume the entire// given string, the ok value returned will be false.func ( string, func(, string)) ( bool) { , , := consumeBareInnerList(, )return == "" && }// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-item.func consumeItem( string, func(, string)) (, string, bool) {var , stringif , , = consumeBareItem(); ! {return"", , }if , , = consumeParameter(, nil); ! {return"", , }if != nil { (, ) }return [:len()-len()], , true}// ParseItem parses an item from a given HTTP Structured Field Values.//// Given an HTTP SFV string that represents an item, it will call the given// function once, with the bare item and the parameter of the item. This allows// the caller to extract information out of the item.//// This function will return once it encounters the end of the string, or// something that is not an item. If it cannot consume the entire given// string, the ok value returned will be false.//// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-item.func ( string, func(, string)) ( bool) { , , := consumeItem(, )return == "" && }// ParseDictionary parses a dictionary from a given HTTP Structured Field// Values.//// Given an HTTP SFV string that represents a dictionary, it will call the// given function using each of the keys, values, and parameters contained in// the dictionary. This allows the caller to extract information out of the// dictionary.//// This function will return once it encounters the end of the string, or// something that is not a dictionary. If it cannot consume the entire given// string, the ok value returned will be false.//// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-dictionary.func ( string, func(, , string)) ( bool) {forlen() != 0 {var , , string = "?1"// Default value for empty val is boolean true.if , , = consumeKey(); ! {return }iflen() != 0 && [0] == '=' { = [1:]iflen() != 0 && [0] == '(' {if , , = consumeBareInnerList(, nil); ! {return } } else {if , , = consumeBareItem(); ! {return } } }if , , = consumeParameter(, nil); ! {return }if != nil { (, , ) } = [countLeftWhitespace():]iflen() == 0 {break }if [0] == ',' { = [1:] } = [countLeftWhitespace():]iflen() == 0 {returnfalse } }returntrue}// https://www.rfc-editor.org/rfc/rfc9651.html#parse-param.func consumeParameter( string, func(, string)) (, string, bool) { = forlen() != 0 {var , string = "?1"// Default value for empty val is boolean true.if [0] != ';' {break } = [1:] = [countLeftWhitespace():] , , = consumeKey()if ! {return"", , }iflen() != 0 && [0] == '=' { = [1:] , , = consumeBareItem()if ! {return"", , } }if != nil { (, ) } }return [:len()-len()], , true}// ParseParameter parses a parameter from a given HTTP Structured Field Values.//// Given an HTTP SFV string that represents a parameter, it will call the given// function using each of the keys and values contained in the parameter. This// allows the caller to extract information out of the parameter.//// This function will return once it encounters the end of the string, or// something that is not a parameter. If it cannot consume the entire given// string, the ok value returned will be false.//// https://www.rfc-editor.org/rfc/rfc9651.html#parse-param.func ( string, func(, string)) ( bool) { , , := consumeParameter(, )return == "" && }// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-key.func consumeKey( string) (, string, bool) {iflen() == 0 || (!isLCAlpha([0]) && [0] != '*') {return"", , false } := 0for , := range []byte() {if !isLCAlpha() && !isDigit() && !slices.Contains([]byte("_-.*"), ) {break } ++ }return [:], [:], true}// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-integer-or-decim.func consumeIntegerOrDecimal( string) (, string, bool) {var , , intvarboolif < len() && [] == '-' { ++ ++ }if >= len() {return"", , false }if !isDigit([]) {return"", , false }for < len() { := []ifisDigit() { ++continue }if ! && == '.' {if - > 12 {return"", , false } = = true ++continue }break }if ! && - > 15 {return"", , false }if {if - > 16 {return"", , false }if [-1] == '.' {return"", , false }if --1 > 3 {return"", , false } }return [:], [:], true}// ParseInteger parses an integer from a given HTTP Structured Field Values.//// The entire HTTP SFV string must consist of a valid integer. It returns the// parsed integer and an ok boolean value, indicating success or not.//// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-integer-or-decim.func ( string) ( int64, bool) {if , , := consumeIntegerOrDecimal(); ! || != "" {return0, false }if , := strconv.ParseInt(, 10, 64); == nil {return , true }return0, false}// ParseDecimal parses a decimal from a given HTTP Structured Field Values.//// The entire HTTP SFV string must consist of a valid decimal. It returns the// parsed decimal and an ok boolean value, indicating success or not.//// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-integer-or-decim.func ( string) ( float64, bool) {if , , := consumeIntegerOrDecimal(); ! || != "" {return0, false }if !strings.Contains(, ".") {return0, false }if , := strconv.ParseFloat(, 64); == nil {return , true }return0, false}// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-string.func consumeString( string) (, string, bool) {iflen() == 0 || [0] != '"' {return"", , false }for := 1; < len(); ++ {switch := []; {case'\\':if +1 >= len() {return"", , false } ++if = []; != '"' && != '\\' {return"", , false }case'"':return [:+1], [+1:], truedefault:if !isVChar() && !isSP() {return"", , false } } }return"", , false}// ParseString parses a Go string from a given HTTP Structured Field Values.//// The entire HTTP SFV string must consist of a valid string. It returns the// parsed string and an ok boolean value, indicating success or not.//// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-string.func ( string) ( string, bool) {if , , := consumeString(); ! || != "" {return"", false }return [1 : len()-1], true}// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-tokenfunc consumeToken( string) (, string, bool) {iflen() == 0 || (!isAlpha([0]) && [0] != '*') {return"", , false } := 0for , := range []byte() {if !isTChar() && !slices.Contains([]byte(":/"), ) {break } ++ }return [:], [:], true}// ParseToken parses a token from a given HTTP Structured Field Values.//// The entire HTTP SFV string must consist of a valid token. It returns the// parsed token and an ok boolean value, indicating success or not.//// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-tokenfunc ( string) ( string, bool) {if , , := consumeToken(); ! || != "" {return"", false }return , true}// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-byte-sequence.func consumeByteSequence( string) (, string, bool) {iflen() == 0 || [0] != ':' {return"", , false }for := 1; < len(); ++ {if := []; == ':' {return [:+1], [+1:], true }if := []; !isAlpha() && !isDigit() && !slices.Contains([]byte("+/="), ) {return"", , false } }return"", , false}// ParseByteSequence parses a byte sequence from a given HTTP Structured Field// Values.//// The entire HTTP SFV string must consist of a valid byte sequence. It returns// the parsed byte sequence and an ok boolean value, indicating success or not.//// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-byte-sequence.func ( string) ( []byte, bool) {if , , := consumeByteSequence(); ! || != "" {returnnil, false }return []byte([1 : len()-1]), true}// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-boolean.func consumeBoolean( string) (, string, bool) {iflen() >= 2 && ([:2] == "?0" || [:2] == "?1") {return [:2], [2:], true }return"", , false}// ParseBoolean parses a boolean from a given HTTP Structured Field Values.//// The entire HTTP SFV string must consist of a valid boolean. It returns the// parsed boolean and an ok boolean value, indicating success or not.//// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-boolean.func ( string) ( bool, bool) {if , , := consumeBoolean(); ! || != "" {returnfalse, false }return == "?1", true}// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-date.func consumeDate( string) (, string, bool) {iflen() == 0 || [0] != '@' {return"", , false }if _, , = consumeIntegerOrDecimal([1:]); ! {return"", , } = [:len()-len()]ifslices.Contains([]byte(), '.') {return"", , false }return , , }// ParseDate parses a date from a given HTTP Structured Field Values.//// The entire HTTP SFV string must consist of a valid date. It returns the// parsed date and an ok boolean value, indicating success or not.//// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-date.func ( string) ( time.Time, bool) {if , , := consumeDate(); ! || != "" {returntime.Time{}, false }if , := ParseInteger([1:]); ! {returntime.Time{}, false } else {returntime.Unix(, 0), true }}// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-display-string.func consumeDisplayString( string) (, string, bool) {// To prevent excessive allocation, especially when input is large, we // maintain a buffer of 4 bytes to keep track of the last rune we // encounter. This way, we can validate that the display string conforms to // UTF-8 without actually building the whole string.var [4]bytevarint := func( byte) bool { [] = ++ifutf8.FullRune([:]) { , := utf8.DecodeRune([:])if == utf8.RuneError {returnfalse }copy([:], [:]) -= returntrue }return <= 4 }iflen() <= 1 || [:2] != `%"` {return"", , false } := 2for < len() { := []if !isVChar() && !isSP() {return"", , false }switch {case'"':if > 0 {return"", , false }return [:+1], [+1:], truecase'%':if +2 >= len() {return"", , false }if , = decOctetHex([+1], [+2]); ! {return"", , }if = (); ! {return"", , } += 3default:if = (); ! {return"", , } ++ } }return"", , false}// ParseDisplayString parses a display string from a given HTTP Structured// Field Values.//// The entire HTTP SFV string must consist of a valid display string. It// returns the parsed display string and an ok boolean value, indicating// success or not.//// https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-display-string.func ( string) ( string, bool) {if , , := consumeDisplayString(); ! || != "" {return"", false }// consumeDisplayString() already validates that we have a valid display // string. Therefore, we can just construct the display string, without // validating it again. = [2 : len()-1]varstrings.Builderfor := 0; < len(); {if [] == '%' { , := decOctetHex([+1], [+2]) .WriteByte() += 3continue } .WriteByte([]) ++ }return .String(), true}// https://www.rfc-editor.org/rfc/rfc9651.html#parse-bare-item.func consumeBareItem( string) (, string, bool) {iflen() == 0 {return"", , false } := [0]switch {case == '-' || isDigit():returnconsumeIntegerOrDecimal()case == '"':returnconsumeString()case == '*' || isAlpha():returnconsumeToken()case == ':':returnconsumeByteSequence()case == '?':returnconsumeBoolean()case == '@':returnconsumeDate()case == '%':returnconsumeDisplayString()default:return"", , false }}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.