package imageasset

import (
	
	
	
	
	
	
	
	
	
	
	_ 
	_ 
	_ 
	
	
	
	
	

	
	_ 
	

	
	libsvg 
)

var errUnsupportedSVG = errors.New("unsupported SVG")

// XML nesting receives an independent safety ceiling in addition to the
// caller's byte-derived element, attribute, and token budgets. encoding/xml
// itself is iterative, but downstream SVG consumers commonly retain ancestry.
const maxSVGXMLDepth = 256

type reserveResource func(encodedBytes, decodedBytes int64) (rollback func(), err error)

func probeResource( context.Context,  loadedSource,  Limits,  reserveResource) (*Resource, error) {
	if len(.data) == 0 {
		return nil, errors.New("empty image resource")
	}
	if  := checkContext();  != nil {
		return nil, 
	}
	 := normalizeMIME(.mimeHint)
	 := rasterSignature(.data)
	if  != "" {
		if  := checkByteLimits(.data, );  != nil {
			return nil, 
		}
		return probeRaster(, , , , )
	}
	,  := looksLikeXML(, .data)
	if  != nil {
		return nil, 
	}
	if  == "image/svg+xml" ||  {
		var  error
		if  {
			,  := svgByteLimit()
			if int64(len(.data)) >  {
				 = &LimitError{Name: , Actual: int64(len(.data)), Limit: }
			} else {
				 = validateSVG(, .data, )
			}
		} else {
			 = errors.New("not raw SVG XML")
		}
		if  == nil {
			if  := checkByteLimits(.data, );  != nil {
				return nil, 
			}
			 := int64(len(.data))
			if  := validateDecodedLimits(0, 0, , KindSVG, );  != nil {
				return nil, 
			}
			return svgResource(, , , )
		}
		if errors.Is(, context.Canceled) || errors.Is(, context.DeadlineExceeded) {
			return nil, 
		}
		if errors.Is(, errUnsupportedSVG) {
			return nil, 
		}
		var  *LimitError
		if errors.As(, &) &&  != "image/svg+xml" {
			return nil, 
		}
		if  == "image/svg+xml" {
			,  := expandDeclaredSVG(, , )
			if  != nil {
				if errors.Is(, errUnsupportedSVG) {
					return nil, 
				}
				if  != nil {
					return nil, 
				}
				if  {
					return nil, fmt.Errorf("malformed SVG: %w", )
				}
				return nil, fmt.Errorf("malformed SVG (raw validation failed: %v; compressed fallback failed): %w", , )
			}
			 := int64(len(.data))
			if  := validateDecodedLimits(0, 0, , KindSVG, );  != nil {
				return nil, 
			}
			return svgResource(, , , )
		}
	}
	if  := checkByteLimits(.data, );  != nil {
		return nil, 
	}
	if  != "" {
		return nil, fmt.Errorf("unsupported or malformed image with MIME type %q", )
	}
	return nil, fmt.Errorf("unsupported or malformed image (sniffed %q)", normalizeMIME(http.DetectContentType(.data)))
}

func svgResource( context.Context,  loadedSource,  int64,  reserveResource) ( *Resource,  error) {
	if  := checkContext();  != nil {
		return nil, 
	}
	,  := (int64(len(.data)), )
	if  != nil {
		return nil, 
	}
	 := false
	defer func() {
		if ! {
			()
		}
	}()
	 := newResource(KindSVG, "image/svg+xml", , 0, 0, )
	if  := checkContext();  != nil {
		return nil, 
	}
	 = true
	return , nil
}

type compressedSVGCodec struct {
	name string
	open func(context.Context, []byte) (io.ReadCloser, error)
}

