// Copyright 2018 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. package html import ( "fmt" "html/template" "net/url" "reflect" "strings" texttemplate "text/template" "time" "github.com/google/syzkaller/dashboard/dashapi" "github.com/google/syzkaller/pkg/vcs" ) func CreateGlob(glob string) *template.Template { return template.Must(template.New("").Funcs(Funcs).ParseGlob(glob)) } func CreateTextGlob(glob string) *texttemplate.Template { return texttemplate.Must(texttemplate.New("").Funcs(Funcs).ParseGlob(glob)) } var Funcs = template.FuncMap{ "link": link, "optlink": optlink, "formatTime": FormatTime, "formatDate": FormatDate, "formatKernelTime": formatKernelTime, "formatJSTime": formatJSTime, "formatClock": formatClock, "formatDuration": formatDuration, "formatLateness": formatLateness, "formatReproLevel": formatReproLevel, "formatStat": formatStat, "formatShortHash": formatShortHash, "formatTagHash": formatTagHash, "formatCommitTableTitle": formatCommitTableTitle, "formatList": formatStringList, "selectBisect": selectBisect, "dereference": dereferencePointer, "commitLink": commitLink, } func selectBisect(rep *dashapi.BugReport) *dashapi.BisectResult { if rep.BisectFix != nil { return rep.BisectFix } return rep.BisectCause } func link(url, text string) template.HTML { text = template.HTMLEscapeString(text) if url != "" { text = fmt.Sprintf(`%v`, url, text) } return template.HTML(text) } func optlink(url, text string) template.HTML { if url == "" { return template.HTML("") } return link(url, text) } func FormatTime(t time.Time) string { if t.IsZero() { return "" } return t.Format("2006/01/02 15:04") } func FormatDate(t time.Time) string { if t.IsZero() { return "" } return t.Format("2006/01/02") } func formatKernelTime(t time.Time) string { if t.IsZero() { return "" } // This is how dates appear in git log. return t.Format("Mon Jan 2 15:04:05 2006 -0700") } func formatJSTime(t time.Time) string { return t.Format("2006-01-02T15:04:05") // ISO 8601 without time zone } func formatClock(t time.Time) string { if t.IsZero() { return "" } return t.Format("15:04") } func formatDuration(d time.Duration) string { if d == 0 { return "" } days := int(d / (24 * time.Hour)) hours := int(d / time.Hour % 24) mins := int(d / time.Minute % 60) if days >= 10 { return fmt.Sprintf("%vd", days) } else if days != 0 { return fmt.Sprintf("%vd%02vh", days, hours) } else if hours != 0 { return fmt.Sprintf("%vh%02vm", hours, mins) } return fmt.Sprintf("%vm", mins) } func formatLateness(now, t time.Time) string { if t.IsZero() { return "never" } d := now.Sub(t) if d < 5*time.Minute { return "now" } return formatDuration(d) } func formatReproLevel(l dashapi.ReproLevel) string { switch l { case dashapi.ReproLevelSyz: return "syz" case dashapi.ReproLevelC: return "C" default: return "" } } func formatStat(v int64) string { if v == 0 { return "" } return fmt.Sprint(v) } func formatShortHash(v string) string { const hashLen = 8 if len(v) <= hashLen { return v } return v[:hashLen] } func formatTagHash(v string) string { // Note: Fixes/References commit tags should include 12-char hash // (see Documentation/process/submitting-patches.rst). Don't change this const. const hashLen = 12 if len(v) <= hashLen { return v } return v[:hashLen] } func formatCommitTableTitle(v string) string { // This function is very specific to how we format tables in text emails. // Truncate commit title so that whole line fits into 78 chars. const commitTitleLen = 47 if len(v) <= commitTitleLen { return v } return v[:commitTitleLen-2] + ".." } func formatStringList(list []string) string { return strings.Join(list, ", ") } func dereferencePointer(v interface{}) interface{} { reflectValue := reflect.ValueOf(v) if !reflectValue.IsNil() && reflectValue.Kind() == reflect.Ptr { elem := reflectValue.Elem() if elem.CanInterface() { return elem.Interface() } } return v } func commitLink(repo, commit string) string { return vcs.CommitLink(repo, commit) } func AmendURL(baseURL, key, value string) string { return TransformURL(baseURL, key, func(_ []string) []string { if value == "" { return nil } return []string{value} }) } func DropParam(baseURL, key, value string) string { return TransformURL(baseURL, key, func(oldValues []string) []string { if value == "" { return nil } var newValues []string for _, iterVal := range oldValues { if iterVal != value { newValues = append(newValues, iterVal) } } return newValues }) } func TransformURL(baseURL, key string, f func([]string) []string) string { if baseURL == "" { return "" } parsed, err := url.Parse(baseURL) if err != nil { return "" } values := parsed.Query() ret := f(values[key]) if len(ret) == 0 { values.Del(key) } else { values[key] = ret } parsed.RawQuery = values.Encode() return parsed.String() }