| 
									
										
										
										
											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-03-31 20:41:29 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2019-08-08 07:59:02 +02:00
										 |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 	"html/template" | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 	weakrand "math/rand" | 
					
						
							| 
									
										
										
										
											2019-06-21 14:36:26 -06:00
										 |  |  | 	"mime" | 
					
						
							| 
									
										
										
										
											2019-03-31 20:41:29 -06:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 	"os" | 
					
						
							|  |  |  | 	"path" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							|  |  |  | 	"strconv" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2019-08-08 07:59:02 +02:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2019-03-31 20:41:29 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-02 12:37:06 -06:00
										 |  |  | 	"github.com/caddyserver/caddy/v2" | 
					
						
							|  |  |  | 	"github.com/caddyserver/caddy/v2/modules/caddyhttp" | 
					
						
							| 
									
										
										
										
											2019-03-31 20:41:29 -06:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func init() { | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 	weakrand.Seed(time.Now().UnixNano()) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-21 10:46:35 -06:00
										 |  |  | 	caddy.RegisterModule(FileServer{}) | 
					
						
							| 
									
										
										
										
											2019-03-31 20:41:29 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 21:21:33 -06:00
										 |  |  | // FileServer implements a static file server responder for Caddy. | 
					
						
							|  |  |  | type FileServer struct { | 
					
						
							| 
									
										
										
										
											2019-07-11 17:02:57 -06:00
										 |  |  | 	Root       string   `json:"root,omitempty"` // default is current directory | 
					
						
							|  |  |  | 	Hide       []string `json:"hide,omitempty"` | 
					
						
							|  |  |  | 	IndexNames []string `json:"index_names,omitempty"` | 
					
						
							|  |  |  | 	Browse     *Browse  `json:"browse,omitempty"` | 
					
						
							| 
									
										
										
										
											2019-03-31 20:41:29 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-21 10:46:35 -06:00
										 |  |  | // CaddyModule returns the Caddy module information. | 
					
						
							|  |  |  | func (FileServer) CaddyModule() caddy.ModuleInfo { | 
					
						
							|  |  |  | 	return caddy.ModuleInfo{ | 
					
						
							|  |  |  | 		Name: "http.handlers.file_server", | 
					
						
							|  |  |  | 		New:  func() caddy.Module { return new(FileServer) }, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | // Provision sets up the static files responder. | 
					
						
							| 
									
										
										
										
											2019-06-14 11:58:28 -06:00
										 |  |  | func (fsrv *FileServer) Provision(ctx caddy.Context) error { | 
					
						
							| 
									
										
										
										
											2019-09-06 12:36:45 -06:00
										 |  |  | 	if fsrv.Root == "" { | 
					
						
							|  |  |  | 		fsrv.Root = "{http.vars.root}" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 21:21:33 -06:00
										 |  |  | 	if fsrv.IndexNames == nil { | 
					
						
							|  |  |  | 		fsrv.IndexNames = defaultIndexNames | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 21:21:33 -06:00
										 |  |  | 	if fsrv.Browse != nil { | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 		var tpl *template.Template | 
					
						
							|  |  |  | 		var err error | 
					
						
							| 
									
										
										
										
											2019-05-20 21:21:33 -06:00
										 |  |  | 		if fsrv.Browse.TemplateFile != "" { | 
					
						
							|  |  |  | 			tpl, err = template.ParseFiles(fsrv.Browse.TemplateFile) | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return fmt.Errorf("parsing browse template file: %v", err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			tpl, err = template.New("default_listing").Parse(defaultBrowseTemplate) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return fmt.Errorf("parsing default browse template: %v", err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-05-20 21:21:33 -06:00
										 |  |  | 		fsrv.Browse.template = tpl | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-31 20:41:29 -06:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-09 12:58:39 -06:00
										 |  |  | func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ caddyhttp.Handler) error { | 
					
						
							| 
									
										
										
										
											2019-06-14 11:58:28 -06:00
										 |  |  | 	repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 21:21:33 -06:00
										 |  |  | 	filesToHide := fsrv.transformHidePaths(repl) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-09 12:05:47 -06:00
										 |  |  | 	root := repl.ReplaceAll(fsrv.Root, ".") | 
					
						
							| 
									
										
										
										
											2019-07-11 17:02:57 -06:00
										 |  |  | 	suffix := repl.ReplaceAll(r.URL.Path, "") | 
					
						
							|  |  |  | 	filename := sanitizedPathJoin(root, suffix) | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// get information about the file | 
					
						
							|  |  |  | 	info, err := os.Stat(filename) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-05-20 17:15:38 -06:00
										 |  |  | 		err = mapDirOpenError(err, filename) | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 		if os.IsNotExist(err) { | 
					
						
							|  |  |  | 			return caddyhttp.Error(http.StatusNotFound, err) | 
					
						
							|  |  |  | 		} else if os.IsPermission(err) { | 
					
						
							|  |  |  | 			return caddyhttp.Error(http.StatusForbidden, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// TODO: treat this as resource exhaustion like with os.Open? Or unnecessary here? | 
					
						
							|  |  |  | 		return caddyhttp.Error(http.StatusInternalServerError, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// if the request mapped to a directory, see if | 
					
						
							|  |  |  | 	// there is an index file we can serve | 
					
						
							| 
									
										
										
										
											2019-05-20 21:21:33 -06:00
										 |  |  | 	if info.IsDir() && len(fsrv.IndexNames) > 0 { | 
					
						
							|  |  |  | 		for _, indexPage := range fsrv.IndexNames { | 
					
						
							| 
									
										
										
										
											2019-05-20 17:15:38 -06:00
										 |  |  | 			indexPath := sanitizedPathJoin(filename, indexPage) | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 			if fileHidden(indexPath, filesToHide) { | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 				// pretend this file doesn't exist | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			indexInfo, err := os.Stat(indexPath) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// we found an index file that might work, | 
					
						
							| 
									
										
										
										
											2019-07-11 17:02:57 -06:00
										 |  |  | 			// so rewrite the request path | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 			r.URL.Path = path.Join(r.URL.Path, indexPage) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			info = indexInfo | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 			filename = indexPath | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// if still referencing a directory, delegate | 
					
						
							|  |  |  | 	// to browse or return an error | 
					
						
							|  |  |  | 	if info.IsDir() { | 
					
						
							| 
									
										
										
										
											2019-05-20 21:21:33 -06:00
										 |  |  | 		if fsrv.Browse != nil && !fileHidden(filename, filesToHide) { | 
					
						
							|  |  |  | 			return fsrv.serveBrowse(filename, w, r) | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		return caddyhttp.Error(http.StatusNotFound, nil) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 17:15:38 -06:00
										 |  |  | 	// TODO: content negotiation (brotli sidecar files, etc...) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 21:21:33 -06:00
										 |  |  | 	// one last check to ensure the file isn't hidden (we might | 
					
						
							|  |  |  | 	// have changed the filename from when we last checked) | 
					
						
							|  |  |  | 	if fileHidden(filename, filesToHide) { | 
					
						
							|  |  |  | 		return caddyhttp.Error(http.StatusNotFound, nil) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 	// open the file | 
					
						
							| 
									
										
										
										
											2019-05-20 21:21:33 -06:00
										 |  |  | 	file, err := fsrv.openFile(filename, w) | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 		return err | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	defer file.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-27 13:09:10 -06:00
										 |  |  | 	// set the ETag - note that a conditional If-None-Match request is handled | 
					
						
							|  |  |  | 	// by http.ServeContent below, which checks against this ETag value | 
					
						
							|  |  |  | 	w.Header().Set("ETag", calculateEtag(info)) | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-07 19:59:17 -06:00
										 |  |  | 	if w.Header().Get("Content-Type") == "" { | 
					
						
							| 
									
										
										
										
											2019-06-21 14:36:26 -06:00
										 |  |  | 		mtyp := mime.TypeByExtension(filepath.Ext(filename)) | 
					
						
							|  |  |  | 		if mtyp == "" { | 
					
						
							|  |  |  | 			// do not allow Go to sniff the content-type; see | 
					
						
							|  |  |  | 			// https://www.youtube.com/watch?v=8t8JYpt0egE | 
					
						
							|  |  |  | 			// TODO: Consider writing a default mime type of application/octet-stream - this is secure but violates spec | 
					
						
							|  |  |  | 			w.Header()["Content-Type"] = nil | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			w.Header().Set("Content-Type", mtyp) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-06-07 19:59:17 -06:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-05-20 21:21:33 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 	// let the standard library do what it does best; note, however, | 
					
						
							|  |  |  | 	// that errors generated by ServeContent are written immediately | 
					
						
							| 
									
										
										
										
											2019-06-21 14:36:26 -06:00
										 |  |  | 	// to the response, so we cannot handle them (but errors there | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 	// are rare) | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 	http.ServeContent(w, r, info.Name(), info.ModTime(), file) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | // openFile opens the file at the given filename. If there was an error, | 
					
						
							|  |  |  | // the response is configured to inform the client how to best handle it | 
					
						
							|  |  |  | // and a well-described handler error is returned (do not wrap the | 
					
						
							|  |  |  | // returned error value). | 
					
						
							| 
									
										
										
										
											2019-05-20 21:21:33 -06:00
										 |  |  | func (fsrv *FileServer) openFile(filename string, w http.ResponseWriter) (*os.File, error) { | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 	file, err := os.Open(filename) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-05-20 17:15:38 -06:00
										 |  |  | 		err = mapDirOpenError(err, filename) | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | 		if os.IsNotExist(err) { | 
					
						
							|  |  |  | 			return nil, caddyhttp.Error(http.StatusNotFound, err) | 
					
						
							|  |  |  | 		} else if os.IsPermission(err) { | 
					
						
							|  |  |  | 			return nil, caddyhttp.Error(http.StatusForbidden, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// maybe the server is under load and ran out of file descriptors? | 
					
						
							|  |  |  | 		// have client wait arbitrary seconds to help prevent a stampede | 
					
						
							|  |  |  | 		backoff := weakrand.Intn(maxBackoff-minBackoff) + minBackoff | 
					
						
							|  |  |  | 		w.Header().Set("Retry-After", strconv.Itoa(backoff)) | 
					
						
							|  |  |  | 		return nil, caddyhttp.Error(http.StatusServiceUnavailable, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return file, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 17:15:38 -06:00
										 |  |  | // mapDirOpenError maps the provided non-nil error from opening name | 
					
						
							|  |  |  | // to a possibly better non-nil error. In particular, it turns OS-specific errors | 
					
						
							|  |  |  | // about opening files in non-directories into os.ErrNotExist. See golang/go#18984. | 
					
						
							|  |  |  | // Adapted from the Go standard library; originally written by Nathaniel Caza. | 
					
						
							|  |  |  | // https://go-review.googlesource.com/c/go/+/36635/ | 
					
						
							|  |  |  | // https://go-review.googlesource.com/c/go/+/36804/ | 
					
						
							|  |  |  | func mapDirOpenError(originalErr error, name string) error { | 
					
						
							|  |  |  | 	if os.IsNotExist(originalErr) || os.IsPermission(originalErr) { | 
					
						
							|  |  |  | 		return originalErr | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	parts := strings.Split(name, string(filepath.Separator)) | 
					
						
							|  |  |  | 	for i := range parts { | 
					
						
							|  |  |  | 		if parts[i] == "" { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		fi, err := os.Stat(strings.Join(parts[:i+1], string(filepath.Separator))) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return originalErr | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if !fi.IsDir() { | 
					
						
							|  |  |  | 			return os.ErrNotExist | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return originalErr | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | // transformHidePaths performs replacements for all the elements of | 
					
						
							| 
									
										
										
										
											2019-05-20 21:21:33 -06:00
										 |  |  | // fsrv.Hide and returns a new list of the transformed values. | 
					
						
							| 
									
										
										
										
											2019-06-14 11:58:28 -06:00
										 |  |  | func (fsrv *FileServer) transformHidePaths(repl caddy.Replacer) []string { | 
					
						
							| 
									
										
										
										
											2019-05-20 21:21:33 -06:00
										 |  |  | 	hide := make([]string, len(fsrv.Hide)) | 
					
						
							|  |  |  | 	for i := range fsrv.Hide { | 
					
						
							|  |  |  | 		hide[i] = repl.ReplaceAll(fsrv.Hide[i], "") | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return hide | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 17:15:38 -06:00
										 |  |  | // sanitizedPathJoin performs filepath.Join(root, reqPath) that | 
					
						
							|  |  |  | // is safe against directory traversal attacks. It uses logic | 
					
						
							|  |  |  | // similar to that in the Go standard library, specifically | 
					
						
							| 
									
										
										
										
											2019-05-20 21:21:33 -06:00
										 |  |  | // in the implementation of http.Dir. The root is assumed to | 
					
						
							|  |  |  | // be a trusted path, but reqPath is not. | 
					
						
							| 
									
										
										
										
											2019-05-20 17:15:38 -06:00
										 |  |  | func sanitizedPathJoin(root, reqPath string) string { | 
					
						
							|  |  |  | 	// TODO: Caddy 1 uses this: | 
					
						
							|  |  |  | 	// prevent absolute path access on Windows, e.g. http://localhost:5000/C:\Windows\notepad.exe | 
					
						
							|  |  |  | 	// if runtime.GOOS == "windows" && len(reqPath) > 0 && filepath.IsAbs(reqPath[1:]) { | 
					
						
							|  |  |  | 	// TODO. | 
					
						
							|  |  |  | 	// } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// TODO: whereas std lib's http.Dir.Open() uses this: | 
					
						
							|  |  |  | 	// if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) { | 
					
						
							|  |  |  | 	// 	return nil, errors.New("http: invalid character in file path") | 
					
						
							|  |  |  | 	// } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// TODO: see https://play.golang.org/p/oh77BiVQFti for another thing to consider | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if root == "" { | 
					
						
							|  |  |  | 		root = "." | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return filepath.Join(root, filepath.FromSlash(path.Clean("/"+reqPath))) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 15:46:34 -06:00
										 |  |  | // fileHidden returns true if filename is hidden | 
					
						
							|  |  |  | // according to the hide list. | 
					
						
							|  |  |  | func fileHidden(filename string, hide []string) bool { | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 	nameOnly := filepath.Base(filename) | 
					
						
							|  |  |  | 	sep := string(filepath.Separator) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, h := range hide { | 
					
						
							|  |  |  | 		// assuming h is a glob/shell-like pattern, | 
					
						
							|  |  |  | 		// use it to compare the whole file path; | 
					
						
							|  |  |  | 		// but if there is no separator in h, then | 
					
						
							|  |  |  | 		// just compare against the file's name | 
					
						
							|  |  |  | 		compare := filename | 
					
						
							|  |  |  | 		if !strings.Contains(h, sep) { | 
					
						
							|  |  |  | 			compare = nameOnly | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		hidden, err := filepath.Match(h, compare) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			// malformed pattern; fallback by checking prefix | 
					
						
							|  |  |  | 			if strings.HasPrefix(filename, h) { | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if hidden { | 
					
						
							|  |  |  | 			// file name or path matches hide pattern | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-27 13:09:10 -06:00
										 |  |  | // calculateEtag produces a strong etag by default, although, for | 
					
						
							|  |  |  | // efficiency reasons, it does not actually consume the contents | 
					
						
							|  |  |  | // of the file to make a hash of all the bytes. ¯\_(ツ)_/¯ | 
					
						
							|  |  |  | // Prefix the etag with "W/" to convert it into a weak etag. | 
					
						
							|  |  |  | // See: https://tools.ietf.org/html/rfc7232#section-2.3 | 
					
						
							|  |  |  | func calculateEtag(d os.FileInfo) string { | 
					
						
							|  |  |  | 	t := strconv.FormatInt(d.ModTime().Unix(), 36) | 
					
						
							|  |  |  | 	s := strconv.FormatInt(d.Size(), 36) | 
					
						
							|  |  |  | 	return `"` + t + s + `"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-09 12:05:47 -06:00
										 |  |  | var defaultIndexNames = []string{"index.html", "index.txt"} | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-08 07:59:02 +02:00
										 |  |  | var bufPool = sync.Pool{ | 
					
						
							|  |  |  | 	New: func() interface{} { | 
					
						
							|  |  |  | 		return new(bytes.Buffer) | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-20 10:59:20 -06:00
										 |  |  | const minBackoff, maxBackoff = 2, 5 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-07 14:12:22 -06:00
										 |  |  | // Interface guards | 
					
						
							|  |  |  | var ( | 
					
						
							| 
									
										
										
										
											2019-07-09 12:58:39 -06:00
										 |  |  | 	_ caddy.Provisioner           = (*FileServer)(nil) | 
					
						
							|  |  |  | 	_ caddyhttp.MiddlewareHandler = (*FileServer)(nil) | 
					
						
							| 
									
										
										
										
											2019-07-07 14:12:22 -06:00
										 |  |  | ) |