lint and clean up

This commit is contained in:
dre 2021-07-08 21:44:18 +08:00
parent 802bf1eb6d
commit a53418f77c
5 changed files with 62 additions and 35 deletions

View file

@ -1,7 +1,7 @@
# gmifs
Gemini File Server, short gmifs, is intended to be minimal and serve static files. It is used
to accompany a hugo blog served via httpd and make it available via the [gemini
to accompany a hugo blog served via httpd and makes it available via the [gemini
protocol](https://gemini.circumlunar.space/docs/specification.gmi). Why built yet another gemini
server? Because it's educational and that's the spirit of the protocol.

View file

@ -13,13 +13,16 @@ import (
"github.com/n0x1m/gmifs/gemini"
)
var errDirWithoutIndexFile = errors.New("path without index.gmi not allowed")
var (
ErrDirWithoutIndexFile = errors.New("path without index.gmi not allowed")
ErrUnsupportedFileType = errors.New("disabled/unsupported file type")
)
func Serve(root string, autoindex bool) func(w io.Writer, r *gemini.Request) {
return func(w io.Writer, r *gemini.Request) {
fullpath, err := fullPath(root, r.URL.Path)
if err != nil {
if err == errDirWithoutIndexFile && autoindex {
if errors.Is(err, ErrDirWithoutIndexFile) && autoindex {
body, mimeType, err := listDirectory(fullpath, r.URL.Path)
if err != nil {
gemini.WriteHeader(w, gemini.StatusNotFound, err.Error())
@ -30,9 +33,11 @@ func Serve(root string, autoindex bool) func(w io.Writer, r *gemini.Request) {
gemini.Write(w, body)
return
}
gemini.WriteHeader(w, gemini.StatusNotFound, err.Error())
return
}
body, mimeType, err := readFile(fullpath)
if err != nil {
gemini.WriteHeader(w, gemini.StatusNotFound, err.Error())
@ -55,7 +60,7 @@ func fullPath(root, requestPath string) (string, error) {
if pathInfo.IsDir() {
subDirIndex := path.Join(fullpath, gemini.IndexFile)
if _, err := os.Stat(subDirIndex); os.IsNotExist(err) {
return fullpath, errDirWithoutIndexFile
return fullpath, ErrDirWithoutIndexFile
}
fullpath = subDirIndex
@ -67,7 +72,7 @@ func fullPath(root, requestPath string) (string, error) {
func readFile(filepath string) ([]byte, string, error) {
mimeType := getMimeType(filepath)
if mimeType == "" {
return nil, "", errors.New("disabled/unsupported file type")
return nil, "", ErrUnsupportedFileType
}
file, err := os.Open(filepath)
@ -92,7 +97,7 @@ func getMimeType(fullpath string) string {
func listDirectory(fullpath, relpath string) ([]byte, string, error) {
files, err := ioutil.ReadDir(fullpath)
if err != nil {
return nil, "", err
return nil, "", fmt.Errorf("list directory: %w", err)
}
var out []byte

View file

@ -49,10 +49,15 @@ const (
)
var (
ErrServerClosed = errors.New("gemini: server closed")
ErrHeaderTooLong = errors.New("gemini: header too long")
ErrMissingFile = errors.New("gemini: no such file")
ErrEmptyRequest = errors.New("gemini: empty request")
ErrServerClosed = errors.New("gemini: server closed")
ErrHeaderTooLong = errors.New("gemini: header too long")
ErrMissingFile = errors.New("gemini: no such file")
ErrEmptyRequest = errors.New("gemini: empty request")
ErrEmptyRequestURL = errors.New("gemini: empty request URL")
ErrInvalidPath = errors.New("gemini: path error")
ErrInvalidHost = errors.New("gemini: empty host")
ErrInvalidUtf8 = errors.New("empty request URL")
ErrUnknownProtocol = fmt.Errorf("unknown protocol scheme")
)
type Request struct {
@ -107,6 +112,7 @@ func (s *Server) log(v string) {
if s.Logger == nil {
return
}
s.Logger.Println("gmifs: " + v)
}
@ -114,6 +120,7 @@ func (s *Server) logf(format string, v ...interface{}) {
if s.Logger == nil {
return
}
s.log(fmt.Sprintf(format, v...))
}
@ -130,9 +137,11 @@ func (s *Server) ListenAndServe() error {
hup := make(chan os.Signal, 1)
signal.Notify(hup, syscall.SIGHUP)
go func() {
for {
<-hup
s.log("reloading certificate")
if s.listener != nil {
err := s.loadTLS()
@ -150,6 +159,7 @@ func (s *Server) ListenAndServe() error {
// TLSConfig.
for {
s.closed = make(chan struct{})
var err error
s.listener, err = tls.Listen("tcp", s.Addr, s.TLSConfig)
if err != nil {
@ -164,6 +174,7 @@ func (s *Server) ListenAndServe() error {
conn, err := s.listener.Accept()
if err != nil {
s.logf("server accept error: %v", err)
break
}
queue <- conn
@ -173,12 +184,14 @@ func (s *Server) ListenAndServe() error {
break
}
}
// closed confirms the accept call stopped
close(s.closed)
if s.shutdown {
break
}
}
s.log("closing listener gracefully")
return s.listener.Close()
}
@ -207,6 +220,7 @@ func (s *Server) handleConnection(conn net.Conn, sem chan struct{}) {
case header := <-reqChan:
if header.err != nil {
s.handleRequestError(conn, header)
return
}
ctx := context.Background()
@ -234,6 +248,7 @@ func (s *Server) handleRequestError(conn net.Conn, req request) {
var gmierr *GmiError
if errors.As(req.err, &gmierr) {
WriteHeader(conn, gmierr.Code, gmierr.Error())
return
}
@ -270,11 +285,11 @@ func readHeader(c net.Conn) (*request, error) {
requestURL := strings.TrimSpace(req)
if requestURL == "" {
return r, Error(StatusBadRequest, errors.New("empty request URL"))
} else if !utf8.ValidString(requestURL) {
return r, Error(StatusBadRequest, errors.New("not a valid utf-8 url"))
return r, Error(StatusBadRequest, ErrEmptyRequestURL)
} else if len(requestURL) > URLMaxBytes {
return r, Error(StatusBadRequest, ErrHeaderTooLong)
} else if !utf8.ValidString(requestURL) {
return r, Error(StatusBadRequest, ErrInvalidUtf8)
}
parsedURL, err := url.Parse(requestURL)
@ -285,15 +300,16 @@ func readHeader(c net.Conn) (*request, error) {
r.URL = parsedURL
if parsedURL.Scheme != "" && parsedURL.Scheme != "gemini" {
return r, Error(StatusProxyRequestRefused, fmt.Errorf("unknown protocol scheme %s", parsedURL.Scheme))
return r, Error(StatusProxyRequestRefused, ErrUnknownProtocol)
} else if parsedURL.Host == "" {
return r, Error(StatusBadRequest, errors.New("empty host"))
return r, Error(StatusBadRequest, ErrInvalidHost)
}
if parsedURL.Path == "" {
// This error is a redirect path.
return r, Error(StatusRedirectPermanent, errors.New("./"+parsedURL.Path))
} else if parsedURL.Path != path.Clean(parsedURL.Path) {
return r, Error(StatusBadRequest, errors.New("path error"))
return r, Error(StatusBadRequest, ErrInvalidPath)
}
return r, nil
@ -308,11 +324,10 @@ func (s *Server) Shutdown(ctx context.Context) error {
go func() {
s.shutdown = true
// un-stuck call to self
conn, err := tls.Dial("tcp", "localhost:1965", &tls.Config{
InsecureSkipVerify: true,
})
conn, err := tls.Dial("tcp", s.Addr, &tls.Config{InsecureSkipVerify: true})
if err != nil {
s.logf("un-stuck call error: %v", err)
s.logf("un-stuck call failed (ok): %v", err)
return
}
defer conn.Close()
@ -327,20 +342,21 @@ func (s *Server) Shutdown(ctx context.Context) error {
s.logf("error while closing listener %v", err)
}
}
return nil
}
func WriteHeader(c io.Writer, code int, message string) {
func WriteHeader(c io.Writer, code int, message string) (int, error) {
// <STATUS><SPACE><META><CR><LF>
var header []byte
if len(message) == 0 {
header = []byte(fmt.Sprintf("%d%s", code, Termination))
return c.Write([]byte(fmt.Sprintf("%d%s", code, Termination)))
}
header = []byte(fmt.Sprintf("%d %s%s", code, message, Termination))
c.Write(header)
return c.Write([]byte(fmt.Sprintf("%d %s%s", code, message, Termination)))
}
func Write(c io.Writer, body []byte) {
func Write(c io.Writer, body []byte) (int64, error) {
reader := bytes.NewReader(body)
io.Copy(c, reader)
return io.Copy(c, reader)
}

View file

@ -6,10 +6,13 @@ import (
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"math/big"
"time"
)
const rsaBits = 2048
// GenX509KeyPair generates a TLS keypair with one week validity.
func GenX509KeyPair(host string, daysvalid int) (tls.Certificate, error) {
now := time.Now()
@ -28,15 +31,15 @@ func GenX509KeyPair(host string, daysvalid int) (tls.Certificate, error) {
x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
}
priv, err := rsa.GenerateKey(rand.Reader, 2048)
priv, err := rsa.GenerateKey(rand.Reader, rsaBits)
if err != nil {
return tls.Certificate{}, err
return tls.Certificate{}, fmt.Errorf("generate key: %w", err)
}
cert, err := x509.CreateCertificate(rand.Reader, template, template,
priv.Public(), priv)
if err != nil {
return tls.Certificate{}, err
return tls.Certificate{}, fmt.Errorf("create certificate: %w", err)
}
var out tls.Certificate

13
main.go
View file

@ -84,10 +84,12 @@ func main() {
}
confirm := make(chan struct{}, 1)
go func() {
if err := server.ListenAndServe(); err != nil && !errors.Is(err, gemini.ErrServerClosed) {
log.Fatalf("ListenAndServe terminated unexpectedly: %v", err)
}
close(confirm)
}()
@ -110,7 +112,7 @@ func setupCertificate(crt, key, host string) func() (*tls.Config, error) {
if crt != "" && key != "" {
cert, err := tls.LoadX509KeyPair(crt, key)
if err != nil {
return nil, err
return nil, fmt.Errorf("load x509 keypair: %w", err)
}
return gemini.TLSConfig(host, cert), nil
}
@ -118,7 +120,7 @@ func setupCertificate(crt, key, host string) func() (*tls.Config, error) {
log.Println("generating self-signed temporary certificate")
cert, err := gemini.GenX509KeyPair(host, autoCertDaysValid)
if err != nil {
return nil, err
return nil, fmt.Errorf("generate x509 keypair: %w", err)
}
return gemini.TLSConfig(host, cert), nil
}
@ -132,12 +134,13 @@ func setupLogger(dir, filename string) (*log.Logger, error) {
logpath := filepath.Join(dir, filename)
_, err := setupFileLogging(logger, logpath)
if err != nil {
log.Fatalf("failed to open log file: %v", err)
return logger, fmt.Errorf("setup logger: %w", err)
}
go func(logger *log.Logger, logpath string) {
hup := make(chan os.Signal, 1)
signal.Notify(hup, syscall.SIGHUP)
for {
<-hup
logger.Println("rotating log file after SIGHUP")
@ -146,16 +149,16 @@ func setupLogger(dir, filename string) (*log.Logger, error) {
log.Fatalf("failed to rotate log file: %v", err)
}
}
}(logger, logpath)
}
return logger, nil
}
func setupFileLogging(logger *log.Logger, logpath string) (*os.File, error) {
logfile, err := os.OpenFile(logpath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return logfile, err
return logfile, fmt.Errorf("log file open: %w", err)
}
logger.SetOutput(logfile)