mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-04 03:27:23 -05:00 
			
		
		
		
	
						commit
						47096e112a
					
				@ -202,9 +202,33 @@ th {
 | 
				
			|||||||
		<main>
 | 
							<main>
 | 
				
			||||||
			<table>
 | 
								<table>
 | 
				
			||||||
				<tr>
 | 
									<tr>
 | 
				
			||||||
					<th>Name</th>
 | 
										<th>
 | 
				
			||||||
					<th>Size</th>
 | 
											{{if and (eq .Sort "name") (ne .Order "desc")}}
 | 
				
			||||||
					<th class="hideable">Modified</th>
 | 
											<a href="?sort=name&order=desc">Name↓</a>
 | 
				
			||||||
 | 
											{{else if and (eq .Sort "name") (ne .Order "asc")}}
 | 
				
			||||||
 | 
											<a href="?sort=name&order=asc">Name↑</a>
 | 
				
			||||||
 | 
											{{else}}
 | 
				
			||||||
 | 
											<a href="?sort=name&order=asc">Name</a>
 | 
				
			||||||
 | 
											{{end}}
 | 
				
			||||||
 | 
										</th>
 | 
				
			||||||
 | 
										<th>
 | 
				
			||||||
 | 
											{{if and (eq .Sort "size") (ne .Order "desc")}}
 | 
				
			||||||
 | 
											<a href="?sort=size&order=desc">Size↓</a>
 | 
				
			||||||
 | 
											{{else if and (eq .Sort "size") (ne .Order "asc")}}
 | 
				
			||||||
 | 
											<a href="?sort=size&order=asc">Size↑</a>
 | 
				
			||||||
 | 
											{{else}}
 | 
				
			||||||
 | 
											<a href="?sort=size&order=asc">Size</a>
 | 
				
			||||||
 | 
											{{end}}
 | 
				
			||||||
 | 
										</th>
 | 
				
			||||||
 | 
										<th class="hideable">
 | 
				
			||||||
 | 
											{{if and (eq .Sort "time") (ne .Order "desc")}}
 | 
				
			||||||
 | 
											<a href="?sort=time&order=desc">Modified↓</a>
 | 
				
			||||||
 | 
											{{else if and (eq .Sort "time") (ne .Order "asc")}}
 | 
				
			||||||
 | 
											<a href="?sort=time&order=asc">Modified↑</a>
 | 
				
			||||||
 | 
											{{else}}
 | 
				
			||||||
 | 
											<a href="?sort=time&order=asc">Modified</a>
 | 
				
			||||||
 | 
											{{end}}
 | 
				
			||||||
 | 
										</th>
 | 
				
			||||||
				</tr>
 | 
									</tr>
 | 
				
			||||||
				{{range .Items}}
 | 
									{{range .Items}}
 | 
				
			||||||
				<tr>
 | 
									<tr>
 | 
				
			||||||
 | 
				
			|||||||
@ -4,11 +4,13 @@ package browse
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
	"html/template"
 | 
						"html/template"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
 | 
						"sort"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -43,6 +45,12 @@ type Listing struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// The items (files and folders) in the path
 | 
						// The items (files and folders) in the path
 | 
				
			||||||
	Items []FileInfo
 | 
						Items []FileInfo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Which sorting order is used
 | 
				
			||||||
 | 
						Sort string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// And which order
 | 
				
			||||||
 | 
						Order string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// FileInfo is the info about a particular file or directory
 | 
					// FileInfo is the info about a particular file or directory
 | 
				
			||||||
@ -55,6 +63,61 @@ type FileInfo struct {
 | 
				
			|||||||
	Mode    os.FileMode
 | 
						Mode    os.FileMode
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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 {
 | 
				
			||||||
 | 
						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] }
 | 
				
			||||||
 | 
					func (l bySize) Less(i, j int) bool { return l.Items[i].Size < l.Items[j].Size }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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.Unix() < l.Items[j].ModTime.Unix() }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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:
 | 
				
			||||||
 | 
								// If not one of the above, do nothing
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// HumanSize returns the size of the file as a human-readable string.
 | 
					// HumanSize returns the size of the file as a human-readable string.
 | 
				
			||||||
