Fixing up some directory traversal attacks

This commit is contained in:
dtookey 2023-07-31 10:57:41 -04:00
parent b11be2f32c
commit 7a42f90b3f
3 changed files with 106 additions and 16 deletions

View File

@ -10,12 +10,24 @@ import (
type ( type (
ConfigType string ConfigType string
// ConfigEntry defines a single route configuration entry.
// Used to configure routes from a JSON config file.
ConfigEntry struct { ConfigEntry struct {
// ConfigType indicates the type of route - Text, Image etc.
ConfigType ConfigType ConfigType ConfigType
// UrlPattern is the URL regex pattern to match for the route.
UrlPattern string UrlPattern string
// FileLocation is the file path or directory to serve files from.
FileLocation string FileLocation string
// UseBuiltinCache indicates whether to use the built-in LRU cache.
UseBuiltinCache bool UseBuiltinCache bool
} }
Config struct { Config struct {
ListeningAddress string ListeningAddress string
Routes []ConfigEntry Routes []ConfigEntry

View File

@ -25,12 +25,19 @@ type ErrorResponse struct {
} }
type ( 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 { VinegarServlet struct {
Port string Port string
Routes []*VinegarRoute Routes []*VinegarRoute
ErrorRoutes map[int]*TemplateRoute 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 { VinegarRoute struct {
Pattern *regexp.Regexp Pattern *regexp.Regexp
Handler VinegarHandlerFunction Handler VinegarHandlerFunction

View File

@ -5,20 +5,67 @@ import (
util "geniuscartel.xyz/vinegar/vinegarUtil" util "geniuscartel.xyz/vinegar/vinegarUtil"
"net/http" "net/http"
"path" "path"
"path/filepath"
"strings" "strings"
) )
type ( type (
// FileRoute implements a static file serving route.
// It serves files from a given file path.
FileRoute struct { FileRoute struct {
*VinegarRoute
// 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 srv *VinegarServlet
// fileRoot is the base file path to serve files from.
fileRoot string fileRoot string
// UseCache indicates whether to use caching for this route.
UseCache bool 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 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 { var NewTextRoute RouteConstructor = func(servlet *VinegarServlet, urlPattern string, pathlike string, useCache bool) *FileRoute {
defaultPrune := strings.Replace(urlPattern, ".*", "", -1) defaultPrune := strings.Replace(urlPattern, ".*", "", -1)
route := FileRoute{srv: servlet, fileRoot: pathlike, UseCache: useCache} route := FileRoute{srv: servlet, fileRoot: pathlike, UseCache: useCache}
@ -61,6 +108,30 @@ var NewSingleFileRoute RouteConstructor = func(servlet *VinegarServlet, urlPatte
return &route 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 { func createSingleFileServletFunction(route *FileRoute) VinegarHandlerFunction {
var fun VinegarHandlerFunction = func(w http.ResponseWriter, req *http.Request) { var fun VinegarHandlerFunction = func(w http.ResponseWriter, req *http.Request) {
@ -68,9 +139,9 @@ func createSingleFileServletFunction(route *FileRoute) VinegarHandlerFunction {
var exists bool var exists bool
if route.UseCache { if route.UseCache {
cache, exists = route.Cache.Get("") cache, exists = route.VinegarRoute.Cache.Get("")
} else { } else {
cache, exists = route.Cache.GetFresh("") cache, exists = route.VinegarRoute.Cache.GetFresh("")
} }
if !exists { if !exists {
@ -98,17 +169,17 @@ func createSingleFileServletFunction(route *FileRoute) VinegarHandlerFunction {
func createCompressibleFileServletFunction(route *FileRoute, basePattern string, pathlike string) VinegarHandlerFunction { func createCompressibleFileServletFunction(route *FileRoute, basePattern string, pathlike string) VinegarHandlerFunction {
var fun VinegarHandlerFunction = func(w http.ResponseWriter, req *http.Request) { var fun VinegarHandlerFunction = func(w http.ResponseWriter, req *http.Request) {
stub := strings.Replace(req.URL.Path, basePattern, "", 1) 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 //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 { if !exists {
content, fileExists := util.GetDiskContent(resourcePath) content, fileExists := util.GetDiskContent(resourcePath)
if fileExists { if fileExists {
if route.UseCache { if route.UseCache {
route.Cache.Put(stub, resourcePath) route.VinegarRoute.Cache.Put(stub, resourcePath)
cachedContent, _ = route.Cache.Get(stub) cachedContent, _ = route.VinegarRoute.Cache.Get(stub)
} else { } else {
w.Header().Add(ContentTypeHeaderKey, util.GuessMimetype(stub)) w.Header().Add(ContentTypeHeaderKey, util.GuessMimetype(stub))
w.Write(*content) w.Write(*content)
@ -138,12 +209,12 @@ func createCompressibleFileServletFunction(route *FileRoute, basePattern string,
func createUncompressedFileServletFunction(route *FileRoute, basePattern string, pathlike string) VinegarHandlerFunction { func createUncompressedFileServletFunction(route *FileRoute, basePattern string, pathlike string) VinegarHandlerFunction {
var fun VinegarHandlerFunction = func(w http.ResponseWriter, req *http.Request) { 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) resourcePath := path.Join(pathlike, stub)
entry, exists := route.Cache.Get(stub) entry, exists := route.VinegarRoute.Cache.Get(stub)
if !exists { if !exists {
route.Cache.Put(stub, resourcePath) route.VinegarRoute.Cache.Put(stub, resourcePath)
entry, exists = route.Cache.Get(stub) entry, exists = route.VinegarRoute.Cache.Get(stub)
} }
if exists { if exists {