package imageasset

import (
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	

	
)

type sourceKind uint8

const (
	sourceLocal sourceKind = iota + 1
	sourceHTTP
	sourceData
)

type sourceSpec struct {
	kind  sourceKind
	raw   string
	label string
	key   string
	path  string
	url   string
}

type loadedSource struct {
	data              []byte
	mimeHint          string
	fetchedBytes      int64
	decompressedBytes int64
}

type resolveCall struct {
	done     chan struct{}
	resource *Resource
	err      error
}

// Data-URI media-type parameters are metadata, not fetched image bytes. Bound
// them separately before hashing or splitting attacker-controlled input.
const maxDataURIMetadataBytes = 4 << 10

const maxLocatorBytes = 64 << 10

const (
	maxContentEncodingHeaderBytes = 256
	maxContentEncodingTokens      = 4
	maxContentTypeHeaderBytes     = 1024
)

// Resolve resolves one image source and charges its decoded footprint before
// returning. Supported inputs are relative/absolute local paths, http(s) URLs,
// and data URIs. Animated GIF, APNG, and WebP resources retain their original
// bytes; native raster consumers deterministically paint the first animation
// frame on the format's logical canvas.
func ( *Resolver) ( context.Context,  string) (*Resource, error) {
	if  == nil {
		 = context.Background()
	}
	if  := checkContext();  != nil {
		return nil, sourceError(displaySource(), "resolve", )
	}
	,  := .classifySource(, )
	if  != nil {
		return nil, sourceError(displaySource(), "classify source", )
	}
	if  := checkContext();  != nil {
		return nil, sourceError(.label, "resolve", )
	}
	for {
		.mu.Lock()
		if  := .resolvedSources[.key];  != nil {
			.mu.Unlock()
			return , nil
		}
		if  := .inflight[.key];  != nil {
			.mu.Unlock()
			select {
			case <-.Done():
				return nil, sourceError(.label, "resolve", .Err())
			case <-.done:
				if .err == nil {
					if  := checkContext();  != nil {
						return nil, sourceError(.label, "resolve", )
					}
					return .resource, nil
				}
				// A failed leader (including one canceled by its own caller) does
				// not poison another caller's independent context.
				continue
			}
		}
		 := &resolveCall{done: make(chan struct{})}
		.inflight[.key] = 
		.mu.Unlock()

		,  := .resolveOne(, )
		// Inline resources are already bundled. Keep same-resolver memoization,
		// but do not retain changing data URIs in the process-wide IMG_CACHE;
		// this preserves imgbundler behavior in watch mode.
		if  == nil && .cache != nil && .kind != sourceData {
			.cache.Put(.cachePrefix+.key, )
		}

		.mu.Lock()
		if  == nil {
			.resolvedSources[.key] = 
		}
		.resource = 
		.err = 
		delete(.inflight, .key)
		close(.done)
		.mu.Unlock()
		return , 
	}
}

func ( *Resolver) ( context.Context,  sourceSpec) (*Resource, error) {
	if .cache != nil && .kind != sourceData {
		if ,  := .cache.Get(.cachePrefix + .key);  {
			if  == nil {
				return nil, sourceError(.label, "read cache", errors.New("cache returned a nil resource"))
			}
			if  := .validateCached();  != nil {
				return nil, sourceError(.label, "validate cached resource", )
			}
			if  := checkContext();  != nil {
				return nil, sourceError(.label, "resolve", )
			}
			,  := .reserve(int64(len(.data)), .decodedBytes)
			if  != nil {
				return nil, sourceError(.label, "reserve resource", )
			}
			if  := checkContext();  != nil {
				()
				return nil, sourceError(.label, "resolve", )
			}
			return , nil
		}
	}
	// Only the elected loader consumes a resolution slot. Same-resolver hits,
	// process-cache hits, and callers waiting on an in-flight canonical source
	// do not reduce capacity available to unrelated resources.
	select {
	case .resolveSlots <- struct{}{}:
		defer func() { <-.resolveSlots }()
	case <-.Done():
		return nil, sourceError(.label, "wait for resolution slot", .Err())
	}

	,  := .load(, )
	if  != nil {
		return nil, sourceError(.label, "load", )
	}
	if  := checkContext();  != nil {
		return nil, sourceError(.label, "resolve", )
	}
	,  := probeResource(, , .limits, func(,  int64) (func(), error) {
		return .reserve(, )
	})
	if  != nil {
		return nil, sourceError(.label, "probe", )
	}
	return , nil
}

