Compare commits

...

4 Commits

Author SHA1 Message Date
kim (grufwub) 1dce8a6600 update errors library in module files
Signed-off-by: kim (grufwub) <grufwub@gmail.com>
3 years ago
kim (grufwub) 1598f101f1 use updated errors library
Signed-off-by: kim (grufwub) <grufwub@gmail.com>
3 years ago
kim (grufwub) 0ecc21c1de Update errors library
Signed-off-by: kim (grufwub) <grufwub@gmail.com>
3 years ago
kim (grufwub) d84739d018 Fix core.EscapePath() reserved chars list, add test for EscapePath, add benchmarks core/url path escaping
Signed-off-by: kim (grufwub) <grufwub@gmail.com>
3 years ago

@ -38,11 +38,11 @@ func generateCGIEnv(client *Client, request *Request, pathInfo string) []string
} }
// TryExecuteCGIScript attempts to execute supplied CGI script, finding shortest valid path and setting PATH_INFO accordingly // TryExecuteCGIScript attempts to execute supplied CGI script, finding shortest valid path and setting PATH_INFO accordingly
func TryExecuteCGIScript(client *Client, request *Request) errors.Error { func TryExecuteCGIScript(client *Client, request *Request) error {
// Get relative path with CGI dir stripped // Get relative path with CGI dir stripped
partial := request.Path().Relative()[len(cgiPath.Relative()):] partial := request.Path().Relative()[len(cgiPath.Relative()):]
if len(partial) == 0 { if len(partial) == 0 {
return ErrRestrictedPath return ErrRestrictedPath.Extendf("%s is CGI dir", request.Path().Selector())
} }
partial = partial[1:] partial = partial[1:]
@ -56,8 +56,11 @@ func TryExecuteCGIScript(client *Client, request *Request) errors.Error {
// original request // original request
if next == -1 { if next == -1 {
stat, err := StatFile(request.Path()) stat, err := StatFile(request.Path())
if err != nil || !stat.Mode().IsRegular() { switch {
return ErrFileStat case err != nil:
return err.(errors.Error).Extend("CGI error")
case !stat.Mode().IsRegular():
return ErrFileType.Extendf("%s CGI error", request.Path().Absolute())
} }
return ExecuteCGIScript(client, request, "") return ExecuteCGIScript(client, request, "")
} }
@ -85,11 +88,11 @@ func TryExecuteCGIScript(client *Client, request *Request) errors.Error {
} }
// No CGI script was found, return not-found error // No CGI script was found, return not-found error
return ErrFileStat return ErrFileStat.Extendf("%s CGI error", request.Path().Absolute())
} }
// ExecuteCGIScript executes a CGI script, responding with stdout to client // ExecuteCGIScript executes a CGI script, responding with stdout to client
func ExecuteCGIScript(client *Client, request *Request, pathInfo string) errors.Error { func ExecuteCGIScript(client *Client, request *Request, pathInfo string) error {
// Create cmd object // Create cmd object
cmd := exec.Command(request.Path().Absolute()) cmd := exec.Command(request.Path().Absolute())
@ -106,7 +109,7 @@ func ExecuteCGIScript(client *Client, request *Request, pathInfo string) errors.
// Start executing // Start executing
err := cmd.Start() err := cmd.Start()
if err != nil { if err != nil {
return ErrCGIStart.Wrap(err) return errors.With(err).WrapWithin(ErrCGIStart).Extend(request.Path().Absolute())
} }
// NOTE: we don't set a max CGI script run time anymore, // NOTE: we don't set a max CGI script run time anymore,
@ -126,8 +129,7 @@ func ExecuteCGIScript(client *Client, request *Request, pathInfo string) errors.
} }
// Log and return // Log and return
SystemLog.Errorf(cgiExecuteErrStr, request.Path().Absolute(), exitCode) return ErrCGIExitCode.Extendf("%s: %d", request.Path().Absolute(), exitCode)
return ErrCGIExitCode
} }
// Exit fine! // Exit fine!

