diff --git a/Dockerfile.moth-devel b/Dockerfile.moth-devel index 20c659a..6e8466a 100644 --- a/Dockerfile.moth-devel +++ b/Dockerfile.moth-devel @@ -16,6 +16,5 @@ COPY devel /app/ COPY example-puzzles /puzzles/ COPY theme /theme/ -WORKDIR /moth/ ENTRYPOINT [ "python3", "/app/devel-server.py" ] CMD [ "--bind", "0.0.0.0:8080", "--puzzles", "/puzzles", "--theme", "/theme" ] diff --git a/README.md b/README.md index 58f5d7d..9d9d9c9 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,14 @@ and comes with a JavaScript-based scoreboard to display team rankings. Running a Development Server ============================ +To use example puzzles + docker run --rm -it -p 8080:8080 dirtbags/moth-devel +or, to use your own puzzles + + docker run --rm -it -p 8080:8080 -v /path/to/puzzles:/puzzles:ro dirtbags/moth-devel + And point a browser to http://localhost:8080/ (or whatever host is running the server). The development server includes a number of Python libraries that we have found useful in writing puzzles. diff --git a/VERSION b/VERSION index 0ff4b45..eb39e53 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.1-rc2 +3.3 diff --git a/contrib/smash b/contrib/smash new file mode 100755 index 0000000..0e958f0 --- /dev/null +++ b/contrib/smash @@ -0,0 +1,16 @@ +#! /bin/sh + +## Run two of these to trigger the race condition from + +BASEURL=http://localhost:8080 +URL=$BASEURL/answer + +while true; do + curl \ + -X POST \ + -F "cat=byobf" \ + -F "points=10" \ + -F "id=test" \ + -F "answer=6" \ + $URL +done diff --git a/devel/devel-server.py b/devel/devel-server.py index 61d3d41..f81c0cc 100755 --- a/devel/devel-server.py +++ b/devel/devel-server.py @@ -14,18 +14,25 @@ import os import pathlib import random import shutil -import socketserver import sys import traceback import mothballer import parse +import urllib.parse +import posixpath from http import HTTPStatus sys.dont_write_bytecode = True # Don't write .pyc files +try: + ThreadingHTTPServer = http.server.ThreadingHTTPServer +except AttributeError: + import socketserver + class ThreadingHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer): + daemon_threads = True -class MothServer(http.server.ThreadingHTTPServer): +class MothServer(ThreadingHTTPServer): def __init__(self, server_address, RequestHandlerClass): super().__init__(server_address, RequestHandlerClass) self.args = {} @@ -35,13 +42,28 @@ class MothRequestHandler(http.server.SimpleHTTPRequestHandler): endpoints = [] def __init__(self, request, client_address, server): - super().__init__(request, client_address, server, directory=server.args["theme_dir"]) + self.directory = str(server.args["theme_dir"]) + try: + super().__init__(request, client_address, server, directory=server.args["theme_dir"]) + except TypeError: + super().__init__(request, client_address, server) + + + # Backport from Python 3.7 + def translate_path(self, path): + # I guess we just hope that some other thread doesn't call getcwd + getcwd = os.getcwd + os.getcwd = lambda: self.directory + ret = super().translate_path(path) + os.getcwd = getcwd + return ret def get_puzzle(self): category = self.req.get("cat") points = int(self.req.get("points")) - cat = moth.Category(self.server.args["puzzles_dir"].joinpath(category), self.seed) + catpath = str(self.server.args["puzzles_dir"].joinpath(category)) + cat = moth.Category(catpath, self.seed) puzzle = cat.puzzle(points) return puzzle @@ -75,7 +97,7 @@ class MothRequestHandler(http.server.SimpleHTTPRequestHandler): if not p.is_dir() or p.match(".*"): continue catName = p.parts[-1] - cat = moth.Category(p, self.seed) + cat = moth.Category(str(p), self.seed) puzzles[catName] = [[i, str(i)] for i in cat.pointvals()] puzzles[catName].append([0, ""]) if len(puzzles) <= 1: @@ -255,8 +277,6 @@ if __name__ == '__main__': logging.basicConfig(level=logging.INFO) - mydir = os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))) - server = MothServer((addr, port), MothRequestHandler) server.args["base_url"] = args.base server.args["puzzles_dir"] = pathlib.Path(args.puzzles) diff --git a/devel/moth.py b/devel/moth.py index 6a62116..6d04aa2 100644 --- a/devel/moth.py +++ b/devel/moth.py @@ -12,6 +12,7 @@ import os import random import string import tempfile +import shlex messageChars = b'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' @@ -112,7 +113,7 @@ class Puzzle: elif key == 'name': pass elif key == 'file': - parts = val.split() + parts = shlex.split(val) name = parts[0] hidden = False stream = open(name, 'rb') @@ -264,10 +265,10 @@ class Puzzle: def html_body(self): """Format and return the markdown for the puzzle body.""" return mistune.markdown(self.get_body(), escape=False) - + def package(self, answers=False): """Return a dict packaging of the puzzle.""" - + files = [fn for fn,f in self.files.items() if f.visible] return { 'authors': self.authors, diff --git a/docs/CREDITS.md b/docs/CREDITS.md index ada9f18..367b28c 100644 --- a/docs/CREDITS.md +++ b/docs/CREDITS.md @@ -5,6 +5,7 @@ Being in this list is voluntary. Add your name when you contribute code. * Paul Ferrell * Shannon Steinfadt * John Donaldson +* 3ch01c Word List --------- diff --git a/example-puzzles/example/5/helpers.js b/example-puzzles/example/5/helpers.js index 3c566bc..39f4231 100644 --- a/example-puzzles/example/5/helpers.js +++ b/example-puzzles/example/5/helpers.js @@ -17,7 +17,14 @@ function helperUpdateAnswer(event) { values.push(c.value) } } - value = values.join(",") + if (e.classList.contains("sort")) { + values.sort() + } + let join = e.dataset.join + if (join === undefined) { + join = "," + } + value = values.join(join) } // First make any adjustments to the value @@ -33,8 +40,42 @@ function helperUpdateAnswer(event) { answer.dispatchEvent(new InputEvent("input")) } +function helperRemoveInput(e) { + let item = e.target.parentElement + let container = item.parentElement + item.remove() + + var event = new Event("input") + container.dispatchEvent(event) +} + +function helperExpandInputs(e) { + let item = e.target.parentElement + let container = item.parentElement + let template = container.firstElementChild + let newElement = template.cloneNode(true) + + // Add remove button + let remove = document.createElement("button") + remove.innerText = "➖" + remove.title = "Remove this input" + remove.addEventListener("click", helperRemoveInput) + newElement.appendChild(remove) + + // Zero it out, otherwise whatever's in first element is copied too + newElement.querySelector("input").value = "" + + container.insertBefore(newElement, item) + + var event = new Event("input") + container.dispatchEvent(event) +} + function helperActivate(e) { e.addEventListener("input", helperUpdateAnswer) + for (let exp of e.querySelectorAll(".expand")) { + exp.addEventListener("click", helperExpandInputs) + } } function helperInit(event) { diff --git a/example-puzzles/example/5/puzzle.moth b/example-puzzles/example/5/puzzle.moth index 9c3be6b..9455156 100644 --- a/example-puzzles/example/5/puzzle.moth +++ b/example-puzzles/example/5/puzzle.moth @@ -17,8 +17,13 @@ This is just a demonstration page. You will probably only want one of these in a page, to avoid confusing people. -Timestamp - +RFC3339 Timestamp +
+ + + + +
All lower-case letters @@ -31,6 +36,12 @@ Multiple concatenated values +Free input, sorted, concatenated values + + Select from an ordered list of options