diff --git a/.vscode/launch.json b/.vscode/launch.json index 35e027f..093169e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -21,6 +21,24 @@ "showLog": true, "trace": "log", "dlvFlags": ["--check-go-version=false"] + }, + { + "name": "Daemon", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceFolder}", + "env": { + "GODEBUG": "cgocheck=0", + "_go_daemon": "g_daemon" + }, + "args": [ + "run", + "${file}" + ], + "showLog": true, + "trace": "log", + "dlvFlags": ["--check-go-version=false"] } ] } \ No newline at end of file diff --git a/gohttp.go b/gohttp.go index 5ab387a..4568339 100644 --- a/gohttp.go +++ b/gohttp.go @@ -133,7 +133,7 @@ func LoadConfig() { server := model.HttpServerConfig{} err = decoder.Decode(&server) if err == nil { - server.ConfPath = config + server.ConfPath = filepath.Dir(config) normalizeServer(&server) model.Config.Servers = append(model.Config.Servers, &server) } else { diff --git a/handler/file.go b/handler/file.go index 02eb559..0b0d646 100644 --- a/handler/file.go +++ b/handler/file.go @@ -25,27 +25,34 @@ func (f FileHandler) String() string { } func (f FileHandler) Open(name string) (http.File, error) { l := gologger.GetLogger("filehandler") - l.Debug("access:", name) + l.Debug("Open called: name=", name) if strings.HasPrefix(name, "../") { return nil, errors.New("not permitted") } relatedPath := strings.TrimPrefix(name, f.WPath) - rPath := filepath.Join(f.Root, strings.TrimPrefix(relatedPath, "/")) - l.Debug("access:", rPath) + // When URL ends with "/" (like "/") and WPath matches, we should return + // the directory and let http.FileServer handle the redirect to default file. + // This prevents "http: attempting to traverse a non-directory" error. + if relatedPath == "" && f.Default != "" { + relatedPath = "." + } + + // If relatedPath becomes ".", use Root directly to represent the directory itself + var rPath string + if relatedPath == "." { + rPath = f.Root + } else { + rPath = filepath.Join(f.Root, strings.TrimPrefix(relatedPath, "/")) + } // Resolve symlinks and clean path to prevent path traversal realRoot, _ := filepath.EvalSymlinks(f.Root) - realPath, _ := filepath.EvalSymlinks(filepath.Dir(rPath)) - if realPath != realRoot && !strings.HasPrefix(realPath, realRoot+string(filepath.Separator)) { - return nil, errors.New("not permitted") - } + realRPath, _ := filepath.EvalSymlinks(rPath) - if rPath == f.Root { - if f.Default == "" { - return nil, errors.New("not permit list dir") - } - rPath = filepath.Join(rPath, f.Default) + // Check if the resolved path is within root + if realRPath != realRoot && !strings.HasPrefix(realRPath, realRoot+string(filepath.Separator)) { + return nil, errors.New("not permitted") } fInfo, exists, err := FileExists(rPath) @@ -57,11 +64,13 @@ func (f FileHandler) Open(name string) (http.File, error) { return nil, os.ErrNotExist } - if fInfo.IsDir() && rPath != strings.TrimSuffix(f.Root, "/") { + // If it's a directory and not the root, don't allow listing + if fInfo.IsDir() && rPath != f.Root { return nil, errors.New("not permit list dir") } - if fInfo.Mode() == fs.ModeSymlink { + // Use Lstat to check if the path itself is a symlink (doesn't follow symlinks) + if lstatInfo, err := os.Lstat(rPath); err == nil && lstatInfo.Mode()&fs.ModeSymlink != 0 { return nil, errors.New("not permit follow symbol link") } diff --git a/include/www.teststatic.com.json b/include/www.teststatic.com.json index 76056f9..09fabad 100644 --- a/include/www.teststatic.com.json +++ b/include/www.teststatic.com.json @@ -2,11 +2,11 @@ "name": "teststatic", "server": ["www.teststatic.com"], "port": 8088, - "enable_ssl":true, + "enable_ssl":false, "paths": [ { "path": "/", - "root": "./example", + "root": "../example", "default": "index.html" } ]