func expandDeclaredSVG( context.Context,  loadedSource,  Limits) (loadedSource, error) {
	 := []compressedSVGCodec{
		{name: "gzip", open: func( context.Context,  []byte) (io.ReadCloser, error) {
			return gzip.NewReader(contextReader{ctx: , r: bytes.NewReader()})
		}},
		{name: "zlib", open: func( context.Context,  []byte) (io.ReadCloser, error) {
			return zlib.NewReader(contextReader{ctx: , r: bytes.NewReader()})
		}},
		{name: "raw deflate", open: func( context.Context,  []byte) (io.ReadCloser, error) {
			return flate.NewReader(contextReader{ctx: , r: bytes.NewReader()}), nil
		}},
		{name: "brotli", open: func( context.Context,  []byte) (io.ReadCloser, error) {
			return io.NopCloser(brotli.NewReader(contextReader{ctx: , r: bytes.NewReader()})), nil
		}},
	}
	var  error
	var  error
	 := "none"
	for ,  := range  {
		if  := checkContext();  != nil {
			return loadedSource{}, 
		}
		 = .name
		,  := .open(, .data)
		if  != nil {
			if  := checkContext();  != nil {
				return loadedSource{}, 
			}
			 = 
			continue
		}
		,  := svgByteLimit()
		,  := readBounded(, , , )
		 := .Close()
		if  != nil {
			if errors.Is(, context.Canceled) || errors.Is(, context.DeadlineExceeded) {
				return loadedSource{}, 
			}
			var  *LimitError
			if  == nil && errors.As(, &) {
				 = 
			}
			 = 
			continue
		}
		if  != nil {
			 = 
			continue
		}
		if int64(len()) > .MaxEncodedBytes {
			 := &LimitError{Name: "encoded bytes", Actual: int64(len()), Limit: .MaxEncodedBytes}
			if  == nil {
				 = 
			}
			 = 
			continue
		}
		,  := svgByteLimit()
		if  := validateSVG(, , );  != nil {
			if errors.Is(, context.Canceled) || errors.Is(, context.DeadlineExceeded) {
				return loadedSource{}, 
			}
			if errors.Is(, errUnsupportedSVG) {
				return loadedSource{}, 
			}
			var  *LimitError
			if errors.As(, &) {
				return loadedSource{}, 
			}
			 = 
			continue
		}
		.data = 
		.decompressedBytes = int64(len())
		return , nil
	}
	if  != nil {
		return loadedSource{}, 
	}
	if  == nil {
		 = errors.New("no compressed data")
	}
	return loadedSource{}, fmt.Errorf("no supported codec produced SVG XML (last codec %s): %w", , )
}

func probeRaster( context.Context,  loadedSource,  string,  Limits,  reserveResource) (*Resource, error) {
	, ,  := rasterimage.Config(, .data, )
	if  != nil {
		return nil, fmt.Errorf("malformed %s image: %w", strings.ToUpper(), )
	}
	return rasterResource(, , , , , )
}

func rasterResource( context.Context,  loadedSource,  image.Config,  string,  Limits,  reserveResource) ( *Resource,  error) {
	if  := checkContext();  != nil {
		return nil, 
	}
	if .Width <= 0 || .Height <= 0 {
		return nil, fmt.Errorf("invalid %s dimensions %dx%d", , .Width, .Height)
	}
	if int64(.Width) > math.MaxInt64/int64(.Height) {
		return nil, errors.New("decoded pixel count overflows int64")
	}
	 := int64(.Width) * int64(.Height)
	 := decodedBytesPerPixel()
	if  > math.MaxInt64/ {
		return nil, errors.New("decoded byte count overflows int64")
	}
	 :=  * 
	if  := validateDecodedLimits(.Width, .Height, , KindRaster, );  != nil {
		return nil, 
	}
	,  := (int64(len(.data)), )
	if  != nil {
		return nil, 
	}
	 := false
	defer func() {
		if ! {
			()
		}
	}()
	// Config has already performed bounded format-specific structure checks and
	// decoded the logical dimensions. Pixel entropy is decoded once, by the
	// renderer that consumes the retained resource.
	if  := checkContext();  != nil {
		return nil, 
	}
	,  := mimeForFormat()
	if ! {
		return nil, fmt.Errorf("unsupported raster format %q", )
	}
	 := newResource(KindRaster, , , .Width, .Height, )
	if  := checkContext();  != nil {
		return nil, 
	}
	 = true
	return , nil
}

func mimeForFormat( string) (string, bool) {
	switch  {
	case "png":
		return "image/png", true
	case "jpeg":
		return "image/jpeg", true
	case "gif":
		return "image/gif", true
	case "webp":
		return "image/webp", true
	default:
		return "", false
	}
}

