matterbridge/vendor/github.com/labstack/echo/v4/middleware/static.go

278 lines
5.9 KiB
Go
Raw Normal View History

2017-02-18 14:00:46 -08:00
package middleware
import (
"errors"
2017-02-18 14:00:46 -08:00
"fmt"
"html/template"
2017-12-07 14:00:56 -08:00
"net/http"
"net/url"
2017-02-18 14:00:46 -08:00
"os"
2017-06-05 15:01:05 -07:00
"path"
2017-02-18 14:00:46 -08:00
"path/filepath"
2017-06-05 15:01:05 -07:00
"strings"
2017-02-18 14:00:46 -08:00
"github.com/labstack/echo/v4"
"github.com/labstack/gommon/bytes"
2017-02-18 14:00:46 -08:00
)
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.
2018-02-20 15:48:10 -08:00
Root string `yaml:"root"`
2017-02-18 14:00:46 -08:00
// Index file for serving a directory.
// Optional. Default value "index.html".
2018-02-20 15:48:10 -08:00
Index string `yaml:"index"`
2017-02-18 14:00:46 -08:00
// 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.
2018-02-20 15:48:10 -08:00
HTML5 bool `yaml:"html5"`
2017-02-18 14:00:46 -08:00
// Enable directory browsing.
// Optional. Default value false.
2018-02-20 15:48:10 -08:00
Browse bool `yaml:"browse"`
2021-03-20 14:40:23 -07:00
// Enable ignoring of the base of the URL path.
// Example: when assigning a static middleware to a non root path group,
// the filesystem path is not doubled
// Optional. Default value false.
IgnoreBase bool `yaml:"ignoreBase"`
2021-05-29 15:25:30 -07:00
// Filesystem provides access to the static content.
// Optional. Defaults to http.Dir(config.Root)
Filesystem http.FileSystem `yaml:"-"`
2017-02-18 14:00:46 -08:00
}
)
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>
`
2017-02-18 14:00:46 -08:00
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
2017-06-05 15:01:05 -07:00
if config.Root == "" {
config.Root = "." // For security we want to restrict to CWD.
}
2017-02-18 14:00:46 -08:00
if config.Skipper == nil {
config.Skipper = DefaultStaticConfig.Skipper
}
if config.Index == "" {
config.Index = DefaultStaticConfig.Index
}
2021-05-29 15:25:30 -07:00
if config.Filesystem == nil {
config.Filesystem = http.Dir(config.Root)
config.Root = "."
}
2017-02-18 14:00:46 -08:00
// Index template
t, err := template.New("index").Parse(html)
if err != nil {
panic(fmt.Sprintf("echo: %v", err))
}
2017-02-18 14:00:46 -08:00
return func(next echo.HandlerFunc) echo.HandlerFunc {
2017-12-07 14:00:56 -08:00
return func(c echo.Context) (err error) {
2017-06-05 15:01:05 -07:00
if config.Skipper(c) {
return next(c)
}
2017-02-18 14:00:46 -08:00
2017-06-05 15:01:05 -07:00
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)
2017-12-07 14:00:56 -08:00
if err != nil {
return
}
2021-03-20 14:40:23 -07:00
name := filepath.Join(config.Root, filepath.Clean("/"+p)) // "/"+ for security
if config.IgnoreBase {
routePath := path.Base(strings.TrimRight(c.Path(), "/*"))
baseURLPath := path.Base(p)
if baseURLPath == routePath {
i := strings.LastIndex(name, routePath)
name = name[:i] + strings.Replace(name[i:], routePath, "", 1)
}
}
2017-06-05 15:01:05 -07:00
2021-05-29 15:25:30 -07:00
file, err := openFile(config.Filesystem, name)
2017-02-18 14:00:46 -08:00
if err != nil {
2021-05-29 15:25:30 -07:00
if !os.IsNotExist(err) {
return err
}
if err = next(c); err == nil {
return err
}
var he *echo.HTTPError
if !(errors.As(err, &he) && config.HTML5 && he.Code == http.StatusNotFound) {
2021-05-29 15:25:30 -07:00
return err
}
file, err = openFile(config.Filesystem, filepath.Join(config.Root, config.Index))
if err != nil {
return err
2017-02-18 14:00:46 -08:00
}
}
2021-05-29 15:25:30 -07:00
defer file.Close()
info, err := file.Stat()
if err != nil {
return err
}
2017-06-05 15:01:05 -07:00
2021-05-29 15:25:30 -07:00
if info.IsDir() {
index, err := openFile(config.Filesystem, filepath.Join(name, config.Index))
2017-06-05 15:01:05 -07:00
if err != nil {
if config.Browse {
2021-05-29 15:25:30 -07:00
return listDir(t, name, file, c.Response())
2017-06-05 15:01:05 -07:00
}
2021-05-29 15:25:30 -07:00
2017-06-05 15:01:05 -07:00
if os.IsNotExist(err) {
return next(c)
}
2017-02-18 14:00:46 -08:00
}
2017-06-05 15:01:05 -07:00
2021-05-29 15:25:30 -07:00
defer index.Close()
info, err = index.Stat()
if err != nil {
return err
}
return serveFile(c, index, info)
2017-02-18 14:00:46 -08:00
}
2017-06-05 15:01:05 -07:00
2021-05-29 15:25:30 -07:00
return serveFile(c, file, info)
2017-02-18 14:00:46 -08:00
}
}
}
2021-05-29 15:25:30 -07:00
func openFile(fs http.FileSystem, name string) (http.File, error) {
pathWithSlashes := filepath.ToSlash(name)
return fs.Open(pathWithSlashes)
}
func serveFile(c echo.Context, file http.File, info os.FileInfo) error {
http.ServeContent(c.Response(), c.Request(), info.Name(), info.ModTime(), file)
return nil
}
func listDir(t *template.Template, name string, dir http.File, res *echo.Response) (err error) {
files, err := dir.Readdir(-1)
2017-02-18 14:00:46 -08:00
if err != nil {
2017-12-07 14:00:56 -08:00
return
2017-02-18 14:00:46 -08:00
}
// Create directory index
2017-02-18 14:00:46 -08:00
res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
data := struct {
Name string
Files []interface{}
}{
Name: name,
2017-02-18 14:00:46 -08:00
}
for _, f := range files {
data.Files = append(data.Files, struct {
Name string
Dir bool
Size string
}{f.Name(), f.IsDir(), bytes.Format(f.Size())})
2017-02-18 14:00:46 -08:00
}
return t.Execute(res, data)
2017-02-18 14:00:46 -08:00
}