From 7a42f90b3f2c886faf5f385b92f48ef5019fcdc6 Mon Sep 17 00:00:00 2001 From: dtookey Date: Mon, 31 Jul 2023 10:57:41 -0400 Subject: [PATCH] Fixing up some directory traversal attacks --- servlet/config.go | 20 +++++++-- servlet/server.go | 7 ++++ servlet/staticRoute.go | 95 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 106 insertions(+), 16 deletions(-) diff --git a/servlet/config.go b/servlet/config.go index 2246f22..9606d7a 100644 --- a/servlet/config.go +++ b/servlet/config.go @@ -9,13 +9,25 @@ import ( ) type ( - ConfigType string + ConfigType string + + // ConfigEntry defines a single route configuration entry. + // Used to configure routes from a JSON config file. ConfigEntry struct { - ConfigType ConfigType - UrlPattern string - FileLocation string + + // ConfigType indicates the type of route - Text, Image etc. + ConfigType ConfigType + + // UrlPattern is the URL regex pattern to match for the route. + UrlPattern string + + // FileLocation is the file path or directory to serve files from. + FileLocation string + + // UseBuiltinCache indicates whether to use the built-in LRU cache. UseBuiltinCache bool } + Config struct { ListeningAddress string Routes []ConfigEntry diff --git a/servlet/server.go b/servlet/server.go index 5cbb0d8..e445a62 100644 --- a/servlet/server.go +++ b/servlet/server.go @@ -25,12 +25,19 @@ type ErrorResponse struct { } type ( + // VinegarServlet is the main server struct that handles HTTP requests and routing. + // It contains the TCP port to listen on, the routes to match requests against, + // and a map of status code to error handling routes. VinegarServlet struct { Port string Routes []*VinegarRoute ErrorRoutes map[int]*TemplateRoute } + // VinegarRoute defines a single route in the router. + // It contains a regex Pattern to match against the URL path, + // a Handler function to call when the route matches, + // and an optional Cache to enable caching for the route. VinegarRoute struct { Pattern *regexp.Regexp Handler VinegarHandlerFunction diff --git a/servlet/staticRoute.go b/servlet/staticRoute.go index a446f44..d9c2c21 100644 --- a/servlet/staticRoute.go +++ b/servlet/staticRoute.go @@ -5,20 +5,67 @@ import ( util "geniuscartel.xyz/vinegar/vinegarUtil" "net/http" "path" + "path/filepath" "strings" ) type ( + // FileRoute implements a static file serving route. + // It serves files from a given file path. FileRoute struct { - *VinegarRoute - srv *VinegarServlet + + // VinegarRoute is the base route containing the URL pattern and handler. + VinegarRoute *VinegarRoute + + // srv is the VinegarServlet instance that this route is attached to. + srv *VinegarServlet + + // fileRoot is the base file path to serve files from. fileRoot string + + // UseCache indicates whether to use caching for this route. UseCache bool } + //RouteConstructor + // + //Params: + // + //servlet - The VinegarServlet instance to add the route to + // + //urlPattern - The URL regex pattern for route to match + // + //pathlike - The file path to serve + // + //useCache - Whether to use caching for this route + // + // A RouteConstructor is a function that accepts a VinegarServlet, urlPattern, file path, and cache option. It uses + // these to construct and return a FileRoute. + // The return value is a FileRoute that will serve the files from the given path. + // + // This function signature allows encapsulating the creation of different types of FileRoutes. It is used to define + // constructor functions for each file type, like NewTextRoute or NewImageRoute. RouteConstructor func(servlet *VinegarServlet, urlPattern string, pathlike string, useCache bool) *FileRoute ) +// NewTextRoute creates a new FileRoute for serving text files. +// +// It handles text files as compressible content, gzipping them when the client sends the Accept-Encoding: gzip header. +// +// Parameters: +// +// servlet - The VinegarServlet instance to attach the route to. +// +// urlPattern - The URL regex pattern that triggers this route. +// +// pathlike - The file path on disk to serve files from. +// +// useCache - Whether to use caching for this route. +// +// Returns: +// +// A FileRoute instance configured for serving text files, added to +// the provided VinegarServlet. var NewTextRoute RouteConstructor = func(servlet *VinegarServlet, urlPattern string, pathlike string, useCache bool) *FileRoute { defaultPrune := strings.Replace(urlPattern, ".*", "", -1) route := FileRoute{srv: servlet, fileRoot: pathlike, UseCache: useCache} @@ -61,6 +108,30 @@ var NewSingleFileRoute RouteConstructor = func(servlet *VinegarServlet, urlPatte return &route } +// createSingleFileServletFunction creates a handler function for serving a single file. +// +// It looks up the file content from the route's cache and writes the appropriate +// headers and file content to the response. +// +// Parameters: +// +// route - The FileRoute instance this handler is attached to. +// +// Returns: +// +// A VinegarHandlerFunction that serves the single file for the provided route. +// +// The handler function checks the route's cache for the file content. +// If caching is enabled, it first checks the cache directly. +// If caching is disabled, it calls GetFresh to avoid the cache. +// +// If the content is not found, it returns a 404 error. +// +// Otherwise, it sets the Content-Type header based on the cache's MIME type, +// and writes the file content to the response. +// +// If the client accepts gzip encoding, it compresses the content before writing. + func createSingleFileServletFunction(route *FileRoute) VinegarHandlerFunction { var fun VinegarHandlerFunction = func(w http.ResponseWriter, req *http.Request) { @@ -68,9 +139,9 @@ func createSingleFileServletFunction(route *FileRoute) VinegarHandlerFunction { var exists bool if route.UseCache { - cache, exists = route.Cache.Get("") + cache, exists = route.VinegarRoute.Cache.Get("") } else { - cache, exists = route.Cache.GetFresh("") + cache, exists = route.VinegarRoute.Cache.GetFresh("") } if !exists { @@ -98,17 +169,17 @@ func createSingleFileServletFunction(route *FileRoute) VinegarHandlerFunction { func createCompressibleFileServletFunction(route *FileRoute, basePattern string, pathlike string) VinegarHandlerFunction { var fun VinegarHandlerFunction = func(w http.ResponseWriter, req *http.Request) { stub := strings.Replace(req.URL.Path, basePattern, "", 1) - cachedContent, exists := route.Cache.Get(stub) + cachedContent, exists := route.VinegarRoute.Cache.Get(stub) //i don't like this logic below. we need to streamline this a lot better. it's a twisty jungle right now - resourcePath := path.Join(pathlike, stub) + resourcePath := path.Join(pathlike, filepath.Clean(stub)) if !exists { content, fileExists := util.GetDiskContent(resourcePath) if fileExists { if route.UseCache { - route.Cache.Put(stub, resourcePath) - cachedContent, _ = route.Cache.Get(stub) + route.VinegarRoute.Cache.Put(stub, resourcePath) + cachedContent, _ = route.VinegarRoute.Cache.Get(stub) } else { w.Header().Add(ContentTypeHeaderKey, util.GuessMimetype(stub)) w.Write(*content) @@ -138,12 +209,12 @@ func createCompressibleFileServletFunction(route *FileRoute, basePattern string, func createUncompressedFileServletFunction(route *FileRoute, basePattern string, pathlike string) VinegarHandlerFunction { var fun VinegarHandlerFunction = func(w http.ResponseWriter, req *http.Request) { - stub := strings.Replace(req.URL.Path, basePattern, "", 1) + stub := filepath.Clean(strings.Replace(req.URL.Path, basePattern, "", 1)) resourcePath := path.Join(pathlike, stub) - entry, exists := route.Cache.Get(stub) + entry, exists := route.VinegarRoute.Cache.Get(stub) if !exists { - route.Cache.Put(stub, resourcePath) - entry, exists = route.Cache.Get(stub) + route.VinegarRoute.Cache.Put(stub, resourcePath) + entry, exists = route.VinegarRoute.Cache.Get(stub) } if exists {