func decodedBytesPerPixel( image.Config) int64 {
	if .ColorModel == nil {
		return 8
	}
	// Go's PNG decoder can allocate 16-bit-per-channel images. Conservatively
	// charge eight bytes per pixel for any 16-bit model; static 8-bit formats
	// are charged at four bytes even when their concrete representation is
	// smaller (for example GIF palettes or JPEG YCbCr planes).
	 := .ColorModel.Convert(color.RGBA64{R: 0x1234, G: 0x5678, B: 0x9abc, A: 0xffff})
	switch .(type) {
	case color.RGBA64, color.NRGBA64, color.Gray16, color.Alpha16:
		return 8
	default:
		return 4
	}
}

func newResource( Kind,  string,  loadedSource, ,  int,  int64) *Resource {
	return &Resource{
		kind:              ,
		mimeType:          ,
		data:              append([]byte(nil), .data...),
		pixelWidth:        ,
		pixelHeight:       ,
		fetchedBytes:      .fetchedBytes,
		decompressedBytes: .decompressedBytes,
		decodedBytes:      ,
	}
}

func validateDecodedLimits(,  int,  int64,  Kind,  Limits) error {
	if  == KindRaster {
		if  > .MaxDecodedWidth {
			return &LimitError{Name: "decoded width", Actual: int64(), Limit: int64(.MaxDecodedWidth)}
		}
		if  > .MaxDecodedHeight {
			return &LimitError{Name: "decoded height", Actual: int64(), Limit: int64(.MaxDecodedHeight)}
		}
		if  <= 0 ||  <= 0 || int64() > math.MaxInt64/int64() {
			return errors.New("decoded pixel count overflows int64")
		}
		 := int64() * int64()
		if  > .MaxDecodedPixels {
			return &LimitError{Name: "decoded pixels", Actual: , Limit: .MaxDecodedPixels}
		}
	}
	if  > .MaxCumulativeDecodedBytes {
		return &LimitError{Name: "decoded bytes", Actual: , Limit: .MaxCumulativeDecodedBytes}
	}
	return nil
}

func normalizeMIME( string) string {
	 = strings.TrimSpace()
	if  == "" {
		return ""
	}
	, ,  := mime.ParseMediaType()
	if  == nil {
		 = 
	} else if  := strings.IndexByte(, ';');  >= 0 {
		 = [:]
	}
	 = strings.ToLower(strings.TrimSpace())
	switch  {
	case "image/jpg", "image/pjpeg":
		return "image/jpeg"
	case "image/x-png":
		return "image/png"
	case "text/xml", "application/xml", "application/svg+xml":
		return "image/svg+xml"
	default:
		return 
	}
}

func rasterSignature( []byte) string {
	switch {
	case len() >= 8 && bytes.Equal([:8], []byte("\x89PNG\r\n\x1a\n")):
		return "png"
	case len() >= 3 && [0] == 0xff && [1] == 0xd8 && [2] == 0xff:
		return "jpeg"
	case len() >= 6 && (bytes.Equal([:6], []byte("GIF87a")) || bytes.Equal([:6], []byte("GIF89a"))):
		return "gif"
	case len() >= 12 && bytes.Equal([:4], []byte("RIFF")) && bytes.Equal([8:12], []byte("WEBP")):
		return "webp"
	default:
		return ""
	}
}

func looksLikeXML( context.Context,  []byte) (bool, error) {
	 := 0
	if bytes.HasPrefix(, []byte{0xef, 0xbb, 0xbf}) {
		 = 3
	}
	for  < len() {
		if %(32*1024) == 0 {
			if  := checkContext();  != nil {
				return false, 
			}
		}
		switch [] {
		case ' ', '\t', '\r', '\n':
			++
			continue
		default:
			return [] == '<', nil
		}
	}
	return false, checkContext()
}

// validateSVG only parses the supplied bytes. Exact SVG 1.0 and 1.1 external
// doctypes are recognized and ignored. Their external subsets are not fetched,
// so DTD entities and default attributes are not applied.
type svgValidationLimits struct {
	bytes          int64
	depth          int
	elements       int64
	attributes     int64
	attributeBytes int64
	tokens         int64
}

