Skip to content

Commit 293ed73

Browse files
committed
feat: add option --force-dir-slash
Use this option to force redirect directory list page without "/" suffix in the URL to the URL with the suffix.
1 parent 43a82f4 commit 293ed73

File tree

14 files changed

+147
-27
lines changed

14 files changed

+147
-27
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ ghfs [options]
116116
Could be useful if server is behind a reverse proxy and
117117
received the request without proxying path stripped.
118118
119+
-/|--force-dir-slash [<status-code>=301]
120+
If a directory list page is requested without tailing "/" in the URL,
121+
redirect to the URL with the suffix.
122+
119123
--default-sort <sortBy>
120124
Default sort rule for files and directories.
121125
Available sort key:

README.zh-CN.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ ghfs [选项]
114114
在指定的URL子路径下提供服务。
115115
如果服务器在反向代理之后,且收到的请求并未去除代理路径前缀,可能较有用。
116116
117+
-/|--force-dir-slash [<状态码>=301]
118+
如果在请求目录列表页时URL没有以“/”结尾,重定向到带有该结尾的URL。
119+
117120
--default-sort <排序规则>
118121
指定文件和目录的默认排序规则。
119122
可用的排序key:

src/param/cli.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ func init() {
3030
err = options.AddFlagValues("prefixurls", "--prefix", "", nil, "serve files under URL path instead of /")
3131
serverErrHandler.CheckFatal(err)
3232

33+
err = options.AddFlagsValue("forcedirslash", []string{"-/", "--force-dir-slash"}, "GHFS_FORCE_DIR_SLASH", "", "auto redirect directory with \"/\" suffix")
34+
serverErrHandler.CheckFatal(err)
35+
3336
opt = goNixArgParser.NewFlagValueOption("defaultsort", "--default-sort", "GHFS_DEFAULT_SORT", "/n", "default sort for files and directories")
3437
opt.Description = "Available sort key:\n- `n` sort by name ascending\n- `N` sort by name descending\n- `e` sort by type(suffix) ascending\n- `E` sort by type(suffix) descending\n- `s` sort by size ascending\n- `S` sort by size descending\n- `t` sort by modify time ascending\n- `T` sort by modify time descending\n- `_` no sort\nDirectory sort:\n- `/<key>` directories before files\n- `<key>/` directories after files\n- `<key>` directories mixed with files\n"
3538
err = options.Add(opt)
@@ -293,6 +296,12 @@ func doParseCli() []*Param {
293296
prefixurls, _ := result.GetStrings("prefixurls")
294297
param.PrefixUrls = normalizeUrlPaths(prefixurls)
295298

299+
// force dir slash
300+
if result.HasKey("forcedirslash") {
301+
forceDirSlash, _ := result.GetString("forcedirslash")
302+
param.ForceDirSlash = normalizeRedirectCode(forceDirSlash)
303+
}
304+
296305
// dir indexes
297306
dirIndexes, _ := result.GetStrings("dirindexes")
298307
param.DirIndexes = normalizeFilenames(dirIndexes)

src/param/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ type Param struct {
1515
Root string
1616
EmptyRoot bool
1717

18-
PrefixUrls []string
18+
PrefixUrls []string
19+
ForceDirSlash int
1920

2021
DefaultSort string
2122
DirIndexes []string

src/param/strUtil.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package param
33
import (
44
"../util"
55
"path/filepath"
6+
"strconv"
67
"strings"
78
"unicode/utf8"
89
)
@@ -295,3 +296,11 @@ func normalizeHttpsPort(httpsPort string, ListensTLS []string) (string, bool) {
295296

296297
return "", false
297298
}
299+
300+
func normalizeRedirectCode(input string) int {
301+
code, _ := strconv.Atoi(input)
302+
if code <= 300 || code > 399 {
303+
return 301
304+
}
305+
return code
306+
}

src/serverHandler/handler.go

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@ type pathStrings struct {
2121
}
2222

2323
type handler struct {
24-
root string
25-
emptyRoot bool
26-
globalHsts bool
27-
globalHttps bool
28-
httpsPort string // with prefix ":"
29-
defaultSort string
30-
aliasPrefix string
24+
root string
25+
emptyRoot bool
26+
forceDirSlash int
27+
globalHsts bool
28+
globalHttps bool
29+
httpsPort string // with prefix ":"
30+
defaultSort string
31+
aliasPrefix string
3132

3233
dirIndexes []string
3334
aliases aliases
@@ -129,6 +130,11 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
129130
return
130131
}
131132

133+
if data.NeedDirSlashRedirect {
134+
h.redirectWithSlashSuffix(w, r, data.prefixReqPath)
135+
return
136+
}
137+
132138
header(w, data.Headers)
133139

134140
if data.CanCors {
@@ -194,14 +200,15 @@ func newHandler(
194200
}
195201

196202
h := &handler{
197-
root: root,
198-
emptyRoot: emptyRoot,
199-
globalHsts: p.GlobalHsts,
200-
globalHttps: p.GlobalHttps,
201-
httpsPort: p.HttpsPort,
202-
defaultSort: p.DefaultSort,
203-
aliasPrefix: aliasPrefix,
204-
aliases: aliases,
203+
root: root,
204+
emptyRoot: emptyRoot,
205+
forceDirSlash: p.ForceDirSlash,
206+
globalHsts: p.GlobalHsts,
207+
globalHttps: p.GlobalHttps,
208+
httpsPort: p.HttpsPort,
209+
defaultSort: p.DefaultSort,
210+
aliasPrefix: aliasPrefix,
211+
aliases: aliases,
205212

206213
dirIndexes: p.DirIndexes,
207214

src/serverHandler/redirect.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package serverHandler
2+
3+
import "net/http"
4+
5+
func (h *handler) redirectWithSlashSuffix(w http.ResponseWriter, r *http.Request, pathWithoutSlashSuffix string) {
6+
target := pathWithoutSlashSuffix + "/"
7+
if len(r.URL.RawQuery) > 0 {
8+
target += "?" + r.URL.RawQuery
9+
}
10+
11+
http.Redirect(w, r, target, h.forceDirSlash)
12+
}

src/serverHandler/responseData.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type itemHtml struct {
2525
}
2626

2727
type responseData struct {
28+
prefixReqPath string
2829
rawReqPath string
2930
handlerReqPath string
3031

@@ -68,6 +69,8 @@ type responseData struct {
6869
SortState SortState
6970
Context *pathContext
7071

72+
NeedDirSlashRedirect bool
73+
7174
Lang string
7275
Trans *i18n.Translation
7376
}
@@ -191,9 +194,9 @@ func (h *handler) mergeAlias(
191194
return subItems, aliasSubItems, errs
192195
}
193196

194-
func getCurrDirRelPath(reqPath, rawReqPath string) string {
195-
if len(reqPath) == 1 && len(rawReqPath) > 1 && rawReqPath[len(rawReqPath)-1] != '/' {
196-
return "./" + path.Base(rawReqPath) + "/"
197+
func getCurrDirRelPath(reqPath, prefixReqPath string) string {
198+
if len(reqPath) == 1 && len(prefixReqPath) > 1 && prefixReqPath[len(prefixReqPath)-1] != '/' {
199+
return "./" + path.Base(prefixReqPath) + "/"
197200
} else {
198201
return "./"
199202
}
@@ -291,6 +294,7 @@ func dereferenceSymbolLinks(reqFsPath string, subItems []os.FileInfo) (errs []er
291294
func (h *handler) getResponseData(r *http.Request) *responseData {
292295
var errs []error
293296

297+
prefixReqPath := r.URL.RawPath
294298
rawReqPath := r.URL.Path
295299
tailSlash := rawReqPath[len(rawReqPath)-1] == '/'
296300

@@ -334,7 +338,7 @@ func (h *handler) getResponseData(r *http.Request) *responseData {
334338
status := http.StatusOK
335339
isRoot := rawReqPath == "/"
336340

337-
currDirRelPath := getCurrDirRelPath(rawReqPath, r.URL.RawPath)
341+
currDirRelPath := getCurrDirRelPath(rawReqPath, prefixReqPath)
338342
pathEntries := getPathEntries(currDirRelPath, rawReqPath, tailSlash)
339343
var rootRelPath string
340344
if len(pathEntries) > 0 {
@@ -349,7 +353,9 @@ func (h *handler) getResponseData(r *http.Request) *responseData {
349353
status = getStatusByErr(_statErr)
350354
}
351355

352-
indexFile, indexItem, _statIdxErr := h.statIndexFile(rawReqPath, reqFsPath, item, authSuccess)
356+
needDirSlashRedirect := h.forceDirSlash > 0 && prefixReqPath[len(prefixReqPath)-1] != '/' && !shouldServeAsContent(file, item)
357+
358+
indexFile, indexItem, _statIdxErr := h.statIndexFile(rawReqPath, reqFsPath, item, authSuccess && !needDirSlashRedirect)
353359
if _statIdxErr != nil {
354360
errs = append(errs, _statIdxErr)
355361
status = getStatusByErr(_statIdxErr)
@@ -367,13 +373,13 @@ func (h *handler) getResponseData(r *http.Request) *responseData {
367373

368374
itemName := getItemName(item, r)
369375

370-
subItems, _readdirErr := readdir(file, item, authSuccess && !isMutate && allowAccess && needResponseBody(r.Method))
376+
subItems, _readdirErr := readdir(file, item, authSuccess && !isMutate && !needDirSlashRedirect && allowAccess && needResponseBody(r.Method))
371377
if _readdirErr != nil {
372378
errs = append(errs, _readdirErr)
373379
status = http.StatusInternalServerError
374380
}
375381

376-
subItems, aliasSubItems, _mergeErrs := h.mergeAlias(rawReqPath, item, subItems, authSuccess && allowAccess)
382+
subItems, aliasSubItems, _mergeErrs := h.mergeAlias(rawReqPath, item, subItems, authSuccess && !needDirSlashRedirect && allowAccess)
377383
if len(_mergeErrs) > 0 {
378384
errs = append(errs, _mergeErrs...)
379385
status = http.StatusInternalServerError
@@ -407,6 +413,7 @@ func (h *handler) getResponseData(r *http.Request) *responseData {
407413
}
408414

409415
return &responseData{
416+
prefixReqPath: prefixReqPath,
410417
rawReqPath: rawReqPath,
411418
handlerReqPath: reqPath,
412419

@@ -449,5 +456,7 @@ func (h *handler) getResponseData(r *http.Request) *responseData {
449456
SubItemPrefix: subItemPrefix,
450457
SortState: sortState,
451458
Context: context,
459+
460+
NeedDirSlashRedirect: needDirSlashRedirect,
452461
}
453462
}

test/case/011.upload.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ cleanup() {
66

77
source "$root"/lib.bash
88

9-
"$ghfs" -l 3003 -r "$fs"/uploaded --upload /1 --upload-dir "$fs"/uploaded/2 --mkdir /2 2>/dev/null &
9+
"$ghfs" -l 3003 -r "$fs"/uploaded --upload /1 --upload-dir "$fs"/uploaded/2 --mkdir /2 -E '' &
1010
sleep 0.05 # wait server ready
1111
cleanup
1212

test/case/012.alias.upload.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ cleanup() {
77

88
source "$root"/lib.bash
99

10-
"$ghfs" -l 3003 -r "$fs"/vhost1 --alias :/my/upload:"$fs"/uploaded/2 --upload / --mkdir / &> /dev/null &
10+
"$ghfs" -l 3003 -r "$fs"/vhost1 --alias :/my/upload:"$fs"/uploaded/2 --upload / --mkdir / -E '' &
1111
sleep 0.05 # wait server ready
1212
cleanup
1313

0 commit comments

Comments
 (0)