Compare commits
33 Commits
Author | SHA1 | Date |
---|---|---|
hedy | 8ae39ed6c6 | |
hedy | 593d4ca9cb | |
~hedy | d07ffafa2e | |
hedy | 95ac946384 | |
hedy | e504383e4a | |
hedy | 381097e8e5 | |
hedy | b48fd7633e | |
hedy | b399ace963 | |
hedy | 2c4c0d81f4 | |
hedy | 141a7a2bd3 | |
hedy | cd14087878 | |
hedy | d6b98d7c84 | |
hedy | 8995bd4fac | |
hedy | 60977f6963 | |
hedy | 4e2ec455eb | |
hedy | 5a16845882 | |
hedy | f12ce378ad | |
hedy | ba2692b5e9 | |
hedy | f7f6a27898 | |
hedy | 60374d9e70 | |
hedy | 1cf4ef3952 | |
hedy | 2dfaed4fa3 | |
hedy | efbf17cfd6 | |
Hedy Li | 66b01724c0 | |
Hedy Li | 930dc6380f | |
Hedy Li | 01e0687594 | |
Hedy Li | 44b8fc116d | |
Hedy Li | 8cf1358f1c | |
Hedy Li | 253fba2824 | |
Hedy Li | c68d835ca0 | |
Hedy Li | 4e96db6122 | |
Hedy Li | 7eff471da0 | |
Hedy Li | 070098ad2d |
|
@ -0,0 +1,2 @@
|
|||
*.out
|
||||
bin
|
4
LICENSE
4
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Hedy Li
|
||||
Copyright (c) 2021-2023 hedy
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
SOFTWARE.
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
.PHONY: help build clean build-all package release
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
pkg_root = .
|
||||
|
||||
### Calculate a few variables for use in building
|
||||
VERSION = $(shell git describe --tags --abbrev=0 --always)
|
||||
COMMIT = $(shell git log --pretty='format:%h' -n 1)
|
||||
BUILDDATE = $(shell date +"%Y-%m-%dT%H:%M:%S")
|
||||
# ldflags inject new values into variables at compilation time
|
||||
# this is how we dynamically set the version/etc of the application
|
||||
ldflags = "-X 'main.appVersion=$(VERSION)' \
|
||||
-X 'main.appCommit=$(COMMIT)' \
|
||||
-X 'main.buildTime=$(BUILDDATE)' \
|
||||
-w -s"
|
||||
|
||||
##@ Help
|
||||
help: ## Display this help
|
||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||
@echo
|
||||
@echo "Variable pkg_root is set to . by default. This should be the directory of spsrv source code."
|
||||
|
||||
##@ Utilities
|
||||
init: ## Install utils
|
||||
go mod download
|
||||
|
||||
dep-tidy: ## Remove unused dependencies
|
||||
go mod tidy
|
||||
|
||||
dep-upgrade: ## Upgrade versions of dependencies
|
||||
go get -u
|
||||
|
||||
##@ Build
|
||||
build: clean ## Build spsrv for your native architecture
|
||||
go build -o ./bin/spsrv -ldflags=$(ldflags) $(pkg_root)
|
||||
|
||||
build-all: clean ## Build spsrv for linux and mac
|
||||
GOOS=darwin GOARCH=amd64 go build -o ./bin/spsrv-darwin-amd64/spsrv -ldflags=$(ldflags) $(pkg_root)
|
||||
GOOS=darwin GOARCH=arm64 go build -o ./bin/spsrv-darwin-arm64/spsrv -ldflags=$(ldflags) $(pkg_root)
|
||||
GOOS=linux GOARCH=amd64 go build -o ./bin/spsrv-linux-amd64/spsrv -ldflags=$(ldflags) $(pkg_root)
|
||||
GOOS=linux GOARCH=arm64 go build -o ./bin/spsrv-linux-arm64/spsrv -ldflags=$(ldflags) $(pkg_root)
|
||||
|
||||
clean: ## Delete any compiled artifacts
|
||||
rm -rf ./bin
|
||||
|
||||
##@ Release
|
||||
package: build-all ## Build everything and package up arch-specific tarballs
|
||||
tar czvf ./bin/spsrv-darwin-amd64.tar.gz ./bin/spsrv-darwin-amd64
|
||||
tar czvf ./bin/spsrv-darwin-arm64.tar.gz ./bin/spsrv-darwin-arm64
|
||||
tar czvf ./bin/spsrv-linux-amd64.tar.gz ./bin/spsrv-linux-amd64
|
||||
tar czvf ./bin/spsrv-linux-arm64.tar.gz ./bin/spsrv-linux-arm64
|
||||
|
||||
release: package ## Attach packages to sr.ht ref for current tag
|
||||
./_scripts/release.sh
|
||||
|
||||
##@ Test
|
||||
test: ## Run tests
|
||||
go test $(shell go list ./...) -coverprofile=coverage.out
|
||||
# go tool cover -func=coverage.out
|
|
@ -0,0 +1,212 @@
|
|||
# spsrv
|
||||
|
||||
A static spartan server with many features:
|
||||
|
||||
* folder redirects
|
||||
* /~user directories
|
||||
* directory listing
|
||||
* CONF or TOML config file
|
||||
* CGI
|
||||
|
||||
Known servers running spsrv
|
||||
|
||||
=> spartan://hedy.tilde.cafe:3333
|
||||
=> spartan://tilde.team
|
||||
=> spartan://tilde.cafe
|
||||
=> spartan://earthlight.xyz:3000
|
||||
=> spartan://jdcard.com:3300
|
||||
|
||||
Questions / Support
|
||||
|
||||
=> irc://irc.tilde.chat:6697/#spartan #spartan on Tilde.Chat (please ping hedy)
|
||||
=> mailto:~hedy/inbox@lists.sr.ht Public inbox on lists.sr.ht
|
||||
|
||||
|
||||
Table of Contents
|
||||
|
||||
=> #install install
|
||||
=> #configuration configuation
|
||||
=> #cli CLI
|
||||
=> #cgi CGI
|
||||
=> #todo todo
|
||||
|
||||
|
||||
## install
|
||||
|
||||
you have three options:
|
||||
|
||||
### Option 1: Prebuilt binary
|
||||
|
||||
prebuilt binaries for darwin and linux architectures arm/amd-64 are provided since v0.5.4. Head over to the tags page on git.sr.ht, click on a desired tag and download the binary for your architecture.
|
||||
|
||||
=> https://git.sr.ht/~hedy/spsrv/refs
|
||||
|
||||
|
||||
### Option 2: with go
|
||||
|
||||
first, you need to have go installed and have a folder ~/go with $GOPATH pointing to it.
|
||||
|
||||
```
|
||||
go install git.sr.ht/~hedy/spsrv@latest
|
||||
```
|
||||
|
||||
there will be a binary at ~/go/bin/ with the source code at ~/go/src/
|
||||
|
||||
feel free to move the binary somewhere else like /usr/sbin/
|
||||
|
||||
note that it's recommended to pin any latest version `@v0.0.0` rather than the latest commit since it may not be stable.
|
||||
|
||||
|
||||
### Option 3: just build it yourself
|
||||
|
||||
run git clone https://git.sr.ht/~hedy/spsrv from any directory and cd spsrv
|
||||
|
||||
make sure you have go installed and working.
|
||||
|
||||
```
|
||||
git checkout v0.0.0 # recommended to pin a specific tag
|
||||
make build
|
||||
```
|
||||
|
||||
when it finishes, the binary will be in ./bin.
|
||||
|
||||
if you don't have make, you can just `go build` (just that version and build information will not be available with `spsrv --version`).
|
||||
|
||||
### otherwise...
|
||||
|
||||
if you do not wish to install go or clone the repo, and your architecture is not supported in the prebuilt binaries, drop an email to my public inbox (or contact me privately) so I could perhaps compile a binary for your architecture.
|
||||
|
||||
=> mailto:~hedy/inbox@lists.sr.ht public inbox
|
||||
|
||||
|
||||
## configuration
|
||||
|
||||
The default config file location is /etc/spsrv.conf you can specify your own path by running spsrv like
|
||||
|
||||
```
|
||||
spsrv -c /path/to/file.conf
|
||||
```
|
||||
|
||||
You don't need a config file to have spsrv running, it will just use the default values.
|
||||
|
||||
config options:
|
||||
|
||||
Note that the options are case insensitive.
|
||||
|
||||
Here are the config options and their default values
|
||||
|
||||
### general
|
||||
|
||||
port=300: port to listen to
|
||||
|
||||
hostname="localhost": if this is set, any request that for hostnames other than this value would be rejected
|
||||
|
||||
rootdir="/var/spartan": folder for fetching files
|
||||
|
||||
### directory listing
|
||||
|
||||
dirlistEnable=true: enable directory listing for folders that does not have index.gmi
|
||||
|
||||
dirlistReverse=false: reverse the order of which files are listed
|
||||
|
||||
dirlistSort="name": how files are sorted, only "name", "size", and "time" are accepted. Defaults to "name" if an unknown option is encountered
|
||||
|
||||
dirlistTitles=true: if true, directory listing will use first top level header in *.gmi files instead of the filename
|
||||
|
||||
### ~user/ directories
|
||||
|
||||
userdirEnable=true: enable serving /~user/* requests
|
||||
|
||||
userdir="public_spartan": root directory for users. This should not have trailing slashes, and it is relative to /home/user/
|
||||
|
||||
userSubdomains=false: User vhosts. Whether to allow user.host.name/foo.txt being the same as host.name/~user/foo.txt (When hostname="host.name"). NOTE: This only works when hostname option is set.
|
||||
|
||||
### CGI
|
||||
|
||||
CGIPaths=["cgi/"]: list of paths where world-executable files will be run as CGI processes. These paths would be checked if it prefix the requested path. For the default value, a request of /cgi/hi.sh (requesting to ./public/cgi/hi.sh, for example) will run hi.sh script if it's world executable.
|
||||
|
||||
usercgiEnable=false: enable running user's CGI scripts too. This is dangerous as spsrv does not (yet) change the Uid of the CGI process, hence the process would be ran by the same user that is running the server, which could mean write access to configuration files, etc. Note that this option will be assumed false if userdirEnable is set to false. Which means if user directories are not enabled, there will be no per-user CGI.
|
||||
|
||||
Check out some example configuraton in the examples/ directory.
|
||||
|
||||
=> https://tildegit.org/hedy/spsrv/src/branch/main/examples/ examples/
|
||||
|
||||
## CLI
|
||||
|
||||
You can override values in config file if you supply them from the command line:
|
||||
|
||||
```
|
||||
Usage: spsrv [ [ -c <path> -h <hostname> -p <port> -d <path> ] | --help | --version ]
|
||||
|
||||
-c, --config string Path to config file
|
||||
-d, --dir string Root content directory
|
||||
-h, --hostname string Hostname
|
||||
-p, --port int Port to listen to
|
||||
```
|
||||
|
||||
Note that you cannot set the hostname or the dir path to , because spsrv uses that to check whether you provided an option. You can't set port to 0 either, sorry, this limitation comes with the advantage of being able to override config values from the command line.
|
||||
|
||||
There are no arguments wanted when running spsrv, only options as listed above :)
|
||||
|
||||
## CGI
|
||||
|
||||
The following environment values are set for CGI scripts:
|
||||
|
||||
```
|
||||
GATEWAY_INTERFACE # CGI/1.1
|
||||
REMOTE_ADDR # Remote address
|
||||
SCRIPT_PATH # (Relative) path of the CGI script
|
||||
SERVER_SOFTWARE # SPSRV
|
||||
SERVER_PROTOCOL # SPARTAN
|
||||
REQUEST_METHOD # Set to nothing
|
||||
SERVER_PORT # Port
|
||||
SERVER_NAME # Hostname
|
||||
DATA_LENGTH # Input data length
|
||||
```
|
||||
|
||||
The data block, if any, will be piped as stdin to the CGI process.
|
||||
|
||||
Keep in mind that CGI scripts (as of now) are run by the same user as the server process, hence it is generally dangerous for allowing users to have their own CGI scripts. See configuration section for more details.
|
||||
|
||||
Check out some example CGI scripts in the examples/ directory.
|
||||
|
||||
=> https://tildegit.org/hedy/spsrv/src/branch/main/examples/ examples/
|
||||
|
||||
Example systemd service configurations are also listed there. Feel free to contribute for other OSes :)
|
||||
|
||||
|
||||
## Help / Issues / Feedback
|
||||
|
||||
Please either use the #spartan channel on tilde.chat IRC or my public inbox.
|
||||
|
||||
Both are listed at the top of this document.
|
||||
|
||||
|
||||
## todo
|
||||
|
||||
```todo list
|
||||
* [x] /folder to /folder/ redirects
|
||||
* [x] directory listing
|
||||
* [ ] logging to files
|
||||
* [x] ~user directories
|
||||
* [x] refactor working dir part
|
||||
* [x] config
|
||||
* [ ] status meta
|
||||
* [x] user homedir
|
||||
* [x] hostname, port
|
||||
* [x] public dir
|
||||
* [x] dirlist title
|
||||
* [x] user vhost
|
||||
* [ ] userdir slug
|
||||
* [ ] redirects
|
||||
* [x] CGI
|
||||
* [x] pipe data block
|
||||
* [ ] user cgi config and change uid to user
|
||||
* [ ] regex in cgi paths
|
||||
* [ ] SCGI
|
||||
* [ ] Multiple servers with each of their own confs
|
||||
|
||||
README:
|
||||
* [x] Add example confs (added in examples/ directory)
|
||||
* [x] Add example .service files (added in examples/ directory)
|
||||
```
|
208
README.md
208
README.md
|
@ -6,11 +6,202 @@ A static spartan server with many features:
|
|||
* /~user directories
|
||||
* directory listing
|
||||
* CONF or TOML config file
|
||||
* directory listing options
|
||||
* user directory feature and userdir path
|
||||
* CGI
|
||||
|
||||
Known servers running spsrv:
|
||||
* [hedy.tilde.cafe:3333](https://portal.mozz.us/spartan/hedy.tilde.cafe:3333)
|
||||
* [tilde.team](https://portal.mozz.us/spartan/tilde.team)
|
||||
* [tilde.cafe](https://portal.mozz.us/spartan/tilde.cafe)
|
||||
* [earthlight.xyz:3000](https://portal.mozz.us/spartan/earthlight.xyz:3000)
|
||||
* [jdcard.com:3300](https://portal.mozz.us/spartan/jdcard.com:3300/)
|
||||
|
||||
**Questions / Support**
|
||||
|
||||
* [#spartan on Tilde.Chat IRC](https://tilde.chat/kiwi/#spartan) (please ping
|
||||
hedy)
|
||||
* [Public inbox](mailto:~hedy/inbox@lists.sr.ht) (general mailing list on
|
||||
lists.sr.ht)
|
||||
* Patches: { [~hedy/inbox at lists.sr.ht](https://lists.sr.ht/~hedy/inbox) }
|
||||
|
||||
---
|
||||
|
||||
**Table of contents**
|
||||
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
|
||||
* [install](#install)
|
||||
* [Option 1: prebuilt binaries](#option-1-prebuilt-binaries)
|
||||
* [Option 2: with `go install`](#option-2-with-go-install)
|
||||
* [Option 3: just build it yourself](#option-3-just-build-it-yourself)
|
||||
* [otherwise...](#otherwise)
|
||||
* [configuration](#configuration)
|
||||
* [config options](#config-options)
|
||||
* [CLI](#cli)
|
||||
* [CGI](#cgi)
|
||||
* [Help / Issues / Feedback](#help--issues--feedback)
|
||||
* [todo](#todo)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
## install
|
||||
|
||||
you have three options:
|
||||
|
||||
### Option 1: prebuilt binaries
|
||||
|
||||
prebuilt binaries for darwin and linux architectures arm/amd-64 are provided
|
||||
since v0.5.4. Head over to the [tags page on
|
||||
git.sr.ht](https://git.sr.ht/~hedy/spsrv/refs), click on a desired tag and
|
||||
download the binary for your architecture.
|
||||
|
||||
|
||||
### Option 2: with `go install`
|
||||
|
||||
first, you need to have go installed and have a folder `~/go` with `$GOPATH`
|
||||
pointing to it.
|
||||
|
||||
```
|
||||
go install git.sr.ht/~hedy/spsrv@latest
|
||||
```
|
||||
|
||||
there will be a binary at `~/go/bin/` with the source code at `~/go/src/`
|
||||
|
||||
feel free to move the binary somewhere else like `/usr/sbin/`
|
||||
|
||||
note that it's recommended to pin any latest version `@v0.0.0` rather than the
|
||||
latest commit since it may not be stable.
|
||||
|
||||
|
||||
### Option 3: just build it yourself
|
||||
|
||||
run `git clone https://git.sr.ht/~hedy/spsrv` from any directory and `cd spsrv`
|
||||
|
||||
make sure you have go installed and working.
|
||||
|
||||
```
|
||||
git checkout v0.0.0 # recommended to pin a specific tag
|
||||
make build
|
||||
```
|
||||
|
||||
when it finishes, the binary will be in `./bin`.
|
||||
|
||||
if you don't have make, you can just `go build` (just that version and build
|
||||
information will not be available with `spsrv --version`).
|
||||
|
||||
### otherwise...
|
||||
|
||||
if you do not wish to install go or clone the repo, and your architecture is not
|
||||
supported in the prebuilt binaries, drop an email to my [public
|
||||
inbox](mailto:~hedy/inbox@lists.sr.ht) (or contact me privately) so I could
|
||||
perhaps compile a binary for your architecture.
|
||||
|
||||
|
||||
## configuration
|
||||
|
||||
The default config file location is `/etc/spsrv.conf` you can specify your own
|
||||
path by running spsrv like
|
||||
|
||||
```
|
||||
spsrv -c /path/to/file.conf
|
||||
```
|
||||
|
||||
You don't need a config file to have spsrv running, it will just use the
|
||||
default values.
|
||||
|
||||
|
||||
### config options
|
||||
|
||||
Note that the options are case insensitive.
|
||||
|
||||
Here are the config options and their default values
|
||||
|
||||
**general**
|
||||
|
||||
* `port=300`: port to listen to
|
||||
* `hostname="localhost"`: if this is set, any request that for hostnames other than this value would be rejected
|
||||
* `rootdir="/var/spartan"`: folder for fetching files
|
||||
|
||||
**directory listing**
|
||||
|
||||
* `dirlistEnable=true`: enable directory listing for folders that does not have `index.gmi`
|
||||
* `dirlistReverse=false`: reverse the order of which files are listed
|
||||
* `dirlistSort="name"`: how files are sorted, only "name", "size", and "time" are accepted. Defaults to "name" if an unknown option is encountered
|
||||
* `dirlistTitles=true`: if true, directory listing will use first top level header in `*.gmi` files instead of the filename
|
||||
|
||||
**~user/ directories**
|
||||
|
||||
* `userdirEnable=true`: enable serving `/~user/*` requests
|
||||
* `userdir="public_spartan"`: root directory for users. This should not have trailing slashes, and it is relative to `/home/user/`
|
||||
* `userSubdomains=false`: User vhosts. Whether to allow `user.host.name/foo.txt` being the same as `host.name/~user/foo.txt` (When `hostname="host.name"`). **NOTE**: This only works when `hostname` option is set.
|
||||
|
||||
**CGI**
|
||||
|
||||
* `CGIPaths=["cgi/"]`: list of paths where world-executable files will be run as CGI processes. These paths would be checked if it prefix the requested path. For the default value, a request of `/cgi/hi.sh` (requesting to `./public/cgi/hi.sh`, for example) will run `hi.sh` script if it's world executable.
|
||||
* `usercgiEnable=false`: enable running user's CGI scripts too. This is dangerous as spsrv does not (yet) change the Uid of the CGI process, hence the process would be ran by the same user that is running the server, which could mean write access to configuration files, etc. Note that this option will be assumed `false` if `userdirEnable` is set to `false`. Which means if user directories are not enabled, there will be no per-user CGI.
|
||||
|
||||
Check out some example configuraton in the [examples/](examples/) directory.
|
||||
|
||||
## CLI
|
||||
|
||||
You can override values in config file if you supply them from the command line:
|
||||
|
||||
```
|
||||
Usage: spsrv [ [ -c <path> -h <hostname> -p <port> -d <path> ] | --help | --version ]
|
||||
|
||||
-c, --config string Path to config file
|
||||
-d, --dir string Root content directory
|
||||
-h, --hostname string Hostname
|
||||
-p, --port int Port to listen to
|
||||
```
|
||||
|
||||
Note that you *cannot* set the hostname or the dir path to `,` because spsrv
|
||||
uses that to check whether you provided an option. You can't set port to `0`
|
||||
either, sorry, this limitation comes with the advantage of being able to
|
||||
override config values from the command line.
|
||||
|
||||
There are no arguments wanted when running spsrv, only options as listed above :)
|
||||
|
||||
## CGI
|
||||
|
||||
The following environment values are set for CGI scripts:
|
||||
|
||||
```
|
||||
GATEWAY_INTERFACE # CGI/1.1
|
||||
REMOTE_ADDR # Remote address
|
||||
SCRIPT_PATH # (Relative) path of the CGI script
|
||||
SERVER_SOFTWARE # SPSRV
|
||||
SERVER_PROTOCOL # SPARTAN
|
||||
REQUEST_METHOD # Set to nothing
|
||||
SERVER_PORT # Port
|
||||
SERVER_NAME # Hostname
|
||||
DATA_LENGTH # Input data length
|
||||
```
|
||||
|
||||
The data block, if any, will be piped as stdin to the CGI process.
|
||||
|
||||
Keep in mind that CGI scripts (as of now) are run by the same user as the
|
||||
server process, hence it is generally dangerous for allowing users to have
|
||||
their own CGI scripts. See configuration section for more details.
|
||||
|
||||
Check out some example CGI scripts in the [examples/](examples/) directory.
|
||||
|
||||
Example systemd service configurations are also listed there. Feel free to
|
||||
contribute for other OSes :)
|
||||
|
||||
|
||||
## Help / Issues / Feedback
|
||||
|
||||
Please either use the [#spartan channel on tilde.chat
|
||||
IRC](https://tilde.chat/kiwi/#spartan) or my [public
|
||||
inbox](https://lists.sr.ht/~hedy/inbox).
|
||||
|
||||
Both are listed at the top of this document.
|
||||
|
||||
**Patches** -> [public inbox](https://lists.sr.ht/~hedy/inbox)
|
||||
|
||||
|
||||
## todo
|
||||
|
||||
- [x] /folder to /folder/ redirects
|
||||
- [x] directory listing
|
||||
- [ ] logging to files
|
||||
|
@ -21,9 +212,18 @@ A static spartan server with many features:
|
|||
- [x] user homedir
|
||||
- [x] hostname, port
|
||||
- [x] public dir
|
||||
- [ ] dirlist title
|
||||
- [x] dirlist title
|
||||
- [x] user vhost
|
||||
- [ ] userdir slug
|
||||
- [ ] redirects
|
||||
- [x] CGI
|
||||
- [ ] pipe data block
|
||||
- [x] pipe data block
|
||||
- [ ] user cgi config and change uid to user
|
||||
- [ ] regex in cgi paths
|
||||
- [ ] SCGI
|
||||
|
||||
- [ ] Multiple servers with each of their own confs
|
||||
|
||||
README:
|
||||
- [x] Add example confs (added in [examples/](examples) directory)
|
||||
- [x] Add example .service files (added in [examples/](examples) directory)
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Version of the server used should be included when reporting vulnerabilities,
|
||||
however please try to use the latest versions if you are the server admin.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 0.5.x | :white_check_mark: |
|
||||
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Send an email to **hedy at tilde dot cafe**.
|
||||
|
||||
Do NOT use my public inbox on lists.sr.ht or tell me publicly since other public
|
||||
servers running on spsrv (not only yours) could be prone to the same issue.
|
||||
|
||||
=> [mailto link](mailto:hedy@tilde.cafe)
|
||||
|
||||
You can expect emails to be read within a week, and I will reply promply to indicate when
|
||||
I'll be able to work on a fix, if possible.
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env sh
|
||||
set -e
|
||||
|
||||
git_tag=`git describe --exact-match 2> /dev/null || echo ""`
|
||||
if [ "$git_tag" != "" ]; then
|
||||
echo "Releasing $git_tag!"
|
||||
mv ./bin/spsrv-darwin-amd64.tar.gz ./bin/spsrv-darwin-amd64-$git_tag.tar.gz
|
||||
mv ./bin/spsrv-darwin-arm64.tar.gz ./bin/spsrv-darwin-arm64-$git_tag.tar.gz
|
||||
mv ./bin/spsrv-linux-amd64.tar.gz ./bin/spsrv-linux-amd64-$git_tag.tar.gz
|
||||
mv ./bin/spsrv-linux-arm64.tar.gz ./bin/spsrv-linux-arm64-$git_tag.tar.gz
|
||||
|
||||
curl -H"Authorization: token $SRHT_TOKEN" https://git.sr.ht/api/~hedy/repos/spsrv/artifacts/$git_tag -F "file=@./bin/spsrv-darwin-amd64-$git_tag.tar.gz"
|
||||
curl -H"Authorization: token $SRHT_TOKEN" https://git.sr.ht/api/~hedy/repos/spsrv/artifacts/$git_tag -F "file=@./bin/spsrv-darwin-arm64-$git_tag.tar.gz"
|
||||
curl -H"Authorization: token $SRHT_TOKEN" https://git.sr.ht/api/~hedy/repos/spsrv/artifacts/$git_tag -F "file=@./bin/spsrv-linux-amd64-$git_tag.tar.gz"
|
||||
curl -H"Authorization: token $SRHT_TOKEN" https://git.sr.ht/api/~hedy/repos/spsrv/artifacts/$git_tag -F "file=@./bin/spsrv-linux-arm64-$git_tag.tar.gz"
|
||||
echo ""
|
||||
echo "DONE!"
|
||||
else
|
||||
echo "Non-tagged commit, not releasing!"
|
||||
fi
|
46
config.go
46
config.go
|
@ -10,31 +10,33 @@ import (
|
|||
)
|
||||
|
||||
type Config struct {
|
||||
Port int
|
||||
Hostname string
|
||||
RootDir string
|
||||
UserDirEnable bool
|
||||
UserDir string
|
||||
DirlistEnable bool
|
||||
DirlistReverse bool
|
||||
DirlistSort string
|
||||
DirlistTitles bool
|
||||
RestrictHostname string
|
||||
CGIPaths []string
|
||||
Port int
|
||||
Hostname string
|
||||
RootDir string
|
||||
UserDirEnable bool
|
||||
UserDir string
|
||||
UserSubdomains bool
|
||||
DirlistEnable bool
|
||||
DirlistReverse bool
|
||||
DirlistSort string
|
||||
DirlistTitles bool
|
||||
CGIPaths []string
|
||||
UserCGIEnable bool
|
||||
}
|
||||
|
||||
var defaultConf = &Config{
|
||||
Port: 300,
|
||||
Hostname: "localhost",
|
||||
RootDir: "/var/spartan/",
|
||||
DirlistEnable: true,
|
||||
DirlistReverse: false,
|
||||
DirlistSort: "name",
|
||||
DirlistTitles: true,
|
||||
UserDirEnable: true,
|
||||
UserDir: "public_spartan",
|
||||
RestrictHostname: "",
|
||||
CGIPaths: []string{"cgi/"},
|
||||
Port: 300,
|
||||
Hostname: "localhost",
|
||||
RootDir: "/var/spartan/",
|
||||
DirlistEnable: true,
|
||||
DirlistReverse: false,
|
||||
DirlistSort: "name",
|
||||
DirlistTitles: true,
|
||||
UserDirEnable: true,
|
||||
UserDir: "public_spartan",
|
||||
UserSubdomains: false,
|
||||
CGIPaths: []string{"cgi/"},
|
||||
UserCGIEnable: false, // Turned off by default because scripts are run by server user as of now
|
||||
}
|
||||
|
||||
func LoadConfig(path string) (*Config, error) {
|
||||
|
|
15
dynamic.go
15
dynamic.go
|
@ -26,10 +26,12 @@ func handleCGI(conf *Config, req *Request, cgiPath string) (ok bool) {
|
|||
|
||||
info, err := os.Stat(scriptPath)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
if !(info.Mode().Perm()&0555 == 0555) {
|
||||
log.Println("File not executable")
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
|
@ -76,15 +78,19 @@ func handleCGI(conf *Config, req *Request, cgiPath string) (ok bool) {
|
|||
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
log.Println("Terminating CGI process " + path + " due to exceeding 10 second runtime limit.")
|
||||
conn.Write([]byte("42 CGI process timed out!\r\n"))
|
||||
conn.Write([]byte("5 CGI process timed out!\r\n"))
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Println("Error running CGI program " + path + ": " + err.Error())
|
||||
if strings.Contains(err.Error(), "permission denied") {
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
if err, ok := err.(*exec.ExitError); ok {
|
||||
log.Println("↳ stderr output: " + string(err.Stderr))
|
||||
}
|
||||
conn.Write([]byte("42 CGI error\r\n"))
|
||||
conn.Write([]byte("5 CGI error\r\n"))
|
||||
return
|
||||
}
|
||||
// Extract response header
|
||||
|
@ -92,7 +98,7 @@ func handleCGI(conf *Config, req *Request, cgiPath string) (ok bool) {
|
|||
_, err2 := strconv.Atoi(strings.Fields(string(header))[0])
|
||||
if err != nil || err2 != nil {
|
||||
log.Println("Unable to parse first line of output from CGI process " + path + " as valid Gemini response header. Line was: " + string(header))
|
||||
conn.Write([]byte("42 CGI error\r\n"))
|
||||
conn.Write([]byte("5 CGI error\r\n"))
|
||||
return
|
||||
}
|
||||
log.Println("Returning CGI output")
|
||||
|
@ -110,13 +116,14 @@ func prepareCGIVariables(conf *Config, req *Request, script_path string) map[str
|
|||
|
||||
func prepareGatewayVariables(conf *Config, req *Request) map[string]string {
|
||||
vars := make(map[string]string)
|
||||
// vars["QUERY_STRING"] = URL.RawQuery
|
||||
vars["REQUEST_METHOD"] = ""
|
||||
vars["SERVER_NAME"] = conf.Hostname
|
||||
vars["SERVER_PORT"] = strconv.Itoa(conf.Port)
|
||||
vars["SERVER_PROTOCOL"] = "SPARTAN"
|
||||
vars["SERVER_SOFTWARE"] = "SPSRV"
|
||||
|
||||
vars["DATA_LENGTH"] = strconv.Itoa(req.dataLen)
|
||||
|
||||
host, _, _ := net.SplitHostPort((*req.netConn).RemoteAddr().String())
|
||||
vars["REMOTE_ADDR"] = host
|
||||
return vars
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Echos back whatever data you send it
|
||||
|
||||
printf "2 application/octet-stream\r\n"
|
||||
cat /dev/stdin
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
printf "2 text/gemini\r\n"
|
||||
whoami
|
||||
printenv
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# User can enter their name and this will greet them
|
||||
# Example inpu link:
|
||||
#
|
||||
# =: greet.sh Enter your name
|
||||
|
||||
printf "2 text/plain\r\n"
|
||||
name=$(cat /dev/stdin)
|
||||
echo "Hello, ${name:-World}!"
|
|
@ -0,0 +1,13 @@
|
|||
# Example config for a local testing server
|
||||
|
||||
hostname="localhost"
|
||||
port=3000
|
||||
|
||||
rootDir = "./public"
|
||||
|
||||
# attempt CGI for all requests
|
||||
cgiPaths=[""]
|
||||
|
||||
dirlistEnable=true
|
||||
dirlistSort="time"
|
||||
dirlistTitles=true
|
|
@ -0,0 +1,19 @@
|
|||
# Example config for a multi-user unix server
|
||||
|
||||
# accept any hostname
|
||||
hostname=""
|
||||
port=300
|
||||
rootdir="/var/spartan"
|
||||
|
||||
# allow CGI for all files - so you can have your root index.gmi do a user
|
||||
# listing
|
||||
cgipaths=[""]
|
||||
|
||||
userdirEnable=true
|
||||
# each user would have their content be at /home/user/public_spartan,
|
||||
# accessible via spartan://host.name/~user/
|
||||
userdir="public_spartan"
|
||||
|
||||
# enable per-user CGI (you might wanna be running the spartan server under
|
||||
# another user, such as spartan:nogroup)
|
||||
usercgiEnable=true
|
|
@ -0,0 +1,18 @@
|
|||
# Example config for allowing per-user vhosts on your pubnix, i.e.: allownig
|
||||
# spartan://user.example.org
|
||||
|
||||
# you must set a non-empty hostname for user-vhost to work
|
||||
hostname="example.org"
|
||||
port=300
|
||||
|
||||
userdirEnable=true
|
||||
|
||||
# each user would have their content be at /home/user/public_spartan,
|
||||
# accessible via both spartan://example.org/~user/ and
|
||||
# spartan://user.example.org/
|
||||
userdir="public_spartan"
|
||||
userSubdomains=true
|
||||
|
||||
# enable per-user CGI (you might wanna be running the spartan server under
|
||||
# another user, such as spartan:nogroup)
|
||||
usercgiEnable=true
|
|
@ -0,0 +1,15 @@
|
|||
[Unit]
|
||||
Description=spsrv
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
User=spartan
|
||||
Group=spartan
|
||||
ExecStart=/usr/local/bin/spsrv -c /etc/spsrv.conf
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
2
go.mod
2
go.mod
|
@ -3,6 +3,6 @@ module spsrv
|
|||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/BurntSushi/toml v1.3.2
|
||||
github.com/spf13/pflag v1.0.5
|
||||
)
|
||||
|
|
4
go.sum
4
go.sum
|
@ -1,4 +1,4 @@
|
|||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
|
|
180
spsrv.go
180
spsrv.go
|
@ -18,14 +18,14 @@ import (
|
|||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var doneScanningRequest = false
|
||||
|
||||
type Request struct {
|
||||
conn io.ReadWriteCloser
|
||||
netConn *net.Conn
|
||||
vhost string
|
||||
user string
|
||||
path string // Requested path
|
||||
filePath string // Actual file path that does not include the content dir name
|
||||
dataLen int
|
||||
data string
|
||||
}
|
||||
|
||||
|
@ -36,15 +36,56 @@ const (
|
|||
statusServerError = 5
|
||||
)
|
||||
|
||||
// The following default values are set so that a user would never set any value from the CLI to
|
||||
// the following. so we can distinguish between user supplied value and the default value.
|
||||
// The default char is not "" because you can set hostname to "" and it will allow requests to
|
||||
// any hostname.
|
||||
// This is not using defaultConf values either because if the config has non-default values, and
|
||||
// default value is supplied from the CLI, we want to keep taht default value, which is likely what
|
||||
// user wants.
|
||||
var cliDefaultChar = ","
|
||||
var cliDefaultInt = 0
|
||||
|
||||
var (
|
||||
hostname = flag.StringP("hostname", "h", defaultConf.Hostname, "Hostname")
|
||||
port = flag.IntP("port", "p", defaultConf.Port, "Port to listen to")
|
||||
rootDir = flag.StringP("dir", "d", defaultConf.RootDir, "Root content directory")
|
||||
hostname = flag.StringP("hostname", "h", cliDefaultChar, "Hostname")
|
||||
port = flag.IntP("port", "p", cliDefaultInt, "Port to listen to")
|
||||
rootDir = flag.StringP("dir", "d", cliDefaultChar, "Root content directory")
|
||||
confPath = flag.StringP("config", "c", "/etc/spsrv.conf", "Path to config file")
|
||||
helpFlag = flag.BoolP("help", "?", false, "Get CLI help")
|
||||
versionFlag = flag.BoolP("version", "v", false, "View version and exit")
|
||||
)
|
||||
|
||||
var (
|
||||
appVersion = "unknown version"
|
||||
buildTime = "date unknown"
|
||||
appCommit = "unknown"
|
||||
)
|
||||
|
||||
|
||||
func main() {
|
||||
// Custom usage function because we don't want the "pflag: help requested" message, and
|
||||
// we don't want to show the default values.
|
||||
flag.Usage = func() {
|
||||
fmt.Println(`Usage: spsrv [ [ -c <path> -h <hostname> -p <port> -d <path> ] | --help | --version ]
|
||||
|
||||
-c, --config string Path to config file
|
||||
-d, --dir string Root content directory
|
||||
-h, --hostname string Hostname
|
||||
-p, --port int Port to listen to`)
|
||||
}
|
||||
flag.Parse()
|
||||
|
||||
if *helpFlag {
|
||||
flag.Usage()
|
||||
return
|
||||
}
|
||||
|
||||
if *versionFlag {
|
||||
fmt.Printf("spsrv %s, commit %s, built %s", appVersion, appCommit, buildTime)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
conf, err := LoadConfig(*confPath)
|
||||
if err != nil {
|
||||
fmt.Println("Error loading config")
|
||||
|
@ -53,17 +94,16 @@ func main() {
|
|||
}
|
||||
|
||||
// This allows users overriding values in config via the CLI
|
||||
if *hostname != defaultConf.Hostname {
|
||||
if *hostname != cliDefaultChar {
|
||||
conf.Hostname = *hostname
|
||||
}
|
||||
if *port != defaultConf.Port {
|
||||
if *port != cliDefaultInt {
|
||||
conf.Port = *port
|
||||
}
|
||||
if *rootDir != defaultConf.RootDir {
|
||||
if *rootDir != cliDefaultChar {
|
||||
conf.RootDir = *rootDir
|
||||
}
|
||||
|
||||
// TODO: do something with conf.Hostname (b(like restricting to ipv4/6 etc)
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", conf.Port))
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to listen: %s", err)
|
||||
|
@ -96,9 +136,24 @@ func handleConnection(netConn net.Conn, conf *Config) {
|
|||
log.Println("Closed connection")
|
||||
}()
|
||||
|
||||
doneScanningRequest := false
|
||||
// Check the size of the request buffer.
|
||||
s := bufio.NewScanner(conn)
|
||||
s.Split(ScanRequest)
|
||||
s.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
|
||||
if atEOF && len(data) == 0 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
if doneScanningRequest {
|
||||
// Return a byte
|
||||
return 1, data[:1], nil
|
||||
}
|
||||
// Read request
|
||||
if i := bytes.IndexByte(data, '\n'); i >= 0 {
|
||||
return i + 1, bytes.TrimRight(data[0:i], "\r"), nil
|
||||
}
|
||||
return 0, nil, nil
|
||||
})
|
||||
|
||||
// Sanity check incoming request URL content.
|
||||
if ok := s.Scan(); !ok {
|
||||
|
@ -116,11 +171,17 @@ func handleConnection(netConn net.Conn, conf *Config) {
|
|||
sendResponseHeader(conn, statusClientError, "Bad request")
|
||||
return
|
||||
}
|
||||
if conf.RestrictHostname != "" {
|
||||
if conf.RestrictHostname != host {
|
||||
log.Println("Request host does not match conf.RestrictHostname, returning client error.")
|
||||
sendResponseHeader(conn, statusClientError, "No proxying to other hosts!")
|
||||
return
|
||||
userSubdomainReq := false
|
||||
if conf.Hostname != "" {
|
||||
if conf.Hostname != host {
|
||||
if conf.UserDirEnable && conf.UserSubdomains && strings.HasSuffix(host, conf.Hostname) {
|
||||
userSubdomainReq = true
|
||||
}
|
||||
if !userSubdomainReq {
|
||||
log.Println("Request host does not match config value Hostname, returning client error.")
|
||||
sendResponseHeader(conn, statusClientError, "No proxying to other hosts!")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if strings.Contains(reqPath, "..") {
|
||||
|
@ -146,7 +207,13 @@ func handleConnection(netConn net.Conn, conf *Config) {
|
|||
data += newData
|
||||
}
|
||||
}
|
||||
req := &Request{path: reqPath, netConn: &netConn, conn: conn, data: data}
|
||||
|
||||
var vhost string
|
||||
if userSubdomainReq {
|
||||
// TODO: Handle extra dots like a.b.host.name?
|
||||
vhost = strings.TrimSuffix(host, "."+conf.Hostname)
|
||||
}
|
||||
req := &Request{vhost: vhost, path: reqPath, netConn: &netConn, conn: conn, data: data, dataLen: dataLen}
|
||||
|
||||
// Time to fetch the files!
|
||||
path := resolvePath(reqPath, conf, req)
|
||||
|
@ -154,49 +221,67 @@ func handleConnection(netConn net.Conn, conf *Config) {
|
|||
// Check for CGI
|
||||
for _, cgiPath := range conf.CGIPaths {
|
||||
if strings.HasPrefix(req.filePath, cgiPath) {
|
||||
if req.user != "" && (!conf.UserCGIEnable || !conf.UserDirEnable) {
|
||||
break
|
||||
}
|
||||
if req.user != "" && (req.filePath == "" || req.filePath == "/") {
|
||||
// TODO: Refactor - ATM `path` would contain the current CGI file wanted
|
||||
// But for hitting /~user/, req.filePath is NOT index.gmi
|
||||
req.filePath = "index.gmi"
|
||||
}
|
||||
log.Println("Attempting CGI:", req.filePath)
|
||||
|
||||
ok := handleCGI(conf, req, cgiPath)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
|
||||
break // CGI failed. just handle the request as if it's a static file.
|
||||
}
|
||||
}
|
||||
|
||||
// Reaching here means it is a static file
|
||||
if dataLen != 0 {
|
||||
log.Printf("Got data block of length %v, returning client error.", dataLen)
|
||||
sendResponseHeader(conn, statusClientError, "Unwanted input data block received")
|
||||
return
|
||||
log.Printf("Got data block of length %v for request where CGI not found.", dataLen)
|
||||
// Not erroring out here because if file not found, return not found rather
|
||||
// than 'Unexpected input'
|
||||
}
|
||||
|
||||
serveFile(conn, reqPath, path, conf)
|
||||
serveFile(conn, reqPath, path, conf, dataLen != 0)
|
||||
}
|
||||
|
||||
// resolvePath takes in teh request path and returns the cleaned filepath that needs to be fetched.
|
||||
// It also handles user directories paths /~user/ and /~user if user directories is enabled in the config.
|
||||
func resolvePath(reqPath string, conf *Config, req *Request) (path string) {
|
||||
// Handle tildes
|
||||
if conf.UserDirEnable && strings.HasPrefix(reqPath, "/~") {
|
||||
var user string
|
||||
// Handle user subdomains
|
||||
if req.vhost != "" {
|
||||
user = req.vhost
|
||||
path = reqPath
|
||||
} else if conf.UserDirEnable && strings.HasPrefix(reqPath, "/~") {
|
||||
// Handle tildes
|
||||
// Note that user.host.name/~user/ would treat it as a literal folder named /~user/
|
||||
// (hence using `else if`)
|
||||
bits := strings.Split(reqPath, "/")
|
||||
username := bits[1][1:]
|
||||
user = bits[1][1:]
|
||||
|
||||
// /~user to /~user/ is somehow able to be handled together with any other /folder to /foler/ redirects
|
||||
// /~user to /~user/ is somehow able to be handled together with any other /folder to /folder/ redirects
|
||||
// So I won't worry about that nor handle it specifically
|
||||
|
||||
req.filePath = strings.TrimPrefix(filepath.Clean(strings.TrimPrefix(reqPath, "/~"+username)), "/")
|
||||
req.filePath = strings.TrimPrefix(filepath.Clean(strings.TrimPrefix(reqPath, "/~"+user)), "/")
|
||||
path = req.filePath
|
||||
}
|
||||
|
||||
new_prefix := filepath.Join("/home/", username, conf.UserDir)
|
||||
req.user = username
|
||||
path = filepath.Clean(strings.Replace(reqPath, bits[1], new_prefix, 1))
|
||||
if user != "" {
|
||||
req.filePath = path
|
||||
path = filepath.Join("/home/", user, conf.UserDir, path)
|
||||
req.user = user
|
||||
|
||||
if strings.HasSuffix(reqPath, "/") {
|
||||
path = filepath.Join(path, "index.gmi")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
path = reqPath
|
||||
// TODO: [config] default index file for a directory is index.gmi
|
||||
if strings.HasSuffix(reqPath, "/") || reqPath == "" {
|
||||
|
@ -208,7 +293,7 @@ func resolvePath(reqPath string, conf *Config, req *Request) (path string) {
|
|||
}
|
||||
|
||||
// serveFile serves opens the requested path and returns the file content
|
||||
func serveFile(conn io.ReadWriteCloser, reqPath, path string, conf *Config) {
|
||||
func serveFile(conn io.ReadWriteCloser, reqPath, path string, conf *Config, hasData bool) {
|
||||
// If the content directory is not specified as an absolute path, make it absolute.
|
||||
// prefixDir := ""
|
||||
// var rootDir http.Dir
|
||||
|
@ -227,7 +312,11 @@ func serveFile(conn io.ReadWriteCloser, reqPath, path string, conf *Config) {
|
|||
// be opened without errors
|
||||
// Directory listing
|
||||
if conf.DirlistEnable && strings.HasSuffix(path, "index.gmi") {
|
||||
// fullPath := filepath.Join(fmt.Sprint(rootDir), path)
|
||||
if hasData {
|
||||
log.Println("Returning client error due to unexpected data block")
|
||||
sendResponseHeader(conn, statusClientError, "Unexpected input data block received")
|
||||
return
|
||||
}
|
||||
fullPath := path
|
||||
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
|
||||
// If and only if the path is index.gmi AND index.gmi does not exist
|
||||
|
@ -241,18 +330,27 @@ func serveFile(conn io.ReadWriteCloser, reqPath, path string, conf *Config) {
|
|||
sendResponseHeader(conn, statusServerError, "Error generating directory listing")
|
||||
return
|
||||
}
|
||||
path += ".gmi" // OOF, this is just to have the text/gemini meta later lol
|
||||
path = strings.TrimSuffix(path, "index.gmi")
|
||||
serveContent(conn, content, path)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Println(err)
|
||||
log.Println("Returning not found")
|
||||
sendResponseHeader(conn, statusClientError, "Not found")
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Only show this if we are certain that the request was for a static file.
|
||||
// Which does not include the 'Not found'.
|
||||
if hasData {
|
||||
log.Println("Returning client error due to unexpected data block")
|
||||
sendResponseHeader(conn, statusClientError, "Unexpected input data block received")
|
||||
return
|
||||
}
|
||||
|
||||
// Read da file
|
||||
content, err = ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
|
@ -275,7 +373,7 @@ func serveFile(conn io.ReadWriteCloser, reqPath, path string, conf *Config) {
|
|||
func serveContent(conn io.ReadWriteCloser, content []byte, path string) {
|
||||
// MIME
|
||||
meta := http.DetectContentType(content)
|
||||
if strings.HasSuffix(path, ".gmi") {
|
||||
if strings.HasSuffix(path, ".gmi") || strings.HasSuffix(path, "/") {
|
||||
meta = "text/gemini; lang=en; charset=utf-8" // TODO: configure custom meta string
|
||||
}
|
||||
|
||||
|
@ -318,19 +416,3 @@ func parseRequest(r string) (host, path string, contentLength int, err error) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ScanRequest returns a line if we haven't scanned request line yet, else, returns a byte
|
||||
func ScanRequest(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
if atEOF && len(data) == 0 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
if doneScanningRequest {
|
||||
// Return a byte
|
||||
return 1, data[:1], nil
|
||||
}
|
||||
// Read request
|
||||
if i := bytes.IndexByte(data, '\n'); i >= 0 {
|
||||
return i + 1, bytes.TrimRight(data[0:i], "\r"), nil
|
||||
}
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue