Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
173 lines
4.0 KiB
173 lines
4.0 KiB
// Copyright 2016 The Gitea Authors. All rights reserved. |
|
// Use of this source code is governed by a MIT-style |
|
// license that can be found in the LICENSE file. |
|
|
|
package public |
|
|
|
import ( |
|
"log" |
|
"net/http" |
|
"path" |
|
"path/filepath" |
|
"strings" |
|
|
|
"code.gitea.io/gitea/modules/httpcache" |
|
"code.gitea.io/gitea/modules/setting" |
|
) |
|
|
|
// Options represents the available options to configure the handler. |
|
type Options struct { |
|
Directory string |
|
IndexFile string |
|
SkipLogging bool |
|
FileSystem http.FileSystem |
|
Prefix string |
|
} |
|
|
|
// KnownPublicEntries list all direct children in the `public` directory |
|
var KnownPublicEntries = []string{ |
|
"css", |
|
"fonts", |
|
"img", |
|
"js", |
|
"serviceworker.js", |
|
"vendor", |
|
} |
|
|
|
// Custom implements the static handler for serving custom assets. |
|
func Custom(opts *Options) func(next http.Handler) http.Handler { |
|
return opts.staticHandler(path.Join(setting.CustomPath, "public")) |
|
} |
|
|
|
// staticFileSystem implements http.FileSystem interface. |
|
type staticFileSystem struct { |
|
dir *http.Dir |
|
} |
|
|
|
func newStaticFileSystem(directory string) staticFileSystem { |
|
if !filepath.IsAbs(directory) { |
|
directory = filepath.Join(setting.AppWorkPath, directory) |
|
} |
|
dir := http.Dir(directory) |
|
return staticFileSystem{&dir} |
|
} |
|
|
|
func (fs staticFileSystem) Open(name string) (http.File, error) { |
|
return fs.dir.Open(name) |
|
} |
|
|
|
// StaticHandler sets up a new middleware for serving static files in the |
|
func StaticHandler(dir string, opts *Options) func(next http.Handler) http.Handler { |
|
return opts.staticHandler(dir) |
|
} |
|
|
|
func (opts *Options) staticHandler(dir string) func(next http.Handler) http.Handler { |
|
return func(next http.Handler) http.Handler { |
|
// Defaults |
|
if len(opts.IndexFile) == 0 { |
|
opts.IndexFile = "index.html" |
|
} |
|
// Normalize the prefix if provided |
|
if opts.Prefix != "" { |
|
// Ensure we have a leading '/' |
|
if opts.Prefix[0] != '/' { |
|
opts.Prefix = "/" + opts.Prefix |
|
} |
|
// Remove any trailing '/' |
|
opts.Prefix = strings.TrimRight(opts.Prefix, "/") |
|
} |
|
if opts.FileSystem == nil { |
|
opts.FileSystem = newStaticFileSystem(dir) |
|
} |
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { |
|
if !opts.handle(w, req, opts) { |
|
next.ServeHTTP(w, req) |
|
} |
|
}) |
|
} |
|
} |
|
|
|
// parseAcceptEncoding parse Accept-Encoding: deflate, gzip;q=1.0, *;q=0.5 as compress methods |
|
func parseAcceptEncoding(val string) map[string]bool { |
|
parts := strings.Split(val, ";") |
|
var types = make(map[string]bool) |
|
for _, v := range strings.Split(parts[0], ",") { |
|
types[strings.TrimSpace(v)] = true |
|
} |
|
return types |
|
} |
|
|
|
func (opts *Options) handle(w http.ResponseWriter, req *http.Request, opt *Options) bool { |
|
if req.Method != "GET" && req.Method != "HEAD" { |
|
return false |
|
} |
|
|
|
file := req.URL.Path |
|
// if we have a prefix, filter requests by stripping the prefix |
|
if opt.Prefix != "" { |
|
if !strings.HasPrefix(file, opt.Prefix) { |
|
return false |
|
} |
|
file = file[len(opt.Prefix):] |
|
if file != "" && file[0] != '/' { |
|
return false |
|
} |
|
} |
|
|
|
f, err := opt.FileSystem.Open(file) |
|
if err != nil { |
|
// 404 requests to any known entries in `public` |
|
if path.Base(opts.Directory) == "public" { |
|
parts := strings.Split(file, "/") |
|
if len(parts) < 2 { |
|
return false |
|
} |
|
for _, entry := range KnownPublicEntries { |
|
if entry == parts[1] { |
|
w.WriteHeader(404) |
|
return true |
|
} |
|
} |
|
} |
|
return false |
|
} |
|
defer f.Close() |
|
|
|
fi, err := f.Stat() |
|
if err != nil { |
|
log.Printf("[Static] %q exists, but fails to open: %v", file, err) |
|
return true |
|
} |
|
|
|
// Try to serve index file |
|
if fi.IsDir() { |
|
// Redirect if missing trailing slash. |
|
if !strings.HasSuffix(req.URL.Path, "/") { |
|
http.Redirect(w, req, path.Clean(req.URL.Path+"/"), http.StatusFound) |
|
return true |
|
} |
|
|
|
f, err = opt.FileSystem.Open(file) |
|
if err != nil { |
|
return false // Discard error. |
|
} |
|
defer f.Close() |
|
|
|
fi, err = f.Stat() |
|
if err != nil || fi.IsDir() { |
|
return false |
|
} |
|
} |
|
|
|
if !opt.SkipLogging { |
|
log.Println("[Static] Serving " + file) |
|
} |
|
|
|
if httpcache.HandleFileETagCache(req, w, fi) { |
|
return true |
|
} |
|
|
|
ServeContent(w, req, fi, fi.ModTime(), f) |
|
return true |
|
}
|
|
|