func (fi FileInfo) HumanSize() string {
 | 
					func (fi FileInfo) HumanSize() string {
 | 
				
			||||||
	return humanize.Bytes(uint64(fi.Size))
 | 
						return humanize.Bytes(uint64(fi.Size))
 | 
				
			||||||
@ -72,6 +135,42 @@ var IndexPages = []string{
 | 
				
			|||||||
	"default.htm",
 | 
						"default.htm",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func directoryListing(files []os.FileInfo, urlPath string, canGoUp bool) (Listing, error) {
 | 
				
			||||||
 | 
						var fileinfos []FileInfo
 | 
				
			||||||
 | 
						for _, f := range files {
 | 
				
			||||||
 | 
							name := f.Name()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Directory is not browsable if it contains index file
 | 
				
			||||||
 | 
							for _, indexName := range IndexPages {
 | 
				
			||||||
 | 
								if name == indexName {
 | 
				
			||||||
 | 
									return Listing{}, errors.New("Directory contains index file, not browsable!")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if f.IsDir() {
 | 
				
			||||||
 | 
								name += "/"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							url := url.URL{Path: name}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fileinfos = append(fileinfos, FileInfo{
 | 
				
			||||||
 | 
								IsDir:   f.IsDir(),
 | 
				
			||||||
 | 
								Name:    f.Name(),
 | 
				
			||||||
 | 
								Size:    f.Size(),
 | 
				
			||||||
 | 
								URL:     url.String(),
 | 
				
			||||||
 | 
								ModTime: f.ModTime(),
 | 
				
			||||||
 | 
								Mode:    f.Mode(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return Listing{
 | 
				
			||||||
 | 
							Name:    path.Base(urlPath),
 | 
				
			||||||
 | 
							Path:    urlPath,
 | 
				
			||||||
 | 
							CanGoUp: canGoUp,
 | 
				
			||||||
 | 
							Items:   fileinfos,
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ServeHTTP implements the middleware.Handler interface.
 | 
					// ServeHTTP implements the middleware.Handler interface.
 | 
				
			||||||
func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
 | 
					func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
 | 
				
			||||||
	filename := b.Root + r.URL.Path
 | 
						filename := b.Root + r.URL.Path
 | 
				
			||||||
@ -113,42 +212,6 @@ func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
 | 
				
			|||||||
			return http.StatusForbidden, err
 | 
								return http.StatusForbidden, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Assemble listing of directory contents
 | 
					 | 
				
			||||||
		var fileinfos []FileInfo
 | 
					 | 
				
			||||||
		var abort bool // we bail early if we find an index file
 | 
					 | 
				
			||||||
		for _, f := range files {
 | 
					 | 
				
			||||||
			name := f.Name()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Directory is not browseable if it contains index file
 | 
					 | 
				
			||||||
			for _, indexName := range IndexPages {
 | 
					 | 
				
			||||||
				if name == indexName {
 | 
					 | 
				
			||||||
					abort = true
 | 
					 | 
				
			||||||
					break
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if abort {
 | 
					 | 
				
			||||||
				break
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if f.IsDir() {
 | 
					 | 
				
			||||||
				name += "/"
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			url := url.URL{Path: name}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			fileinfos = append(fileinfos, FileInfo{
 | 
					 | 
				
			||||||
				IsDir:   f.IsDir(),
 | 
					 | 
				
			||||||
				Name:    f.Name(),
 | 
					 | 
				
			||||||
				Size:    f.Size(),
 | 
					 | 
				
			||||||
				URL:     url.String(),
 | 
					 | 
				
			||||||
				ModTime: f.ModTime(),
 | 
					 | 
				
			||||||
				Mode:    f.Mode(),
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if abort {
 | 
					 | 
				
			||||||
			// this dir has an index file, so not browsable
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Determine if user can browse up another folder
 | 
							// Determine if user can browse up another folder
 | 
				
			||||||
		var canGoUp bool
 | 
							var canGoUp bool
 | 
				
			||||||
		curPath := strings.TrimSuffix(r.URL.Path, "/")
 | 
							curPath := strings.TrimSuffix(r.URL.Path, "/")
 | 
				
			||||||
@ -158,14 +221,24 @@ func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
 | 
				
			|||||||
				break
 | 
									break
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							// Assemble listing of directory contents
 | 
				
			||||||
		listing := Listing{
 | 
							listing, err := directoryListing(files, r.URL.Path, canGoUp)
 | 
				
			||||||
			Name:    path.Base(r.URL.Path),
 | 
							if err != nil { // directory isn't browsable
 | 
				
			||||||
			Path:    r.URL.Path,
 | 
								continue
 | 
				
			||||||
			CanGoUp: canGoUp,
 | 
					 | 
				
			||||||
			Items:   fileinfos,
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get the query vales and store them in the Listing struct
 | 
				
			||||||
 | 
							listing.Sort, listing.Order = r.URL.Query().Get("sort"), r.URL.Query().Get("order")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If the query 'sort' is empty, default to "name" and "asc"
 | 
				
			||||||
 | 
							if listing.Sort == "" {
 | 
				
			||||||
 | 
								listing.Sort = "name"
 | 
				
			||||||
 | 
								listing.Order = "asc"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Apply the sorting
 | 
				
			||||||
 | 
							listing.applySort()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var buf bytes.Buffer
 | 
							var buf bytes.Buffer
 | 
				
			||||||
		err = bc.Template.Execute(&buf, listing)
 | 
							err = bc.Template.Execute(&buf, listing)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										96
									
								
								middleware/browse/browse_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								middleware/browse/browse_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,96 @@
 | 
				
			|||||||
 | 
					package browse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"sort"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// "sort" package has "IsSorted" function, but no "IsReversed";
 | 
				
			||||||
 | 
					func isReversed(data sort.Interface) bool {
 | 
				
			||||||
 | 
						n := data.Len()
 | 
				
			||||||
 | 
						for i := n - 1; i > 0; i-- {
 | 
				
			||||||
 | 
							if !data.Less(i, i-1) {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestSort(t *testing.T) {
 | 
				
			||||||
 | 
						// making up []fileInfo with bogus values;
 | 
				
			||||||
 | 
						// to be used to make up our "listing"
 | 
				
			||||||
 | 
						fileInfos := []FileInfo{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Name:    "fizz",
 | 
				
			||||||
 | 
								Size:    4,
 | 
				
			||||||
 | 
								ModTime: time.Now().AddDate(-1, 1, 0),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Name:    "buzz",
 | 
				
			||||||
 | 
								Size:    2,
 | 
				
			||||||
 | 
								ModTime: time.Now().AddDate(0, -3, 3),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Name:    "bazz",
 | 
				
			||||||
 | 
								Size:    1,
 | 
				
			||||||
 | 
								ModTime: time.Now().AddDate(0, -2, -23),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Name:    "jazz",
 | 
				
			||||||
 | 
								Size:    3,
 | 
				
			||||||
 | 
								ModTime: time.Now(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						listing := Listing{
 | 
				
			||||||
 | 
							Name:    "foobar",
 | 
				
			||||||
 | 
							Path:    "/fizz/buzz",
 | 
				
			||||||
 | 
							CanGoUp: false,
 | 
				
			||||||
 | 
							Items:   fileInfos,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// sort by name
 | 
				
			||||||
 | 
						listing.Sort = "name"
 | 
				
			||||||
 | 
						listing.applySort()
 | 
				
			||||||
 | 
						if !sort.IsSorted(byName(listing)) {
 | 
				
			||||||
 | 
							t.Errorf("The listing isn't name sorted: %v", listing.Items)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// sort by size
 | 
				
			||||||
 | 
						listing.Sort = "size"
 | 
				
			||||||
 | 
						listing.applySort()
 | 
				
			||||||
 | 
						if !sort.IsSorted(bySize(listing)) {
 | 
				
			||||||
 | 
							t.Errorf("The listing isn't size sorted: %v", listing.Items)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// sort by Time
 | 
				
			||||||
 | 
						listing.Sort = "time"
 | 
				
			||||||
 | 
						listing.applySort()
 | 
				
			||||||
 | 
						if !sort.IsSorted(byTime(listing)) {
 | 
				
			||||||
 | 
							t.Errorf("The listing isn't time sorted: %v", listing.Items)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// reverse by name
 | 
				
			||||||
 | 
						listing.Sort = "name"
 | 
				
			||||||
 | 
						listing.Order = "desc"
 | 
				
			||||||
 | 
						listing.applySort()
 | 
				
			||||||
 | 
						if !isReversed(byName(listing)) {
 | 
				
			||||||
 | 
							t.Errorf("The listing isn't reversed by name: %v", listing.Items)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// reverse by size
 | 
				
			||||||
 | 
						listing.Sort = "size"
 | 
				
			||||||
 | 
						listing.Order = "desc"
 | 
				
			||||||
 | 
						listing.applySort()
 | 
				
			||||||
 | 
						if !isReversed(bySize(listing)) {
 | 
				
			||||||
 | 
							t.Errorf("The listing isn't reversed by size: %v", listing.Items)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// reverse by time
 | 
				
			||||||
 | 
						listing.Sort = "time"
 | 
				
			||||||
 | 
						listing.Order = "desc"
 | 
				
			||||||
 | 
						listing.applySort()
 | 
				
			||||||
 | 
						if !isReversed(byTime(listing)) {
 | 
				
			||||||
 | 
							t.Errorf("The listing isn't reversed by time: %v", listing.Items)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user