diff --git a/config.go b/config.go index dca309f..0e0e96c 100644 --- a/config.go +++ b/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 diff --git a/filesystem.go b/filesystem.go index abea917..eb11a98 100644 --- a/filesystem.go +++ b/filesystem.go @@ -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 { diff --git a/filesystem_read.go b/filesystem_read.go index f9fd8ee..3d029f0 100644 --- a/filesystem_read.go +++ b/filesystem_read.go @@ -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) } diff --git a/format.go b/format.go index b3188c7..a712aaa 100644 --- a/format.go +++ b/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) } diff --git a/gophor.go b/gophor.go index 6069fba..40c168a 100644 --- a/gophor.go +++ b/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()) } } diff --git a/logger.go b/logger.go index 150bef5..a9e39f5 100644 --- a/logger.go +++ b/logger.go @@ -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...) diff --git a/parse.go b/parse.go index f58586d..40a89ba 100644 --- a/parse.go +++ b/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 */ diff --git a/request.go b/request.go index 8a27195..fd75650 100644 --- a/request.go +++ b/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 } }