// Copyright 2011 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 template

import (
	
	
)

// urlFilter returns its input unless it contains an unsafe scheme in which
// case it defangs the entire URL.
//
// Schemes that cause unintended side effects that are irreversible without user
// interaction are considered unsafe. For example, clicking on a "javascript:"
// link can immediately trigger JavaScript code execution.
//
// This filter conservatively assumes that all schemes other than the following
// are unsafe:
//   - http:   Navigates to a new website, and may open a new window or tab.
//     These side effects can be reversed by navigating back to the
//     previous website, or closing the window or tab. No irreversible
//     changes will take place without further user interaction with
//     the new website.
//   - https:  Same as http.
//   - mailto: Opens an email program and starts a new draft. This side effect
//     is not irreversible until the user explicitly clicks send; it
//     can be undone by closing the email program.
//
// To allow URLs containing other schemes to bypass this filter, developers must
// explicitly indicate that such a URL is expected and safe by encapsulating it
// in a template.URL value.
func urlFilter( ...any) string {
	,  := stringify(...)
	if  == contentTypeURL {
		return 
	}
	if !isSafeURL() {
		return "#" + filterFailsafe
	}
	return 
}

// isSafeURL is true if s is a relative URL or if URL has a protocol in
// (http, https, mailto).
func isSafeURL( string) bool {
	if , ,  := strings.Cut(, ":");  && !strings.Contains(, "/") {
		if !strings.EqualFold(, "http") && !strings.EqualFold(, "https") && !strings.EqualFold(, "mailto") {
			return false
		}
	}
	return true
}

// urlEscaper produces an output that can be embedded in a URL query.
// The output can be embedded in an HTML attribute without further escaping.
func urlEscaper( ...any) string {
	return urlProcessor(false, ...)
}

// urlNormalizer normalizes URL content so it can be embedded in a quote-delimited
// string or parenthesis delimited url(...).
// The normalizer does not encode all HTML specials. Specifically, it does not
// encode '&' so correct embedding in an HTML attribute requires escaping of
// '&' to '&'.
func urlNormalizer( ...any) string {
	return urlProcessor(true, ...)
}

// urlProcessor normalizes (when norm is true) or escapes its input to produce
// a valid hierarchical or opaque URL part.
func urlProcessor( bool,  ...any) string {
	,  := stringify(...)
	if  == contentTypeURL {
		 = true
	}
	var  strings.Builder
	if processURLOnto(, , &) {
		return .String()
	}
	return 
}

// processURLOnto appends a normalized URL corresponding to its input to b
// and reports whether the appended content differs from s.
func processURLOnto( string,  bool,  *strings.Builder) bool {
	.Grow(len() + 16)
	 := 0
	// The byte loop below assumes that all URLs use UTF-8 as the
	// content-encoding. This is similar to the URI to IRI encoding scheme
	// defined in section 3.1 of  RFC 3987, and behaves the same as the
	// EcmaScript builtin encodeURIComponent.
	// It should not cause any misencoding of URLs in pages with
	// Content-type: text/html;charset=UTF-8.
	for ,  := 0, len();  < ; ++ {
		 := []
		switch  {
		// Single quote and parens are sub-delims in RFC 3986, but we
		// escape them so the output can be embedded in single
		// quoted attributes and unquoted CSS url(...) constructs.
		// Single quotes are reserved in URLs, but are only used in
		// the obsolete "mark" rule in an appendix in RFC 3986
		// so can be safely encoded.
		case '!', '#', '$', '&', '*', '+', ',', '/', ':', ';', '=', '?', '@', '[', ']':
			if  {
				continue
			}
		// Unreserved according to RFC 3986 sec 2.3
		// "For consistency, percent-encoded octets in the ranges of
		// ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D),
		// period (%2E), underscore (%5F), or tilde (%7E) should not be
		// created by URI producers
		case '-', '.', '_', '~':
			continue
		case '%':
			// When normalizing do not re-encode valid escapes.
			if  && +2 < len() && isHex([+1]) && isHex([+2]) {
				continue
			}
		default:
			// Unreserved according to RFC 3986 sec 2.3
			if 'a' <=  &&  <= 'z' {
				continue
			}
			if 'A' <=  &&  <= 'Z' {
				continue
			}
			if '0' <=  &&  <= '9' {
				continue
			}
		}
		.WriteString([:])
		fmt.Fprintf(, "%%%02x", )
		 =  + 1
	}
	.WriteString([:])
	return  != 0
}

// Filters and normalizes srcset values which are comma separated
// URLs followed by metadata.
func srcsetFilterAndEscaper( ...any) string {
	,  := stringify(...)
	switch  {
	case contentTypeSrcset:
		return 
	case contentTypeURL:
		// Normalizing gets rid of all HTML whitespace
		// which separate the image URL from its metadata.
		var  strings.Builder
		if processURLOnto(, true, &) {
			 = .String()
		}
		// Additionally, commas separate one source from another.
		return strings.ReplaceAll(, ",", "%2c")
	}

	var  strings.Builder
	 := 0
	for  := 0;  < len(); ++ {
		if [] == ',' {
			filterSrcsetElement(, , , &)
			.WriteString(",")
			 =  + 1
		}
	}
	filterSrcsetElement(, , len(), &)
	return .String()
}

// Derived from https://play.golang.org/p/Dhmj7FORT5
const htmlSpaceAndASCIIAlnumBytes = "\x00\x36\x00\x00\x01\x00\xff\x03\xfe\xff\xff\x07\xfe\xff\xff\x07"

// isHTMLSpace is true iff c is a whitespace character per
// https://infra.spec.whatwg.org/#ascii-whitespace
func isHTMLSpace( byte) bool {
	return ( <= 0x20) && 0 != (htmlSpaceAndASCIIAlnumBytes[>>3]&(1<<uint(&0x7)))
}

func isHTMLSpaceOrASCIIAlnum( byte) bool {
	return ( < 0x80) && 0 != (htmlSpaceAndASCIIAlnumBytes[>>3]&(1<<uint(&0x7)))
}

func filterSrcsetElement( string,  int,  int,  *strings.Builder) {
	 := 
	for  <  && isHTMLSpace([]) {
		++
	}
	 := 
	for  := ;  < ; ++ {
		if isHTMLSpace([]) {
			 = 
			break
		}
	}
	if  := [:]; isSafeURL() {
		// If image metadata is only spaces or alnums then
		// we don't need to URL normalize it.
		 := true
		for  := ;  < ; ++ {
			if !isHTMLSpaceOrASCIIAlnum([]) {
				 = false
				break
			}
		}
		if  {
			.WriteString([:])
			processURLOnto(, true, )
			.WriteString([:])
			return
		}
	}
	.WriteString("#")
	.WriteString(filterFailsafe)
}