| // 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 ( |
| "bytes" |
| "fmt" |
| "strings" |
| ) |
| |
| // 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(args ...interface{}) string { |
| s, t := stringify(args...) |
| if t == contentTypeURL { |
| return s |
| } |
| if i := strings.IndexRune(s, ':'); i >= 0 && !strings.ContainsRune(s[:i], '/') { |
| protocol := strings.ToLower(s[:i]) |
| if protocol != "http" && protocol != "https" && protocol != "mailto" { |
| return "#" + filterFailsafe |
| } |
| } |
| return s |
| } |
| |
| // 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(args ...interface{}) string { |
| return urlProcessor(false, args...) |
| } |
| |
| // 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(args ...interface{}) string { |
| return urlProcessor(true, args...) |
| } |
| |
| // urlProcessor normalizes (when norm is true) or escapes its input to produce |
| // a valid hierarchical or opaque URL part. |
| func urlProcessor(norm bool, args ...interface{}) string { |
| s, t := stringify(args...) |
| if t == contentTypeURL { |
| norm = true |
| } |
| var b bytes.Buffer |
| written := 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 i, n := 0, len(s); i < n; i++ { |
| c := s[i] |
| switch c { |
| // 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 norm { |
| 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 norm && i+2 < len(s) && isHex(s[i+1]) && isHex(s[i+2]) { |
| continue |
| } |
| default: |
| // Unreserved according to RFC 3986 sec 2.3 |
| if 'a' <= c && c <= 'z' { |
| continue |
| } |
| if 'A' <= c && c <= 'Z' { |
| continue |
| } |
| if '0' <= c && c <= '9' { |
| continue |
| } |
| } |
| b.WriteString(s[written:i]) |
| fmt.Fprintf(&b, "%%%02x", c) |
| written = i + 1 |
| } |
| if written == 0 { |
| return s |
| } |
| b.WriteString(s[written:]) |
| return b.String() |
| } |