add virtual filesystem support (remapping file paths)
Signed-off-by: kim (grufwub) <grufwub@gmail.com>
This commit is contained in:
parent
32e8a47073
commit
0b8c3b41ba
22
config.go
22
config.go
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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) }
|
||||
|
10
format.go
10
format.go
@ -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)
|
||||
}
|
||||
|
19
gophor.go
19
gophor.go
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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...)
|
||||
|
32
parse.go
32
parse.go
@ -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 */
|
||||
|
22
request.go
22
request.go
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user