2018-05-02 15:45:45 -06:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2018-05-04 17:20:51 -06:00
|
|
|
"os"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
"strconv"
|
2018-09-14 18:24:48 -06:00
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"bufio"
|
2018-05-02 15:45:45 -06:00
|
|
|
)
|
|
|
|
|
2018-09-14 18:24:48 -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
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (ctx Instance) registerHandler(w http.ResponseWriter, req *http.Request) {
|
2018-05-04 17:20:51 -06:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-09-14 18:24:48 -06:00
|
|
|
if ! anchoredSearchFile(ctx.StatePath("teamids.txt"), teamid, 0) {
|
2018-05-04 17:20:51 -06:00
|
|
|
showPage(w, "Invalid Team ID", "I don't have a record of that team ID. Maybe you used capital letters accidentally?")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-09-14 18:24:48 -06:00
|
|
|
f, err := os.OpenFile(ctx.StatePath(teamid), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
|
2018-05-04 17:20:51 -06:00
|
|
|
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!")
|
|
|
|
}
|
|
|
|
|
2018-09-14 18:24:48 -06:00
|
|
|
func (ctx Instance) tokenHandler(w http.ResponseWriter, req *http.Request) {
|
2018-05-04 17:20:51 -06:00
|
|
|
teamid := req.FormValue("t")
|
|
|
|
token := req.FormValue("k")
|
|
|
|
|
|
|
|
// Check answer
|
2018-09-14 18:24:48 -06:00
|
|
|
if ! anchoredSearchFile(ctx.StatePath("tokens.txt"), token, 0) {
|
2018-05-04 17:20:51 -06:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-09-14 18:24:48 -06:00
|
|
|
if err := ctx.AwardPoints(teamid, category, points); err != nil {
|
2018-05-04 17:20:51 -06:00
|
|
|
showPage(w, "Error awarding points", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
showPage(w, "Points awarded", fmt.Sprintf("%d points for %s!", points, teamid))
|
|
|
|
}
|
|
|
|
|
2018-09-14 18:24:48 -06:00
|
|
|
func (ctx Instance) answerHandler(w http.ResponseWriter, req *http.Request) {
|
2018-05-04 17:20:51 -06:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-09-14 18:24:48 -06:00
|
|
|
catmb, ok := ctx.Categories[category]
|
|
|
|
if ! ok {
|
|
|
|
showPage(w, "Category does not exist", "The specified category does not exist. Sorry!")
|
|
|
|
return
|
2018-05-04 17:20:51 -06:00
|
|
|
}
|
|
|
|
|
2018-09-14 18:24:48 -06:00
|
|
|
// Get the answers
|
|
|
|
haystack, err := catmb.Open("answers.txt")
|
|
|
|
if err != nil {
|
|
|
|
showPage(w, "Answers do not exist",
|
|
|
|
"Please tell the contest people that the mothball for this category has no answers.txt in it!")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer haystack.Close()
|
|
|
|
|
|
|
|
// Look for the answer
|
|
|
|
needle := fmt.Sprintf("%d %s", points, answer)
|
2018-05-04 17:20:51 -06:00
|
|
|
if ! anchoredSearch(haystack, needle, 0) {
|
|
|
|
showPage(w, "Wrong answer", err.Error())
|
2018-09-14 18:24:48 -06:00
|
|
|
return
|
2018-05-04 17:20:51 -06:00
|
|
|
}
|
|
|
|
|
2018-09-14 18:24:48 -06:00
|
|
|
if err := ctx.AwardPoints(teamid, category, points); err != nil {
|
2018-05-04 17:20:51 -06:00
|
|
|
showPage(w, "Error awarding points", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
showPage(w, "Points awarded", fmt.Sprintf("%d points for %s!", points, teamid))
|
|
|
|
}
|
|
|
|
|
2018-09-14 18:24:48 -06:00
|
|
|
func (ctx Instance) puzzlesHandler(w http.ResponseWriter, req *http.Request) {
|
2018-05-08 12:45:50 -06:00
|
|
|
puzzles := map[string][]interface{}{}
|
|
|
|
// v := map[string][]interface{}{"Moo": {1, "0177f85ae895a33e2e7c5030c3dc484e8173e55c"}}
|
|
|
|
// j, _ := json.Marshal(v)
|
|
|
|
|
2018-09-14 18:24:48 -06:00
|
|
|
for _, category := range ctx.Categories {
|
|
|
|
log.Print(puzzles, category)
|
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-05-08 12:45:50 -06:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-05-06 21:37:52 -06:00
|
|
|
// staticHandler serves up static files.
|
2018-09-14 18:24:48 -06:00
|
|
|
func (ctx Instance) rootHandler(w http.ResponseWriter, req *http.Request) {
|
2018-05-06 21:37:52 -06:00
|
|
|
if req.URL.Path == "/" {
|
|
|
|
showPage(
|
|
|
|
w,
|
|
|
|
"Welcome",
|
|
|
|
`
|
|
|
|
<h2>Register your team</h2>
|
|
|
|
|
|
|
|
<form action="register" action="post">
|
|
|
|
Team ID: <input name="h"> <br>
|
|
|
|
Team name: <input name="n">
|
|
|
|
<input type="submit" value="Register">
|
|
|
|
</form>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
If someone on your team has already registered,
|
|
|
|
proceed to the
|
|
|
|
<a href="puzzles">puzzles overview</a>.
|
|
|
|
</div>
|
|
|
|
`,
|
|
|
|
)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
http.NotFound(w, req)
|
|
|
|
}
|
2018-09-14 18:24:48 -06:00
|
|
|
|
|
|
|
func (ctx Instance) BindHandlers(mux *http.ServeMux) {
|
|
|
|
mux.HandleFunc(ctx.Base + "/", ctx.rootHandler)
|
|
|
|
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)
|
|
|
|
}
|