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 | 3 years ago |
kim (grufwub) | d43597b2fa | 3 years ago |
kim (grufwub) | db73c33fa2 | 3 years ago |
kim (grufwub) | 5d44d9c0e0 | 3 years ago |
kim (grufwub) | 40a5e6e70e | 3 years ago |
kim (grufwub) | 27366e224e | 3 years ago |
kim (grufwub) | ea83ab3f78 | 3 years ago |
kim (grufwub) | eef30c742f | 3 years ago |
kim (grufwub) | 41f0ab6b1c | 3 years ago |
kim (grufwub) | 81ff43ab29 | 3 years ago |
kim (grufwub) | f155e76661 | 3 years ago |
kim (grufwub) | 230a979993 | 3 years ago |
kim (grufwub) | c95d2e8939 | 3 years ago |
kim (grufwub) | 352cebcf50 | 3 years ago |
kim (grufwub) | c842ae78ad | 3 years ago |
kim (grufwub) | 8a541b1369 | 3 years ago |
kim (grufwub) | 5aeb201cc0 | 3 years ago |
kim (grufwub) | b2cbcc32f3 | 3 years ago |
kim (grufwub) | 2e97b28ce5 | 3 years ago |
kim (grufwub) | 1d17310377 | 3 years ago |
kim (grufwub) | e3aee14034 | 3 years ago |
kim (grufwub) | 99b9d041fc | 3 years ago |
kim (grufwub) | 239e7dd07d | 3 years ago |
kim (grufwub) | c9436be644 | 3 years ago |
kim (grufwub) | 97f30066a0 | 3 years ago |
kim (grufwub) | bdc0c427de | 3 years ago |
kim (grufwub) | f5be927a26 | 3 years ago |
kim (grufwub) | 29a8030fec | 3 years ago |
kim (grufwub) | 6eb1f5cde7 | 3 years ago |
kim (grufwub) | 3385c71f2f | 3 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
|
||||
*.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
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"net"
|
||||
|
||||
// serverListener holds the global Listener object
|
||||
var serverListener *listener
|
||||
"github.com/grufwub/go-errors"
|
||||
)
|
||||
|
||||
// listener wraps a net.TCPListener to return our own clients on each Accept()
|
||||
type listener struct {
|
||||
l *net.TCPListener
|
||||
// Listener wraps a net.Listener to return our own clients on each Accept()
|
||||
type Listener struct {
|
||||
l net.Listener
|
||||
}
|
||||
|
||||
// NewListener returns a new Listener or Error
|
||||
func newListener(ip, port string) (*listener, Error) {
|
||||
// Try resolve provided ip and port details
|
||||
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
|
||||
// NewListener returns a new Listener object wrapping a net.Listener
|
||||
func NewListener(l net.Listener) *Listener {
|
||||
return &Listener{l}
|
||||
}
|
||||
|
||||
// Accept accepts a new connection and returns a client, or error
|
||||
func (l *listener) Accept() (*Client, Error) {
|
||||
conn, err := l.l.AcceptTCP()
|
||||
func (l *Listener) Accept() (*Client, error) {
|
||||
conn, err := l.l.Accept()
|
||||
if err != nil {
|
||||
return nil, WrapError(ListenerAcceptErr, err)
|
||||
return nil, errors.With(err).WrapWithin(ErrListenerAccept)
|
||||
}
|
||||
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
|
||||
|
||||
// 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 {
|
||||
p *Path
|
||||
params string
|
||||
path *Path
|
||||
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
|
||||
func (r *Request) Path() *Path {
|
||||
return r.p
|
||||
return r.path
|
||||
}
|
||||
|
||||
// Params returns the request's parameters string
|
||||
func (r *Request) Params() string {
|
||||
return r.params
|
||||
// Query returns the request's query string
|
||||
func (r *Request) Query() string {
|
||||
return r.query
|
||||
}
|
||||
|
||||
// Remap modifies a request to use new relative path, and accommodate supplied extra parameters
|
||||
func (r *Request) Remap(rel, params string) {
|
||||
if len(r.params) > 0 {
|
||||
r.params = params + "&" + r.params
|
||||
// AddToQuery adds provided query to beginning of existing query, formatting as necessary
|
||||
func (r *Request) AddToQuery(query string) {
|
||||
// Ensure we have been given query
|
||||
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
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// getRequestPaths points to either of the getRequestPath____ functions
|
||||
getRequestPath func(string) *Path
|
||||
"github.com/grufwub/go-errors"
|
||||
)
|
||||
|
||||
// ParseURLEncodedRequest takes a received string and safely parses a request from this
|
||||
func ParseURLEncodedRequest(received string) (*Request, Error) {
|
||||
// Check for ASCII control bytes
|
||||
for i := 0; i < len(received); i++ {
|
||||
if received[i] < ' ' || received[i] == 0x7f {
|
||||
return nil, NewError(InvalidRequestErr)
|
||||
// HasAsciiControlBytes returns whether a byte slice contains ASCII control bytes
|
||||
func HasAsciiControlBytes(raw string) bool {
|
||||
for i := 0; i < len(raw); i++ {
|
||||
if raw[i] < ' ' || raw[i] == 0xf {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Split into 2 substrings by '?'. URL path and query
|
||||
rawPath, params := splitBy(received, "?")
|
||||
// ParseScheme attempts to parse a scheme from a raw url
|
||||
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
|
||||
rawPath, err := url.PathUnescape(rawPath)
|
||||
if err != nil {
|
||||
return nil, WrapError(InvalidRequestErr, err)
|
||||
// Iterate
|
||||
for i := 1; i < len(raw); i++ {
|
||||
c := raw[i]
|
||||
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
|
||||
return &Request{getRequestPath(rawPath), params}, nil
|
||||
// No encoding? return as-is. Else, escape
|
||||
if count == 0 {
|
||||
return raw, nil
|
||||
}
|
||||
return unescape(raw, count), nil
|
||||
}
|
||||
|
||||
// ParseInternalRequest parses an internal request string based on the current directory
|
||||
func ParseInternalRequest(p *Path, line string) *Request {
|
||||
rawPath, params := splitBy(line, "?")
|
||||
if path.IsAbs(rawPath) {
|
||||
return &Request{getRequestPath(rawPath), params}
|
||||
func unescapePath(raw string) (string, error) {
|
||||
// Count all the percent signs
|
||||
count := 0
|
||||
length := len(raw)
|
||||
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
|
||||
func getRequestPathUserDirEnabled(rawPath string) *Path {
|
||||
if userPath := strings.TrimPrefix(rawPath, "/"); strings.HasPrefix(userPath, "~") {
|
||||
// We found a user path! Split into the user part, and remaining path
|
||||
user, remaining := splitBy(userPath, "/")
|
||||
// EscapePath escapes a URL path
|
||||
func EscapePath(path string) string {
|
||||
const upperhex = "0123456789ABCDEF"
|
||||
|
||||
// Empty user, we been duped! Return server root
|
||||
if len(user) <= 1 {
|
||||
return &Path{Root, "", "/"}
|
||||
count := 0
|
||||
for i := 0; i < len(path); i++ {
|
||||
if shouldPathEscape(path[i]) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
// Get sanitized user root, else return server root
|
||||
root, ok := sanitizeUserRoot(path.Join("/home", user[1:], userDir))
|
||||
if !ok {
|
||||
return &Path{Root, "", "/"}
|
||||
if count == 0 {
|
||||
return path
|
||||
}
|
||||
|
||||
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
|
||||
rel := sanitizeRawPath(root, remaining)
|
||||
sel := "/~" + user[1:] + formatSelector(rel)
|
||||
return &Path{root, rel, sel}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// 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
|
||||
return newSanitizedPath(Root, rawPath)
|
||||
// Split by last ':' and return
|
||||
host, port := SplitByLast(raw, ":")
|
||||
return host, port, nil
|
||||
}
|
||||
|
||||
// getRequestPathUserDirDisabled creates a Path object from raw path, always at server root
|
||||
func getRequestPathUserDirDisabled(rawPath string) *Path {
|
||||
return newSanitizedPath(Root, rawPath)
|
||||
// ParseEncodedURI parses encoded URI, safely returning unescaped path and still-escaped query
|
||||
func ParseEncodedURI(received string) (string, string, error) {
|
||||
// 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
|
||||
|
||||
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
|
||||
|
||||
```
|
||||
# Set
|
||||
SELECTOR
|
||||
DOCUMENT_ROOT
|
||||
REQUEST_URI
|
||||
# Gemini specific
|
||||
GEMINI_URL | full gemini url (including hostname, port, scheme)
|
||||
TLS_CIPHER | TLS cipher in use
|
||||
TLS_VERSION | TLS version in use
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"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()
|
||||
func setup() {
|
||||
pWidth := flag.Uint(pageWidthFlagStr, 80, pageWidthDescStr)
|
||||
footerText := flag.String(footerTextFlagStr, "Gophi, a Gopher server in Go!", footerTextDescStr)
|
||||
subgopherSizeMax := flag.Float64(subgopherSizeMaxFlagStr, 1.0, subgopherSizeMaxDescStr)
|
||||
admin := flag.String(adminFlagStr, "", adminDescStr)
|
||||
desc := flag.String(descFlagStr, "", descDescStr)
|
||||
geo := flag.String(geoFlagStr, "", geoDescStr)
|
||||
core.ParseFlagsAndSetup("gopher", generateErrorMessage)
|
||||
// Run does as says :)
|
||||
func Run() {
|
||||
// Create new TOML config parser
|
||||
tree := make(config.Tree)
|
||||
|
||||
// Parse gopher specific flags, then all
|
||||
pWidth := tree.Uint64("gopher.page-width", 80)
|
||||
footerText := tree.String("gopher.footer", "")
|
||||
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
|
||||
subgophermapSizeMax = int64(1048576.0 * *subgopherSizeMax) // convert float to megabytes
|
||||
pageWidth = int(*pWidth)
|
||||
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 :)
|
||||
func Run() {
|
||||
setup()
|
||||
// Add generated policy file to cache
|
||||
p := core.NewSanitizedPathAtRoot(core.Root, "caps.txt")
|
||||
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)
|
||||
}
|
||||
|
@ -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