Compare commits
193 Commits
master
...
developmen
Author | SHA1 | Date |
---|---|---|
kim (grufwub) | 60295a28bb | 3 years ago |
kim (grufwub) | 1dce8a6600 | 3 years ago |
kim (grufwub) | 1598f101f1 | 3 years ago |
kim (grufwub) | 0ecc21c1de | 3 years ago |
kim (grufwub) | d84739d018 | 3 years ago |
kim (grufwub) | d6df45cd41 | 3 years ago |
kim (grufwub) | e4187eb229 | 3 years ago |
kim (grufwub) | ffa42c0183 | 3 years ago |
kim (grufwub) | fd3bf21758 | 3 years ago |
kim (grufwub) | 627b83c395 | 3 years ago |
kim (grufwub) | 088730d3cb | 3 years ago |
kim (grufwub) | e0f402cfd9 | 3 years ago |
kim (grufwub) | cdd55c0f60 | 3 years ago |
kim (grufwub) | f2f0b6d615 | 3 years ago |
kim (grufwub) | 2d1b5a9ab3 | 3 years ago |
kim (grufwub) | 1d05e31dfb | 3 years ago |
kim (grufwub) | f7e43d4d10 | 3 years ago |
kim (grufwub) | 69d608d5f5 | 3 years ago |
kim (grufwub) | f07e9c05e9 | 3 years ago |
kim (grufwub) | 14abeb7077 | 3 years ago |
kim (grufwub) | 369e8fef13 | 3 years ago |
kim (grufwub) | f3cc83a2a3 | 3 years ago |
kim (grufwub) | 563138f3ec | 3 years ago |
kim (grufwub) | 62c005d417 | 3 years ago |
kim (grufwub) | f2c3dd60b7 | 3 years ago |
kim (grufwub) | d6bf0f44a6 | 3 years ago |
kim (grufwub) | 3e182207e0 | 3 years ago |
kim (grufwub) | b2ed836f63 | 3 years ago |
kim (grufwub) | 6f4714e144 | 3 years ago |
kim (grufwub) | 03e7384159 | 3 years ago |
kim (grufwub) | 39aac0eac8 | 3 years ago |
kim (grufwub) | ee92133c47 | 3 years ago |
kim (grufwub) | 5b1f31ca92 | 3 years ago |
kim (grufwub) | 72ea175925 | 3 years ago |
kim (grufwub) | dccf4bd636 | 3 years ago |
kim (grufwub) | bff3b23b49 | 3 years ago |
kim (grufwub) | aaddfe3679 | 3 years ago |
kim (grufwub) | 692d439180 | 3 years ago |
kim (grufwub) | 259d01fdd6 | 3 years ago |
kim (grufwub) | 0ba05dc956 | 3 years ago |
kim (grufwub) | 37e257fcc8 | 3 years ago |
kim (grufwub) | 13227bbd21 | 4 years ago |
kim (grufwub) | d43597b2fa | 4 years ago |
kim (grufwub) | db73c33fa2 | 4 years ago |
kim (grufwub) | 5d44d9c0e0 | 4 years ago |
kim (grufwub) | 40a5e6e70e | 4 years ago |
kim (grufwub) | 27366e224e | 4 years ago |
kim (grufwub) | ea83ab3f78 | 4 years ago |
kim (grufwub) | eef30c742f | 4 years ago |
kim (grufwub) | 41f0ab6b1c | 4 years ago |
kim (grufwub) | 81ff43ab29 | 4 years ago |
kim (grufwub) | f155e76661 | 4 years ago |
kim (grufwub) | 230a979993 | 4 years ago |
kim (grufwub) | c95d2e8939 | 4 years ago |
kim (grufwub) | 352cebcf50 | 4 years ago |
kim (grufwub) | c842ae78ad | 4 years ago |
kim (grufwub) | 8a541b1369 | 4 years ago |
kim (grufwub) | 5aeb201cc0 | 4 years ago |
kim (grufwub) | b2cbcc32f3 | 4 years ago |
kim (grufwub) | 2e97b28ce5 | 4 years ago |
kim (grufwub) | 1d17310377 | 4 years ago |
kim (grufwub) | e3aee14034 | 4 years ago |
kim (grufwub) | 99b9d041fc | 4 years ago |
kim (grufwub) | 239e7dd07d | 4 years ago |
kim (grufwub) | c9436be644 | 4 years ago |
kim (grufwub) | 97f30066a0 | 4 years ago |
kim (grufwub) | bdc0c427de | 4 years ago |
kim (grufwub) | f5be927a26 | 4 years ago |
kim (grufwub) | 29a8030fec | 4 years ago |
kim (grufwub) | 6eb1f5cde7 | 4 years ago |
kim (grufwub) | 3385c71f2f | 4 years ago |
kim (grufwub) | 4b580976c8 | 4 years ago |
kim (grufwub) | f919fac0a0 | 4 years ago |
kim (grufwub) | df5f14c440 | 4 years ago |
kim (grufwub) | 32aebac22a | 4 years ago |
kim (grufwub) | ce73853fd0 | 4 years ago |
kim (grufwub) | 21dabb5050 | 4 years ago |
kim (grufwub) | 1d37e4461d | 4 years ago |
kim (grufwub) | afbcaa4a1c | 4 years ago |
kim (grufwub) | 4066ff11f9 | 4 years ago |
kim (grufwub) | 30bf8d5b80 | 4 years ago |
kim (grufwub) | 8c207d1cab | 4 years ago |
kim (grufwub) | aa21e807c4 | 4 years ago |
kim (grufwub) | 6064d8124b | 4 years ago |
kim (grufwub) | 4553cb6897 | 4 years ago |
kim (grufwub) | eab3e38dd7 | 4 years ago |
kim (grufwub) | 551d99578b | 4 years ago |
kim (grufwub) | 06b50ff573 | 4 years ago |
kim (grufwub) | df2bc06360 | 4 years ago |
kim (grufwub) | ee67c63219 | 4 years ago |
kim (grufwub) | 1f8e62e0b2 | 4 years ago |
kim (grufwub) | 67f249c242 | 4 years ago |
kim (grufwub) | b14b3302b3 | 4 years ago |
kim (grufwub) | 0e558ff28f | 4 years ago |
kim (grufwub) | 45f9f92a66 | 4 years ago |
kim (grufwub) | aedd30eb81 | 4 years ago |
kim (grufwub) | 91b3f12c20 | 4 years ago |
kim (grufwub) | 69b2fed568 | 4 years ago |
kim (grufwub) | 3c72f14ab6 | 4 years ago |
kim (grufwub) | b7625eef64 | 4 years ago |
kim (grufwub) | 1a141c5fe4 | 4 years ago |
kim (grufwub) | 7fcf5824bc | 4 years ago |
kim (grufwub) | c5a7d3d301 | 4 years ago |
kim (grufwub) | b2cd388b56 | 4 years ago |
kim (grufwub) | e0473efb9d | 4 years ago |
kim (grufwub) | d899881c88 | 4 years ago |
kim (grufwub) | e0742bbb1b | 4 years ago |
kim (grufwub) | 4e7e28dba7 | 4 years ago |
kim (grufwub) | 2675a14351 | 4 years ago |
kim (grufwub) | 214365ee2c | 4 years ago |
kim (grufwub) | d1d9ead618 | 4 years ago |
kim (grufwub) | 65e1b1a879 | 4 years ago |
kim (grufwub) | a5d6fa361d | 4 years ago |
kim (grufwub) | de07f8825f | 4 years ago |
kim (grufwub) | 7450bc417f | 4 years ago |
kim (grufwub) | 0bdca86a83 | 4 years ago |
kim (grufwub) | 1cce82a480 | 4 years ago |
kim (grufwub) | 5d4b97c607 | 4 years ago |
kim (grufwub) | be23439933 | 4 years ago |
kim (grufwub) | f2e6381c3a | 4 years ago |
kim (grufwub) | 0f559836ee | 4 years ago |
kim (grufwub) | 0f83dcb9c7 | 4 years ago |
kim (grufwub) | 265007046b | 4 years ago |
kim (grufwub) | 1335a51024 | 4 years ago |
kim (grufwub) | 9816b6c717 | 4 years ago |
kim (grufwub) | aa2c0ee14d | 4 years ago |
kim (grufwub) | ba2bc9f033 | 4 years ago |
kim (grufwub) | 797ad97e1c | 4 years ago |
kim (grufwub) | d201f98573 | 4 years ago |
kim (grufwub) | ca81ea0e6e | 4 years ago |
kim (grufwub) | 4401fa0499 | 4 years ago |
kim (grufwub) | e3e8acd163 | 4 years ago |
kim (grufwub) | c31b04c745 | 4 years ago |
kim (grufwub) | ad0545b49e | 4 years ago |
kim (grufwub) | e011e71923 | 4 years ago |
kim (grufwub) | 7733b88594 | 4 years ago |
kim (grufwub) | eb89e4044f | 4 years ago |
kim (grufwub) | 41ad240e26 | 4 years ago |
kim (grufwub) | f8c9c738ab | 4 years ago |
kim (grufwub) | a6108587cd | 4 years ago |
kim (grufwub) | f689409692 | 4 years ago |
kim (grufwub) | eafdc58d40 | 4 years ago |
kim (grufwub) | ae3b8f1f4b | 4 years ago |
kim (grufwub) | f9c81b5ffd | 4 years ago |
kim (grufwub) | f8718ad1b6 | 4 years ago |
kim (grufwub) | 4c52f96af6 | 4 years ago |
kim (grufwub) | 8aef4f13a2 | 4 years ago |
kim (grufwub) | 8242b4eb35 | 4 years ago |
kim (grufwub) | a47f958499 | 4 years ago |
kim (grufwub) | 07b5ea1d7c | 4 years ago |
kim (grufwub) | d473872bd0 | 4 years ago |
kim (grufwub) | af23ed865a | 4 years ago |
kim (grufwub) | 781087c9ba | 4 years ago |
kim (grufwub) | 75fb6be3f2 | 4 years ago |
kim (grufwub) | 55c34db7b7 | 4 years ago |
kim (grufwub) | a5c6e1080c | 4 years ago |
kim (grufwub) | 08101e5da5 | 4 years ago |
kim (grufwub) | d1150ad6a0 | 4 years ago |
kim (grufwub) | b7879a105a | 4 years ago |
kim (grufwub) | 6e6115ae54 | 4 years ago |
kim (grufwub) | c1c15b4266 | 4 years ago |
kim (grufwub) | 145f54b85d | 4 years ago |
kim (grufwub) | e36f1ecdb9 | 4 years ago |
kim (grufwub) | 69dc4b66c6 | 4 years ago |
kim (grufwub) | 6502922a4f | 4 years ago |
kim (grufwub) | 56e5eff136 | 4 years ago |
kim (grufwub) | be8a92edfb | 4 years ago |
kim (grufwub) | 3ffba65c76 | 4 years ago |
kim (grufwub) | 2dd51728a4 | 4 years ago |
kim (grufwub) | 0c68a84861 | 4 years ago |
kim (grufwub) | 748622c8e1 | 4 years ago |
kim (grufwub) | 0981f37f54 | 4 years ago |
kim (grufwub) | 7ea9a90f97 | 4 years ago |
kim (grufwub) | fbdd422493 | 4 years ago |
kim (grufwub) | f91493663b | 4 years ago |
kim (grufwub) | c7a3f33660 | 4 years ago |
kim (grufwub) | 69df7e257a | 4 years ago |
kim (grufwub) | 0f73b3255d | 4 years ago |
kim (grufwub) | 3a47c21aec | 4 years ago |
kim (grufwub) | 89a3ab743d | 4 years ago |
kim (grufwub) | aef3d9f269 | 4 years ago |
kim (grufwub) | 5248e27528 | 4 years ago |
kim (grufwub) | 47aeb49681 | 4 years ago |
kim (grufwub) | e4737ae616 | 4 years ago |
kim (grufwub) | 216d2df070 | 4 years ago |
kim (grufwub) | f23b452cb9 | 4 years ago |
kim (grufwub) | 549aa31158 | 4 years ago |
kim (grufwub) | 52a75eacec | 4 years ago |
kim (grufwub) | 55b5bd86a6 | 4 years ago |
kim (grufwub) | 7cb9a0045b | 4 years ago |
kim (grufwub) | 8477da7698 | 4 years ago |
kim (grufwub) | b238b45b20 | 4 years ago |
kim (grufwub) | 433de3eeab | 4 years ago |
@ -1,3 +0,0 @@
|
|||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
liberapay: grufwub
|
|
@ -1,4 +1,8 @@
|
|||||||
gophi.*
|
|
||||||
*.log
|
*.log
|
||||||
*.old
|
*.old
|
||||||
build*/
|
*.crt
|
||||||
|
*.key
|
||||||
|
*.toml
|
||||||
|
build/
|
||||||
|
gophi.gemini
|
||||||
|
gophi.gopher
|
@ -1,2 +0,0 @@
|
|||||||
- preallocate slices
|
|
||||||
- use sync.Pool ? (for byte buffers?)
|
|
@ -1,162 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
PROJECT='gophi.gopher'
|
|
||||||
VERSION="$(cat 'core/server.go' | grep -E '^\s*Version' | sed -e 's|\s*Version = \"||' -e 's|\"\s*$||')"
|
|
||||||
LOGFILE='build.log'
|
|
||||||
OUTDIR="build-gopher-${VERSION}"
|
|
||||||
|
|
||||||
upx_compress() {
|
|
||||||
local level="$1" filename="$2" topack="${2}.topack"
|
|
||||||
|
|
||||||
cp "$filename" "$topack"
|
|
||||||
|
|
||||||
if (upx "$level" "$topack" >> "$LOGFILE" 2>&1); then
|
|
||||||
if (upx --test "$topack"); then
|
|
||||||
mv "$topack" "$filename"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
rm "$topack"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
rm "$topack"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
compress() {
|
|
||||||
local filename="$1"
|
|
||||||
|
|
||||||
echo "Attempting to compress ${filename}..."
|
|
||||||
|
|
||||||
if (upx_compress '--ultra-brute' "$filename"); then
|
|
||||||
echo "Compressed with --ultra-brute!"
|
|
||||||
elif (upx_compress '--best' "$filename"); then
|
|
||||||
echo "Compressed with --best!"
|
|
||||||
elif (upx_compress '' "$filename"); then
|
|
||||||
echo "Compressed with no flags."
|
|
||||||
else
|
|
||||||
echo "Compression failed!"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
build_for() {
|
|
||||||
local archname="$1" toolchain="$2" os="$3" arch="$4"
|
|
||||||
shift 4
|
|
||||||
if [ "$arch" = 'arm' ]; then
|
|
||||||
local armversion="$1"
|
|
||||||
shift 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Building for ${os} ${archname} with ${toolchain}..."
|
|
||||||
local filename="${OUTDIR}/${PROJECT}_${os}_${archname}"
|
|
||||||
CC="${toolchain}-gcc" CGO_ENABLED=0 GOOS="$os" GOARCH="$arch" GOARM="$armversion" go build -trimpath -o "$filename" "$@" 'cmd/gopher/main.go' >> "$LOGFILE" 2>&1
|
|
||||||
if [ "$?" -ne 0 ]; then
|
|
||||||
echo "Failed!"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
compress "$filename"
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "PLEASE BE WARNED THIS SCRIPT IS WRITTEN FOR A VOID LINUX (MUSL) BUILD ENVIRONMENT"
|
|
||||||
echo "YOUR CC TOOLCHAIN LOCATIONS MAY DIFFER"
|
|
||||||
echo "IF THE SCRIPT FAILS, CHECK THE OUTPUT OF: ${LOGFILE}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Clean logfile
|
|
||||||
rm -f "$LOGFILE"
|
|
||||||
|
|
||||||
# Clean and recreate directory
|
|
||||||
rm -rf "$OUTDIR"
|
|
||||||
mkdir -p "$OUTDIR"
|
|
||||||
|
|
||||||
# Build time :)
|
|
||||||
|
|
||||||
# Linux
|
|
||||||
build_for '386' 'i686-linux-musl' 'linux' '386' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'amd64' 'x86_64-linux-musl' 'linux' 'amd64' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv5' 'arm-linux-musleabi' 'linux' 'arm' '5' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv5hf' 'arm-linux-musleabihf' 'linux' 'arm' '5' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv6' 'arm-linux-musleabi' 'linux' 'arm' '6' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv6hf' 'arm-linux-musleabihf' 'linux' 'arm' '6' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv7lhf' 'armv7l-linux-musleabihf' 'linux' 'arm' '7' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'arm64' 'aarch64-linux-musl' 'linux' 'arm64' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'mips' 'mips-linux-musl' 'linux' 'mips' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'mipshf' 'mips-linux-muslhf' 'linux' 'mips' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'mipsle' 'mipsel-linux-musl' 'linux' 'mipsle' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'mipslehf' 'mipsel-linux-muslhf' 'linux' 'mipsle' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'ppc64le' 'powerpc64le-linux-musl' 'linux' 'ppc64le' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
# Netbsd
|
|
||||||
build_for '386' 'i686-linux-musl' 'netbsd' '386' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'amd64' 'x86_64-linux-musl' 'netbsd' 'amd64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv5' 'arm-linux-musleabi' 'netbsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv5hf' 'arm-linux-musleabihf' 'netbsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv6' 'arm-linux-musleabi' 'netbsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv6hf' 'arm-linux-musleabihf' 'netbsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv7lhf' 'armv7l-linux-musleabihf' 'netbsd' 'arm' '7' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'arm64' 'aarch64-linux-musl' 'netbsd' 'arm64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
# Openbsd
|
|
||||||
build_for '386' 'i686-linux-musl' 'openbsd' '386' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'amd64' 'x86_64-linux-musl' 'openbsd' 'amd64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv5' 'arm-linux-musleabi' 'openbsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv5hf' 'arm-linux-musleabihf' 'openbsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv6' 'arm-linux-musleabi' 'openbsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv6hf' 'arm-linux-musleabihf' 'openbsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv7lhf' 'armv7l-linux-musleabihf' 'openbsd' 'arm' '7' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'arm64' 'aarch64-linux-musl' 'openbsd' 'arm64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
# Freebsd
|
|
||||||
build_for '386' 'i686-linux-musl' 'freebsd' '386' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'amd64' 'x86_64-linux-musl' 'freebsd' 'amd64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv5' 'arm-linux-musleabi' 'freebsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv5hf' 'arm-linux-musleabihf' 'freebsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv6' 'arm-linux-musleabi' 'freebsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv6hf' 'arm-linux-musleabihf' 'freebsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'armv7lhf' 'armv7l-linux-musleabihf' 'freebsd' 'arm' '7' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'arm64' 'aarch64-linux-musl' 'freebsd' 'arm64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
# Dragonfly
|
|
||||||
build_for 'amd64' 'x86_64-linux-musl' 'dragonfly' 'amd64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
# Macos
|
|
||||||
build_for '386' 'i686-linux-musl' 'darwin' '386' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
||||||
|
|
||||||
build_for 'amd64' 'x86_64-linux-musl' 'darwin' 'amd64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
|
@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
CC='x86_64-linux-musl-gcc' CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -buildmode 'pie' -a -v -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"' -o 'gophi.gopher' 'cmd/gopher/main.go'
|
|
@ -0,0 +1,173 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
VERSION="$(cat 'core/server.go' | grep -E '^\s*Version' | sed -e 's|\s*Version = \"||' -e 's|\"\s*$||')" \
|
||||||
|
|| {
|
||||||
|
echo 'Failed to get gophi version!'
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
GOROOT="${HOME}/Projects/github.com/grufwub/go"
|
||||||
|
PATH="${GOROOT}/bin:${PATH}"
|
||||||
|
|
||||||
|
build_for() {
|
||||||
|
# Grab build information
|
||||||
|
local protocol="$1" archname="$2" toolchain="$3" os="$4" arch="$5"
|
||||||
|
shift 5
|
||||||
|
if [ "$arch" = 'arm' ]; then
|
||||||
|
local armversion="$1"
|
||||||
|
shift 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate unique filename
|
||||||
|
local filename="build/gophi.${protocol}_${os}_${archname}"
|
||||||
|
|
||||||
|
# Build binary!
|
||||||
|
echo "Building for ${os} ${archname} with ${toolchain}..."
|
||||||
|
CC="${toolchain}-gcc" CGO_ENABLED=0 GOOS="$os" GOARCH="$arch" GOARM="$armversion" go build -trimpath -o "$filename" "$@" "cmd/${protocol}/main.go" \
|
||||||
|
|| {
|
||||||
|
echo 'Failed!'
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "PLEASE BE WARNED THIS SCRIPT IS WRITTEN FOR A VOID LINUX (MUSL) BUILD ENVIRONMENT"
|
||||||
|
echo "YOUR CC TOOLCHAIN LOCATIONS MAY DIFFER"
|
||||||
|
echo "IF THE SCRIPT FAILS, CHECK THE OUTPUT OF: ${LOGFILE}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Clean build directory
|
||||||
|
rm -rf build/; mkdir -p build/
|
||||||
|
|
||||||
|
# Write the version
|
||||||
|
echo "$VERSION" > 'build/version.txt'
|
||||||
|
|
||||||
|
# Build time :)
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
build_for 'gopher' '386' 'i686-linux-musl' 'linux' '386' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' '386' 'i686-linux-musl' 'linux' '386' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'amd64' 'x86_64-linux-musl' 'linux' 'amd64' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'amd64' 'x86_64-linux-musl' 'linux' 'amd64' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv5' 'arm-linux-musleabi' 'linux' 'arm' '5' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv5' 'arm-linux-musleabi' 'linux' 'arm' '5' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv5hf' 'arm-linux-musleabihf' 'linux' 'arm' '5' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv5hf' 'arm-linux-musleabihf' 'linux' 'arm' '5' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv6' 'arm-linux-musleabi' 'linux' 'arm' '6' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv6' 'arm-linux-musleabi' 'linux' 'arm' '6' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv6hf' 'arm-linux-musleabihf' 'linux' 'arm' '6' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv6hf' 'arm-linux-musleabihf' 'linux' 'arm' '6' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv7lhf' 'armv7l-linux-musleabihf' 'linux' 'arm' '7' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv7lhf' 'armv7l-linux-musleabihf' 'linux' 'arm' '7' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'arm64' 'aarch64-linux-musl' 'linux' 'arm64' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'arm64' 'aarch64-linux-musl' 'linux' 'arm64' -buildmode 'pie' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'mips' 'mips-linux-musl' 'linux' 'mips' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'mips' 'mips-linux-musl' 'linux' 'mips' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'mipshf' 'mips-linux-muslhf' 'linux' 'mips' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'mipshf' 'mips-linux-muslhf' 'linux' 'mips' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'mipsle' 'mipsel-linux-musl' 'linux' 'mipsle' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'mipsle' 'mipsel-linux-musl' 'linux' 'mipsle' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'mipslehf' 'mipsel-linux-muslhf' 'linux' 'mipsle' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'mipslehf' 'mipsel-linux-muslhf' 'linux' 'mipsle' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'ppc64' 'powerpc64-linux-musl' 'linux' 'ppc64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'ppc64' 'powerpc64-linux-musl' 'linux' 'ppc64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'ppc64le' 'powerpc64le-linux-musl' 'linux' 'ppc64le' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'ppc64le' 'powerpc64le-linux-musl' 'linux' 'ppc64le' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
# Netbsd
|
||||||
|
build_for 'gopher' '386' 'i686-linux-musl' 'netbsd' '386' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' '386' 'i686-linux-musl' 'netbsd' '386' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'amd64' 'x86_64-linux-musl' 'netbsd' 'amd64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'amd64' 'x86_64-linux-musl' 'netbsd' 'amd64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv5' 'arm-linux-musleabi' 'netbsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv5' 'arm-linux-musleabi' 'netbsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv5hf' 'arm-linux-musleabihf' 'netbsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv5hf' 'arm-linux-musleabihf' 'netbsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv6' 'arm-linux-musleabi' 'netbsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv6' 'arm-linux-musleabi' 'netbsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv6hf' 'arm-linux-musleabihf' 'netbsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv6hf' 'arm-linux-musleabihf' 'netbsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv7lhf' 'armv7l-linux-musleabihf' 'netbsd' 'arm' '7' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv7lhf' 'armv7l-linux-musleabihf' 'netbsd' 'arm' '7' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'arm64' 'aarch64-linux-musl' 'netbsd' 'arm64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'arm64' 'aarch64-linux-musl' 'netbsd' 'arm64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
# Openbsd
|
||||||
|
build_for 'gopher' '386' 'i686-linux-musl' 'openbsd' '386' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' '386' 'i686-linux-musl' 'openbsd' '386' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'amd64' 'x86_64-linux-musl' 'openbsd' 'amd64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'amd64' 'x86_64-linux-musl' 'openbsd' 'amd64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv5' 'arm-linux-musleabi' 'openbsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv5' 'arm-linux-musleabi' 'openbsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv5hf' 'arm-linux-musleabihf' 'openbsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv5hf' 'arm-linux-musleabihf' 'openbsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv6' 'arm-linux-musleabi' 'openbsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv6' 'arm-linux-musleabi' 'openbsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv6hf' 'arm-linux-musleabihf' 'openbsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv6hf' 'arm-linux-musleabihf' 'openbsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv7lhf' 'armv7l-linux-musleabihf' 'openbsd' 'arm' '7' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv7lhf' 'armv7l-linux-musleabihf' 'openbsd' 'arm' '7' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'arm64' 'aarch64-linux-musl' 'openbsd' 'arm64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'arm64' 'aarch64-linux-musl' 'openbsd' 'arm64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
# Freebsd
|
||||||
|
build_for 'gopher' '386' 'i686-linux-musl' 'freebsd' '386' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' '386' 'i686-linux-musl' 'freebsd' '386' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'amd64' 'x86_64-linux-musl' 'freebsd' 'amd64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'amd64' 'x86_64-linux-musl' 'freebsd' 'amd64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv5' 'arm-linux-musleabi' 'freebsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv5' 'arm-linux-musleabi' 'freebsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv5hf' 'arm-linux-musleabihf' 'freebsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv5hf' 'arm-linux-musleabihf' 'freebsd' 'arm' '5' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv6' 'arm-linux-musleabi' 'freebsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv6' 'arm-linux-musleabi' 'freebsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv6hf' 'arm-linux-musleabihf' 'freebsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv6hf' 'arm-linux-musleabihf' 'freebsd' 'arm' '6' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'armv7lhf' 'armv7l-linux-musleabihf' 'freebsd' 'arm' '7' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'armv7lhf' 'armv7l-linux-musleabihf' 'freebsd' 'arm' '7' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'arm64' 'aarch64-linux-musl' 'freebsd' 'arm64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'arm64' 'aarch64-linux-musl' 'freebsd' 'arm64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
# Dragonfly
|
||||||
|
build_for 'gopher' 'amd64' 'x86_64-linux-musl' 'dragonfly' 'amd64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'amd64' 'x86_64-linux-musl' 'dragonfly' 'amd64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
# Macos
|
||||||
|
build_for 'gopher' 'amd64' 'x86_64-linux-musl' 'darwin' 'amd64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'amd64' 'x86_64-linux-musl' 'darwin' 'amd64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
|
||||||
|
build_for 'gopher' 'arm64' 'aarch64-linux-musl' 'darwin' 'arm64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
||||||
|
build_for 'gemini' 'arm64' 'aarch64-linux-musl' 'darwin' 'arm64' -buildmode 'default' -a -tags 'netgo osusergo static_build' -ldflags '-s -w -extldflags "-static"'
|
@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gophi/gemini"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
gemini.Run()
|
||||||
|
}
|
@ -1,77 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import "container/list"
|
|
||||||
|
|
||||||
// element wraps a map key and value
|
|
||||||
type element struct {
|
|
||||||
key string
|
|
||||||
value *file
|
|
||||||
}
|
|
||||||
|
|
||||||
// lruCacheMap is a fixed-size LRU hash map
|
|
||||||
type lruCacheMap struct {
|
|
||||||
hashMap map[string]*list.Element
|
|
||||||
list *list.List
|
|
||||||
size int
|
|
||||||
}
|
|
||||||
|
|
||||||
// newLRUCacheMap returns a new LRUCacheMap of specified size
|
|
||||||
func newLRUCacheMap(size int) *lruCacheMap {
|
|
||||||
return &lruCacheMap{
|
|
||||||
// size+1 to account for moment during put after adding new value but before old value is purged
|
|
||||||
make(map[string]*list.Element, size+1),
|
|
||||||
&list.List{},
|
|
||||||
size,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns file from LRUCacheMap for key
|
|
||||||
func (lru *lruCacheMap) Get(key string) (*file, bool) {
|
|
||||||
lElem, ok := lru.hashMap[key]
|
|
||||||
if !ok {
|
|
||||||
return nil, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move element to front of the list
|
|
||||||
lru.list.MoveToFront(lElem)
|
|
||||||
|
|
||||||
// Get Element and return *File value from it
|
|
||||||
element, _ := lElem.Value.(*element)
|
|
||||||
return element.value, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put file in LRUCacheMap at key
|
|
||||||
func (lru *lruCacheMap) Put(key string, value *file) {
|
|
||||||
lElem := lru.list.PushFront(&element{key, value})
|
|
||||||
lru.hashMap[key] = lElem
|
|
||||||
|
|
||||||
if lru.list.Len() > lru.size {
|
|
||||||
// Get element at back of list and Element from it
|
|
||||||
lElem = lru.list.Back()
|
|
||||||
element, _ := lElem.Value.(*element)
|
|
||||||
|
|
||||||
// Delete entry in hashMap with key from Element, and from list
|
|
||||||
delete(lru.hashMap, element.key)
|
|
||||||
lru.list.Remove(lElem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove file in LRUCacheMap with key
|
|
||||||
func (lru *lruCacheMap) Remove(key string) {
|
|
||||||
lElem, ok := lru.hashMap[key]
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete entry in hashMap and list
|
|
||||||
delete(lru.hashMap, key)
|
|
||||||
lru.list.Remove(lElem)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate performs an iteration over all key:value pairs in LRUCacheMap with supplied function
|
|
||||||
func (lru *lruCacheMap) Iterate(iterator func(key string, value *file)) {
|
|
||||||
for key := range lru.hashMap {
|
|
||||||
element := lru.hashMap[key].Value.(*element)
|
|
||||||
iterator(element.key, element.value)
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,294 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"os/user"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/grufwub/go-bufpools"
|
||||||
|
"github.com/grufwub/go-config"
|
||||||
|
"github.com/grufwub/go-filecache"
|
||||||
|
log "github.com/grufwub/go-logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func usage(code int) {
|
||||||
|
fmt.Printf("Usage: %s [-v|--version] [-c|--config $file]\n", os.Args[0])
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseConfigAndSetup parses necessary core server config from file (and any others defined), and sets up the core ready for Start() to be called
|
||||||
|
func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newListener func() (*Listener, error), fileContent func(*Path) FileContent, dirHandler func(*Client, *os.File, *Path) error, largeHandler func(*Client, *os.File, *Path) error, appendCgi func(*Client, *Request, []string) []string) {
|
||||||
|
// Default configuration file location
|
||||||
|
configFile := "/etc/gophi." + proto + ".conf"
|
||||||
|
|
||||||
|
// If we have arguments to handle, do so!
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
switch os.Args[1] {
|
||||||
|
case "-c", "--config":
|
||||||
|
if len(os.Args) != 3 {
|
||||||
|
usage(1)
|
||||||
|
}
|
||||||
|
configFile = os.Args[2]
|
||||||
|
case "-v", "--version":
|
||||||
|
fmt.Printf("Gophi (%s) %s\n", proto, Version)
|
||||||
|
os.Exit(0)
|
||||||
|
default:
|
||||||
|
usage(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Core configuration
|
||||||
|
tree.StringVar(&Root, "root", "/var/"+proto)
|
||||||
|
tree.StringVar(&Bind, "listen", "")
|
||||||
|
tree.StringVar(&Hostname, "hostname", "")
|
||||||
|
port := tree.Uint64("port", uint64(defaultPort))
|
||||||
|
chroot := tree.String("chroot", "")
|
||||||
|
username := tree.String("user", "")
|
||||||
|
groupname := tree.String("group", "")
|
||||||
|
|
||||||
|
// Filesystem configuration
|
||||||
|
fReadBuf := tree.Uint64("filesystem.read-buf", 1024)
|
||||||
|
tree.DurationVar(&monitorSleepTime, "filesystem.cache.monitor-freq", time.Second*60)
|
||||||
|
cacheMax := tree.Float64("filesystem.cache.file-max", 1.0)
|
||||||
|
cacheSize := tree.Uint64("filesystem.cache.size", 100)
|
||||||
|
cacheAgeMax := tree.Duration("filesystem.cache.age-max", time.Minute*5)
|
||||||
|
|
||||||
|
// Request mapping, hiding, restricting
|
||||||
|
restrictedPathsList := tree.StringArray("requests.restrict", []string{})
|
||||||
|
hiddenPathsList := tree.StringArray("requests.hidden", []string{})
|
||||||
|
remapRequestsList := tree.StringArray("requests.remap", []string{})
|
||||||
|
|
||||||
|
// Logging configuration
|
||||||
|
sysLog := tree.String("log.system", "stdout")
|
||||||
|
accLog := tree.String("log.access", "stdout")
|
||||||
|
|
||||||
|
// Connection configuration
|
||||||
|
tree.DurationVar(&connReadDeadline, "connection.read-timeout", time.Second*5)
|
||||||
|
tree.DurationVar(&connWriteDeadline, "connection.write-timeout", time.Second*15)
|
||||||
|
cWriteBuf := tree.Uint64("connection.write-buf", 1024)
|
||||||
|
cReadMax := tree.Uint64("connection.read-max", 1024)
|
||||||
|
|
||||||
|
// CGI configuration
|
||||||
|
cgiDir := tree.String("cgi.directory", "")
|
||||||
|
safePath := tree.String("cgi.safe-path", "/bin:/usr/bin")
|
||||||
|
|
||||||
|
// User space configuration
|
||||||
|
userSpacesEnabled := tree.Bool("user-spaces", false)
|
||||||
|
|
||||||
|
// Parse provided config file
|
||||||
|
tree.Parse(configFile)
|
||||||
|
|
||||||
|
// Setup loggers
|
||||||
|
SystemLog = setupLogger(*sysLog)
|
||||||
|
if *sysLog == *accLog {
|
||||||
|
AccessLog = SystemLog
|
||||||
|
} else {
|
||||||
|
AccessLog = setupLogger(*accLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check valid values for BindAddr and Hostname
|
||||||
|
if Hostname == "" {
|
||||||
|
if Bind == "" {
|
||||||
|
SystemLog.Fatal("At least one of 'hostname' or 'listen' must be non-empty!")
|
||||||
|
}
|
||||||
|
Hostname = Bind
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check valid root (i.e. not empty!)
|
||||||
|
if Root == "" {
|
||||||
|
SystemLog.Fatal("No server root directory supplied!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set port info
|
||||||
|
Port = strconv.Itoa(int(*port))
|
||||||
|
|
||||||
|
// Set protocol string (only really used by CGI and one call to SystemLog)
|
||||||
|
protocol = proto
|
||||||
|
|
||||||
|
// Setup listener BEFORE entering chroot
|
||||||
|
// in case TLS cert+key needs to be read
|
||||||
|
var err error
|
||||||
|
serverListener, err = newListener()
|
||||||
|
if err != nil {
|
||||||
|
SystemLog.Fatalf("Failed to start listener on %s://%s:%s (%s:%s) - %s", protocol, Hostname, Port, Bind, Port, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the sync pools
|
||||||
|
// NOTE:
|
||||||
|
// - this must be done early on before Root is used so we can
|
||||||
|
// sanitize it first
|
||||||
|
// - cReadMax is +2 because it accounts for \r\n line-end
|
||||||
|
connRequestBufferPool = bufpools.NewBufferPool(int(*cReadMax + 2))
|
||||||
|
connBufferedWriterPool = bufpools.NewBufferedWriterPool(int(*cWriteBuf))
|
||||||
|
fileBufferedReaderPool = bufpools.NewBufferedReaderPool(int(*fReadBuf))
|
||||||
|
fileBufferPool = bufpools.NewBufferPool(int(*fReadBuf))
|
||||||
|
pathBuilderPool = &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return newPathBuilder()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// - username supplied, lookup and set uid.
|
||||||
|
// - groupname supplied, lookup and set gid.
|
||||||
|
// - username but no groupname, we use the user primary gid
|
||||||
|
var uid, gid int
|
||||||
|
if *username != "" {
|
||||||
|
u, err := user.Lookup(*username)
|
||||||
|
if err != nil {
|
||||||
|
SystemLog.Fatalf("Error looking up user: %s", err.Error())
|
||||||
|
}
|
||||||
|
uid, _ = strconv.Atoi(u.Uid)
|
||||||
|
gid, _ = strconv.Atoi(u.Gid)
|
||||||
|
}
|
||||||
|
if *groupname != "" {
|
||||||
|
g, err := user.LookupGroup(*groupname)
|
||||||
|
if err != nil {
|
||||||
|
SystemLog.Fatalf("Error looking up group: %s", err.Error())
|
||||||
|
}
|
||||||
|
gid, _ = strconv.Atoi(g.Gid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If chroot provided, change to this!
|
||||||
|
if *chroot != "" {
|
||||||
|
err := syscall.Chroot(*chroot)
|
||||||
|
if err != nil {
|
||||||
|
SystemLog.Fatalf("Error chrooting into directory: %s", err.Error())
|
||||||
|
}
|
||||||
|
SystemLog.Infof("Chrooting into dir: %s", *chroot)
|
||||||
|
|
||||||
|
// Ensure we're at root of chroot
|
||||||
|
err = os.Chdir("/")
|
||||||
|
if err != nil {
|
||||||
|
SystemLog.Fatalf("Error entering server directory: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitize the root path! This function always returns
|
||||||
|
// a relative path, so we have to check beforehand whether
|
||||||
|
// we need to make sure its an absolute path
|
||||||
|
isAbs := (Root[0] == '/')
|
||||||
|
Root = sanitizePath(Root)
|
||||||
|
if isAbs {
|
||||||
|
Root = "/" + Root
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change to server root
|
||||||
|
err = os.Chdir(Root)
|
||||||
|
if err != nil {
|
||||||
|
SystemLog.Fatalf("Error entering server directory: %s", err.Error())
|
||||||
|
}
|
||||||
|
SystemLog.Infof("Entered server dir: %s", Root)
|
||||||
|
|
||||||
|
// If been supplied a group, change to requested group
|
||||||
|
if *groupname != "" {
|
||||||
|
err := syscall.Setgid(gid)
|
||||||
|
if err != nil {
|
||||||
|
SystemLog.Fatalf("Error performing setgid: %s", err.Error())
|
||||||
|
}
|
||||||
|
SystemLog.Infof("Running as group: %s", *groupname)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If been supplied a user, switch to requested user
|
||||||
|
if *username != "" {
|
||||||
|
err := syscall.Setuid(uid)
|
||||||
|
if err != nil {
|
||||||
|
SystemLog.Fatalf("Error performing setuid: %s", err.Error())
|
||||||
|
}
|
||||||
|
SystemLog.Infof("Running as user: %s", *username)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check not running as root
|
||||||
|
if syscall.Geteuid() == 0 || syscall.Getegid() == 0 {
|
||||||
|
SystemLog.Fatal("Gophi does not support running as root!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileSystemObject (and related) setup
|
||||||
|
fileSizeMax = int64(1048576.0 * *cacheMax) // gets megabytes value in bytes
|
||||||
|
FileCache = filecache.NewFileCache(int(*cacheSize), *cacheAgeMax)
|
||||||
|
|
||||||
|
// If no restricted paths provided, set to the disabled function. Else, compile and enable
|
||||||
|
if len(*restrictedPathsList) == 0 {
|
||||||
|
SystemLog.Info("Path restrictions disabled")
|
||||||
|
IsRestrictedPath = isRestrictedPathDisabled
|
||||||
|
} else {
|
||||||
|
SystemLog.Info("Path restrictions enabled")
|
||||||
|
restrictedPaths = compileRestrictedPathsRegex(*restrictedPathsList)
|
||||||
|
IsRestrictedPath = isRestrictedPathEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no hidden paths provided, set to the disabled function. Else, compile and enable
|
||||||
|
if len(*hiddenPathsList) == 0 {
|
||||||
|
SystemLog.Info("Path hiding disabled")
|
||||||
|
IsHiddenPath = isHiddenPathDisabled
|
||||||
|
} else {
|
||||||
|
SystemLog.Info("Path hiding enabled")
|
||||||
|
hiddenPaths = compileHiddenPathsRegex(*hiddenPathsList)
|
||||||
|
IsHiddenPath = isHiddenPathEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no remapped paths provided, set to the disabled function. Else, compile and enable
|
||||||
|
if len(*remapRequestsList) == 0 {
|
||||||
|
SystemLog.Info("Request remapping disabled")
|
||||||
|
RemapRequest = remapRequestDisabled
|
||||||
|
} else {
|
||||||
|
SystemLog.Info("Request remapping enabled")
|
||||||
|
requestRemaps = compileRequestRemapRegex(*remapRequestsList)
|
||||||
|
RemapRequest = remapRequestEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no CGI dir supplied, set to disabled function. Else, compile and enable
|
||||||
|
if *cgiDir == "" {
|
||||||
|
SystemLog.Info("CGI script support disabled")
|
||||||
|
WithinCGIDir = withinCGIDirDisabled
|
||||||
|
} else {
|
||||||
|
SystemLog.Info("CGI script support enabled")
|
||||||
|
SystemLog.Infof("CGI safe path: %s", *safePath)
|
||||||
|
cgiPath = NewSanitizedPathAtRoot(Root, *cgiDir)
|
||||||
|
cgiDirRegex = compileCGIRegex(cgiPath.Relative())
|
||||||
|
cgiEnv = setupInitialCGIEnv(*safePath)
|
||||||
|
WithinCGIDir = withinCGIDirEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set appropriate Path builder function depending
|
||||||
|
// on whether user spaces enabled or disabled
|
||||||
|
if *userSpacesEnabled {
|
||||||
|
SystemLog.Info("User spaces support enabled")
|
||||||
|
BuildPath = buildPathUserSpacesEnabled
|
||||||
|
SystemLog.Info("User space directory: public_" + protocol)
|
||||||
|
} else {
|
||||||
|
SystemLog.Info("User spaces support disabled")
|
||||||
|
BuildPath = buildPathUserSpacesDisabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set provided protocol specific functions
|
||||||
|
newFileContent = fileContent
|
||||||
|
handleDirectory = dirHandler
|
||||||
|
handleLargeFile = largeHandler
|
||||||
|
appendCgiEnv = appendCgi
|
||||||
|
|
||||||
|
// Setup signal channel
|
||||||
|
sigChannel = make(chan os.Signal)
|
||||||
|
signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupLogger(output string) *log.SLogger {
|
||||||
|
switch output {
|
||||||
|
case "stdout":
|
||||||
|
return log.NewSLogger(os.Stdout, true)
|
||||||
|
case "stderr":
|
||||||
|
return log.NewSLogger(os.Stderr, true)
|
||||||
|
case "null":
|
||||||
|
return log.NewSLogger(&log.NilWriter{}, true)
|
||||||
|
default:
|
||||||
|
file, err := os.OpenFile(output, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error opening log output %s: %s", output, err.Error())
|
||||||
|
}
|
||||||
|
return log.NewSLogger(file, true)
|
||||||
|
}
|
||||||
|
}
|
@ -1,80 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// isGeneratedType just checks if a file's contents implemented is GeneratedFileContents
|
|
||||||
func isGeneratedType(f *file) bool {
|
|
||||||
switch f.contents.(type) {
|
|
||||||
case *generatedFileContents:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// file provides a structure for managing a cached file including freshness, last refresh time etc
|
|
||||||
type file struct {
|
|
||||||
contents FileContents
|
|
||||||
lastRefresh int64
|
|
||||||
isFresh bool
|
|
||||||
UpgradeableMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// newFile returns a new File based on supplied FileContents
|
|
||||||
func newFile(contents FileContents) *file {
|
|
||||||
return &file{
|
|
||||||
contents,
|
|
||||||
0,
|
|
||||||
true,
|
|
||||||
UpgradeableMutex{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsFresh returns files freshness status
|
|
||||||
func (f *file) IsFresh() bool {
|
|
||||||
return f.isFresh
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFresh sets the file as fresh
|
|
||||||
func (f *file) SetFresh() {
|
|
||||||
f.isFresh = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetUnfresh sets the file as unfresh
|
|
||||||
func (f *file) SetUnfresh() {
|
|
||||||
f.isFresh = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// LastRefresh gets the time in nanoseconds of last refresh
|
|
||||||
func (f *file) LastRefresh() int64 {
|
|
||||||
return f.lastRefresh
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateRefreshTime updates the lastRefresh time to the current time in nanoseconds
|
|
||||||
func (f *file) UpdateRefreshTime() {
|
|
||||||
f.lastRefresh = time.Now().UnixNano()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CacheContents caches the file contents using the supplied file descriptor
|
|
||||||
func (f *file) CacheContents(fd *os.File, path *Path) Error {
|
|
||||||
f.contents.Clear()
|
|
||||||
|
|
||||||
// Load the file contents into cache
|
|
||||||
err := f.contents.Load(fd, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the cache freshness
|
|
||||||
f.UpdateRefreshTime()
|
|
||||||
f.SetFresh()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteToClient writes the cached file contents to the supplied client
|
|
||||||
func (f *file) WriteToClient(client *Client, path *Path) Error {
|
|
||||||
return f.contents.WriteToClient(client, path)
|
|
||||||
}
|
|
@ -0,0 +1,34 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileContent provides an interface for caching, rendering and getting cached contents of a file
|
||||||
|
type FileContent interface {
|
||||||
|
Load(*Path, *os.File) error
|
||||||
|
WriteToClient(*Client, *Path) error
|
||||||
|
Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegularFileContent is the simplest implementation of core.FileContents for regular files
|
||||||
|
type RegularFileContent struct {
|
||||||
|
content []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load takes an open FD and loads the file contents into FileContents memory
|
||||||
|
func (fc *RegularFileContent) Load(p *Path, file *os.File) error {
|
||||||
|
var err error
|
||||||
|
fc.content, err = ReadFile(file)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteToClient writes the current contents of FileContents to the client
|
||||||
|
func (fc *RegularFileContent) WriteToClient(client *Client, p *Path) error {
|
||||||
|
return client.Conn().Write(fc.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear empties currently cached FileContents memory
|
||||||
|
func (fc *RegularFileContent) Clear() {
|
||||||
|
fc.content = nil
|
||||||
|
}
|
@ -1,48 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import "os"
|
|
||||||
|
|
||||||
// FileContents provides an interface for caching, rendering and getting cached contents of a file
|
|
||||||
type FileContents interface {
|
|
||||||
WriteToClient(*Client, *Path) Error
|
|
||||||
Load(*os.File, *Path) Error
|
|
||||||
Clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
// generatedFileContents is a simple FileContents implementation for holding onto a generated (virtual) file contents
|
|
||||||
type generatedFileContents struct {
|
|
||||||
content []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteToClient writes the generated file contents to the client
|
|
||||||
func (fc *generatedFileContents) WriteToClient(client *Client, path *Path) Error {
|
|
||||||
return client.Conn().Write(fc.content)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load does nothing
|
|
||||||
func (fc *generatedFileContents) Load(fd *os.File, path *Path) Error { return nil }
|
|
||||||
|
|
||||||
// Clear does nothing
|
|
||||||
func (fc *generatedFileContents) Clear() {}
|
|
||||||
|
|
||||||
// RegularFileContents is the simplest implementation of core.FileContents for regular files
|
|
||||||
type RegularFileContents struct {
|
|
||||||
contents []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteToClient writes the current contents of FileContents to the client
|
|
||||||
func (fc *RegularFileContents) WriteToClient(client *Client, path *Path) Error {
|
|
||||||
return client.Conn().Write(fc.contents)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load takes an open FD and loads the file contents into FileContents memory
|
|
||||||
func (fc *RegularFileContents) Load(fd *os.File, path *Path) Error {
|
|
||||||
var err Error
|
|
||||||
fc.contents, err = FileSystem.ReadFile(fd)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear empties currently cached FileContents memory
|
|
||||||
func (fc *RegularFileContents) Clear() {
|
|
||||||
fc.contents = nil
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Root stores the server's root directory
|
|
||||||
Root string
|
|
||||||
|
|
||||||
// Bind stores the server's bound IP
|
|
||||||
Bind string
|
|
||||||
|
|
||||||
// Hostname stores the host's outward hostname
|
|
||||||
Hostname string
|
|
||||||
|
|
||||||
// Port stores the internal port the host is binded to
|
|
||||||
Port string
|
|
||||||
|
|
||||||
// FwdPort stores the host's outward port number
|
|
||||||
FwdPort string
|
|
||||||
)
|
|
@ -1,37 +1,26 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import "net"
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
// serverListener holds the global Listener object
|
"github.com/grufwub/go-errors"
|
||||||
var serverListener *listener
|
)
|
||||||
|
|
||||||
// listener wraps a net.TCPListener to return our own clients on each Accept()
|
// Listener wraps a net.Listener to return our own clients on each Accept()
|
||||||
type listener struct {
|
type Listener struct {
|
||||||
l *net.TCPListener
|
l net.Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewListener returns a new Listener or Error
|
// NewListener returns a new Listener object wrapping a net.Listener
|
||||||
func newListener(ip, port string) (*listener, Error) {
|
func NewListener(l net.Listener) *Listener {
|
||||||
// Try resolve provided ip and port details
|
return &Listener{l}
|
||||||
laddr, err := net.ResolveTCPAddr("tcp", ip+":"+port)
|
|
||||||
if err != nil {
|
|
||||||
return nil, WrapError(ListenerResolveErr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create listener!
|
|
||||||
l, err := net.ListenTCP("tcp", laddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, WrapError(ListenerBeginErr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &listener{l}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accept accepts a new connection and returns a client, or error
|
// Accept accepts a new connection and returns a client, or error
|
||||||
func (l *listener) Accept() (*Client, Error) {
|
func (l *Listener) Accept() (*Client, error) {
|
||||||
conn, err := l.l.AcceptTCP()
|
conn, err := l.l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, WrapError(ListenerAcceptErr, err)
|
return nil, errors.With(err).WrapWithin(ErrListenerAccept)
|
||||||
}
|
}
|
||||||
return NewClient(conn), nil
|
return NewClient(conn), nil
|
||||||
}
|
}
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// AccessLog holds a global access LogObject
|
|
||||||
AccessLog loggerInterface
|
|
||||||
|
|
||||||
// SystemLog holds a global system LogObject
|
|
||||||
SystemLog loggerInterface
|
|
||||||
)
|
|
||||||
|
|
||||||
func setupLogger(output string) loggerInterface {
|
|
||||||
switch output {
|
|
||||||
case "stdout":
|
|
||||||
return &stdLogger{}
|
|
||||||
case "null":
|
|
||||||
return &nullLogger{}
|
|
||||||
default:
|
|
||||||
fd, err := os.OpenFile(output, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf(logOutputErrStr, output, err.Error())
|
|
||||||
}
|
|
||||||
return &logger{log.New(fd, "", log.LstdFlags)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoggerInterface specifies an interface that can log different message levels
|
|
||||||
type loggerInterface interface {
|
|
||||||
Info(string, ...interface{})
|
|
||||||
Error(string, ...interface{})
|
|
||||||
Fatal(string, ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// StdLogger implements LoggerInterface to log to output using regular log
|
|
||||||
type stdLogger struct{}
|
|
||||||
|
|
||||||
// Info logs to log.Logger with info level prefix
|
|
||||||
func (l *stdLogger) Info(fmt string, args ...interface{}) {
|
|
||||||
log.Printf(":: I :: "+fmt, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error logs to log.Logger with error level prefix
|
|
||||||
func (l *stdLogger) Error(fmt string, args ...interface{}) {
|
|
||||||
log.Printf(":: E :: "+fmt, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatal logs to standard log with fatal prefix and terminates program
|
|
||||||
func (l *stdLogger) Fatal(fmt string, args ...interface{}) {
|
|
||||||
log.Fatalf(":: F :: "+fmt, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// logger implements LoggerInterface to log to output using underlying log.Logger
|
|
||||||
type logger struct {
|
|
||||||
lg *log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info logs to log.Logger with info level prefix
|
|
||||||
func (l *logger) Info(fmt string, args ...interface{}) {
|
|
||||||
l.lg.Printf("I :: "+fmt, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error logs to log.Logger with error level prefix
|
|
||||||
func (l *logger) Error(fmt string, args ...interface{}) {
|
|
||||||
l.lg.Printf("E :: "+fmt, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatal logs to log.Logger with fatal prefix and terminates program
|
|
||||||
func (l *logger) Fatal(fmt string, args ...interface{}) {
|
|
||||||
l.lg.Fatalf("F :: "+fmt, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// nullLogger implements LoggerInterface to do absolutely fuck-all
|
|
||||||
type nullLogger struct{}
|
|
||||||
|
|
||||||
// Info does nothing
|
|
||||||
func (l *nullLogger) Info(fmt string, args ...interface{}) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error does nothing
|
|
||||||
func (l *nullLogger) Error(fmt string, args ...interface{}) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatal simply terminates the program
|
|
||||||
func (l *nullLogger) Fatal(fmt string, args ...interface{}) {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UpgradeableMutex wraps a RWMutex and provides safe upgrading / downgrading
|
|
||||||
// by informing you whether a write lock was achieved in the brief swap time
|
|
||||||
type UpgradeableMutex struct {
|
|
||||||
wLast int64
|
|
||||||
internal sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// RLock exactly wraps the internal RWMutex
|
|
||||||
func (mu *UpgradeableMutex) RLock() {
|
|
||||||
mu.internal.RLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RUnlock exactly wraps the internal RWMutex
|
|
||||||
func (mu *UpgradeableMutex) RUnlock() {
|
|
||||||
mu.internal.RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock wraps the internal RWMutex, atomically storing the last write-lock time
|
|
||||||
func (mu *UpgradeableMutex) Lock() {
|
|
||||||
mu.internal.Lock()
|
|
||||||
atomic.StoreInt64(&mu.wLast, time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlock exactly wraps the internal RWMutex
|
|
||||||
func (mu *UpgradeableMutex) Unlock() {
|
|
||||||
mu.internal.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// safeSwap stores the current time, performs the swap function and checks if
|
|
||||||
// any write locks were achieved during the swap function
|
|
||||||
func (mu *UpgradeableMutex) safeSwap(swapFn func()) bool {
|
|
||||||
// Get the 'now' time
|
|
||||||
now := time.Now().UnixNano()
|
|
||||||
|
|
||||||
// Store now time
|
|
||||||
atomic.StoreInt64(&mu.wLast, now)
|
|
||||||
|
|
||||||
// Perform the swap
|
|
||||||
swapFn()
|
|
||||||
|
|
||||||
// Successful swap determined by if last write-lock
|
|
||||||
// is still equal to 'now'
|
|
||||||
return atomic.LoadInt64(&mu.wLast) == now
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpgradeLock upgrades a read to a write lock, returning success state as a bool
|
|
||||||
func (mu *UpgradeableMutex) UpgradeLock() bool {
|
|
||||||
return mu.safeSwap(func() {
|
|
||||||
mu.internal.RUnlock()
|
|
||||||
mu.internal.Lock()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// DowngradeLock downgrades a write to a read lock, returning success state as a bool
|
|
||||||
func (mu *UpgradeableMutex) DowngradeLock() bool {
|
|
||||||
return mu.safeSwap(func() {
|
|
||||||
mu.internal.Unlock()
|
|
||||||
mu.internal.RLock()
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,108 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
connBufferedReaderPool *bufferedReaderPool
|
|
||||||
connBufferedWriterPool *bufferedWriterPool
|
|
||||||
fileBufferedReaderPool *bufferedReaderPool
|
|
||||||
fileBufferPool *bufferPool
|
|
||||||
)
|
|
||||||
|
|
||||||
type bufferPool struct {
|
|
||||||
pool sync.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBufferPool(size int) *bufferPool {
|
|
||||||
return &bufferPool{
|
|
||||||
pool: sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return make([]byte, size)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *bufferPool) Get() []byte {
|
|
||||||
// Just return and cast a buffer
|
|
||||||
return bp.pool.Get().([]byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *bufferPool) Put(b []byte) {
|
|
||||||
// Just put back in pool
|
|
||||||
bp.pool.Put(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
type bufferedReaderPool struct {
|
|
||||||
pool sync.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBufferedReaderPool(size int) *bufferedReaderPool {
|
|
||||||
return &bufferedReaderPool{
|
|
||||||
pool: sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return bufio.NewReaderSize(nil, size)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *bufferedReaderPool) Get(r io.Reader) *bufio.Reader {
|
|
||||||
// Get a buffered reader from the pool
|
|
||||||
br := bp.pool.Get().(*bufio.Reader)
|
|
||||||
|
|
||||||
// Reset to use our new reader!
|
|
||||||
br.Reset(r)
|
|
||||||
|
|
||||||
// Return
|
|
||||||
return br
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *bufferedReaderPool) Put(br *bufio.Reader) {
|
|
||||||
// We must reset again here to ensure
|
|
||||||
// we don't mess with GC with unused underlying
|
|
||||||
// reader.
|
|
||||||
br.Reset(nil)
|
|
||||||
|
|
||||||
// Put back in the pool
|
|
||||||
bp.pool.Put(br)
|
|
||||||
}
|
|
||||||
|
|
||||||
type bufferedWriterPool struct {
|
|
||||||
pool sync.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBufferedWriterPool(size int) *bufferedWriterPool {
|
|
||||||
return &bufferedWriterPool{
|
|
||||||
pool: sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return bufio.NewWriterSize(nil, size)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *bufferedWriterPool) Get(w io.Writer) *bufio.Writer {
|
|
||||||
// Get a buffered writer from the pool
|
|
||||||
bw := bp.pool.Get().(*bufio.Writer)
|
|
||||||
|
|
||||||
// Reset to user our new writer
|
|
||||||
bw.Reset(w)
|
|
||||||
|
|
||||||
// Return
|
|
||||||
return bw
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *bufferedWriterPool) Put(bw *bufio.Writer) {
|
|
||||||
// We must reset again here to ensure
|
|
||||||
// we don't mess with GC with unused underlying
|
|
||||||
// writer.
|
|
||||||
bw.Reset(nil)
|
|
||||||
|
|
||||||
// Put back in the pool
|
|
||||||
bp.pool.Put(bw)
|
|
||||||
}
|
|
@ -1,25 +1,61 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
// Request is a data structure for storing a filesystem path, and params, parsed from a client's request
|
// Request is a data structure for storing a filesystem path, and query, parsed from a client's request
|
||||||
type Request struct {
|
type Request struct {
|
||||||
p *Path
|
path *Path
|
||||||
params string
|
query string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRequest returns a new Request object
|
||||||
|
func NewRequest(path *Path, query string) *Request {
|
||||||
|
return &Request{
|
||||||
|
path: path,
|
||||||
|
query: query,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the full parsed request string (mainly for logging)
|
||||||
|
func (r *Request) String() string {
|
||||||
|
query := r.query
|
||||||
|
if query != "" {
|
||||||
|
query = "?" + query
|
||||||
|
}
|
||||||
|
return r.path.Selector() + query
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path returns the requests associate Path object
|
// Path returns the requests associate Path object
|
||||||
func (r *Request) Path() *Path {
|
func (r *Request) Path() *Path {
|
||||||
return r.p
|
return r.path
|
||||||
}
|
}
|
||||||
|
|
||||||
// Params returns the request's parameters string
|
// Query returns the request's query string
|
||||||
func (r *Request) Params() string {
|
func (r *Request) Query() string {
|
||||||
return r.params
|
return r.query
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remap modifies a request to use new relative path, and accommodate supplied extra parameters
|
// AddToQuery adds provided query to beginning of existing query, formatting as necessary
|
||||||
func (r *Request) Remap(rel, params string) {
|
func (r *Request) AddToQuery(query string) {
|
||||||
if len(r.params) > 0 {
|
// Ensure we have been given query
|
||||||
r.params = params + "&" + r.params
|
if len(query) < 1 {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
r.p.Remap(rel)
|
|
||||||
|
// Either append or set query
|
||||||
|
if len(r.query) > 0 {
|
||||||
|
r.query = query + "&" + r.query
|
||||||
|
} else {
|
||||||
|
r.query = query
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remap modifies a request to use new relative path, and accommodate supplied extra query if provided
|
||||||
|
func (r *Request) Remap(rel, query string) {
|
||||||
|
r.AddToQuery(query)
|
||||||
|
r.path.Remap(rel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemapDirect modifies a request to use new absolute path, and accomodate supplied extra query if provided
|
||||||
|
func (r *Request) RemapDirect(abs, query string) {
|
||||||
|
r.AddToQuery(query)
|
||||||
|
r.path.RemapDirect(abs)
|
||||||
}
|
}
|
||||||
|
@ -1,162 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
// Core flag string constants
|
|
||||||
const (
|
|
||||||
sysLogFlagStr = "sys-log"
|
|
||||||
sysLogDescStr = "System log output location ['stdout', 'null', $filename]"
|
|
||||||
|
|
||||||
accLogFlagStr = "acc-log"
|
|
||||||
accLogDescStr = "Access log output location ['stdout', 'null', $filename]"
|
|
||||||
|
|
||||||
rootFlagStr = "root"
|
|
||||||
rootDescStr = "Server root directory"
|
|
||||||
|
|
||||||
bindFlagStr = "bind"
|
|
||||||
bindDescStr = "IP address to bind to"
|
|
||||||
|
|
||||||
hostnameFlagStr = "hostname"
|
|
||||||
hostnameDescStr = "Server hostname (FQDN)"
|
|
||||||
|
|
||||||
portFlagStr = "port"
|
|
||||||
portDescStr = "Port to listen on"
|
|
||||||
|
|
||||||
fwdPortFlagStr = "fwd-port"
|
|
||||||
fwdPortDescStr = "Outward-facing port"
|
|
||||||
|
|
||||||
readDeadlineFlagStr = "read-deadline"
|
|
||||||
readDeadlineDescStr = "Connection read deadline (timeout)"
|
|
||||||
|
|
||||||
writeDeadlineFlagStr = "write-deadline"
|
|
||||||
writeDeadlineDescStr = "Connection write deadline (timeout)"
|
|
||||||
|
|
||||||
connReadBufFlagStr = "conn-read-buf"
|
|
||||||
connReadBufDescStr = "Connection read buffer size (bytes)"
|
|
||||||
|
|
||||||
connWriteBufFlagStr = "conn-write-buf"
|
|
||||||
connWriteBufDescStr = "Connection write buffer size (bytes)"
|
|
||||||
|
|
||||||
connReadMaxFlagStr = "conn-read-max"
|
|
||||||
connReadMaxDescStr = "Connection read max (bytes)"
|
|
||||||
|
|
||||||
fileReadBufFlagStr = "file-read-buf"
|
|
||||||
fileReadBufDescStr = "File read buffer size (bytes)"
|
|
||||||
|
|
||||||
monitorSleepTimeFlagStr = "cache-monitor-freq"
|
|
||||||
monitorSleepTimeDescStr = "File cache freshness monitor frequency"
|
|
||||||
|
|
||||||
cacheFileMaxFlagStr = "cache-file-max"
|
|
||||||
cacheFileMaxDescStr = "Max cached file size (megabytes)"
|
|
||||||
|
|
||||||
cacheSizeFlagStr = "cache-size"
|
|
||||||
cacheSizeDescStr = "File cache size"
|
|
||||||
|
|
||||||
restrictPathsFlagStr = "restrict-paths"
|
|
||||||
restrictPathsDescStr = "Restrict paths as new-line separated list of regex statements (see documenation)"
|
|
||||||
|
|
||||||
hiddenPathsFlagStr = "hidden-paths"
|
|
||||||
hiddenPathsDescStr = "Hidden paths as new-line separated list of regex statements (see documentation)"
|
|
||||||
|
|
||||||
remapRequestsFlagStr = "remap-requests"
|
|
||||||
remapRequestsDescStr = "Remap requests as new-line separated list of remap statements (see documenation)"
|
|
||||||
|
|
||||||
cgiDirFlagStr = "cgi-dir"
|
|
||||||
cgiDirDescStr = "CGI scripts directory (empty to disable)"
|
|
||||||
|
|
||||||
maxCGITimeFlagStr = "max-cgi-time"
|
|
||||||
maxCGITimeDescStr = "Max CGI script execution time"
|
|
||||||
|
|
||||||
safePathFlagStr = "safe-path"
|
|
||||||
safePathDescStr = "CGI environment safe PATH variable"
|
|
||||||
|
|
||||||
httpCompatCGIFlagStr = "http-compat-cgi"
|
|
||||||
httpCompatCGIDescStr = "Enable HTTP compatibility for CGI scripts by stripping headers"
|
|
||||||
|
|
||||||
httpPrefixBufFlagStr = "http-prefix-buf"
|
|
||||||
httpPrefixBufDescStr = "Buffer size used for stripping HTTP headers"
|
|
||||||
|
|
||||||
userDirFlagStr = "user-dir"
|
|
||||||
userDirDescStr = "User subdir for personal server space"
|
|
||||||
|
|
||||||
versionFlagStr = "version"
|
|
||||||
versionDescStr = "Print version string"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Log string constants
|
|
||||||
const (
|
|
||||||
hostnameBindEmptyStr = "At least one of hostname or bind-addr must be non-empty!"
|
|
||||||
|
|
||||||
chDirStr = "Entered server dir: %s"
|
|
||||||
chDirErrStr = "Error entering server directory: %s"
|
|
||||||
|
|
||||||
listenerBeginFailStr = "Failed to start listener on %s://%s:%s (%s:%s) - %s"
|
|
||||||
listeningOnStr = "Listening on %s://%s:%s (%s:%s)"
|
|
||||||
|
|
||||||
cacheMonitorStartStr = "Starting cache monitor with freq: %s"
|
|
||||||
cacheFileStatErrStr = "Failed to stat file in cache: %s"
|
|
||||||
|
|
||||||
pathRestrictionsEnabledStr = "Path restrictions enabled"
|
|
||||||
pathRestrictionsDisabledStr = "Path restrictions disabled"
|
|
||||||
pathRestrictRegexCompileFailStr = "Failed compiling restricted path regex: %s"
|
|
||||||
pathRestrictRegexCompiledStr = "Compiled restricted path regex: %s"
|
|
||||||
|
|
||||||
pathHidingEnabledStr = "Path hiding enabled"
|
|
||||||
pathHidingDisableStr = "Path hiding disabled"
|
|
||||||
pathHidingRegexCompileFailStr = "Failed compiling hidden path regex: %s"
|
|
||||||
pathHidingRegexCompiledStr = "Compiled hidden path regex: %s"
|
|
||||||
|
|
||||||
requestRemapEnabledStr = "Request remapping enabled"
|
|
||||||
requestRemapDisabledStr = "Request remapping disabled"
|
|
||||||
requestRemapRegexInvalidStr = "Invalid request remap regex: %s"
|
|
||||||
requestRemapRegexCompileFailStr = "Failed compiling request remap regex: %s"
|
|
||||||
requestRemapRegexCompiledStr = "Compiled path remap regex: %s"
|
|
||||||
requestRemappedStr = "Remapped request: %s %s"
|
|
||||||
|
|
||||||
cgiSupportEnabledStr = "CGI script support enabled"
|
|
||||||
cgiSupportDisabledStr = "CGI script support disabled"
|
|
||||||
cgiDirOutsideRootStr = "CGI directory must not be outside server root!"
|
|
||||||
cgiDirStr = "CGI directory: %s"
|
|
||||||
cgiHTTPCompatEnabledStr = "CGI HTTP compatibility enabled, prefix buffer: %d"
|
|
||||||
cgiExecuteErrStr = "Exit executing: %s [%d]"
|
|
||||||
|
|
||||||
userDirEnabledStr = "User directory support enabled"
|
|
||||||
userDirDisabledStr = "User directory support disabled"
|
|
||||||
userDirBackTraverseErrStr = "User directory with back-traversal not supported: %s"
|
|
||||||
userDirStr = "User directory: %s"
|
|
||||||
|
|
||||||
signalReceivedStr = "Signal received: %v. Shutting down..."
|
|
||||||
|
|
||||||
logOutputErrStr = "Error opening log output %s: %s"
|
|
||||||
|
|
||||||
pgidNotFoundErrStr = "Process unfinished, PGID not found!"
|
|
||||||
pgidStopErrStr = "Error stopping process group %d: %s"
|
|
||||||
|
|
||||||
connWriteErrStr = "Conn write error"
|
|
||||||
connReadErrStr = "Conn read error"
|
|
||||||
connCloseErrStr = "Conn close error"
|
|
||||||
listenerResolveErrStr = "Listener resolve error"
|
|
||||||
listenerBeginErrStr = "Listener begin error"
|
|
||||||
listenerAcceptErrStr = "Listener accept error"
|
|
||||||
invalidIPErrStr = "Invalid IP"
|
|
||||||
invalidPortErrStr = "Invalid port"
|
|
||||||
mutexUpgradeErrStr = "Mutex upgrade fail"
|
|
||||||
mutexDowngradeErrStr = "Mutex downgrade fail"
|
|
||||||
fileOpenErrStr = "File open error"
|
|
||||||
fileStatErrStr = "File stat error"
|
|
||||||
fileReadErrStr = "File read error"
|
|
||||||
fileTypeErrStr = "Unsupported file type"
|
|
||||||
directoryReadErrStr = "Directory read error"
|
|
||||||
restrictedPathErrStr = "Restricted path"
|
|
||||||
invalidRequestErrStr = "Invalid request"
|
|
||||||
cgiStartErrStr = "CGI start error"
|
|
||||||
cgiExitCodeErrStr = "CGI non-zero exit code"
|
|
||||||
cgiStatus400ErrStr = "CGI status: 400"
|
|
||||||
cgiStatus401ErrStr = "CGI status: 401"
|
|
||||||
cgiStatus403ErrStr = "CGI status: 403"
|
|
||||||
cgiStatus404ErrStr = "CGI status: 404"
|
|
||||||
cgiStatus408ErrStr = "CGI status: 408"
|
|
||||||
cgiStatus410ErrStr = "CGI status: 410"
|
|
||||||
cgiStatus500ErrStr = "CGI status: 500"
|
|
||||||
cgiStatus501ErrStr = "CGI status: 501"
|
|
||||||
cgiStatus503ErrStr = "CGI status: 503"
|
|
||||||
cgiStatusUnknownErrStr = "CGI status: unknown"
|
|
||||||
)
|
|
@ -0,0 +1,9 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
// Temporary check on Linux to ensure
|
||||||
|
// correct Go version being used
|
||||||
|
var _ = syscall.AllThreadsSyscall
|
@ -1,75 +1,271 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
"github.com/grufwub/go-errors"
|
||||||
// getRequestPaths points to either of the getRequestPath____ functions
|
|
||||||
getRequestPath func(string) *Path
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseURLEncodedRequest takes a received string and safely parses a request from this
|
// HasAsciiControlBytes returns whether a byte slice contains ASCII control bytes
|
||||||
func ParseURLEncodedRequest(received string) (*Request, Error) {
|
func HasAsciiControlBytes(raw string) bool {
|
||||||
// Check for ASCII control bytes
|
for i := 0; i < len(raw); i++ {
|
||||||
for i := 0; i < len(received); i++ {
|
if raw[i] < ' ' || raw[i] == 0xf {
|
||||||
if received[i] < ' ' || received[i] == 0x7f {
|
return true
|
||||||
return nil, NewError(InvalidRequestErr)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Split into 2 substrings by '?'. URL path and query
|
// ParseScheme attempts to parse a scheme from a raw url
|
||||||
rawPath, params := splitBy(received, "?")
|
func ParseScheme(raw string) (string, string, error) {
|
||||||
|
// If first char is:
|
||||||
|
// - valid ascii (but non-scheme), return here no errors
|
||||||
|
// - end of scheme char, return bad request error
|
||||||
|
c := raw[0]
|
||||||
|
switch {
|
||||||
|
case ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'):
|
||||||
|
// All good, continue
|
||||||
|
case c == ':':
|
||||||
|
return "", "", ErrParsingScheme.Extend(raw)
|
||||||
|
default:
|
||||||
|
// Invalid scheme char (or scheme first-char) return
|
||||||
|
return "", raw, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Unescape path
|
// Iterate
|
||||||
rawPath, err := url.PathUnescape(rawPath)
|
for i := 1; i < len(raw); i++ {
|
||||||
if err != nil {
|
c := raw[i]
|
||||||
return nil, WrapError(InvalidRequestErr, err)
|
switch {
|
||||||
|
case ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') ||
|
||||||
|
('0' <= c && c <= '9') || c == '+' || c == '-' || c == '.':
|
||||||
|
// Is valid ASCII, do nothing
|
||||||
|
case c == ':':
|
||||||
|
// Return the scheme (lowercase)
|
||||||
|
return strings.ToLower(raw[:i]), raw[i+1:], nil
|
||||||
|
default:
|
||||||
|
// Invalid char, return
|
||||||
|
return "", raw, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", raw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHex(b byte) bool {
|
||||||
|
return ('a' <= b && b <= 'f') ||
|
||||||
|
('A' <= b && b <= 'F') ||
|
||||||
|
('0' <= b && b <= '9')
|
||||||
|
}
|
||||||
|
|
||||||
|
func unHex(b byte) byte {
|
||||||
|
switch {
|
||||||
|
case '0' <= b && b <= '9':
|
||||||
|
return b - '0'
|
||||||
|
case 'a' <= b && b <= 'f':
|
||||||
|
return b - 'a' + 10
|
||||||
|
case 'A' <= b && b <= 'F':
|
||||||
|
return b - 'A' + 10
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldEscape(b byte) bool {
|
||||||
|
// All alphanumeric are unreserved
|
||||||
|
if 'a' <= b && b <= 'z' || 'A' <= b && b <= 'Z' || '0' <= b && b <= '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch b {
|
||||||
|
// Further unreserved
|
||||||
|
case '-', '_', '.', '~':
|
||||||
|
return false
|
||||||
|
|
||||||
|
// All else should be escaped
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldHostEscape(b byte) bool {
|
||||||
|
switch b {
|
||||||
|
// Allowed host sub-delims +
|
||||||
|
// ':' for port +
|
||||||
|
// '[]' for ipv6 +
|
||||||
|
// '<>' only others we can allow (can't be % encoded)
|
||||||
|
case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '"':
|
||||||
|
return false
|
||||||
|
|
||||||
|
// Check all-else
|
||||||
|
default:
|
||||||
|
return shouldEscape(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldPathEscape(b byte) bool {
|
||||||
|
switch b {
|
||||||
|
// Reserved character in path.
|
||||||
|
// Bear in mind ;, ARE allowed in a URL path,
|
||||||
|
// but when converting from a filesystem-->URL path
|
||||||
|
// (how this will be used), it will need escaping.
|
||||||
|
case '?', ';', ',':
|
||||||
|
return true
|
||||||
|
|
||||||
|
// Allowed in path
|
||||||
|
case '$', '&', '+', '/', ':', '=', '@':
|
||||||
|
return false
|
||||||
|
|
||||||
|
// Check all-else
|
||||||
|
default:
|
||||||
|
return shouldEscape(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unescape(raw string, count int) string {
|
||||||
|
var t strings.Builder
|
||||||
|
t.Grow(len(raw) - 2*count)
|
||||||
|
for i := 0; i < len(raw); i++ {
|
||||||
|
switch raw[i] {
|
||||||
|
// Replace % encoded char
|
||||||
|
case '%':
|
||||||
|
t.WriteByte(unHex(raw[i+1])<<4 | unHex(raw[i+2]))
|
||||||
|
i += 2
|
||||||
|
|
||||||
|
// Write as-is
|
||||||
|
default:
|
||||||
|
t.WriteByte(raw[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func unescapeHost(raw string) (string, error) {
|
||||||
|
// Count all the percent signs
|
||||||
|
count := 0
|
||||||
|
for i := 0; i < len(raw); {
|
||||||
|
switch raw[i] {
|
||||||
|
case '%':
|
||||||
|
// Increase count
|
||||||
|
count++
|
||||||
|
|
||||||
|
// If not a valid % encoded hex value, return with error
|
||||||
|
if i+2 >= len(raw) || !isHex(raw[i+1]) || !isHex(raw[i+2]) {
|
||||||
|
return "", ErrUnescapingHost.Extend(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the host component % encoding can only be used
|
||||||
|
// for non-ASCII bytes. And rfc6874 introduces %25 for
|
||||||
|
// escaped percent sign in IPv6 literals
|
||||||
|
if unHex(raw[i+1]) < 8 && raw[i:i+3] != "%25" {
|
||||||
|
return "", ErrUnescapingHost.Extend(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip iteration past the
|
||||||
|
// hex we just confirmed
|
||||||
|
i += 3
|
||||||
|
default:
|
||||||
|
// If within ASCII range, and shoud be escaped, return error
|
||||||
|
if raw[i] < 0x80 && shouldHostEscape(raw[i]) {
|
||||||
|
return "", ErrUnescapingHost.Extend(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iter
|
||||||
|
i++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return new request
|
// No encoding? return as-is. Else, escape
|
||||||
return &Request{getRequestPath(rawPath), params}, nil
|
if count == 0 {
|
||||||
|
return raw, nil
|
||||||
|
}
|
||||||
|
return unescape(raw, count), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseInternalRequest parses an internal request string based on the current directory
|
func unescapePath(raw string) (string, error) {
|
||||||
func ParseInternalRequest(p *Path, line string) *Request {
|
// Count all the percent signs
|
||||||
rawPath, params := splitBy(line, "?")
|
count := 0
|
||||||
if path.IsAbs(rawPath) {
|
length := len(raw)
|
||||||
return &Request{getRequestPath(rawPath), params}
|
for i := 0; i < length; {
|
||||||
|
switch raw[i] {
|
||||||
|
case '%':
|
||||||
|
// Increase count
|
||||||
|
count++
|
||||||
|
|
||||||
|
// If not a valid % encoded hex value, return with error
|
||||||
|
if i+2 >= length || !isHex(raw[i+1]) || !isHex(raw[i+2]) {
|
||||||
|
return "", ErrUnescapingPath.Extend(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip iteration past the
|
||||||
|
// hex we just confirmed
|
||||||
|
i += 3
|
||||||
|
default:
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No encoding? return as-is. Else, escape
|
||||||
|
if count == 0 {
|
||||||
|
return raw, nil
|
||||||
}
|
}
|
||||||
return &Request{newSanitizedPath(p.Root(), rawPath), params}
|
return unescape(raw, count), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRequestPathUserDirEnabled creates a Path object from raw path, converting ~USER to user subdirectory roots, else at server root
|
// EscapePath escapes a URL path
|
||||||
func getRequestPathUserDirEnabled(rawPath string) *Path {
|
func EscapePath(path string) string {
|
||||||
if userPath := strings.TrimPrefix(rawPath, "/"); strings.HasPrefix(userPath, "~") {
|
const upperhex = "0123456789ABCDEF"
|
||||||
// We found a user path! Split into the user part, and remaining path
|
|
||||||
user, remaining := splitBy(userPath, "/")
|
|
||||||
|
|
||||||
// Empty user, we been duped! Return server root
|
count := 0
|
||||||
if len(user) <= 1 {
|
for i := 0; i < len(path); i++ {
|
||||||
return &Path{Root, "", "/"}
|
if shouldPathEscape(path[i]) {
|
||||||
|
count++
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get sanitized user root, else return server root
|
if count == 0 {
|
||||||
root, ok := sanitizeUserRoot(path.Join("/home", user[1:], userDir))
|
return path
|
||||||
if !ok {
|
}
|
||||||
return &Path{Root, "", "/"}
|
|
||||||
|
sb := strings.Builder{}
|
||||||
|
sb.Grow(len(path) + 2*count)
|
||||||
|
for i := 0; i < len(path); i++ {
|
||||||
|
c := path[i]
|
||||||
|
if shouldPathEscape(c) {
|
||||||
|
sb.WriteByte('%')
|
||||||
|
sb.WriteByte(upperhex[c>>4])
|
||||||
|
sb.WriteByte(upperhex[c&15])
|
||||||
|
} else {
|
||||||
|
sb.WriteByte(c)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build new Path
|
return sb.String()
|
||||||
rel := sanitizeRawPath(root, remaining)
|
}
|
||||||
sel := "/~" + user[1:] + formatSelector(rel)
|
|
||||||
return &Path{root, rel, sel}
|
// ParseEncodedHost parses encoded host info, safely returning unescape host and port
|
||||||
|
func ParseEncodedHost(raw string) (string, string, error) {
|
||||||
|
// Unescape the host info
|
||||||
|
raw, err := unescapeHost(raw)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err.(errors.Error).WrapWithin(ErrParsingHost)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return regular server root + rawPath
|
// Split by last ':' and return
|
||||||
return newSanitizedPath(Root, rawPath)
|
host, port := SplitByLast(raw, ":")
|
||||||
|
return host, port, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRequestPathUserDirDisabled creates a Path object from raw path, always at server root
|
// ParseEncodedURI parses encoded URI, safely returning unescaped path and still-escaped query
|
||||||
func getRequestPathUserDirDisabled(rawPath string) *Path {
|
func ParseEncodedURI(received string) (string, string, error) {
|
||||||
return newSanitizedPath(Root, rawPath)
|
// Split into path and query
|
||||||
|
rawPath, query := SplitBy(received, "?")
|
||||||
|
|
||||||
|
// Unescape path, query is up-to CGI scripts
|
||||||
|
rawPath, err := unescapePath(rawPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err.(errors.Error).WrapWithin(ErrParsingURI)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the raw path and query
|
||||||
|
return rawPath, query, nil
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
package core_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"gophi/core"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var toEscape = []string{
|
||||||
|
"",
|
||||||
|
"abc",
|
||||||
|
"abc+def",
|
||||||
|
"a?b",
|
||||||
|
"one two",
|
||||||
|
"10%",
|
||||||
|
" ?&=#+%!<>#\"{}|\\^[]`☺\t:@$'()*,;",
|
||||||
|
}
|
||||||
|
|
||||||
|
var escaped = []string{
|
||||||
|
"",
|
||||||
|
"abc",
|
||||||
|
"abc+def",
|
||||||
|
"a%3Fb",
|
||||||
|
"one%20two",
|
||||||
|
"10%25",
|
||||||
|
"%20%3F&=%23+%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09:@$%27%28%29%2A%2C%3B",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathEscape(t *testing.T) {
|
||||||
|
for i, path := range toEscape {
|
||||||
|
if escapedPath := core.EscapePath(path); escapedPath != escaped[i] {
|
||||||
|
t.Fatalf("Failed escaping path!\nGot: %s\nExpected: %s\n", escapedPath, escaped[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseEncodedHost(t *testing.T) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseEncodedURI(t *testing.T) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCorePathEscape(b *testing.B) {
|
||||||
|
var s string
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, path := range toEscape {
|
||||||
|
s = core.EscapePath(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprint(ioutil.Discard, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkURLPathEscape(b *testing.B) {
|
||||||
|
var s string
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, path := range toEscape {
|
||||||
|
// This isn't *exactly* a fair comparison to core.EscapePath,
|
||||||
|
// since url.PathEscape() escapes a path _segment_ as opposed
|
||||||
|
// to any entire path, whereas core.EscapePath() escapes an entire
|
||||||
|
// filesystem path.
|
||||||
|
s = url.PathEscape(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprint(ioutil.Discard, s)
|
||||||
|
}
|
@ -1,46 +1,35 @@
|
|||||||
# CGI/1.1 Compliance
|
# CGI/1.1 Compliance
|
||||||
|
|
||||||
The list of environment variables that gophor sets are as follows.
|
The list of environment variables that gophi sets are as follows.
|
||||||
|
|
||||||
RFC 3875 standard:
|
|
||||||
|
|
||||||
```
|
|
||||||
# Set
|
|
||||||
GATEWAY INTERFACE
|
|
||||||
SERVER_SOFTWARE
|
|
||||||
SERVER_PROTOCOL
|
|
||||||
CONTENT_LENGTH
|
|
||||||
REQUEST_METHOD
|
|
||||||
SERVER_NAME
|
|
||||||
SERVER_PORT
|
|
||||||
REMOTE_ADDR
|
|
||||||
QUERY_STRING
|
|
||||||
SCRIPT_NAME
|
|
||||||
SCRIPT_FILENAME
|
|
||||||
|
|
||||||
# NOT set
|
|
||||||
Env Var | Reasoning
|
|
||||||
----------------------------------------------
|
|
||||||
PATH_INFO | This variable can fuck off, having to find the shortest
|
|
||||||
| valid part of path heirarchy in a URI every single
|
|
||||||
| CGI request so you can split and set this variable is SO
|
|
||||||
| inefficient. However, if someone more knowledgeable has
|
|
||||||
| other opinions or would like to point out where I'm wrong I
|
|
||||||
| will happily change my tune on this.
|
|
||||||
PATH_TRANSLATED | See above.
|
|
||||||
AUTH_TYPE | Until we implement authentication of some kind, ignoring.
|
|
||||||
CONTENT_TYPE | Very HTTP-centric relying on 'content-type' header.
|
|
||||||
REMOTE_IDENT | Remote client identity information.
|
|
||||||
REMOTE_HOST | Basically if the client has a resolving name (not just
|
|
||||||
| IP), not really necessary.
|
|
||||||
REMOTE_USER | Remote user id, not used as again no user auth yet.
|
|
||||||
```
|
```
|
||||||
|
# Both
|
||||||
|
GATEWAY INTERFACE | "CGI/1.1"
|
||||||
|
SERVER_SOFTWARE | "gophi" + protocol + version
|
||||||
|
SERVER_PROTOCOL | protocol
|
||||||
|
SERVER_NAME | server hostname
|
||||||
|
SERVER_PORT | server listening port
|
||||||
|
REMOTE_ADDR | client ip
|
||||||
|
QUERY_STRING | request query string
|
||||||
|
SCRIPT_NAME | script relative path
|
||||||
|
SCRIPT_FILENAME | script absolute path
|
||||||
|
PATH_INFO | remainder of URI's path after selector
|
||||||
|
DOCUMENT_ROOT | server root directory
|
||||||
|
REQUEST_URI | request URI (i.e. selector)
|
||||||
|
|
||||||
Non-standard:
|
# Gopher specific
|
||||||
|
COLUMNS | no. columns server is configured for
|
||||||
|
|
||||||
```
|
# Gemini specific
|
||||||
# Set
|
GEMINI_URL | full gemini url (including hostname, port, scheme)
|
||||||
SELECTOR
|
TLS_CIPHER | TLS cipher in use
|
||||||
DOCUMENT_ROOT
|
TLS_VERSION | TLS version in use
|
||||||
REQUEST_URI
|
AUTH_TYPE | "Certificate" when client certs found
|
||||||
|
REMOTE_USER | TLS client cert subject common name
|
||||||
|
TLs_CLIENT_HASH | TLS client cert sha256 hash ("SHA256:______")
|
||||||
|
TLS_CLIENT_NOT_BEFORE | TLS client cert not before date
|
||||||
|
TLS_CLIENT_NOT_AFTER | TLS client cert not after date
|
||||||
|
TLS_CLIENT_ISSUER | TLS client cert issuer
|
||||||
|
TLS_CLIENT_SUBJECT | TLS client cert subject
|
||||||
|
TLS_CLIENT_VERIFIED | TLS client cert validity, ("SUCCESS" or "FAIL:<error string>")
|
||||||
```
|
```
|
@ -0,0 +1,5 @@
|
|||||||
|
# Features
|
||||||
|
|
||||||
|
- Serve `DIR/index.gmi` by default, else falls back to directory listing
|
||||||
|
|
||||||
|
- [coming soon...] vhosts!
|
@ -0,0 +1,128 @@
|
|||||||
|
# Chroot directory (empty to disable)
|
||||||
|
chroot = "/var/gophi"
|
||||||
|
|
||||||
|
# Server root. If chroot enabled
|
||||||
|
# this will be seen as relative to
|
||||||
|
# the server chroot
|
||||||
|
root = "/"
|
||||||
|
|
||||||
|
# UNIX user and group names server
|
||||||
|
# should run under:
|
||||||
|
# - if user supplied but no group, then
|
||||||
|
# user's primary group is used
|
||||||
|
# - if both are blank, runs as current
|
||||||
|
# user
|
||||||
|
user = "grufwub"
|
||||||
|
group = ""
|
||||||
|
|
||||||
|
# Server bind address
|
||||||
|
listen = "127.0.0.1"
|
||||||
|
|
||||||
|
# Server hostname
|
||||||
|
hostname = "localhost"
|
||||||
|
|
||||||
|
# Port to listen on
|
||||||
|
port = 1024
|
||||||
|
|
||||||
|
# Enable user server spaces, e.g.
|
||||||
|
# ~/public_{gopher,gemini}
|
||||||
|
user-spaces = false
|
||||||
|
|
||||||
|
[connection]
|
||||||
|
# Connection read timeout
|
||||||
|
read-timeout = "5s"
|
||||||
|
|
||||||
|
# Connection write timeout
|
||||||
|
write-timeout = "15s"
|
||||||
|
|
||||||
|
# Connection write buffer size (in bytes)
|
||||||
|
write-buf = 1024
|
||||||
|
|
||||||
|
# Connection read max (in bytes), with
|
||||||
|
# max read sizes this low we don't bother
|
||||||
|
# buffering reads
|
||||||
|
read-max = 1024
|
||||||
|
|
||||||
|
[filesystem]
|
||||||
|
# File read buffer size (in bytes)
|
||||||
|
read-buf = 1024
|
||||||
|
|
||||||
|
[filesystem.cache]
|
||||||
|
# Filesystem monitor check freq.
|
||||||
|
monitor-freq = "60s"
|
||||||
|
|
||||||
|
# Maximum cached file size (in MB)
|
||||||
|
file-max = 1.0
|
||||||
|
|
||||||
|
# Maximum file age before mark
|
||||||
|
# as stale, i.e. safe to be
|
||||||
|
# removed on next monitor sweep
|
||||||
|
age-max = "5m"
|
||||||
|
|
||||||
|
# Cache size count
|
||||||
|
size= 100
|
||||||
|
|
||||||
|
[requests]
|
||||||
|
# NOTE: please use apostrophe declared
|
||||||
|
# strings (i.e. ' not ") otherwise
|
||||||
|
# backslashes will need to be escaped
|
||||||
|
|
||||||
|
# String array of filesystem path
|
||||||
|
# regex statements to restrict.
|
||||||
|
restrict = [
|
||||||
|
'/(.+/)?\.[a-zA-Z0-9-_.]+',
|
||||||
|
]
|
||||||
|
|
||||||
|
# String array of filesystem path
|
||||||
|
# regex statements to hide from dir
|
||||||
|
# listings
|
||||||
|
hidden = [
|
||||||
|
'',
|
||||||
|
]
|
||||||
|
|
||||||
|
# String array of request remapping
|
||||||
|
# regex statements
|
||||||
|
remap = [
|
||||||
|
'',
|
||||||
|
]
|
||||||
|
|
||||||
|
[log]
|
||||||
|
# Log output locations, options:
|
||||||
|
# - stdout -> /dev/stdout
|
||||||
|
# - stderr -> /dev/stderr
|
||||||
|
# - null -> /dev/null
|
||||||
|
# - $file -> $file
|
||||||
|
system = "stdout"
|
||||||
|
access = "stdout"
|
||||||
|
|
||||||
|
[cgi]
|
||||||
|
# Relative CGI scripts directory
|
||||||
|
# path within server root
|
||||||
|
directory = "cgi-bin"
|
||||||
|
|
||||||
|
# CGI environment $PATH
|
||||||
|
safe-path = "/bin:/usr/bin"
|
||||||
|
|
||||||
|
# Gopher specific configuration, uncomment
|
||||||
|
# if you have built gophi as a gopher server
|
||||||
|
#[gopher]
|
||||||
|
# # Page width before line truncation
|
||||||
|
# page-width = 80
|
||||||
|
#
|
||||||
|
# # Footer text included below gophermaps
|
||||||
|
# footer = ""
|
||||||
|
#
|
||||||
|
# # Subgophermap size max (in megabytes)
|
||||||
|
# subgopher-max = 1.0
|
||||||
|
#
|
||||||
|
# # Information included in caps.txt
|
||||||
|
# # policy file
|
||||||
|
# admin-email = ""
|
||||||
|
# description = ""
|
||||||
|
# geolocation = ""
|
||||||
|
|
||||||
|
# Gemini specific configuration, uncomment
|
||||||
|
# if you have built gophi as a gemini server
|
||||||
|
#[gemini]
|
||||||
|
# tls-cert = ""
|
||||||
|
# tls-key = ""
|
@ -0,0 +1,68 @@
|
|||||||
|
package gemini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
|
"gophi/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mapped to tls.Version__ where:
|
||||||
|
// value - 0x300 = index.
|
||||||
|
// Deprecated versions will always be
|
||||||
|
// prefixed by 'DEPRECATED'
|
||||||
|
var tlsVersionStrings = []string{
|
||||||
|
"DEPRECATED:SSLv3",
|
||||||
|
"TLSv1.0",
|
||||||
|
"TLSv1.1",
|
||||||
|
"TLSv1.2",
|
||||||
|
"TLSv1.3",
|
||||||
|
}
|
||||||
|
|
||||||
|
func certSha256Hash(cert *x509.Certificate) string {
|
||||||
|
checksum := sha256.Sum256(cert.Raw)
|
||||||
|
return hex.EncodeToString(checksum[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendCgiEnv(client *core.Client, request *core.Request, env []string) []string {
|
||||||
|
// Build and append the full gemini url
|
||||||
|
env = append(env, "GEMINI_URL=gemini://"+core.Hostname+":"+core.Port+request.Path().Selector())
|
||||||
|
|
||||||
|
// Cast client underlying net.Conn as tls.Conn
|
||||||
|
tlsConn := client.Conn().Conn().(*tls.Conn)
|
||||||
|
state := tlsConn.ConnectionState()
|
||||||
|
|
||||||
|
// Append TLS env vars
|
||||||
|
env = append(env, "TLS_CIPHER="+tls.CipherSuiteName(state.CipherSuite))
|
||||||
|
env = append(env, "TLS_VERSION="+tlsVersionStrings[state.Version-0x300])
|
||||||
|
|
||||||
|
// Append TLS client cert vars (if present!)
|
||||||
|
clientCerts := state.PeerCertificates
|
||||||
|
if len(clientCerts) > 0 {
|
||||||
|
// Only use first if multiple client
|
||||||
|
// certs available
|
||||||
|
cert := clientCerts[0]
|
||||||
|
|
||||||
|
// Verify client cert
|
||||||
|
var isVerified string
|
||||||
|
_, err := cert.Verify(x509.VerifyOptions{})
|
||||||
|
if err != nil {
|
||||||
|
isVerified = "FAILED:" + err.Error()
|
||||||
|
} else {
|
||||||
|
isVerified = "SUCCESS"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set user cert environment vars
|
||||||
|
env = append(env, "AUTH_TYPE=Certificate")
|
||||||
|
env = append(env, "REMOTE_USER="+cert.Subject.CommonName)
|
||||||
|
env = append(env, "TLS_CLIENT_HASH=SHA256:"+certSha256Hash(cert))
|
||||||
|
env = append(env, "TLS_CLIENT_NOT_BEFORE="+cert.NotBefore.String())
|
||||||
|
env = append(env, "TLS_CLIENT_NOT_AFTER="+cert.NotAfter.String())
|
||||||
|
env = append(env, "TLS_CLIENT_ISSUER="+cert.Issuer.String())
|
||||||
|
env = append(env, "TLS_CLIENT_SUBJECT="+cert.Subject.String())
|
||||||
|
env = append(env, "TLS_CLIENT_VERIFIED="+isVerified)
|
||||||
|
}
|
||||||
|
|
||||||
|
return env
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
package gemini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gophi/core"
|
||||||
|
|
||||||
|
"github.com/grufwub/go-errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Gemini specific base errors
|
||||||
|
var (
|
||||||
|
errInvalidScheme = errors.BaseError("invalid request scheme")
|
||||||
|
errProxyRequest = errors.BaseError("host:port pair differ from our own")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Gemini status codes
|
||||||
|
var (
|
||||||
|
statusInput = "10"
|
||||||
|
statusSensitive = "11"
|
||||||
|
statusTemporaryRedirect = "30"
|
||||||
|
statusPermanentRedirect = "31"
|
||||||
|
statusTemporaryFailure = "40"
|
||||||
|
statusServerUnavailable = "41"
|
||||||
|
statusCGIError = "42"
|
||||||
|
statusProxyError = "43"
|
||||||
|
statusSlowDown = "44"
|
||||||
|
statusPermanentFailure = "50"
|
||||||
|
statusNotFound = "51"
|
||||||
|
statusGone = "52"
|
||||||
|
statusProxyRequestRefused = "53"
|
||||||
|
statusBadRequest = "59"
|
||||||
|
statusClientCertificateRequired = "60"
|
||||||
|
statusClientCertificateNotAuthorized = "61"
|
||||||
|
statusCertificateNotValid = "62"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Gemini error responses
|
||||||
|
var (
|
||||||
|
// more specific respnoses
|
||||||
|
errConnReadRsp = buildResponseHeader(statusTemporaryFailure, "Read Failure")
|
||||||
|
errRestrictedRsp = buildResponseHeader(statusNotFound, "Restricted Path")
|
||||||
|
errInvalidSchemeRsp = buildResponseHeader(statusProxyRequestRefused, "Unsupported Scheme")
|
||||||
|
errProxyRequestRsp = buildResponseHeader(statusProxyRequestRefused, "Proxying Unsupported")
|
||||||
|
|
||||||
|
// generic responses
|
||||||
|
errNotFoundRsp = buildResponseHeader(statusNotFound, "Not Found")
|
||||||
|
errTemporaryFailureRsp = buildResponseHeader(statusTemporaryFailure, "Temporary Failure")
|
||||||
|
errPermanentFailureRsp = buildResponseHeader(statusPermanentFailure, "Permanent Failure")
|
||||||
|
errBadRequestRsp = buildResponseHeader(statusBadRequest, "Bad Request")
|
||||||
|
)
|
||||||
|
|
||||||
|
// generateErrorResponse takes an error code and generates an error response byte slice
|
||||||
|
func generateErrorResponse(err error) ([]byte, bool) {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, core.ErrConnWrite):
|
||||||
|
return nil, false // no point responding if we couldn't write
|
||||||
|
case errors.Is(err, core.ErrConnRead):
|
||||||
|
return errConnReadRsp, true
|
||||||
|
case errors.Is(err, core.ErrConnClose):
|
||||||
|
return nil, false // no point responding if we couldn't close
|
||||||
|
case errors.Is(err, core.ErrMutexUpgrade):
|
||||||
|
return errTemporaryFailureRsp, true
|
||||||
|
case errors.Is(err, core.ErrMutexDowngrade):
|
||||||
|
return errTemporaryFailureRsp, true
|
||||||
|
case errors.Is(err, core.ErrFileOpen):
|
||||||
|
return errNotFoundRsp, true
|
||||||
|
case errors.Is(err, core.ErrFileStat):
|
||||||
|
return errNotFoundRsp, true
|
||||||
|
case errors.Is(err, core.ErrFileRead):
|
||||||
|
return errNotFoundRsp, true
|
||||||
|
case errors.Is(err, core.ErrFileType):
|
||||||
|
return errNotFoundRsp, true
|
||||||
|
case errors.Is(err, core.ErrDirectoryRead):
|
||||||
|
return errNotFoundRsp, true
|
||||||
|
case errors.Is(err, core.ErrRestrictedPath):
|
||||||
|
return errRestrictedRsp, true
|
||||||
|
case errors.Is(err, core.ErrInvalidRequest):
|
||||||
|
return errBadRequestRsp, true
|
||||||
|
case errors.Is(err, core.ErrParsingScheme):
|
||||||
|
return errBadRequestRsp, true
|
||||||
|
case errors.Is(err, core.ErrParsingHost):
|
||||||
|
return errBadRequestRsp, true
|
||||||
|
case errors.Is(err, core.ErrParsingURI):
|
||||||
|
return errBadRequestRsp, true
|
||||||
|
case errors.Is(err, core.ErrCGIStart):
|
||||||
|
return errPermanentFailureRsp, true
|
||||||
|
case errors.Is(err, core.ErrCGIExitCode):
|
||||||
|
return errTemporaryFailureRsp, true
|
||||||
|
case errors.Is(err, errInvalidScheme):
|
||||||
|
return errInvalidSchemeRsp, true
|
||||||
|
case errors.Is(err, errProxyRequest):
|
||||||
|
return errProxyRequestRsp, true
|
||||||
|
default:
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package gemini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gophi/core"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type headerPlusFileContent struct {
|
||||||
|
contents []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteToClient writes the current contents of FileContents to the client
|
||||||
|
func (fc *headerPlusFileContent) WriteToClient(client *core.Client, p *core.Path) error {
|
||||||
|
return client.Conn().Write(fc.contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load takes an open FD and loads the file contents into FileContents memory
|
||||||
|
func (fc *headerPlusFileContent) Load(p *core.Path, file *os.File) error {
|
||||||
|
// Read the file contents
|
||||||
|
contents, err := core.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sucess header + mime type response header
|
||||||
|
header := buildResponseHeader("20", getFileStatusMeta(p))
|
||||||
|
|
||||||
|
// Set the store contents and return ok
|
||||||
|
fc.contents = append(header, contents...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear empties currently cached FileContents memory
|
||||||
|
func (fc *headerPlusFileContent) Clear() {
|
||||||
|
fc.contents = nil
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package gemini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gophi/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
const gemMimeType = "text/gemini"
|
||||||
|
|
||||||
|
func getFileStatusMeta(p *core.Path) string {
|
||||||
|
// if this is a gem, return this
|
||||||
|
if isGem(p) {
|
||||||
|
return gemMimeType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get file extension
|
||||||
|
ext := core.FileExt(p.Relative())
|
||||||
|
|
||||||
|
// Try get the mime type, or use default unknown (application octet-stream)
|
||||||
|
mimeType, ok := mimeTypes[ext]
|
||||||
|
if !ok {
|
||||||
|
mimeType = "application/octet-stream"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate mime type for extension
|
||||||
|
return mimeType
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildRedirect(to string) []byte {
|
||||||
|
return buildResponseHeader("31", to)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildResponseHeader(statusCode, statusMeta string) []byte {
|
||||||
|
return []byte(statusCode + " " + statusMeta + "\r\n")
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package gemini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/tls"
|
||||||
|
"gophi/core"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/grufwub/go-config"
|
||||||
|
"github.com/grufwub/go-logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// As part of init perform initial entropy assertion
|
||||||
|
b := make([]byte, 1)
|
||||||
|
_, err := io.ReadFull(rand.Reader, b)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Failed to assert safe source of system entropy exists!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run does as says :)
|
||||||
|
func Run() {
|
||||||
|
// Create new TOML config parser
|
||||||
|
tree := make(config.Tree)
|
||||||
|
|
||||||
|
// Parse gemini specific flags, then all
|
||||||
|
certFile := tree.String("gemini.tls-cert", "")
|
||||||
|
keyFile := tree.String("gemini.tls-key", "")
|
||||||
|
core.ParseConfigAndSetup(
|
||||||
|
tree,
|
||||||
|
"gemini",
|
||||||
|
1965,
|
||||||
|
func() (*core.Listener, error) {
|
||||||
|
// Load the supplied key pair
|
||||||
|
cert, err := tls.LoadX509KeyPair(*certFile, *keyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create TLS config
|
||||||
|
config := &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
}
|
||||||
|
config.Rand = rand.Reader
|
||||||
|
|
||||||
|
// Create listener!
|
||||||
|
l, err := tls.Listen("tcp", core.Bind+":"+core.Port, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return wrapper listener
|
||||||
|
return core.NewListener(l), nil
|
||||||
|
},
|
||||||
|
newFileContent,
|
||||||
|
handleDirectory,
|
||||||
|
handleLargeFile,
|
||||||
|
appendCgiEnv,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Generate the root redirect byte slice
|
||||||
|
// (has to be done here once the Hostname and Port have been set)
|
||||||
|
rootRedirect = buildRedirect("gemini://" + core.Hostname + ":" + core.Port + "/")
|
||||||
|
|
||||||
|
// Start!
|
||||||
|
core.Start(serve)
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,16 @@
|
|||||||
|
package gemini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gophi/core"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// gemRegex is the precompiled gemini file name regex check
|
||||||
|
gemRegex = regexp.MustCompile(`^(|.+/|.+\.)gmi$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// isGem checks against gemini regex as to whether a file path is a gemini file
|
||||||
|
func isGem(path *core.Path) bool {
|
||||||
|
return gemRegex.MatchString(path.Relative())
|
||||||
|
}
|
@ -0,0 +1,175 @@
|
|||||||
|
package gemini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gophi/core"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// rootRedirectHeader stores the root redirect header byte slice,
|
||||||
|
// because there is no need in recalculating it every time it's needed
|
||||||
|
var rootRedirect []byte
|
||||||
|
|
||||||
|
// serve is the global gemini server's serve function
|
||||||
|
func serve(client *core.Client) {
|
||||||
|
// Receive line from client
|
||||||
|
received, err := client.Conn().ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
client.LogError("Conn read fail")
|
||||||
|
handleError(client, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
raw := string(received)
|
||||||
|
|
||||||
|
// Ensure is a valid URL string
|
||||||
|
if core.HasAsciiControlBytes(raw) {
|
||||||
|
client.LogError("Invalid request: %s", raw)
|
||||||
|
handleError(client, core.ErrInvalidRequest.Extendf("%s has ascii control bytes", raw))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the URL scheme (or error!)
|
||||||
|
scheme, path, err := core.ParseScheme(raw)
|
||||||
|
if err != nil {
|
||||||
|
client.LogError("Invalid request: %s", raw)
|
||||||
|
handleError(client, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infer no schema as 'gemini', else check we
|
||||||
|
// were explicitly provided 'gemini'
|
||||||
|
if scheme != "" && scheme != "gemini" {
|
||||||
|
client.LogError("Invalid request: %s", raw)
|
||||||
|
handleError(client, errInvalidScheme.Extend(scheme))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split by first '/' (with prefix '//' trimmed) to get host info and path strings
|
||||||
|
host, path := core.SplitByBefore(strings.TrimPrefix(path, "//"), "/")
|
||||||
|
|
||||||
|
// Parse the URL encoded host info
|
||||||
|
host, port, err := core.ParseEncodedHost(host)
|
||||||
|
if err != nil {
|
||||||
|
client.LogError("Invalid request: %s", raw)
|
||||||
|
handleError(client, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the host and port are our own (empty port is allowed)
|
||||||
|
if host != core.Hostname || (port != "" && port != core.Port) {
|
||||||
|
client.LogError("Invalid request: %s", raw)
|
||||||
|
handleError(client, errProxyRequest.Extend(host+":"+port))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the encoded URI into path and query components
|
||||||
|
path, query, err := core.ParseEncodedURI(path)
|
||||||
|
if err != nil {
|
||||||
|
client.LogError("Invalid request: %s", raw)
|
||||||
|
handleError(client, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect empty path to root
|
||||||
|
if len(path) < 1 {
|
||||||
|
client.LogInfo("Redirect to: /")
|
||||||
|
client.Conn().Write(rootRedirect)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build new Request from raw path and query
|
||||||
|
request := core.NewRequest(core.BuildPath(path), query)
|
||||||
|
|
||||||
|
// Handle the request! And finally, error
|
||||||
|
err = core.HandleClient(client, request)
|
||||||
|
if err != nil {
|
||||||
|
handleError(client, err)
|
||||||
|
client.LogError("Failed to serve: %s", request.String())
|
||||||
|
} else {
|
||||||
|
client.LogInfo("Served: %s", request.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleError determines whether to send an error response to the client, and logs to system
|
||||||
|
func handleError(client *core.Client, err error) {
|
||||||
|
response, ok := generateErrorResponse(err)
|
||||||
|
if ok {
|
||||||
|
client.Conn().Write(response)
|
||||||
|
}
|
||||||
|
core.SystemLog.Error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleDirectory(client *core.Client, file *os.File, p *core.Path) error {
|
||||||
|
// First check for index gem, create gem Path object
|
||||||
|
indexGem := p.JoinPathUnsafe("index.gmi")
|
||||||
|
|
||||||
|
// If index gem exists, we fetch this
|
||||||
|
file2, err := core.OpenFile(indexGem)
|
||||||
|
if err == nil {
|
||||||
|
stat, err := file2.Stat()
|
||||||
|
if err == nil {
|
||||||
|
// Fetch gem and defer close
|
||||||
|
defer file2.Close()
|
||||||
|
return core.FetchFile(client, file2, stat, indexGem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else, just close fd2
|
||||||
|
file2.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slice to write
|
||||||
|
dirContents := make([]byte, 0)
|
||||||
|
|
||||||
|
// Escape the previous dir
|
||||||
|
dirSel := core.EscapePath(p.SelectorDir())
|
||||||
|
|
||||||
|
// Add directory heading, empty line and a back line
|
||||||
|
dirContents = append(dirContents, []byte("["+core.Hostname+p.Selector()+"]\n\n")...)
|
||||||
|
dirContents = append(dirContents, []byte("=> "+dirSel+" ..\n")...)
|
||||||
|
|
||||||
|
// Scan directory and build lines
|
||||||
|
err = core.ScanDirectory(
|
||||||
|
file,
|
||||||
|
p,
|
||||||
|
func(file os.FileInfo, fp *core.Path) {
|
||||||
|
// Calculate escaped selector path
|
||||||
|
sel := core.EscapePath(fp.Selector())
|
||||||
|
|
||||||
|
// If it's a dir, append final '/' to selector
|
||||||
|
if file.IsDir() {
|
||||||
|
sel += "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append new formatted file listing
|
||||||
|
dirContents = append(dirContents, []byte("=> "+sel+" "+file.Name()+"\n")...)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate gem file header
|
||||||
|
header := buildResponseHeader("20", gemMimeType)
|
||||||
|
|
||||||
|
// Write contents!
|
||||||
|
return client.Conn().Write(append(header, []byte(dirContents)...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleLargeFile(client *core.Client, file *os.File, p *core.Path) error {
|
||||||
|
// Build the response header
|
||||||
|
header := buildResponseHeader("20", getFileStatusMeta(p))
|
||||||
|
|
||||||
|
// Write the initial header (or return!)
|
||||||
|
err := client.Conn().Write(header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally write directly from file
|
||||||
|
return client.Conn().ReadFrom(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFileContents returns a new FileContents object
|
||||||
|
func newFileContent(p *core.Path) core.FileContent {
|
||||||
|
return &headerPlusFileContent{}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
[ $# -ne 1 ] && {
|
||||||
|
echo "Usage: ${0} <hostname>"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Generating TLS cert + privkey for: ${1}"
|
||||||
|
openssl req -x509 -newkey rsa:4096 -sha256 \
|
||||||
|
-days 365 -nodes -keyout "${1}.key" \
|
||||||
|
-out "${1}.crt" -subj "/CN=${1}" \
|
||||||
|
-addext "subjectAltName=DNS:${1}"
|
@ -0,0 +1,44 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Mime types JSON source
|
||||||
|
URL='https://raw.githubusercontent.com/micnic/mime.json/master/index.json'
|
||||||
|
|
||||||
|
# Define intro to file
|
||||||
|
FILE='
|
||||||
|
// This is an automatically generated file, do not edit
|
||||||
|
package gemini
|
||||||
|
|
||||||
|
var mimeTypes = map[string]string{
|
||||||
|
// Mimetype for empty file extensions
|
||||||
|
"": "application/octet-stream",
|
||||||
|
|
||||||
|
// Begin file extension definitions
|
||||||
|
'
|
||||||
|
|
||||||
|
# Set break on new-line
|
||||||
|
IFS='
|
||||||
|
'
|
||||||
|
|
||||||
|
for line in $(curl -fL "$URL" | grep -E '".+"\s*:\s*".+"'); do
|
||||||
|
# Trim final whitespace
|
||||||
|
line=$(echo "$line" | sed -e 's|\s*$||')
|
||||||
|
|
||||||
|
# Ensure it ends in a comma
|
||||||
|
[ "${line%,}" = "$line" ] && line="${line},"
|
||||||
|
|
||||||
|
# Add to file
|
||||||
|
FILE="${FILE}${line}
|
||||||
|
"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Add final statement to file
|
||||||
|
FILE="${FILE}
|
||||||
|
}
|
||||||
|
|
||||||
|
"
|
||||||
|
|
||||||
|
# Write to file
|
||||||
|
echo "$FILE" > 'gemini/mime.go'
|
||||||
|
|
||||||
|
# Check for valid go
|
||||||
|
goimports -w 'gemini/mime.go'
|
@ -1,3 +1,11 @@
|
|||||||
module gophi
|
module gophi
|
||||||
|
|
||||||
go 1.14
|
go 1.15
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/grufwub/go-bufpools v0.1.1
|
||||||
|
github.com/grufwub/go-config v0.1.0
|
||||||
|
github.com/grufwub/go-errors v0.3.2
|
||||||
|
github.com/grufwub/go-filecache v0.1.0
|
||||||
|
github.com/grufwub/go-logger v0.1.1
|
||||||
|
)
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/grufwub/go-bufpools v0.1.1 h1:TOUKNY+UaQ784EtvP+wKoXssYQQRX5zewtrZ7u4C81M=
|
||||||
|
github.com/grufwub/go-bufpools v0.1.1/go.mod h1:ITqLRtG+W1bZHGdkWewV7inb+GcWfq2Jcjqx4AZ7aBY=
|
||||||
|
github.com/grufwub/go-config v0.1.0 h1:/UDEmprs4h4qEkgmQqthmtGZeJs8eB44qMVSqa+5sxU=
|
||||||
|
github.com/grufwub/go-config v0.1.0/go.mod h1:0U5Y0EkNeL09YkY70fNZv4Kelfayp/VroEs2UzmUG04=
|
||||||
|
github.com/grufwub/go-errors v0.3.2 h1:IB17KWLB+NNXCb+YUPMxPpvlNXGpxt0Xpad7wPxxRoo=
|
||||||
|
github.com/grufwub/go-errors v0.3.2/go.mod h1:AXGtU2fWv8ejaUUT0+9wTOlWqcxYDo8wuYnhrYtoBKM=
|
||||||
|
github.com/grufwub/go-filecache v0.1.0 h1:OugzIHzLco8LLRnAlD7m6zSFQTILjltNO8Hhr/8vcCo=
|
||||||
|
github.com/grufwub/go-filecache v0.1.0/go.mod h1:iAfqEfsC5YsyGD+f8JducuWeRqCDBVPi1+VmCaPL07Q=
|
||||||
|
github.com/grufwub/go-logger v0.1.1 h1:KnD6NNyeq3cz6dZKW/Gr+Fz9dNvkLf8KvYZXKGM5cN0=
|
||||||
|
github.com/grufwub/go-logger v0.1.1/go.mod h1:pZny1PMTpy9FAKMbaDYbPJbthl0wrSpVoIcnEjkRZaQ=
|
||||||
|
github.com/grufwub/go-upmutex v0.1.0 h1:ePACrB9VwGjBDqYfdJB8tDnnq3PNn05I4DhR0p1NT6M=
|
||||||
|
github.com/grufwub/go-upmutex v0.1.0/go.mod h1:Eb/BM4cKjBdmbwJ0XJ4GxIeSFCBOIWzIUx7RN/VKHNs=
|
||||||
|
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
|
||||||
|
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
@ -0,0 +1,11 @@
|
|||||||
|
package gopher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gophi/core"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func appendCgiEnv(client *core.Client, request *core.Request, env []string) []string {
|
||||||
|
env = append(env, "COLUMNS="+strconv.Itoa(pageWidth))
|
||||||
|
return env
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package gopher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gophi/core"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// generatedFileContents is a simple core.FileContent implementation for holding onto a generated (virtual) file contents
|
||||||
|
type generatedFileContent struct {
|
||||||
|
content []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAllFrom does nothing for generated content
|
||||||
|
func (fc *generatedFileContent) Load(p *core.Path, file *os.File) error { return nil }
|
||||||
|
|
||||||
|
// WriteAllTo writes the generated FileContent to client
|
||||||
|
func (fc *generatedFileContent) WriteToClient(client *core.Client, p *core.Path) error {
|
||||||
|
return client.Conn().Write(fc.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear does nothing
|
||||||
|
func (fc *generatedFileContent) Clear() {}
|
||||||
|
|
||||||
|
// gophermapContents is an implementation of core.FileContent that holds individually renderable sections of a gophermap
|
||||||
|
type gophermapContent struct {
|
||||||
|
sections []gophermapSection
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load takes an open FD and loads the gophermap contents into memory as different renderable sections
|
||||||
|
func (gc *gophermapContent) Load(path *core.Path, file *os.File) error {
|
||||||
|
var err error
|
||||||
|
gc.sections, err = readGophermap(file, path)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteToClient renders each cached section of the gophermap, and writes them to the client
|
||||||
|
func (gc *gophermapContent) WriteToClient(client *core.Client, path *core.Path) error {
|
||||||
|
// Render + write the sections!
|
||||||
|
for _, section := range gc.sections {
|
||||||
|
err := section.RenderAndWrite(client)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, write the footer (including last-line)
|
||||||
|
return client.Conn().Write(footer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear empties currently cached GophermapContents memory
|
||||||
|
func (gc *gophermapContent) Clear() {
|
||||||
|
gc.sections = nil
|
||||||
|
}
|
@ -1,37 +0,0 @@
|
|||||||
package gopher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gophi/core"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// gophermapContents is an implementation of core.FileContents that holds individually renderable sections of a gophermap
|
|
||||||
type gophermapContents struct {
|
|
||||||
sections []gophermapSection
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteToClient renders each cached section of the gophermap, and writes them to the client
|
|
||||||
func (gc *gophermapContents) WriteToClient(client *core.Client, path *core.Path) core.Error {
|
|
||||||
// Render + write the sections!
|
|
||||||
for _, section := range gc.sections {
|
|
||||||
err := section.RenderAndWrite(client)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, write the footer (including last-line)
|
|
||||||
return client.Conn().Write(footer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load takes an open FD and loads the gophermap contents into memory as different renderable sections
|
|
||||||
func (gc *gophermapContents) Load(fd *os.File, path *core.Path) core.Error {
|
|
||||||
var err core.Error
|
|
||||||
gc.sections, err = readGophermap(fd, path)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear empties currently cached GophermapContents memory
|
|
||||||
func (gc *gophermapContents) Clear() {
|
|
||||||
gc.sections = nil
|
|
||||||
}
|
|
@ -1,42 +1,58 @@
|
|||||||
package gopher
|
package gopher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"gophi/core"
|
"gophi/core"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/grufwub/go-config"
|
||||||
|
"github.com/grufwub/go-filecache"
|
||||||
)
|
)
|
||||||
|
|
||||||
// setup parses gopher specific flags, and all core flags, preparing server for .Run()
|
// Run does as says :)
|
||||||
func setup() {
|
func Run() {
|
||||||
pWidth := flag.Uint(pageWidthFlagStr, 80, pageWidthDescStr)
|
// Create new TOML config parser
|
||||||
footerText := flag.String(footerTextFlagStr, "Gophi, a Gopher server in Go!", footerTextDescStr)
|
tree := make(config.Tree)
|
||||||
subgopherSizeMax := flag.Float64(subgopherSizeMaxFlagStr, 1.0, subgopherSizeMaxDescStr)
|
|
||||||
admin := flag.String(adminFlagStr, "", adminDescStr)
|
// Parse gopher specific flags, then all
|
||||||
desc := flag.String(descFlagStr, "", descDescStr)
|
pWidth := tree.Uint64("gopher.page-width", 80)
|
||||||
geo := flag.String(geoFlagStr, "", geoDescStr)
|
footerText := tree.String("gopher.footer", "")
|
||||||
core.ParseFlagsAndSetup("gopher", generateErrorMessage)
|
subgopherSizeMax := tree.Float64("gopher.subgopher-max", 1.0)
|
||||||
|
admin := tree.String("gopher.admin-email", "")
|
||||||
|
desc := tree.String("gopher.description", "")
|
||||||
|
geo := tree.String("gopher.geolocation", "")
|
||||||
|
core.ParseConfigAndSetup(
|
||||||
|
tree,
|
||||||
|
"gopher",
|
||||||
|
70,
|
||||||
|
func() (*core.Listener, error) {
|
||||||
|
l, err := net.Listen("tcp", core.Bind+":"+core.Port)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return core.NewListener(l), nil
|
||||||
|
},
|
||||||
|
newFileContent,
|
||||||
|
handleDirectory,
|
||||||
|
handleLargeFile,
|
||||||
|
appendCgiEnv,
|
||||||
|
)
|
||||||
|
|
||||||
// Setup gopher specific global variables
|
// Setup gopher specific global variables
|
||||||
subgophermapSizeMax = int64(1048576.0 * *subgopherSizeMax) // convert float to megabytes
|
subgophermapSizeMax = int64(1048576.0 * *subgopherSizeMax) // convert float to megabytes
|
||||||
pageWidth = int(*pWidth)
|
pageWidth = int(*pWidth)
|
||||||
footer = buildFooter(*footerText)
|
footer = buildFooter(*footerText)
|
||||||
gophermapRegex = compileGophermapRegex()
|
|
||||||
|
|
||||||
// Add generated files to cache if not present
|
|
||||||
p := core.NewPath(core.Root, "caps.txt")
|
|
||||||
if _, err := core.FileSystem.StatFile(p); err != nil {
|
|
||||||
core.SystemLog.Info("Policy file %s not found! Generating...", p.Absolute())
|
|
||||||
core.FileSystem.AddGeneratedFile(p, generateCapsTxt(*desc, *admin, *geo))
|
|
||||||
}
|
|
||||||
|
|
||||||
p = core.NewPath(core.Root, "robots.txt")
|
|
||||||
if _, err := core.FileSystem.StatFile(p); err != nil {
|
|
||||||
core.SystemLog.Info("Policy file %s not found! Generating...", p.Absolute())
|
|
||||||
core.FileSystem.AddGeneratedFile(p, generateRobotsTxt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run does as says :)
|
// Add generated policy file to cache
|
||||||
func Run() {
|
p := core.NewSanitizedPathAtRoot(core.Root, "caps.txt")
|
||||||
setup()
|
core.SystemLog.Infof("Generating policy file %s...", p.Absolute())
|
||||||
|
core.FileCache.Put(filecache.NewFile(p.Absolute(), false, &generatedFileContent{generateCapsTxt(*desc, *admin, *geo)}))
|
||||||
|
|
||||||
|
// Remove things we don't need hanging around
|
||||||
|
desc = nil
|
||||||
|
admin = nil
|
||||||
|
geo = nil
|
||||||
|
tree = nil
|
||||||
|
|
||||||
|
// Start!
|
||||||
core.Start(serve)
|
core.Start(serve)
|
||||||
}
|
}
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
package gopher
|
|
||||||
|
|
||||||
// Client error response strings
|
|
||||||
const (
|
|
||||||
errorResponse400 = "400 Bad Request"
|
|
||||||
errorResponse401 = "401 Unauthorised"
|
|
||||||
errorResponse403 = "403 Forbidden"
|
|
||||||
errorResponse404 = "404 Not Found"
|
|
||||||
errorResponse408 = "408 Request Time-out"
|
|
||||||
errorResponse410 = "410 Gone"
|
|
||||||
errorResponse500 = "500 Internal Server Error"
|
|
||||||
errorResponse501 = "501 Not Implemented"
|
|
||||||
errorResponse503 = "503 Service Unavailable"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Gopher flag string constants
|
|
||||||
const (
|
|
||||||
pageWidthFlagStr = "page-width"
|
|
||||||
pageWidthDescStr = "Gopher page width"
|
|
||||||
|
|
||||||
footerTextFlagStr = "footer-text"
|
|
||||||
footerTextDescStr = "Footer text (empty to disable)"
|
|
||||||
|
|
||||||
subgopherSizeMaxFlagStr = "subgopher-size-max"
|
|
||||||
subgopherSizeMaxDescStr = "Subgophermap size max (megabytes)"
|
|
||||||
|
|
||||||
adminFlagStr = "admin"
|
|
||||||
adminDescStr = "Generated policy file admin email"
|
|
||||||
|
|
||||||
descFlagStr = "description"
|
|
||||||
descDescStr = "Generated policy file server description"
|
|
||||||
|
|
||||||
geoFlagStr = "geolocation"
|
|
||||||
geoDescStr = "Generated policy file server geolocation"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Log string constants
|
|
||||||
const (
|
|
||||||
clientReadFailStr = "Failed to read"
|
|
||||||
clientRedirectFmtStr = "Redirecting to: %s"
|
|
||||||
clientRequestParseFailStr = "Failed to parse request"
|
|
||||||
clientServeFailStr = "Failed to serve: %s"
|
|
||||||
clientServedStr = "Served: %s"
|
|
||||||
|
|
||||||
invalidGophermapErrStr = "Invalid gophermap"
|
|
||||||
subgophermapIsDirErrStr = "Subgophermap path is dir"
|
|
||||||
subgophermapSizeErrStr = "Subgophermap size too large"
|
|
||||||
unknownErrStr = "Unknown error code"
|
|
||||||
)
|
|
Loading…
Reference in New Issue