func ( *Resolver) ( context.Context,  string) (sourceSpec, error) {
	 := 
	if  == "" {
		return sourceSpec{}, errors.New("empty source")
	}
	if hasPrefixFold(, "data:") {
		 := [len("data:"):]
		 := len()
		if  > maxDataURIMetadataBytes+1 {
			 = maxDataURIMetadataBytes + 1
		}
		 := strings.IndexByte([:], ',')
		if  < 0 {
			if len() > maxDataURIMetadataBytes {
				return sourceSpec{}, &LimitError{Name: "data URI metadata bytes", Actual: maxDataURIMetadataBytes + 1, Limit: maxDataURIMetadataBytes}
			}
			return sourceSpec{}, errors.New("data URI has no comma separator")
		}
		 := len() -  - 1
		if int64() > .limits.MaxFetchedBytes {
			return sourceSpec{}, &LimitError{Name: "fetched bytes", Actual: int64(), Limit: .limits.MaxFetchedBytes}
		}
		,  := hashSource(, )
		if  != nil {
			return sourceSpec{}, 
		}
		return sourceSpec{kind: sourceData, raw: , label: displaySource(), key: "data:" + }, nil
	}
	if len() > maxLocatorBytes {
		return sourceSpec{}, &LimitError{Name: "source locator bytes", Actual: int64(len()), Limit: maxLocatorBytes}
	}
	if strings.HasPrefix(, "//") {
		return sourceSpec{}, errors.New("network-path references beginning with // are unsupported; use an explicit http:// or https:// URL")
	}
	if hasPrefixFold(, "http://") || hasPrefixFold(, "https://") {
		,  := url.Parse()
		if  != nil {
			return sourceSpec{}, sanitizeError("invalid HTTP URL", )
		}
		if .Host == "" {
			return sourceSpec{}, errors.New("HTTP URL has no host")
		}
		.Fragment = ""
		.RawFragment = ""
		 := .String()
		,  := hashSource(, )
		if  != nil {
			return sourceSpec{}, 
		}
		return sourceSpec{kind: sourceHTTP, raw: , label: displaySource(), key: "http:" + , url: }, nil
	}
	if ,  := uriScheme();  && !filepath.IsAbs() {
		if ,  := url.Parse();  != nil {
			return sourceSpec{}, sanitizeError("invalid "++" URI", )
		}
		return sourceSpec{}, fmt.Errorf("unsupported URI scheme %q", )
	}
	 := 
	if !filepath.IsAbs() && .baseDir != "" {
		 = filepath.Join(.baseDir, )
	}
	,  := filepath.Abs()
	if  != nil {
		return sourceSpec{}, 
	}
	 = filepath.Clean()
	,  := .localFiles.CacheKey()
	if  != nil {
		return sourceSpec{}, 
	}
	return sourceSpec{kind: sourceLocal, raw: , label: , key: "file:" + , path: }, nil
}

func displaySource( string) string {
	if hasPrefixFold(, "data:") {
		 := [len("data:"):]
		if len() > 129 {
			 = [:129]
		}
		, _, _ = strings.Cut(, ",")
		, ,  := strings.Cut(, ";")
		 = strings.TrimSpace()
		if !safeMediaTypeLabel() {
			return "data URI"
		}
		return "data:" + 
	}
	if strings.HasPrefix(, "//") {
		return "<network-path reference>"
	}
	if len() > maxLocatorBytes {
		return "<oversized source locator>"
	}
	if ,  := uriScheme();  && !filepath.IsAbs() {
		if ,  := url.Parse();  == nil {
			if .Opaque != "" {
				return .Scheme + ":<redacted>"
			}
			.User = nil
			.RawQuery = ""
			.ForceQuery = false
			.Fragment = ""
			.RawFragment = ""
			return .String()
		}
		return "<malformed URI>"
	}
	return 
}

