add virtual filesystem support (remapping file paths)

Signed-off-by: kim (grufwub) <grufwub@gmail.com>
This commit is contained in:
kim (grufwub) 2020-05-07 20:52:25 +01:00
parent 32e8a47073
commit 0b8c3b41ba
8 changed files with 114 additions and 37 deletions

View File

@ -13,15 +13,15 @@ import (
*/
type ServerConfig struct {
/* Executable Settings */
Env []string
CgiEnv []string
CgiEnabled bool
MaxExecRunTime time.Duration
Env []string
CgiEnv []string
CgiEnabled bool
MaxExecRunTime time.Duration
/* Content settings */
CharSet string
FooterText []byte
PageWidth int
CharSet string
FooterText []byte
PageWidth int
/* Regex */
CmdParseLineRegex *regexp.Regexp
@ -29,13 +29,13 @@ type ServerConfig struct {
RestrictedCommands []*regexp.Regexp
/* Logging */
SysLog LoggerInterface
AccLog LoggerInterface
SysLog LoggerInterface
AccLog LoggerInterface
/* Filesystem access */
FileSystem *FileSystem
FileSystem *FileSystem
/* */
/* Buffer sizes */
SocketWriteBufSize int
SocketReadBufSize int
SocketReadMax int

View File

@ -24,20 +24,40 @@ type FileSystem struct {
CacheMap *FixedMap /* Fixed size cache map */
CacheMutex sync.RWMutex /* RWMutex for safe cachemap access */
CacheFileMax int64 /* Cache file size max */
Remap map[string]string
ReverseRemap map[string]string
}
func (fs *FileSystem) Init(size int, fileSizeMax float64) {
fs.CacheMap = NewFixedMap(size)
fs.CacheMutex = sync.RWMutex{}
fs.CacheFileMax = int64(BytesInMegaByte * fileSizeMax)
/* {,Reverse}Remap map is setup in `gophor.go`, no need to here */
}
func (fs *FileSystem) RemapRequestPath(requestPath *RequestPath) {
realPath, ok := fs.Remap[requestPath.Relative()]
if ok {
requestPath.RemapActual(realPath)
}
}
func (fs *FileSystem)ReverseRemapRequestPath(requestPath *RequestPath) {
virtualPath, ok := fs.Remap[requestPath.Relative()]
if ok {
requestPath.RemapVirtual(virtualPath)
}
}
func (fs *FileSystem) HandleRequest(responder *Responder) *GophorError {
/* Check if restricted file */
if isRestrictedFile(responder.Request.AbsPath()) {
if isRestrictedFile(responder.Request.RelPath()) {
return &GophorError{ IllegalPathErr, nil }
}
/* Remap RequestPath if necessary */
fs.RemapRequestPath(responder.Request.Path)
/* Get filesystem stat, check it exists! */
stat, err := os.Stat(responder.Request.AbsPath())
if err != nil {

View File

@ -140,24 +140,29 @@ func listDir(responder *Responder, hidden map[string]bool) *GophorError {
dirContents = append(dirContents, buildLine(TypeDirectory, "..", responder.Request.PathJoinSelector(".."), responder.Host.Name(), responder.Host.Port())...)
/* Walk through files :D */
var reqPath *RequestPath
for _, file := range files {
/* If regex match in restricted files || requested hidden */
if _, ok := hidden[file.Name()]; ok {
reqPath = NewRequestPath(responder.Request.RootDir(), file.Name())
/* If hidden file, or restricted file, continue! */
if isHiddenFile(hidden, file.Name()) || isRestrictedFile(reqPath.Relative()) {
continue
}
/* If requires remap, do so! */
Config.FileSystem.ReverseRemapRequestPath(reqPath)
/* Handle file, directory or ignore others */
switch {
case file.Mode() & os.ModeDir != 0:
/* Directory -- create directory listing */
itemPath := responder.Request.PathJoinSelector(file.Name())
dirContents = append(dirContents, buildLine(TypeDirectory, file.Name(), itemPath, responder.Host.Name(), responder.Host.Port())...)
dirContents = append(dirContents, buildLine(TypeDirectory, file.Name(), reqPath.Selector(), responder.Host.Name(), responder.Host.Port())...)
case file.Mode() & os.ModeType == 0:
/* Regular file -- find item type and creating listing */
itemPath := responder.Request.PathJoinSelector(file.Name())
itemPath := reqPath.Selector()
itemType := getItemType(itemPath)
dirContents = append(dirContents, buildLine(itemType, file.Name(), itemPath, responder.Host.Name(), responder.Host.Port())...)
dirContents = append(dirContents, buildLine(itemType, file.Name(), reqPath.Selector(), responder.Host.Name(), responder.Host.Port())...)
default:
/* Ignore */
@ -168,6 +173,11 @@ func listDir(responder *Responder, hidden map[string]bool) *GophorError {
return responder.WriteFlush(append(dirContents, Config.FooterText...))
}
func isHiddenFile(hiddenMap map[string]bool, fileName string) bool {
_, ok := hiddenMap[fileName]
return ok
}
/* Took a leaf out of go-gopher's book here. */
type byName []os.FileInfo
func (s byName) Len() int { return len(s) }

View File

@ -174,21 +174,21 @@ func buildLine(t ItemType, name, selector, host string, port string) []byte {
/* Add name, truncate name if too long */
if len(name) > Config.PageWidth {
ret += name[:Config.PageWidth-5]+"...\t"
ret += name[:Config.PageWidth-5]+"..."+Tab
} else {
ret += name+"\t"
ret += name+Tab
}
/* Add selector. If too long use err, skip if empty */
selectorLen := len(selector)
if selectorLen > MaxSelectorLen {
ret += SelectorErrorStr+"\t"
ret += SelectorErrorStr+Tab
} else if selectorLen > 0 {
ret += selector+"\t"
ret += selector+Tab
}
/* Add host + port */
ret += host+"\t"+port+DOSLineEnd
ret += host+Tab+port+DOSLineEnd
return []byte(ret)
}

View File

@ -2,6 +2,7 @@ package main
import (
"os"
"log"
"strconv"
"syscall"
"os/signal"
@ -69,8 +70,11 @@ func setupServer() []*GophorListener {
logOutput := flag.String("log-output", "stderr", "Change server log file handling (disable|stderr|file)")
logOpts := flag.String("log-opts", "timestamp,ip", "Comma-separated list of log options (timestamp|ip)")
/* Cache settings */
/* File system */
fileMonitorFreq := flag.Duration("file-monitor-freq", time.Second*60, "Change file monitor frequency.")
fileSystemRemap := flag.String("file-remap", "", "New-line separated list of file remappings of format: `/virtual/relative/path -> /actual/relative/path`")
/* Cache settings */
cacheSize := flag.Int("cache-size", 50, "Change file cache size, measured in file count.")
cacheFileSizeMax := flag.Float64("cache-file-max", 0.5, "Change maximum file size to be cached (in megabytes).")
cacheDisabled := flag.Bool("disable-cache", false, "Disable file caching.")
@ -134,8 +138,6 @@ func setupServer() []*GophorListener {
if *disableCgi {
Config.SysLog.Info("", "CGI support disabled")
Config.CgiEnabled = false
} else {
/* Enable CGI */
Config.SysLog.Info("", "CGI support enabled")
@ -157,7 +159,7 @@ func setupServer() []*GophorListener {
/* If running as root, get ready to drop privileges */
if syscall.Getuid() == 0 || syscall.Getgid() == 0 {
Config.SysLog.Fatal("", "Gophor does not support running as root!\n")
log.Fatalf("", "Gophor does not support running as root!\n")
}
/* Enter server dir */
@ -176,11 +178,11 @@ func setupServer() []*GophorListener {
l, err := BeginGophorListen(*serverBindAddr, *serverHostname, strconv.Itoa(*serverPort), strconv.Itoa(*serverFwdPort), *serverRoot)
if err != nil {
Config.SysLog.Fatal("", "Error setting up (unencrypted) listener: %s\n", err.Error())
log.Fatalf("Error setting up (unencrypted) listener: %s\n", err.Error())
}
listeners = append(listeners, l)
} else {
Config.SysLog.Fatal("", "No valid port to listen on\n")
log.Fatalf("No valid port to listen on\n")
}
/* Compile regex statements */
@ -213,6 +215,9 @@ func setupServer() []*GophorListener {
cachePolicyFiles(*serverRoot, *serverDescription, *serverAdmin, *serverGeoloc)
}
/* Setup file remappings */
Config.FileSystem.Remap, Config.FileSystem.ReverseRemap = parseFileSystemRemaps(*fileSystemRemap)
/* Return the created listeners slice :) */
return listeners
}
@ -220,6 +225,6 @@ func setupServer() []*GophorListener {
func enterServerDir(path string) {
err := syscall.Chdir(path)
if err != nil {
Config.SysLog.Fatal("", "Error changing dir to server root %s: %s\n", path, err.Error())
log.Fatalf("Error changing dir to server root %s: %s\n", path, err.Error())
}
}

View File

@ -52,11 +52,11 @@ func (l *Logger) Fatal(prefix, format string, args ...interface{}) {
l.Logger.Fatalf(LogPrefixFatal+prefix+format, args...)
}
/* Logger implementation that ignores the prefix (e.g. when not printing IPs) */
type LoggerNoPrefix struct {
Logger *log.Logger
}
/* Logger implementation that ignores the prefix (e.g. when not printing IPs) */
func (l *LoggerNoPrefix) Info(prefix, format string, args ...interface{}) {
/* Ignore the prefix */
l.Logger.Printf(LogPrefixInfo+format, args...)

View File

@ -2,8 +2,40 @@ package main
import (
"strings"
"log"
)
const (
FileRemapSeparator = " -> "
)
/* Parse file system remaps */
func parseFileSystemRemaps(fileSystemRemap string) (map[string]string, map[string]string) {
remap := make(map[string]string)
reverseRemap := make(map[string]string)
for _, remapEntry := range strings.Split(fileSystemRemap, UnixLineEnd) {
/* Empty remap entry */
if len(remapEntry) == 0 {
continue
}
/* Split remap entry into virtual and actual path, then append */
mapSplit := strings.Split(remapEntry, FileRemapSeparator)
if len(mapSplit) != 2 {
log.Fatalf("Invalid filesystem remap entry: %s\n", remapEntry)
} else {
virtualPath := strings.TrimPrefix(mapSplit[0], "/")
actualPath := strings.TrimPrefix(mapSplit[1], "/")
remap[virtualPath] = actualPath
reverseRemap[actualPath] = virtualPath
}
}
return remap, reverseRemap
}
/* 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 */

View File

@ -12,13 +12,23 @@ type RequestPath struct {
* and filesystem reading
*/
Root string
Rel string
Abs string
Root string
Rel string
Abs string
Select string
}
func NewRequestPath(rootDir, relPath string) *RequestPath {
return &RequestPath{ rootDir, relPath, path.Join(rootDir, strings.TrimSuffix(relPath, "/")) }
return &RequestPath{ rootDir, relPath, path.Join(rootDir, strings.TrimSuffix(relPath, "/")), relPath }
}
func (rp *RequestPath) RemapActual(newRel string) {
rp.Rel = newRel
rp.Abs = path.Join(rp.Root, strings.TrimSuffix(newRel, "/"))
}
func (rp *RequestPath) RemapVirtual(newSel string) {
rp.Select = newSel
}
func (rp *RequestPath) RootDir() string {
@ -34,10 +44,10 @@ func (rp *RequestPath) Absolute() string {
}
func (rp *RequestPath) Selector() string {
if rp.Rel == "." {
if rp.Select == "." {
return "/"
} else {
return "/"+rp.Rel
return "/"+rp.Select
}
}