Major changes on API
parent
01c78d2b36
commit
1f7974de38
|
@ -1,6 +1,6 @@
|
|||
.DS_Store
|
||||
node_modules/
|
||||
_assets/dist_dev/*
|
||||
_assets/dist/*
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
|
|
@ -6,10 +6,10 @@ module.exports = {
|
|||
env: {
|
||||
NODE_ENV: '"production"'
|
||||
},
|
||||
index: path.resolve(__dirname, '../dist/templates/index.html'),
|
||||
index: path.resolve(__dirname, '../dist/index.html'),
|
||||
assetsRoot: path.resolve(__dirname, '../dist'),
|
||||
assetsSubDirectory: '_',
|
||||
assetsPublicPath: '{{ .BaseURL }}',
|
||||
assetsSubDirectory: 'static',
|
||||
assetsPublicPath: '{{ .BaseURL }}/',
|
||||
productionSourceMap: true,
|
||||
// Gzip off by default as many popular static hosts such as
|
||||
// Surge or Netlify already gzip all static assets for you.
|
||||
|
@ -27,10 +27,10 @@ module.exports = {
|
|||
env: {
|
||||
NODE_ENV: '"development"'
|
||||
},
|
||||
index: path.resolve(__dirname, '../dist_dev/templates/index.html'),
|
||||
assetsRoot: path.resolve(__dirname, '../dist_dev/'),
|
||||
assetsSubDirectory: '_',
|
||||
assetsPublicPath: '{{ .BaseURL }}',
|
||||
index: path.resolve(__dirname, '../dist/index.html'),
|
||||
assetsRoot: path.resolve(__dirname, '../dist/'),
|
||||
assetsSubDirectory: 'static',
|
||||
assetsPublicPath: '{{ .BaseURL }}/',
|
||||
produceSourceMap: true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
window.addEventListener('load', function() {
|
||||
if ('serviceWorker' in navigator &&
|
||||
(window.location.protocol === 'https:' || isLocalhost)) {
|
||||
navigator.serviceWorker.register('{{ .BaseURL }}/_/service-worker.js')
|
||||
navigator.serviceWorker.register('{{ .BaseURL }}/sw.js')
|
||||
.then(function(registration) {
|
||||
// updatefound is fired if service-worker.js changes.
|
||||
registration.onupdatefound = function() {
|
||||
|
|
|
@ -6,6 +6,7 @@ exports.assetsPath = function (_path) {
|
|||
var assetsSubDirectory = process.env.NODE_ENV === 'production'
|
||||
? config.build.assetsSubDirectory
|
||||
: config.dev.assetsSubDirectory
|
||||
|
||||
return path.posix.join(assetsSubDirectory, _path)
|
||||
}
|
||||
|
||||
|
|
|
@ -98,8 +98,9 @@ var webpackConfig = merge(baseWebpackConfig, {
|
|||
]),
|
||||
// service worker caching
|
||||
new SWPrecacheWebpackPlugin({
|
||||
cacheId: 'my-vue-app',
|
||||
filename: 'service-worker.js',
|
||||
cacheId: 'File Manager',
|
||||
filename: 'sw.js',
|
||||
replacePrefix: '{{ .BaseURL }}/',
|
||||
staticFileGlobs: ['dist/**/*.{js,html,css}'],
|
||||
minify: true,
|
||||
stripPrefix: 'dist/'
|
||||
|
|
|
@ -4,27 +4,28 @@
|
|||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<meta name="base" content="{{ .BaseURL }}">
|
||||
<title>File Manager</title>
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ .BaseURL }}/_/img/icons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ .BaseURL }}/_/img/icons/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ .BaseURL }}/static/img/icons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ .BaseURL }}/static/img/icons/favicon-16x16.png">
|
||||
<!--[if IE]><link rel="shortcut icon" href="/static/img/icons/favicon.ico"><![endif]-->
|
||||
<!-- Add to home screen for Android and modern mobile browsers -->
|
||||
<link rel="manifest" href="{{ .BaseURL }}/_/manifest.json">
|
||||
<meta name="theme-color" content="#4DBA87">
|
||||
<link rel="manifest" href="{{ .BaseURL }}/static/manifest.json">
|
||||
<meta name="theme-color" content="#2979ff">
|
||||
|
||||
<!-- Add to home screen for Safari on iOS -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="assets">
|
||||
<link rel="apple-touch-icon" href="{{ .BaseURL }}/_/img/icons/apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" href="{{ .BaseURL }}/static/img/icons/apple-touch-icon-152x152.png">
|
||||
<!-- Add to home screen for Windows -->
|
||||
<meta name="msapplication-TileImage" content="{{ .BaseURL }}/_/img/icons/msapplication-icon-144x144.png">
|
||||
<meta name="msapplication-TileColor" content="#000000">
|
||||
<meta name="msapplication-TileImage" content="{{ .BaseURL }}/static/img/icons/msapplication-icon-144x144.png">
|
||||
<meta name="msapplication-TileColor" content="#2979ff">
|
||||
|
||||
<% for (var chunk of webpack.chunks) {
|
||||
for (var file of chunk.files) {
|
||||
if (file.match(/\.(js|css)$/)) { %>
|
||||
<link rel="<%= chunk.initial?'preload':'prefetch' %>" href="{{ .BaseURL }}/<%= file.replace('static', '') %>" as="<%= file.match(/\.css$/)?'style':'script' %>"><% }}} %>
|
||||
<link rel="<%= chunk.initial?'preload':'prefetch' %>" href="{{ .BaseURL }}/<%= file %>" as="<%= file.match(/\.css$/)?'style':'script' %>"><% }}} %>
|
||||
|
||||
<style>
|
||||
#loading {
|
||||
|
@ -88,20 +89,8 @@
|
|||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
{{- if ne .User.StyleSheet "" -}}
|
||||
<style>{{ CSS .User.StyleSheet }}</style>
|
||||
{{- end -}}
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
var info = {
|
||||
user: JSON.parse('{{ Marshal .User }}'),
|
||||
req: JSON.parse('{{ Marshal . }}'),
|
||||
webdavURL: "{{ .WebDavURL }}",
|
||||
baseURL: "{{.BaseURL}}"
|
||||
}
|
||||
</script>
|
||||
<div id="app"></div>
|
||||
|
||||
<div id="loading">
|
||||
|
|
|
@ -3,18 +3,18 @@
|
|||
"short_name": "File Manager",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/static/img/icons/android-chrome-192x192.png",
|
||||
"src": "{{ .BaseURL }}/static/img/icons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/img/icons/android-chrome-512x512.png",
|
||||
"src": "{{ .BaseURL }}/static/img/icons/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"start_url": "/index.html",
|
||||
"start_url": "{{ .BaseURL }}/",
|
||||
"display": "standalone",
|
||||
"background_color": "#000000",
|
||||
"theme_color": "#4DBA87"
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#2979ff"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,281 @@
|
|||
package filemanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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:len(path)]
|
||||
}
|
||||
|
||||
func serveAPI(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if r.URL.Path == "/auth" {
|
||||
return getTokenHandler(c, w, r)
|
||||
}
|
||||
|
||||
/* valid, user := validAuth(c, r)
|
||||
if !valid {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
fmt.Println(user)
|
||||
c.us = user */
|
||||
|
||||
c.us = c.fm.User
|
||||
|
||||
var router string
|
||||
router, r.URL.Path = cleanURL(r.URL.Path)
|
||||
|
||||
if !c.us.Allowed(r.URL.Path) {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
if router == "checksum" || router == "download" {
|
||||
var err error
|
||||
c.fi, err = getInfo(r.URL, c.fm, c.us)
|
||||
if err != nil {
|
||||
return errorToHTTP(err, false), err
|
||||
}
|
||||
}
|
||||
|
||||
switch router {
|
||||
case "download":
|
||||
return downloadHandler(c, w, r)
|
||||
case "checksum":
|
||||
return checksumHandler(c, w, r)
|
||||
case "command":
|
||||
return command(c, w, r)
|
||||
case "search":
|
||||
return search(c, w, r)
|
||||
case "resource":
|
||||
return resourceHandler(c, w, r)
|
||||
}
|
||||
|
||||
return http.StatusNotFound, nil
|
||||
}
|
||||
|
||||
func resourceHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
return getHandler(c, w, r)
|
||||
case http.MethodDelete:
|
||||
return deleteHandler(c, w, r)
|
||||
case http.MethodPut:
|
||||
return putHandler(c, w, r)
|
||||
case http.MethodPost:
|
||||
// Handle renaming
|
||||
}
|
||||
|
||||
/* // Execute beforeSave if it is a PUT request.
|
||||
if r.Method == http.MethodPut {
|
||||
if err := c.fm.BeforeSave(r, c.fm, c.us); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
|
||||
// Execute afterSave if it is a PUT request.
|
||||
if r.Method == http.MethodPut {
|
||||
if err := c.fm.AfterSave(r, c.fm, c.us); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
} */
|
||||
|
||||
return http.StatusNotImplemented, nil
|
||||
}
|
||||
|
||||
func getHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// Obtains the information of the directory/file.
|
||||
f, err := getInfo(r.URL, c.fm, c.us)
|
||||
if err != nil {
|
||||
return errorToHTTP(err, false), err
|
||||
}
|
||||
|
||||
// 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, "/") {
|
||||
r.URL.Path = r.URL.Path + "/"
|
||||
}
|
||||
|
||||
// If it is a dir, go and serve the listing.
|
||||
if f.IsDir {
|
||||
c.fi = f
|
||||
return listingHandler(c, w, r)
|
||||
}
|
||||
|
||||
// Tries to get the file type.
|
||||
if err = f.RetrieveFileType(); err != nil {
|
||||
return errorToHTTP(err, true), err
|
||||
}
|
||||
|
||||
// If it can't be edited or the user isn't allowed to,
|
||||
// serve it as a listing, with a preview of the file.
|
||||
if !f.CanBeEdited() || !c.us.AllowEdit {
|
||||
f.Kind = "preview"
|
||||
} else {
|
||||
// Otherwise, we just bring the editor in!
|
||||
f.Kind = "editor"
|
||||
|
||||
err = f.getEditor()
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
|
||||
return renderJSON(w, f)
|
||||
}
|
||||
|
||||
func listingHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
f := c.fi
|
||||
f.Kind = "listing"
|
||||
|
||||
err := f.getListing(c, r)
|
||||
if err != nil {
|
||||
return errorToHTTP(err, true), err
|
||||
}
|
||||
|
||||
listing := f.listing
|
||||
|
||||
cookieScope := c.fm.RootURL()
|
||||
if cookieScope == "" {
|
||||
cookieScope = "/"
|
||||
}
|
||||
|
||||
// Copy the query values into the Listing struct
|
||||
listing.Sort, listing.Order, err = handleSortOrder(w, r, cookieScope)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
|
||||
listing.ApplySort()
|
||||
listing.Display = displayMode(w, r, cookieScope)
|
||||
|
||||
return renderJSON(w, f)
|
||||
}
|
||||
|
||||
func deleteHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// Prevent the removal of the root directory.
|
||||
if r.URL.Path == "/" {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
// Remove the file or folder.
|
||||
err := c.us.fileSystem.RemoveAll(context.TODO(), r.URL.Path)
|
||||
if err != nil {
|
||||
return errorToHTTP(err, true), err
|
||||
}
|
||||
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
func putHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if strings.HasSuffix(r.URL.Path, "/") {
|
||||
err := c.us.fileSystem.Mkdir(context.TODO(), r.URL.Path, 0666)
|
||||
return errorToHTTP(err, false), err
|
||||
}
|
||||
|
||||
f, err := c.us.fileSystem.OpenFile(context.TODO(), r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
defer f.Close()
|
||||
|
||||
if err != nil {
|
||||
return errorToHTTP(err, false), err
|
||||
}
|
||||
|
||||
_, err = io.Copy(f, r.Body)
|
||||
if err != nil {
|
||||
return errorToHTTP(err, false), err
|
||||
}
|
||||
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return errorToHTTP(err, false), err
|
||||
}
|
||||
|
||||
etag := fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size())
|
||||
w.Header().Set("ETag", etag)
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
// displayMode obtaisn the display mode from URL, or from the
|
||||
// cookie.
|
||||
func displayMode(w http.ResponseWriter, r *http.Request, scope string) string {
|
||||
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,
|
||||
MaxAge: 31536000,
|
||||
Path: scope,
|
||||
Secure: r.TLS != nil,
|
||||
})
|
||||
|
||||
return displayMode
|
||||
}
|
||||
|
||||
// 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, err error) {
|
||||
sort = r.URL.Query().Get("sort")
|
||||
order = r.URL.Query().Get("order")
|
||||
|
||||
// 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,
|
||||
MaxAge: 31536000,
|
||||
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,
|
||||
MaxAge: 31536000,
|
||||
Path: scope,
|
||||
Secure: r.TLS != nil,
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package filemanager
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"github.com/dgrijalva/jwt-go/request"
|
||||
)
|
||||
|
||||
/* Set up a global string for our secret */
|
||||
var key = []byte("secret")
|
||||
|
||||
type claims struct {
|
||||
*User
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
func getTokenHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// TODO: get user and password info from the request
|
||||
// check if the password is correct for that user using a DB or JSOn
|
||||
// or something.
|
||||
|
||||
claims := claims{
|
||||
c.fm.User,
|
||||
jwt.StandardClaims{
|
||||
ExpiresAt: time.Now().Add(time.Minute * 5).Unix(),
|
||||
Issuer: "test",
|
||||
},
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
string, err := token.SignedString(key)
|
||||
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
w.Write([]byte(string))
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func validAuth(c *requestContext, r *http.Request) (bool, *User) {
|
||||
token, err := request.ParseFromRequestWithClaims(r, request.AuthorizationHeaderExtractor, &claims{},
|
||||
func(token *jwt.Token) (interface{}, error) {
|
||||
return key, nil
|
||||
})
|
||||
|
||||
if err == nil && token.Valid {
|
||||
return true, c.fm.User
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
|
@ -94,7 +94,6 @@ func parse(c *caddy.Controller) ([]*config, error) {
|
|||
|
||||
if len(args) > 0 {
|
||||
m.baseURL = args[0]
|
||||
m.webDavURL = "/webdav"
|
||||
m.SetBaseURL(args[0])
|
||||
}
|
||||
|
||||
|
@ -108,13 +107,6 @@ func parse(c *caddy.Controller) ([]*config, error) {
|
|||
if m.AfterSave, err = makeCommand(c, m); err != nil {
|
||||
return configs, err
|
||||
}
|
||||
case "webdav":
|
||||
if !c.NextArg() {
|
||||
return configs, c.ArgErr()
|
||||
}
|
||||
|
||||
m.webDavURL = "c.Val()"
|
||||
m.SetWebDavURL(c.Val())
|
||||
case "show":
|
||||
if !c.NextArg() {
|
||||
return configs, c.ArgErr()
|
||||
|
@ -251,7 +243,7 @@ func makeCommand(c *caddy.Controller, m *config) (Command, error) {
|
|||
|
||||
fn = func(r *http.Request, c *FileManager, u *User) error {
|
||||
path := strings.Replace(r.URL.Path, m.baseURL+m.webDavURL, "", 1)
|
||||
path = u.Scope() + "/" + path
|
||||
path = u.Scope + "/" + path
|
||||
path = filepath.Clean(path)
|
||||
|
||||
for i := range args {
|
||||
|
|
|
@ -77,7 +77,7 @@ func command(c *requestContext, w http.ResponseWriter, r *http.Request) (int, er
|
|||
}
|
||||
|
||||
// Gets the path and initializes a buffer.
|
||||
path := c.us.scope + "/" + r.URL.Path
|
||||
path := c.us.Scope + "/" + r.URL.Path
|
||||
path = filepath.Clean(path)
|
||||
buff := new(bytes.Buffer)
|
||||
|
||||
|
|
|
@ -12,10 +12,10 @@ import (
|
|||
"github.com/mholt/archiver"
|
||||
)
|
||||
|
||||
// serveDownload creates an archive in one of the supported formats (zip, tar,
|
||||
// downloadHandler 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")
|
||||
func downloadHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
query := r.URL.Query().Get("format")
|
||||
|
||||
if !c.fi.IsDir {
|
||||
w.Header().Set("Content-Disposition", "attachment; filename="+c.fi.Name)
|
||||
|
|
22
file.go
22
file.go
|
@ -29,6 +29,8 @@ var (
|
|||
|
||||
// file contains the information about a particular file or directory.
|
||||
type file struct {
|
||||
// Indicates the Kind of view on the front-end (listing, editor or preview).
|
||||
Kind string `json:"kind"`
|
||||
// The name of the file.
|
||||
Name string `json:"name"`
|
||||
// The Size of the file.
|
||||
|
@ -79,15 +81,13 @@ type listing struct {
|
|||
func getInfo(url *url.URL, c *FileManager, u *User) (*file, error) {
|
||||
var err error
|
||||
|
||||
i := &file{URL: c.RootURL() + url.Path}
|
||||
i.VirtualPath = url.Path
|
||||
i.VirtualPath = strings.TrimPrefix(i.VirtualPath, "/")
|
||||
i.VirtualPath = "/" + i.VirtualPath
|
||||
i := &file{
|
||||
URL: c.RootURL() + "/files" + url.Path,
|
||||
VirtualPath: url.Path,
|
||||
Path: filepath.Join(u.Scope, url.Path),
|
||||
}
|
||||
|
||||
i.Path = u.scope + i.VirtualPath
|
||||
i.Path = filepath.Clean(i.Path)
|
||||
|
||||
info, err := os.Stat(i.Path)
|
||||
info, err := u.fileSystem.Stat(context.TODO(), i.Path)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
|
@ -103,8 +103,6 @@ func getInfo(url *url.URL, c *FileManager, u *User) (*file, error) {
|
|||
|
||||
// getListing gets the information about a specific directory and its files.
|
||||
func (i *file) getListing(c *requestContext, r *http.Request) error {
|
||||
baseURL := c.fm.RootURL() + r.URL.Path
|
||||
|
||||
// Gets the directory information using the Virtual File System of
|
||||
// the user configuration.
|
||||
f, err := c.us.fileSystem.OpenFile(context.TODO(), c.fi.VirtualPath, os.O_RDONLY, 0)
|
||||
|
@ -140,7 +138,7 @@ func (i *file) getListing(c *requestContext, r *http.Request) error {
|
|||
}
|
||||
|
||||
// Absolute URL
|
||||
url := url.URL{Path: baseURL + name}
|
||||
url := url.URL{Path: i.URL + name}
|
||||
|
||||
i := file{
|
||||
Name: f.Name(),
|
||||
|
@ -165,7 +163,7 @@ func (i *file) getListing(c *requestContext, r *http.Request) error {
|
|||
}
|
||||
|
||||
// getEditor gets the editor based on a Info struct
|
||||
func (i *file) getEditor(r *http.Request) error {
|
||||
func (i *file) getEditor() error {
|
||||
i.Language = editorLanguage(i.Extension)
|
||||
|
||||
// If the editor will hold only content, leave now.
|
||||
|
|
|
@ -29,11 +29,6 @@ type FileManager struct {
|
|||
// a trailing slash and mustn't contain prefixURL, if set.
|
||||
baseURL string
|
||||
|
||||
// webDavURL is the path where the WebDAV will be accessible. It can be set to ""
|
||||
// in order to override the GUI and only use the WebDAV. It musn't end with
|
||||
// a trailing slash.
|
||||
webDavURL string
|
||||
|
||||
// Users is a map with the different configurations for each user.
|
||||
Users map[string]*User
|
||||
|
||||
|
@ -43,8 +38,7 @@ type FileManager struct {
|
|||
// AfterSave is a function that is called before saving a file.
|
||||
AfterSave Command
|
||||
|
||||
templates *rice.Box
|
||||
static http.Handler
|
||||
assets *rice.Box
|
||||
}
|
||||
|
||||
// Command is a command function.
|
||||
|
@ -53,15 +47,12 @@ type Command func(r *http.Request, m *FileManager, u *User) error
|
|||
// User contains the configuration for each user. It should be created
|
||||
// using NewUser on a File Manager instance.
|
||||
type User struct {
|
||||
// scope is the physical path the user has access to.
|
||||
scope string
|
||||
// Scope is the physical path the user has access to.
|
||||
Scope string
|
||||
|
||||
// fileSystem is the virtual file system the user has access.
|
||||
fileSystem webdav.FileSystem
|
||||
|
||||
// handler handles incoming requests to the WebDAV backend.
|
||||
handler *webdav.Handler
|
||||
|
||||
// Rules is an array of access and deny rules.
|
||||
Rules []*Rule `json:"-"`
|
||||
|
||||
|
@ -106,13 +97,11 @@ func New(scope string) *FileManager {
|
|||
Users: map[string]*User{},
|
||||
BeforeSave: func(r *http.Request, m *FileManager, u *User) error { return nil },
|
||||
AfterSave: func(r *http.Request, m *FileManager, u *User) error { return nil },
|
||||
static: http.FileServer(rice.MustFindBox("./_assets/dist_dev/_").HTTPBox()),
|
||||
templates: rice.MustFindBox("./_assets/dist_dev/templates"),
|
||||
assets: rice.MustFindBox("./_assets/dist"),
|
||||
}
|
||||
|
||||
m.SetScope(scope, "")
|
||||
m.SetBaseURL("/")
|
||||
m.SetWebDavURL("/webdav")
|
||||
|
||||
return m
|
||||
}
|
||||
|
@ -126,7 +115,7 @@ func (m FileManager) RootURL() string {
|
|||
// WebDavURL returns the actual URL
|
||||
// where WebDAV can be accessed.
|
||||
func (m FileManager) WebDavURL() string {
|
||||
return m.prefixURL + m.baseURL + m.webDavURL
|
||||
return m.prefixURL + m.baseURL + "/api/webdav"
|
||||
}
|
||||
|
||||
// SetPrefixURL updates the prefixURL of a File
|
||||
|
@ -147,32 +136,6 @@ func (m *FileManager) SetBaseURL(url string) {
|
|||
m.baseURL = strings.TrimSuffix(url, "/")
|
||||
}
|
||||
|
||||
// SetWebDavURL updates the webDavURL of a File Manager
|
||||
// object and updates it's main handler.
|
||||
func (m *FileManager) SetWebDavURL(url string) {
|
||||
url = strings.TrimPrefix(url, "/")
|
||||
url = strings.TrimSuffix(url, "/")
|
||||
|
||||
m.webDavURL = "/" + url
|
||||
|
||||
// update base user webdav handler
|
||||
m.handler = &webdav.Handler{
|
||||
Prefix: m.webDavURL,
|
||||
FileSystem: m.fileSystem,
|
||||
LockSystem: webdav.NewMemLS(),
|
||||
}
|
||||
|
||||
// update other users' handlers to match
|
||||
// the new URL
|
||||
for _, u := range m.Users {
|
||||
u.handler = &webdav.Handler{
|
||||
Prefix: m.webDavURL,
|
||||
FileSystem: u.fileSystem,
|
||||
LockSystem: webdav.NewMemLS(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetScope updates a user scope and its virtual file system.
|
||||
// If the user string is blank, it will change the base scope.
|
||||
func (m *FileManager) SetScope(scope string, username string) error {
|
||||
|
@ -188,14 +151,8 @@ func (m *FileManager) SetScope(scope string, username string) error {
|
|||
}
|
||||
}
|
||||
|
||||
u.scope = strings.TrimSuffix(scope, "/")
|
||||
u.fileSystem = webdav.Dir(u.scope)
|
||||
|
||||
u.handler = &webdav.Handler{
|
||||
Prefix: m.webDavURL,
|
||||
FileSystem: u.fileSystem,
|
||||
LockSystem: webdav.NewMemLS(),
|
||||
}
|
||||
u.Scope = strings.TrimSuffix(scope, "/")
|
||||
u.fileSystem = webdav.Dir(u.Scope)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -208,9 +165,8 @@ func (m *FileManager) NewUser(username string) error {
|
|||
}
|
||||
|
||||
m.Users[username] = &User{
|
||||
scope: m.User.scope,
|
||||
fileSystem: m.User.fileSystem,
|
||||
handler: m.User.handler,
|
||||
Scope: m.User.Scope,
|
||||
Rules: m.User.Rules,
|
||||
AllowNew: m.User.AllowNew,
|
||||
AllowEdit: m.User.AllowEdit,
|
||||
|
@ -252,8 +208,3 @@ func (u User) Allowed(url string) bool {
|
|||
|
||||
return true
|
||||
}
|
||||
|
||||
// Scope returns the user scope.
|
||||
func (u User) Scope() string {
|
||||
return u.scope
|
||||
}
|
||||
|
|
250
http.go
250
http.go
|
@ -1,200 +1,98 @@
|
|||
package filemanager
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// assetsURL is the url where static assets are served.
|
||||
const assetsURL = "/_"
|
||||
|
||||
// requestContext contains the needed information to make handlers work.
|
||||
type requestContext struct {
|
||||
us *User
|
||||
fm *FileManager
|
||||
fi *file
|
||||
pg *page
|
||||
}
|
||||
|
||||
// serveHTTP is the main entry point of this HTML application.
|
||||
func serveHTTP(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
var (
|
||||
code int
|
||||
err 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)
|
||||
|
||||
// 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) || len(c.fm.baseURL) == 0 {
|
||||
r.URL.Path = p
|
||||
} else {
|
||||
if len(p) >= len(r.URL.Path) && c.fm.baseURL != "" {
|
||||
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 {
|
||||
r.URL.Path = strings.TrimPrefix(r.URL.Path, assetsURL)
|
||||
c.fm.static.ServeHTTP(w, r)
|
||||
return 0, 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(r.URL.Path),
|
||||
"application/javascript",
|
||||
c.fm.RootURL(),
|
||||
)
|
||||
}
|
||||
|
||||
return http.StatusForbidden, nil
|
||||
// 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
|
||||
}
|
||||
|
||||
username, _, _ := r.BasicAuth()
|
||||
if _, ok := c.fm.Users[username]; ok {
|
||||
c.us = c.fm.Users[username]
|
||||
} else {
|
||||
c.us = c.fm.User
|
||||
return staticHandler(c, w, r)
|
||||
}
|
||||
|
||||
// Checks if the request URL is for the WebDav server.
|
||||
if matchURL(r.URL.Path, c.fm.webDavURL) {
|
||||
return serveWebDAV(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 serveAPI(c, w, r)
|
||||
}
|
||||
|
||||
// Checks if this request is made to the base path /files. If so,
|
||||
// shows the index.html page.
|
||||
if matchURL(r.URL.Path, "/files") {
|
||||
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 renderFile(
|
||||
w,
|
||||
c.fm.assets.MustString("index.html"),
|
||||
"text/html",
|
||||
c.fm.RootURL(),
|
||||
)
|
||||
}
|
||||
|
||||
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 *file
|
||||
|
||||
// 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)
|
||||
http.Redirect(w, r, c.fm.RootURL()+"/files"+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
|
||||
default:
|
||||
code, err = serveDefault(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
|
||||
}
|
||||
}
|
||||
|
||||
// Execute beforeSave if it is a PUT request.
|
||||
if r.Method == http.MethodPut {
|
||||
if err = c.fm.BeforeSave(r, c.fm, c.us); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
|
||||
c.fm.handler.ServeHTTP(w, r)
|
||||
|
||||
// Execute afterSave if it is a PUT request.
|
||||
if r.Method == http.MethodPut {
|
||||
if err = c.fm.AfterSave(r, c.fm, c.us); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
|
||||
// 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(r.URL.Path),
|
||||
"application/json",
|
||||
c.fm.RootURL(),
|
||||
)
|
||||
}
|
||||
|
||||
// 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")
|
||||
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 {
|
||||
|
@ -207,30 +105,32 @@ func serveChecksum(c *requestContext, w http.ResponseWriter, r *http.Request) (i
|
|||
return 0, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
// renderFile renders a file using a template with some needed variables.
|
||||
func renderFile(w http.ResponseWriter, file string, contentType string, baseURL string) (int, error) {
|
||||
tpl := template.Must(template.New("file").Parse(file))
|
||||
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
|
||||
|
||||
err := tpl.Execute(w, map[string]string{"BaseURL": baseURL})
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
// 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)
|
||||
// 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.
|
||||
|
@ -244,6 +144,8 @@ func matchURL(first, second string) bool {
|
|||
// 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):
|
||||
|
|
195
page.go
195
page.go
|
@ -1,195 +0,0 @@
|
|||
package filemanager
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// functions contains the non-standard functions that are available
|
||||
// to use on the HTML templates.
|
||||
var functions = template.FuncMap{
|
||||
"CSS": func(s string) template.CSS {
|
||||
return template.CSS(s)
|
||||
},
|
||||
"Marshal": func(v interface{}) template.JS {
|
||||
a, _ := json.Marshal(v)
|
||||
return template.JS(a)
|
||||
},
|
||||
}
|
||||
|
||||
// page contains the information needed to fill a page template.
|
||||
type page struct {
|
||||
User *User `json:"-"`
|
||||
BaseURL string `json:"-"`
|
||||
WebDavURL string `json:"-"`
|
||||
Kind string `json:"kind"`
|
||||
Data *file `json:"data"`
|
||||
}
|
||||
|
||||
/*
|
||||
// breadcrumbItem contains the Name and the URL of a breadcrumb piece.
|
||||
type breadcrumbItem struct {
|
||||
Name string
|
||||
URL string
|
||||
}
|
||||
|
||||
// BreadcrumbMap returns p.Path where every element is a map
|
||||
// of URLs and path segment names.
|
||||
func (p page) BreadcrumbMap() []breadcrumbItem {
|
||||
// TODO: when it is preview alongside with listing!!!!!!!!!!
|
||||
result := []breadcrumbItem{}
|
||||
|
||||
if len(p.Path) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
// skip trailing slash
|
||||
lpath := p.Path
|
||||
if lpath[len(lpath)-1] == '/' {
|
||||
lpath = lpath[:len(lpath)-1]
|
||||
}
|
||||
|
||||
parts := strings.Split(lpath, "/")
|
||||
for i, part := range parts {
|
||||
if i == len(parts)-1 {
|
||||
continue
|
||||
}
|
||||
|
||||
if i == 0 && part == "" {
|
||||
result = append([]breadcrumbItem{{
|
||||
Name: "/",
|
||||
URL: "/",
|
||||
}}, result...)
|
||||
continue
|
||||
}
|
||||
|
||||
result = append([]breadcrumbItem{{
|
||||
Name: part,
|
||||
URL: strings.Join(parts[:i+1], "/") + "/",
|
||||
}}, result...)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// PreviousLink returns the URL of the previous folder.
|
||||
func (p page) PreviousLink() string {
|
||||
path := strings.TrimSuffix(p.Path, "/")
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
path = p.BaseURL + "/" + path
|
||||
path = path[0 : len(path)-len(p.Name)]
|
||||
|
||||
if len(path) < len(p.BaseURL+"/") {
|
||||
return ""
|
||||
}
|
||||
|
||||
return path
|
||||
} */
|
||||
|
||||
func (p page) Render(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if strings.Contains(r.Header.Get("Accept"), "application/json") {
|
||||
marsh, err := json.MarshalIndent(p, "", " ")
|
||||
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
|
||||
}
|
||||
|
||||
var tpl *template.Template
|
||||
|
||||
// Get the template from the assets
|
||||
file, err := c.fm.templates.String("index.html")
|
||||
|
||||
// Check if there is some error. If so, the template doesn't exist
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
tpl, err = template.New("index").Funcs(functions).Parse(file)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err = tpl.Execute(buf, p)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, err = buf.WriteTo(w)
|
||||
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// htmlError prints the error page
|
||||
func htmlError(w http.ResponseWriter, code int, err error) (int, error) {
|
||||
tpl := errTemplate
|
||||
tpl = strings.Replace(tpl, "TITLE", strconv.Itoa(code)+" "+http.StatusText(code), -1)
|
||||
tpl = strings.Replace(tpl, "CODE", err.Error(), -1)
|
||||
|
||||
_, err = w.Write([]byte(tpl))
|
||||
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
const errTemplate = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>TITLE</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
html {
|
||||
background-color: #2196f3;
|
||||
color: #fff;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
code {
|
||||
background-color: rgba(0,0,0,0.1);
|
||||
border-radius: 5px;
|
||||
padding: 1em;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.center {
|
||||
max-width: 40em;
|
||||
margin: 2em auto 0;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #eee;
|
||||
font-weight: bold;
|
||||
}
|
||||
p {
|
||||
line-height: 1.3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="center">
|
||||
<h1>TITLE</h1>
|
||||
|
||||
<p>Try reloading the page or hitting the back button. If this error persists, it seems that you may have found a bug! Please create an issue at <a href="https://github.com/hacdias/caddy-filemanager/issues">hacdias/caddy-filemanager</a> repository on GitHub with the code below.</p>
|
||||
|
||||
<code>CODE</code>
|
||||
</div>
|
||||
</html>`
|
|
@ -73,7 +73,7 @@ func search(c *requestContext, w http.ResponseWriter, r *http.Request) (int, err
|
|||
search = parseSearch(value)
|
||||
scope := strings.TrimPrefix(r.URL.Path, "/")
|
||||
scope = "/" + scope
|
||||
scope = c.us.scope + scope
|
||||
scope = c.us.Scope + scope
|
||||
scope = strings.Replace(scope, "\\", "/", -1)
|
||||
scope = filepath.Clean(scope)
|
||||
|
||||
|
|
142
serve.go
142
serve.go
|
@ -1,142 +0,0 @@
|
|||
package filemanager
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func serveDefault(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
var err error
|
||||
|
||||
// Starts building the page.
|
||||
c.pg = &page{
|
||||
User: c.us,
|
||||
BaseURL: c.fm.RootURL(),
|
||||
WebDavURL: c.fm.WebDavURL(),
|
||||
Data: c.fi,
|
||||
}
|
||||
|
||||
// If it is a dir, go and serve the listing.
|
||||
if c.fi.IsDir {
|
||||
return serveListing(c, w, r)
|
||||
}
|
||||
|
||||
// Tries to get the file type.
|
||||
if err = c.fi.RetrieveFileType(); err != nil {
|
||||
return errorToHTTP(err, true), err
|
||||
}
|
||||
|
||||
// If it can't be edited or the user isn't allowed to,
|
||||
// serve it as a listing, with a preview of the file.
|
||||
if !c.fi.CanBeEdited() || !c.us.AllowEdit {
|
||||
c.pg.Kind = "preview"
|
||||
} else {
|
||||
// Otherwise, we just bring the editor in!
|
||||
c.pg.Kind = "editor"
|
||||
|
||||
err = c.fi.getEditor(r)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
|
||||
return c.pg.Render(c, w, r)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
c.pg.Kind = "listing"
|
||||
|
||||
err = c.fi.getListing(c, r)
|
||||
if err != nil {
|
||||
return errorToHTTP(err, true), err
|
||||
}
|
||||
|
||||
listing := c.fi.listing
|
||||
|
||||
cookieScope := c.fm.RootURL()
|
||||
if cookieScope == "" {
|
||||
cookieScope = "/"
|
||||
}
|
||||
|
||||
// Copy the query values into the Listing struct
|
||||
listing.Sort, listing.Order, err = handleSortOrder(w, r, cookieScope)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
|
||||
listing.ApplySort()
|
||||
|
||||
listing.Display = displayMode(w, r, cookieScope)
|
||||
return c.pg.Render(c, w, r)
|
||||
}
|
||||
|
||||
// displayMode obtaisn the display mode from URL, or from the
|
||||
// cookie.
|
||||
func displayMode(w http.ResponseWriter, r *http.Request, scope string) string {
|
||||
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,
|
||||
MaxAge: 31536000,
|
||||
Path: scope,
|
||||
Secure: r.TLS != nil,
|
||||
})
|
||||
|
||||
return displayMode
|
||||
}
|
||||
|
||||
// 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, err error) {
|
||||
sort = r.URL.Query().Get("sort")
|
||||
order = r.URL.Query().Get("order")
|
||||
|
||||
// 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,
|
||||
MaxAge: 31536000,
|
||||
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,
|
||||
MaxAge: 31536000,
|
||||
Path: scope,
|
||||
Secure: r.TLS != nil,
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
Loading…
Reference in New Issue