// 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 mime

import (
	
	
	
	
	
	
	
	
)

// A WordEncoder is an RFC 2047 encoded-word encoder.
type WordEncoder byte

const (
	// 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' {
			return true
		}
	}
	return false
}

// encodeWord encodes a string into an encoded-word.
func ( WordEncoder) (,  string) string {
	var  strings.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 , ,  int
	for  := 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 ,  int
	for  := 0;  < len();  +=  {
		 := []
		// Multi-byte characters must not be split across encoded-words.
		// See RFC 2047, section 5.3.
		var  int
		if  >= ' ' &&  <= '~' &&  != '=' &&  != '?' &&  != '_' {
			,  = 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 {
	return strings.EqualFold(, "UTF-8")
}

const upperhex = "0123456789ABCDEF"

// A WordDecoder decodes MIME headers containing RFC 2047 encoded-words.
type WordDecoder struct {
	// 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.
	if len() < 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(, "?")
	if len() != 1 {
		return "", errInvalidWord
	}

	,  := decode([0], )
	if  != nil {
		return "", 
	}

	var  strings.Builder
	if  := .convert(&, , );  != nil {
		return "", 
	}
	return .String(), nil
}

// DecodeHeader decodes all encoded-words of the given string. It returns an
// error if and only if 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
	}

	var  strings.Builder

	.WriteString([:])
	 = [:]

	 := false
	for {
		 := strings.Index(, "=?")
		if  == -1 {
			break
		}
		 :=  + len("=?")

		 := strings.Index([:], "?")
		if  == -1 {
			break
		}
		 := [ : +]
		 +=  + len("?")

		if len() < +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
	}

	if len() > 0 {
		.WriteString()
	}

	return .String(), nil
}

func decode( byte,  string) ([]byte, error) {
	switch  {
	case 'B', 'b':
		return base64.StdEncoding.DecodeString()
	case 'Q', 'q':
		return qDecode()
	default:
		return nil, errInvalidWord
	}
}

func ( *WordDecoder) ( *strings.Builder,  string,  []byte) error {
	switch {
	case strings.EqualFold("utf-8", ):
		.Write()
	case strings.EqualFold("iso-8859-1", ):
		for ,  := range  {
			.WriteRune(rune())
		}
	case strings.EqualFold("us-ascii", ):
		for ,  := range  {
			if  >= utf8.RuneSelf {
				.WriteRune(unicode.ReplacementChar)
			} else {
				.WriteByte()
			}
		}
	default:
		if .CharsetReader == nil {
			return fmt.Errorf("mime: unhandled charset %q", )
		}
		,  := .CharsetReader(strings.ToLower(), bytes.NewReader())
		if  != nil {
			return 
		}
		if _,  = io.Copy(, );  != nil {
			return 
		}
	}
	return nil
}

// 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:
			return true
		}
	}
	return false
}

// qDecode decodes a Q encoded string.
func qDecode( string) ([]byte, error) {
	 := make([]byte, len())
	 := 0
	for  := 0;  < len(); ++ {
		switch  := []; {
		case  == '_':
			[] = ' '
		case  == '=':
			if +2 >= len() {
				return nil, errInvalidWord
			}
			,  := readHexByte([+1], [+2])
			if  != nil {
				return nil, 
			}
			[] = 
			 += 2
		case ( <= '~' &&  >= ' ') ||  == '\n' ||  == '\r' ||  == '\t':
			[] = 
		default:
			return nil, errInvalidWord
		}
		++
	}

	return [:], nil
}

// readHexByte returns the byte from its quoted-printable representation.
func readHexByte(,  byte) (byte, error) {
	var ,  byte
	var  error
	if ,  = fromHex();  != nil {
		return 0, 
	}
	if ,  = fromHex();  != nil {
		return 0, 
	}
	return <<4 | , nil
}

func fromHex( byte) (byte, error) {
	switch {
	case  >= '0' &&  <= '9':
		return  - '0', nil
	case  >= 'A' &&  <= 'F':
		return  - 'A' + 10, nil
	// Accept badly encoded bytes.
	case  >= 'a' &&  <= 'f':
		return  - 'a' + 10, nil
	}
	return 0, fmt.Errorf("mime: invalid hex byte %#02x", )
}