| // Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT. | |
| //go:generate bundle -o httpsfv.go -prefix= golang.org/x/net/internal/httpsfv | |
| // Package httpsfv provides functionality for dealing with HTTP Structured | |
| // Field Values. | |
| // | |
| package httpsfv | |
| import ( | |
| "slices" | |
| "strconv" | |
| "strings" | |
| "time" | |
| "unicode/utf8" | |
| ) | |
| func isLCAlpha(b byte) bool { | |
| return (b >= 'a' && b <= 'z') | |
| } | |
| func isAlpha(b byte) bool { | |
| return isLCAlpha(b) || (b >= 'A' && b <= 'Z') | |
| } | |
| func isDigit(b byte) bool { | |
| return b >= '0' && b <= '9' | |
| } | |
| func isVChar(b byte) bool { | |
| return b >= 0x21 && b <= 0x7e | |
| } | |
| func isSP(b byte) bool { | |
| return b == 0x20 | |
| } | |
| func isTChar(b byte) bool { | |
| if isAlpha(b) || isDigit(b) { | |
| return true | |
| } | |
| return slices.Contains([]byte{'!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~'}, b) | |
| } | |
| func countLeftWhitespace(s string) int { | |
| i := 0 | |
| for _, ch := range []byte(s) { | |
| if ch != ' ' && ch != '\t' { | |
| break | |
| } | |
| i++ | |
| } | |
| return i | |
| } | |
| // https://www.rfc-editor.org/rfc/rfc4648#section-8. | |
| func decOctetHex(ch1, ch2 byte) (ch byte, ok bool) { | |
| decBase16 := func(in byte) (out byte, ok bool) { | |
| if !isDigit(in) && !(in >= 'a' && in <= 'f') { | |
| return 0, false | |
| } | |
| if isDigit(in) { | |
| return in - '0', true | |
| } | |
| return in - 'a' + 10, true | |
| } | |
| if ch1, ok = decBase16(ch1); !ok { | |
| return 0, ok | |
| } | |
| if ch2, ok = decBase16(ch2); !ok { | |
| return 0, ok | |
| } | |
| return ch1<<4 | ch2, 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 ParseList(s string, f func(member, param string)) (ok bool) { | |
| for len(s) != 0 { | |
| var member, param string | |
| if len(s) != 0 && s[0] == '(' { | |
| if member, s, ok = consumeBareInnerList(s, nil); !ok { | |
| return ok | |
| } | |
| } else { | |
| if member, s, ok = consumeBareItem(s); !ok { | |
| return ok | |
| } | |
| } | |
| if param, s, ok = consumeParameter(s, nil); !ok { | |
| return ok | |
| } | |
| if f != nil { | |
| f(member, param) | |
| } | |
| s = s[countLeftWhitespace(s):] | |
| if len(s) == 0 { | |
| break | |
| } | |
| if s[0] != ',' { | |
| return false | |
| } | |
| s = s[1:] | |
| s = s[countLeftWhitespace(s):] | |
| if len(s) == 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(s string, f func(bareItem, param string)) (consumed, rest string, ok bool) { | |
| if len(s) == 0 || s[0] != '(' { | |
| return "", s, false | |
| } | |
| rest = s[1:] | |
| for len(rest) != 0 { | |
| var bareItem, param string | |
| rest = rest[countLeftWhitespace(rest):] | |
| if len(rest) != 0 && rest[0] == ')' { | |
| rest = rest[1:] | |
| break | |
| } | |
| if bareItem, rest, ok = consumeBareItem(rest); !ok { | |
| return "", s, ok | |
| } | |
| if param, rest, ok = consumeParameter(rest, nil); !ok { | |
| return "", s, ok | |
| } | |
| if len(rest) == 0 || (rest[0] != ')' && !isSP(rest[0])) { | |
| return "", s, false | |
| } | |
| if f != nil { | |
| f(bareItem, param) | |
| } | |
| } | |
| return s[:len(s)-len(rest)], rest, 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 ParseBareInnerList(s string, f func(bareItem, param string)) (ok bool) { | |
| _, rest, ok := consumeBareInnerList(s, f) | |
| return rest == "" && ok | |
| } | |
| // https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-item. | |
| func consumeItem(s string, f func(bareItem, param string)) (consumed, rest string, ok bool) { | |
| var bareItem, param string | |
| if bareItem, rest, ok = consumeBareItem(s); !ok { | |
| return "", s, ok | |
| } | |
| if param, rest, ok = consumeParameter(rest, nil); !ok { | |
| return "", s, ok | |
| } | |
| if f != nil { | |
| f(bareItem, param) | |
| } | |
| return s[:len(s)-len(rest)], rest, 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 ParseItem(s string, f func(bareItem, param string)) (ok bool) { | |
| _, rest, ok := consumeItem(s, f) | |
| return rest == "" && ok | |
| } | |
| // 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 ParseDictionary(s string, f func(key, val, param string)) (ok bool) { | |
| for len(s) != 0 { | |
| var key, val, param string | |
| val = "?1" // Default value for empty val is boolean true. | |
| if key, s, ok = consumeKey(s); !ok { | |
| return ok | |
| } | |
| if len(s) != 0 && s[0] == '=' { | |
| s = s[1:] | |
| if len(s) != 0 && s[0] == '(' { | |
| if val, s, ok = consumeBareInnerList(s, nil); !ok { | |
| return ok | |
| } | |
| } else { | |
| if val, s, ok = consumeBareItem(s); !ok { | |
| return ok | |
| } | |
| } | |
| } | |
| if param, s, ok = consumeParameter(s, nil); !ok { | |
| return ok | |
| } | |
| if f != nil { | |
| f(key, val, param) | |
| } | |
| s = s[countLeftWhitespace(s):] | |
| if len(s) == 0 { | |
| break | |
| } | |
| if s[0] == ',' { | |
| s = s[1:] | |
| } | |
| s = s[countLeftWhitespace(s):] | |
| if len(s) == 0 { | |
| return false | |
| } | |
| } | |
| return true | |
| } | |
| // https://www.rfc-editor.org/rfc/rfc9651.html#parse-param. | |
| func consumeParameter(s string, f func(key, val string)) (consumed, rest string, ok bool) { | |
| rest = s | |
| for len(rest) != 0 { | |
| var key, val string | |
| val = "?1" // Default value for empty val is boolean true. | |
| if rest[0] != ';' { | |
| break | |
| } | |
| rest = rest[1:] | |
| rest = rest[countLeftWhitespace(rest):] | |
| key, rest, ok = consumeKey(rest) | |
| if !ok { | |
| return "", s, ok | |
| } | |
| if len(rest) != 0 && rest[0] == '=' { | |
| rest = rest[1:] | |
| val, rest, ok = consumeBareItem(rest) | |
| if !ok { | |
| return "", s, ok | |
| } | |
| } | |
| if f != nil { | |
| f(key, val) | |
| } | |
| } | |
| return s[:len(s)-len(rest)], rest, 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 ParseParameter(s string, f func(key, val string)) (ok bool) { | |
| _, rest, ok := consumeParameter(s, f) | |
| return rest == "" && ok | |
| } | |
| // https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-key. | |
| func consumeKey(s string) (consumed, rest string, ok bool) { | |
| if len(s) == 0 || (!isLCAlpha(s[0]) && s[0] != '*') { | |
| return "", s, false | |
| } | |
| i := 0 | |
| for _, ch := range []byte(s) { | |
| if !isLCAlpha(ch) && !isDigit(ch) && !slices.Contains([]byte("_-.*"), ch) { | |
| break | |
| } | |
| i++ | |
| } | |
| return s[:i], s[i:], true | |
| } | |
| // https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-integer-or-decim. | |
| func consumeIntegerOrDecimal(s string) (consumed, rest string, ok bool) { | |
| var i, signOffset, periodIndex int | |
| var isDecimal bool | |
| if i < len(s) && s[i] == '-' { | |
| i++ | |
| signOffset++ | |
| } | |
| if i >= len(s) { | |
| return "", s, false | |
| } | |
| if !isDigit(s[i]) { | |
| return "", s, false | |
| } | |
| for i < len(s) { | |
| ch := s[i] | |
| if isDigit(ch) { | |
| i++ | |
| continue | |
| } | |
| if !isDecimal && ch == '.' { | |
| if i-signOffset > 12 { | |
| return "", s, false | |
| } | |
| periodIndex = i | |
| isDecimal = true | |
| i++ | |
| continue | |
| } | |
| break | |
| } | |
| if !isDecimal && i-signOffset > 15 { | |
| return "", s, false | |
| } | |
| if isDecimal { | |
| if i-signOffset > 16 { | |
| return "", s, false | |
| } | |
| if s[i-1] == '.' { | |
| return "", s, false | |
| } | |
| if i-periodIndex-1 > 3 { | |
| return "", s, false | |
| } | |
| } | |
| return s[:i], s[i:], 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 ParseInteger(s string) (parsed int64, ok bool) { | |
| if _, rest, ok := consumeIntegerOrDecimal(s); !ok || rest != "" { | |
| return 0, false | |
| } | |
| if n, err := strconv.ParseInt(s, 10, 64); err == nil { | |
| return n, 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 ParseDecimal(s string) (parsed float64, ok bool) { | |
| if _, rest, ok := consumeIntegerOrDecimal(s); !ok || rest != "" { | |
| return 0, false | |
| } | |
| if !strings.Contains(s, ".") { | |
| return 0, false | |
| } | |
| if n, err := strconv.ParseFloat(s, 64); err == nil { | |
| return n, true | |
| } | |
| return 0, false | |
| } | |
| // https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-string. | |
| func consumeString(s string) (consumed, rest string, ok bool) { | |
| if len(s) == 0 || s[0] != '"' { | |
| return "", s, false | |
| } | |
| for i := 1; i < len(s); i++ { | |
| switch ch := s[i]; ch { | |
| case '\\': | |
| if i+1 >= len(s) { | |
| return "", s, false | |
| } | |
| i++ | |
| if ch = s[i]; ch != '"' && ch != '\\' { | |
| return "", s, false | |
| } | |
| case '"': | |
| return s[:i+1], s[i+1:], true | |
| default: | |
| if !isVChar(ch) && !isSP(ch) { | |
| return "", s, false | |
| } | |
| } | |
| } | |
| return "", s, 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 ParseString(s string) (parsed string, ok bool) { | |
| if _, rest, ok := consumeString(s); !ok || rest != "" { | |
| return "", false | |
| } | |
| return s[1 : len(s)-1], true | |
| } | |
| // https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-token | |
| func consumeToken(s string) (consumed, rest string, ok bool) { | |
| if len(s) == 0 || (!isAlpha(s[0]) && s[0] != '*') { | |
| return "", s, false | |
| } | |
| i := 0 | |
| for _, ch := range []byte(s) { | |
| if !isTChar(ch) && !slices.Contains([]byte(":/"), ch) { | |
| break | |
| } | |
| i++ | |
| } | |
| return s[:i], s[i:], 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 ParseToken(s string) (parsed string, ok bool) { | |
| if _, rest, ok := consumeToken(s); !ok || rest != "" { | |
| return "", false | |
| } | |
| return s, true | |
| } | |
| // https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-byte-sequence. | |
| func consumeByteSequence(s string) (consumed, rest string, ok bool) { | |
| if len(s) == 0 || s[0] != ':' { | |
| return "", s, false | |
| } | |
| for i := 1; i < len(s); i++ { | |
| if ch := s[i]; ch == ':' { | |
| return s[:i+1], s[i+1:], true | |
| } | |
| if ch := s[i]; !isAlpha(ch) && !isDigit(ch) && !slices.Contains([]byte("+/="), ch) { | |
| return "", s, false | |
| } | |
| } | |
| return "", s, 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 ParseByteSequence(s string) (parsed []byte, ok bool) { | |
| if _, rest, ok := consumeByteSequence(s); !ok || rest != "" { | |
| return nil, false | |
| } | |
| return []byte(s[1 : len(s)-1]), true | |
| } | |
| // https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-boolean. | |
| func consumeBoolean(s string) (consumed, rest string, ok bool) { | |
| if len(s) >= 2 && (s[:2] == "?0" || s[:2] == "?1") { | |
| return s[:2], s[2:], true | |
| } | |
| return "", s, 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 ParseBoolean(s string) (parsed bool, ok bool) { | |
| if _, rest, ok := consumeBoolean(s); !ok || rest != "" { | |
| return false, false | |
| } | |
| return s == "?1", true | |
| } | |
| // https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-date. | |
| func consumeDate(s string) (consumed, rest string, ok bool) { | |
| if len(s) == 0 || s[0] != '@' { | |
| return "", s, false | |
| } | |
| if _, rest, ok = consumeIntegerOrDecimal(s[1:]); !ok { | |
| return "", s, ok | |
| } | |
| consumed = s[:len(s)-len(rest)] | |
| if slices.Contains([]byte(consumed), '.') { | |
| return "", s, false | |
| } | |
| return consumed, rest, ok | |
| } | |
| // 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 ParseDate(s string) (parsed time.Time, ok bool) { | |
| if _, rest, ok := consumeDate(s); !ok || rest != "" { | |
| return time.Time{}, false | |
| } | |
| if n, ok := ParseInteger(s[1:]); !ok { | |
| return time.Time{}, false | |
| } else { | |
| return time.Unix(n, 0), true | |
| } | |
| } | |
| // https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-a-display-string. | |
| func consumeDisplayString(s string) (consumed, rest string, ok 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 lastRune [4]byte | |
| var runeLen int | |
| isPartOfValidRune := func(ch byte) bool { | |
| lastRune[runeLen] = ch | |
| runeLen++ | |
| if utf8.FullRune(lastRune[:runeLen]) { | |
| r, s := utf8.DecodeRune(lastRune[:runeLen]) | |
| if r == utf8.RuneError { | |
| return false | |
| } | |
| copy(lastRune[:], lastRune[s:runeLen]) | |
| runeLen -= s | |
| return true | |
| } | |
| return runeLen <= 4 | |
| } | |
| if len(s) <= 1 || s[:2] != `%"` { | |
| return "", s, false | |
| } | |
| i := 2 | |
| for i < len(s) { | |
| ch := s[i] | |
| if !isVChar(ch) && !isSP(ch) { | |
| return "", s, false | |
| } | |
| switch ch { | |
| case '"': | |
| if runeLen > 0 { | |
| return "", s, false | |
| } | |
| return s[:i+1], s[i+1:], true | |
| case '%': | |
| if i+2 >= len(s) { | |
| return "", s, false | |
| } | |
| if ch, ok = decOctetHex(s[i+1], s[i+2]); !ok { | |
| return "", s, ok | |
| } | |
| if ok = isPartOfValidRune(ch); !ok { | |
| return "", s, ok | |
| } | |
| i += 3 | |
| default: | |
| if ok = isPartOfValidRune(ch); !ok { | |
| return "", s, ok | |
| } | |
| i++ | |
| } | |
| } | |
| return "", s, 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 ParseDisplayString(s string) (parsed string, ok bool) { | |
| if _, rest, ok := consumeDisplayString(s); !ok || rest != "" { | |
| return "", false | |
| } | |
| // consumeDisplayString() already validates that we have a valid display | |
| // string. Therefore, we can just construct the display string, without | |
| // validating it again. | |
| s = s[2 : len(s)-1] | |
| var b strings.Builder | |
| for i := 0; i < len(s); { | |
| if s[i] == '%' { | |
| decoded, _ := decOctetHex(s[i+1], s[i+2]) | |
| b.WriteByte(decoded) | |
| i += 3 | |
| continue | |
| } | |
| b.WriteByte(s[i]) | |
| i++ | |
| } | |
| return b.String(), true | |
| } | |
| // https://www.rfc-editor.org/rfc/rfc9651.html#parse-bare-item. | |
| func consumeBareItem(s string) (consumed, rest string, ok bool) { | |
| if len(s) == 0 { | |
| return "", s, false | |
| } | |
| ch := s[0] | |
| switch { | |
| case ch == '-' || isDigit(ch): | |
| return consumeIntegerOrDecimal(s) | |
| case ch == '"': | |
| return consumeString(s) | |
| case ch == '*' || isAlpha(ch): | |
| return consumeToken(s) | |
| case ch == ':': | |
| return consumeByteSequence(s) | |
| case ch == '?': | |
| return consumeBoolean(s) | |
| case ch == '@': | |
| return consumeDate(s) | |
| case ch == '%': | |
| return consumeDisplayString(s) | |
| default: | |
| return "", s, false | |
| } | |
| } |