| OLD | NEW |
| 1 package main | 1 package main |
| 2 | 2 |
| 3 import ( | 3 import ( |
| 4 "encoding/json" | 4 "encoding/json" |
| 5 "flag" | 5 "flag" |
| 6 "fmt" | 6 "fmt" |
| 7 » htemplate "html/template" | 7 » "html/template" |
| 8 » "math/rand" | |
| 9 "net/http" | 8 "net/http" |
| 10 "os" | |
| 11 "path/filepath" | 9 "path/filepath" |
| 12 » "runtime" | 10 ) |
| 13 » "time" | 11 |
| 14 | 12 import ( |
| 15 "github.com/gorilla/mux" | 13 "github.com/gorilla/mux" |
| 16 metrics "github.com/rcrowley/go-metrics" | |
| 17 "github.com/skia-dev/glog" | 14 "github.com/skia-dev/glog" |
| 18 » "go.skia.org/infra/fuzzer/go/config" | 15 ) |
| 19 » "go.skia.org/infra/go/auth" | 16 |
| 17 import ( |
| 18 » "go.skia.org/infra/fuzzer/go/fuzz" |
| 20 "go.skia.org/infra/go/common" | 19 "go.skia.org/infra/go/common" |
| 21 "go.skia.org/infra/go/login" | 20 "go.skia.org/infra/go/login" |
| 22 "go.skia.org/infra/go/metadata" | 21 "go.skia.org/infra/go/metadata" |
| 22 "go.skia.org/infra/go/skiaversion" |
| 23 "go.skia.org/infra/go/util" | 23 "go.skia.org/infra/go/util" |
| 24 "google.golang.org/api/storage/v1" | |
| 25 ) | |
| 26 | |
| 27 var ( | |
| 28 // indexTemplate is the main index.html page we serve. | |
| 29 indexTemplate *htemplate.Template = nil | |
| 30 | |
| 31 requestsCounter = metrics.NewRegisteredCounter("request
s", metrics.DefaultRegistry) | |
| 32 router *mux.Router = mux.NewRouter() | |
| 33 client *http.Client = nil | |
| 34 store *storage.Service = nil | |
| 35 ) | |
| 36 | |
| 37 // Command line flags. | |
| 38 var ( | |
| 39 configFilename = flag.String("config", "fuzzer.toml", "Configuration fil
ename") | |
| 40 ) | 24 ) |
| 41 | 25 |
| 42 const ( | 26 const ( |
| 43 // OAUTH2_CALLBACK_PATH is callback endpoint used for the Oauth2 flow. | 27 // OAUTH2_CALLBACK_PATH is callback endpoint used for the Oauth2 flow. |
| 44 OAUTH2_CALLBACK_PATH = "/oauth2callback/" | 28 OAUTH2_CALLBACK_PATH = "/oauth2callback/" |
| 45 ) | 29 ) |
| 46 | 30 |
| 31 var ( |
| 32 // indexTemplate is the main index.html page we serve. |
| 33 indexTemplate *template.Template = nil |
| 34 detailsTemplate *template.Template = nil |
| 35 ) |
| 36 |
| 37 // Command line flags. |
| 38 var ( |
| 39 graphiteServer = flag.String("graphite_server", "localhost:2003", "Where
is Graphite metrics ingestion server running.") |
| 40 host = flag.String("host", "localhost", "HTTP service host") |
| 41 port = flag.String("port", ":80", "HTTP service port (e.g., ':
8002')") |
| 42 local = flag.Bool("local", false, "Running locally if true. As
opposed to in production.") |
| 43 resourcesDir = flag.String("resources_dir", "", "The directory to find
templates, JS, and CSS files. If blank the current directory will be used.") |
| 44 |
| 45 authWhiteList = flag.String("auth_whitelist", login.DEFAULT_DOMAIN_WHITE
LIST, "White space separated list of domains and email addresses that are allowe
d to login.") |
| 46 redirectURL = flag.String("redirect_url", "https://fuzzer.skia.org/oau
th2callback/", "OAuth2 redirect url. Only used when local=false.") |
| 47 ) |
| 48 |
| 47 func Init() { | 49 func Init() { |
| 50 reloadTemplates() |
| 51 } |
| 52 |
| 53 func reloadTemplates() { |
| 54 indexTemplate = template.Must(template.ParseFiles( |
| 55 filepath.Join(*resourcesDir, "templates/index.html"), |
| 56 filepath.Join(*resourcesDir, "templates/header.html"), |
| 57 )) |
| 58 } |
| 59 |
| 60 func main() { |
| 48 defer common.LogPanic() | 61 defer common.LogPanic() |
| 49 | 62 » // Calls flag.Parse() |
| 50 » rand.Seed(time.Now().UnixNano()) | 63 » common.InitWithMetrics("fuzzer", graphiteServer) |
| 51 | 64 |
| 52 » common.InitWithMetricsCB("fuzzer", func() string { | |
| 53 » » common.DecodeTomlFile(*configFilename, &config.Config) | |
| 54 » » return config.Config.FrontEnd.GraphiteServer | |
| 55 » }) | |
| 56 | |
| 57 » if config.Config.Common.ResourcePath == "" { | |
| 58 » » _, filename, _, _ := runtime.Caller(0) | |
| 59 » » config.Config.Common.ResourcePath = filepath.Join(filepath.Dir(f
ilename), "../..") | |
| 60 » } | |
| 61 | |
| 62 » path, err := filepath.Abs(config.Config.Common.ResourcePath) | |
| 63 » if err != nil { | |
| 64 » » glog.Fatalf("Couldn't get absolute path to fuzzer resources: %s"
, err) | |
| 65 » } | |
| 66 » if err := os.Chdir(path); err != nil { | |
| 67 » » glog.Fatal(err) | |
| 68 » } | |
| 69 | |
| 70 » indexTemplate = htemplate.Must(htemplate.ParseFiles( | |
| 71 » » filepath.Join(path, "templates/index.html"), | |
| 72 » » filepath.Join(path, "templates/header.html"), | |
| 73 » » filepath.Join(path, "templates/titlebar.html"), | |
| 74 » » filepath.Join(path, "templates/footer.html"), | |
| 75 » )) | |
| 76 | |
| 77 » if client, err = auth.NewClient(config.Config.Common.DoOAuth, config.Con
fig.Common.OAuthCacheFile, storage.DevstorageFullControlScope); err != nil { | |
| 78 » » glog.Fatalf("Failed to create authenticated HTTP client: %s", er
r) | |
| 79 » } | |
| 80 | |
| 81 » if store, err = storage.New(client); err != nil { | |
| 82 » » glog.Fatalf("Failed to create storage service client: %s", err) | |
| 83 » } | |
| 84 } | |
| 85 | |
| 86 type IndexContext struct { | |
| 87 » LoadFuzzListURL string | |
| 88 } | |
| 89 | |
| 90 func getURL(router *mux.Router, name string, pairs ...string) string { | |
| 91 » route := router.Get(name) | |
| 92 » if route == nil { | |
| 93 » » glog.Fatalf("Couldn't find any route named %s", name) | |
| 94 » } | |
| 95 | |
| 96 » routeURL, err := route.URL(pairs...) | |
| 97 » if err != nil { | |
| 98 » » glog.Fatalf("Couldn't resolve route %s into a URL", routeURL) | |
| 99 » } | |
| 100 | |
| 101 » return routeURL.String() | |
| 102 } | |
| 103 | |
| 104 func mainHandler(w http.ResponseWriter, r *http.Request) { | |
| 105 » glog.Infof("Main Handler: %q\n", r.URL.Path) | |
| 106 » requestsCounter.Inc(1) | |
| 107 » if r.Method == "GET" { | |
| 108 » » // Expand the template. | |
| 109 » » w.Header().Set("Content-Type", "text/html") | |
| 110 » » fuzzListURL := getURL(router, "fuzzListHandler") | |
| 111 » » context := IndexContext{ | |
| 112 » » » fuzzListURL, | |
| 113 » » } | |
| 114 » » if err := indexTemplate.Execute(w, context); err != nil { | |
| 115 » » » glog.Errorf("Failed to expand template: %q\n", err) | |
| 116 » » } | |
| 117 » } | |
| 118 } | |
| 119 | |
| 120 // makeResourceHandler creates a static file handler that sets a caching policy. | |
| 121 func makeResourceHandler() func(http.ResponseWriter, *http.Request) { | |
| 122 » fileServer := http.FileServer(http.Dir(config.Config.Common.ResourcePath
)) | |
| 123 » return func(w http.ResponseWriter, r *http.Request) { | |
| 124 » » w.Header().Add("Cache-Control", string(300)) | |
| 125 » » fileServer.ServeHTTP(w, r) | |
| 126 » } | |
| 127 } | |
| 128 | |
| 129 func getFuzzes(baseDir string) []string { | |
| 130 » results := []string{} | |
| 131 » glog.Infof("Opening bucket/directory: %s/%s", config.Config.Common.FuzzO
utputGSBucket, baseDir) | |
| 132 | |
| 133 » req := store.Objects.List(config.Config.Common.FuzzOutputGSBucket).Prefi
x(baseDir + "/").Delimiter("/") | |
| 134 » for req != nil { | |
| 135 » » resp, err := req.Do() | |
| 136 » » if err != nil { | |
| 137 » » » return results | |
| 138 » » } | |
| 139 » » for _, result := range resp.Prefixes { | |
| 140 » » » results = append(results, result[len(baseDir)+1:len(resu
lt)-1]) | |
| 141 » » } | |
| 142 » » if len(resp.NextPageToken) > 0 { | |
| 143 » » » req.PageToken(resp.NextPageToken) | |
| 144 » » } else { | |
| 145 » » » req = nil | |
| 146 » » } | |
| 147 » } | |
| 148 » return results | |
| 149 } | |
| 150 | |
| 151 func fuzzListHandler(w http.ResponseWriter, r *http.Request) { | |
| 152 » if err := r.ParseForm(); err != nil { | |
| 153 » » util.ReportError(w, r, err, "Failed to parse form data.") | |
| 154 » » return | |
| 155 » } | |
| 156 | |
| 157 » w.Header().Set("Content-Type", "application/json") | |
| 158 » enc := json.NewEncoder(w) | |
| 159 | |
| 160 » fuzzes := []string{} | |
| 161 | |
| 162 » failed := r.FormValue("failed") | |
| 163 » passed := r.FormValue("passed") | |
| 164 | |
| 165 » if failed == "true" { | |
| 166 » » fuzzes = append(fuzzes, getFuzzes("failed")...) | |
| 167 » } | |
| 168 » if passed == "true" { | |
| 169 » » fuzzes = append(fuzzes, getFuzzes("working")...) | |
| 170 » } | |
| 171 | |
| 172 » if err := enc.Encode(fuzzes); err != nil { | |
| 173 » » glog.Errorf("Failed to write or encode output: %s", err) | |
| 174 » } | |
| 175 } | |
| 176 | |
| 177 func main() { | |
| 178 » flag.Parse() | |
| 179 Init() | 65 Init() |
| 180 | 66 |
| 181 » // Set up login | 67 » setupOAuth() |
| 68 |
| 69 » runServer() |
| 70 } |
| 71 |
| 72 func setupOAuth() { |
| 182 var cookieSalt = "notverysecret" | 73 var cookieSalt = "notverysecret" |
| 74 // This clientID and clientSecret are only used for setting up a local s
erver. |
| 75 // Production id and secrets are in metadata and will be loaded from the
re. |
| 183 var clientID = "31977622648-ubjke2f3staq6ouas64r31h8f8tcbiqp.apps.google
usercontent.com" | 76 var clientID = "31977622648-ubjke2f3staq6ouas64r31h8f8tcbiqp.apps.google
usercontent.com" |
| 184 var clientSecret = "rK-kRY71CXmcg0v9I9KIgWci" | 77 var clientSecret = "rK-kRY71CXmcg0v9I9KIgWci" |
| 185 » var useRedirectURL = fmt.Sprintf("http://localhost%s/oauth2callback/", c
onfig.Config.FrontEnd.Port) | 78 » var useRedirectURL = fmt.Sprintf("http://localhost%s/oauth2callback/", *
port) |
| 186 » if !config.Config.Common.Local { | 79 » if !*local { |
| 187 cookieSalt = metadata.Must(metadata.ProjectGet(metadata.COOKIESA
LT)) | 80 cookieSalt = metadata.Must(metadata.ProjectGet(metadata.COOKIESA
LT)) |
| 188 clientID = metadata.Must(metadata.ProjectGet(metadata.CLIENT_ID)
) | 81 clientID = metadata.Must(metadata.ProjectGet(metadata.CLIENT_ID)
) |
| 189 clientSecret = metadata.Must(metadata.ProjectGet(metadata.CLIENT
_SECRET)) | 82 clientSecret = metadata.Must(metadata.ProjectGet(metadata.CLIENT
_SECRET)) |
| 190 » » useRedirectURL = config.Config.FrontEnd.RedirectURL | 83 » » useRedirectURL = *redirectURL |
| 191 » } | 84 » } |
| 192 | 85 » login.Init(clientID, clientSecret, useRedirectURL, cookieSalt, login.DEF
AULT_SCOPE, *authWhiteList, *local) |
| 193 » login.Init(clientID, clientSecret, useRedirectURL, cookieSalt, login.DEF
AULT_SCOPE, login.DEFAULT_DOMAIN_WHITELIST, config.Config.Common.Local) | 86 } |
| 194 | 87 |
| 195 » // Set up the login related resources. | 88 func runServer() { |
| 196 » router.HandleFunc(OAUTH2_CALLBACK_PATH, login.OAuth2CallbackHandler) | 89 » serverURL := "https://" + *host |
| 197 » router.HandleFunc("/loginstatus/", login.StatusHandler) | 90 » if *local { |
| 198 » router.HandleFunc("/logout/", login.LogoutHandler) | 91 » » serverURL = "http://" + *host + *port |
| 199 | 92 » } |
| 200 » router.HandleFunc("/", mainHandler) | 93 |
| 201 » router.HandleFunc("/failed", mainHandler) | 94 » r := mux.NewRouter() |
| 202 » router.HandleFunc("/passed", mainHandler) | 95 » r.PathPrefix("/res/").HandlerFunc(util.MakeResourceHandler(*resourcesDir
)) |
| 203 » router.PathPrefix("/res/").HandlerFunc(makeResourceHandler()) | 96 |
| 204 | 97 » r.HandleFunc(OAUTH2_CALLBACK_PATH, login.OAuth2CallbackHandler) |
| 205 » jsonRouter := router.PathPrefix("/_").Subrouter() | 98 » r.HandleFunc("/", indexHandler) |
| 206 » jsonRouter.HandleFunc("/list", fuzzListHandler).Name("fuzzListHandler") | 99 » r.HandleFunc("/details", detailHandler) |
| 207 | 100 » r.HandleFunc("/loginstatus/", login.StatusHandler) |
| 208 » rootHandler := util.LoggingGzipRequestResponse(router) | 101 » r.HandleFunc("/logout/", login.LogoutHandler) |
| 209 » if config.Config.FrontEnd.ForceLogin { | 102 » r.HandleFunc("/json/version", skiaversion.JsonHandler) |
| 210 » » rootHandler = login.ForceAuth(rootHandler, OAUTH2_CALLBACK_PATH) | 103 » r.HandleFunc("/json/fuzz-list", fuzzListHandler) |
| 211 » } | 104 |
| 105 » rootHandler := login.ForceAuth(util.LoggingGzipRequestResponse(r), OAUTH
2_CALLBACK_PATH) |
| 212 | 106 |
| 213 http.Handle("/", rootHandler) | 107 http.Handle("/", rootHandler) |
| 214 | 108 » glog.Infof("Ready to serve on %s", serverURL) |
| 215 » glog.Fatal(http.ListenAndServe(config.Config.FrontEnd.Port, nil)) | 109 » glog.Fatal(http.ListenAndServe(*port, nil)) |
| 216 } | 110 } |
| 111 |
| 112 func indexHandler(w http.ResponseWriter, r *http.Request) { |
| 113 » if *local { |
| 114 » » reloadTemplates() |
| 115 » } |
| 116 |
| 117 » w.Header().Set("Content-Type", "text/html") |
| 118 |
| 119 » if err := indexTemplate.Execute(w, nil); err != nil { |
| 120 » » glog.Errorf("Failed to write or encode output: %s", err) |
| 121 » } |
| 122 } |
| 123 |
| 124 func detailHandler(w http.ResponseWriter, r *http.Request) { |
| 125 » if *local { |
| 126 » » reloadTemplates() |
| 127 » } |
| 128 |
| 129 » w.Header().Set("Content-Type", "text/html") |
| 130 |
| 131 » if err := detailsTemplate.Execute(w, nil); err != nil { |
| 132 » » glog.Errorf("Failed to write or encode output: %s", err) |
| 133 » } |
| 134 } |
| 135 |
| 136 type fuzzReport []fuzzReportFile |
| 137 |
| 138 type fuzzReportFile struct { |
| 139 » FileName string `json:"fileName"` |
| 140 » BinaryCount int `json:"binaryCount"` |
| 141 » ApiCount int `json:"apiCount"` |
| 142 » Functions []fuzzReportFunction `json:"byFunction"` |
| 143 } |
| 144 |
| 145 type fuzzReportFunction struct { |
| 146 » FunctionName string `json:"functionName"` |
| 147 » BinaryCount int `json:"binaryCount"` |
| 148 » ApiCount int `json:"apiCount"` |
| 149 » LineNumbers []fuzzReportLineNumber `json:"byLineNumber"` |
| 150 } |
| 151 |
| 152 type fuzzReportLineNumber struct { |
| 153 » LineNumber int `json:"lineNumber"` |
| 154 » BinaryCount int `json:"binaryCount"` |
| 155 » ApiCount int `json:"apiCount"` |
| 156 » BinaryDetails []fuzzReportBinary `json:"binaryReports"` |
| 157 } |
| 158 |
| 159 // We make this intermediate struct so we can control the json names and disenta
gle the backend structure from what is displayed/visualized |
| 160 type fuzzReportBinary struct { |
| 161 » DebugStackTrace fuzz.StackTrace `json:"debugStackTrace"` |
| 162 » ReleaseStackTrace fuzz.StackTrace `json:"releaseStackTrace"` |
| 163 » HumanReadableFlags []string `json:"flags"` |
| 164 » BadBinaryName string `json:"binaryName"` |
| 165 » BinaryType string `json:"binaryType"` |
| 166 } |
| 167 |
| 168 func fuzzListHandler(w http.ResponseWriter, r *http.Request) { |
| 169 » w.Header().Set("Content-Type", "application/json") |
| 170 |
| 171 » mockFuzzes := fuzzReport{ |
| 172 » » { |
| 173 » » » "foo.h", 30, 0, []fuzzReportFunction{ |
| 174 » » » » { |
| 175 » » » » » "frizzle()", 18, 0, []fuzzReportLineNumb
er{ |
| 176 » » » » » » { |
| 177 » » » » » » » 64, 17, 0, []fuzzReportB
inary{}, |
| 178 » » » » » » }, |
| 179 » » » » » » { |
| 180 » » » » » » » 69, 1, 0, []fuzzReportBi
nary{}, |
| 181 » » » » » » }, |
| 182 » » » » » }, |
| 183 » » » » }, { |
| 184 » » » » » "zizzle()", 12, 0, []fuzzReportLineNumbe
r{ |
| 185 » » » » » » { |
| 186 » » » » » » » 123, 12, 0, []fuzzReport
Binary{}, |
| 187 » » » » » » }, |
| 188 » » » » » }, |
| 189 » » » » }, |
| 190 » » » }, |
| 191 » » }, { |
| 192 » » » "bar.h", 15, 3, []fuzzReportFunction{ |
| 193 » » » » { |
| 194 » » » » » "frizzle()", 15, 3, []fuzzReportLineNumb
er{ |
| 195 » » » » » » { |
| 196 » » » » » » » 566, 15, 2, []fuzzReport
Binary{}, |
| 197 » » » » » » }, |
| 198 » » » » » » { |
| 199 » » » » » » » 568, 0, 1, []fuzzReportB
inary{}, |
| 200 » » » » » » }, |
| 201 » » » » » }, |
| 202 » » » » }, |
| 203 » » » }, |
| 204 » » }, |
| 205 » } |
| 206 |
| 207 » if err := json.NewEncoder(w).Encode(mockFuzzes); err != nil { |
| 208 » » glog.Errorf("Failed to write or encode output: %s", err) |
| 209 » » return |
| 210 » } |
| 211 } |
| OLD | NEW |