github.com/filosottile/go@v0.0.0-20170906193555-dbed9972d994/src/html/template/url.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package template
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"strings"
    11  )
    12  
    13  // urlFilter returns its input unless it contains an unsafe protocol in which
    14  // case it defangs the entire URL.
    15  func urlFilter(args ...interface{}) string {
    16  	s, t := stringify(args...)
    17  	if t == contentTypeURL {
    18  		return s
    19  	}
    20  	if i := strings.IndexRune(s, ':'); i >= 0 && !strings.ContainsRune(s[:i], '/') {
    21  		protocol := strings.ToLower(s[:i])
    22  		if protocol != "http" && protocol != "https" && protocol != "mailto" {
    23  			return "#" + filterFailsafe
    24  		}
    25  	}
    26  	return s
    27  }
    28  
    29  // urlEscaper produces an output that can be embedded in a URL query.
    30  // The output can be embedded in an HTML attribute without further escaping.
    31  func urlEscaper(args ...interface{}) string {
    32  	return urlProcessor(false, args...)
    33  }
    34  
    35  // urlNormalizer normalizes URL content so it can be embedded in a quote-delimited
    36  // string or parenthesis delimited url(...).
    37  // The normalizer does not encode all HTML specials. Specifically, it does not
    38  // encode '&' so correct embedding in an HTML attribute requires escaping of
    39  // '&' to '&'.
    40  func urlNormalizer(args ...interface{}) string {
    41  	return urlProcessor(true, args...)
    42  }
    43  
    44  // urlProcessor normalizes (when norm is true) or escapes its input to produce
    45  // a valid hierarchical or opaque URL part.
    46  func urlProcessor(norm bool, args ...interface{}) string {
    47  	s, t := stringify(args...)
    48  	if t == contentTypeURL {
    49  		norm = true
    50  	}
    51  	var b bytes.Buffer
    52  	written := 0
    53  	// The byte loop below assumes that all URLs use UTF-8 as the
    54  	// content-encoding. This is similar to the URI to IRI encoding scheme
    55  	// defined in section 3.1 of  RFC 3987, and behaves the same as the
    56  	// EcmaScript builtin encodeURIComponent.
    57  	// It should not cause any misencoding of URLs in pages with
    58  	// Content-type: text/html;charset=UTF-8.
    59  	for i, n := 0, len(s); i < n; i++ {
    60  		c := s[i]
    61  		switch c {
    62  		// Single quote and parens are sub-delims in RFC 3986, but we
    63  		// escape them so the output can be embedded in single
    64  		// quoted attributes and unquoted CSS url(...) constructs.
    65  		// Single quotes are reserved in URLs, but are only used in
    66  		// the obsolete "mark" rule in an appendix in RFC 3986
    67  		// so can be safely encoded.
    68  		case '!', '#', '$', '&', '*', '+', ',', '/', ':', ';', '=', '?', '@', '[', ']':
    69  			if norm {
    70  				continue
    71  			}
    72  		// Unreserved according to RFC 3986 sec 2.3
    73  		// "For consistency, percent-encoded octets in the ranges of
    74  		// ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D),
    75  		// period (%2E), underscore (%5F), or tilde (%7E) should not be
    76  		// created by URI producers
    77  		case '-', '.', '_', '~':
    78  			continue
    79  		case '%':
    80  			// When normalizing do not re-encode valid escapes.
    81  			if norm && i+2 < len(s) && isHex(s[i+1]) && isHex(s[i+2]) {
    82  				continue
    83  			}
    84  		default:
    85  			// Unreserved according to RFC 3986 sec 2.3
    86  			if 'a' <= c && c <= 'z' {
    87  				continue
    88  			}
    89  			if 'A' <= c && c <= 'Z' {
    90  				continue
    91  			}
    92  			if '0' <= c && c <= '9' {
    93  				continue
    94  			}
    95  		}
    96  		b.WriteString(s[written:i])
    97  		fmt.Fprintf(&b, "%%%02x", c)
    98  		written = i + 1
    99  	}
   100  	if written == 0 {
   101  		return s
   102  	}
   103  	b.WriteString(s[written:])
   104  	return b.String()
   105  }