use updated errors library

Signed-off-by: kim (grufwub) <grufwub@gmail.com>
development
kim (grufwub) 3 years ago
parent 0ecc21c1de
commit 1598f101f1

@ -2,7 +2,6 @@ package core
import (
"os/exec"
"strconv"
"strings"
"syscall"
@ -43,7 +42,7 @@ func TryExecuteCGIScript(client *Client, request *Request) error {
// Get relative path with CGI dir stripped
partial := request.Path().Relative()[len(cgiPath.Relative()):]
if len(partial) == 0 {
return errors.Wrap(request.Path().Relative(), ErrRestrictedPath)
return ErrRestrictedPath.Extendf("%s is CGI dir", request.Path().Selector())
}
partial = partial[1:]
@ -59,9 +58,9 @@ func TryExecuteCGIScript(client *Client, request *Request) error {
stat, err := StatFile(request.Path())
switch {
case err != nil:
return err
return err.(errors.Error).Extend("CGI error")
case !stat.Mode().IsRegular():
return errors.Wrap("cannot execute non-regular file", ErrFileStat)
return ErrFileType.Extendf("%s CGI error", request.Path().Absolute())
}
return ExecuteCGIScript(client, request, "")
}
@ -89,7 +88,7 @@ func TryExecuteCGIScript(client *Client, request *Request) error {
}
// No CGI script was found, return not-found error
return errors.Wrap("file not found", ErrFileStat)
return ErrFileStat.Extendf("%s CGI error", request.Path().Absolute())
}
// ExecuteCGIScript executes a CGI script, responding with stdout to client
@ -110,7 +109,7 @@ func ExecuteCGIScript(client *Client, request *Request, pathInfo string) error {
// Start executing
err := cmd.Start()
if err != nil {
return errors.WrapError(ErrCGIStart, err)
return errors.With(err).WrapWithin(ErrCGIStart).Extend(request.Path().Absolute())
}
// NOTE: we don't set a max CGI script run time anymore,
@ -130,8 +129,7 @@ func ExecuteCGIScript(client *Client, request *Request, pathInfo string) error {
}
// Log and return
SystemLog.Errorf(cgiExecuteErrStr, request.Path().Absolute(), exitCode)
return errors.Wrap(strconv.Itoa(exitCode), ErrCGIExitCode)
return ErrCGIExitCode.Extendf("%s: %d", request.Path().Absolute(), exitCode)
}
// Exit fine!

@ -35,7 +35,7 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
}
configFile = os.Args[2]
case "-v", "--version":
fmt.Println("Gophi (" + proto + ") " + Version)
fmt.Printf("Gophi (%s) %s\n", proto, Version)
os.Exit(0)
default:
usage(1)
@ -94,14 +94,14 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
// Check valid values for BindAddr and Hostname
if Hostname == "" {
if Bind == "" {
SystemLog.Fatal(hostnameBindEmptyStr)
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(rootDirEmptyErrStr)
SystemLog.Fatal("No server root directory supplied!")
}
// Set port info
@ -115,7 +115,7 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
var err error
serverListener, err = newListener()
if err != nil {
SystemLog.Fatalf(listenerBeginFailStr, protocol, Hostname, Port, Bind, Port, err.Error())
SystemLog.Fatalf("Failed to start listener on %s://%s:%s (%s:%s) - %s", protocol, Hostname, Port, Bind, Port, err.Error())
}
// Setup the sync pools
@ -138,33 +138,33 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
// - username but no groupname, we use the user primary gid
var uid, gid int
if *username != "" {
u, osErr := user.Lookup(*username)
if osErr != nil {
SystemLog.Fatalf(userLookupErrStr, osErr)
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, osErr := user.LookupGroup(*groupname)
if osErr != nil {
SystemLog.Fatalf(groupLookupErrStr, osErr)
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 != "" {
osErr := syscall.Chroot(*chroot)
if osErr != nil {
SystemLog.Fatalf(chrootErrStr, osErr)
err := syscall.Chroot(*chroot)
if err != nil {
SystemLog.Fatalf("Error chrooting into directory: %s", err.Error())
}
SystemLog.Infof(chrootStr, *chroot)
SystemLog.Infof("Chrooting into dir: %s", *chroot)
// Ensure we're at root of chroot
osErr = os.Chdir("/")
if osErr != nil {
SystemLog.Fatalf(chDirErrStr, osErr)
err = os.Chdir("/")
if err != nil {
SystemLog.Fatalf("Error entering server directory: %s", err.Error())
}
}
@ -178,33 +178,33 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
}
// Change to server root
osErr := os.Chdir(Root)
if osErr != nil {
SystemLog.Fatalf(chDirErrStr, osErr)
err = os.Chdir(Root)
if err != nil {
SystemLog.Fatalf("Error entering server directory: %s", err.Error())
}
SystemLog.Infof(chDirStr, Root)
SystemLog.Infof("Entered server dir: %s", Root)
// If been supplied a group, change to requested group
if *groupname != "" {
osErr := syscall.Setgid(gid)
if osErr != nil {
SystemLog.Fatalf(setgidErrStr, osErr)
err := syscall.Setgid(gid)
if err != nil {
SystemLog.Fatalf("Error performing setgid: %s", err.Error())
}
SystemLog.Infof(setgidStr, *groupname)
SystemLog.Infof("Running as group: %s", *groupname)
}
// If been supplied a user, switch to requested user
if *username != "" {
osErr := syscall.Setuid(uid)
if osErr != nil {
SystemLog.Fatalf(setuidErrStr, osErr)
err := syscall.Setuid(uid)
if err != nil {
SystemLog.Fatalf("Error performing setuid: %s", err.Error())
}
SystemLog.Infof(setuidStr, *username)
SystemLog.Infof("Running as user: %s", *username)
}
// Check not running as root
if syscall.Geteuid() == 0 || syscall.Getegid() == 0 {
SystemLog.Fatalf(runningAsRootErrStr)
SystemLog.Fatal("Gophi does not support running as root!")
}
// FileSystemObject (and related) setup
@ -213,40 +213,41 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
// If no restricted paths provided, set to the disabled function. Else, compile and enable
if len(*restrictedPathsList) == 0 {
SystemLog.Info(pathRestrictionsDisabledStr)
SystemLog.Info("Path restrictions disabled")
IsRestrictedPath = isRestrictedPathDisabled
} else {
SystemLog.Info(pathRestrictionsEnabledStr)
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(pathHidingDisableStr)
SystemLog.Info("Path hiding disabled")
IsHiddenPath = isHiddenPathDisabled
} else {
SystemLog.Info(pathHidingEnabledStr)
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(requestRemapDisabledStr)
SystemLog.Info("Request remapping disabled")
RemapRequest = remapRequestDisabled
} else {
SystemLog.Info(requestRemapEnabledStr)
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(cgiSupportDisabledStr)
SystemLog.Info("CGI script support disabled")
WithinCGIDir = withinCGIDirDisabled
} else {
SystemLog.Info(cgiSupportEnabledStr)
SystemLog.Info("CGI script support enabled")
SystemLog.Infof("CGI safe path: %s", *safePath)
cgiPath = NewSanitizedPathAtRoot(Root, *cgiDir)
cgiDirRegex = compileCGIRegex(cgiPath.Relative())
cgiEnv = setupInitialCGIEnv(*safePath)
@ -256,11 +257,11 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
// Set appropriate Path builder function depending
// on whether user spaces enabled or disabled
if *userSpacesEnabled {
SystemLog.Info(userSpacesEnabledStr)
SystemLog.Info("User spaces support enabled")
BuildPath = buildPathUserSpacesEnabled
SystemLog.Infof(userSpacesStr, "public_"+protocol)
SystemLog.Info("User space directory: public_" + protocol)
} else {
SystemLog.Info(userSpacesDisabledStr)
SystemLog.Info("User spaces support disabled")
BuildPath = buildPathUserSpacesDisabled
}
@ -286,7 +287,7 @@ func setupLogger(output string) *log.SLogger {
default:
file, err := os.OpenFile(output, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
log.Fatalf(logOutputErrStr, output, err.Error())
log.Fatalf("Error opening log output %s: %s", output, err.Error())
}
return log.NewSLogger(file, true)
}

@ -67,14 +67,14 @@ func (c *conn) ReadLine() ([]byte, error) {
// Perform a single read into the buffer
count, err := c.c.Read(c.b[totalCount:])
if err != nil {
return nil, errors.WrapError(ErrConnRead, err)
return nil, errors.With(err).WrapWithin(ErrConnRead)
}
// Handle empty reads...
if count < 1 {
// After too many empty reads just return error
if !(emptyRead < 100) {
return nil, errors.WrapError(ErrConnRead, io.ErrNoProgress)
return nil, errors.With(io.ErrNoProgress).WrapWithin(ErrConnRead)
}
// Iterate empty read counter
@ -110,7 +110,7 @@ func (c *conn) ReadLine() ([]byte, error) {
func (c *conn) Write(b []byte) error {
_, err := c.bw.Write(b)
if err != nil {
return errors.WrapError(ErrConnWrite, err)
return errors.With(err).WrapWithin(ErrConnWrite)
}
return nil
}
@ -123,7 +123,7 @@ func (c *conn) ReadFrom(r io.Reader) error {
// Write implementation (forcing the deadline to be regularly updated)
_, err := c.bw.ReadFrom(r)
if err != nil {
return errors.WrapError(ErrConnWrite, err)
return errors.With(err).WrapWithin(ErrConnWrite)
}
return nil
}
@ -146,7 +146,7 @@ func (c *conn) Close() error {
// Return error (if exists)
if err != nil {
return errors.WrapError(ErrConnClose, err)
return errors.With(err).WrapWithin(ErrConnClose)
}
return nil
}

@ -1,27 +1,29 @@
package core
import "github.com/grufwub/go-errors"
import (
"github.com/grufwub/go-errors"
)
// Core ErrorCodes
var (
ErrConnWrite = errors.Error(connWriteErrStr)
ErrConnRead = errors.Error(connReadErrStr)
ErrConnClose = errors.Error(connCloseErrStr)
ErrListenerAccept = errors.Error(listenerAcceptErrStr)
ErrMutexUpgrade = errors.Error(mutexUpgradeErrStr)
ErrMutexDowngrade = errors.Error(mutexDowngradeErrStr)
ErrFileOpen = errors.Error(fileOpenErrStr)
ErrFileStat = errors.Error(fileStatErrStr)
ErrFileRead = errors.Error(fileReadErrStr)
ErrFileType = errors.Error(fileTypeErrStr)
ErrDirectoryRead = errors.Error(directoryReadErrStr)
ErrRestrictedPath = errors.Error(restrictedPathErrStr)
ErrUnescapingHost = errors.Error("")
ErrUnescapingPath = errors.Error("")
ErrParsingScheme = errors.Error("")
ErrParsingHost = errors.Error("")
ErrParsingURI = errors.Error("")
ErrInvalidRequest = errors.Error(invalidRequestErrStr)
ErrCGIStart = errors.Error(cgiStartErrStr)
ErrCGIExitCode = errors.Error(cgiExitCodeErrStr)
ErrConnWrite = errors.BaseError("conn write error")
ErrConnRead = errors.BaseError("conn read error")
ErrConnClose = errors.BaseError("conn close error")
ErrListenerAccept = errors.BaseError("listener accept")
ErrMutexUpgrade = errors.BaseError("mutex upgrade fail")
ErrMutexDowngrade = errors.BaseError("mutex downgrade fail")
ErrFileOpen = errors.BaseError("file open error")
ErrFileStat = errors.BaseError("file stat error")
ErrFileRead = errors.BaseError("file read error")
ErrFileType = errors.BaseError("unsupported file type")
ErrDirectoryRead = errors.BaseError("directory read error")
ErrRestrictedPath = errors.BaseError("restricted path")
ErrUnescapingHost = errors.BaseError("unescaping host")
ErrUnescapingPath = errors.BaseError("unescaping path")
ErrParsingScheme = errors.BaseError("scheme parse fail")
ErrParsingHost = errors.BaseError("host parse fail")
ErrParsingURI = errors.BaseError("URI parse fail")
ErrInvalidRequest = errors.BaseError("invalid request")
ErrCGIStart = errors.BaseError("CGI start error")
ErrCGIExitCode = errors.BaseError("CGI non-zero exit code")
)

@ -13,7 +13,7 @@ import (
func OpenFile(p *Path) (*os.File, error) {
file, err := os.OpenFile(p.Absolute(), os.O_RDONLY, 0444)
if err != nil {
return nil, errors.WrapError(ErrFileOpen, err)
return nil, errors.With(err).WrapWithin(ErrFileOpen)
}
return file, nil
}
@ -22,7 +22,7 @@ func OpenFile(p *Path) (*os.File, error) {
func StatFile(p *Path) (os.FileInfo, error) {
stat, err := os.Stat(p.Absolute())
if err != nil {
return nil, errors.WrapError(ErrFileStat, err)
return nil, errors.With(err).WrapWithin(ErrFileStat)
}
return stat, nil
}
@ -37,7 +37,7 @@ func ReadFile(file *os.File) ([]byte, error) {
buf := &bytes.Buffer{}
_, err := br.WriteTo(buf)
if err != nil {
return nil, errors.WrapError(ErrFileRead, err)
return nil, errors.With(err).WrapWithin(ErrFileRead)
}
return buf.Bytes(), nil
}
@ -60,7 +60,7 @@ func ScanFile(file *os.File, iterator func(string) bool) error {
break
} else {
// Bad error, return
return errors.WrapError(ErrFileRead, err)
return errors.With(err).WrapWithin(ErrFileRead)
}
}
@ -79,7 +79,7 @@ func ScanFile(file *os.File, iterator func(string) bool) error {
func ScanDirectory(dir *os.File, p *Path, iterator func(os.FileInfo, *Path)) error {
nameList, err := dir.Readdirnames(-1)
if err != nil {
return errors.WrapError(ErrDirectoryRead, err)
return errors.With(err).WrapWithin(ErrDirectoryRead)
}
// Sort by name

@ -20,7 +20,7 @@ func NewListener(l net.Listener) *Listener {
func (l *Listener) Accept() (*Client, error) {
conn, err := l.l.Accept()
if err != nil {
return nil, errors.WrapError(ErrListenerAccept, err)
return nil, errors.With(err).WrapWithin(ErrListenerAccept)
}
return NewClient(conn), nil
}

@ -1,7 +1,6 @@
package core
import (
"path"
"regexp"
"strings"
)
@ -17,10 +16,7 @@ type RequestRemap struct {
// compileCGIRegex takes a supplied string and returns compiled regular expression
func compileCGIRegex(cgiDir string) *regexp.Regexp {
if path.IsAbs(cgiDir) {
SystemLog.Fatalf(cgiDirNotRelativeStr)
}
SystemLog.Infof(cgiDirStr, cgiDir)
SystemLog.Infof("CGI directory: %s", cgiDir)
return regexp.MustCompile("^" + cgiDir + "(/.*)?$")
}
@ -38,12 +34,12 @@ func compileRestrictedPathsRegex(restrictions []string) []*regexp.Regexp {
// Compile the regular expression
regex, err := regexp.Compile("^" + expr + "$")
if err != nil {
SystemLog.Fatalf(pathRestrictRegexCompileFailStr, expr)
SystemLog.Fatalf("Failed compiling restricted path regex: %s", expr)
}
// Append compiled regex and log
regexes = append(regexes, regex)
SystemLog.Infof(pathRestrictRegexCompiledStr, expr)
SystemLog.Infof("Compiled restricted path regex: %s", expr)
}
return regexes
@ -63,12 +59,12 @@ func compileHiddenPathsRegex(hidden []string) []*regexp.Regexp {
// Compile the regular expression
regex, err := regexp.Compile("^" + expr + "$")
if err != nil {
SystemLog.Fatalf(pathHidingRegexCompileFailStr, expr)
SystemLog.Fatalf("Failed compiling hidden path regex: %s", expr)
}
// Append compiled regex and log
regexes = append(regexes, regex)
SystemLog.Infof(pathHidingRegexCompiledStr, expr)
SystemLog.Infof("Compiled hidden path regex: %s", expr)
}
return regexes
@ -88,18 +84,18 @@ func compileRequestRemapRegex(remaps []string) []*RequestRemap {
// Split into alias and remap
split := strings.Split(expr, requestRemapSeparatorStr)
if len(split) != 2 {
SystemLog.Fatalf(requestRemapRegexInvalidStr, expr)
SystemLog.Fatalf("Invalid request remap regex: %s", expr)
}
// Compile the regular expression
regex, err := regexp.Compile("^" + strings.TrimPrefix(split[0], "/") + "$")
if err != nil {
SystemLog.Fatalf(requestRemapRegexCompileFailStr, expr)
SystemLog.Fatalf("Failed compiling request remap regex: %s", expr)
}
// Append RequestRemap and log
requestRemaps = append(requestRemaps, &RequestRemap{regex, strings.TrimPrefix(split[1], "/")})
SystemLog.Infof(requestRemapRegexCompiledStr, expr)
SystemLog.Infof("Compiled path remap regex: %s", expr)
}
return requestRemaps

@ -14,7 +14,7 @@ import (
const (
// Version holds the current version string
Version = "v3.1.9"
Version = "v3.2.0-beta"
)
var (
@ -98,16 +98,16 @@ var (
// Start begins operation of the server
func Start(serve func(*Client)) {
// Start the FileCache freshness monitor
SystemLog.Infof(cacheMonitorStartStr, monitorSleepTime)
SystemLog.Infof("Starting cache monitor with freq: %s", monitorSleepTime.String())
go FileCache.StartMonitor(monitorSleepTime)
// Start the listener
SystemLog.Infof(listeningOnStr, protocol, Hostname, Port, Bind, Port)
SystemLog.Infof("Listening on %s://%s:%s (%s:%s)", protocol, Hostname, Port, Bind, Port)
go func() {
for {
client, err := serverListener.Accept()
if err != nil {
SystemLog.Errorf(err.Error())
SystemLog.Error(err.Error())
}
// Serve client then close in separate goroutine
@ -120,7 +120,7 @@ func Start(serve func(*Client)) {
// Listen for OS signals and terminate if necessary
sig := <-sigChannel
SystemLog.Infof(signalReceivedStr, sig)
SystemLog.Infof("Signal received: %s. Shutting down...", sig.String())
os.Exit(0)
}
@ -128,12 +128,12 @@ func Start(serve func(*Client)) {
func HandleClient(client *Client, request *Request) error {
// If restricted, return error
if IsRestrictedPath(request.Path()) {
return ErrRestrictedPath
return ErrRestrictedPath.Extend(request.Path().Selector())
}
// Try remap if necessary. If remapped, log!
if ok := RemapRequest(request); ok {
client.LogInfo(requestRemappedStr, request.Path().Selector(), request.Query())
client.LogInfo("Remapped request: %s %s", request.Path().Selector(), request.Query())
}
// If within CGI dir, attempt to execute this!
@ -165,7 +165,7 @@ func HandleClient(client *Client, request *Request) error {
// Get stat
stat, err := file.Stat()
if err != nil {
return errors.WrapError(ErrFileStat, err)
return errors.With(err).WrapWithin(ErrFileStat)
}
switch {

@ -1,84 +0,0 @@
package core
// Error string constants
const (
connWriteErrStr = "Conn write error"
connReadErrStr = "Conn read error"
connCloseErrStr = "Conn close error"
listenerAcceptErrStr = "Listener accept error"
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"
invalidHostErrStr = "Invalid host"
invalidRequestErrStr = "Invalid request"
cgiStartErrStr = "CGI start error"
cgiExitCodeErrStr = "CGI non-zero exit code"
)
// Log string constants
const (
hostnameBindEmptyStr = "At least one of 'hostname' or 'listen' must be non-empty!"
chrootStr = "Chrooting into dir: %s"
chrootErrStr = "Error chrooting into directory: %s"
chDirStr = "Entered server dir: %s"
chDirErrStr = "Error entering server directory: %s"
rootDirEmptyErrStr = "No server root directory supplied!"
userLookupErrStr = "Error looking up user: %s"
groupLookupErrStr = "Error looking up group: %s"
currentUserLookupErrStr = "Error looking up current user: %s"
setuidStr = "Running as user: %s"
setuidErrStr = "Error performing setuid: %s"
setgidStr = "Running as group: %s"
setgidErrStr = "Error performing setgid: %s"
runningAsRootErrStr = "Gophi does not support running as root!"
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"
cgiPathStr = "CGI safe path: %s"
cgiSupportEnabledStr = "CGI script support enabled"
cgiSupportDisabledStr = "CGI script support disabled"
cgiDirNotRelativeStr = "CGI directory must be a relative path!"
cgiDirStr = "CGI directory: %s"
cgiExecuteErrStr = "Exit executing: %s [%d]"
userSpacesEnabledStr = "User spaces support enabled"
userSpacesDisabledStr = "User spaces support disabled"
userSpacesStr = "User space directory: %s"
signalReceivedStr = "Signal received: %v. Shutting down..."
logOutputErrStr = "Error opening log output %s: %s"
)

@ -26,7 +26,7 @@ func ParseScheme(raw string) (string, string, error) {
case ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'):
// All good, continue
case c == ':':
return "", "", errors.Wrap(raw, ErrParsingScheme)
return "", "", ErrParsingScheme.Extend(raw)
default:
// Invalid scheme char (or scheme first-char) return
return "", raw, nil
@ -150,14 +150,14 @@ func unescapeHost(raw string) (string, error) {
// If not a valid % encoded hex value, return with error
if i+2 >= len(raw) || !isHex(raw[i+1]) || !isHex(raw[i+2]) {
return "", errors.Wrap(raw, ErrUnescapingHost)
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 "", errors.Wrap(raw, ErrUnescapingHost)
return "", ErrUnescapingHost.Extend(raw)
}
// Skip iteration past the
@ -166,7 +166,7 @@ func unescapeHost(raw string) (string, error) {
default:
// If within ASCII range, and shoud be escaped, return error
if raw[i] < 0x80 && shouldHostEscape(raw[i]) {
return "", errors.Wrap(raw, ErrUnescapingHost)
return "", ErrUnescapingHost.Extend(raw)
}
// Iter
@ -193,7 +193,7 @@ func unescapePath(raw string) (string, error) {
// If not a valid % encoded hex value, return with error
if i+2 >= length || !isHex(raw[i+1]) || !isHex(raw[i+2]) {
return "", errors.Wrap(raw, ErrUnescapingPath)
return "", ErrUnescapingPath.Extend(raw)
}
// Skip iteration past the
@ -247,7 +247,7 @@ func ParseEncodedHost(raw string) (string, string, error) {
// Unescape the host info
raw, err := unescapeHost(raw)
if err != nil {
return "", "", errors.WrapError(ErrParsingHost, err)
return "", "", err.(errors.Error).WrapWithin(ErrParsingHost)
}
// Split by last ':' and return
@ -263,7 +263,7 @@ func ParseEncodedURI(received string) (string, string, error) {
// Unescape path, query is up-to CGI scripts
rawPath, err := unescapePath(rawPath)
if err != nil {
return "", "", errors.WrapError(ErrParsingURI, err)
return "", "", err.(errors.Error).WrapWithin(ErrParsingURI)
}
// Return the raw path and query

@ -8,8 +8,44 @@ import (
// Gemini specific base errors
var (
errInvalidProtocol = errors.Error(inavlidProtocolErrStr)
errInvalidHostPort = errors.Error(invalidHostPortErrStr)
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")
errInvalidRequestRsp = buildResponseHeader(statusBadRequest, "Invalid Request")
)
// generateErrorResponse takes an error code and generates an error response byte slice
@ -18,49 +54,42 @@ func generateErrorResponse(err error) ([]byte, bool) {
case errors.Is(err, core.ErrConnWrite):
return nil, false // no point responding if we couldn't write
case errors.Is(err, core.ErrConnRead):
return buildErrorResponse("40", statusMeta40), true
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 buildErrorResponse("40", statusMeta40), true
return errTemporaryFailureRsp, true
case errors.Is(err, core.ErrMutexDowngrade):
return buildErrorResponse("40", statusMeta40), true
return errTemporaryFailureRsp, true
case errors.Is(err, core.ErrFileOpen):
return buildErrorResponse("51", statusMeta51), true
return errNotFoundRsp, true
case errors.Is(err, core.ErrFileStat):
return buildErrorResponse("51", statusMeta51), true
return errNotFoundRsp, true
case errors.Is(err, core.ErrFileRead):
return buildErrorResponse("51", statusMeta51), true
return errNotFoundRsp, true
case errors.Is(err, core.ErrFileType):
return buildErrorResponse("51", statusMeta51), true
return errNotFoundRsp, true
case errors.Is(err, core.ErrDirectoryRead):
return buildErrorResponse("51", statusMeta51), true
return errNotFoundRsp, true
case errors.Is(err, core.ErrRestrictedPath):
return buildErrorResponse("51", statusMeta51), true
// All forms of invalid request
return errRestrictedRsp, true
case errors.Is(err, core.ErrInvalidRequest):
return buildErrorResponse("59", statusMeta59), true
return errInvalidRequestRsp, true
case errors.Is(err, core.ErrParsingScheme):
return buildErrorResponse("59", statusMeta59), true
return errInvalidRequestRsp, true
case errors.Is(err, core.ErrParsingHost):
return buildErrorResponse("59", statusMeta59), true
return errInvalidRequestRsp, true
case errors.Is(err, core.ErrParsingURI):
return buildErrorResponse("59", statusMeta59), true
return errInvalidRequestRsp, true
case errors.Is(err, core.ErrCGIStart):
return buildErrorResponse("42", statusMeta42), true
return errPermanentFailureRsp, true
case errors.Is(err, core.ErrCGIExitCode):
return buildErrorResponse("42", statusMeta42), true
case errors.Is(err, errInvalidProtocol):
return buildErrorResponse("53", statusMeta59), true
case errors.Is(err, errInvalidHostPort):
return buildErrorResponse("53", statusMeta53), true
return errTemporaryFailureRsp, true
case errors.Is(err, errInvalidScheme):
return errInvalidSchemeRsp, true
case errors.Is(err, errProxyRequest):
return errProxyRequestRsp, true
default:
return nil, false
}
}
func buildErrorResponse(statusCode, statusMeta string) []byte {
return buildResponseHeader(statusCode, statusMeta)
}

@ -15,7 +15,7 @@ func init() {
b := make([]byte, 1)
_, err := io.ReadFull(rand.Reader, b)
if err != nil {
logger.Fatal(entropyAssertFailStr)
logger.Fatal("Failed to assert safe source of system entropy exists!")
}
}
@ -60,6 +60,7 @@ func Run() {
)
// 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!

@ -4,8 +4,6 @@ import (
"gophi/core"
"os"
"strings"
"github.com/grufwub/go-errors"
)
// rootRedirectHeader stores the root redirect header byte slice,
@ -17,7 +15,7 @@ func serve(client *core.Client) {
// Receive line from client
received, err := client.Conn().ReadLine()
if err != nil {
client.LogError(clientReadFailStr)
client.LogError("Conn read fail")
handleError(client, err)
return
}
@ -25,27 +23,25 @@ func serve(client *core.Client) {
// Ensure is a valid URL string
if core.HasAsciiControlBytes(raw) {
client.LogError(invalidRequestStr, raw)
handleError(client, errors.Wrap("has ascii control bytes", core.ErrInvalidRequest))
client.LogError("Invalid request: %s", raw)
handleError(client, core.ErrInvalidRequest.Extend("has ascii control bytes"))
return
}
// Get the URL scheme (or error!)
scheme, path, err := core.ParseScheme(raw)
if err != nil {
client.LogError(invalidRequestStr, raw)
client.LogError("Invalid request: %s", raw)
handleError(client, err)
return
}
// Infer no schema as 'gemini', else check we
// were explicitly provided 'gemini'
if scheme != "" {
if scheme != "gemini" {
client.LogError(invalidRequestStr, raw)
handleError(client, errors.Wrap(scheme, errInvalidProtocol))
return
}
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
@ -54,29 +50,29 @@ func serve(client *core.Client) {
// Parse the URL encoded host info
host, port, err := core.ParseEncodedHost(host)
if err != nil {
client.LogError(invalidRequestStr, raw)
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(invalidRequestStr, raw)
handleError(client, errors.Wrap(host+":"+port, errInvalidHostPort))
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(invalidRequestStr, raw)
client.LogError("Invalid request: %s", raw)
handleError(client, err)
return
}
// Redirect empty path to root
if len(path) < 1 {
client.LogInfo(clientRedirectStr, "/")
client.LogInfo("Redirect to: /")
client.Conn().Write(rootRedirect)
return
}
@ -88,9 +84,9 @@ func serve(client *core.Client) {
err = core.HandleClient(client, request)
if err != nil {
handleError(client, err)
client.LogError(clientServeFailStr, request.String())
client.LogError("Failed to serve: %s", request.String())
} else {
client.LogInfo(clientServedStr, request.String())
client.LogInfo("Served: %s", request.String())
}
}
@ -100,7 +96,7 @@ func handleError(client *core.Client, err error) {
if ok {
client.Conn().Write(response)
}
core.SystemLog.Errorf(err.Error())
core.SystemLog.Error(err.Error())
}
func handleDirectory(client *core.Client, file *os.File, p *core.Path) error {
@ -108,17 +104,17 @@ func handleDirectory(client *core.Client, file *os.File, p *core.Path) error {
indexGem := p.JoinPathUnsafe("index.gmi")
// If index gem exists, we fetch this
fd2, err := core.OpenFile(indexGem)
file2, err := core.OpenFile(indexGem)
if err == nil {
stat, osErr := fd2.Stat()
if osErr == nil {
stat, err := file2.Stat()
if err == nil {
// Fetch gem and defer close
defer fd2.Close()
return core.FetchFile(client, fd2, stat, indexGem)
defer file2.Close()
return core.FetchFile(client, file2, stat, indexGem)
}
// Else, just close fd2
fd2.Close()
file2.Close()
}
// Slice to write

@ -1,39 +0,0 @@
package gemini
// Client error response strings
const (
statusMeta10 = "Input"
statusMeta11 = "Sensitive Input"
statusMeta30 = "Temporary Redirect"
statusMeta31 = "Permanent Redirect"
statusMeta40 = "Temporary Failure"
statusMeta41 = "Server Unavailable"
statusMeta42 = "CGI Error"
statusMeta43 = "Proxy Error"
statusMeta44 = "Slow Down"
statusMeta50 = "Permanent Failure"
statusMeta51 = "Not Found"
statusMeta52 = "Gone"
statusMeta53 = "Proxy Request Refused"
statusMeta59 = "Bad Request"
statusMeta60 = "Client Certificate Required"
statusMeta61 = "Client Certificate Not Authorised"
statusMeta62 = "Certificate Not Valid"
)
// Gemini specific error string constants
const (
invalidTLSConfigErrStr = "Invalid TLS cert or key file"
inavlidProtocolErrStr = "Invalid request protocol"
invalidHostPortErrStr = "Invalid host:port pair"
)
// Log string constants
const (
entropyAssertFailStr = "Failed to assert safe source of system entropy exists!"
clientReadFailStr = "Failed to read"
invalidRequestStr = "Invalid request: %s"
clientServeFailStr = "Failed to serve: %s"
clientServedStr = "Served: %s"
clientRedirectStr = "Redirect to: %s"
)
Loading…
Cancel
Save