Updates
Former-commit-id: 8220f5660a75af69ee6358af3782416dae9aa185 [formerly 098749492c954879b5a01324076804e74947c780] [formerly 34f6dda3fafc72730b4e44d0c23ade6940bb90d4 [formerly 4b3ebca48c
]]
Former-commit-id: 722e71cd19b9ecceb627cc1e65572839bd2a55c8 [formerly 23d5ac81b9ff1f39b51cc042eb2ac12d2e5da6fc]
Former-commit-id: c4e5ea0d6db7edcfdc3b551e0c43d1c77ef587c3
pull/726/head
parent
69a96e56b7
commit
1e99d3d7c1
46
assets.go
46
assets.go
|
@ -1,46 +0,0 @@
|
||||||
package filemanager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"mime"
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// assetsURL is the url where static assets are served.
|
|
||||||
const assetsURL = "/_internal"
|
|
||||||
|
|
||||||
// Serve provides the needed assets for the front-end
|
|
||||||
func serveAssets(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
// gets the filename to be used with Assets function
|
|
||||||
filename := strings.TrimPrefix(r.URL.Path, assetsURL)
|
|
||||||
|
|
||||||
var file []byte
|
|
||||||
var err error
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(filename, "/css"):
|
|
||||||
filename = strings.Replace(filename, "/css/", "", 1)
|
|
||||||
file, err = c.fm.assets.css.Bytes(filename)
|
|
||||||
case strings.HasPrefix(filename, "/js"):
|
|
||||||
filename = strings.Replace(filename, "/js/", "", 1)
|
|
||||||
file, err = c.fm.assets.js.Bytes(filename)
|
|
||||||
default:
|
|
||||||
err = errors.New("not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusNotFound, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the file extension and its mimetype
|
|
||||||
extension := filepath.Ext(filename)
|
|
||||||
mediatype := mime.TypeByExtension(extension)
|
|
||||||
|
|
||||||
// Write the header with the Content-Type and write the file
|
|
||||||
// content to the buffer
|
|
||||||
w.Header().Set("Content-Type", mediatype)
|
|
||||||
w.Write(file)
|
|
||||||
return 200, nil
|
|
||||||
}
|
|
50
checksum.go
50
checksum.go
|
@ -1,50 +0,0 @@
|
||||||
package filemanager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/sha1"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/sha512"
|
|
||||||
"encoding/hex"
|
|
||||||
e "errors"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// serveChecksum calculates the hash of a file. Supports MD5, SHA1, SHA256 and SHA512.
|
|
||||||
func serveChecksum(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
query := r.URL.Query().Get("checksum")
|
|
||||||
|
|
||||||
file, err := os.Open(c.fi.Path)
|
|
||||||
if err != nil {
|
|
||||||
return errorToHTTP(err, true), err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
var h hash.Hash
|
|
||||||
|
|
||||||
switch query {
|
|
||||||
case "md5":
|
|
||||||
h = md5.New()
|
|
||||||
case "sha1":
|
|
||||||
h = sha1.New()
|
|
||||||
case "sha256":
|
|
||||||
h = sha256.New()
|
|
||||||
case "sha512":
|
|
||||||
h = sha512.New()
|
|
||||||
default:
|
|
||||||
return http.StatusBadRequest, e.New("Unknown HASH type")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(h, file)
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
val := hex.EncodeToString(h.Sum(nil))
|
|
||||||
w.Write([]byte(val))
|
|
||||||
return http.StatusOK, nil
|
|
||||||
}
|
|
95
download.go
95
download.go
|
@ -1,95 +0,0 @@
|
||||||
package filemanager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/mholt/archiver"
|
|
||||||
)
|
|
||||||
|
|
||||||
// download creates an archive in one of the supported formats (zip, tar,
|
|
||||||
// tar.gz or tar.bz2) and sends it to be downloaded.
|
|
||||||
func download(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
query := r.URL.Query().Get("download")
|
|
||||||
|
|
||||||
if !c.fi.IsDir {
|
|
||||||
w.Header().Set("Content-Disposition", "attachment; filename="+c.fi.Name)
|
|
||||||
http.ServeFile(w, r, c.fi.Path)
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
files := []string{}
|
|
||||||
names := strings.Split(r.URL.Query().Get("files"), ",")
|
|
||||||
|
|
||||||
if len(names) != 0 {
|
|
||||||
for _, name := range names {
|
|
||||||
name, err := url.QueryUnescape(name)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
files = append(files, filepath.Join(c.fi.Path, name))
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
files = append(files, c.fi.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
if query == "true" {
|
|
||||||
query = "zip"
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
extension string
|
|
||||||
temp string
|
|
||||||
err error
|
|
||||||
tempfile string
|
|
||||||
)
|
|
||||||
|
|
||||||
temp, err = ioutil.TempDir("", "")
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer os.RemoveAll(temp)
|
|
||||||
tempfile = filepath.Join(temp, "temp")
|
|
||||||
|
|
||||||
switch query {
|
|
||||||
case "zip":
|
|
||||||
extension, err = ".zip", archiver.Zip.Make(tempfile, files)
|
|
||||||
case "tar":
|
|
||||||
extension, err = ".tar", archiver.Tar.Make(tempfile, files)
|
|
||||||
case "targz":
|
|
||||||
extension, err = ".tar.gz", archiver.TarGz.Make(tempfile, files)
|
|
||||||
case "tarbz2":
|
|
||||||
extension, err = ".tar.bz2", archiver.TarBz2.Make(tempfile, files)
|
|
||||||
case "tarxz":
|
|
||||||
extension, err = ".tar.xz", archiver.TarXZ.Make(tempfile, files)
|
|
||||||
default:
|
|
||||||
return http.StatusNotImplemented, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.Open(temp + "/temp")
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
name := c.fi.Name
|
|
||||||
if name == "." || name == "" {
|
|
||||||
name = "download"
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Disposition", "attachment; filename="+name+extension)
|
|
||||||
io.Copy(w, file)
|
|
||||||
return http.StatusOK, nil
|
|
||||||
}
|
|
44
editor.go
44
editor.go
|
@ -87,50 +87,6 @@ Error:
|
||||||
return e, nil
|
return e, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveSingle serves a single file in an editor (if it is editable), shows the
|
|
||||||
// plain file, or downloads it if it can't be shown.
|
|
||||||
func serveSingle(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = c.fi.RetrieveFileType(); err != nil {
|
|
||||||
return errorToHTTP(err, true), err
|
|
||||||
}
|
|
||||||
|
|
||||||
p := &page{
|
|
||||||
Name: c.fi.Name,
|
|
||||||
Path: c.fi.VirtualPath,
|
|
||||||
IsDir: false,
|
|
||||||
Data: c.fi,
|
|
||||||
User: c.us,
|
|
||||||
PrefixURL: c.fm.prefixURL,
|
|
||||||
BaseURL: c.fm.RootURL(),
|
|
||||||
WebDavURL: c.fm.WebDavURL(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the request accepts JSON, we send the file information.
|
|
||||||
if strings.Contains(r.Header.Get("Accept"), "application/json") {
|
|
||||||
return p.PrintAsJSON(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.fi.Type == "text" {
|
|
||||||
if err = c.fi.Read(); err != nil {
|
|
||||||
return errorToHTTP(err, true), err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.fi.CanBeEdited() && c.us.AllowEdit {
|
|
||||||
p.Data, err = getEditor(r, c.fi)
|
|
||||||
p.Editor = true
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.PrintAsHTML(w, c.fm.assets.templates, "frontmatter", "editor")
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.PrintAsHTML(w, c.fm.assets.templates, "single")
|
|
||||||
}
|
|
||||||
|
|
||||||
func editorClass(mode string) string {
|
func editorClass(mode string) string {
|
||||||
switch mode {
|
switch mode {
|
||||||
case "json", "toml", "yaml":
|
case "json", "toml", "yaml":
|
||||||
|
|
43
file.go
43
file.go
|
@ -1,6 +1,14 @@
|
||||||
package filemanager
|
package filemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -13,6 +21,10 @@ import (
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errInvalidOption = errors.New("Invalid option")
|
||||||
|
)
|
||||||
|
|
||||||
// fileInfo contains the information about a particular file or directory.
|
// fileInfo contains the information about a particular file or directory.
|
||||||
type fileInfo struct {
|
type fileInfo struct {
|
||||||
// Used to store the file's content temporarily.
|
// Used to store the file's content temporarily.
|
||||||
|
@ -149,6 +161,37 @@ func (i *fileInfo) Read() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i fileInfo) Checksum(kind string) (string, error) {
|
||||||
|
file, err := os.Open(i.Path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var h hash.Hash
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case "md5":
|
||||||
|
h = md5.New()
|
||||||
|
case "sha1":
|
||||||
|
h = sha1.New()
|
||||||
|
case "sha256":
|
||||||
|
h = sha256.New()
|
||||||
|
case "sha512":
|
||||||
|
h = sha512.New()
|
||||||
|
default:
|
||||||
|
return "", errInvalidOption
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(h, file)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return hex.EncodeToString(h.Sum(nil)), nil
|
||||||
|
}
|
||||||
|
|
||||||
// StringifyContent returns a string with the file content.
|
// StringifyContent returns a string with the file content.
|
||||||
func (i fileInfo) StringifyContent() string {
|
func (i fileInfo) StringifyContent() string {
|
||||||
return string(i.content)
|
return string(i.content)
|
||||||
|
|
516
http.go
516
http.go
|
@ -1,66 +1,496 @@
|
||||||
package filemanager
|
package filemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mholt/archiver"
|
||||||
)
|
)
|
||||||
|
|
||||||
// requestContext contains the needed information to make handlers work.
|
// assetsURL is the url where static assets are served.
|
||||||
type requestContext struct {
|
const assetsURL = "/_internal"
|
||||||
us *User
|
|
||||||
fm *FileManager
|
func serveHTTP(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
fi *fileInfo
|
var (
|
||||||
|
code int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
// Checks if the URL contains the baseURL. If so, it strips it. Otherwise,
|
||||||
|
// it throws an error.
|
||||||
|
if p := strings.TrimPrefix(r.URL.Path, c.fm.baseURL); len(p) < len(r.URL.Path) {
|
||||||
|
r.URL.Path = p
|
||||||
|
} else {
|
||||||
|
return http.StatusNotFound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if the URL matches the Assets URL. Returns the asset if the
|
||||||
|
// method is GET and Status Forbidden otherwise.
|
||||||
|
if matchURL(r.URL.Path, assetsURL) {
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
|
return serveAssets(c, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.StatusForbidden, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
username, _, _ := r.BasicAuth()
|
||||||
|
if _, ok := c.fm.Users[username]; ok {
|
||||||
|
c.us = c.fm.Users[username]
|
||||||
|
} else {
|
||||||
|
c.us = c.fm.User
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if the request URL is for the WebDav server.
|
||||||
|
if matchURL(r.URL.Path, c.fm.webDavURL) {
|
||||||
|
return serveWebDAV(c, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("x-frame-options", "SAMEORIGIN")
|
||||||
|
w.Header().Set("x-content-type", "nosniff")
|
||||||
|
w.Header().Set("x-xss-protection", "1; mode=block")
|
||||||
|
|
||||||
|
// Checks if the User is allowed to access this file
|
||||||
|
if !c.us.Allowed(r.URL.Path) {
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
|
return htmlError(
|
||||||
|
w, http.StatusForbidden,
|
||||||
|
errors.New("You don't have permission to access this page"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.StatusForbidden, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.URL.Query().Get("search") != "" {
|
||||||
|
return search(c, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.URL.Query().Get("command") != "" {
|
||||||
|
return command(c, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
|
var f *fileInfo
|
||||||
|
|
||||||
|
// Obtains the information of the directory/file.
|
||||||
|
f, err = getInfo(r.URL, c.fm, c.us)
|
||||||
|
if err != nil {
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
|
return htmlError(w, code, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
code = errorToHTTP(err, false)
|
||||||
|
return code, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.fi = f
|
||||||
|
|
||||||
|
// If it's a dir and the path doesn't end with a trailing slash,
|
||||||
|
// redirect the user.
|
||||||
|
if f.IsDir && !strings.HasSuffix(r.URL.Path, "/") {
|
||||||
|
http.Redirect(w, r, c.fm.RootURL()+r.URL.Path+"/", http.StatusTemporaryRedirect)
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case r.URL.Query().Get("download") != "":
|
||||||
|
code, err = serveDownload(c, w, r)
|
||||||
|
case !f.IsDir && r.URL.Query().Get("checksum") != "":
|
||||||
|
code, err = serveChecksum(c, w, r)
|
||||||
|
case r.URL.Query().Get("raw") == "true" && !f.IsDir:
|
||||||
|
http.ServeFile(w, r, f.Path)
|
||||||
|
code, err = 0, nil
|
||||||
|
case f.IsDir:
|
||||||
|
code, err = serveListing(c, w, r)
|
||||||
|
default:
|
||||||
|
code, err = serveSingle(c, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
code, err = htmlError(w, code, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return code, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.StatusNotImplemented, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// responseWriterNoBody is a wrapper used to suprress the body of the response
|
// serveWebDAV handles the webDAV route of the File Manager.
|
||||||
// to a request. Mainly used for HEAD requests.
|
func serveWebDAV(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
type responseWriterNoBody struct {
|
var err error
|
||||||
http.ResponseWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
// newResponseWriterNoBody creates a new responseWriterNoBody.
|
// Checks for user permissions relatively to this path.
|
||||||
func newResponseWriterNoBody(w http.ResponseWriter) *responseWriterNoBody {
|
if !c.us.Allowed(strings.TrimPrefix(r.URL.Path, c.fm.webDavURL)) {
|
||||||
return &responseWriterNoBody{w}
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header executes the Header method from the http.ResponseWriter.
|
switch r.Method {
|
||||||
func (w responseWriterNoBody) Header() http.Header {
|
case "GET", "HEAD":
|
||||||
return w.ResponseWriter.Header()
|
// Excerpt from RFC4918, section 9.4:
|
||||||
}
|
//
|
||||||
|
// GET, when applied to a collection, may return the contents of an
|
||||||
|
// "index.html" resource, a human-readable view of the contents of
|
||||||
|
// the collection, or something else altogether.
|
||||||
|
//
|
||||||
|
// It was decided on https://github.com/hacdias/caddy-filemanager/issues/85
|
||||||
|
// that GET, for collections, will return the same as PROPFIND method.
|
||||||
|
path := strings.Replace(r.URL.Path, c.fm.webDavURL, "", 1)
|
||||||
|
path = c.us.scope + "/" + path
|
||||||
|
path = filepath.Clean(path)
|
||||||
|
|
||||||
|
var i os.FileInfo
|
||||||
|
i, err = os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
// Is there any error? WebDav will handle it... no worries.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.IsDir() {
|
||||||
|
r.Method = "PROPFIND"
|
||||||
|
|
||||||
|
if r.Method == "HEAD" {
|
||||||
|
w = newResponseWriterNoBody(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "PROPPATCH", "MOVE", "PATCH", "PUT", "DELETE":
|
||||||
|
if !c.us.AllowEdit {
|
||||||
|
return http.StatusForbidden, nil
|
||||||
|
}
|
||||||
|
case "MKCOL", "COPY":
|
||||||
|
if !c.us.AllowNew {
|
||||||
|
return http.StatusForbidden, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preprocess the PUT request if it's the case
|
||||||
|
if r.Method == http.MethodPut {
|
||||||
|
if err = c.fm.BeforeSave(r, c.fm, c.us); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if put(c, w, r) != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.fm.handler.ServeHTTP(w, r)
|
||||||
|
if err = c.fm.AfterSave(r, c.fm, c.us); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
// Write suprresses the body.
|
|
||||||
func (w responseWriterNoBody) Write(data []byte) (int, error) {
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteHeader writes the header to the http.ResponseWriter.
|
// Serve provides the needed assets for the front-end
|
||||||
func (w responseWriterNoBody) WriteHeader(statusCode int) {
|
func serveAssets(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
w.ResponseWriter.WriteHeader(statusCode)
|
// gets the filename to be used with Assets function
|
||||||
}
|
filename := strings.TrimPrefix(r.URL.Path, assetsURL)
|
||||||
|
|
||||||
// matchURL checks if the first URL matches the second.
|
var file []byte
|
||||||
func matchURL(first, second string) bool {
|
var err error
|
||||||
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 {
|
switch {
|
||||||
case os.IsPermission(err):
|
case strings.HasPrefix(filename, "/css"):
|
||||||
return http.StatusForbidden
|
filename = strings.Replace(filename, "/css/", "", 1)
|
||||||
case os.IsNotExist(err):
|
file, err = c.fm.assets.css.Bytes(filename)
|
||||||
if !gone {
|
case strings.HasPrefix(filename, "/js"):
|
||||||
return http.StatusNotFound
|
filename = strings.Replace(filename, "/js/", "", 1)
|
||||||
|
file, err = c.fm.assets.js.Bytes(filename)
|
||||||
|
default:
|
||||||
|
err = errors.New("not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.StatusGone
|
if err != nil {
|
||||||
case os.IsExist(err):
|
return http.StatusNotFound, nil
|
||||||
return http.StatusGone
|
|
||||||
default:
|
|
||||||
return http.StatusInternalServerError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the file extension and its mimetype
|
||||||
|
extension := filepath.Ext(filename)
|
||||||
|
mediatype := mime.TypeByExtension(extension)
|
||||||
|
|
||||||
|
// Write the header with the Content-Type and write the file
|
||||||
|
// content to the buffer
|
||||||
|
w.Header().Set("Content-Type", mediatype)
|
||||||
|
w.Write(file)
|
||||||
|
return 200, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// serveChecksum calculates the hash of a file. Supports MD5, SHA1, SHA256 and SHA512.
|
||||||
|
func serveChecksum(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
query := r.URL.Query().Get("checksum")
|
||||||
|
|
||||||
|
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 http.StatusOK, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// serveSingle serves a single file in an editor (if it is editable), shows the
|
||||||
|
// plain file, or downloads it if it can't be shown.
|
||||||
|
func serveSingle(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if err = c.fi.RetrieveFileType(); err != nil {
|
||||||
|
return errorToHTTP(err, true), err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &page{
|
||||||
|
Name: c.fi.Name,
|
||||||
|
Path: c.fi.VirtualPath,
|
||||||
|
IsDir: false,
|
||||||
|
Data: c.fi,
|
||||||
|
User: c.us,
|
||||||
|
PrefixURL: c.fm.prefixURL,
|
||||||
|
BaseURL: c.fm.RootURL(),
|
||||||
|
WebDavURL: c.fm.WebDavURL(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the request accepts JSON, we send the file information.
|
||||||
|
if strings.Contains(r.Header.Get("Accept"), "application/json") {
|
||||||
|
return p.PrintAsJSON(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.fi.Type == "text" {
|
||||||
|
if err = c.fi.Read(); err != nil {
|
||||||
|
return errorToHTTP(err, true), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.fi.CanBeEdited() && c.us.AllowEdit {
|
||||||
|
p.Data, err = getEditor(r, c.fi)
|
||||||
|
p.Editor = true
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.PrintAsHTML(w, c.fm.assets.templates, "frontmatter", "editor")
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.PrintAsHTML(w, c.fm.assets.templates, "single")
|
||||||
|
}
|
||||||
|
|
||||||
|
// serveDownload creates an archive in one of the supported formats (zip, tar,
|
||||||
|
// tar.gz or tar.bz2) and sends it to be downloaded.
|
||||||
|
func serveDownload(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
query := r.URL.Query().Get("download")
|
||||||
|
|
||||||
|
if !c.fi.IsDir {
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename="+c.fi.Name)
|
||||||
|
http.ServeFile(w, r, c.fi.Path)
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
files := []string{}
|
||||||
|
names := strings.Split(r.URL.Query().Get("files"), ",")
|
||||||
|
|
||||||
|
if len(names) != 0 {
|
||||||
|
for _, name := range names {
|
||||||
|
name, err := url.QueryUnescape(name)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
files = append(files, filepath.Join(c.fi.Path, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
files = append(files, c.fi.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if query == "true" {
|
||||||
|
query = "zip"
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
extension string
|
||||||
|
temp string
|
||||||
|
err error
|
||||||
|
tempfile string
|
||||||
|
)
|
||||||
|
|
||||||
|
temp, err = ioutil.TempDir("", "")
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.RemoveAll(temp)
|
||||||
|
tempfile = filepath.Join(temp, "temp")
|
||||||
|
|
||||||
|
switch query {
|
||||||
|
case "zip":
|
||||||
|
extension, err = ".zip", archiver.Zip.Make(tempfile, files)
|
||||||
|
case "tar":
|
||||||
|
extension, err = ".tar", archiver.Tar.Make(tempfile, files)
|
||||||
|
case "targz":
|
||||||
|
extension, err = ".tar.gz", archiver.TarGz.Make(tempfile, files)
|
||||||
|
case "tarbz2":
|
||||||
|
extension, err = ".tar.bz2", archiver.TarBz2.Make(tempfile, files)
|
||||||
|
case "tarxz":
|
||||||
|
extension, err = ".tar.xz", archiver.TarXZ.Make(tempfile, files)
|
||||||
|
default:
|
||||||
|
return http.StatusNotImplemented, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(temp + "/temp")
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := c.fi.Name
|
||||||
|
if name == "." || name == "" {
|
||||||
|
name = "download"
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename="+name+extension)
|
||||||
|
io.Copy(w, file)
|
||||||
|
return http.StatusOK, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// serveListing presents the user with a listage of a directory folder.
|
||||||
|
func serveListing(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Loads the content of the directory.
|
||||||
|
listing, err := getListing(c.us, c.fi.VirtualPath, c.fm.RootURL()+r.URL.Path)
|
||||||
|
if err != nil {
|
||||||
|
return errorToHTTP(err, true), err
|
||||||
|
}
|
||||||
|
|
||||||
|
cookieScope := c.fm.RootURL()
|
||||||
|
if cookieScope == "" {
|
||||||
|
cookieScope = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the query values into the Listing struct
|
||||||
|
var limit int
|
||||||
|
listing.Sort, listing.Order, limit, err = handleSortOrder(w, r, cookieScope)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusBadRequest, err
|
||||||
|
}
|
||||||
|
|
||||||
|
listing.ApplySort()
|
||||||
|
|
||||||
|
if limit > 0 && limit <= len(listing.Items) {
|
||||||
|
listing.Items = listing.Items[:limit]
|
||||||
|
listing.ItemsLimitedTo = limit
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(r.Header.Get("Accept"), "application/json") {
|
||||||
|
marsh, err := json.Marshal(listing.Items)
|
||||||
|
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 http.StatusOK, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
displayMode := r.URL.Query().Get("display")
|
||||||
|
|
||||||
|
if displayMode == "" {
|
||||||
|
if displayCookie, err := r.Cookie("display"); err == nil {
|
||||||
|
displayMode = displayCookie.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if displayMode == "" || (displayMode != "mosaic" && displayMode != "list") {
|
||||||
|
displayMode = "mosaic"
|
||||||
|
}
|
||||||
|
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: "display",
|
||||||
|
Value: displayMode,
|
||||||
|
Path: cookieScope,
|
||||||
|
Secure: r.TLS != nil,
|
||||||
|
})
|
||||||
|
|
||||||
|
p := &page{
|
||||||
|
minimal: r.Header.Get("Minimal") == "true",
|
||||||
|
Name: listing.Name,
|
||||||
|
Path: c.fi.VirtualPath,
|
||||||
|
IsDir: true,
|
||||||
|
User: c.us,
|
||||||
|
PrefixURL: c.fm.prefixURL,
|
||||||
|
BaseURL: c.fm.RootURL(),
|
||||||
|
WebDavURL: c.fm.WebDavURL(),
|
||||||
|
Display: displayMode,
|
||||||
|
Data: listing,
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.PrintAsHTML(w, c.fm.assets.templates, "listing")
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleSortOrder gets and stores for a Listing the 'sort' and 'order',
|
||||||
|
// and reads 'limit' if given. The latter is 0 if not given. Sets cookies.
|
||||||
|
func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, limit int, err error) {
|
||||||
|
sort = r.URL.Query().Get("sort")
|
||||||
|
order = r.URL.Query().Get("order")
|
||||||
|
limitQuery := r.URL.Query().Get("limit")
|
||||||
|
|
||||||
|
// If the query 'sort' or 'order' is empty, use defaults or any values
|
||||||
|
// previously saved in Cookies.
|
||||||
|
switch sort {
|
||||||
|
case "":
|
||||||
|
sort = "name"
|
||||||
|
if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil {
|
||||||
|
sort = sortCookie.Value
|
||||||
|
}
|
||||||
|
case "name", "size", "type":
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: "sort",
|
||||||
|
Value: sort,
|
||||||
|
Path: scope,
|
||||||
|
Secure: r.TLS != nil,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
switch order {
|
||||||
|
case "":
|
||||||
|
order = "asc"
|
||||||
|
if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
|
||||||
|
order = orderCookie.Value
|
||||||
|
}
|
||||||
|
case "asc", "desc":
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: "order",
|
||||||
|
Value: order,
|
||||||
|
Path: scope,
|
||||||
|
Secure: r.TLS != nil,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if limitQuery != "" {
|
||||||
|
limit, err = strconv.Atoi(limitQuery)
|
||||||
|
// If the 'limit' query can't be interpreted as a number, return err.
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
136
http_listing.go
136
http_listing.go
|
@ -1,136 +0,0 @@
|
||||||
package filemanager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// serveListing presents the user with a listage of a directory folder.
|
|
||||||
func serveListing(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Loads the content of the directory.
|
|
||||||
listing, err := getListing(c.us, c.fi.VirtualPath, c.fm.RootURL()+r.URL.Path)
|
|
||||||
if err != nil {
|
|
||||||
return errorToHTTP(err, true), err
|
|
||||||
}
|
|
||||||
|
|
||||||
cookieScope := c.fm.RootURL()
|
|
||||||
if cookieScope == "" {
|
|
||||||
cookieScope = "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the query values into the Listing struct
|
|
||||||
var limit int
|
|
||||||
listing.Sort, listing.Order, limit, err = handleSortOrder(w, r, cookieScope)
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusBadRequest, err
|
|
||||||
}
|
|
||||||
|
|
||||||
listing.ApplySort()
|
|
||||||
|
|
||||||
if limit > 0 && limit <= len(listing.Items) {
|
|
||||||
listing.Items = listing.Items[:limit]
|
|
||||||
listing.ItemsLimitedTo = limit
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(r.Header.Get("Accept"), "application/json") {
|
|
||||||
marsh, err := json.Marshal(listing.Items)
|
|
||||||
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 http.StatusOK, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
displayMode := r.URL.Query().Get("display")
|
|
||||||
|
|
||||||
if displayMode == "" {
|
|
||||||
if displayCookie, err := r.Cookie("display"); err == nil {
|
|
||||||
displayMode = displayCookie.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if displayMode == "" || (displayMode != "mosaic" && displayMode != "list") {
|
|
||||||
displayMode = "mosaic"
|
|
||||||
}
|
|
||||||
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
|
||||||
Name: "display",
|
|
||||||
Value: displayMode,
|
|
||||||
Path: cookieScope,
|
|
||||||
Secure: r.TLS != nil,
|
|
||||||
})
|
|
||||||
|
|
||||||
p := &page{
|
|
||||||
minimal: r.Header.Get("Minimal") == "true",
|
|
||||||
Name: listing.Name,
|
|
||||||
Path: c.fi.VirtualPath,
|
|
||||||
IsDir: true,
|
|
||||||
User: c.us,
|
|
||||||
PrefixURL: c.fm.prefixURL,
|
|
||||||
BaseURL: c.fm.RootURL(),
|
|
||||||
WebDavURL: c.fm.WebDavURL(),
|
|
||||||
Display: displayMode,
|
|
||||||
Data: listing,
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.PrintAsHTML(w, c.fm.assets.templates, "listing")
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleSortOrder gets and stores for a Listing the 'sort' and 'order',
|
|
||||||
// and reads 'limit' if given. The latter is 0 if not given. Sets cookies.
|
|
||||||
func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, limit int, err error) {
|
|
||||||
sort = r.URL.Query().Get("sort")
|
|
||||||
order = r.URL.Query().Get("order")
|
|
||||||
limitQuery := r.URL.Query().Get("limit")
|
|
||||||
|
|
||||||
// If the query 'sort' or 'order' is empty, use defaults or any values
|
|
||||||
// previously saved in Cookies.
|
|
||||||
switch sort {
|
|
||||||
case "":
|
|
||||||
sort = "name"
|
|
||||||
if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil {
|
|
||||||
sort = sortCookie.Value
|
|
||||||
}
|
|
||||||
case "name", "size", "type":
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
|
||||||
Name: "sort",
|
|
||||||
Value: sort,
|
|
||||||
Path: scope,
|
|
||||||
Secure: r.TLS != nil,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
switch order {
|
|
||||||
case "":
|
|
||||||
order = "asc"
|
|
||||||
if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
|
|
||||||
order = orderCookie.Value
|
|
||||||
}
|
|
||||||
case "asc", "desc":
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
|
||||||
Name: "order",
|
|
||||||
Value: order,
|
|
||||||
Path: scope,
|
|
||||||
Secure: r.TLS != nil,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if limitQuery != "" {
|
|
||||||
limit, err = strconv.Atoi(limitQuery)
|
|
||||||
// If the 'limit' query can't be interpreted as a number, return err.
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
package filemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// requestContext contains the needed information to make handlers work.
|
||||||
|
type requestContext struct {
|
||||||
|
us *User
|
||||||
|
fm *FileManager
|
||||||
|
fi *fileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// responseWriterNoBody is a wrapper used to suprress the body of the response
|
||||||
|
// to a request. Mainly used for HEAD requests.
|
||||||
|
type responseWriterNoBody struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
// newResponseWriterNoBody creates a new responseWriterNoBody.
|
||||||
|
func newResponseWriterNoBody(w http.ResponseWriter) *responseWriterNoBody {
|
||||||
|
return &responseWriterNoBody{w}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header executes the Header method from the http.ResponseWriter.
|
||||||
|
func (w responseWriterNoBody) Header() http.Header {
|
||||||
|
return w.ResponseWriter.Header()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write suprresses the body.
|
||||||
|
func (w responseWriterNoBody) Write(data []byte) (int, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader writes the header to the http.ResponseWriter.
|
||||||
|
func (w responseWriterNoBody) WriteHeader(statusCode int) {
|
||||||
|
w.ResponseWriter.WriteHeader(statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 os.IsPermission(err):
|
||||||
|
return http.StatusForbidden
|
||||||
|
case os.IsNotExist(err):
|
||||||
|
if !gone {
|
||||||
|
return http.StatusNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.StatusGone
|
||||||
|
case os.IsExist(err):
|
||||||
|
return http.StatusGone
|
||||||
|
default:
|
||||||
|
return http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
}
|
182
webdav.go
182
webdav.go
|
@ -1,182 +0,0 @@
|
||||||
package filemanager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func serveHTTP(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
var (
|
|
||||||
code int
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
// Checks if the URL contains the baseURL. If so, it strips it. Otherwise,
|
|
||||||
// it throws an error.
|
|
||||||
if p := strings.TrimPrefix(r.URL.Path, c.fm.baseURL); len(p) < len(r.URL.Path) {
|
|
||||||
r.URL.Path = p
|
|
||||||
} else {
|
|
||||||
return http.StatusNotFound, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if the URL matches the Assets URL. Returns the asset if the
|
|
||||||
// method is GET and Status Forbidden otherwise.
|
|
||||||
if matchURL(r.URL.Path, assetsURL) {
|
|
||||||
if r.Method == http.MethodGet {
|
|
||||||
return serveAssets(c, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
return http.StatusForbidden, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
username, _, _ := r.BasicAuth()
|
|
||||||
if _, ok := c.fm.Users[username]; ok {
|
|
||||||
c.us = c.fm.Users[username]
|
|
||||||
} else {
|
|
||||||
c.us = c.fm.User
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if the request URL is for the WebDav server.
|
|
||||||
if matchURL(r.URL.Path, c.fm.webDavURL) {
|
|
||||||
return serveWebDAV(c, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("x-frame-options", "SAMEORIGIN")
|
|
||||||
w.Header().Set("x-content-type", "nosniff")
|
|
||||||
w.Header().Set("x-xss-protection", "1; mode=block")
|
|
||||||
|
|
||||||
// Checks if the User is allowed to access this file
|
|
||||||
if !c.us.Allowed(r.URL.Path) {
|
|
||||||
if r.Method == http.MethodGet {
|
|
||||||
return htmlError(
|
|
||||||
w, http.StatusForbidden,
|
|
||||||
errors.New("You don't have permission to access this page"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return http.StatusForbidden, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.URL.Query().Get("search") != "" {
|
|
||||||
return search(c, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.URL.Query().Get("command") != "" {
|
|
||||||
return command(c, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Method == http.MethodGet {
|
|
||||||
var f *fileInfo
|
|
||||||
|
|
||||||
// Obtains the information of the directory/file.
|
|
||||||
f, err = getInfo(r.URL, c.fm, c.us)
|
|
||||||
if err != nil {
|
|
||||||
if r.Method == http.MethodGet {
|
|
||||||
return htmlError(w, code, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
code = errorToHTTP(err, false)
|
|
||||||
return code, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.fi = f
|
|
||||||
|
|
||||||
// If it's a dir and the path doesn't end with a trailing slash,
|
|
||||||
// redirect the user.
|
|
||||||
if f.IsDir && !strings.HasSuffix(r.URL.Path, "/") {
|
|
||||||
http.Redirect(w, r, c.fm.RootURL()+r.URL.Path+"/", http.StatusTemporaryRedirect)
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case r.URL.Query().Get("download") != "":
|
|
||||||
code, err = download(c, w, r)
|
|
||||||
case !f.IsDir && r.URL.Query().Get("checksum") != "":
|
|
||||||
code, err = serveChecksum(c, w, r)
|
|
||||||
case r.URL.Query().Get("raw") == "true" && !f.IsDir:
|
|
||||||
http.ServeFile(w, r, f.Path)
|
|
||||||
code, err = 0, nil
|
|
||||||
case f.IsDir:
|
|
||||||
code, err = serveListing(c, w, r)
|
|
||||||
default:
|
|
||||||
code, err = serveSingle(c, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
code, err = htmlError(w, code, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return code, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return http.StatusNotImplemented, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// serveWebDAV handles the webDAV route of the File Manager.
|
|
||||||
func serveWebDAV(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Checks for user permissions relatively to this path.
|
|
||||||
if !c.us.Allowed(strings.TrimPrefix(r.URL.Path, c.fm.webDavURL)) {
|
|
||||||
return http.StatusForbidden, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch r.Method {
|
|
||||||
case "GET", "HEAD":
|
|
||||||
// Excerpt from RFC4918, section 9.4:
|
|
||||||
//
|
|
||||||
// GET, when applied to a collection, may return the contents of an
|
|
||||||
// "index.html" resource, a human-readable view of the contents of
|
|
||||||
// the collection, or something else altogether.
|
|
||||||
//
|
|
||||||
// It was decided on https://github.com/hacdias/caddy-filemanager/issues/85
|
|
||||||
// that GET, for collections, will return the same as PROPFIND method.
|
|
||||||
path := strings.Replace(r.URL.Path, c.fm.webDavURL, "", 1)
|
|
||||||
path = c.us.scope + "/" + path
|
|
||||||
path = filepath.Clean(path)
|
|
||||||
|
|
||||||
var i os.FileInfo
|
|
||||||
i, err = os.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
// Is there any error? WebDav will handle it... no worries.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if i.IsDir() {
|
|
||||||
r.Method = "PROPFIND"
|
|
||||||
|
|
||||||
if r.Method == "HEAD" {
|
|
||||||
w = newResponseWriterNoBody(w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "PROPPATCH", "MOVE", "PATCH", "PUT", "DELETE":
|
|
||||||
if !c.us.AllowEdit {
|
|
||||||
return http.StatusForbidden, nil
|
|
||||||
}
|
|
||||||
case "MKCOL", "COPY":
|
|
||||||
if !c.us.AllowNew {
|
|
||||||
return http.StatusForbidden, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preprocess the PUT request if it's the case
|
|
||||||
if r.Method == http.MethodPut {
|
|
||||||
if err = c.fm.BeforeSave(r, c.fm, c.us); err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if put(c, w, r) != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.fm.handler.ServeHTTP(w, r)
|
|
||||||
if err = c.fm.AfterSave(r, c.fm, c.us); err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue