// 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 httpsfv import ( ) func isLCAlpha( byte) bool { return ( >= 'a' && <= 'z') } func isAlpha( byte) bool { return isLCAlpha() || ( >= '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 { if isAlpha() || isDigit() { return true } return slices.Contains([]byte{'!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~'}, ) } func countLeftWhitespace( string) int { := 0 for , := 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') { return 0, false } if isDigit() { return - '0', true } return - 'a' + 10, true } if , = (); ! { return 0, } if , = (); ! { return 0, } 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) { for len() != 0 { var , string if len() != 0 && [0] == '(' { if , , = consumeBareInnerList(, nil); ! { return } } else { if , , = consumeBareItem(); ! { return } } if , , = consumeParameter(, nil); ! { return } if != nil { (, ) } = [countLeftWhitespace():] if len() == 0 { break } if [0] != ',' { return false } = [1:] = [countLeftWhitespace():] if len() == 0 { return false } } return true } // 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) { if len() == 0 || [0] != '(' { return "", , false } = [1:] for len() != 0 { var , string = [countLeftWhitespace():] if len() != 0 && [0] == ')' { = [1:] break } if , , = consumeBareItem(); ! { return "", , } if , , = consumeParameter(, nil); ! { return "", , } if len() == 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 , string if , , = 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) { for len() != 0 { var , , string = "?1" // Default value for empty val is boolean true. if , , = consumeKey(); ! { return } if len() != 0 && [0] == '=' { = [1:] if len() != 0 && [0] == '(' { if , , = consumeBareInnerList(, nil); ! { return } } else { if , , = consumeBareItem(); ! { return } } } if , , = consumeParameter(, nil); ! { return } if != nil { (, , ) } = [countLeftWhitespace():] if len() == 0 { break } if [0] == ',' { = [1:] } = [countLeftWhitespace():] if len() == 0 { return false } } return true } // https://www.rfc-editor.org/rfc/rfc9651.html#parse-param. func consumeParameter( string, func(, string)) (, string, bool) { = for len() != 0 { var , string = "?1" // Default value for empty val is boolean true. if [0] != ';' { break } = [1:] = [countLeftWhitespace():] , , = consumeKey() if ! { return "", , } if len() != 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) { if len() == 0 || (!isLCAlpha([0]) && [0] != '*') { return "", , false } := 0 for , := 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 , , int var bool if < len() && [] == '-' { ++ ++ } if >= len() { return "", , false } if !isDigit([]) { return "", , false } for < len() { := [] if isDigit() { ++ 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(); ! || != "" { return 0, false } if , := strconv.ParseInt(, 10, 64); == nil { return , true } return 0, 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(); ! || != "" { return 0, false } if !strings.Contains(, ".") { return 0, false } if , := strconv.ParseFloat(, 64); == nil { return , true } return 0, false } // https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-string. func consumeString( string) (, string, bool) { if len() == 0 || [0] != '"' { return "", , false } for := 1; < len(); ++ { switch := []; { case '\\': if +1 >= len() { return "", , false } ++ if = []; != '"' && != '\\' { return "", , false } case '"': return [:+1], [+1:], true default: 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-token func consumeToken( string) (, string, bool) { if len() == 0 || (!isAlpha([0]) && [0] != '*') { return "", , false } := 0 for , := 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-token func ( 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) { if len() == 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(); ! || != "" { return nil, false } return []byte([1 : len()-1]), true } // https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-boolean. func consumeBoolean( string) (, string, bool) { if len() >= 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(); ! || != "" { return false, false } return == "?1", true } // https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-date. func consumeDate( string) (, string, bool) { if len() == 0 || [0] != '@' { return "", , false } if _, , = consumeIntegerOrDecimal([1:]); ! { return "", , } = [:len()-len()] if slices.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(); ! || != "" { return time.Time{}, false } if , := ParseInteger([1:]); ! { return time.Time{}, false } else { return time.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]byte var int := func( byte) bool { [] = ++ if utf8.FullRune([:]) { , := utf8.DecodeRune([:]) if == utf8.RuneError { return false } copy([:], [:]) -= return true } return <= 4 } if len() <= 1 || [:2] != `%"` { return "", , false } := 2 for < len() { := [] if !isVChar() && !isSP() { return "", , false } switch { case '"': if > 0 { return "", , false } return [:+1], [+1:], true case '%': if +2 >= len() { return "", , false } if , = decOctetHex([+1], [+2]); ! { return "", , } if = (); ! { return "", , } += 3 default: 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] var strings.Builder for := 0; < len(); { if [] == '%' { , := decOctetHex([+1], [+2]) .WriteByte() += 3 continue } .WriteByte([]) ++ } return .String(), true } // https://www.rfc-editor.org/rfc/rfc9651.html#parse-bare-item. func consumeBareItem( string) (, string, bool) { if len() == 0 { return "", , false } := [0] switch { case == '-' || isDigit(): return consumeIntegerOrDecimal() case == '"': return consumeString() case == '*' || isAlpha(): return consumeToken() case == ':': return consumeByteSequence() case == '?': return consumeBoolean() case == '@': return consumeDate() case == '%': return consumeDisplayString() default: return "", , false } }