// Copyright 2010 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 (
	
	
	
	
	
)

// FormatMediaType serializes mediatype t and the parameters
// param as a media type conforming to RFC 2045 and RFC 2616.
// The type and parameter names are written in lower-case.
// When any of the arguments result in a standard violation then
// FormatMediaType returns the empty string.
func ( string,  map[string]string) string {
	var  strings.Builder
	if , ,  := strings.Cut(, "/"); ! {
		if !isToken() {
			return ""
		}
		.WriteString(strings.ToLower())
	} else {
		if !isToken() || !isToken() {
			return ""
		}
		.WriteString(strings.ToLower())
		.WriteByte('/')
		.WriteString(strings.ToLower())
	}

	 := make([]string, 0, len())
	for  := range  {
		 = append(, )
	}
	slices.Sort()

	for ,  := range  {
		 := []
		.WriteByte(';')
		.WriteByte(' ')
		if !isToken() {
			return ""
		}
		.WriteString(strings.ToLower())

		 := needsEncoding()
		if  {
			// RFC 2231 section 4
			.WriteByte('*')
		}
		.WriteByte('=')

		if  {
			.WriteString("utf-8''")

			 := 0
			for  := 0;  < len(); ++ {
				 := []
				// {RFC 2231 section 7}
				// attribute-char := <any (US-ASCII) CHAR except SPACE, CTLs, "*", "'", "%", or tspecials>
				if  <= ' ' ||  >= 0x7F ||
					 == '*' ||  == '\'' ||  == '%' ||
					isTSpecial(rune()) {

					.WriteString([:])
					 =  + 1

					.WriteByte('%')
					.WriteByte(upperhex[>>4])
					.WriteByte(upperhex[&0x0F])
				}
			}
			.WriteString([:])
			continue
		}

		if isToken() {
			.WriteString()
			continue
		}

		.WriteByte('"')
		 := 0
		for  := 0;  < len(); ++ {
			 := []
			if  == '"' ||  == '\\' {
				.WriteString([:])
				 = 
				.WriteByte('\\')
			}
		}
		.WriteString([:])
		.WriteByte('"')
	}
	return .String()
}

func checkMediaTypeDisposition( string) error {
	,  := consumeToken()
	if  == "" {
		return errors.New("mime: no media type")
	}
	if  == "" {
		return nil
	}
	if !strings.HasPrefix(, "/") {
		return errors.New("mime: expected slash after first token")
	}
	,  := consumeToken([1:])
	if  == "" {
		return errors.New("mime: expected token after slash")
	}
	if  != "" {
		return errors.New("mime: unexpected content after media subtype")
	}
	return nil
}

// ErrInvalidMediaParameter is returned by [ParseMediaType] if
// the media type value was found but there was an error parsing
// the optional parameters
var ErrInvalidMediaParameter = errors.New("mime: invalid media parameter")

// ParseMediaType parses a media type value and any optional
// parameters, per RFC 1521.  Media types are the values in
// Content-Type and Content-Disposition headers (RFC 2183).
// On success, ParseMediaType returns the media type converted
// to lowercase and trimmed of white space and a non-nil map.
// If there is an error parsing the optional parameter,
// the media type will be returned along with the error
// [ErrInvalidMediaParameter].
// The returned map, params, maps from the lowercase
// attribute to the attribute value with its case preserved.
func ( string) ( string,  map[string]string,  error) {
	, ,  := strings.Cut(, ";")
	 = strings.TrimSpace(strings.ToLower())

	 = checkMediaTypeDisposition()
	if  != nil {
		return "", nil, 
	}

	 = make(map[string]string)

	// Map of base parameter name -> parameter name -> value
	// for parameters containing a '*' character.
	// Lazily initialized.
	var  map[string]map[string]string

	 = [len():]
	for len() > 0 {
		 = strings.TrimLeftFunc(, unicode.IsSpace)
		if len() == 0 {
			break
		}
		, ,  := consumeMediaParam()
		if  == "" {
			if strings.TrimSpace() == ";" {
				// Ignore trailing semicolons.
				// Not an error.
				break
			}
			// Parse error.
			return , nil, ErrInvalidMediaParameter
		}

		 := 
		if , ,  := strings.Cut(, "*");  {
			if  == nil {
				 = make(map[string]map[string]string)
			}
			var  bool
			if ,  = []; ! {
				[] = make(map[string]string)
				 = []
			}
		}
		if ,  := [];  &&  !=  {
			// Duplicate parameter names are incorrect, but we allow them if they are equal.
			return "", nil, errors.New("mime: duplicate parameter name")
		}
		[] = 
		 = 
	}

	// Stitch together any continuations or things with stars
	// (i.e. RFC 2231 things with stars: "foo*0" or "foo*")
	var  strings.Builder
	for ,  := range  {
		 :=  + "*"
		if ,  := [];  {
			if ,  := decode2231Enc();  {
				[] = 
			}
			continue
		}

		.Reset()
		 := false
		for  := 0; ; ++ {
			 := fmt.Sprintf("%s*%d", , )
			if ,  := [];  {
				 = true
				.WriteString()
				continue
			}
			 :=  + "*"
			,  := []
			if ! {
				break
			}
			 = true
			if  == 0 {
				if ,  := decode2231Enc();  {
					.WriteString()
				}
			} else {
				,  := percentHexUnescape()
				.WriteString()
			}
		}
		if  {
			[] = .String()
		}
	}

	return
}

