Skip to content

Commit d932e2a

Browse files
committed
Added directory autoindex
Signed-off-by: Vishal Rana <[email protected]>
1 parent 9d11990 commit d932e2a

File tree

3 files changed

+91
-8
lines changed

3 files changed

+91
-8
lines changed

context.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ func (c *Context) File(path, name string, attachment bool) (err error) {
243243
if attachment {
244244
c.response.Header().Set(ContentDisposition, "attachment; filename="+name)
245245
}
246-
if err = serveFile(dir, file, c); err != nil {
246+
if err = c.echo.serveFile(dir, file, c); err != nil {
247247
c.response.Header().Del(ContentDisposition)
248248
}
249249
return

echo.go

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"runtime"
1313
"strings"
1414
"sync"
15+
"time"
1516

1617
"encoding/xml"
1718

@@ -34,6 +35,7 @@ type (
3435
pool sync.Pool
3536
debug bool
3637
hook http.HandlerFunc
38+
autoIndex bool
3739
router *Router
3840
}
3941

@@ -142,7 +144,7 @@ const (
142144

143145
WebSocket = "websocket"
144146

145-
indexFile = "index.html"
147+
indexPage = "index.html"
146148
)
147149

148150
var (
@@ -178,6 +180,8 @@ var (
178180
return NewHTTPError(http.StatusMethodNotAllowed)
179181
}
180182

183+
unixEpochTime = time.Unix(0, 0)
184+
181185
logger = log.New("echo")
182186
)
183187

@@ -269,6 +273,12 @@ func (e *Echo) Debug() bool {
269273
return e.debug
270274
}
271275

276+
// AutoIndex enables automatically creates a directory listing if the directory
277+
// doesn't contain an index page.
278+
func (e *Echo) AutoIndex(on bool) {
279+
e.autoIndex = on
280+
}
281+
272282
// Hook registers a callback which is invoked from `Echo#ServerHTTP` as the first
273283
// statement. Hook is useful if you want to modify response/response objects even
274284
// before it hits the router or any middleware.
@@ -386,19 +396,19 @@ func (e *Echo) Static(path, dir string) {
386396
// ServeDir serves files from a directory.
387397
func (e *Echo) ServeDir(path, dir string) {
388398
e.Get(path+"*", func(c *Context) error {
389-
return serveFile(dir, c.P(0), c) // Param `_*`
399+
return e.serveFile(dir, c.P(0), c) // Param `_*`
390400
})
391401
}
392402

393403
// ServeFile serves a file.
394404
func (e *Echo) ServeFile(path, file string) {
395405
e.Get(path, func(c *Context) error {
396406
dir, file := filepath.Split(file)
397-
return serveFile(dir, file, c)
407+
return e.serveFile(dir, file, c)
398408
})
399409
}
400410

401-
func serveFile(dir, file string, c *Context) error {
411+
func (e *Echo) serveFile(dir, file string, c *Context) (err error) {
402412
fs := http.Dir(dir)
403413
f, err := fs.Open(file)
404414
if err != nil {
@@ -408,16 +418,49 @@ func serveFile(dir, file string, c *Context) error {
408418

409419
fi, _ := f.Stat()
410420
if fi.IsDir() {
411-
file = filepath.Join(file, indexFile)
421+
if checkLastModified(c.response, c.request, fi.ModTime()) {
422+
return
423+
}
424+
d := f
425+
426+
// Index file
427+
file = filepath.Join(file, indexPage)
412428
f, err = fs.Open(file)
413429
if err != nil {
430+
if e.autoIndex {
431+
// Auto index
432+
return listDir(d, c)
433+
}
414434
return NewHTTPError(http.StatusForbidden)
415435
}
416-
fi, _ = f.Stat()
436+
fi, _ = f.Stat() // Index file stat
417437
}
418438

419439
http.ServeContent(c.response, c.request, fi.Name(), fi.ModTime(), f)
420-
return nil
440+
return
441+
}
442+
443+
func listDir(d http.File, c *Context) (err error) {
444+
dirs, err := d.Readdir(-1)
445+
if err != nil {
446+
return err
447+
}
448+
449+
// Create directory index
450+
w := c.Response()
451+
w.Header().Set(ContentType, TextHTMLCharsetUTF8)
452+
fmt.Fprintf(w, "<pre>\n")
453+
for _, d := range dirs {
454+
name := d.Name()
455+
color := "#212121"
456+
if d.IsDir() {
457+
color = "#e91e63"
458+
name += "/"
459+
}
460+
fmt.Fprintf(w, "<a href=\"%s\" style=\"color: %s;\">%s</a>\n", name, color, name)
461+
}
462+
fmt.Fprintf(w, "</pre>\n")
463+
return
421464
}
422465

423466
// Group creates a new sub router with prefix. It inherits all properties from
@@ -650,3 +693,25 @@ func (binder) Bind(r *http.Request, i interface{}) (err error) {
650693
}
651694
return
652695
}
696+
697+
// Source: net/http/fs.go
698+
func checkLastModified(w http.ResponseWriter, r *http.Request, modtime time.Time) bool {
699+
if modtime.IsZero() || modtime.Equal(unixEpochTime) {
700+
// If the file doesn't have a modtime (IsZero), or the modtime
701+
// is obviously garbage (Unix time == 0), then ignore modtimes
702+
// and don't process the If-Modified-Since header.
703+
return false
704+
}
705+
706+
// The Date-Modified header truncates sub-second precision, so
707+
// use mtime < t+1s instead of mtime <= t to check for unmodified.
708+
if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.Before(t.Add(1*time.Second)) {
709+
h := w.Header()
710+
delete(h, "Content-Type")
711+
delete(h, "Content-Length")
712+
w.WriteHeader(http.StatusNotModified)
713+
return true
714+
}
715+
w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat))
716+
return false
717+
}

website/content/guide/customization.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,24 @@ SetOutput sets the output destination for the global logger.
3737

3838
SetLogLevel sets the log level for global logger. The default value is `log.INFO`.
3939

40+
### Auto index
41+
42+
`Echo#AutoIndex(on bool)`
43+
44+
AutoIndex enables automatically creates a directory listing if the directory doesn't
45+
contain an index page.
46+
47+
*Example*
48+
49+
```go
50+
e := echo.New()
51+
e.AutoIndex(true)
52+
e.ServerDir("/", "/Users/vr/Projects/echo")
53+
e.Run(":1323")
54+
```
55+
56+
Browse to `http://localhost:1323/` to see the directory listing.
57+
4058
### Hook
4159

4260
`Echo#Hook(http.HandlerFunc)`

0 commit comments

Comments
 (0)