diff --git a/Dockerfile.moth b/Dockerfile.moth index 43e55e1..84330fa 100644 --- a/Dockerfile.moth +++ b/Dockerfile.moth @@ -5,4 +5,5 @@ RUN go build -o /mothd /src/*.go FROM alpine COPY --from=builder /mothd /mothd +COPY theme /theme ENTRYPOINT [ "/mothd" ] diff --git a/build.sh b/build.sh index d0cec51..6b32568 100755 --- a/build.sh +++ b/build.sh @@ -4,6 +4,7 @@ set -e version=$(date +%Y%m%d%H%M) +cd $(dirname $0) for img in moth moth-devel; do echo "==== $img" sudo docker build --build-arg http_proxy=$http_proxy --build-arg https_proxy=$https_proxy --tag dirtbags/$img --tag dirtbags/$img:$version -f Dockerfile.$img . diff --git a/src/handlers.go b/src/handlers.go index b5e6590..5f4f524 100644 --- a/src/handlers.go +++ b/src/handlers.go @@ -13,11 +13,11 @@ import ( ) type JSend struct { - Status string `json:"status"` - Data JSendData `json:"data"` + Status string `json:"status"` + Data JSendData `json:"data"` } type JSendData struct { - Short string `json:"short"` + Short string `json:"short"` Description string `json:"description"` } @@ -27,7 +27,7 @@ func ShowJSend(w http.ResponseWriter, status Status, short string, description s resp := JSend{ Status: "success", Data: JSendData{ - Short: short, + Short: short, Description: description, }, } @@ -41,16 +41,24 @@ func ShowJSend(w http.ResponseWriter, status Status, short string, description s } respBytes, err := json.Marshal(resp) - if (err != nil) { + if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - + w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) // RFC2616 makes it pretty clear that 4xx codes are for the user-agent w.Write(respBytes) } +type Status int + +const ( + Success = iota + Fail + Error +) + // 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") @@ -331,7 +339,29 @@ func (ctx *Instance) contentHandler(w http.ResponseWriter, req *http.Request) { } func (ctx *Instance) staticHandler(w http.ResponseWriter, req *http.Request) { - ServeStatic(w, req, ctx.ResourcesDir) + path := req.URL.Path + if strings.Contains(path, "..") { + http.Error(w, "Invalid URL path", http.StatusBadRequest) + return + } + if path == "/" { + path = "/index.html" + } + + f, err := os.Open(ctx.ResourcePath(path)) + if err != nil { + http.NotFound(w, req) + return + } + defer f.Close() + + d, err := f.Stat() + if err != nil { + http.NotFound(w, req) + return + } + + http.ServeContent(w, req, path, d.ModTime(), f) } func (ctx *Instance) BindHandlers(mux *http.ServeMux) { diff --git a/src/instance.go b/src/instance.go index 4a73e03..2794768 100644 --- a/src/instance.go +++ b/src/instance.go @@ -93,6 +93,11 @@ func (ctx *Instance) StatePath(parts ...string) string { return path.Join(ctx.StateDir, tail) } +func (ctx *Instance) ResourcePath(parts ...string) string { + tail := path.Join(parts...) + return path.Join(ctx.ResourcesDir, tail) +} + func (ctx *Instance) PointsLog() []*Award { var ret []*Award diff --git a/src/maintenance.go b/src/maintenance.go index 0879d68..5f08fe6 100644 --- a/src/maintenance.go +++ b/src/maintenance.go @@ -208,7 +208,7 @@ func (ctx *Instance) isEnabled() bool { log.Print("Suspended: disabled file found") return false } - + untilspec, err := ioutil.ReadFile(ctx.StatePath("until")) if err == nil { untilspecs := strings.TrimSpace(string(untilspec)) diff --git a/src/mothd.go b/src/mothd.go index acfda71..5834f24 100644 --- a/src/mothd.go +++ b/src/mothd.go @@ -27,18 +27,18 @@ func main() { ) mothballDir := flag.String( "mothballs", - "/moth/mothballs", + "/mothballs", "Path to read mothballs", ) stateDir := flag.String( "state", - "/moth/state", + "/state", "Path to write state", ) - resourcesDir := flag.String( - "resources", - "/moth/resources", - "Path to static resources (HTML, images, css, ...)", + themeDir := flag.String( + "theme", + "/theme", + "Path to static theme resources (HTML, images, css, ...)", ) maintenanceInterval := flag.Duration( "maint", @@ -56,7 +56,7 @@ func main() { log.Fatal(err) } - ctx, err := NewInstance(*base, *mothballDir, *stateDir, *resourcesDir) + ctx, err := NewInstance(*base, *mothballDir, *stateDir, *themeDir) if err != nil { log.Fatal(err) } diff --git a/src/static.go b/src/static.go deleted file mode 100644 index 41a60bc..0000000 --- a/src/static.go +++ /dev/null @@ -1,435 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - "os" - "path/filepath" - "strings" -) - -type Status int - -const ( - Success = iota - Fail - Error -) - -// staticStylesheet serves up a basic stylesheet. -// This is designed to be usable on small touchscreens (like mobile phones) -func staticStylesheet(w http.ResponseWriter) { - w.Header().Set("Content-Type", "text/css") - w.WriteHeader(http.StatusOK) - - fmt.Fprint( - w, - ` -/* http://paletton.com/#uid=63T0u0k7O9o3ouT6LjHih7ltq4c */ -body { - font-family: sans-serif; - max-width: 40em; - background: #282a33; - color: #f6efdc; -} -a:any-link { - color: #8b969a; -} -h1 { - background: #5e576b; - color: #9e98a8; -} -.Fail, .Error { - background: #3a3119; - color: #ffcc98; -} -.Fail:before { - content: "Fail: "; -} -.Error:before { - content: "Error: "; -} -p { - margin: 1em 0em; -} -form, pre { - margin: 1em; -} -input { - padding: 0.6em; - margin: 0.2em; -} -nav { - border: solid black 2px; -} -nav ul, .category ul { - padding: 1em; -} -nav li, .category li { - display: inline; - margin: 1em; -} -iframe#body { - border: inherit; - width: 100%; -} -img { - max-width: 100%; -} -#scoreboard { - width: 100%; - position: relative; -} - -#scoreboard span { - font-size: 75%; - display: inline-block; - overflow: hidden; - height: 1.7em; -} -#scoreboard span.teamname { - font-size: inherit; - color: white; - text-shadow: 0 0 3px black; - opacity: 0.8; - position: absolute; - right: 0.2em; -} -#scoreboard div * {white-space: nowrap;} -.cat0, .cat8, .cat16 {background-color: #a6cee3; color: black;} -.cat1, .cat9, .cat17 {background-color: #1f78b4; color: white;} -.cat2, .cat10, .cat18 {background-color: #b2df8a; color: black;} -.cat3, .cat11, .cat19 {background-color: #33a02c; color: white;} -.cat4, .cat12, .cat20 {background-color: #fb9a99; color: black;} -.cat5, .cat13, .cat21 {background-color: #e31a1c; color: white;} -.cat6, .cat14, .cat22 {background-color: #fdbf6f; color: black;} -.cat7, .cat15, .cat23 {background-color: #ff7f00; color: black;} - `, - ) -} - -// staticIndex serves up a basic landing page -func staticIndex(w http.ResponseWriter) { - ShowHtml( - w, Success, - "Welcome", - ` -

Register your team

- -
- Team ID:
- Team name: - -
- -

- If someone on your team has already registered, - proceed to the - puzzles overview. -

- `, - ) -} - -func staticScoreboard(w http.ResponseWriter) { - ShowHtml( - w, Success, - "Scoreboard", - ` -
- - `, - ) -} - -func staticPuzzleList(w http.ResponseWriter) { - ShowHtml( - w, Success, - "Open Puzzles", - ` -
-
-
- - `, - ) -} - -func staticPuzzle(w http.ResponseWriter) { - ShowHtml( - w, Success, - "Open Puzzles", - ` -
-
Loading...
-
-
- - - Team ID:
- Answer:
- -
- - `, - ) -} - -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 "/puzzle-list.html": - staticPuzzleList(w) - case "/puzzle.html": - staticPuzzle(w) - default: - http.NotFound(w, req) - } -} diff --git a/res/basic.css b/theme/basic.css similarity index 100% rename from res/basic.css rename to theme/basic.css diff --git a/res/index.html b/theme/index.html similarity index 100% rename from res/index.html rename to theme/index.html diff --git a/res/puzzle-list.html b/theme/puzzle-list.html similarity index 100% rename from res/puzzle-list.html rename to theme/puzzle-list.html diff --git a/res/puzzle.html b/theme/puzzle.html similarity index 100% rename from res/puzzle.html rename to theme/puzzle.html diff --git a/res/scoreboard.html b/theme/scoreboard.html similarity index 100% rename from res/scoreboard.html rename to theme/scoreboard.html