| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 | package middlewareimport (	"fmt"	"html/template"	"net/http"	"net/url"	"os"	"path"	"path/filepath"	"strings"	"github.com/labstack/echo"	"github.com/labstack/gommon/bytes")type (	// StaticConfig defines the config for Static middleware.	StaticConfig struct {		// Skipper defines a function to skip middleware.		Skipper Skipper		// Root directory from where the static content is served.		// Required.		Root string `yaml:"root"`		// Index file for serving a directory.		// Optional. Default value "index.html".		Index string `yaml:"index"`		// Enable HTML5 mode by forwarding all not-found requests to root so that		// SPA (single-page application) can handle the routing.		// Optional. Default value false.		HTML5 bool `yaml:"html5"`		// Enable directory browsing.		// Optional. Default value false.		Browse bool `yaml:"browse"`	})const html = `<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <meta http-equiv="X-UA-Compatible" content="ie=edge">  <title>{{ .Name }}</title>  <style>    body {			font-family: Menlo, Consolas, monospace;			padding: 48px;		}		header {			padding: 4px 16px;			font-size: 24px;		}    ul {			list-style-type: none;			margin: 0;    	padding: 20px 0 0 0;			display: flex;			flex-wrap: wrap;    }    li {			width: 300px;			padding: 16px;		}		li a {			display: block;			overflow: hidden;			white-space: nowrap;			text-overflow: ellipsis;			text-decoration: none;			transition: opacity 0.25s;		}		li span {			color: #707070; 			font-size: 12px;		}		li a:hover {			opacity: 0.50;		}		.dir {			color: #E91E63;		}		.file {			color: #673AB7;		}  </style></head><body>	<header>		{{ .Name }}	</header>	<ul>		{{ range .Files }}		<li>		{{ if .Dir }}			{{ $name := print .Name "/" }}			<a class="dir" href="{{ $name }}">{{ $name }}</a>			{{ else }}			<a class="file" href="{{ .Name }}">{{ .Name }}</a>			<span>{{ .Size }}</span>		{{ end }}		</li>		{{ end }}  </ul></body></html>`var (	// DefaultStaticConfig is the default Static middleware config.	DefaultStaticConfig = StaticConfig{		Skipper: DefaultSkipper,		Index:   "index.html",	})// Static returns a Static middleware to serves static content from the provided// root directory.func Static(root string) echo.MiddlewareFunc {	c := DefaultStaticConfig	c.Root = root	return StaticWithConfig(c)}// StaticWithConfig returns a Static middleware with config.// See `Static()`.func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {	// Defaults	if config.Root == "" {		config.Root = "." // For security we want to restrict to CWD.	}	if config.Skipper == nil {		config.Skipper = DefaultStaticConfig.Skipper	}	if config.Index == "" {		config.Index = DefaultStaticConfig.Index	}	// Index template	t, err := template.New("index").Parse(html)	if err != nil {		panic(fmt.Sprintf("echo: %v", err))	}	return func(next echo.HandlerFunc) echo.HandlerFunc {		return func(c echo.Context) (err error) {			if config.Skipper(c) {				return next(c)			}			p := c.Request().URL.Path			if strings.HasSuffix(c.Path(), "*") { // When serving from a group, e.g. `/static*`.				p = c.Param("*")			}			p, err = url.PathUnescape(p)			if err != nil {				return			}			name := filepath.Join(config.Root, path.Clean("/"+p)) // "/"+ for security			fi, err := os.Stat(name)			if err != nil {				if os.IsNotExist(err) {					if err = next(c); err != nil {						if he, ok := err.(*echo.HTTPError); ok {							if config.HTML5 && he.Code == http.StatusNotFound {								return c.File(filepath.Join(config.Root, config.Index))							}						}						return					}				}				return			}			if fi.IsDir() {				index := filepath.Join(name, config.Index)				fi, err = os.Stat(index)				if err != nil {					if config.Browse {						return listDir(t, name, c.Response())					}					if os.IsNotExist(err) {						return next(c)					}					return				}				return c.File(index)			}			return c.File(name)		}	}}func listDir(t *template.Template, name string, res *echo.Response) (err error) {	file, err := os.Open(name)	if err != nil {		return	}	files, err := file.Readdir(-1)	if err != nil {		return	}	// Create directory index	res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)	data := struct {		Name  string		Files []interface{}	}{		Name: name,	}	for _, f := range files {		data.Files = append(data.Files, struct {			Name string			Dir  bool			Size string		}{f.Name(), f.IsDir(), bytes.Format(f.Size())})	}	return t.Execute(res, data)}
 |