filebrowser/http.go

321 lines
7.6 KiB
Go
Raw Normal View History

2017-06-24 11:12:15 +00:00
package filemanager
import (
2017-07-02 16:40:52 +00:00
"encoding/json"
"html/template"
2017-06-24 11:12:15 +00:00
"net/http"
"os"
"strings"
"time"
"github.com/asdine/storm"
2017-06-24 11:12:15 +00:00
)
// RequestContext contains the needed information to make handlers work.
type RequestContext struct {
*FileManager
User *User
File *file
2017-07-26 17:01:24 +00:00
// On API handlers, Router is the APi handler we want.
Router string
2017-06-27 18:00:58 +00:00
}
2017-07-02 16:40:52 +00:00
// serveHTTP is the main entry point of this HTML application.
func serveHTTP(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
2017-07-02 16:40:52 +00:00
// 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.BaseURL)
2017-06-27 14:44:20 +00:00
if len(p) >= len(r.URL.Path) && c.BaseURL != "" {
2017-06-27 14:44:20 +00:00
return http.StatusNotFound, nil
}
2017-07-02 16:40:52 +00:00
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(
c, w,
c.assets.MustString("sw.js"),
2017-07-02 16:40:52 +00:00
"application/javascript",
)
2017-06-27 14:44:20 +00:00
}
2017-07-02 16:40:52 +00:00
// 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
2017-06-27 14:44:20 +00:00
}
2017-07-02 16:40:52 +00:00
return staticHandler(c, w, r)
2017-06-27 14:44:20 +00:00
}
2017-07-02 16:40:52 +00:00
// 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")
2017-07-08 16:51:47 +00:00
return apiHandler(c, w, r)
2017-06-27 14:44:20 +00:00
}
// If it is a request to the preview and a static website generator is
// active, build the preview.
if strings.HasPrefix(r.URL.Path, "/preview") && c.StaticGen != nil {
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/preview")
return c.StaticGen.Preview(c, w, r)
}
2017-08-13 09:12:13 +00:00
if strings.HasPrefix(r.URL.Path, "/share/") {
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/share/")
return sharePage(c, w, r)
}
2017-07-03 09:40:24 +00:00
// 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")
2017-07-02 16:40:52 +00:00
2017-07-03 09:40:24 +00:00
return renderFile(
c, w,
c.assets.MustString("index.html"),
2017-07-03 09:40:24 +00:00
"text/html",
)
2017-06-27 08:57:11 +00:00
}
2017-07-02 16:40:52 +00:00
// staticHandler handles the static assets path.
func staticHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
2017-07-02 16:40:52 +00:00
if r.URL.Path != "/static/manifest.json" {
http.FileServer(c.assets.HTTPBox()).ServeHTTP(w, r)
2017-07-02 16:40:52 +00:00
return 0, nil
}
2017-07-02 16:40:52 +00:00
return renderFile(
c, w,
c.assets.MustString("static/manifest.json"),
2017-07-02 16:40:52 +00:00
"application/json",
)
}
2017-07-08 16:51:47 +00:00
// apiHandler is the main entry point for the /api endpoint.
func apiHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
2017-07-08 16:51:47 +00:00
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
}
2017-07-26 10:28:59 +00:00
c.Router, r.URL.Path = splitURL(r.URL.Path)
2017-07-08 16:51:47 +00:00
if !c.User.Allowed(r.URL.Path) {
2017-07-08 16:51:47 +00:00
return http.StatusForbidden, nil
}
if c.StaticGen != nil {
// If we are using the 'magic url' for the settings,
// we should redirect the request for the acutual path.
if r.URL.Path == "/settings" {
r.URL.Path = c.StaticGen.SettingsPath()
}
// Executes the Static website generator hook.
code, err := c.StaticGen.Hook(c, w, r)
if code != 0 || err != nil {
return code, err
}
}
if c.Router == "checksum" || c.Router == "download" {
2017-07-08 16:51:47 +00:00
var err error
c.File, err = getInfo(r.URL, c.FileManager, c.User)
2017-07-08 16:51:47 +00:00
if err != nil {
return errorToHTTP(err, false), err
}
}
var code int
var err error
switch c.Router {
2017-07-08 16:51:47 +00:00
case "download":
code, err = downloadHandler(c, w, r)
2017-07-08 16:51:47 +00:00
case "checksum":
code, err = checksumHandler(c, w, r)
2017-07-08 16:51:47 +00:00
case "command":
code, err = command(c, w, r)
2017-07-08 16:51:47 +00:00
case "search":
code, err = search(c, w, r)
2017-07-08 16:51:47 +00:00
case "resource":
code, err = resourceHandler(c, w, r)
2017-07-08 16:51:47 +00:00
case "users":
code, err = usersHandler(c, w, r)
case "settings":
code, err = settingsHandler(c, w, r)
case "share":
code, err = shareHandler(c, w, r)
default:
code = http.StatusNotFound
}
return code, err
2017-07-08 16:51:47 +00:00
}
// 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) {
2017-07-02 16:40:52 +00:00
query := r.URL.Query().Get("algo")
val, err := c.File.Checksum(query)
if err == errInvalidOption {
return http.StatusBadRequest, err
} else if err != nil {
return http.StatusInternalServerError, err
}
w.Write([]byte(val))
return 0, nil
}
2017-07-26 10:28:59 +00:00
// splitURL splits the path and returns everything that stands
2017-07-08 16:51:47 +00:00
// before the first slash and everything that goes after.
2017-07-26 10:28:59 +00:00
func splitURL(path string) (string, string) {
2017-07-08 16:51:47 +00:00
if path == "" {
return "", ""
}
path = strings.TrimPrefix(path, "/")
i := strings.Index(path, "/")
if i == -1 {
return "", path
}
2017-07-19 06:43:04 +00:00
return path[0:i], path[i:]
2017-07-08 16:51:47 +00:00
}
2017-07-02 16:40:52 +00:00
// renderFile renders a file using a template with some needed variables.
func renderFile(c *RequestContext, w http.ResponseWriter, file string, contentType string) (int, error) {
2017-07-29 09:54:05 +00:00
tpl := template.Must(template.New("file").Parse(file))
2017-07-02 16:40:52 +00:00
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
2017-06-25 14:19:23 +00:00
err := tpl.Execute(w, map[string]interface{}{
"BaseURL": c.RootURL(),
"StaticGen": c.staticgen,
})
2017-07-02 16:40:52 +00:00
if err != nil {
return http.StatusInternalServerError, err
}
2017-06-25 14:19:23 +00:00
2017-06-27 18:00:58 +00:00
return 0, nil
2017-06-27 08:28:29 +00:00
}
2017-06-25 14:19:23 +00:00
func sharePage(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
var s shareLink
err := c.db.One("Hash", r.URL.Path, &s)
if err == storm.ErrNotFound {
return renderFile(
c, w,
c.assets.MustString("static/share/404.html"),
"text/html",
)
}
if err != nil {
return http.StatusInternalServerError, err
}
if s.Expires && s.ExpireDate.Before(time.Now()) {
c.db.DeleteStruct(&s)
return renderFile(
c, w,
c.assets.MustString("static/share/404.html"),
"text/html",
)
}
r.URL.Path = s.Path
info, err := os.Stat(s.Path)
if err != nil {
return errorToHTTP(err, false), err
}
c.File = &file{
Path: s.Path,
Name: info.Name(),
ModTime: info.ModTime(),
Mode: info.Mode(),
IsDir: info.IsDir(),
Size: info.Size(),
}
dl := r.URL.Query().Get("dl")
if dl == "" || dl == "0" {
tpl := template.Must(template.New("file").Parse(c.assets.MustString("static/share/index.html")))
w.Header().Set("Content-Type", "text/html; charset=utf-8")
err := tpl.Execute(w, map[string]interface{}{
"BaseURL": c.RootURL(),
"File": c.File,
})
if err != nil {
return http.StatusInternalServerError, err
}
return 0, nil
}
return downloadHandler(c, w, r)
}
2017-07-02 16:40:52 +00:00
// 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
2017-06-27 08:28:29 +00:00
}
2017-06-25 14:19:23 +00:00
2017-06-27 18:00:58 +00:00
// matchURL checks if the first URL matches the second.
func matchURL(first, second string) bool {
first = strings.ToLower(first)
second = strings.ToLower(second)
2017-06-27 14:44:20 +00:00
2017-06-27 18:00:58 +00:00
return strings.HasPrefix(first, second)
2017-06-27 08:28:29 +00:00
}
2017-06-25 14:19:23 +00:00
2017-06-27 18:00:58 +00:00
// errorToHTTP converts errors to HTTP Status Code.
func errorToHTTP(err error, gone bool) int {
switch {
2017-07-02 16:40:52 +00:00
case err == nil:
return http.StatusOK
2017-06-27 18:00:58 +00:00
case os.IsPermission(err):
return http.StatusForbidden
case os.IsNotExist(err):
if !gone {
return http.StatusNotFound
2017-06-27 14:44:20 +00:00
}
2017-06-27 18:00:58 +00:00
return http.StatusGone
case os.IsExist(err):
2017-07-04 17:10:41 +00:00
return http.StatusConflict
2017-06-27 18:00:58 +00:00
default:
return http.StatusInternalServerError
2017-06-27 14:44:20 +00:00
}
2017-06-25 14:19:23 +00:00
}