func safeMediaTypeLabel( string) bool {
	if  == "" || len() > 128 {
		return false
	}
	 := 0
	for  := 0;  < len(); ++ {
		 := []
		switch {
		case  >= 'a' &&  <= 'z',
			 >= 'A' &&  <= 'Z',
			 >= '0' &&  <= '9',
			 == '.',  == '+',  == '-',  == '_':
		case  == '/':
			++
			if  == 0 ||  == len()-1 {
				return false
			}
		default:
			return false
		}
	}
	return  == 1
}

func uriScheme( string) (string, bool) {
	if  == "" || [0] < 'A' || [0] > 'Z' && ([0] < 'a' || [0] > 'z') {
		return "", false
	}
	for  := 1;  < len(); ++ {
		 := []
		if  == ':' {
			return [:], true
		}
		if  >= 'A' &&  <= 'Z' ||  >= 'a' &&  <= 'z' ||
			 >= '0' &&  <= '9' ||  == '+' ||  == '-' ||  == '.' {
			continue
		}
		return "", false
	}
	return "", false
}

func hashSource( context.Context,  string) (string, error) {
	 := sha256.New()
	for  := 0;  < len(); {
		if  := checkContext();  != nil {
			return "", 
		}
		 :=  + 32*1024
		if  > len() {
			 = len()
		}
		_, _ = io.WriteString(, [:])
		 = 
	}
	return fmt.Sprintf("%x", .Sum(nil)), nil
}

func hasPrefixFold(,  string) bool {
	return len() >= len() && strings.EqualFold([:len()], )
}

func ( *Resolver) ( context.Context,  sourceSpec) (loadedSource, error) {
	switch .kind {
	case sourceLocal:
		return .loadLocal(, )
	case sourceHTTP:
		return .loadHTTP(, )
	case sourceData:
		return .loadData(, )
	default:
		return loadedSource{}, errors.New("unknown source kind")
	}
}

func ( *Resolver) ( context.Context,  sourceSpec) ( loadedSource,  error) {
	if  := checkContext();  != nil {
		return loadedSource{}, 
	}
	,  := .localFiles.Open(.path)
	if  != nil {
		if  := .Err();  != nil {
			 = 
		} else if errors.Is(, os.ErrNotExist) {
			 = unavailable()
		}
		return loadedSource{}, fmt.Errorf("open local file %q: %w", .path, )
	}
	defer func() {
		if  := .Close();  != nil {
			 = errors.Join(, fmt.Errorf("close local file %q: %w", .path, ))
		}
	}()
	,  := .Stat()
	if  != nil {
		return loadedSource{}, fmt.Errorf("stat opened local file %q: %w", .path, )
	}
	if !.Mode().IsRegular() {
		return loadedSource{}, fmt.Errorf("opened local path %q is not a regular file", .path)
	}
	if .Size() > .limits.MaxFetchedBytes {
		return loadedSource{}, &LimitError{Name: "fetched bytes", Actual: .Size(), Limit: .limits.MaxFetchedBytes}
	}
	,  := readBounded(, , .limits.MaxFetchedBytes, "fetched bytes")
	if  != nil {
		return loadedSource{}, 
	}
	if int64(len()) > .limits.MaxDecompressedBytes {
		return loadedSource{}, &LimitError{Name: "decompressed bytes", Actual: int64(len()), Limit: .limits.MaxDecompressedBytes}
	}
	 := mime.TypeByExtension(strings.ToLower(filepath.Ext(.path)))
	if strings.EqualFold(filepath.Ext(.path), ".svgz") {
		 = "image/svg+xml"
	}
	return loadedSource{
		data:              ,
		mimeHint:          ,
		fetchedBytes:      int64(len()),
		decompressedBytes: int64(len()),
	}, nil
}

