diff --git a/cache.go b/cache.go index be44951..1f7476f 100644 --- a/cache.go +++ b/cache.go @@ -49,6 +49,12 @@ func checkCacheFreshness() { Config.FileCache.CacheMutex.Unlock() } +/* FileCache: + * 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 FileCache struct { CacheMap *FixedMap CacheMutex sync.RWMutex @@ -62,6 +68,7 @@ func (fc *FileCache) Init(size int, fileSizeMax float64) { } func (fc *FileCache) FetchRegular(request *FileSystemRequest) ([]byte, *GophorError) { + /* Calls fc.Fetch() but with the filecontents init function for a regular file */ return fc.Fetch(request, func(path string) FileContents { contents := new(RegularFileContents) contents.path = path @@ -70,6 +77,7 @@ func (fc *FileCache) FetchRegular(request *FileSystemRequest) ([]byte, *GophorEr } func (fc *FileCache) FetchGophermap(request *FileSystemRequest) ([]byte, *GophorError) { + /* Calls fc.Fetch() but with the filecontents init function for a gophermap */ return fc.Fetch(request, func(path string) FileContents { contents := new(GophermapContents) contents.path = path @@ -82,7 +90,8 @@ func (fc *FileCache) Fetch(request *FileSystemRequest, newFileContents func(stri fc.CacheMutex.RLock() file := fc.CacheMap.Get(request.Path) - /* TODO: work on efficiency */ + /* TODO: work on efficiency. improve use of mutex?? */ + if file != nil { /* File in cache -- before doing anything get file read lock */ file.RLock() @@ -107,7 +116,9 @@ func (fc *FileCache) Fetch(request *FileSystemRequest, newFileContents func(stri file.RLock() } } else { - /* Before we do ANYTHING, we need to check file-size on disk */ + /* 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) if err != nil { /* Error stat'ing file, unlock read mutex then return error */ @@ -122,7 +133,7 @@ func (fc *FileCache) Fetch(request *FileSystemRequest, newFileContents func(stri file = NewFile(contents) /* NOTE: file isn't in cache yet so no need to lock file write mutex - * before loading from disk + * before loading contents from disk */ gophorErr := file.LoadContents() if gophorErr != nil { diff --git a/config.go b/config.go index fe92837..8692f89 100644 --- a/config.go +++ b/config.go @@ -5,12 +5,15 @@ import ( "log" ) +/* ServerConfig: + * Holds onto global server configuration details + * and any data objects we want to keep in memory + * (e.g. loggers, restricted files regular expressions + * and file cache) + */ type ServerConfig struct { /* Base settings */ RootDir string - Hostname string - Port string - TlsPort string /* Caps.txt information */ Description string diff --git a/conn.go b/conn.go index bc9820f..4b4e0ea 100644 --- a/conn.go +++ b/conn.go @@ -4,16 +4,15 @@ import ( "net" ) -/* Data structure to hold specific host details - * for passing on later - */ +/* Data structure to hold specific host details */ type ConnHost struct { Name string Port string } -/* Simple wrapper to Listener that generates - * GophorConn instances on each accept +/* Simple wrapper to Listener that holds onto virtual + * host information and generates GophorConn + * instances on each accept */ type GophorListener struct { Listener net.Listener diff --git a/error.go b/error.go index 58c5afb..3e55eea 100644 --- a/error.go +++ b/error.go @@ -4,9 +4,7 @@ import ( "fmt" ) -/* - * Client error data structure - */ +/* Simple error code type defs */ type ErrorCode int type ErrorResponseCode int const ( @@ -43,11 +41,13 @@ const ( NoResponse ErrorResponseCode = iota ) +/* Simple GophorError data structure to wrap another error */ type GophorError struct { Code ErrorCode Err error } +/* Convert error code to string */ func (e *GophorError) Error() string { var str string switch e.Code { @@ -91,6 +91,7 @@ func (e *GophorError) Error() string { } } +/* Convert a gophor error code to appropriate error response code */ func gophorErrorToResponseCode(code ErrorCode) ErrorResponseCode { switch code { case PathEnumerationErr: @@ -109,7 +110,7 @@ func gophorErrorToResponseCode(code ErrorCode) ErrorResponseCode { case DirListErr: return ErrorResponse404 - /* These are errors sending, no point trying to send error */ + /* These are errors _while_ sending, no point trying to send error */ case SocketWriteErr: return NoResponse case SocketWriteCountErr: @@ -129,6 +130,7 @@ func gophorErrorToResponseCode(code ErrorCode) ErrorResponseCode { } } +/* Generates gopher protocol compatible error response from our code */ func generateGopherErrorResponseFromCode(code ErrorCode) []byte { responseCode := gophorErrorToResponseCode(code) if responseCode == NoResponse { @@ -137,11 +139,13 @@ func generateGopherErrorResponseFromCode(code ErrorCode) []byte { return generateGopherErrorResponse(responseCode) } +/* Generates gopher protocol compatible error response for response code */ func generateGopherErrorResponse(code ErrorResponseCode) []byte { b := buildError(code.String()) return append(b, []byte(LastLine)...) } +/* Error response code to string */ func (e ErrorResponseCode) String() string { switch e { case ErrorResponse200: diff --git a/filecontents.go b/filecontents.go index 3723db6..bbebee8 100644 --- a/filecontents.go +++ b/filecontents.go @@ -17,10 +17,14 @@ type RegularFileContents struct { } 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 } func (fc *RegularFileContents) Load() *GophorError { + /* Load the file into memory */ var gophorErr *GophorError fc.contents, gophorErr = bufferedRead(fc.path) return gophorErr @@ -42,8 +46,10 @@ type GophermapContents struct { } func (gc *GophermapContents) Render(request *FileSystemRequest) []byte { - /* We don't just want to read the contents, but also - * execute any included gophermap execute lines + /* 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. */ returnContents := make([]byte, 0) for _, line := range gc.sections { @@ -54,10 +60,14 @@ func (gc *GophermapContents) Render(request *FileSystemRequest) []byte { returnContents = append(returnContents, content...) } + /* Finally we end render with last line */ + returnContents = append(returnContents, []byte(LastLine)...) + return returnContents } func (gc *GophermapContents) Load() *GophorError { + /* Load the gophermap into memory as gophermap sections */ var gophorErr *GophorError gc.sections, gophorErr = readGophermap(gc.path) return gophorErr @@ -146,7 +156,7 @@ func readGophermap(path string) ([]GophermapSection, *GophorError) { sections = append(sections, NewGophermapText(buildInfoLine(line))) case TypeTitle: - /* Reformat title line to send as info title */ + /* Reformat title line to send as info line with appropriate selector */ if !titleAlready { sections = append(sections, NewGophermapText(buildLine(TypeInfo, line[1:], "TITLE", NullHost, NullPort))) titleAlready = true @@ -207,9 +217,7 @@ func readGophermap(path string) ([]GophermapSection, *GophorError) { return false default: - /* Replace pre-set strings */ - line = strings.Replace(line, ReplaceStrHostname, Config.Hostname, -1) - line = strings.Replace(line, ReplaceStrPort, Config.Port, -1) + /* Just append to sections slice as gophermap text */ sections = append(sections, NewGophermapText([]byte(line+DOSLineEnd))) } @@ -224,7 +232,8 @@ func readGophermap(path string) ([]GophermapSection, *GophorError) { /* 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 context. + * type ALWAYS comes last, at least in the gophermap handled by this call + * to readGophermap(). */ if dirListing != nil { dirListing.Hidden = hidden diff --git a/fixedmap.go b/fixedmap.go index b0f0408..0734c8e 100644 --- a/fixedmap.go +++ b/fixedmap.go @@ -4,10 +4,12 @@ import ( "container/list" ) +/* TODO: work on efficiency. use our own lower level data structure? */ + /* FixedMap: * A fixed size map that pushes the last * used value from the stack if size limit - * is reached and user attempts .Put() + * is reached. */ type FixedMap struct { Map map[string]*MapElement @@ -32,6 +34,7 @@ func NewFixedMap(size int) *FixedMap { return fm } +/* Get file in map for key, or nil */ func (fm *FixedMap) Get(key string) *File { elem, ok := fm.Map[key] if ok { @@ -41,6 +44,8 @@ func (fm *FixedMap) Get(key string) *File { } } +/* Put file in map as key, pushing out last file + * if size limit reached */ func (fm *FixedMap) Put(key string, value *File) { element := fm.List.PushFront(key) fm.Map[key] = &MapElement{ element, value } @@ -60,6 +65,7 @@ func (fm *FixedMap) Put(key string, value *File) { } } +/* Try delete element, else do nothing */ func (fm *FixedMap) Remove(key string) { elem, ok := fm.Map[key] if !ok { diff --git a/format.go b/format.go index 77d5dac..a7d21a3 100644 --- a/format.go +++ b/format.go @@ -72,12 +72,13 @@ func buildError(selector string) []byte { return []byte(ret) } +/* Build gopher compliant line with supplied information */ func buildLine(t ItemType, name, selector, host string, port string) []byte { ret := string(t) /* Add name, truncate name if too long */ if len(name) > Config.PageWidth { - ret += name[:Config.PageWidth-4]+"...\t" + ret += name[:Config.PageWidth-5]+"...\t" } else { ret += name+"\t" } @@ -96,16 +97,12 @@ func buildLine(t ItemType, name, selector, host string, port string) []byte { return []byte(ret) } +/* Build gopher compliant info line */ func buildInfoLine(content string) []byte { return buildLine(TypeInfo, content, NullSelector, NullHost, NullPort) } -/* getItemType(name string) ItemType: - * Here we use an empty function pointer, and set the correct - * function to be used during the restricted files regex parsing. - * This negates need to check if RestrictedFilesRegex is nil every - * single call. - */ +/* Get item type for named file on disk */ func getItemType(name string) ItemType { /* Split, name MUST be lower */ split := strings.Split(strings.ToLower(name), ".") @@ -128,6 +125,7 @@ func getItemType(name string) ItemType { } } +/* Parse line type from contents */ func parseLineType(line string) ItemType { lineLen := len(line) diff --git a/fs.go b/fs.go index 7d34833..ff9b289 100644 --- a/fs.go +++ b/fs.go @@ -107,6 +107,7 @@ type FileContents interface { Clear() } +/* Perform simple buffered read on a file at path */ func bufferedRead(path string) ([]byte, *GophorError) { /* Open file */ fd, err := os.Open(path) @@ -144,6 +145,7 @@ func bufferedRead(path string) ([]byte, *GophorError) { return contents, nil } +/* Perform buffered read on file at path, then scan through with supplied iterator func */ func bufferedScan(path string, scanIterator func(*bufio.Scanner) bool) *GophorError { /* First, read raw file contents */ contents, gophorErr := bufferedRead(path) @@ -155,7 +157,7 @@ func bufferedScan(path string, scanIterator func(*bufio.Scanner) bool) *GophorEr reader := bytes.NewReader(contents) scanner := bufio.NewScanner(reader) - /* If contains DOS line-endings, use them! Else, Unix line-endings */ + /* If contains DOS line-endings, split by DOS! Else, split by Unix */ if bytes.Contains(contents, []byte(DOSLineEnd)) { scanner.Split(dosLineEndSplitter) } else { diff --git a/gophor.go b/gophor.go index 0268de9..7c4b773 100644 --- a/gophor.go +++ b/gophor.go @@ -100,8 +100,6 @@ func setupServer() []*GophorListener { /* Setup the server configuration instance and enter as much as we can right now */ Config = new(ServerConfig) Config.RootDir = *serverRoot - Config.Hostname = *serverHostname - Config.Port = strconv.Itoa(*serverPort) Config.Description = *serverDescription Config.AdminEmail = *serverAdmin Config.Geolocation = *serverGeoloc @@ -143,13 +141,15 @@ func setupServer() []*GophorListener { listeners := make([]*GophorListener, 0) /* If requested, setup unencrypted listener */ - if Config.Port != NullPort { - l, err := BeginGophorListen(*serverBindAddr, *serverHostname, Config.Port) + if *serverPort != 0 { + l, err := BeginGophorListen(*serverBindAddr, *serverHostname, strconv.Itoa(*serverPort)) if err != nil { Config.LogSystemFatal("Error setting up (unencrypted) listener: %s\n", err.Error()) } Config.LogSystem("Listening (unencrypted): gopher://%s\n", l.Addr()) listeners = append(listeners, l) + } else { + Config.LogSystemFatal("No valid port to listen on :(\n") } /* Drop privileges to retrieved UID + GID */ diff --git a/html.go b/html.go index cf36804..1001ebd 100644 --- a/html.go +++ b/html.go @@ -1,17 +1,5 @@ package main -/* -func generateHtmlErrorResponse(code ErrorResponseCode) []byte { - content := - "\n"+ - "\n"+ - code.String()+"\n"+ - "\n"+ - "\n" - return generateHttpResponse(code, content) -} -*/ - func generateHtmlRedirect(url string) []byte { content := "\n"+ diff --git a/regex.go b/regex.go index 5fad8cd..359d1df 100644 --- a/regex.go +++ b/regex.go @@ -6,9 +6,9 @@ import ( ) func compileUserRestrictedFilesRegex(restrictedFiles string) []*regexp.Regexp { - /* Try compiling the RestrictedFilesRegex from finalRegex */ Config.LogSystem("Compiling restricted file regular expressions\n") + /* Return slice */ restrictedFilesRegex := make([]*regexp.Regexp, 0) /* Split the user supplied RestrictedFiles string by new-line */ @@ -23,6 +23,7 @@ func compileUserRestrictedFilesRegex(restrictedFiles string) []*regexp.Regexp { return restrictedFilesRegex } +/* Iterate through restricted file expressions, check if file _is_ restricted */ func isRestrictedFile(name string) bool { for _, regex := range Config.RestrictedFiles { if regex.MatchString(name) { diff --git a/worker.go b/worker.go index d21c212..4f4bad1 100644 --- a/worker.go +++ b/worker.go @@ -42,7 +42,6 @@ func (worker *Worker) Serve() { buf := make([]byte, SocketReadBufSize) received := make([]byte, 0) - /* Buffered read from listener */ iter := 0 for { /* Buffered read from listener */ @@ -78,7 +77,7 @@ func (worker *Worker) Serve() { if gophorErr != nil { Config.LogSystemError("%s\n", gophorErr.Error()) - /* Try generate response bytes from error code */ + /* Generate response bytes from error code */ response := generateGopherErrorResponseFromCode(gophorErr.Code) /* If we got response bytes to send? SEND 'EM! */ @@ -112,7 +111,7 @@ func (worker *Worker) RespondGopher(data []byte) *GophorError { /* According to Gopher spec, only read up to first Tab or Crlf */ dataStr := readUpToFirstTabOrCrlf(data) - /* Handle URL request if so */ + /* Handle URL request if so. TODO: this is so unelegant... */ lenBefore := len(dataStr) dataStr = strings.TrimPrefix(dataStr, "URL:") switch len(dataStr) { @@ -127,7 +126,7 @@ func (worker *Worker) RespondGopher(data []byte) *GophorError { /* Sanitize supplied path */ requestPath := sanitizePath(dataStr) - /* Handle policy files */ + /* Handle policy files. TODO: this is so unelegant... */ switch requestPath { case "/"+CapsTxtStr: return worker.SendRaw(generateCapsTxt()) @@ -142,8 +141,9 @@ func (worker *Worker) RespondGopher(data []byte) *GophorError { return &GophorError{ FileOpenErr, err } } - - /* If not empty requestPath, check file type */ + /* If not empty requestPath, check file type. + * Default type is directory. + */ fileType := FileTypeDir if requestPath != "." { stat, err := file.Stat() @@ -164,9 +164,7 @@ func (worker *Worker) RespondGopher(data []byte) *GophorError { /* Don't need the file handle anymore */ file.Close() - /* TODO: work on efficiency */ - - /* Handle file type */ + /* Handle file type. TODO: work on efficiency */ response := make([]byte, 0) switch fileType { /* Directory */