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, "