package main import ( "bufio" "encoding/json" "fmt" "io" "io/ioutil" "log" "net/http" "os" "rand" "strconv" "strings" ) func respond(w http.ResponseWriter, req *http.Request, status Status, short string, format string, a ...interface{}) { long := fmt.Sprintf(format, a...) // This is a kludge. Do proper parsing when this causes problems. accept := req.Header.Get("Accept") if strings.Contains(accept, "application/json") { ShowJSend(w, status, short, long) } else { ShowHtml(w, status, short, long) } } // hasLine returns true if line appears in r. // The entire line must match. func hasLine(r io.Reader, line string) bool { scanner := bufio.NewScanner(r) for scanner.Scan() { if scanner.Text() == line { return true } } return false } func (ctx Instance) registerHandler(w http.ResponseWriter, req *http.Request) { teamname := req.FormValue("name") teamid := req.FormValue("id") // Keep foolish operators from shooting themselves in the foot // You would have to add a pathname to your list of Team IDs to open this vulnerability, // but I have learned not to overestimate people. if strings.Contains(teamid, "../") { teamid = "rodney" } if (teamid == "") || (teamname == "") { respond( w, req, Fail, "Invalid Entry", "Either `id` or `name` was missing from this request.", ) return } teamids, err := os.Open(ctx.StatePath("teamids.txt")) if err != nil { respond( w, req, Fail, "Cannot read valid team IDs", "An error was encountered trying to read valid teams IDs: %v", err, ) return } defer teamids.Close() if !hasLine(teamids, teamid) { respond( w, req, Fail, "Invalid Team ID", "I don't have a record of that team ID. Maybe you used capital letters accidentally?", ) return } f, err := os.OpenFile(ctx.StatePath("teams", teamid), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644) if err != nil { log.Print(err) respond( w, req, Fail, "Registration failed", "Unable to register. Perhaps a teammate has already registered?", ) return } defer f.Close() fmt.Fprintln(f, teamname) respond( w, req, Success, "Team registered", "Okay, your team has been named and you may begin using your team ID!", ) } func (ctx Instance) tokenHandler(w http.ResponseWriter, req *http.Request) { teamid := req.FormValue("id") token := req.FormValue("token") var category string var points int var fluff string stoken := strings.Replace(token, ":", " ", 2) n, err := fmt.Sscanf(stoken, "%s %d %s", &category, &points, &fluff) if err != nil || n != 3 { respond( w, req, Fail, "Malformed token", "That doesn't look like a token: %v.", err, ) return } if (category == "") || (points <= 0) { respond( w, req, Fail, "Weird token", "That token doesn't make any sense.", ) return } f, err := ctx.OpenCategoryFile(category, "tokens.txt") if err != nil { respond( w, req, Fail, "Cannot list valid tokens", err.Error(), ) return } defer f.Close() // Make sure the token is in the list if !hasLine(f, token) { respond( w, req, Fail, "Unrecognized token", "I don't recognize that token. Did you type in the whole thing?", ) return } if err := ctx.AwardPoints(teamid, category, points); err != nil { respond( w, req, Fail, "Error awarding points", err.Error(), ) return } respond( w, req, Success, "Points awarded", "%d points for %s!", points, teamid, ) } func (ctx Instance) answerHandler(w http.ResponseWriter, req *http.Request) { teamid := req.FormValue("id") category := req.FormValue("cat") pointstr := req.FormValue("points") answer := req.FormValue("answer") points, err := strconv.Atoi(pointstr) if err != nil { respond( w, req, Fail, "Cannot parse point value", "This doesn't look like an integer: %s", pointstr, ) return } haystack, err := ctx.OpenCategoryFile(category, "answers.txt") if err != nil { respond( w, req, Fail, "Cannot list answers", "Unable to read the list of answers for this category.", ) return } defer haystack.Close() // Look for the answer needle := fmt.Sprintf("%d %s", points, answer) if !hasLine(haystack, needle) { respond( w, req, Fail, "Wrong answer", "That is not the correct answer for %s %d.", category, points, ) return } if err := ctx.AwardPoints(teamid, category, points); err != nil { respond( w, req, Error, "Error awarding points", err.Error(), ) return } respond( w, req, Success, "Points awarded", fmt.Sprintf("%d points for %s!", points, teamid), ) } type PuzzleMap struct { Points int Path string } func (pm *PuzzleMap) MarshalJSON() ([]byte, error) { if pm == nil { return []byte("null"), nil } jPath, err := json.Marshal(pm.Path) if err != nil { return nil, err } ret := fmt.Sprintf("[%d,%s]", pm.Points, string(jPath)) return []byte(ret), nil } func (ctx Instance) puzzlesHandler(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) maxByCategory := map[string]int{} for _, a := range ctx.PointsLog() { if a.Points > maxByCategory[a.Category] { maxByCategory[a.Category] = a.Points } } res := map[string][]PuzzleMap{} for catName, mb := range ctx.Categories { mf, err := mb.Open("map.txt") if err != nil { log.Print(err) continue } defer mf.Close() pm := make([]PuzzleMap, 0, 30) completed := true scanner := bufio.NewScanner(mf) for scanner.Scan() { line := scanner.Text() var pointval int var dir string n, err := fmt.Sscanf(line, "%d %s", &pointval, &dir) if err != nil { log.Printf("Parsing map for %s: %v", catName, err) continue } else if n != 2 { log.Printf("Parsing map for %s: short read", catName) continue } pm = append(pm, PuzzleMap{pointval, dir}) if pointval > maxByCategory[catName] { completed = false break } } if completed { pm = append(pm, PuzzleMap{0, ""}) } res[catName] = pm } jres, _ := json.Marshal(res) w.Write(jres) } func (ctx Instance) pointsHandler(w http.ResponseWriter, req *http.Request) { plog := ctx.PointsLog() jlog, err := json.Marshal(plog) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // XXX: go through plog, building an array of teams, so we can anonymize team IDs w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write(jlog) } func (ctx Instance) staticHandler(w http.ResponseWriter, req *http.Request) { ServeStatic(w, req, ctx.ResourcesDir) } func (ctx Instance) BindHandlers(mux *http.ServeMux) { mux.HandleFunc(ctx.Base+"/", ctx.staticHandler) mux.HandleFunc(ctx.Base+"/register", ctx.registerHandler) mux.HandleFunc(ctx.Base+"/token", ctx.tokenHandler) mux.HandleFunc(ctx.Base+"/answer", ctx.answerHandler) mux.HandleFunc(ctx.Base+"/puzzles.json", ctx.puzzlesHandler) mux.HandleFunc(ctx.Base+"/points.json", ctx.pointsHandler) }