github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/html/html.go (about) 1 // Copyright 2018 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package html 5 6 import ( 7 "fmt" 8 "html/template" 9 "net/url" 10 "path/filepath" 11 "reflect" 12 "strings" 13 texttemplate "text/template" 14 "time" 15 16 "github.com/google/syzkaller/dashboard/dashapi" 17 "github.com/google/syzkaller/pkg/vcs" 18 "google.golang.org/appengine/v2" 19 ) 20 21 // search path differs for the tests and AppEngine because we don't follow 22 // the recommended folder structure (my best guess). 23 // When you run the dashboard/app tests, CWD is syzkaller/dashboard/app. 24 // When you deploy AppEngine in the GOPATH mode, CWD is syzkaller/dashboard/app. 25 // When you deploy AppEngine in the GOMOD, CWD is syzkaller/. 26 var globSearchPath = func() string { 27 if appengine.IsAppEngine() { 28 return "dashboard/app/" 29 } 30 return "" 31 }() 32 33 // SetGlobSearchPath overrides the default path where syzkaller looks for templates. 34 func SetGlobSearchPath(path string) { 35 globSearchPath = path 36 } 37 38 func CreateGlob(glob string) *template.Template { 39 if strings.Contains(glob, string(filepath.Separator)) { 40 panic("glob can't be a path, the files mask is expected") 41 } 42 return template.Must( 43 template.New("").Funcs(Funcs).ParseGlob(filepath.Join(globSearchPath, glob))) 44 } 45 46 func CreateTextGlob(glob string) *texttemplate.Template { 47 if strings.Contains(glob, string(filepath.Separator)) { 48 panic("glob can't be a path, the files mask is expected") 49 } 50 return texttemplate.Must( 51 texttemplate.New("").Funcs(Funcs).ParseGlob(filepath.Join(globSearchPath, glob))) 52 } 53 54 var Funcs = template.FuncMap{ 55 "link": link, 56 "optlink": optlink, 57 "formatTime": FormatTime, 58 "formatDate": FormatDate, 59 "formatKernelTime": formatKernelTime, 60 "formatJSTime": formatJSTime, 61 "formatClock": formatClock, 62 "formatDuration": formatDuration, 63 "formatLateness": formatLateness, 64 "formatReproLevel": formatReproLevel, 65 "formatStat": formatStat, 66 "formatShortHash": formatShortHash, 67 "formatTagHash": formatTagHash, 68 "formatCommitTableTitle": formatCommitTableTitle, 69 "formatList": formatStringList, 70 "selectBisect": selectBisect, 71 "dereference": dereferencePointer, 72 "commitLink": commitLink, 73 } 74 75 func selectBisect(rep *dashapi.BugReport) *dashapi.BisectResult { 76 if rep.BisectFix != nil { 77 return rep.BisectFix 78 } 79 return rep.BisectCause 80 } 81 82 func link(url, text string) template.HTML { 83 text = template.HTMLEscapeString(text) 84 if url != "" { 85 text = fmt.Sprintf(`<a href="%v">%v</a>`, url, text) 86 } 87 return template.HTML(text) 88 } 89 90 func optlink(url, text string) template.HTML { 91 if url == "" { 92 return template.HTML("") 93 } 94 return link(url, text) 95 } 96 97 func FormatTime(t time.Time) string { 98 if t.IsZero() { 99 return "" 100 } 101 return t.Format("2006/01/02 15:04") 102 } 103 104 func FormatDate(t time.Time) string { 105 if t.IsZero() { 106 return "" 107 } 108 return t.Format("2006/01/02") 109 } 110 111 func formatKernelTime(t time.Time) string { 112 if t.IsZero() { 113 return "" 114 } 115 // This is how dates appear in git log. 116 return t.Format("Mon Jan 2 15:04:05 2006 -0700") 117 } 118 119 func formatJSTime(t time.Time) string { 120 return t.Format("2006-01-02T15:04:05") // ISO 8601 without time zone 121 } 122 123 func formatClock(t time.Time) string { 124 if t.IsZero() { 125 return "" 126 } 127 return t.Format("15:04") 128 } 129 130 func formatDuration(d time.Duration) string { 131 if d == 0 { 132 return "" 133 } 134 days := int(d / (24 * time.Hour)) 135 hours := int(d / time.Hour % 24) 136 mins := int(d / time.Minute % 60) 137 if days >= 10 { 138 return fmt.Sprintf("%vd", days) 139 } else if days != 0 { 140 return fmt.Sprintf("%vd%02vh", days, hours) 141 } else if hours != 0 { 142 return fmt.Sprintf("%vh%02vm", hours, mins) 143 } 144 return fmt.Sprintf("%vm", mins) 145 } 146 147 func formatLateness(now, t time.Time) string { 148 if t.IsZero() { 149 return "never" 150 } 151 d := now.Sub(t) 152 if d < 5*time.Minute { 153 return "now" 154 } 155 return formatDuration(d) 156 } 157 158 func formatReproLevel(l dashapi.ReproLevel) string { 159 switch l { 160 case dashapi.ReproLevelSyz: 161 return "syz" 162 case dashapi.ReproLevelC: 163 return "C" 164 default: 165 return "" 166 } 167 } 168 169 func formatStat(v int64) string { 170 if v == 0 { 171 return "" 172 } 173 return fmt.Sprint(v) 174 } 175 176 func formatShortHash(v string) string { 177 const hashLen = 8 178 if len(v) <= hashLen { 179 return v 180 } 181 return v[:hashLen] 182 } 183 184 func formatTagHash(v string) string { 185 // Note: Fixes/References commit tags should include 12-char hash 186 // (see Documentation/process/submitting-patches.rst). Don't change this const. 187 const hashLen = 12 188 if len(v) <= hashLen { 189 return v 190 } 191 return v[:hashLen] 192 } 193 194 func formatCommitTableTitle(v string) string { 195 // This function is very specific to how we format tables in text emails. 196 // Truncate commit title so that whole line fits into 78 chars. 197 const commitTitleLen = 47 198 if len(v) <= commitTitleLen { 199 return v 200 } 201 return v[:commitTitleLen-2] + ".." 202 } 203 204 func formatStringList(list []string) string { 205 return strings.Join(list, ", ") 206 } 207 208 func dereferencePointer(v interface{}) interface{} { 209 reflectValue := reflect.ValueOf(v) 210 if !reflectValue.IsNil() && reflectValue.Kind() == reflect.Ptr { 211 elem := reflectValue.Elem() 212 if elem.CanInterface() { 213 return elem.Interface() 214 } 215 } 216 return v 217 } 218 219 func commitLink(repo, commit string) string { 220 return vcs.CommitLink(repo, commit) 221 } 222 223 func AmendURL(baseURL, key, value string) string { 224 return TransformURL(baseURL, key, func(_ []string) []string { 225 if value == "" { 226 return nil 227 } 228 return []string{value} 229 }) 230 } 231 232 func DropParam(baseURL, key, value string) string { 233 return TransformURL(baseURL, key, func(oldValues []string) []string { 234 if value == "" { 235 return nil 236 } 237 var newValues []string 238 for _, iterVal := range oldValues { 239 if iterVal != value { 240 newValues = append(newValues, iterVal) 241 } 242 } 243 return newValues 244 }) 245 } 246 247 func TransformURL(baseURL, key string, f func([]string) []string) string { 248 if baseURL == "" { 249 return "" 250 } 251 parsed, err := url.Parse(baseURL) 252 if err != nil { 253 return "" 254 } 255 values := parsed.Query() 256 ret := f(values[key]) 257 if len(ret) == 0 { 258 values.Del(key) 259 } else { 260 values[key] = ret 261 } 262 parsed.RawQuery = values.Encode() 263 return parsed.String() 264 }