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<>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 } }