diff options
-rw-r--r-- | .gitignore | 15 | ||||
-rw-r--r-- | Makefile | 176 | ||||
-rw-r--r-- | deps.mk | 30 | ||||
-rwxr-xr-x | mkdeps.sh | 40 | ||||
-rw-r--r-- | src/hsts.go | 62 | ||||
-rw-r--r-- | src/main.go | 7 | ||||
-rwxr-xr-x | tests/cli-opts.sh | 2 | ||||
-rw-r--r-- | tests/hsts.go | 9 | ||||
-rwxr-xr-x | tests/integration.sh | 2 | ||||
-rw-r--r-- | tests/lib.sh | 119 | ||||
-rw-r--r-- | tests/main.go | 7 |
11 files changed, 469 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..094db69 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +/src/version.go +/*.bin +/src/*.a +/src/*.bin +/src/*cgo* +/tests/*.a +/tests/*.bin +/tests/functional/*/*.a +/tests/functional/*/*.bin +/tests/fuzz/*/*.a +/tests/fuzz/*/*.bin +/tests/benchmarks/*/*.a +/tests/benchmarks/*/*.bin +/tests/benchmarks/*/*.txt +/tests/fuzz/corpus/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2e93d85 --- /dev/null +++ b/Makefile @@ -0,0 +1,176 @@ +.POSIX: +DATE = 1970-01-01 +VERSION = 0.1.0 +NAME = hsts +NAME_UC = $(NAME) +LANGUAGES = en +## Installation prefix. Defaults to "/usr". +PREFIX = /usr +BINDIR = $(PREFIX)/bin +LIBDIR = $(PREFIX)/lib +GOLIBDIR = $(LIBDIR)/go +INCLUDEDIR = $(PREFIX)/include +SRCDIR = $(PREFIX)/src/$(NAME) +SHAREDIR = $(PREFIX)/share +LOCALEDIR = $(SHAREDIR)/locale +MANDIR = $(SHAREDIR)/man +EXEC = ./ +## Where to store the installation. Empty by default. +DESTDIR = +LDLIBS = --static +GOCFLAGS = -I $(GOLIBDIR) +GOLDFLAGS = -L $(GOLIBDIR) + + + +.SUFFIXES: +.SUFFIXES: .go .a .bin .bin-check + +.go.a: + go tool compile -I $(@D) $(GOCFLAGS) -o $@ -p $(*F) \ + `find $< $$(if [ $(*F) != main ]; then \ + echo src/$(NAME).go src/version.go; fi) | uniq` + +.a.bin: + go tool link -L $(@D) $(GOLDFLAGS) -o $@ --extldflags '$(LDLIBS)' $< + + + +all: +include deps.mk + + +libs.a = $(libs.go:.go=.a) +mains.a = $(mains.go:.go=.a) +mains.bin = $(mains.go:.go=.bin) +functional-tests/lib.a = $(functional-tests/lib.go:.go=.a) +fuzz-targets/lib.a = $(fuzz-targets/lib.go:.go=.a) +benchmarks/lib.a = $(benchmarks/lib.go:.go=.a) + +sources = \ + src/$(NAME).go \ + src/version.go \ + src/main.go \ + + +derived-assets = \ + src/version.go \ + $(libs.a) \ + $(mains.a) \ + $(mains.bin) \ + $(NAME).bin \ + +side-assets = \ + $(NAME).socket \ + tests/fuzz/corpus/ \ + tests/benchmarks/*/main.txt \ + + + +## Default target. Builds all artifacts required for testing +## and installation. +all: $(derived-assets) + + +$(libs.a): Makefile deps.mk +$(libs.a): src/$(NAME).go src/version.go + + +$(fuzz-targets/lib.a): + go tool compile $(GOCFLAGS) -o $@ -p $(NAME) -d=libfuzzer \ + $*.go src/$(NAME).go src/version.go + +src/version.go: Makefile + echo 'package $(NAME); const Version = "$(VERSION)"' > $@ + +$(NAME).bin: src/main.bin + ln -fs $? $@ + + + +tests.bin-check = \ + tests/main.bin-check \ + $(functional-tests/main.go:.go=.bin-check) \ + +$(tests.bin-check): + $(EXEC)$*.bin + +check-unit: $(tests.bin-check) + + +integration-tests = \ + tests/cli-opts.sh \ + tests/integration.sh \ + +.PRECIOUS: $(integration-tests) +$(integration-tests): $(NAME).bin +$(integration-tests): ALWAYS + sh $@ + +check-integration: $(integration-tests) +check-integration: fuzz + + +## Run all tests. Each test suite is isolated, so that a parallel +## build can run tests at the same time. The required artifacts +## are created if missing. +check: check-unit check-integration + + + +FUZZSEC=1 +fuzz-targets/main.bin-check = $(fuzz-targets/main.go:.go=.bin-check) +$(fuzz-targets/main.bin-check): + $(EXEC)$*.bin --test.fuzztime=$(FUZZSEC)s \ + --test.fuzz='.*' --test.fuzzcachedir=tests/fuzz/corpus + +fuzz: $(fuzz-targets/main.bin-check) + + + +benchmarks/main.bin-check = $(benchmarks/main.go:.go=.bin-check) +$(benchmarks/main.bin-check): + printf '%s\n' '$(EXEC)$*.bin' > $*.txt + LANG=POSIX.UTF-8 time -p $(EXEC)$*.bin 2>> $*.txt + printf '%s\n' '$*.txt' + +bench: $(benchmarks/main.bin-check) + + + +## Remove *all* derived artifacts produced during the build. +## A dedicated test asserts that this is always true. +clean: + rm -rf $(derived-assets) $(side-assets) + + +## Installs into $(DESTDIR)$(PREFIX). Its dependency target +## ensures that all installable artifacts are crafted beforehand. +install: all + mkdir -p \ + '$(DESTDIR)$(BINDIR)' \ + '$(DESTDIR)$(GOLIBDIR)' \ + '$(DESTDIR)$(SRCDIR)' \ + + cp $(NAME).bin '$(DESTDIR)$(BINDIR)'/$(NAME) + cp src/$(NAME).a '$(DESTDIR)$(GOLIBDIR)' + cp $(sources) '$(DESTDIR)$(SRCDIR)' + +## Uninstalls from $(DESTDIR)$(PREFIX). This is a perfect mirror +## of the "install" target, and removes *all* that was installed. +## A dedicated test asserts that this is always true. +uninstall: + rm -rf \ + '$(DESTDIR)$(BINDIR)'/$(NAME) \ + '$(DESTDIR)$(GOLIBDIR)'/$(NAME).a \ + '$(DESTDIR)$(SRCDIR)' \ + + + +## Run it locally. +run: all + rm -f $(NAME).socket + ./$(NAME).bin $(NAME).socket + + +ALWAYS: @@ -0,0 +1,30 @@ +libs.go = \ + src/hsts.go \ + tests/hsts.go \ + +mains.go = \ + src/main.go \ + tests/main.go \ + +functional-tests/lib.go = \ + +functional-tests/main.go = \ + +fuzz-targets/lib.go = \ + +fuzz-targets/main.go = \ + +benchmarks/lib.go = \ + +benchmarks/main.go = \ + +src/hsts.a: src/hsts.go +src/main.a: src/main.go +tests/hsts.a: tests/hsts.go +tests/main.a: tests/main.go +src/main.bin: src/main.a +tests/main.bin: tests/main.a +src/main.bin-check: src/main.bin +tests/main.bin-check: tests/main.bin +src/main.a: src/$(NAME).a +tests/main.a: tests/$(NAME).a diff --git a/mkdeps.sh b/mkdeps.sh new file mode 100755 index 0000000..3ce3e05 --- /dev/null +++ b/mkdeps.sh @@ -0,0 +1,40 @@ +#!/bin/sh +set -eu + +export LANG=POSIX.UTF-8 + + +no_main() { + grep -v '/main\.go$' +} + +only_main() { + grep '/main\.go$' +} + +sources() { + find src tests -name '*.go' | grep -v '^src/version\.go$' +} + +libs() { + sources | no_main +} + +mains() { + sources | only_main +} + +libs | varlist 'libs.go' +mains | varlist 'mains.go' + +find tests/functional/*/*.go | no_main | varlist 'functional-tests/lib.go' +find tests/functional/*/main.go | varlist 'functional-tests/main.go' +find tests/fuzz/*/*.go | no_main | varlist 'fuzz-targets/lib.go' +find tests/fuzz/*/main.go | varlist 'fuzz-targets/main.go' +find tests/benchmarks/*/*.go | no_main | varlist 'benchmarks/lib.go' +find tests/benchmarks/*/main.go | varlist 'benchmarks/main.go' + +sources | sort | sed 's/^\(.*\)\.go$/\1.a:\t\1.go/' +mains | sort | sed 's/^\(.*\)\.go$/\1.bin:\t\1.a/' +mains | sort | sed 's/^\(.*\)\.go$/\1.bin-check:\t\1.bin/' +mains | sort | sed 's|^\(.*\)/main\.go$|\1/main.a:\t\1/$(NAME).a|' diff --git a/src/hsts.go b/src/hsts.go new file mode 100644 index 0000000..16082de --- /dev/null +++ b/src/hsts.go @@ -0,0 +1,62 @@ +package hsts + +import ( + "fmt" + "log/slog" + "net" + "net/http" + "os" + + "guuid" + g "gobang" +) + + + +var countRedirection = g.MakeCounter("request-redirection") + + + +func redirectHandler(w http.ResponseWriter, r *http.Request) { + target := "https://" + r.Host + r.RequestURI + http.Redirect(w, r, target, http.StatusMovedPermanently) + countRedirection( + "id", guuid.New().String(), + "url-path", r.URL.Path, + "req-pattern", r.Pattern, + "method", r.Method, + "host", r.Host, + "uri", r.RequestURI, + ) +} + +func listen(fromAddr string) net.Listener { + listener, err := net.Listen("unix", fromAddr) + g.FatalIf(err) + g.Info( + "Started listening", "listen-start", + "from-address", fromAddr, + slog.Group( + "versions", + "guuid", guuid.Version, + "gobang", g.Version, + "hsts", Version, + ), + ) + return listener +} + + + +func Main() { + g.Init("program", "hsts") + + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: %s LISTEN.socket\n", os.Args[0]) + os.Exit(2) + } + + listener := listen(os.Args[1]) + err := http.Serve(listener, http.HandlerFunc(redirectHandler)) + g.FatalIf(err) +} diff --git a/src/main.go b/src/main.go new file mode 100644 index 0000000..9d796c6 --- /dev/null +++ b/src/main.go @@ -0,0 +1,7 @@ +package main + +import "hsts" + +func main() { + hsts.Main() +} diff --git a/tests/cli-opts.sh b/tests/cli-opts.sh new file mode 100755 index 0000000..92b70ea --- /dev/null +++ b/tests/cli-opts.sh @@ -0,0 +1,2 @@ +#!/bin/sh +set -eu diff --git a/tests/hsts.go b/tests/hsts.go new file mode 100644 index 0000000..daff3b6 --- /dev/null +++ b/tests/hsts.go @@ -0,0 +1,9 @@ +package hsts + +import ( +) + + + +func MainTest() { +} diff --git a/tests/integration.sh b/tests/integration.sh new file mode 100755 index 0000000..92b70ea --- /dev/null +++ b/tests/integration.sh @@ -0,0 +1,2 @@ +#!/bin/sh +set -eu diff --git a/tests/lib.sh b/tests/lib.sh new file mode 100644 index 0000000..07ecbef --- /dev/null +++ b/tests/lib.sh @@ -0,0 +1,119 @@ +#!/bin/sh + +end="\033[0m" +red="\033[0;31m" +green="\033[0;32m" +yellow="\033[0;33m" + +N= +OUT= +ERR= +STATUS= + +ERROR() { + # shellcheck disable=2059 + printf "${red}ERROR${end}" +} + +print_debug_info() { + # shellcheck disable=2016 + printf 'LINENO: %s\n$OUT: %s\n$ERR: %s\n' \ + "$N" "$OUT" "$ERR" >&2 +} + +assert_status() { + if [ "$STATUS" != "$1" ]; then + printf '\n%s: Bad status.\n\nexpected: %s\ngot: %s\n' \ + "$(ERROR)" "$1" "$STATUS" >&2 + print_debug_info + exit 1 + fi +} + +assert_usage() { + if ! grep -Fq 'Usage' "$1"; then + echo 'Expected to find "Usage" text, it was missing:' >&2 + cat "$1" >&2 + print_debug_info + exit 1 + fi +} + +assert_empty_stream() { + if [ -s "$2" ]; then + FMT='\n%s: Expected %s (%s) to be empty, but has content:\n%s\n' + # shellcheck disable=2059 + printf "$FMT" \ + "$(ERROR)" "$1" "$2" "$(cat "$2")" >&2 + print_debug_info + exit 1 + fi +} + +assert_empty_stdout() { + assert_empty_stream STDOUT "$OUT" +} + +assert_empty_stderr() { + assert_empty_stream STDERR "$ERR" +} + +assert_stream() { + if [ "$(cat "$2")" != "$3" ]; then + printf '\n%s: Bad %s (%s)\n\nexpected: %s\ngot: %s\n' \ + "$(ERROR)" "$1" "$2" "$3" "$(cat "$2")" >&2 + print_debug_info + exit 1 + fi +} + +assert_stdout() { + assert_stream STDOUT "$OUT" "$1" +} + +assert_stderr() { + assert_stream STDERR "$ERR" "$1" +} + +assert_grep_stream() { + if ! grep -qE "$3" "$2"; then + printf '\n%s: Bad %s (%s)\n\ngrepping: %s\nin:\n%s\n' \ + "$(ERROR)" "$1" "$2" "$3" "$(cat "$2")" >&2 + print_debug_info + exit 1 + fi +} + +assert_grep_stdout() { + assert_grep_stream STDOUT "$OUT" "$1" +} + +assert_grep_stderr() { + assert_grep_stream STDERR "$ERR" "$1" +} + +assert_fgrep_stream() { + if ! grep -Fq -- "$3" "$2"; then + printf '\n%s: Bad %s (%s)\n\ngrepping: %s\nin:\n%s\n' \ + "$(ERROR)" "$1" "$2" "$3" "$(cat "$2")" >&2 + print_debug_info + exit 1 + fi +} + +assert_fgrep_stdout() { + assert_fgrep_stream STDOUT "$OUT" "$1" +} + +assert_fgrep_stderr() { + assert_fgrep_stream STDERR "$ERR" "$1" +} + +testing() { + printf "${yellow}testing${end}: %s..." "$1" >&2 +} + +test_ok() { + # shellcheck disable=2059 + printf " ${green}OK${end}.\n" >&2 +} diff --git a/tests/main.go b/tests/main.go new file mode 100644 index 0000000..dacfa44 --- /dev/null +++ b/tests/main.go @@ -0,0 +1,7 @@ +package main + +import "hsts" + +func main() { + hsts.MainTest() +} |