package themes // Functions directly related to HTML, CSS, Amber or GCSS import ( "bytes" "fmt" "html" "net/url" "strings" "github.com/microcosm-cc/bluemonday" ) const ( // DefaultCSSFilename is the default CSS stylesheet DefaultCSSFilename = "style.css" // DefaultGCSSFilename is the default GCSS stylesheet DefaultGCSSFilename = "style.gcss" // DefaultTheme is the default theme when rendering Markdown and for error pages DefaultTheme = "default" ) // MetaKeywords contains a selection of allowed keywords for the HTML meta tag var MetaKeywords = []string{"application-name", "author", "description", "generator", "keywords", "robots", "language", "googlebot", "Slurp", "bingbot", "geo.position", "geo.placename", "geo.region", "ICBM", "viewport"} // MessagePage is an easy way to output a HTML page only given a title, the body // (will be placed between the tags) and the name of one of the // built-in themes. Does not close and . // Deprecated func MessagePage(title, body, theme string) string { return fmt.Sprintf("%s%s

%s

%s", title, StyleHead(theme), title, body) } var policy = bluemonday.UGCPolicy() // StyleHead returns contents that goes in "", as bytes. // This is either CSS wrapped in a "") buf.WriteString("") } else { buf.WriteString("") } return buf.Bytes() } // MessagePageBytes provides the same functionalityt as MessagePage, // but with []byte instead of string, and without closing func MessagePageBytes(title string, body []byte, theme string) []byte { // Sanitize the theme and title theme = policy.Sanitize(theme) title = policy.Sanitize(title) var buf bytes.Buffer buf.WriteString("") buf.WriteString(title) buf.WriteString("") buf.Write(StyleHead(theme)) buf.WriteString("

") buf.WriteString(title) buf.WriteString("

") buf.Write(body) return buf.Bytes() } // SimpleHTMLPage provides a quick way to build a HTML page func SimpleHTMLPage(title, headline, inhead, body, language []byte) []byte { // Sanitize the title, headline and language titleString := policy.Sanitize(string(title)) headlineString := policy.Sanitize(string(headline)) languageString := policy.Sanitize(string(language)) var buf bytes.Buffer if len(languageString) > 0 { buf.WriteString("") } else { buf.WriteString("") } if len(titleString) > 0 { buf.WriteString("") buf.WriteString(titleString) buf.WriteString("") } buf.Write(inhead) buf.WriteString("") if len(headlineString) > 0 { buf.WriteString("

") buf.WriteString(headlineString) buf.WriteString("

") } buf.Write(body) return buf.Bytes() } // HTMLLink builds an HTML link given the link text, the URL to a file/directory // and a boolean that is true if the given URL is to a directory. func HTMLLink(text, urlPath string, isDirectory bool) string { return HTMLLinkWithBase(text, "", urlPath, isDirectory) } // HTMLLinkWithBase builds an HTML link, just like HTMLLink, but with the given // base URL in front of the URL path. The base URL can be a path, like "/files", // or an absolute URL. An empty base URL links relative to the server root. func HTMLLinkWithBase(text, baseURL, urlPath string, isDirectory bool) string { // Sanitize the link text text = policy.Sanitize(text) // Add a final slash, if needed if isDirectory { text += "/" urlPath += "/" } // Percent-encode the URL path so filenames with newlines, spaces or // other special bytes produce a valid href. See issue #144. href := strings.TrimSuffix(baseURL, "/") + (&url.URL{Path: "/" + urlPath}).EscapedPath() return "" + text + "
" } // StyleAmber modifies Amber source code so that a link to the given stylesheet URL is added func StyleAmber(amberdata []byte, url string) []byte { // Sanitize the URL url = policy.Sanitize(url) // If the given url is not already mentioned and the data contains "body" if !bytes.Contains(amberdata, []byte(url)) && bytes.Contains(amberdata, []byte("html")) && bytes.Contains(amberdata, []byte("body")) { // Extract one level of indendation whitespace := OneLevelOfIndentation(&amberdata, "body") // Check if there already is a head section if bytes.Contains(amberdata, []byte("head")) { // Add a link to the stylesheet return bytes.Replace(amberdata, []byte("head\n"), []byte("head\n"+whitespace+whitespace+`link[href="`+url+`"][rel="stylesheet"][type="text/css"]`+"\n"), 1) } else if bytes.Contains(amberdata, []byte("body")) { // Add a link to the stylesheet return bytes.Replace(amberdata, []byte("html\n"), []byte("html\n"+whitespace+"head\n"+whitespace+whitespace+`link[href="`+url+`"][rel="stylesheet"][type="text/css"]`+"\n"), 1) } } return amberdata } // StyleHTML modifies HTML source code so that a link to the given stylesheet URL is added func StyleHTML(htmldata []byte, url string) []byte { // Sanitize the URL url = policy.Sanitize(url) // If the given url is not already mentioned and the data contains "body" if !bytes.Contains(htmldata, []byte(url)) && bytes.Contains(htmldata, []byte("body")) { if bytes.Contains(htmldata, []byte("")) { return bytes.Replace(htmldata, []byte(""), []byte(" \n "), 1) } else if bytes.Contains(htmldata, []byte("")) { return bytes.Replace(htmldata, []byte(""), []byte(" \n \n \n "), 1) } } return htmldata } // InsertDoctype inserts to the HTML, if missing. // Does not check if the given data is HTML. Assumes it to be HTML. func InsertDoctype(htmldata []byte) []byte { // If there are more than two lines if bytes.Count(htmldata, []byte("\n")) > 2 { fields := bytes.SplitN(htmldata, []byte("\n"), 3) line1 := strings.ToLower(string(fields[0])) line2 := strings.ToLower(string(fields[1])) if strings.Contains(line1, "doctype") || strings.Contains(line2, "doctype") { return htmldata } // Doctype is missing from the first two lines, add it return []byte("\n" + string(htmldata)) } return htmldata } // NoPage generates a HTML page for when a file is not found func NoPage(filename, theme string) []byte { // Sanitize the filename and the theme name filename = policy.Sanitize(filename) theme = policy.Sanitize(theme) // Return a HTML page return MessagePageBytes("Not found", []byte("File not found: "+filename), theme) }