From f84b23ace423703aa239af8f3fb635042c30ec8b Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Fri, 4 May 2018 23:20:51 +0000 Subject: [PATCH] It compiles! --- handlers.go | 104 +++++++++++++++++++++++++++++++++++++++++++++++++ maintenance.go | 23 +++++------ mothd.go | 80 ++++++++++++++++++++++++++++++------- points.go | 91 ++++++++++++++++++++++++++++++------------- 4 files changed, 245 insertions(+), 53 deletions(-) diff --git a/handlers.go b/handlers.go index 9051185..e9f430f 100644 --- a/handlers.go +++ b/handlers.go @@ -3,5 +3,109 @@ package main import ( "fmt" "net/http" + "os" + "regexp" + "strings" + "strconv" ) +func registerHandler(w http.ResponseWriter, req *http.Request) { + teamname := req.FormValue("n") + teamid := req.FormValue("h") + + if matched, _ := regexp.MatchString("[^0-9a-z]", teamid); matched { + teamid = "" + } + + if (teamid == "") || (teamname == "") { + showPage(w, "Invalid Entry", "Oops! Are you sure you got that right?") + return + } + + if ! anchoredSearch(statePath("assigned.txt"), teamid, 0) { + showPage(w, "Invalid Team ID", "I don't have a record of that team ID. Maybe you used capital letters accidentally?") + return + } + + f, err := os.OpenFile(statePath("state", teamid), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644) + if err != nil { + showPage( + w, + "Registration failed", + "Unable to register. Perhaps a teammate has already registered?", + ) + return + } + defer f.Close() + fmt.Fprintln(f, teamname) + showPage(w, "Success", "Okay, your team has been named and you may begin using your team ID!") +} + +func tokenHandler(w http.ResponseWriter, req *http.Request) { + teamid := req.FormValue("t") + token := req.FormValue("k") + + // Check answer + if ! anchoredSearch(token, statePath("tokens.txt"), 0) { + showPage(w, "Unrecognized token", "I don't recognize that token. Did you type in the whole thing?") + return + } + + parts := strings.Split(token, ":") + category := "" + pointstr := "" + if len(parts) >= 2 { + category = parts[0] + pointstr = parts[1] + } + points, err := strconv.Atoi(pointstr) + if err != nil { + points = 0 + } + // Defang category name; prevent directory traversal + if matched, _ := regexp.MatchString("^[A-Za-z0-9_-]", category); matched { + category = "" + } + + if (category == "") || (points == 0) { + showPage(w, "Unrecognized token", "Something doesn't look right about that token") + return + } + + if err := awardPoints(teamid, category, points); err != nil { + showPage(w, "Error awarding points", err.Error()) + return + } + showPage(w, "Points awarded", fmt.Sprintf("%d points for %s!", points, teamid)) +} + +func answerHandler(w http.ResponseWriter, req *http.Request) { + teamid := req.FormValue("t") + category := req.FormValue("c") + pointstr := req.FormValue("p") + answer := req.FormValue("a") + + points, err := strconv.Atoi(pointstr) + if err != nil { + points = 0 + } + + // Defang category name; prevent directory traversal + if matched, _ := regexp.MatchString("^[A-Za-z0-9_-]", category); matched { + category = "" + } + + // Check answer + needle := fmt.Sprintf("%s %s", points, answer) + haystack := mothPath("packages", category, "answers.txt") + if ! anchoredSearch(haystack, needle, 0) { + showPage(w, "Wrong answer", err.Error()) + } + + if err := awardPoints(teamid, category, points); err != nil { + showPage(w, "Error awarding points", err.Error()) + return + } + showPage(w, "Points awarded", fmt.Sprintf("%d points for %s!", points, teamid)) +} + diff --git a/maintenance.go b/maintenance.go index 827d49e..93fca5a 100644 --- a/maintenance.go +++ b/maintenance.go @@ -4,19 +4,14 @@ import ( "log" "io/ioutil" "time" + "os" "strings" ) -func allfiles(dirpath) []string { - files, err := ioutil.ReadDir(dirpath) - if (err != nil) { - log.Printf("Error reading directory %s: %s", dirpath, err) - return [] - } - return files +func cacheMothball(filepath string, categoryName string) { + log.Printf("I'm exploding a mothball %s %s", filepath, categoryName) } - // maintenance runs func tidy() { // Skip if we've been disabled @@ -37,11 +32,17 @@ func tidy() { log.Print("Hello, I'm maintaining!") - // + // Make sure points directories exist + os.Mkdir(statePath("points.tmp"), 0755) + os.Mkdir(statePath("points.new"), 0755) + // Get current list of categories - // newCategories := []string{} - for f := range(allfiles(mothPath("packages"))) { + files, err := ioutil.ReadDir(mothPath("packages")) + if err != nil { + log.Printf("Error reading packages: %s", err) + } + for _, f := range files { filename := f.Name() filepath := mothPath("packages", filename) if ! strings.HasSuffix(filename, ".mb") { diff --git a/mothd.go b/mothd.go index 3ae0c26..08e0e99 100644 --- a/mothd.go +++ b/mothd.go @@ -1,41 +1,90 @@ package main import ( + "bufio" "fmt" - "html" - "io/ioutil" "log" "net/http" + "io/ioutil" "os" "path" - "strconv" "strings" "time" ) -var basePath = "." +var basePath = "/home/neale/src/moth" var maintenanceInterval = 20 * time.Second var categories = []string{} +// anchoredSearch looks for needle in filename, +// skipping the first skip space-delimited words +func anchoredSearch(filename string, needle string, skip int) bool { + f, err := os.Open(filename) + if err != nil { + log.Print("Can't open %s: %s", filename, err) + return false + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + parts := strings.SplitN(" ", line, skip+1) + if parts[skip+1] == needle { + return true + } + } -func mooHandler(w http.ResponseWriter, req *http.Request) { - moo := req.FormValue("moo") - fmt.Fprintf(w, "Hello, %q. %s", html.EscapeString(req.URL.Path), html.EscapeString(moo)) + return false } -func rootHandler(w http.ResponseWriter, req *http.Request) { - if req.URL.Path != "/" { - http.NotFound(w, req) - return + +func awardPoints(teamid string, category string, points int) error { + fn := fmt.Sprintf("%s-%s-%d", teamid, category, points) + tmpfn := statePath("points.tmp", fn) + newfn := statePath("points.new", fn) + + contents := fmt.Sprintf("%d %s %s %d\n", time.Now().Unix(), teamid, points) + + if err := ioutil.WriteFile(tmpfn, []byte(contents), 0644); err != nil { + return err } + + if err := os.Rename(tmpfn, newfn); err != nil { + return err + } + + return nil +} + +func showPage(w http.ResponseWriter, title string, body string) { + w.WriteHeader(http.StatusOK) + + fmt.Fprintf(w, "") + fmt.Fprintf(w, "") + fmt.Fprintf(w, "%s", title) + fmt.Fprintf(w, "") + fmt.Fprintf(w, "") + fmt.Fprintf(w, "

%s

", title) + fmt.Fprintf(w, "
%s
", body) + fmt.Fprintf(w, "") + fmt.Fprintf(w, "") } func mothPath(parts ...string) string { - return path.Join(basePath, parts...) + tail := path.Join(parts...) + return path.Join(basePath, tail) } func statePath(parts ...string) string { - return path.Join(basePath, "state", parts...) + tail := path.Join(parts...) + return path.Join(basePath, "state", tail) } func exists(filename string) bool { @@ -48,8 +97,9 @@ func exists(filename string) bool { func main() { log.Print("Sup") go maintenance(); - http.HandleFunc("/", rootHandler) - http.HandleFunc("/moo/", mooHandler) + http.HandleFunc("/register", registerHandler) + http.HandleFunc("/token", tokenHandler) + http.HandleFunc("/answer", answerHandler) log.Fatal(http.ListenAndServe(":8080", nil)) } diff --git a/points.go b/points.go index 4314d36..9c3e2e2 100644 --- a/points.go +++ b/points.go @@ -1,17 +1,21 @@ package main import ( + "bufio" "fmt" + "io/ioutil" "log" "os" + "strconv" + "strings" + "time" ) type Award struct { - when time.Time, - team string, - category string, - points int, - comment string + when time.Time + team string + category string + points int } func ParseAward(s string) (*Award, error) { @@ -19,53 +23,86 @@ func ParseAward(s string) (*Award, error) { parts := strings.SplitN(s, " ", 5) if len(parts) < 4 { - return nil, Error("Malformed award string") + return nil, fmt.Errorf("Malformed award string") } - whenEpoch, err = strconv.Atoi(parts[0]) + whenEpoch, err := strconv.ParseInt(parts[0], 10, 64) if (err != nil) { - return nil, Errorf("Malformed timestamp: %s", parts[0]) + return nil, fmt.Errorf("Malformed timestamp: %s", parts[0]) } ret.when = time.Unix(whenEpoch, 0) ret.team = parts[1] ret.category = parts[2] - points, err = strconv.Atoi(parts[3]) + points, err := strconv.Atoi(parts[3]) if (err != nil) { - return nil, Errorf("Malformed points: %s", parts[3]) + return nil, fmt.Errorf("Malformed points: %s", parts[3]) } - - if len(parts) == 5 { - ret.comment = parts[4] - } - - return &ret + ret.points = points + + return &ret, nil } func (a *Award) String() string { - return fmt.Sprintf("%d %s %s %d %s", a.when.Unix(), a.team, a.category, a.points, a.comment) + return fmt.Sprintf("%d %s %s %d", a.when.Unix(), a.team, a.category, a.points) +} + +func pointsLog() []Award { + var ret []Award + + fn := statePath("points.log") + f, err := os.Open(fn) + if err != nil { + log.Printf("Unable to open %s: %s", fn, err) + return ret + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + cur, err := ParseAward(line) + if err != nil { + log.Printf("Skipping malformed award line %s: %s", line, err) + continue + } + ret = append(ret, *cur) + } + + return ret } // collectPoints gathers up files in points.new/ and appends their contents to points.log, // removing each points.new/ file as it goes. func collectPoints() { - pointsLog = os.OpenFile(statePath("points.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - defer pointsLog.Close() + logf, err := os.OpenFile(statePath("points.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + log.Printf("Can't append to points log: %s", err) + return + } + defer logf.Close() - for f := range allfiles(statePath("points.new")) { + files, err := ioutil.ReadDir(statePath("points.new")) + if err != nil { + log.Printf("Error reading packages: %s", err) + } + for _, f := range files { filename := statePath("points.new", f.Name()) - s := ioutil.ReadFile(filename) - award, err := ParseAward(s) - if (err != nil) { + s, err := ioutil.ReadFile(filename) + if err != nil { + log.Printf("Can't read points file %s: %s", filename, err) + continue + } + award, err := ParseAward(string(s)) + if err != nil { log.Printf("Can't parse award file %s: %s", filename, err) continue } - fmt.Fprintf(pointsLog, "%s\n", award.String()) + fmt.Fprintf(logf, "%s\n", award.String()) log.Print(award.String()) - pointsLog.Sync() - err := os.Remove(filename) - if (err != nil) { + logf.Sync() + if err := os.Remove(filename); err != nil { log.Printf("Unable to remove %s: %s", filename, err) } }