| 
									
										
										
										
											2019-06-30 16:07:58 -06:00
										 |  |  | // Copyright 2015 Matthew Holt and The Caddy Authors | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  | // you may not use this file except in compliance with the License. | 
					
						
							|  |  |  | // You may obtain a copy of the License at | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | //     http://www.apache.org/licenses/LICENSE-2.0 | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  | // distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  | // See the License for the specific language governing permissions and | 
					
						
							|  |  |  | // limitations under the License. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 21:21:33 -06:00
										 |  |  | package fileserver | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2022-07-30 13:07:44 -06:00
										 |  |  | 	"io/fs" | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 	"net/url" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"path" | 
					
						
							|  |  |  | 	"sort" | 
					
						
							|  |  |  | 	"strconv" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-02 12:37:06 -06:00
										 |  |  | 	"github.com/caddyserver/caddy/v2" | 
					
						
							| 
									
										
										
										
											2021-11-22 13:59:09 -08:00
										 |  |  | 	"github.com/caddyserver/caddy/v2/modules/caddyhttp" | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 	"github.com/dustin/go-humanize" | 
					
						
							| 
									
										
										
										
											2022-07-30 13:07:44 -06:00
										 |  |  | 	"go.uber.org/zap" | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-30 13:07:44 -06:00
										 |  |  | func (fsrv *FileServer) directoryListing(entries []fs.DirEntry, canGoUp bool, root, urlPath string, repl *caddy.Replacer) browseTemplateContext { | 
					
						
							| 
									
										
										
										
											2019-05-20 21:21:33 -06:00
										 |  |  | 	filesToHide := fsrv.transformHidePaths(repl) | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-22 16:50:29 -05:00
										 |  |  | 	var dirCount, fileCount int | 
					
						
							|  |  |  | 	fileInfos := []fileInfo{} | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-30 13:07:44 -06:00
										 |  |  | 	for _, entry := range entries { | 
					
						
							|  |  |  | 		name := entry.Name() | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		if fileHidden(name, filesToHide) { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-30 13:07:44 -06:00
										 |  |  | 		info, err := entry.Info() | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			fsrv.logger.Error("could not get info about directory entry", | 
					
						
							|  |  |  | 				zap.String("name", entry.Name()), | 
					
						
							|  |  |  | 				zap.String("root", root)) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		isDir := entry.IsDir() || fsrv.isSymlinkTargetDir(info, root, urlPath) | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-16 23:40:31 +03:00
										 |  |  | 		// add the slash after the escape of path to avoid escaping the slash as well | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 		if isDir { | 
					
						
							| 
									
										
										
										
											2021-12-11 17:26:21 +03:00
										 |  |  | 			name += "/" | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 			dirCount++ | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			fileCount++ | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-30 13:07:44 -06:00
										 |  |  | 		size := info.Size() | 
					
						
							|  |  |  | 		fileIsSymlink := isSymlink(info) | 
					
						
							| 
									
										
										
										
											2021-09-18 20:51:59 +09:00
										 |  |  | 		if fileIsSymlink { | 
					
						
							| 
									
										
										
										
											2022-07-30 13:07:44 -06:00
										 |  |  | 			path := caddyhttp.SanitizedPathJoin(root, path.Join(urlPath, info.Name())) | 
					
						
							| 
									
										
										
										
											2022-09-05 19:25:34 -04:00
										 |  |  | 			fileInfo, err := fs.Stat(fsrv.fileSystem, path) | 
					
						
							| 
									
										
										
										
											2021-11-22 13:59:09 -08:00
										 |  |  | 			if err == nil { | 
					
						
							|  |  |  | 				size = fileInfo.Size() | 
					
						
							| 
									
										
										
										
											2021-09-18 20:51:59 +09:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-11-22 13:59:09 -08:00
										 |  |  | 			// An error most likely means the symlink target doesn't exist, | 
					
						
							|  |  |  | 			// which isn't entirely unusual and shouldn't fail the listing. | 
					
						
							|  |  |  | 			// In this case, just use the size of the symlink itself, which | 
					
						
							|  |  |  | 			// was already set above. | 
					
						
							| 
									
										
										
										
											2021-09-18 20:51:59 +09:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-11 17:26:21 +03:00
										 |  |  | 		u := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 		fileInfos = append(fileInfos, fileInfo{ | 
					
						
							|  |  |  | 			IsDir:     isDir, | 
					
						
							| 
									
										
										
										
											2021-09-18 20:51:59 +09:00
										 |  |  | 			IsSymlink: fileIsSymlink, | 
					
						
							|  |  |  | 			Name:      name, | 
					
						
							|  |  |  | 			Size:      size, | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 			URL:       u.String(), | 
					
						
							| 
									
										
										
										
											2022-07-30 13:07:44 -06:00
										 |  |  | 			ModTime:   info.ModTime().UTC(), | 
					
						
							|  |  |  | 			Mode:      info.Mode(), | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-11-23 11:13:09 +03:00
										 |  |  | 	name, _ := url.PathUnescape(urlPath) | 
					
						
							| 
									
										
										
										
											2021-04-30 19:17:23 -07:00
										 |  |  | 	return browseTemplateContext{ | 
					
						
							| 
									
										
										
										
											2021-11-23 11:13:09 +03:00
										 |  |  | 		Name:     path.Base(name), | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 		Path:     urlPath, | 
					
						
							|  |  |  | 		CanGoUp:  canGoUp, | 
					
						
							|  |  |  | 		Items:    fileInfos, | 
					
						
							|  |  |  | 		NumDirs:  dirCount, | 
					
						
							|  |  |  | 		NumFiles: fileCount, | 
					
						
							| 
									
										
										
										
											2021-11-22 13:59:09 -08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-30 19:17:23 -07:00
										 |  |  | // browseTemplateContext provides the template context for directory listings. | 
					
						
							|  |  |  | type browseTemplateContext struct { | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 	// The name of the directory (the last element of the path). | 
					
						
							| 
									
										
										
										
											2020-08-31 12:33:43 -06:00
										 |  |  | 	Name string `json:"name"` | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// The full path of the request. | 
					
						
							| 
									
										
										
										
											2020-08-31 12:33:43 -06:00
										 |  |  | 	Path string `json:"path"` | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Whether the parent directory is browseable. | 
					
						
							| 
									
										
										
										
											2020-08-31 12:33:43 -06:00
										 |  |  | 	CanGoUp bool `json:"can_go_up"` | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// The items (files and folders) in the path. | 
					
						
							| 
									
										
										
										
											2020-08-31 12:33:43 -06:00
										 |  |  | 	Items []fileInfo `json:"items,omitempty"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If ≠0 then Items starting from that many elements. | 
					
						
							|  |  |  | 	Offset int `json:"offset,omitempty"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If ≠0 then Items have been limited to that many elements. | 
					
						
							|  |  |  | 	Limit int `json:"limit,omitempty"` | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// The number of directories in the listing. | 
					
						
							| 
									
										
										
										
											2020-08-31 12:33:43 -06:00
										 |  |  | 	NumDirs int `json:"num_dirs"` | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// The number of files (items that aren't directories) in the listing. | 
					
						
							| 
									
										
										
										
											2020-08-31 12:33:43 -06:00
										 |  |  | 	NumFiles int `json:"num_files"` | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Sort column used | 
					
						
							| 
									
										
										
										
											2020-08-31 12:33:43 -06:00
										 |  |  | 	Sort string `json:"sort,omitempty"` | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Sorting order | 
					
						
							| 
									
										
										
										
											2020-08-31 12:33:43 -06:00
										 |  |  | 	Order string `json:"order,omitempty"` | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Breadcrumbs returns l.Path where every element maps | 
					
						
							|  |  |  | // the link to the text to display. | 
					
						
							| 
									
										
										
										
											2021-04-30 19:17:23 -07:00
										 |  |  | func (l browseTemplateContext) Breadcrumbs() []crumb { | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 	if len(l.Path) == 0 { | 
					
						
							| 
									
										
										
										
											2020-11-22 16:50:29 -05:00
										 |  |  | 		return []crumb{} | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// skip trailing slash | 
					
						
							|  |  |  | 	lpath := l.Path | 
					
						
							|  |  |  | 	if lpath[len(lpath)-1] == '/' { | 
					
						
							|  |  |  | 		lpath = lpath[:len(lpath)-1] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	parts := strings.Split(lpath, "/") | 
					
						
							| 
									
										
										
										
											2020-11-22 16:50:29 -05:00
										 |  |  | 	result := make([]crumb, len(parts)) | 
					
						
							|  |  |  | 	for i, p := range parts { | 
					
						
							|  |  |  | 		if i == 0 && p == "" { | 
					
						
							|  |  |  | 			p = "/" | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-11-23 11:13:09 +03:00
										 |  |  | 		// the directory name could include an encoded slash in its path, | 
					
						
							|  |  |  | 		// so the item name should be unescaped in the loop rather than unescaping the | 
					
						
							|  |  |  | 		// entire path outside the loop. | 
					
						
							|  |  |  | 		p, _ = url.PathUnescape(p) | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 		lnk := strings.Repeat("../", len(parts)-i-1) | 
					
						
							| 
									
										
										
										
											2020-11-22 16:50:29 -05:00
										 |  |  | 		result[i] = crumb{Link: lnk, Text: p} | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return result | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-30 19:17:23 -07:00
										 |  |  | func (l *browseTemplateContext) applySortAndLimit(sortParam, orderParam, limitParam string, offsetParam string) { | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 	l.Sort = sortParam | 
					
						
							|  |  |  | 	l.Order = orderParam | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if l.Order == "desc" { | 
					
						
							|  |  |  | 		switch l.Sort { | 
					
						
							|  |  |  | 		case sortByName: | 
					
						
							|  |  |  | 			sort.Sort(sort.Reverse(byName(*l))) | 
					
						
							|  |  |  | 		case sortByNameDirFirst: | 
					
						
							|  |  |  | 			sort.Sort(sort.Reverse(byNameDirFirst(*l))) | 
					
						
							|  |  |  | 		case sortBySize: | 
					
						
							|  |  |  | 			sort.Sort(sort.Reverse(bySize(*l))) | 
					
						
							|  |  |  | 		case sortByTime: | 
					
						
							|  |  |  | 			sort.Sort(sort.Reverse(byTime(*l))) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		switch l.Sort { | 
					
						
							|  |  |  | 		case sortByName: | 
					
						
							|  |  |  | 			sort.Sort(byName(*l)) | 
					
						
							|  |  |  | 		case sortByNameDirFirst: | 
					
						
							|  |  |  | 			sort.Sort(byNameDirFirst(*l)) | 
					
						
							|  |  |  | 		case sortBySize: | 
					
						
							|  |  |  | 			sort.Sort(bySize(*l)) | 
					
						
							|  |  |  | 		case sortByTime: | 
					
						
							|  |  |  | 			sort.Sort(byTime(*l)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-09 14:56:15 +09:00
										 |  |  | 	if offsetParam != "" { | 
					
						
							|  |  |  | 		offset, _ := strconv.Atoi(offsetParam) | 
					
						
							|  |  |  | 		if offset > 0 && offset <= len(l.Items) { | 
					
						
							|  |  |  | 			l.Items = l.Items[offset:] | 
					
						
							| 
									
										
										
										
											2020-08-31 12:33:43 -06:00
										 |  |  | 			l.Offset = offset | 
					
						
							| 
									
										
										
										
											2020-07-09 14:56:15 +09:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 	if limitParam != "" { | 
					
						
							|  |  |  | 		limit, _ := strconv.Atoi(limitParam) | 
					
						
							| 
									
										
										
										
											2020-07-09 14:56:15 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 		if limit > 0 && limit <= len(l.Items) { | 
					
						
							|  |  |  | 			l.Items = l.Items[:limit] | 
					
						
							| 
									
										
										
										
											2020-08-31 12:33:43 -06:00
										 |  |  | 			l.Limit = limit | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // crumb represents part of a breadcrumb menu, | 
					
						
							|  |  |  | // pairing a link with the text to display. | 
					
						
							|  |  |  | type crumb struct { | 
					
						
							|  |  |  | 	Link, Text string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // fileInfo contains serializable information | 
					
						
							|  |  |  | // about a file or directory. | 
					
						
							|  |  |  | type fileInfo struct { | 
					
						
							|  |  |  | 	Name      string      `json:"name"` | 
					
						
							|  |  |  | 	Size      int64       `json:"size"` | 
					
						
							|  |  |  | 	URL       string      `json:"url"` | 
					
						
							|  |  |  | 	ModTime   time.Time   `json:"mod_time"` | 
					
						
							|  |  |  | 	Mode      os.FileMode `json:"mode"` | 
					
						
							|  |  |  | 	IsDir     bool        `json:"is_dir"` | 
					
						
							|  |  |  | 	IsSymlink bool        `json:"is_symlink"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // HumanSize returns the size of the file as a | 
					
						
							|  |  |  | // human-readable string in IEC format (i.e. | 
					
						
							|  |  |  | // power of 2 or base 1024). | 
					
						
							|  |  |  | func (fi fileInfo) HumanSize() string { | 
					
						
							|  |  |  | 	return humanize.IBytes(uint64(fi.Size)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // HumanModTime returns the modified time of the file | 
					
						
							|  |  |  | // as a human-readable string given by format. | 
					
						
							|  |  |  | func (fi fileInfo) HumanModTime(format string) string { | 
					
						
							|  |  |  | 	return fi.ModTime.Format(format) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-30 19:17:23 -07:00
										 |  |  | type ( | 
					
						
							|  |  |  | 	byName         browseTemplateContext | 
					
						
							|  |  |  | 	byNameDirFirst browseTemplateContext | 
					
						
							|  |  |  | 	bySize         browseTemplateContext | 
					
						
							|  |  |  | 	byTime         browseTemplateContext | 
					
						
							|  |  |  | ) | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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] } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (l byName) Less(i, j int) bool { | 
					
						
							|  |  |  | 	return strings.ToLower(l.Items[i].Name) < strings.ToLower(l.Items[j].Name) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (l byNameDirFirst) Len() int      { return len(l.Items) } | 
					
						
							|  |  |  | func (l byNameDirFirst) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (l byNameDirFirst) Less(i, j int) bool { | 
					
						
							|  |  |  | 	// sort by name if both are dir or file | 
					
						
							|  |  |  | 	if l.Items[i].IsDir == l.Items[j].IsDir { | 
					
						
							|  |  |  | 		return strings.ToLower(l.Items[i].Name) < strings.ToLower(l.Items[j].Name) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// sort dir ahead of file | 
					
						
							|  |  |  | 	return l.Items[i].IsDir | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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 { | 
					
						
							|  |  |  | 	const directoryOffset = -1 << 31 // = -math.MinInt32 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	iSize, jSize := l.Items[i].Size, l.Items[j].Size | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// directory sizes depend on the file system; to | 
					
						
							|  |  |  | 	// provide a consistent experience, put them up front | 
					
						
							|  |  |  | 	// and sort them by name | 
					
						
							|  |  |  | 	if l.Items[i].IsDir { | 
					
						
							|  |  |  | 		iSize = directoryOffset | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if l.Items[j].IsDir { | 
					
						
							|  |  |  | 		jSize = directoryOffset | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if l.Items[i].IsDir && l.Items[j].IsDir { | 
					
						
							|  |  |  | 		return strings.ToLower(l.Items[i].Name) < strings.ToLower(l.Items[j].Name) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return iSize < jSize | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	sortByName         = "name" | 
					
						
							| 
									
										
										
										
											2021-07-06 23:54:54 -07:00
										 |  |  | 	sortByNameDirFirst = "namedirfirst" | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 	sortBySize         = "size" | 
					
						
							|  |  |  | 	sortByTime         = "time" | 
					
						
							|  |  |  | ) |