|  | // 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 ( | 
|  | "strings" | 
|  | ) | 
|  |  | 
|  | // attrTypeMap[n] describes the value of the given attribute. | 
|  | // If an attribute affects (or can mask) the encoding or interpretation of | 
|  | // other content, or affects the contents, idempotency, or credentials of a | 
|  | // network message, then the value in this map is contentTypeUnsafe. | 
|  | // This map is derived from HTML5, specifically | 
|  | // https://www.w3.org/TR/html5/Overview.html#attributes-1 | 
|  | // as well as "%URI"-typed attributes from | 
|  | // https://www.w3.org/TR/html4/index/attributes.html | 
|  | var attrTypeMap = map[string]contentType{ | 
|  | "accept":          contentTypePlain, | 
|  | "accept-charset":  contentTypeUnsafe, | 
|  | "action":          contentTypeURL, | 
|  | "alt":             contentTypePlain, | 
|  | "archive":         contentTypeURL, | 
|  | "async":           contentTypeUnsafe, | 
|  | "autocomplete":    contentTypePlain, | 
|  | "autofocus":       contentTypePlain, | 
|  | "autoplay":        contentTypePlain, | 
|  | "background":      contentTypeURL, | 
|  | "border":          contentTypePlain, | 
|  | "checked":         contentTypePlain, | 
|  | "cite":            contentTypeURL, | 
|  | "challenge":       contentTypeUnsafe, | 
|  | "charset":         contentTypeUnsafe, | 
|  | "class":           contentTypePlain, | 
|  | "classid":         contentTypeURL, | 
|  | "codebase":        contentTypeURL, | 
|  | "cols":            contentTypePlain, | 
|  | "colspan":         contentTypePlain, | 
|  | "content":         contentTypeUnsafe, | 
|  | "contenteditable": contentTypePlain, | 
|  | "contextmenu":     contentTypePlain, | 
|  | "controls":        contentTypePlain, | 
|  | "coords":          contentTypePlain, | 
|  | "crossorigin":     contentTypeUnsafe, | 
|  | "data":            contentTypeURL, | 
|  | "datetime":        contentTypePlain, | 
|  | "default":         contentTypePlain, | 
|  | "defer":           contentTypeUnsafe, | 
|  | "dir":             contentTypePlain, | 
|  | "dirname":         contentTypePlain, | 
|  | "disabled":        contentTypePlain, | 
|  | "draggable":       contentTypePlain, | 
|  | "dropzone":        contentTypePlain, | 
|  | "enctype":         contentTypeUnsafe, | 
|  | "for":             contentTypePlain, | 
|  | "form":            contentTypeUnsafe, | 
|  | "formaction":      contentTypeURL, | 
|  | "formenctype":     contentTypeUnsafe, | 
|  | "formmethod":      contentTypeUnsafe, | 
|  | "formnovalidate":  contentTypeUnsafe, | 
|  | "formtarget":      contentTypePlain, | 
|  | "headers":         contentTypePlain, | 
|  | "height":          contentTypePlain, | 
|  | "hidden":          contentTypePlain, | 
|  | "high":            contentTypePlain, | 
|  | "href":            contentTypeURL, | 
|  | "hreflang":        contentTypePlain, | 
|  | "http-equiv":      contentTypeUnsafe, | 
|  | "icon":            contentTypeURL, | 
|  | "id":              contentTypePlain, | 
|  | "ismap":           contentTypePlain, | 
|  | "keytype":         contentTypeUnsafe, | 
|  | "kind":            contentTypePlain, | 
|  | "label":           contentTypePlain, | 
|  | "lang":            contentTypePlain, | 
|  | "language":        contentTypeUnsafe, | 
|  | "list":            contentTypePlain, | 
|  | "longdesc":        contentTypeURL, | 
|  | "loop":            contentTypePlain, | 
|  | "low":             contentTypePlain, | 
|  | "manifest":        contentTypeURL, | 
|  | "max":             contentTypePlain, | 
|  | "maxlength":       contentTypePlain, | 
|  | "media":           contentTypePlain, | 
|  | "mediagroup":      contentTypePlain, | 
|  | "method":          contentTypeUnsafe, | 
|  | "min":             contentTypePlain, | 
|  | "multiple":        contentTypePlain, | 
|  | "name":            contentTypePlain, | 
|  | "novalidate":      contentTypeUnsafe, | 
|  | // Skip handler names from | 
|  | // https://www.w3.org/TR/html5/webappapis.html#event-handlers-on-elements,-document-objects,-and-window-objects | 
|  | // since we have special handling in attrType. | 
|  | "open":        contentTypePlain, | 
|  | "optimum":     contentTypePlain, | 
|  | "pattern":     contentTypeUnsafe, | 
|  | "placeholder": contentTypePlain, | 
|  | "poster":      contentTypeURL, | 
|  | "profile":     contentTypeURL, | 
|  | "preload":     contentTypePlain, | 
|  | "pubdate":     contentTypePlain, | 
|  | "radiogroup":  contentTypePlain, | 
|  | "readonly":    contentTypePlain, | 
|  | "rel":         contentTypeUnsafe, | 
|  | "required":    contentTypePlain, | 
|  | "reversed":    contentTypePlain, | 
|  | "rows":        contentTypePlain, | 
|  | "rowspan":     contentTypePlain, | 
|  | "sandbox":     contentTypeUnsafe, | 
|  | "spellcheck":  contentTypePlain, | 
|  | "scope":       contentTypePlain, | 
|  | "scoped":      contentTypePlain, | 
|  | "seamless":    contentTypePlain, | 
|  | "selected":    contentTypePlain, | 
|  | "shape":       contentTypePlain, | 
|  | "size":        contentTypePlain, | 
|  | "sizes":       contentTypePlain, | 
|  | "span":        contentTypePlain, | 
|  | "src":         contentTypeURL, | 
|  | "srcdoc":      contentTypeHTML, | 
|  | "srclang":     contentTypePlain, | 
|  | "srcset":      contentTypeSrcset, | 
|  | "start":       contentTypePlain, | 
|  | "step":        contentTypePlain, | 
|  | "style":       contentTypeCSS, | 
|  | "tabindex":    contentTypePlain, | 
|  | "target":      contentTypePlain, | 
|  | "title":       contentTypePlain, | 
|  | "type":        contentTypeUnsafe, | 
|  | "usemap":      contentTypeURL, | 
|  | "value":       contentTypeUnsafe, | 
|  | "width":       contentTypePlain, | 
|  | "wrap":        contentTypePlain, | 
|  | "xmlns":       contentTypeURL, | 
|  | } | 
|  |  | 
|  | // attrType returns a conservative (upper-bound on authority) guess at the | 
|  | // type of the lowercase named attribute. | 
|  | func attrType(name string) contentType { | 
|  | if strings.HasPrefix(name, "data-") { | 
|  | // Strip data- so that custom attribute heuristics below are | 
|  | // widely applied. | 
|  | // Treat data-action as URL below. | 
|  | name = name[5:] | 
|  | } else if colon := strings.IndexRune(name, ':'); colon != -1 { | 
|  | if name[:colon] == "xmlns" { | 
|  | return contentTypeURL | 
|  | } | 
|  | // Treat svg:href and xlink:href as href below. | 
|  | name = name[colon+1:] | 
|  | } | 
|  | if t, ok := attrTypeMap[name]; ok { | 
|  | return t | 
|  | } | 
|  | // Treat partial event handler names as script. | 
|  | if strings.HasPrefix(name, "on") { | 
|  | return contentTypeJS | 
|  | } | 
|  |  | 
|  | // Heuristics to prevent "javascript:..." injection in custom | 
|  | // data attributes and custom attributes like g:tweetUrl. | 
|  | // https://www.w3.org/TR/html5/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes | 
|  | // "Custom data attributes are intended to store custom data | 
|  | //  private to the page or application, for which there are no | 
|  | //  more appropriate attributes or elements." | 
|  | // Developers seem to store URL content in data URLs that start | 
|  | // or end with "URI" or "URL". | 
|  | if strings.Contains(name, "src") || | 
|  | strings.Contains(name, "uri") || | 
|  | strings.Contains(name, "url") { | 
|  | return contentTypeURL | 
|  | } | 
|  | return contentTypePlain | 
|  | } |