Mercurial Hosting > d2o
comparison main.go @ 12:84384cccda0e default tip
bubfix subpaths and remove hardcode
| author | atarwn@g5 |
|---|---|
| date | Mon, 23 Mar 2026 12:05:05 +0500 |
| parents | 350589d762a0 |
| children |
comparison
equal
deleted
inserted
replaced
| 11:350589d762a0 | 12:84384cccda0e |
|---|---|
| 190 rr := &respRecorder{ResponseWriter: w, status: 0} | 190 rr := &respRecorder{ResponseWriter: w, status: 0} |
| 191 | 191 |
| 192 host := stripPort(r.Host) | 192 host := stripPort(r.Host) |
| 193 reqPath := path.Clean(r.URL.Path) | 193 reqPath := path.Clean(r.URL.Path) |
| 194 | 194 |
| 195 dirs, caps := h.cfg.Match(host + reqPath) | 195 dirs, caps, subPath := h.cfg.Match(host + reqPath) |
| 196 if dirs == nil { | 196 if dirs == nil { |
| 197 dirs, caps = h.cfg.Match(host) | 197 dirs, caps, _ = h.cfg.Match(host) |
| 198 subPath = reqPath // host-only block: full path is the subpath | |
| 198 } | 199 } |
| 199 if dirs == nil { | 200 if dirs == nil { |
| 200 http.Error(rr, "not found", http.StatusNotFound) | 201 http.Error(rr, "not found", http.StatusNotFound) |
| 201 rr.ensureStatus(http.StatusNotFound) | 202 rr.ensureStatus(http.StatusNotFound) |
| 202 accessPrintf("d2o: %s %s%s -> %d %dB (%s)", r.Method, r.Host, r.URL.RequestURI(), rr.status, rr.bytes, time.Since(start).Truncate(time.Millisecond)) | 203 accessPrintf("d2o: %s %s%s -> %d %dB (%s)", r.Method, r.Host, r.URL.RequestURI(), rr.status, rr.bytes, time.Since(start).Truncate(time.Millisecond)) |
| 203 return | 204 return |
| 204 } | 205 } |
| 205 | 206 |
| 206 h.serve(rr, r, dirs, caps) | 207 h.serve(rr, r, dirs, caps, subPath) |
| 207 rr.ensureStatus(http.StatusOK) | 208 rr.ensureStatus(http.StatusOK) |
| 208 accessPrintf("d2o: %s %s%s -> %d %dB (%s)", r.Method, r.Host, r.URL.RequestURI(), rr.status, rr.bytes, time.Since(start).Truncate(time.Millisecond)) | 209 accessPrintf("d2o: %s %s%s -> %d %dB (%s)", r.Method, r.Host, r.URL.RequestURI(), rr.status, rr.bytes, time.Since(start).Truncate(time.Millisecond)) |
| 209 } | 210 } |
| 210 | 211 |
| 211 func (h *handler) serve(w http.ResponseWriter, r *http.Request, dirs []icf.Directive, caps map[string]string) { | 212 func (h *handler) serve(w http.ResponseWriter, r *http.Request, dirs []icf.Directive, caps map[string]string, subPath string) { |
| 212 var ( | 213 var ( |
| 213 rootDir string | 214 rootDir string |
| 214 rootShow bool | 215 rootShow bool |
| 215 ndex []string | 216 ndex []string |
| 216 fcgiAddr string | 217 fcgiAddr string |
| 275 verbosePrintf("d2o: cgi -> %s (%s)", cgiExec, r.URL.Path) | 276 verbosePrintf("d2o: cgi -> %s (%s)", cgiExec, r.URL.Path) |
| 276 serveCGI(w, r, cgiExec, rootDir) | 277 serveCGI(w, r, cgiExec, rootDir) |
| 277 return | 278 return |
| 278 } | 279 } |
| 279 if rootDir != "" { | 280 if rootDir != "" { |
| 280 fsPath := path.Clean(r.URL.Path) | 281 // subPath is the remainder after the matched path pattern, e.g. for |
| 281 displayPath := fsPath | 282 // block "host/~<user>" and request "/~alice/about.html", subPath="/about.html". |
| 282 if user, ok := caps["user"]; ok && user != "" { | 283 // For host-only blocks it equals the full request path. |
| 283 mount := "/~" + user | 284 displayPath := path.Clean(r.URL.Path) |
| 284 if fsPath == mount || strings.HasPrefix(fsPath, mount+"/") { | |
| 285 trimmed := strings.TrimPrefix(fsPath, mount) | |
| 286 if trimmed == "" { | |
| 287 trimmed = "/" | |
| 288 } | |
| 289 fsPath = trimmed | |
| 290 } | |
| 291 } | |
| 292 | |
| 293 verbosePrintf("d2o: static -> %s (%s)", rootDir, r.URL.Path) | 285 verbosePrintf("d2o: static -> %s (%s)", rootDir, r.URL.Path) |
| 294 serveStatic(w, r, rootDir, rootShow, ndex, fcgiAddr, fcgiPat, cgiExec, cgiPat, fsPath, displayPath) | 286 serveStatic(w, r, rootDir, rootShow, ndex, fcgiAddr, fcgiPat, cgiExec, cgiPat, subPath, displayPath) |
| 295 return | 287 return |
| 296 } | 288 } |
| 297 | 289 |
| 298 http.Error(w, "not found", http.StatusNotFound) | 290 http.Error(w, "not found", http.StatusNotFound) |
| 299 } | 291 } |
| 304 // rootIndex == nil: directory listing forbidden (hide). | 296 // rootIndex == nil: directory listing forbidden (hide). |
| 305 // rootIndex != nil: try each as index candidate; if none found, show listing. | 297 // rootIndex != nil: try each as index candidate; if none found, show listing. |
| 306 func serveStatic(w http.ResponseWriter, r *http.Request, rootDir string, show bool, ndex []string, fcgiAddr, fcgiPat, cgiExec, cgiPat string, fsPath, displayPath string) { | 298 func serveStatic(w http.ResponseWriter, r *http.Request, rootDir string, show bool, ndex []string, fcgiAddr, fcgiPat, cgiExec, cgiPat string, fsPath, displayPath string) { |
| 307 fpath := filepath.Join(rootDir, filepath.FromSlash(path.Clean(fsPath))) | 299 fpath := filepath.Join(rootDir, filepath.FromSlash(path.Clean(fsPath))) |
| 308 | 300 |
| 301 // Use a cloned request with fsPath as URL.Path so that http.ServeFile and | |
| 302 // fcgi/cgi handlers see a path relative to rootDir, not the original URL. | |
| 303 r2 := r.Clone(r.Context()) | |
| 304 r2.URL.Path = fsPath | |
| 305 | |
| 309 info, err := os.Stat(fpath) | 306 info, err := os.Stat(fpath) |
| 310 if os.IsNotExist(err) { | 307 if os.IsNotExist(err) { |
| 311 http.Error(w, "not found", http.StatusNotFound) | 308 http.Error(w, "not found", http.StatusNotFound) |
| 312 return | 309 return |
| 313 } | 310 } |
| 319 if info.IsDir() { | 316 if info.IsDir() { |
| 320 for _, idx := range ndex { | 317 for _, idx := range ndex { |
| 321 idxPath := filepath.Join(fpath, idx) | 318 idxPath := filepath.Join(fpath, idx) |
| 322 if _, err := os.Stat(idxPath); err == nil { | 319 if _, err := os.Stat(idxPath); err == nil { |
| 323 if fcgiAddr != "" && matchGlob(fcgiPat, idx) { | 320 if fcgiAddr != "" && matchGlob(fcgiPat, idx) { |
| 324 r2 := r.Clone(r.Context()) | 321 r3 := r2.Clone(r2.Context()) |
| 325 r2.URL.Path = path.Join(r.URL.Path, idx) | 322 r3.URL.Path = path.Join(fsPath, idx) |
| 326 if err := serveFCGI(w, r2, fcgiAddr, rootDir); err != nil { | 323 if err := serveFCGI(w, r3, fcgiAddr, rootDir); err != nil { |
| 327 log.Printf("d2o: fcgi error: %v", err) | 324 log.Printf("d2o: fcgi error: %v", err) |
| 328 http.Error(w, "gateway error", http.StatusBadGateway) | 325 http.Error(w, "gateway error", http.StatusBadGateway) |
| 329 } | 326 } |
| 330 return | 327 return |
| 331 } | 328 } |
| 332 if cgiExec != "" && matchGlob(cgiPat, idx) { | 329 if cgiExec != "" && matchGlob(cgiPat, idx) { |
| 333 r2 := r.Clone(r.Context()) | 330 r3 := r2.Clone(r2.Context()) |
| 334 r2.URL.Path = path.Join(r.URL.Path, idx) | 331 r3.URL.Path = path.Join(fsPath, idx) |
| 335 serveCGI(w, r2, cgiExec, rootDir) | 332 serveCGI(w, r3, cgiExec, rootDir) |
| 336 return | 333 return |
| 337 } | 334 } |
| 338 http.ServeFile(w, r, idxPath) | 335 http.ServeFile(w, r2, idxPath) |
| 339 return | 336 return |
| 340 } | 337 } |
| 341 } | 338 } |
| 342 if !show { | 339 if !show { |
| 343 http.Error(w, "forbidden", http.StatusForbidden) | 340 http.Error(w, "forbidden", http.StatusForbidden) |
| 344 return | 341 return |
| 345 } | 342 } |
| 346 listDir(w, r, fpath, displayPath) | 343 listDir(w, r2, fpath, displayPath) |
| 347 return | 344 return |
| 348 } | 345 } |
| 349 | 346 |
| 350 http.ServeFile(w, r, fpath) | 347 http.ServeFile(w, r2, fpath) |
| 351 } | 348 } |
| 352 | 349 |
| 353 func listDir(w http.ResponseWriter, r *http.Request, dir, urlPath string) { | 350 func listDir(w http.ResponseWriter, r *http.Request, dir, urlPath string) { |
| 354 entries, err := os.ReadDir(dir) | 351 entries, err := os.ReadDir(dir) |
| 355 if err != nil { | 352 if err != nil { |
