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  }