package main import ( "bufio" "encoding/json" "fmt" "io" "net/http" "os" "path/filepath" "strings" ) // 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) } type Status int const ( Success = iota Fail Error ) // ShowJSend renders a JSend response to w func ShowJSend(w http.ResponseWriter, status Status, short string, description string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) // RFC2616 makes it pretty clear that 4xx codes are for the user-agent statusStr := "" switch status { case Success: statusStr = "success" case Fail: statusStr = "fail" default: statusStr = "error" } jshort, _ := json.Marshal(short) jdesc, _ := json.Marshal(description) fmt.Fprintf( w, `{"status":"%s","data":{"short":%s,"description":%s}}"`, statusStr, jshort, jdesc, ) } // ShowHtml delevers an HTML response to w func ShowHtml(w http.ResponseWriter, status Status, title string, body string) { w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(http.StatusOK) statusStr := "" switch status { case Success: statusStr = "Success" case Fail: statusStr = "Fail" default: statusStr = "Error" } fmt.Fprintf(w, "") fmt.Fprintf(w, "
") fmt.Fprintf(w, "If someone on your team has already registered, proceed to the puzzles overview.
`, ) } func staticScoreboard(w http.ResponseWriter) { ShowHtml( w, Success, "Scoreboard", "XXX: This would be the scoreboard", ) } func staticPuzzles(w http.ResponseWriter) { ShowHtml( w, Success, "Puzzles", "XXX: This would be the puzzles overview", ) } func tryServeFile(w http.ResponseWriter, req *http.Request, path string) bool { f, err := os.Open(path) if err != nil { return false } defer f.Close() d, err := f.Stat() if err != nil { return false } http.ServeContent(w, req, path, d.ModTime(), f) return true } func ServeStatic(w http.ResponseWriter, req *http.Request, resourcesDir string) { path := req.URL.Path if strings.Contains(path, "..") { http.Error(w, "Invalid URL path", http.StatusBadRequest) return } if path == "/" { path = "/index.html" } fpath := filepath.Join(resourcesDir, path) if tryServeFile(w, req, fpath) { return } switch path { case "/basic.css": staticStylesheet(w) case "/index.html": staticIndex(w) case "/scoreboard.html": staticScoreboard(w) case "/puzzles.html": staticPuzzles(w) default: http.NotFound(w, req) } }