mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-31 02:27:19 -04:00 
			
		
		
		
	
						commit
						47096e112a
					
				| @ -202,9 +202,33 @@ th { | ||||
| 		<main> | ||||
| 			<table> | ||||
| 				<tr> | ||||
| 					<th>Name</th> | ||||
| 					<th>Size</th> | ||||
| 					<th class="hideable">Modified</th> | ||||
| 					<th> | ||||
| 						{{if and (eq .Sort "name") (ne .Order "desc")}} | ||||
| 						<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> | ||||
| 				{{range .Items}} | ||||
| 				<tr> | ||||
|  | ||||
| @ -4,11 +4,13 @@ package browse | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"html/template" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| @ -43,6 +45,12 @@ type Listing struct { | ||||
| 
 | ||||
| 	// The items (files and folders) in the path | ||||
| 	Items []FileInfo | ||||
| 
 | ||||
| 	// Which sorting order is used | ||||
| 	Sort string | ||||
| 
 | ||||
| 	// And which order | ||||
| 	Order string | ||||
| } | ||||
| 
 | ||||
| // FileInfo is the info about a particular file or directory | ||||
| @ -55,6 +63,61 @@ type FileInfo struct { | ||||
| 	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. | ||||
| func (fi FileInfo) HumanSize() string { | ||||
| 	return humanize.Bytes(uint64(fi.Size)) | ||||
| @ -72,6 +135,42 @@ var IndexPages = []string{ | ||||
| 	"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. | ||||
| func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { | ||||
| 	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 | ||||
| 		} | ||||
| 
 | ||||
| 		// 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 | ||||
| 		var canGoUp bool | ||||
| 		curPath := strings.TrimSuffix(r.URL.Path, "/") | ||||
| @ -158,14 +221,24 @@ func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		listing := Listing{ | ||||
| 			Name:    path.Base(r.URL.Path), | ||||
| 			Path:    r.URL.Path, | ||||
| 			CanGoUp: canGoUp, | ||||
| 			Items:   fileinfos, | ||||
| 		// Assemble listing of directory contents | ||||
| 		listing, err := directoryListing(files, r.URL.Path, canGoUp) | ||||
| 		if err != nil { // directory isn't browsable | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// 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 | ||||
| 		err = bc.Template.Execute(&buf, listing) | ||||
| 		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