235 lines
5.7 KiB
Go
235 lines
5.7 KiB
Go
package inline
|
|
|
|
// from textproto
|
|
|
|
// A MIMEHeader represents a MIME-style header mapping
|
|
// keys to sets of values.
|
|
type MIMEHeader map[string][]string
|
|
|
|
// Add adds the key, value pair to the header.
|
|
// It appends to any existing values associated with key.
|
|
func (h MIMEHeader) Add(key, value string) {
|
|
key = CanonicalMIMEHeaderKey(key)
|
|
h[key] = append(h[key], value)
|
|
}
|
|
|
|
// Set sets the header entries associated with key to
|
|
// the single element value. It replaces any existing
|
|
// values associated with key.
|
|
func (h MIMEHeader) Set(key, value string) {
|
|
h[CanonicalMIMEHeaderKey(key)] = []string{value}
|
|
}
|
|
|
|
// Get gets the first value associated with the given key.
|
|
// It is case insensitive; [CanonicalMIMEHeaderKey] is used
|
|
// to canonicalize the provided key.
|
|
// If there are no values associated with the key, Get returns "".
|
|
// To use non-canonical keys, access the map directly.
|
|
func (h MIMEHeader) Get(key string) string {
|
|
if h == nil {
|
|
return ""
|
|
}
|
|
v := h[CanonicalMIMEHeaderKey(key)]
|
|
if len(v) == 0 {
|
|
return ""
|
|
}
|
|
return v[0]
|
|
}
|
|
|
|
// Values returns all values associated with the given key.
|
|
// It is case insensitive; [CanonicalMIMEHeaderKey] is
|
|
// used to canonicalize the provided key. To use non-canonical
|
|
// keys, access the map directly.
|
|
// The returned slice is not a copy.
|
|
func (h MIMEHeader) Values(key string) []string {
|
|
if h == nil {
|
|
return nil
|
|
}
|
|
return h[CanonicalMIMEHeaderKey(key)]
|
|
}
|
|
|
|
// Del deletes the values associated with key.
|
|
func (h MIMEHeader) Del(key string) {
|
|
delete(h, CanonicalMIMEHeaderKey(key))
|
|
}
|
|
|
|
// CanonicalMIMEHeaderKey returns the canonical format of the
|
|
// MIME header key s. The canonicalization converts the first
|
|
// letter and any letter following a hyphen to upper case;
|
|
// the rest are converted to lowercase. For example, the
|
|
// canonical key for "accept-encoding" is "Accept-Encoding".
|
|
// MIME header keys are assumed to be ASCII only.
|
|
// If s contains a space or invalid header field bytes, it is
|
|
// returned without modifications.
|
|
func CanonicalMIMEHeaderKey(s string) string {
|
|
// Quick check for canonical encoding.
|
|
upper := true
|
|
for i := 0; i < len(s); i++ {
|
|
c := s[i]
|
|
if !validHeaderFieldByte(c) {
|
|
return s
|
|
}
|
|
if upper && 'a' <= c && c <= 'z' {
|
|
s, _ = canonicalMIMEHeaderKey([]byte(s))
|
|
return s
|
|
}
|
|
if !upper && 'A' <= c && c <= 'Z' {
|
|
s, _ = canonicalMIMEHeaderKey([]byte(s))
|
|
return s
|
|
}
|
|
upper = c == '-'
|
|
}
|
|
return s
|
|
}
|
|
|
|
const toLower = 'a' - 'A'
|
|
|
|
// validHeaderFieldByte reports whether c is a valid byte in a header
|
|
// field name. RFC 7230 says:
|
|
//
|
|
// header-field = field-name ":" OWS field-value OWS
|
|
// field-name = token
|
|
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
|
|
// "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
|
|
// token = 1*tchar
|
|
func validHeaderFieldByte(c byte) bool {
|
|
// mask is a 128-bit bitmap with 1s for allowed bytes,
|
|
// so that the byte c can be tested with a shift and an and.
|
|
// If c >= 128, then 1<<c and 1<<(c-64) will both be zero,
|
|
// and this function will return false.
|
|
const mask = 0 |
|
|
(1<<(10)-1)<<'0' |
|
|
(1<<(26)-1)<<'a' |
|
|
(1<<(26)-1)<<'A' |
|
|
1<<'!' |
|
|
1<<'#' |
|
|
1<<'$' |
|
|
1<<'%' |
|
|
1<<'&' |
|
|
1<<'\'' |
|
|
1<<'*' |
|
|
1<<'+' |
|
|
1<<'-' |
|
|
1<<'.' |
|
|
1<<'^' |
|
|
1<<'_' |
|
|
1<<'`' |
|
|
1<<'|' |
|
|
1<<'~'
|
|
return ((uint64(1)<<c)&(mask&(1<<64-1)) |
|
|
(uint64(1)<<(c-64))&(mask>>64)) != 0
|
|
}
|
|
|
|
// canonicalMIMEHeaderKey is like CanonicalMIMEHeaderKey but is
|
|
// allowed to mutate the provided byte slice before returning the
|
|
// string.
|
|
//
|
|
// For invalid inputs (if a contains spaces or non-token bytes), a
|
|
// is unchanged and a string copy is returned.
|
|
//
|
|
// ok is true if the header key contains only valid characters and spaces.
|
|
// ReadMIMEHeader accepts header keys containing spaces, but does not
|
|
// canonicalize them.
|
|
func canonicalMIMEHeaderKey(a []byte) (_ string, ok bool) {
|
|
if len(a) == 0 {
|
|
return "", false
|
|
}
|
|
|
|
// See if a looks like a header key. If not, return it unchanged.
|
|
noCanon := false
|
|
for _, c := range a {
|
|
if validHeaderFieldByte(c) {
|
|
continue
|
|
}
|
|
// Don't canonicalize.
|
|
if c == ' ' {
|
|
// We accept invalid headers with a space before the
|
|
// colon, but must not canonicalize them.
|
|
// See https://go.dev/issue/34540.
|
|
noCanon = true
|
|
continue
|
|
}
|
|
return string(a), false
|
|
}
|
|
if noCanon {
|
|
return string(a), true
|
|
}
|
|
|
|
upper := true
|
|
for i, c := range a {
|
|
// Canonicalize: first letter upper case
|
|
// and upper case after each dash.
|
|
// (Host, User-Agent, If-Modified-Since).
|
|
// MIME headers are ASCII only, so no Unicode issues.
|
|
if upper && 'a' <= c && c <= 'z' {
|
|
c -= toLower
|
|
} else if !upper && 'A' <= c && c <= 'Z' {
|
|
c += toLower
|
|
}
|
|
a[i] = c
|
|
upper = c == '-' // for next time
|
|
}
|
|
|
|
// The compiler recognizes m[string(byteSlice)] as a special
|
|
// case, so a copy of a's bytes into a new string does not
|
|
// happen in this map lookup:
|
|
if v := commonHeader[string(a)]; v != "" {
|
|
return v, true
|
|
}
|
|
return string(a), true
|
|
}
|
|
|
|
func init() {
|
|
initCommonHeader()
|
|
}
|
|
|
|
// commonHeader interns common header strings.
|
|
var commonHeader map[string]string
|
|
|
|
func initCommonHeader() {
|
|
commonHeader = make(map[string]string)
|
|
for _, v := range []string{
|
|
"Accept",
|
|
"Accept-Charset",
|
|
"Accept-Encoding",
|
|
"Accept-Language",
|
|
"Accept-Ranges",
|
|
"Cache-Control",
|
|
"Cc",
|
|
"Connection",
|
|
"Content-Id",
|
|
"Content-Language",
|
|
"Content-Length",
|
|
"Content-Transfer-Encoding",
|
|
"Content-Type",
|
|
"Cookie",
|
|
"Date",
|
|
"Dkim-Signature",
|
|
"Etag",
|
|
"Expires",
|
|
"From",
|
|
"Host",
|
|
"If-Modified-Since",
|
|
"If-None-Match",
|
|
"In-Reply-To",
|
|
"Last-Modified",
|
|
"Location",
|
|
"Message-Id",
|
|
"Mime-Version",
|
|
"Pragma",
|
|
"Received",
|
|
"Return-Path",
|
|
"Server",
|
|
"Set-Cookie",
|
|
"Subject",
|
|
"To",
|
|
"User-Agent",
|
|
"Via",
|
|
"X-Forwarded-For",
|
|
"X-Imforwards",
|
|
"X-Powered-By",
|
|
} {
|
|
commonHeader[v] = v
|
|
}
|
|
}
|