func decode2231Enc( string) (string, bool) {
	 := strings.SplitN(, "'", 3)
	if len() != 3 {
		return "", false
	}
	// TODO: ignoring lang in sv[1] for now. If anybody needs it we'll
	// need to decide how to expose it in the API. But I'm not sure
	// anybody uses it in practice.
	 := strings.ToLower([0])
	if len() == 0 {
		return "", false
	}
	if  != "us-ascii" &&  != "utf-8" {
		// TODO: unsupported encoding
		return "", false
	}
	,  := percentHexUnescape([2])
	if  != nil {
		return "", false
	}
	return , true
}

func isNotTokenChar( rune) bool {
	return !isTokenChar()
}

// consumeToken consumes a token from the beginning of provided
// string, per RFC 2045 section 5.1 (referenced from 2183), and return
// the token consumed and the rest of the string. Returns ("", v) on
// failure to consume at least one character.
func consumeToken( string) (,  string) {
	 := strings.IndexFunc(, isNotTokenChar)
	if  == -1 {
		return , ""
	}
	if  == 0 {
		return "", 
	}
	return [0:], [:]
}

// consumeValue consumes a "value" per RFC 2045, where a value is
// either a 'token' or a 'quoted-string'.  On success, consumeValue
// returns the value consumed (and de-quoted/escaped, if a
// quoted-string) and the rest of the string. On failure, returns
// ("", v).
func consumeValue( string) (,  string) {
	if  == "" {
		return
	}
	if [0] != '"' {
		return consumeToken()
	}

	// parse a quoted-string
	 := new(strings.Builder)
	for  := 1;  < len(); ++ {
		 := []
		if  == '"' {
			return .String(), [+1:]
		}
		// When MSIE sends a full file path (in "intranet mode"), it does not
		// escape backslashes: "C:\dev\go\foo.txt", not "C:\\dev\\go\\foo.txt".
		//
		// No known MIME generators emit unnecessary backslash escapes
		// for simple token characters like numbers and letters.
		//
		// If we see an unnecessary backslash escape, assume it is from MSIE
		// and intended as a literal backslash. This makes Go servers deal better
		// with MSIE without affecting the way they handle conforming MIME
		// generators.
		if  == '\\' && +1 < len() && isTSpecial(rune([+1])) {
			.WriteByte([+1])
			++
			continue
		}
		if  == '\r' ||  == '\n' {
			return "", 
		}
		.WriteByte([])
	}
	// Did not find end quote.
	return "", 
}

func consumeMediaParam( string) (, ,  string) {
	 = strings.TrimLeftFunc(, unicode.IsSpace)
	if !strings.HasPrefix(, ";") {
		return "", "", 
	}

	 = [1:] // consume semicolon
	 = strings.TrimLeftFunc(, unicode.IsSpace)
	,  = consumeToken()
	 = strings.ToLower()
	if  == "" {
		return "", "", 
	}

	 = strings.TrimLeftFunc(, unicode.IsSpace)
	if !strings.HasPrefix(, "=") {
		return "", "", 
	}
	 = [1:] // consume equals sign
	 = strings.TrimLeftFunc(, unicode.IsSpace)
	,  := consumeValue()
	if  == "" &&  ==  {
		return "", "", 
	}
	 = 
	return , , 
}

func percentHexUnescape( string) (string, error) {
	// Count %, check that they're well-formed.
	 := 0
	for  := 0;  < len(); {
		if [] != '%' {
			++
			continue
		}
		++
		if +2 >= len() || !ishex([+1]) || !ishex([+2]) {
			 = [:]
			if len() > 3 {
				 = [0:3]
			}
			return "", fmt.Errorf("mime: bogus characters after %%: %q", )
		}
		 += 3
	}
	if  == 0 {
		return , nil
	}

	 := make([]byte, len()-2*)
	 := 0
	for  := 0;  < len(); {
		switch [] {
		case '%':
			[] = unhex([+1])<<4 | unhex([+2])
			++
			 += 3
		default:
			[] = []
			++
			++
		}
	}
	return string(), nil
}

func ishex( byte) bool {
	switch {
	case '0' <=  &&  <= '9':
		return true
	case 'a' <=  &&  <= 'f':
		return true
	case 'A' <=  &&  <= 'F':
		return true
	}
	return false
}

func unhex( byte) byte {
	switch {
	case '0' <=  &&  <= '9':
		return  - '0'
	case 'a' <=  &&  <= 'f':
		return  - 'a' + 10
	case 'A' <=  &&  <= 'F':
		return  - 'A' + 10
	}
	return 0
}