@ -12,7 +12,6 @@ import (
"github.com/grufwub/go-bufpools" "github.com/grufwub/go-bufpools"
"github.com/grufwub/go-config" "github.com/grufwub/go-config"
"github.com/grufwub/go-errors"
"github.com/grufwub/go-filecache" "github.com/grufwub/go-filecache"
log "github.com/grufwub/go-logger" log "github.com/grufwub/go-logger"
) )
@ -23,7 +22,7 @@ func usage(code int) {
} }
// ParseConfigAndSetup parses necessary core server config from file (and any others defined), and sets up the core ready for Start() to be called // ParseConfigAndSetup parses necessary core server config from file (and any others defined), and sets up the core ready for Start() to be called
func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newListener func() (*Listener, errors.Error), fileContent func(*Path) FileContent, dirHandler func(*Client, *os.File, *Path) errors.Error, largeHandler func(*Client, *os.File, *Path) errors.Error, appendCgi func(*Client, *Request, []string) []string) { func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newListener func() (*Listener, error), fileContent func(*Path) FileContent, dirHandler func(*Client, *os.File, *Path) error, largeHandler func(*Client, *os.File, *Path) error, appendCgi func(*Client, *Request, []string) []string) {
// Default configuration file location // Default configuration file location
configFile := "/etc/gophi." + proto + ".conf" configFile := "/etc/gophi." + proto + ".conf"
@ -36,7 +35,7 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
} }
configFile = os.Args[2] configFile = os.Args[2]
case "-v", "--version": case "-v", "--version":
fmt.Println("Gophi (" + proto + ") " + Version) fmt.Printf("Gophi (%s) %s\n", proto, Version)
os.Exit(0) os.Exit(0)
default: default:
usage(1) usage(1)
@ -95,14 +94,14 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
// Check valid values for BindAddr and Hostname // Check valid values for BindAddr and Hostname
if Hostname == "" { if Hostname == "" {
if Bind == "" { if Bind == "" {
SystemLog.Fatal(hostnameBindEmptyStr) SystemLog.Fatal("At least one of 'hostname' or 'listen' must be non-empty!")
} }
Hostname = Bind Hostname = Bind
} }
// Check valid root (i.e. not empty!) // Check valid root (i.e. not empty!)
if Root == "" { if Root == "" {
SystemLog.Fatal(rootDirEmptyErrStr) SystemLog.Fatal("No server root directory supplied!")
} }
// Set port info // Set port info
@ -113,10 +112,10 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
// Setup listener BEFORE entering chroot // Setup listener BEFORE entering chroot
// in case TLS cert+key needs to be read // in case TLS cert+key needs to be read
var err errors.Error var err error
serverListener, err = newListener() serverListener, err = newListener()
if err != nil { 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 // Setup the sync pools
@ -139,33 +138,33 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
// - username but no groupname, we use the user primary gid // - username but no groupname, we use the user primary gid
var uid, gid int var uid, gid int
if *username != "" { if *username != "" {
u, osErr := user.Lookup(*username) u, err := user.Lookup(*username)
if osErr != nil { if err != nil {
SystemLog.Fatalf(userLookupErrStr, osErr) SystemLog.Fatalf("Error looking up user: %s", err.Error())
} }
uid, _ = strconv.Atoi(u.Uid) uid, _ = strconv.Atoi(u.Uid)
gid, _ = strconv.Atoi(u.Gid) gid, _ = strconv.Atoi(u.Gid)
} }
if *groupname != "" { if *groupname != "" {
g, osErr := user.LookupGroup(*groupname) g, err := user.LookupGroup(*groupname)
if osErr != nil { if err != nil {
SystemLog.Fatalf(groupLookupErrStr, osErr) SystemLog.Fatalf("Error looking up group: %s", err.Error())
} }
gid, _ = strconv.Atoi(g.Gid) gid, _ = strconv.Atoi(g.Gid)
} }
// If chroot provided, change to this! // If chroot provided, change to this!
if *chroot != "" { if *chroot != "" {
osErr := syscall.Chroot(*chroot) err := syscall.Chroot(*chroot)
if osErr != nil { if err != nil {
SystemLog.Fatalf(chrootErrStr, osErr) 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 // Ensure we're at root of chroot
osErr = os.Chdir("/") err = os.Chdir("/")
if osErr != nil { if err != nil {
SystemLog.Fatalf(chDirErrStr, osErr) SystemLog.Fatalf("Error entering server directory: %s", err.Error())
} }
} }
@ -179,33 +178,33 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
} }
// Change to server root // Change to server root
osErr := os.Chdir(Root) err = os.Chdir(Root)
if osErr != nil { if err != nil {
SystemLog.Fatalf(chDirErrStr, osErr) 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 been supplied a group, change to requested group
if *groupname != "" { if *groupname != "" {
osErr := syscall.Setgid(gid) err := syscall.Setgid(gid)
if osErr != nil { if err != nil {
SystemLog.Fatalf(setgidErrStr, osErr) 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 been supplied a user, switch to requested user
if *username != "" { if *username != "" {
osErr := syscall.Setuid(uid) err := syscall.Setuid(uid)
if osErr != nil { if err != nil {
SystemLog.Fatalf(setuidErrStr, osErr) SystemLog.Fatalf("Error performing setuid: %s", err.Error())
} }
SystemLog.Infof(setuidStr, *username) SystemLog.Infof("Running as user: %s", *username)
} }
// Check not running as root // Check not running as root
if syscall.Geteuid() == 0 || syscall.Getegid() == 0 { if syscall.Geteuid() == 0 || syscall.Getegid() == 0 {
SystemLog.Fatalf(runningAsRootErrStr) SystemLog.Fatal("Gophi does not support running as root!")
} }
// FileSystemObject (and related) setup // FileSystemObject (and related) setup
@ -214,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 no restricted paths provided, set to the disabled function. Else, compile and enable
if len(*restrictedPathsList) == 0 { if len(*restrictedPathsList) == 0 {
SystemLog.Info(pathRestrictionsDisabledStr) SystemLog.Info("Path restrictions disabled")
IsRestrictedPath = isRestrictedPathDisabled IsRestrictedPath = isRestrictedPathDisabled
} else { } else {
SystemLog.Info(pathRestrictionsEnabledStr) SystemLog.Info("Path restrictions enabled")
restrictedPaths = compileRestrictedPathsRegex(*restrictedPathsList) restrictedPaths = compileRestrictedPathsRegex(*restrictedPathsList)
IsRestrictedPath = isRestrictedPathEnabled IsRestrictedPath = isRestrictedPathEnabled
} }
// If no hidden paths provided, set to the disabled function. Else, compile and enable // If no hidden paths provided, set to the disabled function. Else, compile and enable
if len(*hiddenPathsList) == 0 { if len(*hiddenPathsList) == 0 {
SystemLog.Info(pathHidingDisableStr) SystemLog.Info("Path hiding disabled")
IsHiddenPath = isHiddenPathDisabled IsHiddenPath = isHiddenPathDisabled
} else { } else {
SystemLog.Info(pathHidingEnabledStr) SystemLog.Info("Path hiding enabled")
hiddenPaths = compileHiddenPathsRegex(*hiddenPathsList) hiddenPaths = compileHiddenPathsRegex(*hiddenPathsList)
IsHiddenPath = isHiddenPathEnabled IsHiddenPath = isHiddenPathEnabled
} }
// If no remapped paths provided, set to the disabled function. Else, compile and enable // If no remapped paths provided, set to the disabled function. Else, compile and enable
if len(*remapRequestsList) == 0 { if len(*remapRequestsList) == 0 {
SystemLog.Info(requestRemapDisabledStr) SystemLog.Info("Request remapping disabled")
RemapRequest = remapRequestDisabled RemapRequest = remapRequestDisabled
} else { } else {
SystemLog.Info(requestRemapEnabledStr) SystemLog.Info("Request remapping enabled")
requestRemaps = compileRequestRemapRegex(*remapRequestsList) requestRemaps = compileRequestRemapRegex(*remapRequestsList)
RemapRequest = remapRequestEnabled RemapRequest = remapRequestEnabled
} }
// If no CGI dir supplied, set to disabled function. Else, compile and enable // If no CGI dir supplied, set to disabled function. Else, compile and enable
if *cgiDir == "" { if *cgiDir == "" {
SystemLog.Info(cgiSupportDisabledStr) SystemLog.Info("CGI script support disabled")
WithinCGIDir = withinCGIDirDisabled WithinCGIDir = withinCGIDirDisabled
} else { } else {
SystemLog.Info(cgiSupportEnabledStr) SystemLog.Info("CGI script support enabled")
SystemLog.Infof("CGI safe path: %s", *safePath)
cgiPath = NewSanitizedPathAtRoot(Root, *cgiDir) cgiPath = NewSanitizedPathAtRoot(Root, *cgiDir)
cgiDirRegex = compileCGIRegex(cgiPath.Relative()) cgiDirRegex = compileCGIRegex(cgiPath.Relative())
cgiEnv = setupInitialCGIEnv(*safePath) cgiEnv = setupInitialCGIEnv(*safePath)
@ -257,11 +257,11 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
// Set appropriate Path builder function depending // Set appropriate Path builder function depending
// on whether user spaces enabled or disabled // on whether user spaces enabled or disabled
if *userSpacesEnabled { if *userSpacesEnabled {
SystemLog.Info(userSpacesEnabledStr) SystemLog.Info("User spaces support enabled")
BuildPath = buildPathUserSpacesEnabled BuildPath = buildPathUserSpacesEnabled
SystemLog.Infof(userSpacesStr, "public_"+protocol) SystemLog.Info("User space directory: public_" + protocol)
} else { } else {
SystemLog.Info(userSpacesDisabledStr) SystemLog.Info("User spaces support disabled")
BuildPath = buildPathUserSpacesDisabled BuildPath = buildPathUserSpacesDisabled
} }
@ -287,7 +287,7 @@ func setupLogger(output string) *log.SLogger {
default: default:
file, err := os.OpenFile(output, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) file, err := os.OpenFile(output, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil { 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) return log.NewSLogger(file, true)
} }

@ -61,20 +61,20 @@ func (c *conn) Conn() net.Conn {
} }
// ReadLine reads a single line and returns the result, or nil and error // ReadLine reads a single line and returns the result, or nil and error
func (c *conn) ReadLine() ([]byte, errors.Error) { func (c *conn) ReadLine() ([]byte, error) {
totalCount, end, emptyRead := 0, -1, 0 totalCount, end, emptyRead := 0, -1, 0
for { for {
// Perform a single read into the buffer // Perform a single read into the buffer
count, err := c.c.Read(c.b[totalCount:]) count, err := c.c.Read(c.b[totalCount:])
if err != nil { if err != nil {
return nil, ErrConnRead.Wrap(err) return nil, errors.With(err).WrapWithin(ErrConnRead)
} }
// Handle empty reads... // Handle empty reads...
if count < 1 { if count < 1 {
// After too many empty reads just return error // After too many empty reads just return error
if !(emptyRead < 100) { if !(emptyRead < 100) {
return nil, ErrConnRead.Wrap(io.ErrNoProgress) return nil, errors.With(io.ErrNoProgress).WrapWithin(ErrConnRead)
} }
// Iterate empty read counter // Iterate empty read counter
@ -107,23 +107,23 @@ func (c *conn) ReadLine() ([]byte, errors.Error) {
} }
// WriteBytes writes a byte slice to the buffer and returns error status // WriteBytes writes a byte slice to the buffer and returns error status
func (c *conn) Write(b []byte) errors.Error { func (c *conn) Write(b []byte) error {
_, err := c.bw.Write(b) _, err := c.bw.Write(b)
if err != nil { if err != nil {
return ErrConnWrite.Wrap(err) return errors.With(err).WrapWithin(ErrConnWrite)
} }
return nil return nil
} }
// ReadFrom writes to the buffer from a reader and returns error status // ReadFrom writes to the buffer from a reader and returns error status
func (c *conn) ReadFrom(r io.Reader) errors.Error { func (c *conn) ReadFrom(r io.Reader) error {
// Since this buffer wraps deadlineConn, which DOES NOT have // Since this buffer wraps deadlineConn, which DOES NOT have
// a ReadFrom method implemented, it will force the buffer to // a ReadFrom method implemented, it will force the buffer to
// use it's own internal byte buffer along with the deadlineConn's // use it's own internal byte buffer along with the deadlineConn's
// Write implementation (forcing the deadline to be regularly updated) // Write implementation (forcing the deadline to be regularly updated)
_, err := c.bw.ReadFrom(r) _, err := c.bw.ReadFrom(r)
if err != nil { if err != nil {
return ErrConnWrite.Wrap(err) return errors.With(err).WrapWithin(ErrConnWrite)
} }
return nil return nil
} }
@ -135,7 +135,7 @@ func (c *conn) Writer() io.Writer {
// Close flushes the underlying buffer, closes the conn then puts // Close flushes the underlying buffer, closes the conn then puts
// the sync.Pool conn buffers back // the sync.Pool conn buffers back
func (c *conn) Close() errors.Error { func (c *conn) Close() error {
// Flush + close // Flush + close
c.bw.Flush() c.bw.Flush()
err := c.c.Close() err := c.c.Close()
@ -146,7 +146,7 @@ func (c *conn) Close() errors.Error {
// Return error (if exists) // Return error (if exists)
if err != nil { if err != nil {
return ErrConnClose.Wrap(err) return errors.With(err).WrapWithin(ErrConnClose)
} }
return nil return nil
} }

@ -1,23 +1,29 @@
package core package core
import "github.com/grufwub/go-errors" import (
"github.com/grufwub/go-errors"
)
// Core ErrorCodes // Core ErrorCodes
var ( var (
ErrConnWrite = errors.New(connWriteErrStr) ErrConnWrite = errors.BaseError("conn write error")
ErrConnRead = errors.New(connReadErrStr) ErrConnRead = errors.BaseError("conn read error")
ErrConnClose = errors.New(connCloseErrStr) ErrConnClose = errors.BaseError("conn close error")
ErrListenerBegin = errors.New(listenerBeginErrStr) ErrListenerAccept = errors.BaseError("listener accept")
ErrListenerAccept = errors.New(listenerAcceptErrStr) ErrMutexUpgrade = errors.BaseError("mutex upgrade fail")
ErrMutexUpgrade = errors.New(mutexUpgradeErrStr) ErrMutexDowngrade = errors.BaseError("mutex downgrade fail")
ErrMutexDowngrade = errors.New(mutexDowngradeErrStr) ErrFileOpen = errors.BaseError("file open error")
ErrFileOpen = errors.New(fileOpenErrStr) ErrFileStat = errors.BaseError("file stat error")
ErrFileStat = errors.New(fileStatErrStr) ErrFileRead = errors.BaseError("file read error")
ErrFileRead = errors.New(fileReadErrStr) ErrFileType = errors.BaseError("unsupported file type")
ErrFileType = errors.New(fileTypeErrStr) ErrDirectoryRead = errors.BaseError("directory read error")
ErrDirectoryRead = errors.New(directoryReadErrStr) ErrRestrictedPath = errors.BaseError("restricted path")
ErrRestrictedPath = errors.New(restrictedPathErrStr) ErrUnescapingHost = errors.BaseError("unescaping host")
ErrInvalidRequest = errors.New(invalidRequestErrStr) ErrUnescapingPath = errors.BaseError("unescaping path")
ErrCGIStart = errors.New(cgiStartErrStr) ErrParsingScheme = errors.BaseError("scheme parse fail")
ErrCGIExitCode = errors.New(cgiExitCodeErrStr) 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")
) )

@ -2,14 +2,12 @@ package core
import ( import (
"os" "os"
"github.com/grufwub/go-errors"
) )
// FileContent provides an interface for caching, rendering and getting cached contents of a file // FileContent provides an interface for caching, rendering and getting cached contents of a file
type FileContent interface { type FileContent interface {
Load(*Path, *os.File) errors.Error Load(*Path, *os.File) error
WriteToClient(*Client, *Path) errors.Error WriteToClient(*Client, *Path) error
Clear() Clear()
} }
@ -19,14 +17,14 @@ type RegularFileContent struct {
} }
// Load takes an open FD and loads the file contents into FileContents memory // Load takes an open FD and loads the file contents into FileContents memory
func (fc *RegularFileContent) Load(p *Path, file *os.File) errors.Error { func (fc *RegularFileContent) Load(p *Path, file *os.File) error {
var err errors.Error var err error
fc.content, err = ReadFile(file) fc.content, err = ReadFile(file)
return err return err
} }
// WriteToClient writes the current contents of FileContents to the client // WriteToClient writes the current contents of FileContents to the client
func (fc *RegularFileContent) WriteToClient(client *Client, p *Path) errors.Error { func (fc *RegularFileContent) WriteToClient(client *Client, p *Path) error {
return client.Conn().Write(fc.content) return client.Conn().Write(fc.content)
} }

@ -10,25 +10,25 @@ import (
) )
// OpenFile opens a file for reading (read-only, world-readable) // OpenFile opens a file for reading (read-only, world-readable)
func OpenFile(p *Path) (*os.File, errors.Error) { func OpenFile(p *Path) (*os.File, error) {
file, err := os.OpenFile(p.Absolute(), os.O_RDONLY, 0444) file, err := os.OpenFile(p.Absolute(), os.O_RDONLY, 0444)
if err != nil { if err != nil {
return nil, ErrFileOpen.Wrap(err) return nil, errors.With(err).WrapWithin(ErrFileOpen)
} }
return file, nil return file, nil
} }
// StatFile performs a file stat on a file at path // StatFile performs a file stat on a file at path
func StatFile(p *Path) (os.FileInfo, errors.Error) { func StatFile(p *Path) (os.FileInfo, error) {
stat, err := os.Stat(p.Absolute()) stat, err := os.Stat(p.Absolute())
if err != nil { if err != nil {
return nil, ErrFileStat.Wrap(err) return nil, errors.With(err).WrapWithin(ErrFileStat)
} }
return stat, nil return stat, nil
} }
// ReadFile reads a supplied file descriptor into a return byte slice, or error // ReadFile reads a supplied file descriptor into a return byte slice, or error
func ReadFile(file *os.File) ([]byte, errors.Error) { func ReadFile(file *os.File) ([]byte, error) {
// Get read buffers, defer putting back // Get read buffers, defer putting back
br := fileBufferedReaderPool.Get(file) br := fileBufferedReaderPool.Get(file)
defer fileBufferedReaderPool.Put(br) defer fileBufferedReaderPool.Put(br)
@ -37,13 +37,13 @@ func ReadFile(file *os.File) ([]byte, errors.Error) {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
_, err := br.WriteTo(buf) _, err := br.WriteTo(buf)
if err != nil { if err != nil {
return nil, ErrFileRead.Wrap(err) return nil, errors.With(err).WrapWithin(ErrFileRead)
} }
return buf.Bytes(), nil return buf.Bytes(), nil
} }
// ScanFile scans a supplied file at file descriptor, using iterator function // ScanFile scans a supplied file at file descriptor, using iterator function
func ScanFile(file *os.File, iterator func(string) bool) errors.Error { func ScanFile(file *os.File, iterator func(string) bool) error {
// Get read buffer, defer putting back // Get read buffer, defer putting back
br := fileBufferedReaderPool.Get(file) br := fileBufferedReaderPool.Get(file)
defer fileBufferedReaderPool.Put(br) defer fileBufferedReaderPool.Put(br)
@ -60,7 +60,7 @@ func ScanFile(file *os.File, iterator func(string) bool) errors.Error {
break break
} else { } else {
// Bad error, return // Bad error, return
return ErrFileRead.Wrap(err) return errors.With(err).WrapWithin(ErrFileRead)
} }
} }
@ -76,10 +76,10 @@ func ScanFile(file *os.File, iterator func(string) bool) errors.Error {
} }
// ScanDirectory reads the contents of a directory and performs the iterator function on each os.FileInfo entry returned // ScanDirectory reads the contents of a directory and performs the iterator function on each os.FileInfo entry returned
func ScanDirectory(dir *os.File, p *Path, iterator func(os.FileInfo, *Path)) errors.Error { func ScanDirectory(dir *os.File, p *Path, iterator func(os.FileInfo, *Path)) error {
nameList, err := dir.Readdirnames(-1) nameList, err := dir.Readdirnames(-1)
if err != nil { if err != nil {
return ErrDirectoryRead.Wrap(err) return errors.With(err).WrapWithin(ErrDirectoryRead)
} }
// Sort by name // Sort by name

@ -17,10 +17,10 @@ func NewListener(l net.Listener) *Listener {
} }
// Accept accepts a new connection and returns a client, or error // Accept accepts a new connection and returns a client, or error
func (l *Listener) Accept() (*Client, errors.Error) { func (l *Listener) Accept() (*Client, error) {
conn, err := l.l.Accept() conn, err := l.l.Accept()
if err != nil { if err != nil {
return nil, ErrListenerAccept.Wrap(err) return nil, errors.With(err).WrapWithin(ErrListenerAccept)
} }
return NewClient(conn), nil return NewClient(conn), nil
} }

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

@ -14,7 +14,7 @@ import (
const ( const (
// Version holds the current version string // Version holds the current version string
Version = "v3.1.8" Version = "v3.2.0-beta"
) )
var ( var (
@ -91,23 +91,23 @@ var (
// Global client-handling filesystem functions // Global client-handling filesystem functions
newFileContent func(*Path) FileContent newFileContent func(*Path) FileContent
handleDirectory func(*Client, *os.File, *Path) errors.Error handleDirectory func(*Client, *os.File, *Path) error
handleLargeFile func(*Client, *os.File, *Path) errors.Error handleLargeFile func(*Client, *os.File, *Path) error
) )
// Start begins operation of the server // Start begins operation of the server
func Start(serve func(*Client)) { func Start(serve func(*Client)) {
// Start the FileCache freshness monitor // Start the FileCache freshness monitor
SystemLog.Infof(cacheMonitorStartStr, monitorSleepTime) SystemLog.Infof("Starting cache monitor with freq: %s", monitorSleepTime.String())
go FileCache.StartMonitor(monitorSleepTime) go FileCache.StartMonitor(monitorSleepTime)
// Start the listener // 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() { go func() {
for { for {
client, err := serverListener.Accept() client, err := serverListener.Accept()
if err != nil { if err != nil {
SystemLog.Errorf(err.Error()) SystemLog.Error(err.Error())
} }
// Serve client then close in separate goroutine // Serve client then close in separate goroutine
@ -120,20 +120,20 @@ func Start(serve func(*Client)) {
// Listen for OS signals and terminate if necessary // Listen for OS signals and terminate if necessary
sig := <-sigChannel sig := <-sigChannel
SystemLog.Infof(signalReceivedStr, sig) SystemLog.Infof("Signal received: %s. Shutting down...", sig.String())
os.Exit(0) os.Exit(0)
} }
// HandleClient handles a Client, attempting to serve their request from the filesystem whether a regular file, gophermap, dir listing or CGI script // HandleClient handles a Client, attempting to serve their request from the filesystem whether a regular file, gophermap, dir listing or CGI script
func HandleClient(client *Client, request *Request) errors.Error { func HandleClient(client *Client, request *Request) error {
// If restricted, return error // If restricted, return error
if IsRestrictedPath(request.Path()) { if IsRestrictedPath(request.Path()) {
return ErrRestrictedPath return ErrRestrictedPath.Extend(request.Path().Selector())
} }
// Try remap if necessary. If remapped, log! // Try remap if necessary. If remapped, log!
if ok := RemapRequest(request); ok { 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! // If within CGI dir, attempt to execute this!
@ -163,9 +163,9 @@ func HandleClient(client *Client, request *Request) errors.Error {
defer file.Close() defer file.Close()
// Get stat // Get stat
stat, goErr := file.Stat() stat, err := file.Stat()
if err != nil { if err != nil {
return ErrFileStat.Wrap(goErr) return errors.With(err).WrapWithin(ErrFileStat)
} }
switch { switch {
@ -184,7 +184,7 @@ func HandleClient(client *Client, request *Request) errors.Error {
} }
// FetchFile attempts to fetch a file from the cache, using the supplied file stat, Path and serving client. Returns Error status // FetchFile attempts to fetch a file from the cache, using the supplied file stat, Path and serving client. Returns Error status
func FetchFile(client *Client, file *os.File, stat os.FileInfo, p *Path) errors.Error { func FetchFile(client *Client, file *os.File, stat os.FileInfo, p *Path) error {
// If file too big, write direct to client // If file too big, write direct to client
if stat.Size() > fileSizeMax { if stat.Size() > fileSizeMax {
return handleLargeFile(client, file, p) return handleLargeFile(client, file, p)

@ -1,85 +0,0 @@
package core
// Error string constants
const (
connWriteErrStr = "Conn write error"
connReadErrStr = "Conn read error"
connCloseErrStr = "Conn close error"
listenerBeginErrStr = "Listener begin 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"
)

@ -17,7 +17,7 @@ func HasAsciiControlBytes(raw string) bool {
} }
// ParseScheme attempts to parse a scheme from a raw url // ParseScheme attempts to parse a scheme from a raw url
func ParseScheme(raw string) (string, string, errors.Error) { func ParseScheme(raw string) (string, string, error) {
// If first char is: // If first char is:
// - valid ascii (but non-scheme), return here no errors // - valid ascii (but non-scheme), return here no errors
// - end of scheme char, return bad request error // - end of scheme char, return bad request error
@ -26,7 +26,7 @@ func ParseScheme(raw string) (string, string, errors.Error) {
case ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'): case ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'):
// All good, continue // All good, continue
case c == ':': case c == ':':
return "", "", ErrInvalidRequest return "", "", ErrParsingScheme.Extend(raw)
default: default:
// Invalid scheme char (or scheme first-char) return // Invalid scheme char (or scheme first-char) return
return "", raw, nil return "", raw, nil
@ -104,12 +104,15 @@ func shouldHostEscape(b byte) bool {
func shouldPathEscape(b byte) bool { func shouldPathEscape(b byte) bool {
switch b { switch b {
// Reserved character in path // Reserved character in path.
case '?': // Bear in mind ;, ARE allowed in a URL path,
// but when converting from a filesystem-->URL path
// (how this will be used), it will need escaping.
case '?', ';', ',':
return true return true
// Allowed in path // Allowed in path
case '$', '&', '+', ',', '/', ':', ';', '=', '@': case '$', '&', '+', '/', ':', '=', '@':
return false return false
// Check all-else // Check all-else
@ -136,7 +139,7 @@ func unescape(raw string, count int) string {
return t.String() return t.String()
} }
func unescapeHost(raw string) (string, errors.Error) { func unescapeHost(raw string) (string, error) {
// Count all the percent signs // Count all the percent signs
count := 0 count := 0
for i := 0; i < len(raw); { for i := 0; i < len(raw); {
@ -147,14 +150,14 @@ func unescapeHost(raw string) (string, errors.Error) {
// If not a valid % encoded hex value, return with error // If not a valid % encoded hex value, return with error
if i+2 >= len(raw) || !isHex(raw[i+1]) || !isHex(raw[i+2]) { if i+2 >= len(raw) || !isHex(raw[i+1]) || !isHex(raw[i+2]) {
return "", ErrInvalidRequest.Extend("unescaping host info " + raw) return "", ErrUnescapingHost.Extend(raw)
} }
// In the host component % encoding can only be used // In the host component % encoding can only be used
// for non-ASCII bytes. And rfc6874 introduces %25 for // for non-ASCII bytes. And rfc6874 introduces %25 for
// escaped percent sign in IPv6 literals // escaped percent sign in IPv6 literals
if unHex(raw[i+1]) < 8 && raw[i:i+3] != "%25" { if unHex(raw[i+1]) < 8 && raw[i:i+3] != "%25" {
return "", ErrInvalidRequest.Extend("unescaping host " + raw) return "", ErrUnescapingHost.Extend(raw)
} }
// Skip iteration past the // Skip iteration past the
@ -163,7 +166,7 @@ func unescapeHost(raw string) (string, errors.Error) {
default: default:
// If within ASCII range, and shoud be escaped, return error // If within ASCII range, and shoud be escaped, return error
if raw[i] < 0x80 && shouldHostEscape(raw[i]) { if raw[i] < 0x80 && shouldHostEscape(raw[i]) {
return "", ErrInvalidRequest.Extend("unescaping host " + raw) return "", ErrUnescapingHost.Extend(raw)
} }
// Iter // Iter
@ -178,7 +181,7 @@ func unescapeHost(raw string) (string, errors.Error) {
return unescape(raw, count), nil return unescape(raw, count), nil
} }
func unescapePath(raw string) (string, errors.Error) { func unescapePath(raw string) (string, error) {
// Count all the percent signs // Count all the percent signs
count := 0 count := 0
length := len(raw) length := len(raw)
@ -190,7 +193,7 @@ func unescapePath(raw string) (string, errors.Error) {
// If not a valid % encoded hex value, return with error // If not a valid % encoded hex value, return with error
if i+2 >= length || !isHex(raw[i+1]) || !isHex(raw[i+2]) { if i+2 >= length || !isHex(raw[i+1]) || !isHex(raw[i+2]) {
return "", ErrInvalidRequest.Extend("unescaping path " + raw) return "", ErrUnescapingPath.Extend(raw)
} }
// Skip iteration past the // Skip iteration past the
@ -240,11 +243,11 @@ func EscapePath(path string) string {
} }
// ParseEncodedHost parses encoded host info, safely returning unescape host and port // ParseEncodedHost parses encoded host info, safely returning unescape host and port
func ParseEncodedHost(raw string) (string, string, errors.Error) { func ParseEncodedHost(raw string) (string, string, error) {
// Unescape the host info // Unescape the host info
raw, err := unescapeHost(raw) raw, err := unescapeHost(raw)
if err != nil { if err != nil {
return "", "", err return "", "", err.(errors.Error).WrapWithin(ErrParsingHost)
} }
// Split by last ':' and return // Split by last ':' and return
@ -253,14 +256,14 @@ func ParseEncodedHost(raw string) (string, string, errors.Error) {
} }
// ParseEncodedURI parses encoded URI, safely returning unescaped path and still-escaped query // ParseEncodedURI parses encoded URI, safely returning unescaped path and still-escaped query
func ParseEncodedURI(received string) (string, string, errors.Error) { func ParseEncodedURI(received string) (string, string, error) {
// Split into path and query // Split into path and query
rawPath, query := SplitBy(received, "?") rawPath, query := SplitBy(received, "?")
// Unescape path, query is up-to CGI scripts // Unescape path, query is up-to CGI scripts
rawPath, err := unescapePath(rawPath) rawPath, err := unescapePath(rawPath)
if err != nil { if err != nil {
return "", "", ErrInvalidRequest.Wrap(err) return "", "", err.(errors.Error).WrapWithin(ErrParsingURI)
} }
// Return the raw path and query // Return the raw path and query

@ -0,0 +1,67 @@
package core_test
import (
"fmt"
"gophi/core"
"io/ioutil"
"net/url"
"testing"
)
var toEscape = []string{
"",
"abc",
"abc+def",
"a?b",
"one two",
"10%",
" ?&=#+%!<>#\"{}|\\^[]`☺\t:@$'()*,;",
}
var escaped = []string{
"",
"abc",
"abc+def",
"a%3Fb",
"one%20two",
"10%25",
"%20%3F&=%23+%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09:@$%27%28%29%2A%2C%3B",
}
func TestPathEscape(t *testing.T) {
for i, path := range toEscape {
if escapedPath := core.EscapePath(path); escapedPath != escaped[i] {
t.Fatalf("Failed escaping path!\nGot: %s\nExpected: %s\n", escapedPath, escaped[i])
}
}
}
func TestParseEncodedHost(t *testing.T) {
}
func TestParseEncodedURI(t *testing.T) {
}
func BenchmarkCorePathEscape(b *testing.B) {
var s string
for i := 0; i < b.N; i++ {
for _, path := range toEscape {
s = core.EscapePath(path)
}
}
fmt.Fprint(ioutil.Discard, s)
}
func BenchmarkURLPathEscape(b *testing.B) {
var s string
for i := 0; i < b.N; i++ {
for _, path := range toEscape {
// This isn't *exactly* a fair comparison to core.EscapePath,
// since url.PathEscape() escapes a path _segment_ as opposed
// to any entire path, whereas core.EscapePath() escapes an entire
// filesystem path.
s = url.PathEscape(path)
}
}
fmt.Fprint(ioutil.Discard, s)
}

@ -8,51 +8,88 @@ import (
// Gemini specific base errors // Gemini specific base errors
var ( var (
errInvalidTLSConfig = errors.New(invalidTLSConfigErrStr) errInvalidScheme = errors.BaseError("invalid request scheme")
errInvalidProtocol = errors.New(inavlidProtocolErrStr) errProxyRequest = errors.BaseError("host:port pair differ from our own")
errInvalidHostPort = errors.New(invalidHostPortErrStr) )
// 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 // generateErrorResponse takes an error code and generates an error response byte slice
func generateErrorResponse(err errors.Error) ([]byte, bool) { func generateErrorResponse(err error) ([]byte, bool) {
switch { switch {
case err.Equals(core.ErrConnWrite): case errors.Is(err, core.ErrConnWrite):
return nil, false // no point responding if we couldn't write return nil, false // no point responding if we couldn't write
case err.Equals(core.ErrConnRead): case errors.Is(err, core.ErrConnRead):
return buildErrorResponse("40", statusMeta40), true return errConnReadRsp, true
case err.Equals(core.ErrConnClose): case errors.Is(err, core.ErrConnClose):
return nil, false // no point responding if we couldn't close return nil, false // no point responding if we couldn't close
case err.Equals(core.ErrMutexUpgrade): case errors.Is(err, core.ErrMutexUpgrade):
return buildErrorResponse("40", statusMeta40), true return errTemporaryFailureRsp, true
case err.Equals(core.ErrMutexDowngrade): case errors.Is(err, core.ErrMutexDowngrade):
return buildErrorResponse("40", statusMeta40), true return errTemporaryFailureRsp, true
case err.Equals(core.ErrFileOpen): case errors.Is(err, core.ErrFileOpen):
return buildErrorResponse("51", statusMeta51), true return errNotFoundRsp, true
case err.Equals(core.ErrFileStat): case errors.Is(err, core.ErrFileStat):
return buildErrorResponse("51", statusMeta51), true return errNotFoundRsp, true
case err.Equals(core.ErrFileRead): case errors.Is(err, core.ErrFileRead):
return buildErrorResponse("51", statusMeta51), true return errNotFoundRsp, true
case err.Equals(core.ErrFileType): case errors.Is(err, core.ErrFileType):
return buildErrorResponse("51", statusMeta51), true return errNotFoundRsp, true
case err.Equals(core.ErrDirectoryRead): case errors.Is(err, core.ErrDirectoryRead):
return buildErrorResponse("51", statusMeta51), true return errNotFoundRsp, true
case err.Equals(core.ErrRestrictedPath): case errors.Is(err, core.ErrRestrictedPath):
return buildErrorResponse("51", statusMeta51), true return errRestrictedRsp, true
case err.Equals(core.ErrInvalidRequest): case errors.Is(err, core.ErrInvalidRequest):
return buildErrorResponse("59", statusMeta59), true return errInvalidRequestRsp, true
case err.Equals(core.ErrCGIStart): case errors.Is(err, core.ErrParsingScheme):
return buildErrorResponse("42", statusMeta42), true return errInvalidRequestRsp, true
case err.Equals(core.ErrCGIExitCode): case errors.Is(err, core.ErrParsingHost):
return buildErrorResponse("42", statusMeta42), true return errInvalidRequestRsp, true
case err.Equals(errInvalidProtocol): case errors.Is(err, core.ErrParsingURI):
return buildErrorResponse("53", statusMeta59), true return errInvalidRequestRsp, true
case err.Equals(errInvalidHostPort): case errors.Is(err, core.ErrCGIStart):
return buildErrorResponse("53", statusMeta53), true return errPermanentFailureRsp, true
case errors.Is(err, core.ErrCGIExitCode):
return errTemporaryFailureRsp, true
case errors.Is(err, errInvalidScheme):
return errInvalidSchemeRsp, true
case errors.Is(err, errProxyRequest):
return errProxyRequestRsp, true
default: default:
return nil, false return nil, false
} }
} }
func buildErrorResponse(statusCode, statusMeta string) []byte {
return buildResponseHeader(statusCode, statusMeta)
}

@ -3,8 +3,6 @@ package gemini
import ( import (
"gophi/core" "gophi/core"
"os" "os"
"github.com/grufwub/go-errors"
) )
type headerPlusFileContent struct { type headerPlusFileContent struct {
@ -12,12 +10,12 @@ type headerPlusFileContent struct {
} }
// WriteToClient writes the current contents of FileContents to the client // WriteToClient writes the current contents of FileContents to the client
func (fc *headerPlusFileContent) WriteToClient(client *core.Client, p *core.Path) errors.Error { func (fc *headerPlusFileContent) WriteToClient(client *core.Client, p *core.Path) error {
return client.Conn().Write(fc.contents) return client.Conn().Write(fc.contents)
} }
// Load takes an open FD and loads the file contents into FileContents memory // Load takes an open FD and loads the file contents into FileContents memory
func (fc *headerPlusFileContent) Load(p *core.Path, file *os.File) errors.Error { func (fc *headerPlusFileContent) Load(p *core.Path, file *os.File) error {
// Read the file contents // Read the file contents
contents, err := core.ReadFile(file) contents, err := core.ReadFile(file)
if err != nil { if err != nil {

@ -7,7 +7,6 @@ import (
"io" "io"
"github.com/grufwub/go-config" "github.com/grufwub/go-config"
"github.com/grufwub/go-errors"
"github.com/grufwub/go-logger" "github.com/grufwub/go-logger"
) )
@ -16,7 +15,7 @@ func init() {
b := make([]byte, 1) b := make([]byte, 1)
_, err := io.ReadFull(rand.Reader, b) _, err := io.ReadFull(rand.Reader, b)
if err != nil { if err != nil {
logger.Fatal(entropyAssertFailStr) logger.Fatal("Failed to assert safe source of system entropy exists!")
} }
} }
@ -32,11 +31,11 @@ func Run() {
tree, tree,
"gemini", "gemini",
1965, 1965,
func() (*core.Listener, errors.Error) { func() (*core.Listener, error) {
// Load the supplied key pair // Load the supplied key pair
cert, err := tls.LoadX509KeyPair(*certFile, *keyFile) cert, err := tls.LoadX509KeyPair(*certFile, *keyFile)
if err != nil { if err != nil {
return nil, errInvalidTLSConfig.Wrap(err) return nil, err
} }
// Create TLS config // Create TLS config
@ -48,7 +47,7 @@ func Run() {
// Create listener! // Create listener!
l, err := tls.Listen("tcp", core.Bind+":"+core.Port, config) l, err := tls.Listen("tcp", core.Bind+":"+core.Port, config)
if err != nil { if err != nil {
return nil, core.ErrListenerBegin.Wrap(err) return nil, err
} }
// Return wrapper listener // Return wrapper listener
@ -61,6 +60,7 @@ func Run() {
) )
// Generate the root redirect byte slice // 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 + "/") rootRedirect = buildRedirect("gemini://" + core.Hostname + ":" + core.Port + "/")
// Start! // Start!

@ -4,8 +4,6 @@ import (
"gophi/core" "gophi/core"
"os" "os"
"strings" "strings"
"github.com/grufwub/go-errors"
) )
// rootRedirectHeader stores the root redirect header byte slice, // rootRedirectHeader stores the root redirect header byte slice,
@ -17,7 +15,7 @@ func serve(client *core.Client) {
// Receive line from client // Receive line from client
received, err := client.Conn().ReadLine() received, err := client.Conn().ReadLine()
if err != nil { if err != nil {
client.LogError(clientReadFailStr) client.LogError("Conn read fail")
handleError(client, err) handleError(client, err)
return return
} }
@ -25,27 +23,25 @@ func serve(client *core.Client) {
// Ensure is a valid URL string // Ensure is a valid URL string
if core.HasAsciiControlBytes(raw) { if core.HasAsciiControlBytes(raw) {
client.LogError(invalidRequestStr, raw) client.LogError("Invalid request: %s", raw)
handleError(client, core.ErrInvalidRequest) handleError(client, core.ErrInvalidRequest.Extend("has ascii control bytes"))
return return
} }
// Get the URL scheme (or error!) // Get the URL scheme (or error!)
scheme, path, err := core.ParseScheme(raw) scheme, path, err := core.ParseScheme(raw)
if err != nil { if err != nil {
client.LogError(invalidRequestStr, raw) client.LogError("Invalid request: %s", raw)
handleError(client, err) handleError(client, err)
return return
} }
// Infer no schema as 'gemini', else check we // Infer no schema as 'gemini', else check we
// were explicitly provided 'gemini' // were explicitly provided 'gemini'
if scheme != "" { if scheme != "" && scheme != "gemini" {
if scheme != "gemini" { client.LogError("Invalid request: %s", raw)
client.LogError(invalidRequestStr, raw) handleError(client, errInvalidScheme.Extend(scheme))
handleError(client, errInvalidProtocol.Extend(scheme)) return
return
}
} }
// Split by first '/' (with prefix '//' trimmed) to get host info and path strings // 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 // Parse the URL encoded host info
host, port, err := core.ParseEncodedHost(host) host, port, err := core.ParseEncodedHost(host)
if err != nil { if err != nil {
client.LogError(invalidRequestStr, raw) client.LogError("Invalid request: %s", raw)
handleError(client, err) handleError(client, err)
return return
} }
// Check the host and port are our own (empty port is allowed) // Check the host and port are our own (empty port is allowed)
if host != core.Hostname || (port != "" && port != core.Port) { if host != core.Hostname || (port != "" && port != core.Port) {
client.LogError(invalidRequestStr, raw) client.LogError("Invalid request: %s", raw)
handleError(client, errInvalidHostPort.Extend(host+":"+port)) handleError(client, errProxyRequest.Extend(host+":"+port))
return return
} }
// Parse the encoded URI into path and query components // Parse the encoded URI into path and query components
path, query, err := core.ParseEncodedURI(path) path, query, err := core.ParseEncodedURI(path)
if err != nil { if err != nil {
client.LogError(invalidRequestStr, raw) client.LogError("Invalid request: %s", raw)
handleError(client, err) handleError(client, err)
return return
} }
// Redirect empty path to root // Redirect empty path to root
if len(path) < 1 { if len(path) < 1 {
client.LogInfo(clientRedirectStr, "/") client.LogInfo("Redirect to: /")
client.Conn().Write(rootRedirect) client.Conn().Write(rootRedirect)
return return
} }
@ -88,37 +84,37 @@ func serve(client *core.Client) {
err = core.HandleClient(client, request) err = core.HandleClient(client, request)
if err != nil { if err != nil {
handleError(client, err) handleError(client, err)
client.LogError(clientServeFailStr, request.String()) client.LogError("Failed to serve: %s", request.String())
} else { } else {
client.LogInfo(clientServedStr, request.String()) client.LogInfo("Served: %s", request.String())
} }
} }
// handleError determines whether to send an error response to the client, and logs to system // handleError determines whether to send an error response to the client, and logs to system
func handleError(client *core.Client, err errors.Error) { func handleError(client *core.Client, err error) {
response, ok := generateErrorResponse(err) response, ok := generateErrorResponse(err)
if ok { if ok {
client.Conn().Write(response) 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) errors.Error { func handleDirectory(client *core.Client, file *os.File, p *core.Path) error {
// First check for index gem, create gem Path object // First check for index gem, create gem Path object
indexGem := p.JoinPathUnsafe("index.gmi") indexGem := p.JoinPathUnsafe("index.gmi")
// If index gem exists, we fetch this // If index gem exists, we fetch this
fd2, err := core.OpenFile(indexGem) file2, err := core.OpenFile(indexGem)
if err == nil { if err == nil {
stat, osErr := fd2.Stat() stat, err := file2.Stat()
if osErr == nil { if err == nil {
// Fetch gem and defer close // Fetch gem and defer close
defer fd2.Close() defer file2.Close()
return core.FetchFile(client, fd2, stat, indexGem) return core.FetchFile(client, file2, stat, indexGem)
} }
// Else, just close fd2 // Else, just close fd2
fd2.Close() file2.Close()
} }
// Slice to write // Slice to write
@ -159,7 +155,7 @@ func handleDirectory(client *core.Client, file *os.File, p *core.Path) errors.Er
return client.Conn().Write(append(header, []byte(dirContents)...)) return client.Conn().Write(append(header, []byte(dirContents)...))
} }
func handleLargeFile(client *core.Client, file *os.File, p *core.Path) errors.Error { func handleLargeFile(client *core.Client, file *os.File, p *core.Path) error {
// Build the response header // Build the response header
header := buildResponseHeader("20", getFileStatusMeta(p)) header := buildResponseHeader("20", getFileStatusMeta(p))

@ -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"
)

@ -5,7 +5,7 @@ go 1.15
require ( require (
github.com/grufwub/go-bufpools v0.1.1 github.com/grufwub/go-bufpools v0.1.1
github.com/grufwub/go-config v0.1.0 github.com/grufwub/go-config v0.1.0
github.com/grufwub/go-errors v0.1.0 github.com/grufwub/go-errors v0.3.1
github.com/grufwub/go-filecache v0.1.0 github.com/grufwub/go-filecache v0.1.0
github.com/grufwub/go-logger v0.1.1 github.com/grufwub/go-logger v0.1.1
) )

@ -4,8 +4,8 @@ github.com/grufwub/go-bufpools v0.1.1 h1:TOUKNY+UaQ784EtvP+wKoXssYQQRX5zewtrZ7u4
github.com/grufwub/go-bufpools v0.1.1/go.mod h1:ITqLRtG+W1bZHGdkWewV7inb+GcWfq2Jcjqx4AZ7aBY= github.com/grufwub/go-bufpools v0.1.1/go.mod h1:ITqLRtG+W1bZHGdkWewV7inb+GcWfq2Jcjqx4AZ7aBY=
github.com/grufwub/go-config v0.1.0 h1:/UDEmprs4h4qEkgmQqthmtGZeJs8eB44qMVSqa+5sxU= github.com/grufwub/go-config v0.1.0 h1:/UDEmprs4h4qEkgmQqthmtGZeJs8eB44qMVSqa+5sxU=
github.com/grufwub/go-config v0.1.0/go.mod h1:0U5Y0EkNeL09YkY70fNZv4Kelfayp/VroEs2UzmUG04= github.com/grufwub/go-config v0.1.0/go.mod h1:0U5Y0EkNeL09YkY70fNZv4Kelfayp/VroEs2UzmUG04=
github.com/grufwub/go-errors v0.1.0 h1:1gNPO4zzVDS88Kgtd60cBs7ZJiWy1hjjP6bFG6Ik+t8= github.com/grufwub/go-errors v0.3.1 h1:Q0N5njkkFgAquX26MmNctonYD86htRly8yEZyWIhF+E=
github.com/grufwub/go-errors v0.1.0/go.mod h1:AXGtU2fWv8ejaUUT0+9wTOlWqcxYDo8wuYnhrYtoBKM= github.com/grufwub/go-errors v0.3.1/go.mod h1:AXGtU2fWv8ejaUUT0+9wTOlWqcxYDo8wuYnhrYtoBKM=
github.com/grufwub/go-filecache v0.1.0 h1:OugzIHzLco8LLRnAlD7m6zSFQTILjltNO8Hhr/8vcCo= github.com/grufwub/go-filecache v0.1.0 h1:OugzIHzLco8LLRnAlD7m6zSFQTILjltNO8Hhr/8vcCo=
github.com/grufwub/go-filecache v0.1.0/go.mod h1:iAfqEfsC5YsyGD+f8JducuWeRqCDBVPi1+VmCaPL07Q= github.com/grufwub/go-filecache v0.1.0/go.mod h1:iAfqEfsC5YsyGD+f8JducuWeRqCDBVPi1+VmCaPL07Q=
github.com/grufwub/go-logger v0.1.1 h1:KnD6NNyeq3cz6dZKW/Gr+Fz9dNvkLf8KvYZXKGM5cN0= github.com/grufwub/go-logger v0.1.1 h1:KnD6NNyeq3cz6dZKW/Gr+Fz9dNvkLf8KvYZXKGM5cN0=

@ -8,47 +8,52 @@ import (
// Gopher specific error codes // Gopher specific error codes
var ( var (
errInvalidGophermap = errors.New(invalidGophermapErrStr) errInvalidGophermap = errors.Error(invalidGophermapErrStr)
errSubgophermapIsDir = errors.New(subgophermapIsDirErrStr) errSubgophermapIsDir = errors.Error(subgophermapIsDirErrStr)
errSubgophermapSize = errors.New(subgophermapSizeErrStr) errSubgophermapSize = errors.Error(subgophermapSizeErrStr)
) )
// generateErrorResponse takes an error code and generates an error response byte slice // generateErrorResponse takes an error code and generates an error response byte slice
func generateErrorResponse(err errors.Error) ([]byte, bool) { func generateErrorResponse(err error) ([]byte, bool) {
switch { switch {
case err.Equals(core.ErrConnWrite): case errors.Is(err, core.ErrConnWrite):
return nil, false // no point responding if we couldn't write return nil, false // no point responding if we couldn't write
case err.Equals(core.ErrConnRead): case errors.Is(err, core.ErrConnRead):
return buildErrorLine(errorResponse503), true return buildErrorLine(errorResponse503), true
case err.Equals(core.ErrConnClose): case errors.Is(err, core.ErrConnClose):
return nil, false // no point responding if we couldn't close return nil, false // no point responding if we couldn't close
case err.Equals(core.ErrMutexUpgrade): case errors.Is(err, core.ErrMutexUpgrade):
return buildErrorLine(errorResponse500), true return buildErrorLine(errorResponse500), true
case err.Equals(core.ErrMutexDowngrade): case errors.Is(err, core.ErrMutexDowngrade):
return buildErrorLine(errorResponse500), true return buildErrorLine(errorResponse500), true
case err.Equals(core.ErrFileOpen): case errors.Is(err, core.ErrFileOpen):
return buildErrorLine(errorResponse404), true return buildErrorLine(errorResponse404), true
case err.Equals(core.ErrFileStat): case errors.Is(err, core.ErrFileStat):
return buildErrorLine(errorResponse500), true return buildErrorLine(errorResponse500), true
case err.Equals(core.ErrFileRead): case errors.Is(err, core.ErrFileRead):
return buildErrorLine(errorResponse500), true return buildErrorLine(errorResponse500), true
case err.Equals(core.ErrFileType): case errors.Is(err, core.ErrFileType):
return buildErrorLine(errorResponse404), true return buildErrorLine(errorResponse404), true
case err.Equals(core.ErrDirectoryRead): case errors.Is(err, core.ErrDirectoryRead):
return buildErrorLine(errorResponse500), true return buildErrorLine(errorResponse500), true
case err.Equals(core.ErrRestrictedPath): case errors.Is(err, core.ErrRestrictedPath):
return buildErrorLine(errorResponse403), true return buildErrorLine(errorResponse403), true
case err.Equals(core.ErrInvalidRequest):
// All forms of invalid request
case errors.Is(err, core.ErrInvalidRequest):
return buildErrorLine(errorResponse400), true
case errors.Is(err, core.ErrParsingScheme):
return buildErrorLine(errorResponse400), true return buildErrorLine(errorResponse400), true
case err.Equals(core.ErrCGIStart):
case errors.Is(err, core.ErrCGIStart):
return buildErrorLine(errorResponse500), true return buildErrorLine(errorResponse500), true
case err.Equals(core.ErrCGIExitCode): case errors.Is(err, core.ErrCGIExitCode):
return buildErrorLine(errorResponse500), true return buildErrorLine(errorResponse500), true
case err.Equals(errInvalidGophermap): case errors.Is(err, errInvalidGophermap):
return buildErrorLine(errorResponse500), true return buildErrorLine(errorResponse500), true
case err.Equals(errSubgophermapIsDir): case errors.Is(err, errSubgophermapIsDir):
return buildErrorLine(errorResponse500), true return buildErrorLine(errorResponse500), true
case err.Equals(errSubgophermapSize): case errors.Is(err, errSubgophermapSize):
return buildErrorLine(errorResponse500), true return buildErrorLine(errorResponse500), true
default: default:
return nil, false return nil, false

@ -3,8 +3,6 @@ package gopher
import ( import (
"gophi/core" "gophi/core"
"os" "os"
"github.com/grufwub/go-errors"
) )
// generatedFileContents is a simple core.FileContent implementation for holding onto a generated (virtual) file contents // generatedFileContents is a simple core.FileContent implementation for holding onto a generated (virtual) file contents
@ -13,10 +11,10 @@ type generatedFileContent struct {
} }
// ReadAllFrom does nothing for generated content // ReadAllFrom does nothing for generated content
func (fc *generatedFileContent) Load(p *core.Path, file *os.File) errors.Error { return nil } func (fc *generatedFileContent) Load(p *core.Path, file *os.File) error { return nil }
// WriteAllTo writes the generated FileContent to client // WriteAllTo writes the generated FileContent to client
func (fc *generatedFileContent) WriteToClient(client *core.Client, p *core.Path) errors.Error { func (fc *generatedFileContent) WriteToClient(client *core.Client, p *core.Path) error {
return client.Conn().Write(fc.content) return client.Conn().Write(fc.content)
} }
@ -29,14 +27,14 @@ type gophermapContent struct {
} }
// Load takes an open FD and loads the gophermap contents into memory as different renderable sections // Load takes an open FD and loads the gophermap contents into memory as different renderable sections
func (gc *gophermapContent) Load(path *core.Path, file *os.File) errors.Error { func (gc *gophermapContent) Load(path *core.Path, file *os.File) error {
var err errors.Error var err error
gc.sections, err = readGophermap(file, path) gc.sections, err = readGophermap(file, path)
return err return err
} }
// WriteToClient renders each cached section of the gophermap, and writes them to the client // WriteToClient renders each cached section of the gophermap, and writes them to the client
func (gc *gophermapContent) WriteToClient(client *core.Client, path *core.Path) errors.Error { func (gc *gophermapContent) WriteToClient(client *core.Client, path *core.Path) error {
// Render + write the sections! // Render + write the sections!
for _, section := range gc.sections { for _, section := range gc.sections {
err := section.RenderAndWrite(client) err := section.RenderAndWrite(client)

@ -17,9 +17,9 @@ const (
// formatName formats a gopher line name string // formatName formats a gopher line name string
func formatName(name string) string { func formatName(name string) string {
if len(name) > pageWidth { if len(name) > pageWidth {
return name[:pageWidth-4] + "...\t" return name[:pageWidth-4] + "..."
} }
return name + "\t" return name
} }
// formatSelector formats a gopher line selector string // formatSelector formats a gopher line selector string
@ -27,7 +27,7 @@ func formatSelector(selector string) string {
if len(selector) > maxSelectorLen { if len(selector) > maxSelectorLen {
return errorSelector return errorSelector
} }
return selector return core.EscapePath(selector)
} }
// replacePlacementStrs replaces any placement strings found in the line (e.g $hostname, $port) // replacePlacementStrs replaces any placement strings found in the line (e.g $hostname, $port)
@ -48,12 +48,12 @@ func replacePlacementStrs(line string) string {
// buildLine builds a gopher line string // buildLine builds a gopher line string
func buildLine(t ItemType, name, selector, host, port string) []byte { func buildLine(t ItemType, name, selector, host, port string) []byte {
return []byte(string(t) + formatName(name) + formatSelector(selector) + "\t" + host + "\t" + port + "\r\n") return []byte(string(t) + formatName(name) + "\t" + formatSelector(selector) + "\t" + host + "\t" + port + "\r\n")
} }
// buildInfoLine builds a gopher info line string // buildInfoLine builds a gopher info line string
func buildInfoLine(line string) []byte { func buildInfoLine(line string) []byte {
return []byte(string(typeInfo) + formatName(line) + "\t" + nullHost + "\t" + nullPort + "\r\n") return []byte(string(typeInfo) + formatName(line) + "\t\t" + nullHost + "\t" + nullPort + "\r\n")
} }
// buildErrorLine builds a gopher error line string // buildErrorLine builds a gopher error line string

@ -3,17 +3,18 @@ package gopher
import ( import (
"gophi/core" "gophi/core"
"os" "os"
"strconv"
"github.com/grufwub/go-errors" "github.com/grufwub/go-errors"
) )
// GophermapSection is an interface that specifies individually renderable (and writeable) sections of a gophermap // GophermapSection is an interface that specifies individually renderable (and writeable) sections of a gophermap
type gophermapSection interface { type gophermapSection interface {
RenderAndWrite(*core.Client) errors.Error RenderAndWrite(*core.Client) error
} }
// readGophermap reads a FD and Path as gophermap sections // readGophermap reads a FD and Path as gophermap sections
func readGophermap(file *os.File, p *core.Path) ([]gophermapSection, errors.Error) { func readGophermap(file *os.File, p *core.Path) ([]gophermapSection, error) {
// Create return slice // Create return slice
sections := make([]gophermapSection, 0) sections := make([]gophermapSection, 0)
@ -23,7 +24,7 @@ func readGophermap(file *os.File, p *core.Path) ([]gophermapSection, errors.Erro
} }
// Declare variables // Declare variables
var returnErr errors.Error var returnErr error
titleAlready := false titleAlready := false
// Perform scan of gophermap FD // Perform scan of gophermap FD
@ -82,10 +83,10 @@ func readGophermap(file *os.File, p *core.Path) ([]gophermapSection, errors.Erro
// Get stat // Get stat
stat, err := subFile.Stat() stat, err := subFile.Stat()
if err != nil { if err != nil {
returnErr = core.ErrFileStat.Wrap(err) returnErr = errors.WrapError(core.ErrFileStat, err)
return false return false
} else if stat.IsDir() { } else if stat.IsDir() {
returnErr = errSubgophermapIsDir returnErr = errors.Wrap(request.Path().Absolute(), errSubgophermapIsDir)
return false return false
} }
@ -97,7 +98,7 @@ func readGophermap(file *os.File, p *core.Path) ([]gophermapSection, errors.Erro
// Error out if file too big // Error out if file too big
if stat.Size() > subgophermapSizeMax { if stat.Size() > subgophermapSizeMax {
returnErr = errSubgophermapSize returnErr = errors.Wrap(strconv.FormatInt(stat.Size(), 10), errSubgophermapSize)
return false return false
} }
@ -145,7 +146,7 @@ type TextSection struct {
} }
// RenderAndWrite simply writes the byte slice to the client // RenderAndWrite simply writes the byte slice to the client
func (s *TextSection) RenderAndWrite(client *core.Client) errors.Error { func (s *TextSection) RenderAndWrite(client *core.Client) error {
return client.Conn().Write(s.contents) return client.Conn().Write(s.contents)
} }
@ -156,7 +157,7 @@ type DirectorySection struct {
} }
// RenderAndWrite scans and renders a list of the contents of a directory (skipping hidden or restricted files) // RenderAndWrite scans and renders a list of the contents of a directory (skipping hidden or restricted files)
func (s *DirectorySection) RenderAndWrite(client *core.Client) errors.Error { func (s *DirectorySection) RenderAndWrite(client *core.Client) error {
file, err := core.OpenFile(s.path) file, err := core.OpenFile(s.path)
if err != nil { if err != nil {
return err return err
@ -190,7 +191,7 @@ type FileSection struct {
} }
// RenderAndWrite simply opens, reads and writes the file contents to the client // RenderAndWrite simply opens, reads and writes the file contents to the client
func (s *FileSection) RenderAndWrite(client *core.Client) errors.Error { func (s *FileSection) RenderAndWrite(client *core.Client) error {
// Open FD for the file // Open FD for the file
file, err := core.OpenFile(s.path) file, err := core.OpenFile(s.path)
if err != nil { if err != nil {
@ -222,7 +223,7 @@ type SubgophermapSection struct {
} }
// RenderAndWrite reads, renders and writes the contents of the gophermap to the client // RenderAndWrite reads, renders and writes the contents of the gophermap to the client
func (s *SubgophermapSection) RenderAndWrite(client *core.Client) errors.Error { func (s *SubgophermapSection) RenderAndWrite(client *core.Client) error {
// Get FD for gophermap // Get FD for gophermap
file, err := core.OpenFile(s.path) file, err := core.OpenFile(s.path)
if err != nil { if err != nil {
@ -252,6 +253,6 @@ type CGISection struct {
} }
// RenderAndWrite takes the request, and executes the associated CGI script with parameters // RenderAndWrite takes the request, and executes the associated CGI script with parameters
func (s *CGISection) RenderAndWrite(client *core.Client) errors.Error { func (s *CGISection) RenderAndWrite(client *core.Client) error {
return core.TryExecuteCGIScript(client, s.request) return core.TryExecuteCGIScript(client, s.request)
} }

@ -5,7 +5,6 @@ import (
"net" "net"
"github.com/grufwub/go-config" "github.com/grufwub/go-config"
"github.com/grufwub/go-errors"
"github.com/grufwub/go-filecache" "github.com/grufwub/go-filecache"
) )
@ -25,10 +24,10 @@ func Run() {
tree, tree,
"gopher", "gopher",
70, 70,
func() (*core.Listener, errors.Error) { func() (*core.Listener, error) {
l, err := net.Listen("tcp", core.Bind+":"+core.Port) l, err := net.Listen("tcp", core.Bind+":"+core.Port)
if err != nil { if err != nil {
return nil, core.ErrListenerBegin.Wrap(err) return nil, err
} }
return core.NewListener(l), nil return core.NewListener(l), nil
}, },

@ -4,8 +4,6 @@ import (
"gophi/core" "gophi/core"
"os" "os"
"strings" "strings"
"github.com/grufwub/go-errors"
) )
var ( var (
@ -69,7 +67,7 @@ func serve(client *core.Client) {
} }
} }
func handleDirectory(client *core.Client, file *os.File, p *core.Path) errors.Error { func handleDirectory(client *core.Client, file *os.File, p *core.Path) error {
// First check for gophermap, create gophermap Path object // First check for gophermap, create gophermap Path object
gophermap := p.JoinPathUnsafe("gophermap") gophermap := p.JoinPathUnsafe("gophermap")
@ -90,10 +88,13 @@ func handleDirectory(client *core.Client, file *os.File, p *core.Path) errors.Er
// Slice to write // Slice to write
dirContents := make([]byte, 0) dirContents := make([]byte, 0)
// Escape the previous dir
dirSel := core.EscapePath(p.SelectorDir())
// Add directory heading, empty line and a back line // Add directory heading, empty line and a back line
dirContents = append(dirContents, buildLine(typeInfo, "[ "+core.Hostname+p.Selector()+" ]", "TITLE", nullHost, nullPort)...) dirContents = append(dirContents, buildLine(typeInfo, "[ "+core.Hostname+p.Selector()+" ]", "TITLE", nullHost, nullPort)...)
dirContents = append(dirContents, buildInfoLine("")...) dirContents = append(dirContents, buildInfoLine("")...)
dirContents = append(dirContents, buildLine(typeDirectory, "..", p.SelectorDir(), core.Hostname, core.Port)...) dirContents = append(dirContents, buildLine(typeDirectory, "..", dirSel, core.Hostname, core.Port)...)
// Scan directory and build lines // Scan directory and build lines
err = core.ScanDirectory( err = core.ScanDirectory(
@ -113,12 +114,12 @@ func handleDirectory(client *core.Client, file *os.File, p *core.Path) errors.Er
return client.Conn().Write(dirContents) return client.Conn().Write(dirContents)
} }
func handleLargeFile(client *core.Client, file *os.File, p *core.Path) errors.Error { func handleLargeFile(client *core.Client, file *os.File, p *core.Path) error {
return client.Conn().ReadFrom(file) return client.Conn().ReadFrom(file)
} }
// handleError determines whether to send an error response to the client, and logs to system // handleError determines whether to send an error response to the client, and logs to system
func handleError(client *core.Client, err errors.Error) { func handleError(client *core.Client, err error) {
response, ok := generateErrorResponse(err) response, ok := generateErrorResponse(err)
if ok { if ok {
client.Conn().Write(response) client.Conn().Write(response)

Loading…
Cancel
Save