package main import ( "fmt" "log" "net/url" "os" "path/filepath" "strings" gemini "sanctum.geek.nz/code/go-gemini.git.git" ) // Handler is the main handler of the server type Handler struct { cfg Config } func (h Handler) validateRequest(r gemini.Request, absItemPath string) error { u, err := url.Parse(r.URL) if err != nil { return gemini.Error{Err: err, Status: gemini.StatusBadRequest} } if u.Scheme != "" && u.Scheme != "gemini" { err = fmt.Errorf("proxy is not supported by the server") return gemini.Error{Err: err, Status: gemini.StatusProxyRequestRefused} } if !strings.HasPrefix(absItemPath, h.cfg.SourceDir) { return gemini.Error{Err: fmt.Errorf("permission denied"), Status: gemini.StatusBadRequest} } return nil } func (h Handler) urlAbsPath(rawURL string) (string, error) { u, err := url.Parse(rawURL) if err != nil { return "", gemini.Error{Err: err, Status: gemini.StatusBadRequest} } itemPath, err := filepath.Abs(filepath.Join(h.cfg.SourceDir, u.Path)) if err != nil { return "", gemini.Error{Err: err, Status: gemini.StatusTemporaryFailure} } return itemPath, nil } func (h Handler) getFilePath(rawURL string) (string, error) { itemPath, err := h.urlAbsPath(rawURL) if err != nil { return "", err } if isFile(itemPath) { return itemPath, nil } for _, indexFile := range h.cfg.IndexFiles { indexPath := filepath.Join(itemPath, indexFile) if isFile(indexPath) { return indexPath, nil } } return "", gemini.Error{Err: fmt.Errorf("file not found"), Status: gemini.StatusNotFound} } func (h Handler) serveFile(path string) gemini.Response { log.Println("Serving file from", path) file, err := os.Open(path) if err != nil { return gemini.ErrorResponse(err) } meta := absPathMime(path) return gemini.Response{Status: gemini.StatusSuccess, Meta: meta, Body: file} } // Handle implement the gemini.Handler interface by serving files from a given source directory func (h Handler) Handle(r gemini.Request) gemini.Response { path, err := h.getFilePath(r.URL) if err != nil { return gemini.ErrorResponse(err) } err = h.validateRequest(r, path) if err != nil { return gemini.ErrorResponse(err) } return h.serveFile(path) }