func ( *Resolver) ( context.Context,  sourceSpec) (loadedSource, error) {
	,  := context.WithTimeout(, time.Minute)
	defer ()
	,  := http.NewRequestWithContext(, http.MethodGet, .url, nil)
	if  != nil {
		return loadedSource{}, sanitizeError("create HTTP request failed", )
	}
	.Header.Set("User-Agent", "Mozilla/5.0 (compatible; D2 imageasset)")
	.Header.Set("Accept", "image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8")
	// Setting this explicitly prevents net/http from transparently handling only
	// gzip, allowing the same bounded path to handle gzip, deflate, and Brotli.
	.Header.Set("Accept-Encoding", "gzip, deflate, br")

	,  := .client.Do()
	if  != nil {
		var  *LimitError
		if  := .Err();  != nil {
			 = 
		} else if  == nil && !errors.As(, &) {
			// net/http returns both a response and an error only when the
			// caller's redirect policy rejects a request. Preserve that policy
			// error and transport-enforced limits instead of treating either as
			// transport unavailability.
			 = unavailable()
		}
		return loadedSource{}, sanitizeError("HTTP request failed", )
	}
	defer .Body.Close()
	if .StatusCode != http.StatusOK {
		if  := .Err();  != nil {
			return loadedSource{}, 
		}
		// A custom transport can supply an arbitrary Status string. Report only
		// the numeric code so an untrusted reason phrase cannot reach diagnostics.
		return loadedSource{}, unavailable(fmt.Errorf("expected status 200 but got status code %d", .StatusCode))
	}
	if .ContentLength > .limits.MaxFetchedBytes {
		return loadedSource{}, &LimitError{Name: "fetched bytes", Actual: .ContentLength, Limit: .limits.MaxFetchedBytes}
	}
	,  := selectContentType(, .Header.Values("Content-Type"))
	if  != nil {
		return loadedSource{}, 
	}
	,  := parseContentEncodings(, .Header.Values("Content-Encoding"))
	if  != nil {
		return loadedSource{}, 
	}
	,  := readBounded(, .Body, .limits.MaxFetchedBytes, "fetched bytes")
	if  != nil {
		var  *LimitError
		if  := .Err();  != nil {
			 = 
		} else if !errors.As(, &) {
			 = unavailable()
		}
		return loadedSource{}, sanitizeURLError("read HTTP response body failed", )
	}
	,  := decodeContentEncodings(, , , .limits.MaxDecompressedBytes)
	if  != nil {
		return loadedSource{}, 
	}
	return loadedSource{
		data:              ,
		mimeHint:          ,
		fetchedBytes:      int64(len()),
		decompressedBytes: int64(len()),
	}, nil
}

type sanitizedError struct {
	message  string
	original error
}

func ( *sanitizedError) () string { return .message }

// Is preserves sentinel matching without exposing a URL-bearing original
// error through Unwrap or errors.As.
func ( *sanitizedError) ( error) bool { return errors.Is(.original, ) }

func sanitizeError( string,  error) error {
	return &sanitizedError{message: , original: }
}

func sanitizeURLError( string,  error) error {
	var  *url.Error
	if errors.As(, &) {
		return sanitizeError(, )
	}
	return 
}

