NormalizeURL normalizes url to be safely included as an href based on golang.org/pkg/html/template
(s string)
| 34 | // NormalizeURL normalizes url to be safely included as an href |
| 35 | // based on golang.org/pkg/html/template |
| 36 | func NormalizeURL(s string) string { |
| 37 | if i := strings.IndexRune(s, ':'); i >= 0 && strings.IndexRune(s[:i], '/') < 0 { |
| 38 | protocol := strings.ToLower(s[:i]) |
| 39 | if protocol != "http" && protocol != "https" && protocol != "mailto" { |
| 40 | return "#ZurlZ" |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | var b bytes.Buffer |
| 45 | written := 0 |
| 46 | // The byte loop below assumes that all URLs use UTF-8 as the |
| 47 | // content-encoding. This is similar to the URI to IRI encoding scheme |
| 48 | // defined in section 3.1 of RFC 3987, and behaves the same as the |
| 49 | // EcmaScript builtin encodeURIComponent. |
| 50 | // It should not cause any misencoding of URLs in pages with |
| 51 | // Content-type: text/html;charset=UTF-8. |
| 52 | for i, n := 0, len(s); i < n; i++ { |
| 53 | c := s[i] |
| 54 | switch c { |
| 55 | // Single quote and parens are sub-delims in RFC 3986, but we |
| 56 | // escape them so the output can be embedded in single |
| 57 | // quoted attributes and unquoted CSS url(...) constructs. |
| 58 | // Single quotes are reserved in URLs, but are only used in |
| 59 | // the obsolete "mark" rule in an appendix in RFC 3986 |
| 60 | // so can be safely encoded. |
| 61 | case '!', '#', '$', '&', '*', '+', ',', '/', ':', ';', '=', '?', '@', '[', ']': |
| 62 | continue |
| 63 | // Unreserved according to RFC 3986 sec 2.3 |
| 64 | // "For consistency, percent-encoded octets in the ranges of |
| 65 | // ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D), |
| 66 | // period (%2E), underscore (%5F), or tilde (%7E) should not be |
| 67 | // created by URI producers |
| 68 | case '-', '.', '_', '~': |
| 69 | continue |
| 70 | case '%': |
| 71 | // When normalizing do not re-encode valid escapes. |
| 72 | if i+2 < len(s) && isHex(s[i+1]) && isHex(s[i+2]) { |
| 73 | continue |
| 74 | } |
| 75 | default: |
| 76 | // Unreserved according to RFC 3986 sec 2.3 |
| 77 | if 'a' <= c && c <= 'z' { |
| 78 | continue |
| 79 | } |
| 80 | if 'A' <= c && c <= 'Z' { |
| 81 | continue |
| 82 | } |
| 83 | if '0' <= c && c <= '9' { |
| 84 | continue |
| 85 | } |
| 86 | } |
| 87 | b.WriteString(s[written:i]) |
| 88 | fmt.Fprintf(&b, "%%%02x", c) |
| 89 | written = i + 1 |
| 90 | } |
| 91 | if written == 0 { |
| 92 | return s |
| 93 | } |