From a53418f77c7f0cbe06c972ce1e99843c72e5460d Mon Sep 17 00:00:00 2001 From: dre Date: Thu, 8 Jul 2021 21:44:18 +0800 Subject: [PATCH] lint and clean up --- README.md | 2 +- fileserver/fileserver.go | 15 +++++++---- gemini/gemini.go | 58 +++++++++++++++++++++++++--------------- gemini/gencert.go | 9 ++++--- main.go | 13 +++++---- 5 files changed, 62 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 28191ed..5f587e1 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/fileserver/fileserver.go b/fileserver/fileserver.go index e04593a..2ee9214 100644 --- a/fileserver/fileserver.go +++ b/fileserver/fileserver.go @@ -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 diff --git a/gemini/gemini.go b/gemini/gemini.go index bfbebef..01d4a09 100644 --- a/gemini/gemini.go +++ b/gemini/gemini.go @@ -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) { // - 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) } diff --git a/gemini/gencert.go b/gemini/gencert.go index df31ccb..63e0e79 100644 --- a/gemini/gencert.go +++ b/gemini/gencert.go @@ -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 diff --git a/main.go b/main.go index 242e8db..743a3b6 100644 --- a/main.go +++ b/main.go @@ -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)