// Copyright 2014 The Gogs Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package templates import ( "container/list" "encoding/json" "fmt" "html/template" "mime" "path/filepath" "runtime" "strings" "time" "golang.org/x/net/html/charset" "golang.org/x/text/transform" "gopkg.in/editorconfig/editorconfig-core-go.v1" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markdown" "code.gitea.io/gitea/modules/setting" ) // NewFuncMap returns functions for injecting to templates func NewFuncMap() []template.FuncMap { return []template.FuncMap{map[string]interface{}{ "GoVer": func() string { return strings.Title(runtime.Version()) }, "UseHTTPS": func() bool { return strings.HasPrefix(setting.AppURL, "https") }, "AppName": func() string { return setting.AppName }, "AppSubUrl": func() string { return setting.AppSubURL }, "AppUrl": func() string { return setting.AppURL }, "AppVer": func() string { return setting.AppVer }, "AppDomain": func() string { return setting.Domain }, "DisableGravatar": func() bool { return setting.DisableGravatar }, "ShowFooterTemplateLoadTime": func() bool { return setting.ShowFooterTemplateLoadTime }, "LoadTimes": func(startTime time.Time) string { return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms" }, "AvatarLink": base.AvatarLink, "Safe": Safe, "Str2html": Str2html, "TimeSince": base.TimeSince, "RawTimeSince": base.RawTimeSince, "FileSize": base.FileSize, "Subtract": base.Subtract, "Add": func(a, b int) int { return a + b }, "ActionIcon": ActionIcon, "DateFmtLong": func(t time.Time) string { return t.Format(time.RFC1123Z) }, "DateFmtShort": func(t time.Time) string { return t.Format("Jan 02, 2006") }, "List": List, "SubStr": func(str string, start, length int) string { if len(str) == 0 { return "" } end := start + length if length == -1 { end = len(str) } if len(str) < end { return str } return str[start:end] }, "EllipsisString": base.EllipsisString, "DiffTypeToStr": DiffTypeToStr, "DiffLineTypeToStr": DiffLineTypeToStr, "Sha1": Sha1, "ShortSha": base.ShortSha, "MD5": base.EncodeMD5, "ActionContent2Commits": ActionContent2Commits, "EscapePound": func(str string) string { return strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(str) }, "RenderCommitMessage": RenderCommitMessage, "ThemeColorMetaTag": func() string { return setting.UI.ThemeColorMetaTag }, "FilenameIsImage": func(filename string) bool { mimeType := mime.TypeByExtension(filepath.Ext(filename)) return strings.HasPrefix(mimeType, "image/") }, "TabSizeClass": func(ec *editorconfig.Editorconfig, filename string) string { if ec != nil { def := ec.GetDefinitionForFilename(filename) if def.TabWidth > 0 { return fmt.Sprintf("tab-size-%d", def.TabWidth) } } return "tab-size-8" }, }} } // Safe render raw as HTML func Safe(raw string) template.HTML { return template.HTML(raw) } // Str2html render Markdown text to HTML func Str2html(raw string) template.HTML { return template.HTML(markdown.Sanitizer.Sanitize(raw)) } // List traversings the list func List(l *list.List) chan interface{} { e := l.Front() c := make(chan interface{}) go func() { for e != nil { c <- e.Value e = e.Next() } close(c) }() return c } // Sha1 returns sha1 sum of string func Sha1(str string) string { return base.EncodeSha1(str) } // ToUTF8WithErr converts content to UTF8 encoding func ToUTF8WithErr(content []byte) (string, error) { charsetLabel, err := base.DetectEncoding(content) if err != nil { return "", err } else if charsetLabel == "UTF-8" { return string(content), nil } encoding, _ := charset.Lookup(charsetLabel) if encoding == nil { return string(content), fmt.Errorf("Unknown encoding: %s", charsetLabel) } // If there is an error, we concatenate the nicely decoded part and the // original left over. This way we won't loose data. result, n, err := transform.String(encoding.NewDecoder(), string(content)) if err != nil { result = result + string(content[n:]) } return result, err } // ToUTF8 converts content to UTF8 encoding and ignore error func ToUTF8(content string) string { res, _ := ToUTF8WithErr([]byte(content)) return res } // ReplaceLeft replaces all prefixes 'old' in 's' with 'new'. func ReplaceLeft(s, old, new string) string { oldLen, newLen, i, n := len(old), len(new), 0, 0 for ; i < len(s) && strings.HasPrefix(s[i:], old); n++ { i += oldLen } // simple optimization if n == 0 { return s } // allocating space for the new string curLen := n*newLen + len(s[i:]) replacement := make([]byte, curLen, curLen) j := 0 for ; j < n*newLen; j += newLen { copy(replacement[j:j+newLen], new) } copy(replacement[j:], s[i:]) return string(replacement) } // RenderCommitMessage renders commit message with XSS-safe and special links. func RenderCommitMessage(full bool, msg, urlPrefix string, metas map[string]string) template.HTML { cleanMsg := template.HTMLEscapeString(msg) fullMessage := string(markdown.RenderIssueIndexPattern([]byte(cleanMsg), urlPrefix, metas)) msgLines := strings.Split(strings.TrimSpace(fullMessage), "\n") numLines := len(msgLines) if numLines == 0 { return template.HTML("") } else if !full { return template.HTML(msgLines[0]) } else if numLines == 1 || (numLines >= 2 && len(msgLines[1]) == 0) { // First line is a header, standalone or followed by empty line header := fmt.Sprintf("
%s", strings.Join(msgLines[2:], "\n")) } else { fullMessage = header } } else { // Non-standard git message, there is no header line fullMessage = fmt.Sprintf("