func ( *Resolver) ( context.Context,  sourceSpec) (loadedSource, error) {
	, ,  := strings.Cut(.raw[len("data:"):], ",")
	if ! {
		return loadedSource{}, errors.New("data URI has no comma separator")
	}
	if int64(len()) > .limits.MaxFetchedBytes {
		return loadedSource{}, &LimitError{Name: "fetched bytes", Actual: int64(len()), Limit: .limits.MaxFetchedBytes}
	}
	 := strings.Split(, ";")
	 := strings.TrimSpace([0])
	 := false
	for ,  := range [1:] {
		if strings.EqualFold(strings.TrimSpace(), "base64") {
			if  ||  != len([1:])-1 {
				return loadedSource{}, errors.New("data URI base64 marker must occur once and last")
			}
			 = true
		}
	}
	 := .limits.MaxFetchedBytes
	if ! && .limits.MaxDecompressedBytes <  {
		 = .limits.MaxDecompressedBytes
	}
	,  := readBounded(, &percentDecodeReader{source: }, , "data URI decoded payload bytes")
	if  != nil {
		return loadedSource{}, fmt.Errorf("decode data URI escapes: %w", )
	}
	var  []byte
	if  {
		 := base64.StdEncoding
		 := 0
		for ,  := range  {
			if %(32*1024) == 0 {
				if  := checkContext();  != nil {
					return loadedSource{}, 
				}
			}
			if  != '\r' &&  != '\n' {
				++
			}
		}
		if %4 != 0 {
			 = base64.RawStdEncoding
		}
		 := .limits.MaxFetchedBytes
		if .limits.MaxDecompressedBytes <  {
			 = .limits.MaxDecompressedBytes
		}
		,  = readBounded(, base64.NewDecoder(, bytes.NewReader()), , "decompressed bytes")
		if  != nil {
			return loadedSource{}, fmt.Errorf("decode data URI base64: %w", )
		}
	} else {
		 = 
	}
	if int64(len()) > .limits.MaxDecompressedBytes {
		return loadedSource{}, &LimitError{Name: "decompressed bytes", Actual: int64(len()), Limit: .limits.MaxDecompressedBytes}
	}
	return loadedSource{
		data:              ,
		mimeHint:          ,
		fetchedBytes:      int64(len()),
		decompressedBytes: int64(len()),
	}, nil
}

type percentDecodeReader struct {
	source string
	offset int
}

func ( *percentDecodeReader) ( []byte) (int, error) {
	if len() == 0 {
		return 0, nil
	}
	 := 0
	for  < len() && .offset < len(.source) {
		 := .source[.offset]
		if  != '%' {
			[] = 
			.offset++
			++
			continue
		}
		if len(.source)-.offset < 3 {
			return , errors.New("incomplete percent escape")
		}
		,  := hexNibble(.source[.offset+1])
		,  := hexNibble(.source[.offset+2])
		if ! || ! {
			return , errors.New("invalid percent escape")
		}
		[] = <<4 | 
		.offset += 3
		++
	}
	if  == 0 && .offset == len(.source) {
		return 0, io.EOF
	}
	return , nil
}

func hexNibble( byte) (byte, bool) {
	switch {
	case  >= '0' &&  <= '9':
		return  - '0', true
	case  >= 'a' &&  <= 'f':
		return  - 'a' + 10, true
	case  >= 'A' &&  <= 'F':
		return  - 'A' + 10, true
	default:
		return 0, false
	}
}

func checkByteLimits( []byte,  Limits) error {
	 := int64(len())
	if  > .MaxEncodedBytes {
		return &LimitError{Name: "encoded bytes", Actual: , Limit: .MaxEncodedBytes}
	}
	if  > .MaxDecompressedBytes {
		return &LimitError{Name: "decompressed bytes", Actual: , Limit: .MaxDecompressedBytes}
	}
	return nil
}

