// Copyright 2015 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 mimeimport ()// A WordEncoder is an RFC 2047 encoded-word encoder.typeWordEncoderbyteconst (// BEncoding represents Base64 encoding scheme as defined by RFC 2045.BEncoding = WordEncoder('b')// QEncoding represents the Q-encoding scheme as defined by RFC 2047.QEncoding = WordEncoder('q'))var ( errInvalidWord = errors.New("mime: invalid RFC 2047 encoded-word"))// Encode returns the encoded-word form of s. If s is ASCII without special// characters, it is returned unchanged. The provided charset is the IANA// charset name of s. It is case insensitive.func ( WordEncoder) (, string) string {if !needsEncoding() {return }return .encodeWord(, )}func needsEncoding( string) bool {for , := range {if ( < ' ' || > '~') && != '\t' {returntrue } }returnfalse}// encodeWord encodes a string into an encoded-word.func ( WordEncoder) (, string) string {varstrings.Builder// Could use a hint like len(s)*3, but that's not enough for cases // with word splits and too much for simpler inputs. // 48 is close to maxEncodedWordLen/2, but adjusted to allocator size class. .Grow(48) .openWord(&, )if == BEncoding { .bEncode(&, , ) } else { .qEncode(&, , ) }closeWord(&)return .String()}const (// The maximum length of an encoded-word is 75 characters. // See RFC 2047, section 2. maxEncodedWordLen = 75// maxContentLen is how much content can be encoded, ignoring the header and // 2-byte footer. maxContentLen = maxEncodedWordLen - len("=?UTF-8?q?") - len("?="))var maxBase64Len = base64.StdEncoding.DecodedLen(maxContentLen)// bEncode encodes s using base64 encoding and writes it to buf.func ( WordEncoder) ( *strings.Builder, , string) { := base64.NewEncoder(base64.StdEncoding, )// If the charset is not UTF-8 or if the content is short, do not bother // splitting the encoded-word.if !isUTF8() || base64.StdEncoding.EncodedLen(len()) <= maxContentLen {io.WriteString(, ) .Close()return }var , , intfor := 0; < len(); += {// Multi-byte characters must not be split across encoded-words. // See RFC 2047, section 5.3. _, = utf8.DecodeRuneInString([:])if + <= maxBase64Len { += } else {io.WriteString(, [:]) .Close() .splitWord(, ) = = } }io.WriteString(, [:]) .Close()}// qEncode encodes s using Q encoding and writes it to buf. It splits the// encoded-words when necessary.func ( WordEncoder) ( *strings.Builder, , string) {// We only split encoded-words when the charset is UTF-8.if !isUTF8() {writeQString(, )return }var , intfor := 0; < len(); += { := []// Multi-byte characters must not be split across encoded-words. // See RFC 2047, section 5.3.varintif >= ' ' && <= '~' && != '=' && != '?' && != '_' { , = 1, 1 } else { _, = utf8.DecodeRuneInString([:]) = 3 * }if + > maxContentLen { .splitWord(, ) = 0 }writeQString(, [:+]) += }}// writeQString encodes s using Q encoding and writes it to buf.func writeQString( *strings.Builder, string) {for := 0; < len(); ++ {switch := []; {case == ' ': .WriteByte('_')case >= '!' && <= '~' && != '=' && != '?' && != '_': .WriteByte()default: .WriteByte('=') .WriteByte(upperhex[>>4]) .WriteByte(upperhex[&0x0f]) } }}// openWord writes the beginning of an encoded-word into buf.func ( WordEncoder) ( *strings.Builder, string) { .WriteString("=?") .WriteString() .WriteByte('?') .WriteByte(byte()) .WriteByte('?')}// closeWord writes the end of an encoded-word into buf.func closeWord( *strings.Builder) { .WriteString("?=")}// splitWord closes the current encoded-word and opens a new one.func ( WordEncoder) ( *strings.Builder, string) {closeWord() .WriteByte(' ') .openWord(, )}func isUTF8( string) bool {returnstrings.EqualFold(, "UTF-8")}const upperhex = "0123456789ABCDEF"// A WordDecoder decodes MIME headers containing RFC 2047 encoded-words.typeWordDecoderstruct {// CharsetReader, if non-nil, defines a function to generate // charset-conversion readers, converting from the provided // charset into UTF-8. // Charsets are always lower-case. utf-8, iso-8859-1 and us-ascii charsets // are handled by default. // One of the CharsetReader's result values must be non-nil. CharsetReader func(charset string, input io.Reader) (io.Reader, error)}// Decode decodes an RFC 2047 encoded-word.func ( *WordDecoder) ( string) (string, error) {// See https://tools.ietf.org/html/rfc2047#section-2 for details. // Our decoder is permissive, we accept empty encoded-text.iflen() < 8 || !strings.HasPrefix(, "=?") || !strings.HasSuffix(, "?=") || strings.Count(, "?") != 4 {return"", errInvalidWord } = [2 : len()-2]// split word "UTF-8?q?text" into "UTF-8", 'q', and "text" , , := strings.Cut(, "?")if == "" {return"", errInvalidWord } , , := strings.Cut(, "?")iflen() != 1 {return"", errInvalidWord } , := decode([0], )if != nil {return"", }varstrings.Builderif := .convert(&, , ); != nil {return"", }return .String(), nil}// DecodeHeader decodes all encoded-words of the given string. It returns an// error if and only if WordDecoder.CharsetReader of d returns an error.func ( *WordDecoder) ( string) (string, error) {// If there is no encoded-word, returns before creating a buffer. := strings.Index(, "=?")if == -1 {return , nil }varstrings.Builder .WriteString([:]) = [:] := falsefor { := strings.Index(, "=?")if == -1 {break } := + len("=?") := strings.Index([:], "?")if == -1 {break } := [ : +] += + len("?")iflen() < +len("Q??=") {break } := [] ++if [] != '?' {break } ++ := strings.Index([:], "?=")if == -1 {break } := [ : +] := + + len("?=") , := decode(, )if != nil { = false .WriteString([:+2]) = [+2:]continue }// Write characters before the encoded-word. White-space and newline // characters separating two encoded-words must be deleted.if > 0 && (! || hasNonWhitespace([:])) { .WriteString([:]) }if := .convert(&, , ); != nil {return"", } = [:] = true }iflen() > 0 { .WriteString() }return .String(), nil}func decode( byte, string) ([]byte, error) {switch {case'B', 'b':returnbase64.StdEncoding.DecodeString()case'Q', 'q':returnqDecode()default:returnnil, errInvalidWord }}func ( *WordDecoder) ( *strings.Builder, string, []byte) error {switch {casestrings.EqualFold("utf-8", ): .Write()casestrings.EqualFold("iso-8859-1", ):for , := range { .WriteRune(rune()) }casestrings.EqualFold("us-ascii", ):for , := range {if >= utf8.RuneSelf { .WriteRune(unicode.ReplacementChar) } else { .WriteByte() } }default:if .CharsetReader == nil {returnfmt.Errorf("mime: unhandled charset %q", ) } , := .CharsetReader(strings.ToLower(), bytes.NewReader())if != nil {return }if _, = io.Copy(, ); != nil {return } }returnnil}// hasNonWhitespace reports whether s (assumed to be ASCII) contains at least// one byte of non-whitespace.func hasNonWhitespace( string) bool {for , := range {switch {// Encoded-words can only be separated by linear white spaces which does // not include vertical tabs (\v).case' ', '\t', '\n', '\r':default:returntrue } }returnfalse}// qDecode decodes a Q encoded string.func qDecode( string) ([]byte, error) { := make([]byte, len()) := 0for := 0; < len(); ++ {switch := []; {case == '_': [] = ' 'case == '=':if +2 >= len() {returnnil, errInvalidWord } , := readHexByte([+1], [+2])if != nil {returnnil, } [] = += 2case ( <= '~' && >= ' ') || == '\n' || == '\r' || == '\t': [] = default:returnnil, errInvalidWord } ++ }return [:], nil}// readHexByte returns the byte from its quoted-printable representation.func readHexByte(, byte) (byte, error) {var , bytevarerrorif , = fromHex(); != nil {return0, }if , = fromHex(); != nil {return0, }return <<4 | , nil}func fromHex( byte) (byte, error) {switch {case >= '0' && <= '9':return - '0', nilcase >= 'A' && <= 'F':return - 'A' + 10, nil// Accept badly encoded bytes.case >= 'a' && <= 'f':return - 'a' + 10, nil }return0, fmt.Errorf("mime: invalid hex byte %#02x", )}
The pages are generated with Goldsv0.7.0-preview. (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.