mirror of
https://github.com/filebrowser/filebrowser.git
synced 2024-06-07 23:00:43 +00:00
9ef2229835
Former-commit-id: f8f0efe6986155fb2277b6b471f6387f28a8d8c2 [formerly 33103c80bc2d9ee3e8b4139254d0a3edbfc1721b] [formerly 258653deda36f045230cca84e23e880041b48aba [formerly a3c70f7930
]]
Former-commit-id: d46ca2362ad160c59b090fa71d1d2bbeb3425cf6 [formerly 51c7941a77cbbdee4ede206edd98981b82a56bba]
Former-commit-id: 08fde98df060f53f8f4f7163956c0ce6ef78f434
257 lines
6.0 KiB
Go
257 lines
6.0 KiB
Go
package filemanager
|
|
|
|
import (
|
|
"encoding/json"
|
|
"html/template"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// RequestContext contains the needed information to make handlers work.
|
|
type RequestContext struct {
|
|
User *User
|
|
FM *FileManager
|
|
FI *file
|
|
// On API handlers, Router is the APi handler we want. TODO: review this
|
|
Router string
|
|
}
|
|
|
|
// serveHTTP is the main entry point of this HTML application.
|
|
func serveHTTP(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
// Checks if the URL contains the baseURL and strips it. Otherwise, it just
|
|
// returns a 404 error because we're not supposed to be here!
|
|
p := strings.TrimPrefix(r.URL.Path, c.FM.BaseURL)
|
|
|
|
if len(p) >= len(r.URL.Path) && c.FM.BaseURL != "" {
|
|
return http.StatusNotFound, nil
|
|
}
|
|
|
|
r.URL.Path = p
|
|
|
|
// Check if this request is made to the service worker. If so,
|
|
// pass it through a template to add the needed variables.
|
|
if r.URL.Path == "/sw.js" {
|
|
return renderFile(
|
|
w,
|
|
c.FM.assets.MustString("sw.js"),
|
|
"application/javascript",
|
|
c,
|
|
)
|
|
}
|
|
|
|
// Checks if this request is made to the static assets folder. If so, and
|
|
// if it is a GET request, returns with the asset. Otherwise, returns
|
|
// a status not implemented.
|
|
if matchURL(r.URL.Path, "/static") {
|
|
if r.Method != http.MethodGet {
|
|
return http.StatusNotImplemented, nil
|
|
}
|
|
|
|
return staticHandler(c, w, r)
|
|
}
|
|
|
|
// Checks if this request is made to the API and directs to the
|
|
// API handler if so.
|
|
if matchURL(r.URL.Path, "/api") {
|
|
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/api")
|
|
return apiHandler(c, w, r)
|
|
}
|
|
|
|
// Any other request should show the index.html file.
|
|
w.Header().Set("x-frame-options", "SAMEORIGIN")
|
|
w.Header().Set("x-content-type", "nosniff")
|
|
w.Header().Set("x-xss-protection", "1; mode=block")
|
|
|
|
return renderFile(
|
|
w,
|
|
c.FM.assets.MustString("index.html"),
|
|
"text/html",
|
|
c,
|
|
)
|
|
}
|
|
|
|
// staticHandler handles the static assets path.
|
|
func staticHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
if r.URL.Path != "/static/manifest.json" {
|
|
http.FileServer(c.FM.assets.HTTPBox()).ServeHTTP(w, r)
|
|
return 0, nil
|
|
}
|
|
|
|
return renderFile(
|
|
w,
|
|
c.FM.assets.MustString("static/manifest.json"),
|
|
"application/json",
|
|
c,
|
|
)
|
|
}
|
|
|
|
// apiHandler is the main entry point for the /api endpoint.
|
|
func apiHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
if r.URL.Path == "/auth/get" {
|
|
return authHandler(c, w, r)
|
|
}
|
|
|
|
if r.URL.Path == "/auth/renew" {
|
|
return renewAuthHandler(c, w, r)
|
|
}
|
|
|
|
valid, _ := validateAuth(c, r)
|
|
if !valid {
|
|
return http.StatusForbidden, nil
|
|
}
|
|
|
|
c.Router, r.URL.Path = cleanURL(r.URL.Path)
|
|
|
|
if !c.User.Allowed(r.URL.Path) {
|
|
return http.StatusForbidden, nil
|
|
}
|
|
|
|
for _, p := range c.FM.Plugins {
|
|
code, err := p.BeforeAPI(c, w, r)
|
|
if code != 0 || err != nil {
|
|
return code, err
|
|
}
|
|
}
|
|
|
|
if c.Router == "checksum" || c.Router == "download" {
|
|
var err error
|
|
c.FI, err = getInfo(r.URL, c.FM, c.User)
|
|
if err != nil {
|
|
return errorToHTTP(err, false), err
|
|
}
|
|
}
|
|
|
|
var code int
|
|
var err error
|
|
|
|
switch c.Router {
|
|
case "download":
|
|
code, err = downloadHandler(c, w, r)
|
|
case "checksum":
|
|
code, err = checksumHandler(c, w, r)
|
|
case "command":
|
|
code, err = command(c, w, r)
|
|
case "search":
|
|
code, err = search(c, w, r)
|
|
case "resource":
|
|
code, err = resourceHandler(c, w, r)
|
|
case "users":
|
|
code, err = usersHandler(c, w, r)
|
|
case "commands":
|
|
code, err = commandsHandler(c, w, r)
|
|
case "plugins":
|
|
code, err = pluginsHandler(c, w, r)
|
|
}
|
|
|
|
if code >= 300 || err != nil {
|
|
return code, err
|
|
}
|
|
|
|
for _, p := range c.FM.Plugins {
|
|
code, err := p.AfterAPI(c, w, r)
|
|
if code != 0 || err != nil {
|
|
return code, err
|
|
}
|
|
}
|
|
|
|
return code, err
|
|
}
|
|
|
|
// serveChecksum calculates the hash of a file. Supports MD5, SHA1, SHA256 and SHA512.
|
|
func checksumHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
query := r.URL.Query().Get("algo")
|
|
|
|
val, err := c.FI.Checksum(query)
|
|
if err == errInvalidOption {
|
|
return http.StatusBadRequest, err
|
|
} else if err != nil {
|
|
return http.StatusInternalServerError, err
|
|
}
|
|
|
|
w.Write([]byte(val))
|
|
return 0, nil
|
|
}
|
|
|
|
// cleanURL splits the path and returns everything that stands
|
|
// before the first slash and everything that goes after.
|
|
func cleanURL(path string) (string, string) {
|
|
if path == "" {
|
|
return "", ""
|
|
}
|
|
|
|
path = strings.TrimPrefix(path, "/")
|
|
|
|
i := strings.Index(path, "/")
|
|
if i == -1 {
|
|
return "", path
|
|
}
|
|
|
|
return path[0:i], path[i:]
|
|
}
|
|
|
|
// renderFile renders a file using a template with some needed variables.
|
|
func renderFile(w http.ResponseWriter, file string, contentType string, c *RequestContext) (int, error) {
|
|
functions := template.FuncMap{
|
|
"JS": func(s string) template.JS {
|
|
return template.JS(s)
|
|
},
|
|
}
|
|
|
|
tpl := template.Must(template.New("file").Funcs(functions).Parse(file))
|
|
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
|
|
|
|
err := tpl.Execute(w, map[string]interface{}{
|
|
"BaseURL": c.FM.RootURL(),
|
|
"Plugins": c.FM.Plugins,
|
|
})
|
|
if err != nil {
|
|
return http.StatusInternalServerError, err
|
|
}
|
|
|
|
return 0, nil
|
|
}
|
|
|
|
// renderJSON prints the JSON version of data to the browser.
|
|
func renderJSON(w http.ResponseWriter, data interface{}) (int, error) {
|
|
marsh, err := json.Marshal(data)
|
|
if err != nil {
|
|
return http.StatusInternalServerError, err
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
if _, err := w.Write(marsh); err != nil {
|
|
return http.StatusInternalServerError, err
|
|
}
|
|
|
|
return 0, nil
|
|
}
|
|
|
|
// matchURL checks if the first URL matches the second.
|
|
func matchURL(first, second string) bool {
|
|
first = strings.ToLower(first)
|
|
second = strings.ToLower(second)
|
|
|
|
return strings.HasPrefix(first, second)
|
|
}
|
|
|
|
// errorToHTTP converts errors to HTTP Status Code.
|
|
func errorToHTTP(err error, gone bool) int {
|
|
switch {
|
|
case err == nil:
|
|
return http.StatusOK
|
|
case os.IsPermission(err):
|
|
return http.StatusForbidden
|
|
case os.IsNotExist(err):
|
|
if !gone {
|
|
return http.StatusNotFound
|
|
}
|
|
|
|
return http.StatusGone
|
|
case os.IsExist(err):
|
|
return http.StatusConflict
|
|
default:
|
|
return http.StatusInternalServerError
|
|
}
|
|
}
|