func ( *Resolver) ( *Resource) error {
	if len(.data) == 0 {
		return errors.New("cached resource has no data")
	}
	switch .kind {
	case KindRaster:
		if .mimeType != "image/png" && .mimeType != "image/jpeg" &&
			.mimeType != "image/gif" && .mimeType != "image/webp" {
			return fmt.Errorf("cached raster has invalid MIME type %q", .mimeType)
		}
	case KindSVG:
		if .mimeType != "image/svg+xml" || .pixelWidth != 0 || .pixelHeight != 0 {
			return errors.New("cached SVG metadata is invalid")
		}
	default:
		return fmt.Errorf("cached resource has invalid kind %d", .kind)
	}
	if .fetchedBytes <= 0 || .fetchedBytes > .limits.MaxFetchedBytes {
		return &LimitError{Name: "fetched bytes", Actual: .fetchedBytes, Limit: .limits.MaxFetchedBytes}
	}
	if .decompressedBytes != int64(len(.data)) {
		return errors.New("cached resource decompressed-byte metadata is invalid")
	}
	if int64(len(.data)) > .limits.MaxEncodedBytes {
		return &LimitError{Name: "encoded bytes", Actual: int64(len(.data)), Limit: .limits.MaxEncodedBytes}
	}
	if .decompressedBytes > .limits.MaxDecompressedBytes {
		return &LimitError{Name: "decompressed bytes", Actual: .decompressedBytes, Limit: .limits.MaxDecompressedBytes}
	}
	if .kind == KindSVG {
		,  := svgByteLimit(.limits)
		if int64(len(.data)) >  {
			return &LimitError{Name: , Actual: int64(len(.data)), Limit: }
		}
	}
	if .kind == KindRaster {
		if .pixelWidth <= 0 || .pixelHeight <= 0 ||
			int64(.pixelWidth) > int64(^uint64(0)>>1)/int64(.pixelHeight) {
			return errors.New("cached raster dimension metadata is invalid")
		}
		 := int64(.pixelWidth) * int64(.pixelHeight)
		if  > int64(^uint64(0)>>1)/8 ||
			.decodedBytes != *4 && .decodedBytes != *8 {
			return errors.New("cached raster decoded-byte metadata is invalid")
		}
	} else if .decodedBytes != int64(len(.data)) {
		return errors.New("cached SVG decoded-byte metadata is invalid")
	}
	return validateDecodedLimits(.pixelWidth, .pixelHeight, .decodedBytes, .kind, .limits)
}

// reserve atomically claims aggregate count, retained encoded bytes, and
// decoded memory before validation allocates a full raster or immutable copy.
// The returned rollback is idempotent and exact.
func ( *Resolver) (,  int64) (func(), error) {
	.mu.Lock()
	defer .mu.Unlock()
	if .assetCount >= .limits.MaxAssets {
		 := int64(.assetCount)
		if  < int64(^uint64(0)>>1) {
			++
		}
		return nil, &LimitError{Name: "assets", Actual: , Limit: int64(.limits.MaxAssets)}
	}
	if  > .limits.MaxCumulativeEncodedBytes-.cumulativeEncodedBytes {
		 := saturatedAdd(.cumulativeEncodedBytes, )
		return nil, &LimitError{Name: "cumulative encoded bytes", Actual: , Limit: .limits.MaxCumulativeEncodedBytes}
	}
	if  > .limits.MaxCumulativeDecodedBytes-.cumulativeDecodedBytes {
		 := saturatedAdd(.cumulativeDecodedBytes, )
		return nil, &LimitError{Name: "cumulative decoded bytes", Actual: , Limit: .limits.MaxCumulativeDecodedBytes}
	}
	.assetCount++
	.cumulativeEncodedBytes += 
	.cumulativeDecodedBytes += 
	 := false
	return func() {
		.mu.Lock()
		defer .mu.Unlock()
		if  {
			return
		}
		 = true
		.assetCount--
		.cumulativeEncodedBytes -= 
		.cumulativeDecodedBytes -= 
	}, nil
}

func saturatedAdd(,  int64) int64 {
	 := int64(^uint64(0) >> 1)
	if  > - {
		return 
	}
	return  + 
}

type contextReader struct {
	ctx context.Context
	r   io.Reader
}

func ( contextReader) ( []byte) (int, error) {
	if  := checkContext(.ctx);  != nil {
		return 0, 
	}
	,  := .r.Read()
	if  == nil {
		if  := checkContext(.ctx);  != nil {
			return , 
		}
	}
	return , 
}

