@@ -12,6 +12,7 @@ import (
1212 "runtime"
1313 "strings"
1414 "sync"
15+ "time"
1516
1617 "encoding/xml"
1718
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
148150var (
@@ -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.
387397func (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.
394404func (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+ }
0 commit comments