package main import ( "crypto/sha1" "embed" "encoding/hex" "encoding/json" "html/template" "io/ioutil" "log" "net/http" "os" "regexp" "sort" "github.com/gorilla/mux" ) //go:embed *.json *.css *.js *.svg var content embed.FS //go:embed *.tmpl var internal embed.FS var ( templates *template.Template root string validToken *regexp.Regexp ) func main() { initRoot() validToken = regexp.MustCompile(`[a-fA-F0-9]+`) port := os.Getenv("DERELICTPORT") if port == "" { port = "8080" } templates = template.Must(template.ParseFS(internal, "*.tmpl")) router := mux.NewRouter() router.HandleFunc("/", handleRecent).Methods("GET") router.HandleFunc("/view/{id:[_0-9a-f]+}", handleView).Methods("GET") router.HandleFunc("/battles", handleBattlesPost).Methods("POST") router.HandleFunc("/battles/{id:[_0-9a-f]+}", handleBattlesId).Methods("GET") router.PathPrefix("/").Handler(http.FileServer(http.FS(content))) log.Fatal(http.ListenAndServe(":"+port, router)) } func initRoot() { root = os.Getenv("DERELICTROOT") if root == "" { root = ".derelict" } os.Mkdir(root, 0755) os.Mkdir(root+"/battles", 0755) os.Mkdir(root+"/tokens", 0700) } func handleBattlesPost(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("Derelict-Token") if !validToken.MatchString(token) { http.Error(w, "Invalid token", http.StatusBadRequest) return } _, err := os.Stat(root + "/tokens/" + token) if _, ok := err.(*os.PathError); ok { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } if err != nil { http.Error(w, "Unexpected error 1", http.StatusInternalServerError) return } type wreck struct { Team int `json:"team"` Id string `json:"id"` Hash string `json:"hash"` } var wrecks []wreck data, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, "Unexpected error 2", http.StatusInternalServerError) return } err = json.Unmarshal(data, &wrecks) if err != nil { http.Error(w, "Error in body", http.StatusBadRequest) return } hash := sha1.Sum(data) name := hex.EncodeToString(hash[:]) ioutil.WriteFile(root+"/battles/"+name, data, 0644) w.WriteHeader(http.StatusOK) w.Write([]byte(name)) } func handleBattlesId(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] w.Header().Add("Content-Type", "application/json") http.ServeFile(w, r, root+"/battles/"+id) } func handleRecent(w http.ResponseWriter, r *http.Request) { files, err := ioutil.ReadDir(root + "/battles") if err != nil { panic(err) } sort.Slice(files, func(lhs, rhs int) bool { return files[lhs].ModTime().After(files[rhs].ModTime()) }) count := 10 if count > len(files) { count = len(files) } templates.ExecuteTemplate(w, "recent", files[:count]) } func handleView(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] templates.ExecuteTemplate(w, "view", id) }