func readBounded( context.Context,  io.Reader,  int64,  string) ([]byte, error) {
	var  bytes.Buffer
	 := make([]byte, 32*1024)
	 = contextReader{ctx: , r: }
	var  int64
	 := 0
	for {
		,  := .Read()
		if  > 0 {
			 = 0
			if int64() > - {
				return nil, &LimitError{Name: , Actual: saturatedAdd(, int64()), Limit: }
			}
			_, _ = .Write([:])
			 += int64()
		}
		if  != nil {
			if  == io.EOF {
				return .Bytes(), nil
			}
			return nil, 
		}
		if  == 0 {
			++
			if  >= 100 {
				return nil, io.ErrNoProgress
			}
		}
	}
}

func selectContentType( context.Context,  []string) (string, error) {
	 := 0
	 := ""
	for ,  := range  {
		if  := checkContext();  != nil {
			return "", 
		}
		if len() > maxContentTypeHeaderBytes- {
			return "", &LimitError{Name: "Content-Type header bytes", Actual: int64( + len()), Limit: maxContentTypeHeaderBytes}
		}
		 += len()
		 = strings.TrimSpace()
		if  == "" {
			continue
		}
		if  != "" {
			return "", errors.New("multiple Content-Type header values")
		}
		 = 
	}
	return , nil
}

func parseContentEncodings( context.Context,  []string) ([]string, error) {
	if len() == 0 {
		return nil, nil
	}
	 := 0
	 := make([]string, 0, maxContentEncodingTokens)
	for ,  := range  {
		if len() > maxContentEncodingHeaderBytes- {
			return nil, &LimitError{Name: "Content-Encoding header bytes", Actual: int64( + len()), Limit: maxContentEncodingHeaderBytes}
		}
		 += len()
		 := 0
		for {
			if  := checkContext();  != nil {
				return nil, 
			}
			 := 
			for  < len() && [] != ',' {
				if %(32*1024) == 0 {
					if  := checkContext();  != nil {
						return nil, 
					}
				}
				++
			}
			 := strings.TrimSpace([:])
			if  == "" {
				return nil, errors.New("Content-Encoding contains an empty token")
			}
			,  := canonicalContentEncoding()
			if  != nil {
				return nil, 
			}
			if len() == maxContentEncodingTokens {
				return nil, fmt.Errorf("content encoding layer count %d exceeds limit %d", len()+1, maxContentEncodingTokens)
			}
			 = append(, )
			if  == len() {
				break
			}
			 =  + 1
		}
	}
	return , nil
}

func canonicalContentEncoding( string) (string, error) {
	for ,  := range []string{"identity", "gzip", "x-gzip", "deflate", "br"} {
		if strings.EqualFold(, ) {
			return , nil
		}
	}
	return "", fmt.Errorf("unsupported content encoding %q", )
}

func decodeContentEncodings( context.Context,  []byte,  []string,  int64) ([]byte, error) {
	for  := len() - 1;  >= 0; -- {
		 := []
		if  == "identity" {
			continue
		}
		var  io.ReadCloser
		 := contextReader{ctx: , r: bytes.NewReader()}
		switch  {
		case "gzip", "x-gzip":
			,  := gzip.NewReader()
			if  != nil {
				return nil, fmt.Errorf("decode %s content encoding: %w", , )
			}
			 = 
		case "deflate":
			,  := zlib.NewReader()
			if  == nil {
				 = 
			} else {
				if  := checkContext();  != nil {
					return nil, fmt.Errorf("decode %s content encoding: %w", , )
				}
				 = flate.NewReader(contextReader{ctx: , r: bytes.NewReader()})
			}
		case "br":
			 = io.NopCloser(brotli.NewReader())
		default:
			return nil, fmt.Errorf("unsupported canonical content encoding %q", )
		}
		,  := readBounded(, , , "decompressed bytes")
		 := .Close()
		if  != nil {
			return nil, fmt.Errorf("decode %s content encoding: %w", , )
		}
		if  != nil {
			return nil, fmt.Errorf("decode %s content encoding: %w", , )
		}
		 = 
	}
	if int64(len()) >  {
		return nil, &LimitError{Name: "decompressed bytes", Actual: int64(len()), Limit: }
	}
	return , nil
}