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 {
|
type ServerConfig struct {
|
||||||
/* Executable Settings */
|
/* Executable Settings */
|
||||||
Env []string
|
Env []string
|
||||||
CgiEnv []string
|
CgiEnv []string
|
||||||
CgiEnabled bool
|
CgiEnabled bool
|
||||||
MaxExecRunTime time.Duration
|
MaxExecRunTime time.Duration
|
||||||
|
|
||||||
/* Content settings */
|
/* Content settings */
|
||||||
CharSet string
|
CharSet string
|
||||||
FooterText []byte
|
FooterText []byte
|
||||||
PageWidth int
|
PageWidth int
|
||||||
|
|
||||||
/* Regex */
|
/* Regex */
|
||||||
CmdParseLineRegex *regexp.Regexp
|
CmdParseLineRegex *regexp.Regexp
|
||||||
@ -29,13 +29,13 @@ type ServerConfig struct {
|
|||||||
RestrictedCommands []*regexp.Regexp
|
RestrictedCommands []*regexp.Regexp
|
||||||
|
|
||||||
/* Logging */
|
/* Logging */
|
||||||
SysLog LoggerInterface
|
SysLog LoggerInterface
|
||||||
AccLog LoggerInterface
|
AccLog LoggerInterface
|
||||||
|
|
||||||
/* Filesystem access */
|
/* Filesystem access */
|
||||||
FileSystem *FileSystem
|
FileSystem *FileSystem
|
||||||
|
|
||||||
/* */
|
/* Buffer sizes */
|
||||||
SocketWriteBufSize int
|
SocketWriteBufSize int
|
||||||
SocketReadBufSize int
|
SocketReadBufSize int
|
||||||
SocketReadMax int
|
SocketReadMax int
|
||||||
|
@ -24,20 +24,40 @@ type FileSystem struct {
|
|||||||
CacheMap *FixedMap /* Fixed size cache map */
|
CacheMap *FixedMap /* Fixed size cache map */
|
||||||
CacheMutex sync.RWMutex /* RWMutex for safe cachemap access */
|
CacheMutex sync.RWMutex /* RWMutex for safe cachemap access */
|
||||||
CacheFileMax int64 /* Cache file size max */
|
CacheFileMax int64 /* Cache file size max */
|
||||||
|
Remap map[string]string
|
||||||
|
ReverseRemap map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FileSystem) Init(size int, fileSizeMax float64) {
|
func (fs *FileSystem) Init(size int, fileSizeMax float64) {
|
||||||
fs.CacheMap = NewFixedMap(size)
|
fs.CacheMap = NewFixedMap(size)
|
||||||
fs.CacheMutex = sync.RWMutex{}
|
fs.CacheMutex = sync.RWMutex{}
|
||||||
fs.CacheFileMax = int64(BytesInMegaByte * fileSizeMax)
|
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 {
|
func (fs *FileSystem) HandleRequest(responder *Responder) *GophorError {
|
||||||
/* Check if restricted file */
|
/* Check if restricted file */
|
||||||
if isRestrictedFile(responder.Request.AbsPath()) {
|
if isRestrictedFile(responder.Request.RelPath()) {
|
||||||
return &GophorError{ IllegalPathErr, nil }
|
return &GophorError{ IllegalPathErr, nil }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Remap RequestPath if necessary */
|
||||||
|
fs.RemapRequestPath(responder.Request.Path)
|
||||||
|
|
||||||
/* Get filesystem stat, check it exists! */
|
/* Get filesystem stat, check it exists! */
|
||||||
stat, err := os.Stat(responder.Request.AbsPath())
|
stat, err := os.Stat(responder.Request.AbsPath())
|
||||||
if err != nil {
|
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())...)
|
dirContents = append(dirContents, buildLine(TypeDirectory, "..", responder.Request.PathJoinSelector(".."), responder.Host.Name(), responder.Host.Port())...)
|
||||||
|
|
||||||
/* Walk through files :D */
|
/* Walk through files :D */
|
||||||
|
var reqPath *RequestPath
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
/* If regex match in restricted files || requested hidden */
|
reqPath = NewRequestPath(responder.Request.RootDir(), file.Name())
|
||||||
if _, ok := hidden[file.Name()]; ok {
|
|
||||||
|
/* If hidden file, or restricted file, continue! */
|
||||||
|
if isHiddenFile(hidden, file.Name()) || isRestrictedFile(reqPath.Relative()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If requires remap, do so! */
|
||||||
|
Config.FileSystem.ReverseRemapRequestPath(reqPath)
|
||||||
|
|
||||||
/* Handle file, directory or ignore others */
|
/* Handle file, directory or ignore others */
|
||||||
switch {
|
switch {
|
||||||
case file.Mode() & os.ModeDir != 0:
|
case file.Mode() & os.ModeDir != 0:
|
||||||
/* Directory -- create directory listing */
|
/* Directory -- create directory listing */
|
||||||
itemPath := responder.Request.PathJoinSelector(file.Name())
|
dirContents = append(dirContents, buildLine(TypeDirectory, file.Name(), reqPath.Selector(), responder.Host.Name(), responder.Host.Port())...)
|
||||||
dirContents = append(dirContents, buildLine(TypeDirectory, file.Name(), itemPath, responder.Host.Name(), responder.Host.Port())...)
|
|
||||||
|
|
||||||
case file.Mode() & os.ModeType == 0:
|
case file.Mode() & os.ModeType == 0:
|
||||||
/* Regular file -- find item type and creating listing */
|
/* Regular file -- find item type and creating listing */
|
||||||
itemPath := responder.Request.PathJoinSelector(file.Name())
|
itemPath := reqPath.Selector()
|
||||||
itemType := getItemType(itemPath)
|
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:
|
default:
|
||||||
/* Ignore */
|
/* Ignore */
|
||||||
@ -168,6 +173,11 @@ func listDir(responder *Responder, hidden map[string]bool) *GophorError {
|
|||||||
return responder.WriteFlush(append(dirContents, Config.FooterText...))
|
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. */
|
/* Took a leaf out of go-gopher's book here. */
|
||||||
type byName []os.FileInfo
|
type byName []os.FileInfo
|
||||||
func (s byName) Len() int { return len(s) }
|
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 */
|
/* Add name, truncate name if too long */
|
||||||
if len(name) > Config.PageWidth {
|
if len(name) > Config.PageWidth {
|
||||||
ret += name[:Config.PageWidth-5]+"...\t"
|
ret += name[:Config.PageWidth-5]+"..."+Tab
|
||||||
} else {
|
} else {
|
||||||
ret += name+"\t"
|
ret += name+Tab
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add selector. If too long use err, skip if empty */
|
/* Add selector. If too long use err, skip if empty */
|
||||||
selectorLen := len(selector)
|
selectorLen := len(selector)
|
||||||
if selectorLen > MaxSelectorLen {
|
if selectorLen > MaxSelectorLen {
|
||||||
ret += SelectorErrorStr+"\t"
|
ret += SelectorErrorStr+Tab
|
||||||
} else if selectorLen > 0 {
|
} else if selectorLen > 0 {
|
||||||
ret += selector+"\t"
|
ret += selector+Tab
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add host + port */
|
/* Add host + port */
|
||||||
ret += host+"\t"+port+DOSLineEnd
|
ret += host+Tab+port+DOSLineEnd
|
||||||
|
|
||||||
return []byte(ret)
|
return []byte(ret)
|
||||||
}
|
}
|
||||||
|
19
gophor.go
19
gophor.go
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@ -69,8 +70,11 @@ func setupServer() []*GophorListener {
|
|||||||
logOutput := flag.String("log-output", "stderr", "Change server log file handling (disable|stderr|file)")
|
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)")
|
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.")
|
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.")
|
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).")
|
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.")
|
cacheDisabled := flag.Bool("disable-cache", false, "Disable file caching.")
|
||||||
@ -134,8 +138,6 @@ func setupServer() []*GophorListener {
|
|||||||
if *disableCgi {
|
if *disableCgi {
|
||||||
Config.SysLog.Info("", "CGI support disabled")
|
Config.SysLog.Info("", "CGI support disabled")
|
||||||
Config.CgiEnabled = false
|
Config.CgiEnabled = false
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
/* Enable CGI */
|
/* Enable CGI */
|
||||||
Config.SysLog.Info("", "CGI support enabled")
|
Config.SysLog.Info("", "CGI support enabled")
|
||||||
@ -157,7 +159,7 @@ func setupServer() []*GophorListener {
|
|||||||
|
|
||||||
/* If running as root, get ready to drop privileges */
|
/* If running as root, get ready to drop privileges */
|
||||||
if syscall.Getuid() == 0 || syscall.Getgid() == 0 {
|
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 */
|
/* Enter server dir */
|
||||||
@ -176,11 +178,11 @@ func setupServer() []*GophorListener {
|
|||||||
|
|
||||||
l, err := BeginGophorListen(*serverBindAddr, *serverHostname, strconv.Itoa(*serverPort), strconv.Itoa(*serverFwdPort), *serverRoot)
|
l, err := BeginGophorListen(*serverBindAddr, *serverHostname, strconv.Itoa(*serverPort), strconv.Itoa(*serverFwdPort), *serverRoot)
|
||||||
if err != nil {
|
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)
|
listeners = append(listeners, l)
|
||||||
} else {
|
} else {
|
||||||
Config.SysLog.Fatal("", "No valid port to listen on\n")
|
log.Fatalf("No valid port to listen on\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compile regex statements */
|
/* Compile regex statements */
|
||||||
@ -213,6 +215,9 @@ func setupServer() []*GophorListener {
|
|||||||
cachePolicyFiles(*serverRoot, *serverDescription, *serverAdmin, *serverGeoloc)
|
cachePolicyFiles(*serverRoot, *serverDescription, *serverAdmin, *serverGeoloc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Setup file remappings */
|
||||||
|
Config.FileSystem.Remap, Config.FileSystem.ReverseRemap = parseFileSystemRemaps(*fileSystemRemap)
|
||||||
|
|
||||||
/* Return the created listeners slice :) */
|
/* Return the created listeners slice :) */
|
||||||
return listeners
|
return listeners
|
||||||
}
|
}
|
||||||
@ -220,6 +225,6 @@ func setupServer() []*GophorListener {
|
|||||||
func enterServerDir(path string) {
|
func enterServerDir(path string) {
|
||||||
err := syscall.Chdir(path)
|
err := syscall.Chdir(path)
|
||||||
if err != nil {
|
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...)
|
l.Logger.Fatalf(LogPrefixFatal+prefix+format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Logger implementation that ignores the prefix (e.g. when not printing IPs) */
|
||||||
type LoggerNoPrefix struct {
|
type LoggerNoPrefix struct {
|
||||||
Logger *log.Logger
|
Logger *log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Logger implementation that ignores the prefix (e.g. when not printing IPs) */
|
|
||||||
func (l *LoggerNoPrefix) Info(prefix, format string, args ...interface{}) {
|
func (l *LoggerNoPrefix) Info(prefix, format string, args ...interface{}) {
|
||||||
/* Ignore the prefix */
|
/* Ignore the prefix */
|
||||||
l.Logger.Printf(LogPrefixInfo+format, args...)
|
l.Logger.Printf(LogPrefixInfo+format, args...)
|
||||||
|
32
parse.go
32
parse.go
@ -2,8 +2,40 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"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 */
|
/* Parse a request string into a path and parameters string */
|
||||||
func parseRequestString(request string) (string, []string) {
|
func parseRequestString(request string) (string, []string) {
|
||||||
/* Read up to first '?' and then put rest into single slice string array */
|
/* 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
|
* and filesystem reading
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Root string
|
Root string
|
||||||
Rel string
|
Rel string
|
||||||
Abs string
|
Abs string
|
||||||
|
Select string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRequestPath(rootDir, relPath string) *RequestPath {
|
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 {
|
func (rp *RequestPath) RootDir() string {
|
||||||
@ -34,10 +44,10 @@ func (rp *RequestPath) Absolute() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rp *RequestPath) Selector() string {
|
func (rp *RequestPath) Selector() string {
|
||||||
if rp.Rel == "." {
|
if rp.Select == "." {
|
||||||
return "/"
|
return "/"
|
||||||
} else {
|
} else {
|
||||||
return "/"+rp.Rel
|
return "/"+rp.Select
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user