diff --git a/assets/embed/templates/single.tmpl b/assets/embed/templates/single.tmpl index d70d1646..f360663e 100644 --- a/assets/embed/templates/single.tmpl +++ b/assets/embed/templates/single.tmpl @@ -6,7 +6,9 @@ {{ else if eq .Type "audio" }} {{ else if eq .Type "video" }} - + + {{ else if eq .Type "blob" }} + Download {{ else}}
{{ .StringifyContent }}{{ end }} diff --git a/file/download.go b/file/download.go index ce751e3f..b691ba57 100644 --- a/file/download.go +++ b/file/download.go @@ -1,56 +1 @@ package file - -import ( - "io" - "io/ioutil" - "net/http" - "os" - "path/filepath" - - "github.com/mholt/archiver" -) - -// DownloadAs creates an archieve in one of the supported formats (zip, tar, -// tar.gz or tar.bz2) and sends it to be downloaded. -func (i *Info) DownloadAs(w http.ResponseWriter, query string) (int, error) { - 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, []string{i.Path}) - case "tar": - extension, err = ".tar", archiver.Tar.Make(tempfile, []string{i.Path}) - case "targz": - extension, err = ".tar.gz", archiver.TarGz.Make(tempfile, []string{i.Path}) - case "tarbz2": - extension, err = ".tar.bz2", archiver.TarBz2.Make(tempfile, []string{i.Path}) - 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 - } - - w.Header().Set("Content-Disposition", "attachment; filename="+i.Name()+extension) - io.Copy(w, file) - return http.StatusOK, nil -} diff --git a/file/info.go b/file/info.go index 533bae86..9ffaa66d 100644 --- a/file/info.go +++ b/file/info.go @@ -10,7 +10,6 @@ import ( humanize "github.com/dustin/go-humanize" "github.com/hacdias/caddy-filemanager/config" - "github.com/hacdias/caddy-filemanager/page" "github.com/hacdias/caddy-filemanager/utils" ) @@ -75,63 +74,6 @@ func (i Info) HumanModTime(format string) string { return i.ModTime().Format(format) } -func (i *Info) ServeHTTP(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) { - if i.IsDir() { - return i.serveListing(w, r, c, u) - } - - return i.serveSingleFile(w, r, c, u) -} - -func (i *Info) serveSingleFile(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) { - err := i.Read() - if err != nil { - code := http.StatusInternalServerError - - switch { - case os.IsPermission(err): - code = http.StatusForbidden - case os.IsNotExist(err): - code = http.StatusGone - case os.IsExist(err): - code = http.StatusGone - } - - return code, err - } - - if i.Type == "blob" { - http.Redirect( - w, r, - c.AddrPath+r.URL.Path+"?download=true", - http.StatusTemporaryRedirect, - ) - return 0, nil - } - - p := &page.Page{ - Info: &page.Info{ - Name: i.Name(), - Path: i.VirtualPath, - IsDir: false, - Data: i, - User: u, - Config: c, - }, - } - - if i.CanBeEdited() && u.AllowEdit { - p.Data, err = i.GetEditor() - if err != nil { - return http.StatusInternalServerError, err - } - - return p.PrintAsHTML(w, "frontmatter", "editor") - } - - return p.PrintAsHTML(w, "single") -} - func simplifyMediaType(name string) string { if strings.HasPrefix(name, "video") { return "video" diff --git a/file/listing.go b/file/listing.go index 757a9080..91ff2b1a 100644 --- a/file/listing.go +++ b/file/listing.go @@ -1,16 +1,13 @@ package file import ( - "encoding/json" - "net/http" "net/url" "os" "path" + "sort" "strings" "github.com/hacdias/caddy-filemanager/config" - "github.com/hacdias/caddy-filemanager/page" - "github.com/hacdias/caddy-filemanager/utils" "github.com/mholt/caddy/caddyhttp/httpserver" ) @@ -19,7 +16,7 @@ import ( type Listing struct { // The name of the directory (the last element of the path) Name string - // The full path of the request + // The full path of the request relatively to a File System Path string // The items (files and folders) in the path Items []Info @@ -36,76 +33,17 @@ type Listing struct { httpserver.Context `json:"-"` } -func (i *Info) serveListing(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) { - var err error - +// GetListing gets the information about a specific directory and its files. +func GetListing(u *config.User, filePath string, baseURL string) (*Listing, error) { // Gets the directory information using the Virtual File System of - // the user configuration - file, err := u.FileSystem.OpenFile(i.VirtualPath, os.O_RDONLY, 0) + // the user configuration. + file, err := u.FileSystem.OpenFile(filePath, os.O_RDONLY, 0) if err != nil { - return utils.ErrorToHTTPCode(err, true), err + return nil, err } defer file.Close() - // Loads the content of the directory - listing, err := i.loadDirectoryContents(file, r.URL.Path, u) - if err != nil { - return utils.ErrorToHTTPCode(err, true), err - } - - listing.Context = httpserver.Context{ - Root: http.Dir(u.Scope), - Req: r, - URL: r.URL, - } - - // Copy the query values into the Listing struct - var limit int - listing.Sort, listing.Order, limit, err = handleSortOrder(w, r, c.Scope) - 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 - } - - page := &page.Page{ - Info: &page.Info{ - Name: listing.Name, - Path: i.VirtualPath, - IsDir: true, - User: u, - Config: c, - Data: listing, - }, - } - - if r.Header.Get("Minimal") == "true" { - page.Minimal = true - } - - return page.PrintAsHTML(w, "listing") -} - -func (i Info) loadDirectoryContents(file http.File, basePath string, u *config.User) (*Listing, error) { + // Reads the directory and gets the information about the files. files, err := file.Readdir(-1) if err != nil { return nil, err @@ -127,19 +65,108 @@ func (i Info) loadDirectoryContents(file http.File, basePath string, u *config.U } // Absolute URL - url := url.URL{Path: basePath + name} + url := url.URL{Path: baseURL + name} fileinfos = append(fileinfos, Info{ FileInfo: f, URL: url.String(), - UserAllowed: u.Allowed(i.VirtualPath), + UserAllowed: u.Allowed(filePath), }) } return &Listing{ - Name: path.Base(i.VirtualPath), - Path: i.VirtualPath, + Name: path.Base(filePath), + Path: filePath, Items: fileinfos, NumDirs: dirCount, NumFiles: fileCount, }, nil } + +// ApplySort applies the sort order using .Order and .Sort +func (l Listing) ApplySort() { + // Check '.Order' to know how to sort + if l.Order == "desc" { + switch l.Sort { + case "name": + sort.Sort(sort.Reverse(byName(l))) + case "size": + sort.Sort(sort.Reverse(bySize(l))) + case "time": + sort.Sort(sort.Reverse(byTime(l))) + default: + // If not one of the above, do nothing + return + } + } else { // If we had more Orderings we could add them here + switch l.Sort { + case "name": + sort.Sort(byName(l)) + case "size": + sort.Sort(bySize(l)) + case "time": + sort.Sort(byTime(l)) + default: + sort.Sort(byName(l)) + return + } + } +} + +// Implement sorting for Listing +type byName Listing +type bySize Listing +type byTime Listing + +// By Name +func (l byName) Len() int { + return len(l.Items) +} + +func (l byName) Swap(i, j int) { + l.Items[i], l.Items[j] = l.Items[j], l.Items[i] +} + +// Treat upper and lower case equally +func (l byName) Less(i, j int) bool { + if l.Items[i].IsDir() && !l.Items[j].IsDir() { + return true + } + + if !l.Items[i].IsDir() && l.Items[j].IsDir() { + return false + } + + return strings.ToLower(l.Items[i].Name()) < strings.ToLower(l.Items[j].Name()) +} + +// By Size +func (l bySize) Len() int { + return len(l.Items) +} + +func (l bySize) Swap(i, j int) { + l.Items[i], l.Items[j] = l.Items[j], l.Items[i] +} + +const directoryOffset = -1 << 31 // = math.MinInt32 +func (l bySize) Less(i, j int) bool { + iSize, jSize := l.Items[i].Size(), l.Items[j].Size() + if l.Items[i].IsDir() { + iSize = directoryOffset + iSize + } + if l.Items[j].IsDir() { + jSize = directoryOffset + jSize + } + return iSize < jSize +} + +// By Time +func (l byTime) Len() int { + return len(l.Items) +} +func (l byTime) Swap(i, j int) { + l.Items[i], l.Items[j] = l.Items[j], l.Items[i] +} +func (l byTime) Less(i, j int) bool { + return l.Items[i].ModTime().Before(l.Items[j].ModTime()) +} diff --git a/file/listing_sort.go b/file/listing_sort.go deleted file mode 100644 index 54076fda..00000000 --- a/file/listing_sort.go +++ /dev/null @@ -1,148 +0,0 @@ -package file - -import ( - "net/http" - "sort" - "strconv" - "strings" -) - -// 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 -} - -// Add sorting method to "Listing" -// it will apply what's in ".Sort" and ".Order" -func (l Listing) applySort() { - // Check '.Order' to know how to sort - if l.Order == "desc" { - switch l.Sort { - case "name": - sort.Sort(sort.Reverse(byName(l))) - case "size": - sort.Sort(sort.Reverse(bySize(l))) - case "time": - sort.Sort(sort.Reverse(byTime(l))) - default: - // If not one of the above, do nothing - return - } - } else { // If we had more Orderings we could add them here - switch l.Sort { - case "name": - sort.Sort(byName(l)) - case "size": - sort.Sort(bySize(l)) - case "time": - sort.Sort(byTime(l)) - default: - sort.Sort(byName(l)) - return - } - } -} - -// Implement sorting for Listing -type byName Listing -type bySize Listing -type byTime Listing - -// By Name -func (l byName) Len() int { - return len(l.Items) -} - -func (l byName) Swap(i, j int) { - l.Items[i], l.Items[j] = l.Items[j], l.Items[i] -} - -// Treat upper and lower case equally -func (l byName) Less(i, j int) bool { - if l.Items[i].IsDir() && !l.Items[j].IsDir() { - return true - } - - if !l.Items[i].IsDir() && l.Items[j].IsDir() { - return false - } - - return strings.ToLower(l.Items[i].Name()) < strings.ToLower(l.Items[j].Name()) -} - -// By Size -func (l bySize) Len() int { - return len(l.Items) -} - -func (l bySize) Swap(i, j int) { - l.Items[i], l.Items[j] = l.Items[j], l.Items[i] -} - -const directoryOffset = -1 << 31 // = math.MinInt32 -func (l bySize) Less(i, j int) bool { - iSize, jSize := l.Items[i].Size(), l.Items[j].Size() - if l.Items[i].IsDir() { - iSize = directoryOffset + iSize - } - if l.Items[j].IsDir() { - jSize = directoryOffset + jSize - } - return iSize < jSize -} - -// By Time -func (l byTime) Len() int { - return len(l.Items) -} -func (l byTime) Swap(i, j int) { - l.Items[i], l.Items[j] = l.Items[j], l.Items[i] -} -func (l byTime) Less(i, j int) bool { - return l.Items[i].ModTime().Before(l.Items[j].ModTime()) -} diff --git a/filemanager.go b/filemanager.go index a915ba5b..23d31bbd 100644 --- a/filemanager.go +++ b/filemanager.go @@ -10,13 +10,12 @@ package filemanager import ( e "errors" "net/http" - "os/exec" - "path/filepath" "strings" "github.com/hacdias/caddy-filemanager/assets" "github.com/hacdias/caddy-filemanager/config" "github.com/hacdias/caddy-filemanager/file" + "github.com/hacdias/caddy-filemanager/handlers" "github.com/hacdias/caddy-filemanager/page" "github.com/mholt/caddy/caddyhttp/httpserver" ) @@ -126,36 +125,22 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err // Generate anti security token. c.GenerateToken() - if fi.IsDir() { - if val, ok := r.URL.Query()["download"]; ok && val[0] != "" { - return fi.DownloadAs(w, val[0]) - } + switch { + case r.URL.Query().Get("download") != "": + code, err = handlers.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(): + code, err = handlers.ServeListing(w, r, c, user, fi) + default: + code, err = handlers.ServeSingle(w, r, c, user, fi) } - if !fi.IsDir() { - query := r.URL.Query() - webdav := false - - if val, ok := query["raw"]; ok && val[0] == "true" { - webdav = true - } - - if val, ok := query["download"]; ok && val[0] == "true" { - w.Header().Set("Content-Disposition", "attachment; filename="+fi.Name()) - webdav = true - } - - if webdav { - r.URL.Path = strings.Replace(r.URL.Path, c.BaseURL, c.WebDavURL, 1) - c.Handler.ServeHTTP(w, r) - return 0, nil - } - } - - code, err := fi.ServeHTTP(w, r, c, user) if err != nil { - return page.PrintErrorHTML(w, code, err) + code, err = page.PrintErrorHTML(w, code, err) } + return code, err } @@ -172,7 +157,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err return http.StatusUnauthorized, nil } - return command(w, r, c, user) + return handlers.Command(w, r, c, user) } } @@ -182,40 +167,3 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err return f.Next.ServeHTTP(w, r) } - -// command handles the requests for VCS related commands: git, svn and mercurial -func command(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) { - command := strings.Split(r.Header.Get("command"), " ") - - // Check if the command is allowed - mayContinue := false - - for _, cmd := range u.Commands { - if cmd == command[0] { - mayContinue = true - } - } - - if !mayContinue { - return http.StatusForbidden, nil - } - - // Check if the program is talled is installed on the computer - if _, err := exec.LookPath(command[0]); err != nil { - return http.StatusNotImplemented, nil - } - - path := strings.Replace(r.URL.Path, c.BaseURL, c.Scope, 1) - path = filepath.Clean(path) - - cmd := exec.Command(command[0], command[1:len(command)]...) - cmd.Dir = path - output, err := cmd.CombinedOutput() - - if err != nil { - return http.StatusInternalServerError, err - } - - p := &page.Page{Info: &page.Info{Data: string(output)}} - return p.PrintAsJSON(w) -} diff --git a/handlers/command.go b/handlers/command.go new file mode 100644 index 00000000..e2690c42 --- /dev/null +++ b/handlers/command.go @@ -0,0 +1,48 @@ +package handlers + +import ( + "net/http" + "os/exec" + "path/filepath" + "strings" + + "github.com/hacdias/caddy-filemanager/config" + "github.com/hacdias/caddy-filemanager/page" +) + +// Command handles the requests for VCS related commands: git, svn and mercurial +func Command(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) { + command := strings.Split(r.Header.Get("command"), " ") + + // Check if the command is allowed + mayContinue := false + + for _, cmd := range u.Commands { + if cmd == command[0] { + mayContinue = true + } + } + + if !mayContinue { + return http.StatusForbidden, nil + } + + // Check if the program is talled is installed on the computer + if _, err := exec.LookPath(command[0]); err != nil { + return http.StatusNotImplemented, nil + } + + path := strings.Replace(r.URL.Path, c.BaseURL, c.Scope, 1) + path = filepath.Clean(path) + + cmd := exec.Command(command[0], command[1:len(command)]...) + cmd.Dir = path + output, err := cmd.CombinedOutput() + + if err != nil { + return http.StatusInternalServerError, err + } + + p := &page.Page{Info: &page.Info{Data: string(output)}} + return p.PrintAsJSON(w) +} diff --git a/handlers/download.go b/handlers/download.go new file mode 100644 index 00000000..f4fc95a2 --- /dev/null +++ b/handlers/download.go @@ -0,0 +1,70 @@ +package handlers + +import ( + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + + "github.com/hacdias/caddy-filemanager/config" + "github.com/hacdias/caddy-filemanager/file" + "github.com/mholt/archiver" +) + +// Download creates an archieve 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 *config.Config, i *file.Info) (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 + } + + 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, []string{i.Path}) + case "tar": + extension, err = ".tar", archiver.Tar.Make(tempfile, []string{i.Path}) + case "targz": + extension, err = ".tar.gz", archiver.TarGz.Make(tempfile, []string{i.Path}) + case "tarbz2": + extension, err = ".tar.bz2", archiver.TarBz2.Make(tempfile, []string{i.Path}) + 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 + } + + w.Header().Set("Content-Disposition", "attachment; filename="+i.Name()+extension) + io.Copy(w, file) + return http.StatusOK, nil +} diff --git a/handlers/listing.go b/handlers/listing.go new file mode 100644 index 00000000..13b36d33 --- /dev/null +++ b/handlers/listing.go @@ -0,0 +1,123 @@ +package handlers + +import ( + "encoding/json" + "net/http" + "strconv" + "strings" + + "github.com/hacdias/caddy-filemanager/config" + "github.com/hacdias/caddy-filemanager/file" + "github.com/hacdias/caddy-filemanager/page" + "github.com/hacdias/caddy-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 *config.Config, u *config.User, i *file.Info) (int, error) { + var err error + + // Loads the content of the directory + listing, err := file.GetListing(u, i.VirtualPath, 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, + } + + // Copy the query values into the Listing struct + var limit int + listing.Sort, listing.Order, limit, err = handleSortOrder(w, r, c.Scope) + 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 + } + + page := &page.Page{ + Minimal: r.Header.Get("Minimal") == "true", + Info: &page.Info{ + Name: listing.Name, + Path: i.VirtualPath, + IsDir: true, + User: u, + Config: c, + 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 +} diff --git a/handlers/single.go b/handlers/single.go new file mode 100644 index 00000000..93dd3b85 --- /dev/null +++ b/handlers/single.go @@ -0,0 +1,41 @@ +package handlers + +import ( + "net/http" + + "github.com/hacdias/caddy-filemanager/config" + "github.com/hacdias/caddy-filemanager/file" + "github.com/hacdias/caddy-filemanager/page" + "github.com/hacdias/caddy-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 *config.Config, u *config.User, i *file.Info) (int, error) { + err := i.Read() + if 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 i.CanBeEdited() && u.AllowEdit { + p.Data, err = i.GetEditor() + if err != nil { + return http.StatusInternalServerError, err + } + + return p.PrintAsHTML(w, "frontmatter", "editor") + } + + return p.PrintAsHTML(w, "single") +} diff --git a/preprocess.go b/preprocess.go index 0d039252..bec8b2f7 100644 --- a/preprocess.go +++ b/preprocess.go @@ -16,7 +16,13 @@ import ( ) // processPUT is used to update a file that was edited -func processPUT(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User, i *file.Info) (int, error) { +func processPUT( + w http.ResponseWriter, + r *http.Request, + c *config.Config, + u *config.User, + i *file.Info, +) (int, error) { var ( data map[string]interface{} file []byte