2018-05-02 15:45:45 -06:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2018-09-17 17:00:08 -06:00
|
|
|
"bufio"
|
|
|
|
"encoding/json"
|
2018-05-02 15:45:45 -06:00
|
|
|
"fmt"
|
2018-09-17 18:02:44 -06:00
|
|
|
"io"
|
2018-09-17 17:00:08 -06:00
|
|
|
"log"
|
2018-05-02 15:45:45 -06:00
|
|
|
"net/http"
|
2018-05-04 17:20:51 -06:00
|
|
|
"os"
|
|
|
|
"strconv"
|
2018-09-17 17:00:08 -06:00
|
|
|
"strings"
|
2018-05-02 15:45:45 -06:00
|
|
|
)
|
|
|
|
|
2018-09-17 17:40:05 -06:00
|
|
|
func respond(w http.ResponseWriter, req *http.Request, status Status, short string, format string, a ...interface{}) {
|
|
|
|
long := fmt.Sprintf(format, a...)
|
2018-09-17 17:00:08 -06:00
|
|
|
// This is a kludge. Do proper parsing when this causes problems.
|
|
|
|
accept := req.Header.Get("Accept")
|
|
|
|
if strings.Contains(accept, "application/json") {
|
2018-09-17 17:40:05 -06:00
|
|
|
ShowJSend(w, status, short, long)
|
2018-09-17 17:00:08 -06:00
|
|
|
} else {
|
2018-09-17 17:40:05 -06:00
|
|
|
ShowHtml(w, status, short, long)
|
2018-09-14 18:24:48 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-17 18:02:44 -06:00
|
|
|
// anchoredSearch looks for needle in r,
|
|
|
|
// skipping the first skip space-delimited words
|
|
|
|
func anchoredSearch(r io.Reader, needle string, skip int) bool {
|
|
|
|
scanner := bufio.NewScanner(r)
|
|
|
|
for scanner.Scan() {
|
|
|
|
line := scanner.Text()
|
|
|
|
parts := strings.SplitN(line, " ", skip+1)
|
|
|
|
if (len(parts) > skip) && (parts[skip] == needle) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// anchoredSearchFile performs an anchoredSearch on a given filename
|
|
|
|
func anchoredSearchFile(filename string, needle string, skip int) bool {
|
|
|
|
r, err := os.Open(filename)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
defer r.Close()
|
|
|
|
|
|
|
|
return anchoredSearch(r, needle, skip)
|
|
|
|
}
|
|
|
|
|
2018-09-14 18:24:48 -06:00
|
|
|
func (ctx Instance) registerHandler(w http.ResponseWriter, req *http.Request) {
|
2018-09-17 17:00:08 -06:00
|
|
|
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"
|
2018-05-04 17:20:51 -06:00
|
|
|
}
|
2018-09-17 17:00:08 -06:00
|
|
|
|
2018-05-04 17:20:51 -06:00
|
|
|
if (teamid == "") || (teamname == "") {
|
2018-09-17 17:00:08 -06:00
|
|
|
respond(
|
|
|
|
w, req, Fail,
|
|
|
|
"Invalid Entry",
|
|
|
|
"Either `id` or `name` was missing from this request.",
|
|
|
|
)
|
2018-05-04 17:20:51 -06:00
|
|
|
return
|
|
|
|
}
|
2018-09-17 17:00:08 -06:00
|
|
|
|
|
|
|
if !anchoredSearchFile(ctx.StatePath("teamids.txt"), teamid, 0) {
|
|
|
|
respond(
|
|
|
|
w, req, Fail,
|
|
|
|
"Invalid Team ID",
|
|
|
|
"I don't have a record of that team ID. Maybe you used capital letters accidentally?",
|
|
|
|
)
|
2018-05-04 17:20:51 -06:00
|
|
|
return
|
|
|
|
}
|
2018-09-17 17:00:08 -06:00
|
|
|
|
|
|
|
f, err := os.OpenFile(ctx.StatePath("teams", teamid), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
|
2018-05-04 17:20:51 -06:00
|
|
|
if err != nil {
|
2018-09-17 17:00:08 -06:00
|
|
|
log.Print(err)
|
|
|
|
respond(
|
|
|
|
w, req, Fail,
|
2018-05-04 17:20:51 -06:00
|
|
|
"Registration failed",
|
|
|
|
"Unable to register. Perhaps a teammate has already registered?",
|
|
|
|
)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
fmt.Fprintln(f, teamname)
|
2018-09-17 17:00:08 -06:00
|
|
|
respond(
|
|
|
|
w, req, Success,
|
|
|
|
"Team registered",
|
|
|
|
"Okay, your team has been named and you may begin using your team ID!",
|
|
|
|
)
|
2018-05-04 17:20:51 -06:00
|
|
|
}
|
|
|
|
|
2018-09-14 18:24:48 -06:00
|
|
|
func (ctx Instance) tokenHandler(w http.ResponseWriter, req *http.Request) {
|
2018-09-17 17:40:05 -06:00
|
|
|
teamid := req.FormValue("id")
|
|
|
|
token := req.FormValue("token")
|
|
|
|
|
|
|
|
var category string
|
|
|
|
var points int
|
|
|
|
var fluff string
|
2018-05-04 17:20:51 -06:00
|
|
|
|
2018-09-17 17:40:05 -06:00
|
|
|
stoken := strings.Replace(token, ":", " ", 2)
|
|
|
|
n, err := fmt.Sscanf(stoken, "%s %d %s", &category, &points, &fluff)
|
|
|
|
if err != nil || n != 3 {
|
2018-09-17 17:00:08 -06:00
|
|
|
respond(
|
|
|
|
w, req, Fail,
|
2018-09-17 17:40:05 -06:00
|
|
|
"Malformed token",
|
|
|
|
"That doesn't look like a token: %v.", err,
|
2018-09-17 17:00:08 -06:00
|
|
|
)
|
2018-05-04 17:20:51 -06:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-09-17 17:40:05 -06:00
|
|
|
if (category == "") || (points <= 0) {
|
|
|
|
respond(
|
|
|
|
w, req, Fail,
|
|
|
|
"Weird token",
|
|
|
|
"That token doesn't make any sense.",
|
|
|
|
)
|
|
|
|
return
|
2018-05-04 17:20:51 -06:00
|
|
|
}
|
2018-09-17 17:40:05 -06:00
|
|
|
|
|
|
|
f, err := ctx.OpenCategoryFile(category, "tokens.txt")
|
2018-05-04 17:20:51 -06:00
|
|
|
if err != nil {
|
2018-09-17 17:40:05 -06:00
|
|
|
respond(
|
|
|
|
w, req, Fail,
|
|
|
|
"Cannot list valid tokens",
|
|
|
|
err.Error(),
|
|
|
|
)
|
|
|
|
return
|
2018-05-04 17:20:51 -06:00
|
|
|
}
|
2018-09-17 17:40:05 -06:00
|
|
|
defer f.Close()
|
2018-09-17 17:00:08 -06:00
|
|
|
|
2018-09-17 17:40:05 -06:00
|
|
|
// Make sure the token is in the list
|
|
|
|
if !anchoredSearch(f, token, 0) {
|
2018-09-17 17:00:08 -06:00
|
|
|
respond(
|
|
|
|
w, req, Fail,
|
|
|
|
"Unrecognized token",
|
2018-09-17 17:40:05 -06:00
|
|
|
"I don't recognize that token. Did you type in the whole thing?",
|
2018-09-17 17:00:08 -06:00
|
|
|
)
|
2018-05-04 17:20:51 -06:00
|
|
|
return
|
|
|
|
}
|
2018-09-17 17:00:08 -06:00
|
|
|
|
2018-09-14 18:24:48 -06:00
|
|
|
if err := ctx.AwardPoints(teamid, category, points); err != nil {
|
2018-09-17 17:00:08 -06:00
|
|
|
respond(
|
|
|
|
w, req, Fail,
|
|
|
|
"Error awarding points",
|
|
|
|
err.Error(),
|
|
|
|
)
|
2018-05-04 17:20:51 -06:00
|
|
|
return
|
|
|
|
}
|
2018-09-17 17:00:08 -06:00
|
|
|
respond(
|
|
|
|
w, req, Success,
|
|
|
|
"Points awarded",
|
2018-09-17 17:40:05 -06:00
|
|
|
"%d points for %s!", points, teamid,
|
2018-09-17 17:00:08 -06:00
|
|
|
)
|
2018-05-04 17:20:51 -06:00
|
|
|
}
|
|
|
|
|
2018-09-14 18:24:48 -06:00
|
|
|
func (ctx Instance) answerHandler(w http.ResponseWriter, req *http.Request) {
|
2018-09-17 17:40:05 -06:00
|
|
|
teamid := req.FormValue("id")
|
|
|
|
category := req.FormValue("cat")
|
|
|
|
pointstr := req.FormValue("points")
|
|
|
|
answer := req.FormValue("answer")
|
2018-05-04 17:20:51 -06:00
|
|
|
|
|
|
|
points, err := strconv.Atoi(pointstr)
|
|
|
|
if err != nil {
|
2018-09-17 17:00:08 -06:00
|
|
|
respond(
|
|
|
|
w, req, Fail,
|
2018-09-17 17:40:05 -06:00
|
|
|
"Cannot parse point value",
|
|
|
|
"This doesn't look like an integer: %s", pointstr,
|
2018-09-17 17:00:08 -06:00
|
|
|
)
|
2018-09-14 18:24:48 -06:00
|
|
|
return
|
2018-05-04 17:20:51 -06:00
|
|
|
}
|
|
|
|
|
2018-09-17 17:40:05 -06:00
|
|
|
haystack, err := ctx.OpenCategoryFile(category, "answers.txt")
|
2018-09-14 18:24:48 -06:00
|
|
|
if err != nil {
|
2018-09-17 17:00:08 -06:00
|
|
|
respond(
|
2018-09-17 17:40:05 -06:00
|
|
|
w, req, Fail,
|
|
|
|
"Cannot list answers",
|
|
|
|
"Unable to read the list of answers for this category.",
|
2018-09-17 17:00:08 -06:00
|
|
|
)
|
2018-09-14 18:24:48 -06:00
|
|
|
return
|
|
|
|
}
|
|
|
|
defer haystack.Close()
|
2018-09-17 17:00:08 -06:00
|
|
|
|
2018-09-14 18:24:48 -06:00
|
|
|
// Look for the answer
|
|
|
|
needle := fmt.Sprintf("%d %s", points, answer)
|
2018-09-17 17:00:08 -06:00
|
|
|
if !anchoredSearch(haystack, needle, 0) {
|
|
|
|
respond(
|
|
|
|
w, req, Fail,
|
|
|
|
"Wrong answer",
|
2018-09-17 17:40:05 -06:00
|
|
|
"That is not the correct answer for %s %d.", category, points,
|
2018-09-17 17:00:08 -06:00
|
|
|
)
|
2018-09-14 18:24:48 -06:00
|
|
|
return
|
2018-05-04 17:20:51 -06:00
|
|
|
}
|
|
|
|
|
2018-09-17 18:02:44 -06:00
|
|
|
if err := ctx.AwardPointsUniquely(teamid, category, points); err != nil {
|
2018-09-17 17:00:08 -06:00
|
|
|
respond(
|
|
|
|
w, req, Error,
|
|
|
|
"Error awarding points",
|
|
|
|
err.Error(),
|
|
|
|
)
|
2018-05-04 17:20:51 -06:00
|
|
|
return
|
|
|
|
}
|
2018-09-17 17:00:08 -06:00
|
|
|
respond(
|
|
|
|
w, req, Success,
|
|
|
|
"Points awarded",
|
|
|
|
fmt.Sprintf("%d points for %s!", points, teamid),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
type PuzzleMap struct {
|
|
|
|
Points int `json:"points"`
|
|
|
|
Path string `json:"path"`
|
2018-05-04 17:20:51 -06:00
|
|
|
}
|
|
|
|
|
2018-09-14 18:24:48 -06:00
|
|
|
func (ctx Instance) puzzlesHandler(w http.ResponseWriter, req *http.Request) {
|
2018-09-17 17:00:08 -06:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
|
|
|
|
res := map[string][]PuzzleMap{}
|
|
|
|
for catName, mb := range ctx.Categories {
|
|
|
|
mf, err := mb.Open("map.txt")
|
|
|
|
if err != nil {
|
|
|
|
log.Print(err)
|
|
|
|
}
|
|
|
|
defer mf.Close()
|
|
|
|
|
|
|
|
pm := make([]PuzzleMap, 0, 30)
|
|
|
|
scanner := bufio.NewScanner(mf)
|
|
|
|
for scanner.Scan() {
|
|
|
|
line := scanner.Text()
|
2018-09-17 17:40:05 -06:00
|
|
|
|
|
|
|
var pointval int
|
|
|
|
var dir string
|
|
|
|
|
|
|
|
n, err := fmt.Sscanf(line, "%d %s", &pointval, &dir)
|
2018-09-17 17:00:08 -06:00
|
|
|
if err != nil {
|
2018-09-17 17:40:05 -06:00
|
|
|
log.Printf("Parsing map for %s: %v", catName, err)
|
|
|
|
continue
|
|
|
|
} else if n != 2 {
|
|
|
|
log.Printf("Parsing map for %s: short read", catName)
|
2018-09-17 17:00:08 -06:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
pm = append(pm, PuzzleMap{pointval, dir})
|
|
|
|
log.Print(pm)
|
|
|
|
}
|
|
|
|
|
|
|
|
res[catName] = pm
|
|
|
|
log.Print(res)
|
2018-05-08 12:45:50 -06:00
|
|
|
}
|
2018-09-17 17:00:08 -06:00
|
|
|
jres, _ := json.Marshal(res)
|
|
|
|
w.Write(jres)
|
2018-05-08 12:45:50 -06:00
|
|
|
}
|
|
|
|
|
2018-09-14 18:24:48 -06:00
|
|
|
func (ctx Instance) pointsHandler(w http.ResponseWriter, req *http.Request) {
|
2018-09-17 17:00:08 -06:00
|
|
|
log := ctx.PointsLog()
|
|
|
|
jlog, err := json.Marshal(log)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
2018-05-06 21:37:52 -06:00
|
|
|
return
|
|
|
|
}
|
2018-09-17 17:00:08 -06:00
|
|
|
|
|
|
|
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)
|
2018-05-06 21:37:52 -06:00
|
|
|
}
|
2018-09-14 18:24:48 -06:00
|
|
|
|
|
|
|
func (ctx Instance) BindHandlers(mux *http.ServeMux) {
|
2018-09-17 17:00:08 -06:00
|
|
|
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)
|
2018-09-14 18:24:48 -06:00
|
|
|
}
|