move requestpath contents into filesystemrequest, which is passed everywhere instead of individual variables

Signed-off-by: kim (grufwub) <grufwub@gmail.com>
master
kim (grufwub) 4 years ago
parent 14f181372d
commit 6cd6adecca

@ -12,20 +12,22 @@ import (
*/
type ServerConfig struct {
/* Executable Settings */
CgiEnv []string
Env []string
CgiEnv []string
/* Content settings */
FooterText []byte
PageWidth int
CharSet string
FooterText []byte
PageWidth int
/* Regex */
CmdParseLineRegex *regexp.Regexp
RestrictedFiles []*regexp.Regexp
CmdParseLineRegex *regexp.Regexp
RestrictedFiles []*regexp.Regexp
/* Logging */
SysLog LoggerInterface
AccLog LoggerInterface
SysLog LoggerInterface
AccLog LoggerInterface
/* Filesystem access */
FileSystem *FileSystem
FileSystem *FileSystem
}

@ -2,20 +2,28 @@ package main
import (
"net"
"strconv"
)
/* Data structure to hold specific host details */
type ConnHost struct {
/* Hold host specific details */
Name string
Port string
RootDir string
}
/* Simple wrapper to Listener that holds onto virtual
* host information and generates GophorConn
* instances on each accept
*/
type ConnClient struct {
/* Hold client specific details */
Ip string
Port string
}
type GophorListener struct {
/* Simple net.Listener wrapper that holds onto virtual
* host information + generates GophorConn instances
*/
Listener net.Listener
Host *ConnHost
}
@ -41,7 +49,17 @@ func (l *GophorListener) Accept() (*GophorConn, error) {
gophorConn := new(GophorConn)
gophorConn.Conn = conn
gophorConn.Host = &ConnHost{ l.Host.Name, l.Host.Port, l.Host.RootDir }
/* Copy over listener host */
gophorConn.Host = l.Host
/* Should always be ok as listener is type TCP (see above) */
addr, _ := conn.RemoteAddr().(*net.TCPAddr)
gophorConn.Client = &ConnClient{
addr.IP.String(),
strconv.Itoa(addr.Port),
}
return gophorConn, nil
}
@ -49,12 +67,12 @@ func (l *GophorListener) Addr() net.Addr {
return l.Listener.Addr()
}
/* Simple wrapper to Conn with easier acccess
* to hostname / port information
*/
type GophorConn struct {
Conn net.Conn
Host *ConnHost
/* Simple net.Conn wrapper with virtual host and client info */
Conn net.Conn
Host *ConnHost
Client *ConnClient
}
func (c *GophorConn) Read(b []byte) (int, error) {
@ -65,10 +83,6 @@ func (c *GophorConn) Write(b []byte) (int, error) {
return c.Conn.Write(b)
}
func (c *GophorConn) RemoteAddr() net.Addr {
return c.Conn.RemoteAddr()
}
func (c *GophorConn) Close() error {
return c.Conn.Close()
}

@ -29,6 +29,7 @@ const (
/* Executing */
BufferReadErr ErrorCode = iota
CommandStartErr ErrorCode = iota
CommandExitCodeErr ErrorCode = iota
/* Error Response Codes */
ErrorResponse200 ErrorResponseCode = iota
@ -85,6 +86,8 @@ func (e *GophorError) Error() string {
str = "buffer read fail"
case CommandStartErr:
str = "command start fail"
case CommandExitCodeErr:
str = "command exit code non-zero"
default:
str = "Unknown"
@ -133,6 +136,8 @@ func gophorErrorToResponseCode(code ErrorCode) ErrorResponseCode {
return ErrorResponse500
case CommandStartErr:
return ErrorResponse500
case CommandExitCodeErr:
return ErrorResponse500
default:
return ErrorResponse503

@ -4,76 +4,107 @@ import (
"os/exec"
"syscall"
"bytes"
"runtime"
"strconv"
"io"
)
type ExecType int
const (
SafeExecPath = ""
/* Executable type */
ExecTypeCgi ExecType = iota
ExecTypeRegular ExecType = iota
SafeExecPath = "/usr/bin:/bin"
ReaderBufSize = 1024
)
func setupInitialCgiEnviron(description string) []string {
func setupExecEnviron() []string {
return []string {
envKeyValue("PATH", SafeExecPath),
}
}
func setupInitialCgiEnviron() []string {
return []string{
/* RFC 3875 standard */
envKeyValue("GATEWAY_INTERFACE", "CGI/1.1"), /* MUST be set to the dialect of CGI being used by the server */
envKeyValue("SERVER_SOFTWARE", "gophor/"+GophorVersion), /* MUST be set to name and version of server software serving this request */
envKeyValue("SERVER_PROTOCOL", "RFC1436"), /* MUST be set to name and version of application protocol used for this request */
envKeyValue("CONTENT_LENGTH", "0"), /* Contains size of message-body attached (always 0 so we set here) */
envKeyValue("REQUEST_METHOD", "GET"), /* MUST be set to method by which script should process request. Always GET */
/* Non-standard */
envKeyValue("PATH", SafeExecPath),
envKeyValue("GATEWAY_INTERFACE", ""),
envKeyValue("SERVER_SOFTWARE", "gophor "+GophorVersion),
envKeyValue("SERVER_ARCH", runtime.GOARCH),
envKeyValue("SERVER_DESCRIPTION", description),
envKeyValue("SERVER_VERSION", GophorVersion),
envKeyValue("SERVER_PROTOCOL", "RFC1436"),
envKeyValue("COLUMNS", Config.PageWidth),
envKeyValue("COLUMNS", strconv.Itoa(Config.PageWidth)),
envKeyValue("GOPHER_CHARSET", Config.CharSet),
envKeyValue("SERVER_CODENAME", ""),
// envKeyValue("SERVER_ARCH", runtime.GOARCH),
// envKeyValue("SERVER_DESCRIPTION", description),
// envKeyValue("SERVER_VERSION", GophorVersion),
}
}
func executeFile(requestPath *RequestPath, args []string) ([]byte, *GophorError) {
func executeCgi(request *FileSystemRequest) ([]byte, *GophorError) {
/* Get initial CgiEnv variables */
cgiEnv := Config.CgiEnv
/* RFC 3875 standard */
cgiEnv = append(cgiEnv, envKeyValue("SERVER_NAME", request.Host.Name)) /* MUST be set to name of server host client is connecting to */
cgiEnv = append(cgiEnv, envKeyValue("SERVER_PORT", request.Host.Port)) /* MUST be set to the server port that client is connecting to */
cgiEnv = append(cgiEnv, envKeyValue("REMOTE_ADDR", request.Client.Ip)) /* Remote client addr, MUST be set */
cgiEnv = append(cgiEnv, envKeyValue("QUERY_STRING", request.Parameters[0])) /* URL encoded search or parameter string, MUST be set even if empty */
// cgiEnv = append(cgiEnv, envKeyValue("PATH_INFO", "")) /* Sub-resource to be fetched by script, derived from path hierarch portion of URI. NOT URL encoded */
cgiEnv = append(cgiEnv, envKeyValue("PATH_TRANSLATED", request.AbsPath())) /* Take PATH_INFO, parse as local URI and append root dir */
// cgiEnv = append(cgiEnv, envKeyValue("SCRIPT_NAME", "")) /* URI path (not URL encoded) which could identify the CGI script (rather than script's output) */
// cgiEnv = append(cgiEnv, envKeyValue("AUTH_TYPE", "")) /* Any method used my server to authenticate user, MUST be set if auth'd */
// cgiEnv = append(cgiEnv, envKeyValue("CONTENT_TYPE", "")) /* Only a MUST if HTTP content-type set (so never for gopher) */
// cgiEnv = append(cgiEnv, envKeyValue("REMOTE_IDENT", "")) /* Remote client identity information */
// cgiEnv = append(cgiEnv, envKeyValue("REMOTE_HOST", "")) /* Remote client domain name */
// cgiEnv = append(cgiEnv, envKeyValue("REMOTE_USER", "")) /* Remote user ID, if AUTH_TYPE, MUST be set */
/* Non-standard */
cgiEnv = append(cgiEnv, envKeyValue("SELECTOR", request.SelectorPath()))
// cgiEnv = append(cgiEnv, envKeyValue("LOCAL_ADDR", ""))
// cgiEnv = append(cgiEnv, envKeyValue("SCRIPT_FILENAME", ""))
cgiEnv = append(cgiEnv, envKeyValue("DOCUMENT_ROOT", request.RootDir))
// cgiEnv = append(cgiEnv, envKeyValue("GOPHER_FILETYPE", ""))
// cgiEnv = append(cgiEnv, envKeyValue("GOPHER_REFERER", ""))
// cgiEnv = append(cgiEnv, envKeyValue("TLS", "")
// cgiEnv = append(cgiEnv, envKeyValue("SERVER_TLS_PORT", ""),
// cgiEnv = append(cgiEnv, envKeyValue("SESSION_ID", ""))
// cgiEnv = append(cgiEnv, envKeyValue("SERVER_HOST", ""))
// cgiEnv = append(cgiEnv, envKeyValue("SEARCHREQUEST", ""))
return execute(cgiEnv, request.AbsPath(), nil)
}
func executeFile(request *FileSystemRequest) ([]byte, *GophorError) {
return execute(Config.Env, request.AbsPath(), request.Parameters)
}
func executeCommand(request *FileSystemRequest) ([]byte, *GophorError) {
return execute(Config.Env, request.AbsPath(), request.Parameters)
}
func execute(env []string, path string, args []string) ([]byte, *GophorError) {
/* Create stdout, stderr buffers */
outBuffer := &bytes.Buffer{}
errBuffer := &bytes.Buffer{}
/* Setup command */
var cmd *exec.Cmd
if args != nil {
cmd = exec.Command(requestPath.AbsolutePath(), args...)
cmd = exec.Command(path, args...)
} else {
cmd = exec.Command(requestPath.AbsolutePath())
cmd = exec.Command(path)
}
/* Setup remaining CGI spec environment values */
cmd.Env = Config.CgiEnv
/* RFC 1436 standard */
cmd.Env = append(cmd.Env, envKeyValue("CONTENT_LENGTH", ""),
cmd.Env = append(cmd.Env, envKeyValue("SERVER_NAME", ""))
cmd.Env = append(cmd.Env, envKeyValue("SERVER_PORT", ""))
cmd.Env = append(cmd.Env, envKeyValue("REQUEST_METHOD", ""))
cmd.Env = append(cmd.Env, envKeyValue("DOCUMENT_ROOT", ""))
cmd.Env = append(cmd.Env, envKeyValue("SCRIPT_NAME", ""))
cmd.Env = append(cmd.Env, envKeyValue("SCRIPT_FILENAME", ""))
cmd.Env = append(cmd.Env, envKeyValue("LOCAL_ADDR", ""))
cmd.Env = append(cmd.Env, envKeyValue("REMOTE_ADDR", ""))
cmd.Env = append(cmd.Env, envKeyValue("SESSION_ID", ""))
cmd.Env = append(cmd.Env, envKeyValue("GOPHER_FILETYPE", ""))
cmd.Env = append(cmd.Env, envKeyValue("GOPHER_REFERER", ""))
cmd.Env = append(cmd.Env, envKeyValue("SERVER_HOST", ""))
cmd.Env = append(cmd.Env, envKeyValue("REQUEST", ""))
cmd.Env = append(cmd.Env, envKeyValue("SEARCHREQUEST", ""))
cmd.Env = append(cmd.Env, envKeyValue("QUERY_STRING", ""))
// environ = append(environ, envKeyValue("TLS", "")
// environ = append(environ, envKeyValue("SERVER_TLS_PORT", ""),
// environ = append(environ, envKeyValue("HTTP_ACCEPT_CHARSET", "")
// environ = append(environ, envKeyValue("HTTP_REFERER", "")
/* Non-standard */
environ = append(environ, envKeyValue("SELECTOR", ""))
/* Setup cmd env */
cmd.Env = env
/* Set buffers*/
/* Setup out buffer */
cmd.Stdout = outBuffer
cmd.Stderr = errBuffer
/* Start executing! */
err := cmd.Start()
@ -100,20 +131,16 @@ func executeFile(requestPath *RequestPath, args []string) ([]byte, *GophorError)
}
if exitCode != 0 {
/* If non-zero exit code return error, print stderr to sys log */
errContents, gophorErr := readBuffer(errBuffer)
if gophorErr == nil {
/* Only print if we successfully fetched errContents */
Config.SysLog.Error("", "Error executing: %s %v\n%s\n", requestPath.AbsolutePath(), args, errContents)
}
return nil, &GophorError{ }
/* If non-zero exit code return error */
//errContents, gophorErr := readBuffer(errBuffer)
Config.SysLog.Error("", "Error executing: %s\n", cmd.String(), args)
return nil, &GophorError{ CommandExitCodeErr, err }
} else {
/* If zero exit code try return outContents and no error */
outContents, gophorErr := readBuffer(outBuffer)
if gophorErr != nil {
/* Failed fetching outContents, return error */
return nil, &GophorError{ }
return nil, gophorErr
}
return outContents, nil

@ -6,16 +6,22 @@ import (
"os"
)
/* GeneratedFileContents:
* The simplest implementation of FileContents that
* stores some bytes and does nothing else.
*/
type FileContents interface {
/* Interface that provides an adaptable implementation
* for holding onto some level of information about the
* contents of a file.
*/
Render(*FileSystemRequest) []byte
Load() *GophorError
Clear()
}
type GeneratedFileContents struct {
contents []byte
Contents []byte /* Generated file contents as byte slice */
}
func (fc *GeneratedFileContents) Render(request *FileSystemRequest) []byte {
return fc.contents
return fc.Contents
}
func (fc *GeneratedFileContents) Load() *GophorError {
@ -27,54 +33,36 @@ func (fc *GeneratedFileContents) Clear() {
/* do nothing */
}
/* RegularFileContents:
* Very simple implementation of FileContents that just
* buffered reads from the stored file path, stores the
* read bytes in a slice and returns when requested.
*/
type RegularFileContents struct {
path *RequestPath
contents []byte
Request *FileSystemRequest /* Stored filesystem request */
Contents []byte /* File contents as byte slice */
}
func (fc *RegularFileContents) Render(request *FileSystemRequest) []byte {
/* Here we can ignore the extra data in request.
* We are but a simple cache'd file
*/
return fc.contents
return fc.Contents
}
func (fc *RegularFileContents) Load() *GophorError {
/* Load the file into memory */
var gophorErr *GophorError
fc.contents, gophorErr = bufferedRead(fc.path.AbsolutePath())
fc.Contents, gophorErr = bufferedRead(fc.Request.AbsPath())
return gophorErr
}
func (fc *RegularFileContents) Clear() {
fc.contents = nil
fc.Contents = nil
}
/* GophermapContents:
* Implementation of FileContents that reads and
* parses a gophermap file into a slice of gophermap
* sections, then renders and returns these sections
* when requested.
*/
type GophermapContents struct {
path *RequestPath
sections []GophermapSection
Request *FileSystemRequest /* Stored filesystem request */
Sections []GophermapSection /* Slice to hold differing gophermap sections */
}
func (gc *GophermapContents) Render(request *FileSystemRequest) []byte {
returnContents := make([]byte, 0)
/* We don't just want to read the contents, each section
* in the sections slice needs a call to render() to
* perform their own required actions in producing a
* sendable byte slice.
*/
for _, line := range gc.sections {
/* Render each of the gophermap sections into byte slices */
for _, line := range gc.Sections {
content, gophorErr := line.Render(request)
if gophorErr != nil {
content = buildInfoLine(GophermapRenderErrorStr)
@ -90,82 +78,76 @@ func (gc *GophermapContents) Render(request *FileSystemRequest) []byte {
func (gc *GophermapContents) Load() *GophorError {
/* Load the gophermap into memory as gophermap sections */
var gophorErr *GophorError
gc.sections, gophorErr = readGophermap(gc.path)
gc.Sections, gophorErr = readGophermap(gc.Request)
return gophorErr
}
func (gc *GophermapContents) Clear() {
gc.sections = nil
gc.Sections = nil
}
/* GophermapSection:
* Provides an interface for different stored sections
* of a gophermap file, whether it's static text that we
* may want stored as-is, or the data required for a dir
* listing or command executed that we may want updated
* upon each file cache request.
*/
type GophermapSection interface {
/* Interface for storing differring types of gophermap
* sections and render when necessary
*/
Render(*FileSystemRequest) ([]byte, *GophorError)
}
/* GophermapText:
* Simple implementation of GophermapSection that holds
* onto a static section of text as a slice of bytes.
*/
type GophermapText struct {
Contents []byte
}
func NewGophermapText(contents []byte) *GophermapText {
return &GophermapText{ contents }
Contents []byte /* Text contents */
}
func (s *GophermapText) Render(request *FileSystemRequest) ([]byte, *GophorError) {
return replaceStrings(string(s.Contents), request.Host), nil
}
/* GophermapDirListing:
* An implementation of GophermapSection that holds onto a
* path and a requested list of hidden files, then enumerates
* the supplied paths (ignoring hidden files) when the content
* Render() call is received.
*/
type GophermapDirListing struct {
Path *RequestPath
Hidden map[string]bool
Request *FileSystemRequest /* Stored filesystem request */
Hidden map[string]bool /* Hidden files map parsed from gophermap */
}
func NewGophermapDirListing(path *RequestPath) *GophermapDirListing {
return &GophermapDirListing{ path, nil }
func (g *GophermapDirListing) Render(request *FileSystemRequest) ([]byte, *GophorError) {
/* Create new filesystem request from mixture of stored + supplied */
return listDir(
&FileSystemRequest{
request.Host,
request.Client,
g.Request.RootDir,
g.Request.RelPath(),
g.Request.AbsPath(),
g.Request.Parameters,
},
g.Hidden,
)
}
func (s *GophermapDirListing) Render(request *FileSystemRequest) ([]byte, *GophorError) {
/* We could just pass the request directly, but in case the request
* path happens to differ for whatever reason we create a new one
*/
return listDir(&FileSystemRequest{ s.Path, request.Host }, s.Hidden)
type GophermapExecCgi struct {
Request *FileSystemRequest /* Stored file system request */
}
/* GophermapExecutable:
* An implementation of GophermapSection that holds onto a path,
* and a string slice of arguments for the supplied executable path.
*/
type GophermapExecutable struct {
Path *RequestPath
Args []string
func (g *GophermapExecCgi) Render(request *FileSystemRequest) ([]byte, *GophorError) {
/* Create new filesystem request from mixture of stored + supplied */
return executeCgi(g.Request)
}
func NewGophermapExecutable(path *RequestPath, args []string) *GophermapExecutable {
return &GophermapExecutable{ path, args }
type GophermapExecFile struct {
Request *FileSystemRequest /* Stored file system request */
}
func (s *GophermapExecutable) Render(request *FileSystemRequest) ([]byte, *GophorError) {
return executeFile(s.Path, s.Args)
func (g *GophermapExecFile) Render(request *FileSystemRequest) ([]byte, *GophorError) {
return executeCommand(g.Request)
}
type GophermapExecCommand struct {
Request *FileSystemRequest
}
func (g *GophermapExecCommand) Render(request *FileSystemRequest) ([]byte, *GophorError) {
return executeCommand(g.Request)
}
func readGophermap(requestPath *RequestPath) ([]GophermapSection, *GophorError) {
func readGophermap(request *FileSystemRequest) ([]GophermapSection, *GophorError) {
/* Create return slice */
sections := make([]GophermapSection, 0)
@ -179,7 +161,7 @@ func readGophermap(requestPath *RequestPath) ([]GophermapSection, *GophorError)
var dirListing *GophermapDirListing
/* Perform buffered scan with our supplied splitter and iterators */
gophorErr := bufferedScan(requestPath.AbsolutePath(),
gophorErr := bufferedScan(request.AbsPath(),
func(scanner *bufio.Scanner) bool {
line := scanner.Text()
@ -188,12 +170,12 @@ func readGophermap(requestPath *RequestPath) ([]GophermapSection, *GophorError)
switch lineType {
case TypeInfoNotStated:
/* Append TypeInfo to the beginning of line */
sections = append(sections, NewGophermapText(buildInfoLine(line)))
sections = append(sections, &GophermapText{ buildInfoLine(line) })
case TypeTitle:
/* Reformat title line to send as info line with appropriate selector */
if !titleAlready {
sections = append(sections, NewGophermapText(buildLine(TypeInfo, line[1:], "TITLE", NullHost, NullPort)))
sections = append(sections, &GophermapText{ buildLine(TypeInfo, line[1:], "TITLE", NullHost, NullPort) })
titleAlready = true
}
@ -206,46 +188,47 @@ func readGophermap(requestPath *RequestPath) ([]GophermapSection, *GophorError)
hidden[line[1:]] = true
case TypeSubGophermap:
/* Create new request path and args array */
subPath, args := parseLineFileSystemRequest(requestPath.Root, line[1:])
if !subPath.HasAbsolutePrefix("/") {
/* Special case here where command must be in path, return GophermapExecutable */
sections = append(sections, NewGophermapExecutable(subPath, args))
} else if subPath.RelativePath() == "" {
/* Parse new requestPath and parameters (this automatically sanitizes requestPath) */
subRequest := parseLineRequestString(request, line[1:])
if !subRequest.HasAbsPathPrefix("/") {
/* Special case here where command must be in path, return GophermapExecCommand */
sections = append(sections, &GophermapExecCommand{ subRequest })
} else if subRequest.RelPath() == "" {
/* path cleaning failed */
break
} else if subPath.RelativePath() == requestPath.RelativePath() {
} else if subRequest.RelPath() == request.RelPath() {
/* Same as current gophermap. Recursion bad! */
break
}
/* Perform file stat */
stat, err := os.Stat(subPath.AbsolutePath())
stat, err := os.Stat(subRequest.AbsPath())
if (err != nil) || (stat.Mode() & os.ModeDir != 0) {
/* File read error or is directory */
break
}
/* Check if we've been supplied subgophermap or regular file */
if subPath.HasAbsoluteSuffix("/"+GophermapFileStr) {
if subRequest.HasAbsPathSuffix("/"+GophermapFileStr) {
/* If executable, store as GophermapExecutable, else readGophermap() */
if stat.Mode().Perm() & 0100 != 0 {
sections = append(sections, NewGophermapExecutable(subPath, args))
sections = append(sections, &GophermapExecFile { subRequest })
} else {
/* Treat as any other gophermap! */
submapSections, gophorErr := readGophermap(subPath)
submapSections, gophorErr := readGophermap(subRequest)
if gophorErr == nil {
sections = append(sections, submapSections...)
}
}
} else {
/* If stored in cgi-bin store as GophermapExecutable, else read into GophermapText */
if subPath.HasRelativePrefix(CgiBinDirStr) {
sections = append(sections, NewGophermapExecutable(subPath, args))
if subRequest.HasRelPathPrefix(CgiBinDirStr) {
sections = append(sections, &GophermapExecCgi{ subRequest })
} else {
fileContents, gophorErr := readIntoGophermap(subPath.AbsolutePath())
fileContents, gophorErr := readIntoGophermap(subRequest.AbsPath())
if gophorErr == nil {
sections = append(sections, NewGophermapText(fileContents))
sections = append(sections, &GophermapText{ fileContents })
}
}
}
@ -259,13 +242,13 @@ func readGophermap(requestPath *RequestPath) ([]GophermapSection, *GophorError)
case TypeEndBeginList:
/* Create GophermapDirListing object then break out at end of loop */
dirPath := requestPath.NewTrimPathFromCurrent(GophermapFileStr)
dirListing = NewGophermapDirListing(dirPath)
dirRequest := NewFileSystemRequest(nil, nil, request.RootDir, request.TrimRelPathSuffix(GophermapFileStr), request.Parameters)
dirListing = &GophermapDirListing{ dirRequest, hidden }
return false
default:
/* Just append to sections slice as gophermap text */
sections = append(sections, NewGophermapText([]byte(line+DOSLineEnd)))
sections = append(sections, &GophermapText{ []byte(line+DOSLineEnd) })
}
return true
@ -277,16 +260,6 @@ func readGophermap(requestPath *RequestPath) ([]GophermapSection, *GophorError)
return nil, gophorErr
}
/* If dir listing requested, append the hidden files map then add
* to sections slice. We can do this here as the TypeEndBeginList item
* type ALWAYS comes last, at least in the gophermap handled by this call
* to readGophermap().
*/
if dirListing != nil {
dirListing.Hidden = hidden
sections = append(sections, dirListing)
}
return sections, nil
}

@ -6,7 +6,6 @@ import (
"time"
)
type FileType int
const (
/* Help converting file size stat to supplied size in megabytes */
BytesInMegaByte = 1048576.0
@ -16,16 +15,15 @@ const (
GophermapFileStr = "gophermap"
)
/* FileSystem:
* Object to hold and help manage our file cache. Uses a fixed map
* as a means of easily collecting files by path, but also being able
* to remove cached files in a LRU style. Uses a RW mutex to lock the
* cache map for appropriate functions and ensure thread safety.
*/
type FileSystem struct {
CacheMap *FixedMap
CacheMutex sync.RWMutex
CacheFileMax int64
/* Holds and helps manage our file cache, as well as managing
* access and responding to filesystem requests submitted by
* a worker instance.
*/
CacheMap *FixedMap /* Fixed size cache map */
CacheMutex sync.RWMutex /* RWMutex for safe cachemap access */
CacheFileMax int64 /* Cache file size max */
}
func (fs *FileSystem) Init(size int, fileSizeMax float64) {
@ -34,16 +32,13 @@ func (fs *FileSystem) Init(size int, fileSizeMax float64) {
fs.CacheFileMax = int64(BytesInMegaByte * fileSizeMax)
}
func (fs *FileSystem) HandleRequest(host *ConnHost, requestPath *RequestPath, args []string) ([]byte, *GophorError) {
/* Get absolute path */
absPath := requestPath.AbsolutePath()
func (fs *FileSystem) HandleRequest(request *FileSystemRequest) ([]byte, *GophorError) {
/* Get filesystem stat, check it exists! */
stat, err := os.Stat(absPath)
stat, err := os.Stat(request.AbsPath())
if err != nil {
/* Check file isn't in cache before throwing in the towel */
fs.CacheMutex.RLock()
file := fs.CacheMap.Get(absPath)
file := fs.CacheMap.Get(request.AbsPath())
if file == nil {
fs.CacheMutex.RUnlock()
return nil, &GophorError{ FileStatErr, err }
@ -51,7 +46,7 @@ func (fs *FileSystem) HandleRequest(host *ConnHost, requestPath *RequestPath, ar
/* It's there! Get contents, unlock and return */
file.Mutex.RLock()
b := file.Contents(&FileSystemRequest{ requestPath, host })
b := file.Contents(request)
file.Mutex.RUnlock()
fs.CacheMutex.RUnlock()
@ -63,26 +58,26 @@ func (fs *FileSystem) HandleRequest(host *ConnHost, requestPath *RequestPath, ar
/* Directory */
case stat.Mode() & os.ModeDir != 0:
/* Ignore cgi-bin directory */
if requestPath.HasRelativePrefix(CgiBinDirStr) {
if request.HasRelPathPrefix(CgiBinDirStr) {
return nil, &GophorError{ IllegalPathErr, nil }
}
/* Check Gophermap exists */
gophermapPath := requestPath.NewJoinPathFromCurrent(GophermapFileStr)
stat, err = os.Stat(gophermapPath.AbsolutePath())
gophermapRequest := NewFileSystemRequest(request.Host, request.Client, request.RootDir, request.JoinRelPath(GophermapFileStr), request.Parameters)
stat, err = os.Stat(gophermapRequest.AbsPath())
var output []byte
var gophorErr *GophorError
if err == nil {
/* Gophermap exists! If executable execute, else serve. */
if stat.Mode().Perm() & 0100 != 0 {
output, gophorErr = executeFile(gophermapPath, args)
output, gophorErr = executeFile(gophermapRequest)
} else {
output, gophorErr = fs.FetchFile(&FileSystemRequest{ gophermapPath, host })
output, gophorErr = fs.FetchFile(gophermapRequest)
}
} else {
/* No gophermap, serve directory listing */
output, gophorErr = listDir(&FileSystemRequest{ requestPath, host }, map[string]bool{})
output, gophorErr = listDir(request, map[string]bool{})
}
if gophorErr != nil {
@ -97,10 +92,10 @@ func (fs *FileSystem) HandleRequest(host *ConnHost, requestPath *RequestPath, ar
/* Regular file */
case stat.Mode() & os.ModeType == 0:
/* If cgi-bin, return executed contents. Else, fetch */
if requestPath.HasRelativePrefix(CgiBinDirStr) {
return executeFile(requestPath, args)
if request.HasRelPathPrefix(CgiBinDirStr) {
return executeCgi(request)
} else {
return fs.FetchFile(&FileSystemRequest{ requestPath, host })
return fs.FetchFile(request)
}
/* Unsupported type */
@ -112,7 +107,7 @@ func (fs *FileSystem) HandleRequest(host *ConnHost, requestPath *RequestPath, ar
func (fs *FileSystem) FetchFile(request *FileSystemRequest) ([]byte, *GophorError) {
/* Get cache map read lock then check if file in cache map */
fs.CacheMutex.RLock()
file := fs.CacheMap.Get(request.Path.AbsolutePath())
file := fs.CacheMap.Get(request.AbsPath())
if file != nil {
/* File in cache -- before doing anything get file read lock */
@ -141,7 +136,7 @@ func (fs *FileSystem) FetchFile(request *FileSystemRequest) ([]byte, *GophorErro
/* Perform filesystem stat ready for checking file size later.
* Doing this now allows us to weed-out non-existent files early
*/
stat, err := os.Stat(request.Path.AbsolutePath())
stat, err := os.Stat(request.AbsPath())
if err != nil {
/* Error stat'ing file, unlock read mutex then return error */
fs.CacheMutex.RUnlock()
@ -150,14 +145,14 @@ func (fs *FileSystem) FetchFile(request *FileSystemRequest) ([]byte, *GophorErro
/* Create new file contents */
var contents FileContents
if request.Path.HasAbsoluteSuffix("/"+GophermapFileStr) {
contents = &GophermapContents{ request.Path, nil }
if request.HasAbsPathSuffix("/"+GophermapFileStr) {
contents = &GophermapContents{ request, nil }
} else {
contents = &RegularFileContents{ request.Path, nil }
contents = &RegularFileContents{ request, nil }
}
/* Create new file wrapper around contents */
file = NewFile(contents)
file = &File{ contents, sync.RWMutex{}, true, time.Now().UnixNano() }
/* File isn't in cache yet so no need to get file lock mutex */
gophorErr := file.LoadContents()
@ -181,7 +176,7 @@ func (fs *FileSystem) FetchFile(request *FileSystemRequest) ([]byte, *GophorErro
fs.CacheMutex.Lock()
/* Put file in the FixedMap */
fs.CacheMap.Put(request.Path.AbsolutePath(), file)
fs.CacheMap.Put(request.AbsPath(), file)
/* Before unlocking cache mutex, lock file read for upcoming call to .Contents() */
file.Mutex.RLock()
@ -201,37 +196,28 @@ func (fs *FileSystem) FetchFile(request *FileSystemRequest) ([]byte, *GophorErro
return b, nil
}
/* File:
* Wraps around the cached contents of a file and
* helps with management of this content by the
* global FileCache objects.
*/
type File struct {
contents FileContents
/* Wraps around the cached contents of a file
* helping with management of this content by
* a FileSystem instance.
*/
Content FileContents
Mutex sync.RWMutex
Fresh bool
LastRefresh int64
}
func NewFile(contents FileContents) *File {
return &File{
contents,
sync.RWMutex{},
true,
0,
}
}
func (f *File) Contents(request *FileSystemRequest) []byte {
return f.contents.Render(request)
return f.Content.Render(request)
}
func (f *File) LoadContents() *GophorError {
/* Clear current file contents */
f.contents.Clear()
f.Content.Clear()
/* Reload the file */
gophorErr := f.contents.Load()
gophorErr := f.Content.Load()
if gophorErr != nil {
return gophorErr
}
@ -243,19 +229,6 @@ func (f *File) LoadContents() *GophorError {
return nil
}
/* FileContents:
* Interface that provides an adaptable implementation
* for holding onto some level of information about
* the contents of a file, also methods for processing
* and returning the results when the file contents
* are requested.
*/
type FileContents interface {
Render(*FileSystemRequest) []byte
Load() *GophorError
Clear()
}
func startFileMonitor(sleepTime time.Duration) {
go func() {
for {
@ -306,7 +279,7 @@ func checkCacheFreshness() {
func isGeneratedType(file *File) bool {
/* Just a helper function to neaten-up checking if file contents is of generated type */
switch file.contents.(type) {
switch file.Content.(type) {
case *GeneratedFileContents:
return true
default:

@ -113,16 +113,16 @@ func unixLineEndSplitter(data []byte, atEOF bool) (advance int, token []byte, er
/* List the files in a directory, hiding those requested */
func listDir(request *FileSystemRequest, hidden map[string]bool) ([]byte, *GophorError) {
/* Open directory file descriptor */
fd, err := os.Open(request.Path.AbsolutePath())
fd, err := os.Open(request.AbsPath())
if err != nil {
Config.SysLog.Error("", "failed to open %s: %s\n", request.Path, err.Error())
Config.SysLog.Error("", "failed to open %s: %s\n", request.AbsPath(), err.Error())
return nil, &GophorError{ FileOpenErr, err }
}
/* Read files in directory */
files, err := fd.Readdir(-1)
if err != nil {
Config.SysLog.Error("", "failed to enumerate dir %s: %s\n", request.Path, err.Error())
Config.SysLog.Error("", "failed to enumerate dir %s: %s\n", request.AbsPath(), err.Error())
return nil, &GophorError{ DirListErr, err }
}
@ -133,11 +133,11 @@ func listDir(request *FileSystemRequest, hidden map[string]bool) ([]byte, *Gopho
dirContents := make([]byte, 0)
/* First add a title + a space */
dirContents = append(dirContents, buildLine(TypeInfo, "[ "+request.Host.Name+request.Path.SelectorPath()+" ]", "TITLE", NullHost, NullPort)...)
dirContents = append(dirContents, buildLine(TypeInfo, "[ "+request.Host.Name+request.SelectorPath()+" ]", "TITLE", NullHost, NullPort)...)
dirContents = append(dirContents, buildInfoLine("")...)
/* Add a 'back' entry. GoLang Readdir() seems to miss this */
dirContents = append(dirContents, buildLine(TypeDirectory, "..", request.Path.JoinRelativePath(".."), request.Host.Name, request.Host.Port)...)
dirContents = append(dirContents, buildLine(TypeDirectory, "..", request.JoinRelPath(".."), request.Host.Name, request.Host.Port)...)
/* Walk through files :D */
for _, file := range files {
@ -152,12 +152,12 @@ func listDir(request *FileSystemRequest, hidden map[string]bool) ([]byte, *Gopho
switch {
case file.Mode() & os.ModeDir != 0:
/* Directory -- create directory listing */
itemPath := request.Path.JoinSelectorPath(file.Name())
itemPath := request.JoinSelectorPath(file.Name())
dirContents = append(dirContents, buildLine(TypeDirectory, file.Name(), itemPath, request.Host.Name, request.Host.Port)...)
case file.Mode() & os.ModeType == 0:
/* Regular file -- find item type and creating listing */
itemPath := request.Path.JoinSelectorPath(file.Name())
itemPath := request.JoinSelectorPath(file.Name())
itemType := getItemType(itemPath)
dirContents = append(dirContents, buildLine(itemType, file.Name(), itemPath, request.Host.Name, request.Host.Port)...)

@ -5,117 +5,132 @@ import (
"strings"
)
/* FileSystemRequest:
* Makes a request to the filesystem either through
* the FileCache or directly to a function like listDir().
* It carries the requested filesystem path and any extra
* needed information, for the moment just a set of details
* about the virtual host.. Opens things up a lot more for
* the future :)
/* TODO: having 2 separate rootdir string values in Host and RootDir
* doesn't sit right with me. It cleans up code a lot for now
* but could get confusing. Figure out a more elegant way of
* structuring the filesystem request that gets passed around.
*/
type FileSystemRequest struct {
Path *RequestPath
Host *ConnHost
}
type RequestPath struct {
Root string
Path string
AbsPath string /* Cache the absolute path */
}
func NewSanitizedRequestPath(root, request string) *RequestPath {
/* Here we must sanitize the request path. Start with a clean :) */
requestPath := path.Clean(request)
if path.IsAbs(requestPath) {
/* Is absolute. Try trimming root and leading '/' */
requestPath = strings.TrimPrefix(strings.TrimPrefix(requestPath, root), "/")
} else {
/* Is relative. If back dir traversal, give them root */
if strings.HasPrefix(requestPath, "..") {
requestPath = ""
}
type FileSystemRequest struct {
/* A file system request with any possible required
* data required. Either handled through FileSystem or to
* direct function like listDir()
*/
/* Virtual host and client information */
Host *ConnHost
Client *ConnClient
/* File path information */
RootDir string
Rel string
Abs string
/* Other parameters */
Parameters []string /* CGI-bin params will be 1 length slice, shell commands populate >=1 */
}
func NewSanitizedFileSystemRequest(host *ConnHost, client *ConnClient, request string) *FileSystemRequest {
/* Split dataStr into request path and parameter string (if pressent) */
requestPath, parameters := parseRequestString(request)
requestPath = sanitizeRequestPath(host.RootDir, requestPath)
return NewFileSystemRequest(host, client, host.RootDir, requestPath, parameters)
}
func NewFileSystemRequest(host *ConnHost, client *ConnClient, rootDir, requestPath string, parameters []string) *FileSystemRequest {
return &FileSystemRequest{
host,
client,
rootDir,
requestPath,
path.Join(rootDir, requestPath),
parameters,
}
return NewRequestPath(root, requestPath)
}
func NewRequestPath(root, relative string) *RequestPath {
return &RequestPath{ root, relative, path.Join(root, relative) }
}
func (rp *RequestPath) SelectorPath() string {
if rp.Path == "." {
func (r *FileSystemRequest) SelectorPath() string {
if r.Rel == "." {
return "/"
} else {
return "/"+rp.Path
return "/"+r.Rel
}
}
func (rp *RequestPath) AbsolutePath() string {
return rp.AbsPath
func (r *FileSystemRequest) AbsPath() string {
return r.Abs
}
func (rp *RequestPath) RelativePath() string {
return rp.Path
func (r *FileSystemRequest) RelPath() string {
return r.Rel
}
func (rp *RequestPath) JoinSelectorPath(extPath string) string {
if rp.Path == "." {
func (r *FileSystemRequest) JoinSelectorPath(extPath string) string {
if r.Rel == "." {
return path.Join("/", extPath)
} else {
return "/"+path.Join(rp.Path, extPath)
return "/"+path.Join(r.Rel, extPath)
}
}
func (rp *RequestPath) JoinAbsolutePath(extPath string) string {
return path.Join(rp.AbsolutePath(), extPath)
func (r *FileSystemRequest) JoinAbsPath(extPath string) string {
return path.Join(r.AbsPath(), extPath)
}
func (rp *RequestPath) JoinRelativePath(extPath string) string {
return path.Join(rp.RelativePath(), extPath)
func (r *FileSystemRequest) JoinRelPath(extPath string) string {
return path.Join(r.RelPath(), extPath)
}
func (rp *RequestPath) HasAbsolutePrefix(prefix string) bool {
return strings.HasPrefix(rp.AbsolutePath(), prefix)
func (r *FileSystemRequest) HasAbsPathPrefix(prefix string) bool {
return strings.HasPrefix(r.AbsPath(), prefix)
}
func (rp *RequestPath) HasRelativePrefix(prefix string) bool {
return strings.HasPrefix(rp.RelativePath(), prefix)
func (r *FileSystemRequest) HasRelPathPrefix(prefix string) bool {
return strings.HasPrefix(r.RelPath(), prefix)
}
func (rp *RequestPath) HasRelativeSuffix(suffix string) bool {
return strings.HasSuffix(rp.RelativePath(), suffix)
func (r *FileSystemRequest) HasRelPathSuffix(suffix string) bool {
return strings.HasSuffix(r.RelPath(), suffix)
}
func (rp *RequestPath) HasAbsoluteSuffix(suffix string) bool {
return strings.HasSuffix(rp.AbsolutePath(), suffix)
func (r *FileSystemRequest) HasAbsPathSuffix(suffix string) bool {
return strings.HasSuffix(r.AbsPath(), suffix)
}
func (rp *RequestPath) TrimRelativeSuffix(suffix string) string {
return strings.TrimSuffix(rp.RelativePath(), suffix)
func (r *FileSystemRequest) TrimRelPathSuffix(suffix string) string {
return strings.TrimSuffix(strings.TrimSuffix(r.RelPath(), suffix), "/")
}
func (rp *RequestPath) TrimAbsoluteSuffix(suffix string) string {
return strings.TrimSuffix(rp.AbsolutePath(), suffix)
func (r *FileSystemRequest) TrimAbsPathSuffix(suffix string) string {
return strings.TrimSuffix(strings.TrimSuffix(r.AbsPath(), suffix), "/")
}
func (rp *RequestPath) JoinPathFromRoot(extPath string) string {
return path.Join(rp.Root, extPath)
func (r *FileSystemRequest) JoinPathFromRoot(extPath string) string {
return path.Join(r.RootDir, extPath)
}
func (rp *RequestPath) NewJoinPathFromCurrent(extPath string) *RequestPath {
func (r *FileSystemRequest) NewStoredRequestAtRoot(relPath string, parameters []string) *FileSystemRequest {
/* DANGER THIS DOES NOT CHECK FOR BACK-DIR TRAVERSALS */
return NewRequestPath(rp.Root, rp.JoinRelativePath(extPath))
return NewFileSystemRequest(nil, nil, r.RootDir, relPath, parameters)
}
func (rp *RequestPath) NewTrimPathFromCurrent(trimSuffix string) *RequestPath {
/* DANGER THIS DOES NOT CHECK FOR BACK-DIR TRAVERSALS */
return NewRequestPath(rp.Root, rp.TrimRelativeSuffix(trimSuffix))
func (r *FileSystemRequest) NewStoredRequest() *FileSystemRequest {
return NewFileSystemRequest(nil, nil, r.RootDir, r.RelPath(), r.Parameters)
}
func (rp *RequestPath) NewPathAtRoot(extPath string) *RequestPath {
/* Sanitized and safe (hopefully) */
return NewSanitizedRequestPath(rp.Root, extPath)
/* Sanitize a request path string */
func sanitizeRequestPath(rootDir, requestPath string) string {
/* Start with a clean :) */
requestPath = path.Clean(requestPath)
if path.IsAbs(requestPath) {
/* Is absolute. Try trimming root and leading '/' */
requestPath = strings.TrimPrefix(strings.TrimPrefix(requestPath, rootDir), "/")
} else {
/* Is relative. If back dir traversal, give them root */
if strings.HasPrefix(requestPath, "..") {
requestPath = ""
}
}
return requestPath
}

@ -74,14 +74,11 @@ const (
TypeSubGophermap = ItemType('=') /* [SERVER ONLY] Include subgophermap / regular file here. */
TypeEndBeginList = ItemType('*') /* [SERVER ONLY] Last line + directory listing -- stop processing gophermap and end on directory listing */
/* Planned To Be Supported */
TypeExec = ItemType('$') /* [SERVER ONLY] Execute shell command and print stdout here */
/* Default type */
TypeDefault = TypeBin
/* Gophor specific types */
TypeInfoNotStated = ItemType('z') /* [INTERNAL USE] */
TypeInfoNotStated = ItemType('I') /* [INTERNAL USE] */
TypeUnknown = ItemType('?') /* [INTERNAL USE] */
)

@ -10,7 +10,7 @@ import (
)
const (
GophorVersion = "0.7-beta-PR1"
GophorVersion = "0.7-beta-PR1"
)
var (
@ -113,8 +113,10 @@ func setupServer() []*GophorListener {
Config.SysLog.Info("", "Entered server directory: %s\n", *serverRoot)
/* Setup initial server shell environment with the info we have to hand */
/* Setup regular and cgi shell environments */
Config.Env = setupExecEnviron()
Config.CgiEnv = setupInitialCgiEnviron()
Config.SysLog.Info("", "Using user supplied shell environment\n")
/* Setup listeners */
listeners := make([]*GophorListener, 0)
@ -135,7 +137,6 @@ func setupServer() []*GophorListener {
/* Compile user restricted files regex */
Config.RestrictedFiles = compileUserRestrictedFilesRegex(*restrictedFiles)
Config.SysLog.Info("", "Compiled restricted files regular expressions\n")
/* Setup file cache */
Config.FileSystem = new(FileSystem)

@ -2,10 +2,21 @@ package main
import (
"strings"
"path"
"net/url"
)
/* Parse a request string into a path and parameters string */
func parseRequestString(request string) (string, []string) {
/* Read up to first '?' and then put rest into single slice string array */
i := 0
for i < len(request) {
if request[i] == '?' {
break
}
i += 1
}
return request[:i], []string{ request[i:] }
}
/* Parse line type from contents */
func parseLineType(line string) ItemType {
lineLen := len(line)
@ -41,8 +52,6 @@ func parseLineType(line string) ItemType {
return TypeHiddenFile
case TypeSubGophermap:
return TypeSubGophermap
case TypeExec:
return TypeExec
default:
return TypeInfoNotStated
}
@ -52,80 +61,30 @@ func parseLineType(line string) ItemType {
}
/* Parses a line in a gophermap into a filesystem request path and a string slice of arguments */
func parseLineFileSystemRequest(rootDir, requestStr string) (*RequestPath, []string) {
if path.IsAbs(requestStr) {
/* This is an absolute path, assume it must be within gopher directory */
args := splitLineStringArgs(requestStr)
requestPath := NewSanitizedRequestPath(rootDir, args[0])
if len(args) > 1 {
return requestPath, args[1:]
func parseLineRequestString(request *FileSystemRequest, lineStr string) (*FileSystemRequest) {
if strings.HasPrefix(lineStr, "/") {
/* We are dealing with a file input of some kind. Figure out if CGI-bin */
if strings.HasPrefix(lineStr[1:], CgiBinDirStr) {
/* CGI-bind script, parse requestPath and parameters as standard URL encoding */
requestPath, parameters := parseRequestString(lineStr)
return NewFileSystemRequest(nil, nil, request.RootDir, requestPath, parameters)
} else {
return requestPath, nil
/* Regular file, no more parsing needing */
return NewFileSystemRequest(nil, nil, request.RootDir, lineStr[1:], request.Parameters)
}
} else {
/* Not an absolute path, if starts with cgi-bin treat as within gopher directory, else as command in path */
if strings.HasPrefix(requestStr, CgiBinDirStr) {
args := splitLineStringArgs(requestStr)
requestPath := NewSanitizedRequestPath(rootDir, args[0])
if len(args) > 1 {
return requestPath, args[1:]
} else {
return requestPath, nil
}
/* We have been passed a command string */
args := splitCommandString(lineStr)
if len(args) > 1 {
return NewFileSystemRequest(nil, nil, "", args[0], args[1:])
} else {
args := splitLineStringArgs(requestStr)
/* Manually create specialised request path */
requestPath := NewRequestPath(args[0], "")
if len(args) > 1 {
return requestPath, args[1:]
} else {
return requestPath, nil
}
return NewFileSystemRequest(nil, nil, "", args[0], []string{})
}
}
}
/* Parses a gopher request string into a filesystem request path and string slice of arguments */
func parseFileSystemRequest(rootDir, requestStr string) (*RequestPath, []string, *GophorError) {
/* Split the request string */
args := splitRequestStringArgs(requestStr)
/* Now URL decode all the parts. */
var err error
for i := range args {
args[i], err = url.QueryUnescape(args[i])
if err != nil {
return nil, nil, &GophorError{ InvalidRequestErr, err }
}
}
/* Create request path */
requestPath := NewSanitizedRequestPath(rootDir, args[0])
/* Return request path and args if precent */
if len(args) > 1 {
return requestPath, args[1:], nil
} else {
return requestPath, nil, nil
}
}
/* Parse new-line separated string of environment variables into a slice */
func parseEnvironmentString(env string) []string {
return splitStringByRune(env, '\n')
}
/* Splits a request string into it's arguments with the '?' delimiter */
func splitRequestStringArgs(requestStr string) []string {
return splitStringByRune(requestStr, '?')
}
/* Splits a line string into it's arguments with standard space delimiter */
func splitLineStringArgs(requestStr string) []string {
func splitCommandString(requestStr string) []string {
split := Config.CmdParseLineRegex.Split(requestStr, -1)
if split == nil {
return []string{ requestStr }

@ -2,6 +2,7 @@ package main
import (
"os"
"sync"
)
const (
@ -19,7 +20,7 @@ func cachePolicyFiles(description, admin, geoloc string) {
/* Create new file object from generated file contents */
fileContents := &GeneratedFileContents{ content }
file := NewFile(fileContents)
file := &File{ fileContents, sync.RWMutex{}, true, 0 }
/* Trigger a load contents just to set it as fresh etc */
file.LoadContents()
@ -36,7 +37,7 @@ func cachePolicyFiles(description, admin, geoloc string) {
/* Create new file object from generated file contents */
fileContents := &GeneratedFileContents{ content }
file := NewFile(fileContents)
file := &File{ fileContents, sync.RWMutex{}, true, 0 }
/* Trigger a load contents just to set it as fresh etc */
file.LoadContents()

@ -2,12 +2,13 @@ package main
import (
"strings"
"io"
)
const (
/* Socket settings */
SocketReadBufSize = 256 /* Supplied selector should be <= this len */
MaxSocketReadChunks = 1
SocketReadBufSize = 1024
MaxSocketReadChunks = 4
)
type Worker struct {
@ -36,16 +37,18 @@ func (worker *Worker) Serve() {
/* Buffered read from listener */
count, err = worker.Conn.Read(buf)
if err != nil {
if err == io.EOF {
break
}
Config.SysLog.Error("", "Error reading from socket on port %s: %s\n", worker.Conn.Host.Port, err.Error())
return
}
/* Only copy non-null bytes */
received = append(received, buf[:count]...)
/* If count is less than expected read size, we've hit EOF */
if count < SocketReadBufSize {
/* EOF */
/* Reached EOF */
break
}
@ -76,11 +79,11 @@ func (worker *Worker) Serve() {
}
func (worker *Worker) Log(format string, args ...interface{}) {
Config.AccLog.Info("("+worker.Conn.RemoteAddr().String()+") ", format, args...)
Config.AccLog.Info("("+worker.Conn.Client.Ip+") ", format, args...)
}
func (worker *Worker) LogError(format string, args ...interface{}) {
Config.AccLog.Error("("+worker.Conn.RemoteAddr().String()+") ", format, args...)
Config.AccLog.Error("("+worker.Conn.Client.Ip+") ", format, args...)
}
func (worker *Worker) Send(b []byte) *GophorError {
@ -94,8 +97,22 @@ func (worker *Worker) Send(b []byte) *GophorError {
}
func (worker *Worker) RespondGopher(data []byte) *GophorError {
/* According to Gopher spec, only read up to first Tab or Crlf */
dataStr := readUpToFirstTabOrCrlf(data)
/* Only read up to first tab or cr-lf */
dataStr := ""
dataLen := len(data)
for i := 0; i < dataLen; i += 1 {
if data[i] == Tab[0] {
break
} else if data[i] == DOSLineEnd[0] {
if i == dataLen-1 || data[i+1] == DOSLineEnd[1] {
break
} else {
dataStr += string(data[i])
}
} else {
dataStr += string(data[i])
}
}
/* Handle URL request if presented */
lenBefore := len(dataStr)
@ -109,42 +126,19 @@ func (worker *Worker) RespondGopher(data []byte) *GophorError {
/* Do nothing */
}
/* Parse filesystem request and arg string slice */
requestPath, args, gophorErr := parseFileSystemRequest(worker.Conn.Host.RootDir, dataStr)
if gophorErr != nil {
return gophorErr
}
/* Create new filesystem request */
request := NewSanitizedFileSystemRequest(worker.Conn.Host, worker.Conn.Client, dataStr)
/* Handle filesystem request */
response, gophorErr := Config.FileSystem.HandleRequest(worker.Conn.Host, requestPath, args)
response, gophorErr := Config.FileSystem.HandleRequest(request)
if gophorErr != nil {
/* Log to system and access logs, then return error */
Config.SysLog.Error("", "Error serving %s: %s\n", dataStr, gophorErr.Error())
worker.LogError("Failed to serve: %s\n", requestPath.AbsolutePath())
worker.LogError("Failed to serve: %s\n", request.AbsPath())
return gophorErr
}
worker.Log("Served: %s\n", requestPath.AbsolutePath())
worker.Log("Served: %s\n", request.AbsPath())
/* Serve response */
return worker.Send(response)
}
func readUpToFirstTabOrCrlf(data []byte) string {
/* Only read up to first tab or cr-lf */
dataStr := ""
dataLen := len(data)
for i := 0; i < dataLen; i += 1 {
switch data[i] {
case '\t':
return dataStr
case DOSLineEnd[0]:
if i == dataLen-1 || data[i+1] == DOSLineEnd[1] {
return dataStr
}
default:
dataStr += string(data[i])
}
}
return dataStr
}

Loading…
Cancel
Save