summaryrefslogtreecommitdiff
path: root/modules/caddyhttp/staticfiles/browse.go
diff options
context:
space:
mode:
authorMatthew Holt <mholt@users.noreply.github.com>2019-05-20 15:46:34 -0600
committerMatthew Holt <mholt@users.noreply.github.com>2019-05-20 15:46:52 -0600
commit22995e5655b3d5117743d98bf5c7ba8ed335a2c5 (patch)
treef13543b62f6b6d082a845355ccc30dcbec3bf582 /modules/caddyhttp/staticfiles/browse.go
parent043eb1d9e5db456b9b78c0423cb44716fc81a932 (diff)
Implement most of browse; fix a couple obvious bugs; some cleanup
Diffstat (limited to 'modules/caddyhttp/staticfiles/browse.go')
-rw-r--r--modules/caddyhttp/staticfiles/browse.go308
1 files changed, 130 insertions, 178 deletions
diff --git a/modules/caddyhttp/staticfiles/browse.go b/modules/caddyhttp/staticfiles/browse.go
index 15ff105..2bb130f 100644
--- a/modules/caddyhttp/staticfiles/browse.go
+++ b/modules/caddyhttp/staticfiles/browse.go
@@ -1,58 +1,65 @@
package staticfiles
import (
+ "bytes"
+ "encoding/json"
+ "html/template"
"net/http"
+ "os"
+ "path"
+ "strings"
+
+ "bitbucket.org/lightcodelabs/caddy2"
+ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
)
// Browse configures directory browsing.
type Browse struct {
-}
+ TemplateFile string `json:"template_file"`
-// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
-// If so, control is handed over to ServeListing.
-func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
- // TODO: convert this handler
- return nil
+ template *template.Template
+}
- // // Browse works on existing directories; delegate everything else
- // requestedFilepath, err := bc.Fs.Root.Open(r.URL.Path)
- // if err != nil {
- // switch {
- // case os.IsPermission(err):
- // return http.StatusForbidden, err
- // case os.IsExist(err):
- // return http.StatusNotFound, err
- // default:
- // return b.Next.ServeHTTP(w, r)
- // }
- // }
- // defer requestedFilepath.Close()
+func (sf *StaticFiles) serveBrowse(dirPath string, w http.ResponseWriter, r *http.Request) error {
+ dir, err := sf.openFile(dirPath, w)
+ if err != nil {
+ return err
+ }
+ defer dir.Close()
+
+ repl := r.Context().Value(caddy2.ReplacerCtxKey).(caddy2.Replacer)
+
+ listing, err := sf.loadDirectoryContents(dir, r.URL.Path, repl)
+ switch {
+ case os.IsPermission(err):
+ return caddyhttp.Error(http.StatusForbidden, err)
+ case os.IsNotExist(err):
+ return caddyhttp.Error(http.StatusNotFound, err)
+ case err != nil:
+ return caddyhttp.Error(http.StatusInternalServerError, err)
+ }
+
+ sf.browseApplyQueryParams(w, r, &listing)
+
+ // write response as either JSON or HTML
+ var buf *bytes.Buffer
+ acceptHeader := strings.ToLower(strings.Join(r.Header["Accept"], ","))
+ if strings.Contains(acceptHeader, "application/json") {
+ if buf, err = sf.browseWriteJSON(listing); err != nil {
+ return caddyhttp.Error(http.StatusInternalServerError, err)
+ }
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ } else {
+ if buf, err = sf.browseWriteHTML(listing); err != nil {
+ return caddyhttp.Error(http.StatusInternalServerError, err)
+ }
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ }
+ buf.WriteTo(w)
- // info, err := requestedFilepath.Stat()
- // if err != nil {
- // switch {
- // case os.IsPermission(err):
- // return http.StatusForbidden, err
- // case os.IsExist(err):
- // return http.StatusGone, err
- // default:
- // return b.Next.ServeHTTP(w, r)
- // }
- // }
- // if !info.IsDir() {
- // return b.Next.ServeHTTP(w, r)
- // }
-
- // // Do not reply to anything else because it might be nonsensical
- // switch r.Method {
- // case http.MethodGet, http.MethodHead:
- // // proceed, noop
- // case "PROPFIND", http.MethodOptions:
- // return http.StatusNotImplemented, nil
- // default:
- // return b.Next.ServeHTTP(w, r)
- // }
+ return nil
+ // TODO: Sigh... do we have to put this here?
// // Browsing navigation gets messed up if browsing a directory
// // that doesn't end in "/" (which it should, anyway)
// u := *r.URL
@@ -68,138 +75,83 @@ func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
// return b.ServeListing(w, r, requestedFilepath, bc)
}
-// func (b Browse) loadDirectoryContents(requestedFilepath http.File, urlPath string, config *Config) (*Listing, bool, error) {
-// files, err := requestedFilepath.Readdir(-1)
-// if err != nil {
-// return nil, false, err
-// }
-
-// // Determine if user can browse up another folder
-// var canGoUp bool
-// curPathDir := path.Dir(strings.TrimSuffix(urlPath, "/"))
-// for _, other := range b.Configs {
-// if strings.HasPrefix(curPathDir, other.PathScope) {
-// canGoUp = true
-// break
-// }
-// }
-
-// // Assemble listing of directory contents
-// listing, hasIndex := directoryListing(files, canGoUp, urlPath, config)
-
-// return &listing, hasIndex, nil
-// }
-
-// // handleSortOrder gets and stores for a Listing the 'sort' and 'order',
-// // and reads 'limit' if given. The latter is 0 if not given.
-// //
-// // This sets Cookies.
-// func (b Browse) handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, limit int, err error) {
-// sort, order, limitQuery := r.URL.Query().Get("sort"), r.URL.Query().Get("order"), 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 = sortByNameDirFirst
-// if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil {
-// sort = sortCookie.Value
-// }
-// case sortByName, sortByNameDirFirst, sortBySize, sortByTime:
-// 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 err != nil { // if the 'limit' query can't be interpreted as a number, return err
-// return
-// }
-// }
-
-// return
-// }
-
-// // ServeListing returns a formatted view of 'requestedFilepath' contents'.
-// func (b Browse) ServeListing(w http.ResponseWriter, r *http.Request, requestedFilepath http.File, bc *Config) (int, error) {
-// listing, containsIndex, err := b.loadDirectoryContents(requestedFilepath, r.URL.Path, bc)
-// if err != nil {
-// switch {
-// case os.IsPermission(err):
-// return http.StatusForbidden, err
-// case os.IsExist(err):
-// return http.StatusGone, err
-// default:
-// return http.StatusInternalServerError, err
-// }
-// }
-// if containsIndex && !b.IgnoreIndexes { // directory isn't browsable
-// return b.Next.ServeHTTP(w, r)
-// }
-// listing.Context = httpserver.Context{
-// Root: bc.Fs.Root,
-// Req: r,
-// URL: r.URL,
-// }
-// listing.User = bc.Variables
-
-// // Copy the query values into the Listing struct
-// var limit int
-// listing.Sort, listing.Order, limit, err = b.handleSortOrder(w, r, bc.PathScope)
-// if err != nil {
-// return http.StatusBadRequest, err
-// }
-
-// listing.applySort()
-
-// if limit > 0 && limit <= len(listing.Items) {
-// listing.Items = listing.Items[:limit]
-// listing.ItemsLimitedTo = limit
-// }
-
-// var buf *bytes.Buffer
-// acceptHeader := strings.ToLower(strings.Join(r.Header["Accept"], ","))
-// switch {
-// case strings.Contains(acceptHeader, "application/json"):
-// if buf, err = b.formatAsJSON(listing, bc); err != nil {
-// return http.StatusInternalServerError, err
-// }
-// w.Header().Set("Content-Type", "application/json; charset=utf-8")
-
-// default: // There's no 'application/json' in the 'Accept' header; browse normally
-// if buf, err = b.formatAsHTML(listing, bc); err != nil {
-// return http.StatusInternalServerError, err
-// }
-// w.Header().Set("Content-Type", "text/html; charset=utf-8")
-
-// }
-
-// _, _ = buf.WriteTo(w)
-
-// return http.StatusOK, nil
-// }
-
-// func (b Browse) formatAsJSON(listing *Listing, bc *Config) (*bytes.Buffer, error) {
-// marsh, err := json.Marshal(listing.Items)
-// if err != nil {
-// return nil, err
-// }
-
-// buf := new(bytes.Buffer)
-// _, err = buf.Write(marsh)
-// return buf, err
-// }
-
-// func (b Browse) formatAsHTML(listing *Listing, bc *Config) (*bytes.Buffer, error) {
-// buf := new(bytes.Buffer)
-// err := bc.Template.Execute(buf, listing)
-// return buf, err
-// }
+func (sf *StaticFiles) loadDirectoryContents(dir *os.File, urlPath string, repl caddy2.Replacer) (browseListing, error) {
+ files, err := dir.Readdir(-1)
+ if err != nil {
+ return browseListing{}, err
+ }
+
+ // determine if user can browse up another folder
+ curPathDir := path.Dir(strings.TrimSuffix(urlPath, "/"))
+ canGoUp := strings.HasPrefix(curPathDir, sf.Root)
+
+ return sf.directoryListing(files, canGoUp, urlPath, repl), nil
+}
+
+// browseApplyQueryParams applies query parameters to the listing.
+// It mutates the listing and may set cookies.
+func (sf *StaticFiles) browseApplyQueryParams(w http.ResponseWriter, r *http.Request, listing *browseListing) {
+ sortParam := r.URL.Query().Get("sort")
+ orderParam := r.URL.Query().Get("order")
+ limitParam := r.URL.Query().Get("limit")
+
+ // first figure out what to sort by
+ switch sortParam {
+ case "":
+ sortParam = sortByNameDirFirst
+ if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil {
+ sortParam = sortCookie.Value
+ }
+ case sortByName, sortByNameDirFirst, sortBySize, sortByTime:
+ http.SetCookie(w, &http.Cookie{Name: "sort", Value: sortParam, Secure: r.TLS != nil})
+ }
+
+ // then figure out the order
+ switch orderParam {
+ case "":
+ orderParam = "asc"
+ if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
+ orderParam = orderCookie.Value
+ }
+ case "asc", "desc":
+ http.SetCookie(w, &http.Cookie{Name: "order", Value: orderParam, Secure: r.TLS != nil})
+ }
+
+ // finally, apply the sorting and limiting
+ listing.applySortAndLimit(sortParam, orderParam, limitParam)
+}
+
+func (sf *StaticFiles) browseWriteJSON(listing browseListing) (*bytes.Buffer, error) {
+ buf := new(bytes.Buffer)
+ err := json.NewEncoder(buf).Encode(listing.Items)
+ return buf, err
+}
+
+func (sf *StaticFiles) browseWriteHTML(listing browseListing) (*bytes.Buffer, error) {
+ buf := new(bytes.Buffer)
+ err := sf.Browse.template.Execute(buf, listing)
+ return buf, err
+}
+
+// isSymlink return true if f is a symbolic link
+func isSymlink(f os.FileInfo) bool {
+ return f.Mode()&os.ModeSymlink != 0
+}
+
+// isSymlinkTargetDir return true if f's symbolic link target
+// is a directory. Return false if not a symbolic link.
+// TODO: Re-implement
+func isSymlinkTargetDir(f os.FileInfo, urlPath string) bool {
+ // if !isSymlink(f) {
+ // return false
+ // }
+
+ // // TODO: Ensure path is sanitized
+ // target:= path.Join(root, urlPath, f.Name()))
+ // targetInfo, err := os.Stat(target)
+ // if err != nil {
+ // return false
+ // }
+ // return targetInfo.IsDir()
+ return false
+}