aadd first files based on caddy-filemanager
Former-commit-id: 20baeeb41a9555cefc3b31b495e24e907736c443
This commit is contained in:
53
http/checksum.go
Normal file
53
http/checksum.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
e "errors"
|
||||
"hash"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
fm "github.com/hacdias/filemanager"
|
||||
"github.com/hacdias/filemanager/utils"
|
||||
)
|
||||
|
||||
// checksum calculates the hash of a filemanager. Supports MD5, SHA1, SHA256 and SHA512.
|
||||
func checksum(w http.ResponseWriter, r *http.Request, c *fm.Config, i *fm.FileInfo) (int, error) {
|
||||
query := r.URL.Query().Get("checksum")
|
||||
|
||||
file, err := os.Open(i.Path)
|
||||
if err != nil {
|
||||
return utils.ErrorToHTTPCode(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
|
||||
}
|
||||
136
http/command.go
Normal file
136
http/command.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
fm "github.com/hacdias/filemanager"
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
}
|
||||
|
||||
var (
|
||||
cmdNotImplemented = []byte("Command not implemented.")
|
||||
cmdNotAllowed = []byte("Command not allowed.")
|
||||
)
|
||||
|
||||
// command handles the requests for VCS related commands: git, svn and mercurial
|
||||
func command(w http.ResponseWriter, r *http.Request, c *fm.Config, u *fm.User) (int, error) {
|
||||
// Upgrades the connection to a websocket and checks for errors.
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var (
|
||||
message []byte
|
||||
command []string
|
||||
)
|
||||
|
||||
// Starts an infinite loop until a valid command is captured.
|
||||
for {
|
||||
_, message, err = conn.ReadMessage()
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
command = strings.Split(string(message), " ")
|
||||
if len(command) != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the command is allowed
|
||||
allowed := false
|
||||
|
||||
for _, cmd := range u.Commands {
|
||||
if cmd == command[0] {
|
||||
allowed = true
|
||||
}
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
err = conn.WriteMessage(websocket.BinaryMessage, cmdNotAllowed)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Check if the program is talled is installed on the computer.
|
||||
if _, err = exec.LookPath(command[0]); err != nil {
|
||||
err = conn.WriteMessage(websocket.BinaryMessage, cmdNotImplemented)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
return http.StatusNotImplemented, nil
|
||||
}
|
||||
|
||||
// Gets the path and initializes a buffer.
|
||||
path := strings.Replace(r.URL.Path, c.BaseURL, c.Scope, 1)
|
||||
path = filepath.Clean(path)
|
||||
buff := new(bytes.Buffer)
|
||||
|
||||
// Sets up the command executation.
|
||||
cmd := exec.Command(command[0], command[1:]...)
|
||||
cmd.Dir = path
|
||||
cmd.Stderr = buff
|
||||
cmd.Stdout = buff
|
||||
|
||||
// Starts the command and checks for errors.
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
// Set a 'done' variable to check whetever the command has already finished
|
||||
// running or not. This verification is done using a goroutine that uses the
|
||||
// method .Wait() from the command.
|
||||
done := false
|
||||
go func() {
|
||||
err = cmd.Wait()
|
||||
done = true
|
||||
}()
|
||||
|
||||
// Function to print the current information on the buffer to the connection.
|
||||
print := func() error {
|
||||
by := buff.Bytes()
|
||||
if len(by) > 0 {
|
||||
err = conn.WriteMessage(websocket.TextMessage, by)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// While the command hasn't finished running, continue sending the output
|
||||
// to the client in intervals of 100 milliseconds.
|
||||
for !done {
|
||||
if err = print(); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
// After the command is done executing, send the output one more time to the
|
||||
// browser to make sure it gets the latest information.
|
||||
if err = print(); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
96
http/download.go
Normal file
96
http/download.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
fm "github.com/hacdias/filemanager"
|
||||
"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(w http.ResponseWriter, r *http.Request, c *fm.Config, i *fm.FileInfo) (int, error) {
|
||||
query := r.URL.Query().Get("download")
|
||||
|
||||
if !i.IsDir {
|
||||
w.Header().Set("Content-Disposition", "attachment; filename="+i.Name)
|
||||
http.ServeFile(w, r, i.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(i.Path, name))
|
||||
}
|
||||
|
||||
} else {
|
||||
files = append(files, i.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 := i.Name
|
||||
if name == "." || name == "" {
|
||||
name = "download"
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Disposition", "attachment; filename="+name+extension)
|
||||
io.Copy(w, file)
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
121
http/editor.go
Normal file
121
http/editor.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
fm "github.com/hacdias/filemanager"
|
||||
"github.com/hacdias/filemanager/frontmatter"
|
||||
"github.com/spf13/hugo/parser"
|
||||
)
|
||||
|
||||
// Editor contains the information for the editor page
|
||||
type Editor struct {
|
||||
Class string
|
||||
Mode string
|
||||
Visual bool
|
||||
Content string
|
||||
FrontMatter struct {
|
||||
Content *frontmatter.Content
|
||||
Rune rune
|
||||
}
|
||||
}
|
||||
|
||||
// getEditor gets the editor based on a FileInfo struct
|
||||
func getEditor(r *http.Request, i *fm.FileInfo) (*Editor, error) {
|
||||
var err error
|
||||
|
||||
// Create a new editor variable and set the mode
|
||||
e := new(Editor)
|
||||
e.Mode = editorMode(i.Name)
|
||||
e.Class = editorClass(e.Mode)
|
||||
|
||||
if e.Class == "frontmatter-only" || e.Class == "complete" {
|
||||
e.Visual = true
|
||||
}
|
||||
|
||||
if r.URL.Query().Get("visual") == "false" {
|
||||
e.Class = "content-only"
|
||||
}
|
||||
|
||||
hasRune := frontmatter.HasRune(i.Content)
|
||||
|
||||
if e.Class == "frontmatter-only" && !hasRune {
|
||||
e.FrontMatter.Rune, err = frontmatter.StringFormatToRune(e.Mode)
|
||||
if err != nil {
|
||||
goto Error
|
||||
}
|
||||
i.Content = frontmatter.AppendRune(i.Content, e.FrontMatter.Rune)
|
||||
hasRune = true
|
||||
}
|
||||
|
||||
if e.Class == "frontmatter-only" && hasRune {
|
||||
e.FrontMatter.Content, _, err = frontmatter.Pretty(i.Content)
|
||||
if err != nil {
|
||||
goto Error
|
||||
}
|
||||
}
|
||||
|
||||
if e.Class == "complete" && hasRune {
|
||||
var page parser.Page
|
||||
// Starts a new buffer and parses the file using Hugo's functions
|
||||
buffer := bytes.NewBuffer(i.Content)
|
||||
page, err = parser.ReadFrom(buffer)
|
||||
|
||||
if err != nil {
|
||||
goto Error
|
||||
}
|
||||
|
||||
// Parses the page content and the frontmatter
|
||||
e.Content = strings.TrimSpace(string(page.Content()))
|
||||
e.FrontMatter.Rune = rune(i.Content[0])
|
||||
e.FrontMatter.Content, _, err = frontmatter.Pretty(page.FrontMatter())
|
||||
}
|
||||
|
||||
if e.Class == "complete" && !hasRune {
|
||||
err = errors.New("Complete but without rune")
|
||||
}
|
||||
|
||||
Error:
|
||||
if e.Class == "content-only" || err != nil {
|
||||
e.Class = "content-only"
|
||||
e.Content = i.StringifyContent()
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func editorClass(mode string) string {
|
||||
switch mode {
|
||||
case "json", "toml", "yaml":
|
||||
return "frontmatter-only"
|
||||
case "markdown", "asciidoc", "rst":
|
||||
return "complete"
|
||||
}
|
||||
|
||||
return "content-only"
|
||||
}
|
||||
|
||||
func editorMode(filename string) string {
|
||||
mode := strings.TrimPrefix(filepath.Ext(filename), ".")
|
||||
|
||||
switch mode {
|
||||
case "md", "markdown", "mdown", "mmark":
|
||||
mode = "markdown"
|
||||
case "asciidoc", "adoc", "ad":
|
||||
mode = "asciidoc"
|
||||
case "rst":
|
||||
mode = "rst"
|
||||
case "html", "htm":
|
||||
mode = "html"
|
||||
case "js":
|
||||
mode = "javascript"
|
||||
case "go":
|
||||
mode = "golang"
|
||||
}
|
||||
|
||||
return mode
|
||||
}
|
||||
173
http/http.go
Normal file
173
http/http.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
fm "github.com/hacdias/filemanager"
|
||||
"github.com/hacdias/filemanager/assets"
|
||||
"github.com/hacdias/filemanager/page"
|
||||
"github.com/hacdias/filemanager/utils"
|
||||
"github.com/hacdias/filemanager/wrapper"
|
||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||
)
|
||||
|
||||
// ServeHTTP starts FileManager.
|
||||
func ServeHTTP(w http.ResponseWriter, r *http.Request, c *fm.Config) (int, error) {
|
||||
var (
|
||||
fi *fm.FileInfo
|
||||
user *fm.User
|
||||
code int
|
||||
err error
|
||||
)
|
||||
|
||||
// Checks if the URL matches the Assets URL. Returns the asset if the
|
||||
// method is GET and Status Forbidden otherwise.
|
||||
if strings.HasPrefix(r.URL.Path, c.BaseURL+assets.BaseURL) {
|
||||
if r.Method == http.MethodGet {
|
||||
return assets.Serve(w, r, c)
|
||||
}
|
||||
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
// Obtains the user.
|
||||
username, _, _ := r.BasicAuth()
|
||||
if _, ok := c.Users[username]; ok {
|
||||
user = c.Users[username]
|
||||
} else {
|
||||
user = c.User
|
||||
}
|
||||
|
||||
// Checks if the request URL is for the WebDav server
|
||||
if httpserver.Path(r.URL.Path).Matches(c.WebDavURL) {
|
||||
// Checks for user permissions relatively to this PATH
|
||||
if !user.Allowed(strings.TrimPrefix(r.URL.Path, c.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.WebDavURL, "", 1)
|
||||
path = user.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 = wrapper.NewResponseWriterNoBody(w)
|
||||
}
|
||||
}
|
||||
case "PROPPATCH", "MOVE", "PATCH", "PUT", "DELETE":
|
||||
if !user.AllowEdit {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
case "MKCOL", "COPY":
|
||||
if !user.AllowNew {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Preprocess the PUT request if it's the case
|
||||
if r.Method == http.MethodPut {
|
||||
if err = c.BeforeSave(r, c, user); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
if preProccessPUT(w, r, c, user) != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
|
||||
c.Handler.ServeHTTP(w, r)
|
||||
if err = c.AfterSave(r, c, user); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
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 !user.Allowed(strings.TrimPrefix(r.URL.Path, c.BaseURL)) {
|
||||
if r.Method == http.MethodGet {
|
||||
return page.PrintErrorHTML(
|
||||
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(w, r, c, user)
|
||||
}
|
||||
|
||||
if r.URL.Query().Get("command") != "" {
|
||||
return command(w, r, c, user)
|
||||
}
|
||||
|
||||
if r.Method == http.MethodGet {
|
||||
// Gets the information of the directory/file
|
||||
fi, err = fm.GetInfo(r.URL, c, user)
|
||||
code = utils.ErrorToHTTPCode(err, false)
|
||||
if err != nil {
|
||||
if r.Method == http.MethodGet {
|
||||
return page.PrintErrorHTML(w, code, err)
|
||||
}
|
||||
return code, err
|
||||
}
|
||||
|
||||
// If it's a dir and the path doesn't end with a trailing slash,
|
||||
// redirect the user.
|
||||
if fi.IsDir && !strings.HasSuffix(r.URL.Path, "/") {
|
||||
http.Redirect(w, r, c.PrefixURL+r.URL.Path+"/", http.StatusTemporaryRedirect)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case r.URL.Query().Get("download") != "":
|
||||
code, err = download(w, r, c, fi)
|
||||
case r.URL.Query().Get("raw") == "true" && !fi.IsDir:
|
||||
http.ServeFile(w, r, fi.Path)
|
||||
code, err = 0, nil
|
||||
case !fi.IsDir && r.URL.Query().Get("checksum") != "":
|
||||
code, err = checksum(w, r, c, fi)
|
||||
case fi.IsDir:
|
||||
code, err = serveListing(w, r, c, user, fi)
|
||||
default:
|
||||
code, err = serveSingle(w, r, c, user, fi)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
code, err = page.PrintErrorHTML(w, code, err)
|
||||
}
|
||||
|
||||
return code, err
|
||||
}
|
||||
|
||||
return http.StatusNotImplemented, nil
|
||||
}
|
||||
147
http/listing.go
Normal file
147
http/listing.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
fm "github.com/hacdias/filemanager"
|
||||
"github.com/hacdias/filemanager/page"
|
||||
"github.com/hacdias/filemanager/utils"
|
||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||
)
|
||||
|
||||
// serveListing presents the user with a listage of a directory folder.
|
||||
func serveListing(w http.ResponseWriter, r *http.Request, c *fm.Config, u *fm.User, i *fm.FileInfo) (int, error) {
|
||||
var err error
|
||||
|
||||
// Loads the content of the directory
|
||||
listing, err := fm.GetListing(u, i.VirtualPath, c.PrefixURL+r.URL.Path)
|
||||
if err != nil {
|
||||
return utils.ErrorToHTTPCode(err, true), err
|
||||
}
|
||||
|
||||
listing.Context = httpserver.Context{
|
||||
Root: http.Dir(u.Scope),
|
||||
Req: r,
|
||||
URL: r.URL,
|
||||
}
|
||||
|
||||
cookieScope := c.BaseURL
|
||||
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,
|
||||
})
|
||||
|
||||
page := &page.Page{
|
||||
Minimal: r.Header.Get("Minimal") == "true",
|
||||
Info: &page.Info{
|
||||
Name: listing.Name,
|
||||
Path: i.VirtualPath,
|
||||
IsDir: true,
|
||||
User: u,
|
||||
Config: c,
|
||||
Display: displayMode,
|
||||
Data: listing,
|
||||
},
|
||||
}
|
||||
|
||||
return page.PrintAsHTML(w, "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
|
||||
}
|
||||
144
http/put.go
Normal file
144
http/put.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hacdias/filemanager"
|
||||
"github.com/hacdias/filemanager/frontmatter"
|
||||
)
|
||||
|
||||
// preProccessPUT is used to update a file that was edited
|
||||
func preProccessPUT(
|
||||
w http.ResponseWriter,
|
||||
r *http.Request,
|
||||
c *filemanager.Config,
|
||||
u *filemanager.User,
|
||||
) (err error) {
|
||||
var (
|
||||
data = map[string]interface{}{}
|
||||
file []byte
|
||||
kind string
|
||||
rawBuffer = new(bytes.Buffer)
|
||||
)
|
||||
|
||||
kind = r.Header.Get("kind")
|
||||
rawBuffer.ReadFrom(r.Body)
|
||||
|
||||
if kind != "" {
|
||||
err = json.Unmarshal(rawBuffer.Bytes(), &data)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case "frontmatter-only":
|
||||
if file, err = ParseFrontMatterOnlyFile(data, r.URL.Path); err != nil {
|
||||
return
|
||||
}
|
||||
case "content-only":
|
||||
mainContent := data["content"].(string)
|
||||
mainContent = strings.TrimSpace(mainContent)
|
||||
file = []byte(mainContent)
|
||||
case "complete":
|
||||
var mark rune
|
||||
|
||||
if v := r.Header.Get("Rune"); v != "" {
|
||||
var n int
|
||||
n, err = strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mark = rune(n)
|
||||
}
|
||||
|
||||
if file, err = ParseCompleteFile(data, r.URL.Path, mark); err != nil {
|
||||
return
|
||||
}
|
||||
default:
|
||||
file = rawBuffer.Bytes()
|
||||
}
|
||||
|
||||
// Overwrite the request Body
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(file))
|
||||
return
|
||||
}
|
||||
|
||||
// ParseFrontMatterOnlyFile parses a frontmatter only file
|
||||
func ParseFrontMatterOnlyFile(data interface{}, filename string) ([]byte, error) {
|
||||
frontmatter := strings.TrimPrefix(filepath.Ext(filename), ".")
|
||||
f, err := ParseFrontMatter(data, frontmatter)
|
||||
fString := string(f)
|
||||
|
||||
// If it's toml or yaml, strip frontmatter identifier
|
||||
if frontmatter == "toml" {
|
||||
fString = strings.TrimSuffix(fString, "+++\n")
|
||||
fString = strings.TrimPrefix(fString, "+++\n")
|
||||
}
|
||||
|
||||
if frontmatter == "yaml" {
|
||||
fString = strings.TrimSuffix(fString, "---\n")
|
||||
fString = strings.TrimPrefix(fString, "---\n")
|
||||
}
|
||||
|
||||
f = []byte(fString)
|
||||
return f, err
|
||||
}
|
||||
|
||||
// ParseFrontMatter is the frontmatter parser
|
||||
func ParseFrontMatter(data interface{}, front string) ([]byte, error) {
|
||||
var mark rune
|
||||
|
||||
switch front {
|
||||
case "toml":
|
||||
mark = '+'
|
||||
case "json":
|
||||
mark = '{'
|
||||
case "yaml":
|
||||
mark = '-'
|
||||
default:
|
||||
return nil, errors.New("Unsupported Format provided")
|
||||
}
|
||||
|
||||
return frontmatter.Marshal(data, mark)
|
||||
}
|
||||
|
||||
// ParseCompleteFile parses a complete file
|
||||
func ParseCompleteFile(data map[string]interface{}, filename string, mark rune) ([]byte, error) {
|
||||
mainContent := ""
|
||||
|
||||
if _, ok := data["content"]; ok {
|
||||
// The main content of the file
|
||||
mainContent = data["content"].(string)
|
||||
mainContent = "\n\n" + strings.TrimSpace(mainContent) + "\n"
|
||||
|
||||
// Removes the main content from the rest of the frontmatter
|
||||
delete(data, "content")
|
||||
}
|
||||
|
||||
if _, ok := data["date"]; ok {
|
||||
data["date"] = data["date"].(string) + ":00"
|
||||
}
|
||||
|
||||
front, err := frontmatter.Marshal(data, mark)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
front = frontmatter.AppendRune(front, mark)
|
||||
|
||||
// Generates the final file
|
||||
f := new(bytes.Buffer)
|
||||
f.Write(front)
|
||||
f.Write([]byte(mainContent))
|
||||
return f.Bytes(), nil
|
||||
}
|
||||
118
http/search.go
Normal file
118
http/search.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
fm "github.com/hacdias/filemanager"
|
||||
)
|
||||
|
||||
type searchOptions struct {
|
||||
CaseInsensitive bool
|
||||
Terms []string
|
||||
}
|
||||
|
||||
func parseSearch(value string) *searchOptions {
|
||||
opts := &searchOptions{
|
||||
CaseInsensitive: strings.Contains(value, "case:insensitive"),
|
||||
}
|
||||
|
||||
// removes the options from the value
|
||||
value = strings.Replace(value, "case:insensitive", "", -1)
|
||||
value = strings.Replace(value, "case:sensitive", "", -1)
|
||||
value = strings.TrimSpace(value)
|
||||
|
||||
if opts.CaseInsensitive {
|
||||
value = strings.ToLower(value)
|
||||
}
|
||||
|
||||
// if the value starts with " and finishes what that character, we will
|
||||
// only search for that term
|
||||
if value[0] == '"' && value[len(value)-1] == '"' {
|
||||
unique := strings.TrimPrefix(value, "\"")
|
||||
unique = strings.TrimSuffix(unique, "\"")
|
||||
|
||||
opts.Terms = []string{unique}
|
||||
return opts
|
||||
}
|
||||
|
||||
opts.Terms = strings.Split(value, " ")
|
||||
return opts
|
||||
}
|
||||
|
||||
// search ...
|
||||
func search(w http.ResponseWriter, r *http.Request, c *fm.Config, u *fm.User) (int, error) {
|
||||
// Upgrades the connection to a websocket and checks for errors.
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var (
|
||||
value string
|
||||
search *searchOptions
|
||||
message []byte
|
||||
)
|
||||
|
||||
// Starts an infinite loop until a valid command is captured.
|
||||
for {
|
||||
_, message, err = conn.ReadMessage()
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
if len(message) != 0 {
|
||||
value = string(message)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
search = parseSearch(value)
|
||||
scope := strings.Replace(r.URL.Path, c.BaseURL, "", 1)
|
||||
scope = strings.TrimPrefix(scope, "/")
|
||||
scope = "/" + scope
|
||||
scope = u.Scope + scope
|
||||
scope = strings.Replace(scope, "\\", "/", -1)
|
||||
scope = filepath.Clean(scope)
|
||||
|
||||
err = filepath.Walk(scope, func(path string, f os.FileInfo, err error) error {
|
||||
if search.CaseInsensitive {
|
||||
path = strings.ToLower(path)
|
||||
}
|
||||
|
||||
path = strings.Replace(path, "\\", "/", -1)
|
||||
is := false
|
||||
|
||||
for _, term := range search.Terms {
|
||||
if is {
|
||||
break
|
||||
}
|
||||
|
||||
if strings.Contains(path, term) {
|
||||
if !u.Allowed(path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
is = true
|
||||
}
|
||||
}
|
||||
|
||||
if !is {
|
||||
return nil
|
||||
}
|
||||
|
||||
path = strings.TrimPrefix(path, scope)
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
return conn.WriteMessage(websocket.TextMessage, []byte(path))
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
54
http/single.go
Normal file
54
http/single.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
fm "github.com/hacdias/filemanager"
|
||||
"github.com/hacdias/filemanager/page"
|
||||
"github.com/hacdias/filemanager/utils"
|
||||
)
|
||||
|
||||
// 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(w http.ResponseWriter, r *http.Request, c *fm.Config, u *fm.User, i *fm.FileInfo) (int, error) {
|
||||
var err error
|
||||
|
||||
if err = i.RetrieveFileType(); err != nil {
|
||||
return utils.ErrorToHTTPCode(err, true), err
|
||||
}
|
||||
|
||||
p := &page.Page{
|
||||
Info: &page.Info{
|
||||
Name: i.Name,
|
||||
Path: i.VirtualPath,
|
||||
IsDir: false,
|
||||
Data: i,
|
||||
User: u,
|
||||
Config: c,
|
||||
},
|
||||
}
|
||||
|
||||
// If the request accepts JSON, we send the file information.
|
||||
if strings.Contains(r.Header.Get("Accept"), "application/json") {
|
||||
return p.PrintAsJSON(w)
|
||||
}
|
||||
|
||||
if i.Type == "text" {
|
||||
if err = i.Read(); err != nil {
|
||||
return utils.ErrorToHTTPCode(err, true), err
|
||||
}
|
||||
}
|
||||
|
||||
if i.CanBeEdited() && u.AllowEdit {
|
||||
p.Data, err = getEditor(r, i)
|
||||
p.Editor = true
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
return p.PrintAsHTML(w, "frontmatter", "editor")
|
||||
}
|
||||
|
||||
return p.PrintAsHTML(w, "single")
|
||||
}
|
||||
Reference in New Issue
Block a user