func svgByteLimit( Limits) (int64, string) {
	 := .MaxSVGBytes
	 := "SVG bytes"
	if .MaxEncodedBytes <  {
		 = .MaxEncodedBytes
		 = "encoded bytes"
	}
	if .MaxDecompressedBytes <  {
		 = .MaxDecompressedBytes
		 = "decompressed bytes"
	}
	return , 
}

func validationLimitsForSVG( int64) svgValidationLimits {
	// The shortest element, attribute, and distinct XML token encodings consume
	// multiple source bytes. These deliberately generous derived ceilings make
	// every parser work dimension finite without imposing importer semantics.
	return svgValidationLimits{
		bytes: , depth: maxSVGXMLDepth,
		elements: /3 + 1, attributes: /3 + 1,
		attributeBytes: , tokens: /2 + 2,
	}
}

func validateSVG( context.Context,  []byte,  int64) error {
	return validateSVGWithLimits(, , validationLimitsForSVG())
}

func validateSVGWithLimits( context.Context,  []byte,  svgValidationLimits) error {
	if int64(len()) > .bytes {
		return &LimitError{Name: "SVG bytes", Actual: int64(len()), Limit: .bytes}
	}
	 = bytes.TrimPrefix(, []byte{0xef, 0xbb, 0xbf})
	 := xml.NewDecoder(contextReader{ctx: , r: bytes.NewReader()})
	.Strict = true
	.CharsetReader = iso88591XMLReader
	 := 0
	 := false
	 := false
	 := false
	 := false
	 := true
	var  int64
	var  int64
	var  int64
	var  int64
	for {
		if  := checkContext();  != nil {
			return 
		}
		,  := .Token()
		if  == io.EOF {
			if ! || ! ||  != 0 {
				return errors.New("missing or unclosed svg root element")
			}
			return nil
		}
		if  != nil {
			if errors.Is(, context.Canceled) || errors.Is(, context.DeadlineExceeded) {
				return 
			}
			return errors.New("malformed SVG XML")
		}
		++
		if  > .tokens {
			return &LimitError{Name: "SVG XML tokens", Actual: , Limit: .tokens}
		}
		switch token := .(type) {
		case xml.StartElement:
			 = false
			++
			if  > .elements {
				return &LimitError{Name: "SVG elements", Actual: , Limit: .elements}
			}
			if int64(len(.Attr)) > .attributes- {
				return &LimitError{Name: "SVG attributes", Actual:  + int64(len(.Attr)), Limit: .attributes}
			}
			 += int64(len(.Attr))
			for ,  := range .Attr {
				 := int64(len(.Name.Space) + len(.Name.Local) + len(.Value))
				if  > .attributeBytes- {
					return &LimitError{Name: "SVG attribute bytes", Actual:  + , Limit: .attributeBytes}
				}
				 += 
			}
			if  == 0 {
				if  {
					return errors.New("multiple root elements")
				}
				if .Name.Local != "svg" {
					return errors.New("root element is not svg")
				}
				if .Name.Space != "" && .Name.Space != "http://www.w3.org/2000/svg" {
					return errors.New("svg root has an unexpected namespace")
				}
				 = true
			}
			if  {
				return errors.New("element after svg root")
			}
			++
			if  > .depth {
				return &LimitError{Name: "SVG XML depth", Actual: int64(), Limit: int64(.depth)}
			}
		case xml.EndElement:
			 = false
			--
			if  < 0 {
				return errors.New("unexpected closing element")
			}
			if  == 0 {
				 = true
			}
		case xml.Directive:
			if  ||  || !libsvg.IsSupportedExternalDoctype(, ) {
				return fmt.Errorf("%w: XML directive or DTD", errUnsupportedSVG)
			}
			 = true
			 = false
		case xml.ProcInst:
			if .Target != "xml" ||  || ! ||  {
				return errors.New("processing instruction is not allowed")
			}
			 = true
			 = false
		case xml.CharData:
			if  == 0 && len(bytes.TrimSpace()) != 0 {
				return errors.New("text outside svg root element")
			}
			if len() != 0 {
				 = false
			}
		case xml.Comment:
			 = false
		}
	}
}

func iso88591XMLReader( string,  io.Reader) (io.Reader, error) {
	if !strings.EqualFold(, "iso-8859-1") {
		return nil, errors.New("unsupported XML character encoding")
	}
	return charmap.ISO8859_1.NewDecoder().Reader(), nil
}