summaryrefslogtreecommitdiffstats
path: root/chromium/third_party/skia/tools/bug_chomper/src/server/server.go
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/skia/tools/bug_chomper/src/server/server.go')
-rw-r--r--chromium/third_party/skia/tools/bug_chomper/src/server/server.go376
1 files changed, 376 insertions, 0 deletions
diff --git a/chromium/third_party/skia/tools/bug_chomper/src/server/server.go b/chromium/third_party/skia/tools/bug_chomper/src/server/server.go
new file mode 100644
index 00000000000..9fb21ed594d
--- /dev/null
+++ b/chromium/third_party/skia/tools/bug_chomper/src/server/server.go
@@ -0,0 +1,376 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/*
+ Serves a webpage for easy management of Skia bugs.
+
+ WARNING: This server is NOT secure and should not be made publicly
+ accessible.
+*/
+
+package main
+
+import (
+ "encoding/json"
+ "flag"
+ "fmt"
+ "html/template"
+ "issue_tracker"
+ "log"
+ "net/http"
+ "net/url"
+ "path"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+)
+
+import "github.com/gorilla/securecookie"
+
+const certFile = "certs/cert.pem"
+const keyFile = "certs/key.pem"
+const issueComment = "Edited by BugChomper"
+const oauthCallbackPath = "/oauth2callback"
+const oauthConfigFile = "oauth_client_secret.json"
+const defaultPort = 8000
+const localHost = "127.0.0.1"
+const maxSessionLen = time.Duration(3600 * time.Second)
+const priorityPrefix = "Priority-"
+const project = "skia"
+const cookieName = "BugChomperCookie"
+
+var scheme = "http"
+
+var curdir, _ = filepath.Abs(".")
+var templatePath, _ = filepath.Abs("templates")
+var templates = template.Must(template.ParseFiles(
+ path.Join(templatePath, "bug_chomper.html"),
+ path.Join(templatePath, "submitted.html"),
+ path.Join(templatePath, "error.html")))
+
+var hashKey = securecookie.GenerateRandomKey(32)
+var blockKey = securecookie.GenerateRandomKey(32)
+var secureCookie = securecookie.New(hashKey, blockKey)
+
+// SessionState contains data for a given session.
+type SessionState struct {
+ IssueTracker *issue_tracker.IssueTracker
+ OrigRequestURL string
+ SessionStart time.Time
+}
+
+// getAbsoluteURL returns the absolute URL of the given Request.
+func getAbsoluteURL(r *http.Request) string {
+ return scheme + "://" + r.Host + r.URL.Path
+}
+
+// getOAuth2CallbackURL returns a callback URL to be used by the OAuth2 login
+// page.
+func getOAuth2CallbackURL(r *http.Request) string {
+ return scheme + "://" + r.Host + oauthCallbackPath
+}
+
+func saveSession(session *SessionState, w http.ResponseWriter, r *http.Request) error {
+ encodedSession, err := secureCookie.Encode(cookieName, session)
+ if err != nil {
+ return fmt.Errorf("unable to encode session state: %s", err)
+ }
+ cookie := &http.Cookie{
+ Name: cookieName,
+ Value: encodedSession,
+ Domain: strings.Split(r.Host, ":")[0],
+ Path: "/",
+ HttpOnly: true,
+ }
+ http.SetCookie(w, cookie)
+ return nil
+}
+
+// makeSession creates a new session for the Request.
+func makeSession(w http.ResponseWriter, r *http.Request) (*SessionState, error) {
+ log.Println("Creating new session.")
+ // Create the session state.
+ issueTracker, err := issue_tracker.MakeIssueTracker(
+ oauthConfigFile, getOAuth2CallbackURL(r))
+ if err != nil {
+ return nil, fmt.Errorf("unable to create IssueTracker for session: %s", err)
+ }
+ session := SessionState{
+ IssueTracker: issueTracker,
+ OrigRequestURL: getAbsoluteURL(r),
+ SessionStart: time.Now(),
+ }
+
+ // Encode and store the session state.
+ if err := saveSession(&session, w, r); err != nil {
+ return nil, err
+ }
+
+ return &session, nil
+}
+
+// getSession retrieves the active SessionState or creates and returns a new
+// SessionState.
+func getSession(w http.ResponseWriter, r *http.Request) (*SessionState, error) {
+ cookie, err := r.Cookie(cookieName)
+ if err != nil {
+ log.Println("No cookie found! Starting new session.")
+ return makeSession(w, r)
+ }
+ var session SessionState
+ if err := secureCookie.Decode(cookieName, cookie.Value, &session); err != nil {
+ log.Printf("Invalid or corrupted session. Starting another: %s", err.Error())
+ return makeSession(w, r)
+ }
+
+ currentTime := time.Now()
+ if currentTime.Sub(session.SessionStart) > maxSessionLen {
+ log.Printf("Session starting at %s is expired. Starting another.",
+ session.SessionStart.Format(time.RFC822))
+ return makeSession(w, r)
+ }
+ saveSession(&session, w, r)
+ return &session, nil
+}
+
+// reportError serves the error page with the given message.
+func reportError(w http.ResponseWriter, msg string, code int) {
+ errData := struct {
+ Code int
+ CodeString string
+ Message string
+ }{
+ Code: code,
+ CodeString: http.StatusText(code),
+ Message: msg,
+ }
+ w.WriteHeader(code)
+ err := templates.ExecuteTemplate(w, "error.html", errData)
+ if err != nil {
+ log.Println("Failed to display error.html!!")
+ }
+}
+
+// makeBugChomperPage builds and serves the BugChomper page.
+func makeBugChomperPage(w http.ResponseWriter, r *http.Request) {
+ session, err := getSession(w, r)
+ if err != nil {
+ reportError(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ issueTracker := session.IssueTracker
+ user, err := issueTracker.GetLoggedInUser()
+ if err != nil {
+ reportError(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ log.Println("Loading bugs for " + user)
+ bugList, err := issueTracker.GetBugs(project, user)
+ if err != nil {
+ reportError(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ bugsById := make(map[string]*issue_tracker.Issue)
+ bugsByPriority := make(map[string][]*issue_tracker.Issue)
+ for _, bug := range bugList.Items {
+ bugsById[strconv.Itoa(bug.Id)] = bug
+ var bugPriority string
+ for _, label := range bug.Labels {
+ if strings.HasPrefix(label, priorityPrefix) {
+ bugPriority = label[len(priorityPrefix):]
+ }
+ }
+ if _, ok := bugsByPriority[bugPriority]; !ok {
+ bugsByPriority[bugPriority] = make(
+ []*issue_tracker.Issue, 0)
+ }
+ bugsByPriority[bugPriority] = append(
+ bugsByPriority[bugPriority], bug)
+ }
+ bugsJson, err := json.Marshal(bugsById)
+ if err != nil {
+ reportError(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ data := struct {
+ Title string
+ User string
+ BugsJson template.JS
+ BugsByPriority *map[string][]*issue_tracker.Issue
+ Priorities []string
+ PriorityPrefix string
+ }{
+ Title: "BugChomper",
+ User: user,
+ BugsJson: template.JS(string(bugsJson)),
+ BugsByPriority: &bugsByPriority,
+ Priorities: issue_tracker.BugPriorities,
+ PriorityPrefix: priorityPrefix,
+ }
+
+ if err := templates.ExecuteTemplate(w, "bug_chomper.html", data); err != nil {
+ reportError(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// authIfNeeded determines whether the current user is logged in. If not, it
+// redirects to a login page. Returns true if the user is redirected and false
+// otherwise.
+func authIfNeeded(w http.ResponseWriter, r *http.Request) bool {
+ session, err := getSession(w, r)
+ if err != nil {
+ reportError(w, err.Error(), http.StatusInternalServerError)
+ return false
+ }
+ issueTracker := session.IssueTracker
+ if !issueTracker.IsAuthenticated() {
+ loginURL := issueTracker.MakeAuthRequestURL()
+ log.Println("Redirecting for login:", loginURL)
+ http.Redirect(w, r, loginURL, http.StatusTemporaryRedirect)
+ return true
+ }
+ return false
+}
+
+// submitData attempts to submit data from a POST request to the IssueTracker.
+func submitData(w http.ResponseWriter, r *http.Request) {
+ session, err := getSession(w, r)
+ if err != nil {
+ reportError(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ issueTracker := session.IssueTracker
+ edits := r.FormValue("all_edits")
+ var editsMap map[string]*issue_tracker.Issue
+ if err := json.Unmarshal([]byte(edits), &editsMap); err != nil {
+ errMsg := "Could not parse edits from form response: " + err.Error()
+ reportError(w, errMsg, http.StatusInternalServerError)
+ return
+ }
+ data := struct {
+ Title string
+ Message string
+ BackLink string
+ }{}
+ if len(editsMap) == 0 {
+ data.Title = "No Changes Submitted"
+ data.Message = "You didn't change anything!"
+ data.BackLink = ""
+ if err := templates.ExecuteTemplate(w, "submitted.html", data); err != nil {
+ reportError(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ return
+ }
+ errorList := make([]error, 0)
+ for issueId, newIssue := range editsMap {
+ log.Println("Editing issue " + issueId)
+ if err := issueTracker.SubmitIssueChanges(newIssue, issueComment); err != nil {
+ errorList = append(errorList, err)
+ }
+ }
+ if len(errorList) > 0 {
+ errorStrings := ""
+ for _, err := range errorList {
+ errorStrings += err.Error() + "\n"
+ }
+ errMsg := "Not all changes could be submitted: \n" + errorStrings
+ reportError(w, errMsg, http.StatusInternalServerError)
+ return
+ }
+ data.Title = "Submitted Changes"
+ data.Message = "Your changes were submitted to the issue tracker."
+ data.BackLink = ""
+ if err := templates.ExecuteTemplate(w, "submitted.html", data); err != nil {
+ reportError(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ return
+}
+
+// handleBugChomper handles HTTP requests for the bug_chomper page.
+func handleBugChomper(w http.ResponseWriter, r *http.Request) {
+ if authIfNeeded(w, r) {
+ return
+ }
+ switch r.Method {
+ case "GET":
+ makeBugChomperPage(w, r)
+ case "POST":
+ submitData(w, r)
+ }
+}
+
+// handleOAuth2Callback handles callbacks from the OAuth2 sign-in.
+func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
+ session, err := getSession(w, r)
+ if err != nil {
+ reportError(w, err.Error(), http.StatusInternalServerError)
+ }
+ issueTracker := session.IssueTracker
+ invalidLogin := "Invalid login credentials"
+ params, err := url.ParseQuery(r.URL.RawQuery)
+ if err != nil {
+ reportError(w, invalidLogin+": "+err.Error(), http.StatusForbidden)
+ return
+ }
+ code, ok := params["code"]
+ if !ok {
+ reportError(w, invalidLogin+": redirect did not include auth code.",
+ http.StatusForbidden)
+ return
+ }
+ log.Println("Upgrading auth token:", code[0])
+ if err := issueTracker.UpgradeCode(code[0]); err != nil {
+ errMsg := "failed to upgrade token: " + err.Error()
+ reportError(w, errMsg, http.StatusForbidden)
+ return
+ }
+ if err := saveSession(session, w, r); err != nil {
+ reportError(w, "failed to save session: "+err.Error(),
+ http.StatusInternalServerError)
+ return
+ }
+ http.Redirect(w, r, session.OrigRequestURL, http.StatusTemporaryRedirect)
+ return
+}
+
+// handleRoot is the handler function for all HTTP requests at the root level.
+func handleRoot(w http.ResponseWriter, r *http.Request) {
+ log.Println("Fetching " + r.URL.Path)
+ if r.URL.Path == "/" || r.URL.Path == "/index.html" {
+ handleBugChomper(w, r)
+ return
+ }
+ http.NotFound(w, r)
+}
+
+// Run the BugChomper server.
+func main() {
+ var public bool
+ flag.BoolVar(
+ &public, "public", false, "Make this server publicly accessible.")
+ flag.Parse()
+
+ http.HandleFunc("/", handleRoot)
+ http.HandleFunc(oauthCallbackPath, handleOAuth2Callback)
+ http.Handle("/res/", http.FileServer(http.Dir(curdir)))
+ port := ":" + strconv.Itoa(defaultPort)
+ log.Println("Server is running at " + scheme + "://" + localHost + port)
+ var err error
+ if public {
+ log.Println("WARNING: This server is not secure and should not be made " +
+ "publicly accessible.")
+ scheme = "https"
+ err = http.ListenAndServeTLS(port, certFile, keyFile, nil)
+ } else {
+ scheme = "http"
+ err = http.ListenAndServe(localHost+port, nil)
+ }
+ if err != nil {
+ log.Println(err.Error())
+ }
+}