// Rewrite rewrites the internal location of the current request. func (r *ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re Result) { rPath := req.URL.Path replacer := newReplacer(req) // validate base if !middleware.Path(rPath).Matches(r.Base) { return } // validate extensions if !r.matchExt(rPath) { return } // validate regexp if present if r.Regexp != nil { // include trailing slash in regexp if present start := len(r.Base) if strings.HasSuffix(r.Base, "/") { start-- } matches := r.FindStringSubmatch(rPath[start:]) switch len(matches) { case 0: // no match return default: // set regexp match variables {1}, {2} ... // url escaped values of ? and #. q, f := url.QueryEscape("?"), url.QueryEscape("#") for i := 1; i < len(matches); i++ { // Special case of unescaped # and ? by stdlib regexp. // Reverse the unescape. if strings.ContainsAny(matches[i], "?#") { matches[i] = strings.NewReplacer("?", q, "#", f).Replace(matches[i]) } replacer.Set(fmt.Sprint(i), matches[i]) } } } // validate rewrite conditions for _, i := range r.Ifs { if !i.True(req) { return } } // if status is present, stop rewrite and return it. if r.Status != 0 { return RewriteStatus } // attempt rewrite return To(fs, req, r.To, replacer) }
func (l Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, rule := range l.Rules { if middleware.Path(r.URL.Path).Matches(rule.PathScope) { // Record the response responseRecorder := middleware.NewResponseRecorder(w) // Attach the Replacer we'll use so that other middlewares can // set their own placeholders if they want to. rep := middleware.NewReplacer(r, responseRecorder, CommonLogEmptyValue) responseRecorder.Replacer = rep // Bon voyage, request! status, err := l.Next.ServeHTTP(responseRecorder, r) if status >= 400 { // There was an error up the chain, but no response has been written yet. // The error must be handled here so the log entry will record the response size. if l.ErrorFunc != nil { l.ErrorFunc(responseRecorder, r, status) } else { // Default failover error handler responseRecorder.WriteHeader(status) fmt.Fprintf(responseRecorder, "%d %s", status, http.StatusText(status)) } status = 0 } // Write log entry rule.Log.Println(rep.Replace(rule.Format)) return status, err } } return l.Next.ServeHTTP(w, r) }
// ServeHTTP handles requests to BasePath with pprof, or passes // all other requests up the chain. func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { if middleware.Path(r.URL.Path).Matches(BasePath) { h.Mux.ServeHTTP(w, r) return 0, nil } return h.Next.ServeHTTP(w, r) }
// 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) (int, error) { var bc *Config // See if there's a browse configuration to match the path for i := range b.Configs { if middleware.Path(r.URL.Path).Matches(b.Configs[i].PathScope) { bc = &b.Configs[i] goto inScope } } return b.Next.ServeHTTP(w, r) inScope: // Browse works on existing directories; delegate everything else requestedFilepath, err := bc.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() 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) } // Browsing navigation gets messed up if browsing a directory // that doesn't end in "/" (which it should, anyway) if !strings.HasSuffix(r.URL.Path, "/") { http.Redirect(w, r, r.URL.Path+"/", http.StatusTemporaryRedirect) return 0, nil } return b.ServeListing(w, r, requestedFilepath, bc) }
// ServeHTTP implements the middlware.Handler interface. func (i Internal) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { // Internal location requested? -> Not found. for _, prefix := range i.Paths { if middleware.Path(r.URL.Path).Matches(prefix) { return http.StatusNotFound, nil } } // Use internal response writer to ignore responses that will be // redirected to internal locations iw := internalResponseWriter{ResponseWriter: w} status, err := i.Next.ServeHTTP(iw, r) for c := 0; c < maxRedirectCount && isInternalRedirect(iw); c++ { // Redirect - adapt request URL path and send it again // "down the chain" r.URL.Path = iw.Header().Get(redirectHeader) iw.ClearHeader() status, err = i.Next.ServeHTTP(iw, r) } if isInternalRedirect(iw) { // Too many redirect cycles iw.ClearHeader() return http.StatusInternalServerError, nil } return status, err }
// ServeHTTP handles requests to expvar's configured entry point with // expvar, or passes all other requests up the chain. func (e ExpVar) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { if middleware.Path(r.URL.Path).Matches(string(e.Resource)) { expvarHandler(w, r) return 0, nil } return e.Next.ServeHTTP(w, r) }
// AllowedPath checks if requestPath is not an ignored path. func (r Rule) AllowedPath(requestPath string) bool { for _, ignoredSubPath := range r.IgnoredSubPaths { if middleware.Path(path.Clean(requestPath)).Matches(path.Join(r.Path, ignoredSubPath)) { return false } } return true }
func (u *staticUpstream) IsAllowedPath(requestPath string) bool { for _, ignoredSubPath := range u.IgnoredSubPaths { if middleware.Path(path.Clean(requestPath)).Matches(path.Join(u.From(), ignoredSubPath)) { return false } } return true }
// ServeHTTP converts the HTTP request to a WebSocket connection and serves it up. func (ws WebSocket) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, sockconfig := range ws.Sockets { if middleware.Path(r.URL.Path).Matches(sockconfig.Path) { return serveWS(w, r, &sockconfig) } } // Didn't match a websocket path, so pass-thru return ws.Next.ServeHTTP(w, r) }
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { // if the request path is any of the configured paths // write hello for _, p := range h.Paths { if middleware.Path(r.URL.Path).Matches(p) { h.apihandler.ServeHTTP(w, r) return 0, nil } } return h.Next.ServeHTTP(w, r) }
// Rewrite rewrites the internal location of the current request. func (r *RegexpRule) Rewrite(req *http.Request) bool { rPath := req.URL.Path // validate base if !middleware.Path(rPath).Matches(r.Base) { return false } // validate extensions if !r.matchExt(rPath) { return false } // validate regexp if !r.MatchString(rPath[len(r.Base):]) { return false } to := r.To // check variables for _, v := range regexpVars { if strings.Contains(r.To, v) { switch v { case "{path}": to = strings.Replace(to, v, req.URL.Path[1:], -1) case "{query}": to = strings.Replace(to, v, req.URL.RawQuery, -1) case "{frag}": to = strings.Replace(to, v, req.URL.Fragment, -1) case "{file}": _, file := path.Split(req.URL.Path) to = strings.Replace(to, v, file, -1) case "{dir}": dir, _ := path.Split(req.URL.Path) to = path.Clean(strings.Replace(to, v, dir, -1)) } } } // validate resulting path url, err := url.Parse(to) if err != nil { return false } // perform rewrite req.URL.Path = url.Path if url.RawQuery != "" { // overwrite query string if present req.URL.RawQuery = url.RawQuery } return true }
// ServerHTTP is the HTTP handler for this middleware func (s *Search) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { if middleware.Path(r.URL.Path).Matches(s.Config.Endpoint) { if r.Header.Get("Accept") == "application/json" { return s.SearchJSON(w, r) } return s.SearchJSON(w, r) } record := s.Indexer.Record(r.URL.String()) go s.Pipeline.Pipe(record) return s.Next.ServeHTTP(&searchResponseWriter{w, record}, r) }
func (l Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, rule := range l.Rules { if middleware.Path(r.URL.Path).Matches(rule.PathScope) { responseRecorder := middleware.NewResponseRecorder(w) status, err := l.Next.ServeHTTP(responseRecorder, r) rep := middleware.NewReplacer(r, responseRecorder) rule.Log.Println(rep.Replace(rule.Format)) return status, err } } return l.Next.ServeHTTP(w, r) }
// ServeHTTP implements the middleware.Handler interface and serves requests, // setting headers on the response according to the configured rules. func (h Headers) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, rule := range h.Rules { if middleware.Path(r.URL.Path).Matches(rule.Path) { for _, header := range rule.Headers { if strings.HasPrefix(header.Name, "-") { w.Header().Del(strings.TrimLeft(header.Name, "-")) } else { w.Header().Set(header.Name, header.Value) } } } } return h.Next.ServeHTTP(w, r) }
// Rewrite rewrites the internal location of the current request. func (r *ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re RewriteResult) { rPath := req.URL.Path replacer := newReplacer(req) // validate base if !middleware.Path(rPath).Matches(r.Base) { return } // validate extensions if !r.matchExt(rPath) { return } // validate regexp if present if r.Regexp != nil { // include trailing slash in regexp if present start := len(r.Base) if strings.HasSuffix(r.Base, "/") { start-- } matches := r.FindStringSubmatch(rPath[start:]) switch len(matches) { case 0: // no match return default: // set regexp match variables {1}, {2} ... for i := 1; i < len(matches); i++ { replacer.Set(fmt.Sprint(i), matches[i]) } } } // validate rewrite conditions for _, i := range r.Ifs { if !i.True(req) { return } } // if status is present, stop rewrite and return it. if r.Status != 0 { return RewriteStatus } // attempt rewrite return To(fs, req, r.To, replacer) }
// ServeHTTP implements the middleware.Handler interface. func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, rule := range t.Rules { if !middleware.Path(r.URL.Path).Matches(rule.Path) { continue } // Check for index files fpath := r.URL.Path if idx, ok := middleware.IndexFile(t.FileSys, fpath, rule.IndexFiles); ok { fpath = idx } // Check the extension reqExt := path.Ext(fpath) for _, ext := range rule.Extensions { if reqExt == ext { // Create execution context ctx := context{root: t.FileSys, req: r, URL: r.URL} // Build the template tpl, err := template.ParseFiles(filepath.Join(t.Root, fpath)) if err != nil { if os.IsNotExist(err) { return http.StatusNotFound, nil } else if os.IsPermission(err) { return http.StatusForbidden, nil } return http.StatusInternalServerError, err } // Execute it var buf bytes.Buffer err = tpl.Execute(&buf, ctx) if err != nil { return http.StatusInternalServerError, err } buf.WriteTo(w) return http.StatusOK, nil } } } return t.Next.ServeHTTP(w, r) }
// Rewrite rewrites the internal location of the current request. func (r *RegexpRule) Rewrite(req *http.Request) bool { rPath := req.URL.Path // validate base if !middleware.Path(rPath).Matches(r.Base) { return false } // validate extensions if !r.matchExt(rPath) { return false } // include trailing slash in regexp if present start := len(r.Base) if strings.HasSuffix(r.Base, "/") { start -= 1 } // validate regexp if !r.MatchString(rPath[start:]) { return false } // replace variables to := path.Clean(middleware.NewReplacer(req, nil, "").Replace(r.To)) // validate resulting path url, err := url.Parse(to) if err != nil { return false } // take note of this rewrite for internal use by fastcgi // all we need is the URI, not full URL req.Header.Set("Caddy-Rewrite-Original-URI", req.URL.RequestURI()) // perform rewrite req.URL.Path = url.Path if url.RawQuery != "" { // overwrite query string if present req.URL.RawQuery = url.RawQuery } return true }
// ServerHTTP is the HTTP handler for this middleware func (s *Search) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { if middleware.Path(r.URL.Path).Matches(s.Config.Endpoint) { if r.Header.Get("Accept") == "application/json" || s.Config.Template == nil { return s.SearchJSON(w, r) } return s.SearchHTML(w, r) } record := s.Indexer.Record(r.URL.String()) go s.Pipeline.Pipe(record) status, err := s.Next.ServeHTTP(&searchResponseWriter{w, record}, r) if status != http.StatusOK { record.Ignore() } return status, err }
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, rule := range h.Rules { if !middleware.Path(r.URL.Path).Matches(rule.BasePath) { continue } // Check for index file fpath := r.URL.Path if idx, ok := middleware.IndexFile(h.FileSys, fpath, browse.IndexPages); ok { fpath = idx } // TODO: Check extension. If .lua, assume whole file is Lua script. file, err := h.FileSys.Open(filepath.Join(h.Root, fpath)) if err != nil { if os.IsNotExist(err) { return http.StatusNotFound, nil } else if os.IsPermission(err) { return http.StatusForbidden, nil } return http.StatusInternalServerError, err } defer file.Close() contents, err := ioutil.ReadAll(file) if err != nil { return http.StatusInternalServerError, err } var out bytes.Buffer if err := Interpret(&out, contents); err != nil { return http.StatusInternalServerError, err } // Write the combined text to the http.ResponseWriter w.Write(out.Bytes()) return http.StatusOK, nil } return h.Next.ServeHTTP(w, r) }
// Rewrite rewrites the internal location of the current request. func (r *RegexpRule) Rewrite(req *http.Request) bool { rPath := req.URL.Path // validate base if !middleware.Path(rPath).Matches(r.Base) { return false } // validate extensions if !r.matchExt(rPath) { return false } // include trailing slash in regexp if present start := len(r.Base) if strings.HasSuffix(r.Base, "/") { start -= 1 } // validate regexp if !r.MatchString(rPath[start:]) { return false } // replace variables to := path.Clean(middleware.NewReplacer(req, nil, "").Replace(r.To)) // validate resulting path url, err := url.Parse(to) if err != nil { return false } // perform rewrite req.URL.Path = url.Path if url.RawQuery != "" { // overwrite query string if present req.URL.RawQuery = url.RawQuery } return true }
// ServeHTTP implements the middleware.Handler interface. func (a BasicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { var hasAuth bool var isAuthenticated bool for _, rule := range a.Rules { for _, res := range rule.Resources { if !middleware.Path(r.URL.Path).Matches(res) { continue } // Path matches; parse auth header username, password, ok := r.BasicAuth() hasAuth = true // Check credentials if !ok || username != rule.Username || !rule.Password(password) { //subtle.ConstantTimeCompare([]byte(password), []byte(rule.Password)) != 1 { continue } // Flag set only on successful authentication isAuthenticated = true } } if hasAuth { if !isAuthenticated { w.Header().Set("WWW-Authenticate", "Basic") return http.StatusUnauthorized, nil } // "It's an older code, sir, but it checks out. I was about to clear them." return a.Next.ServeHTTP(w, r) } // Pass-thru when no paths match return a.Next.ServeHTTP(w, r) }
func (l Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, rule := range l.Rules { if middleware.Path(r.URL.Path).Matches(rule.PathScope) { responseRecorder := middleware.NewResponseRecorder(w) status, err := l.Next.ServeHTTP(responseRecorder, r) if status >= 400 { // There was an error up the chain, but no response has been written yet. // The error must be handled here so the log entry will record the response size. if l.ErrorFunc != nil { l.ErrorFunc(responseRecorder, r, status) } else { // Default failover error handler responseRecorder.WriteHeader(status) fmt.Fprintf(responseRecorder, "%d %s", status, http.StatusText(status)) } status = 0 } rep := middleware.NewReplacer(r, responseRecorder) rule.Log.Println(rep.Replace(rule.Format)) return status, err } } return l.Next.ServeHTTP(w, r) }
// ServeHTTP implements the http.Handler interface. func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, cfg := range md.Configs { if !middleware.Path(r.URL.Path).Matches(cfg.PathScope) { continue } fpath := r.URL.Path if idx, ok := middleware.IndexFile(md.FileSys, fpath, md.IndexFiles); ok { fpath = idx } for _, ext := range cfg.Extensions { if strings.HasSuffix(fpath, ext) { f, err := md.FileSys.Open(fpath) if err != nil { if os.IsPermission(err) { return http.StatusForbidden, err } return http.StatusNotFound, nil } fs, err := f.Stat() if err != nil { return http.StatusNotFound, nil } // if development is set, scan directory for file changes for links. if cfg.Development { if err := GenerateStatic(md, cfg); err != nil { log.Println("On-demand generation error (markdown):", err) } } cfg.RLock() filepath, ok := cfg.StaticFiles[fpath] cfg.RUnlock() // if static site is generated, attempt to use it if ok { if fs1, err := os.Stat(filepath); err == nil { // if markdown has not been modified since static page // generation, serve the static page if fs.ModTime().Before(fs1.ModTime()) { if html, err := ioutil.ReadFile(filepath); err == nil { w.Write(html) return http.StatusOK, nil } if os.IsPermission(err) { return http.StatusForbidden, err } return http.StatusNotFound, nil } } } body, err := ioutil.ReadAll(f) if err != nil { return http.StatusInternalServerError, err } ctx := middleware.Context{ Root: md.FileSys, Req: r, URL: r.URL, } html, err := md.Process(cfg, fpath, body, ctx) if err != nil { return http.StatusInternalServerError, err } w.Write(html) return http.StatusOK, nil } } } // Didn't qualify to serve as markdown; pass-thru return md.Next.ServeHTTP(w, r) }
// ShouldCompress checks if the request path matches any of the // registered paths to ignore. It returns false if an ignored path // is found and true otherwise. func (p PathFilter) ShouldCompress(r *http.Request) bool { return !p.IgnoredPaths.ContainsFunc(func(value string) bool { return middleware.Path(r.URL.Path).Matches(value) }) }
// ServeHTTP satisfies the middleware.Handler interface. func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, rule := range h.Rules { // First requirement: Base path must match if !middleware.Path(r.URL.Path).Matches(rule.Path) { continue } // In addition to matching the path, a request must meet some // other criteria before being proxied as FastCGI. For example, // we probably want to exclude static assets (CSS, JS, images...) // but we also want to be flexible for the script we proxy to. fpath := r.URL.Path if idx, ok := middleware.IndexFile(h.FileSys, fpath, rule.IndexFiles); ok { fpath = idx } // These criteria work well in this order for PHP sites if fpath[len(fpath)-1] == '/' || strings.HasSuffix(fpath, rule.Ext) || !h.exists(fpath) { // Create environment for CGI script env, err := h.buildEnv(r, rule, fpath) if err != nil { return http.StatusInternalServerError, err } // Connect to FastCGI gateway var fcgi *FCGIClient // check if unix socket or tcp if strings.HasPrefix(rule.Address, "/") || strings.HasPrefix(rule.Address, "unix:") { if strings.HasPrefix(rule.Address, "unix:") { rule.Address = rule.Address[len("unix:"):] } fcgi, err = Dial("unix", rule.Address) } else { fcgi, err = Dial("tcp", rule.Address) } if err != nil { return http.StatusBadGateway, err } var resp *http.Response contentLength, _ := strconv.Atoi(r.Header.Get("Content-Length")) switch r.Method { case "HEAD": resp, err = fcgi.Head(env) case "GET": resp, err = fcgi.Get(env) case "OPTIONS": resp, err = fcgi.Options(env) case "POST": resp, err = fcgi.Post(env, r.Header.Get("Content-Type"), r.Body, contentLength) case "PUT": resp, err = fcgi.Put(env, r.Header.Get("Content-Type"), r.Body, contentLength) case "PATCH": resp, err = fcgi.Patch(env, r.Header.Get("Content-Type"), r.Body, contentLength) case "DELETE": resp, err = fcgi.Delete(env, r.Header.Get("Content-Type"), r.Body, contentLength) default: return http.StatusMethodNotAllowed, nil } if resp.Body != nil { defer resp.Body.Close() } if err != nil && err != io.EOF { return http.StatusBadGateway, err } // Write the response header for key, vals := range resp.Header { for _, val := range vals { w.Header().Add(key, val) } } w.WriteHeader(resp.StatusCode) // Write the response body // TODO: If this has an error, the response will already be // partly written. We should copy out of resp.Body into a buffer // first, then write it to the response... _, err = io.Copy(w, resp.Body) if err != nil { return http.StatusBadGateway, err } return 0, nil } } return h.Next.ServeHTTP(w, r) }
// ServeHTTP implements the middleware.Handler interface. func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { filename := b.Root + r.URL.Path info, err := os.Stat(filename) if err != nil { return b.Next.ServeHTTP(w, r) } if !info.IsDir() { return b.Next.ServeHTTP(w, r) } // See if there's a browse configuration to match the path for _, bc := range b.Configs { if !middleware.Path(r.URL.Path).Matches(bc.PathScope) { continue } // Browsing navigation gets messed up if browsing a directory // that doesn't end in "/" (which it should, anyway) if r.URL.Path[len(r.URL.Path)-1] != '/' { http.Redirect(w, r, r.URL.Path+"/", http.StatusTemporaryRedirect) return 0, nil } // Load directory contents file, err := os.Open(b.Root + r.URL.Path) if err != nil { if os.IsPermission(err) { return http.StatusForbidden, err } return http.StatusNotFound, err } defer file.Close() files, err := file.Readdir(-1) if err != nil { return http.StatusForbidden, err } // Determine if user can browse up another folder var canGoUp bool curPath := strings.TrimSuffix(r.URL.Path, "/") for _, other := range b.Configs { if strings.HasPrefix(path.Dir(curPath), other.PathScope) { canGoUp = true break } } // 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' or 'order' is empty, check the cookies if listing.Sort == "" || listing.Order == "" { sortCookie, sortErr := r.Cookie("sort") orderCookie, orderErr := r.Cookie("order") // if there's no sorting values in the cookies, default to "name" and "asc" if sortErr != nil || orderErr != nil { listing.Sort = "name" listing.Order = "asc" } else { // if we have values in the cookies, use them listing.Sort = sortCookie.Value listing.Order = orderCookie.Value } } else { // save the query value of 'sort' and 'order' as cookies http.SetCookie(w, &http.Cookie{Name: "sort", Value: listing.Sort, Path: "/"}) http.SetCookie(w, &http.Cookie{Name: "order", Value: listing.Order, Path: "/"}) } // Apply the sorting listing.applySort() var buf bytes.Buffer err = bc.Template.Execute(&buf, listing) if err != nil { return http.StatusInternalServerError, err } w.Header().Set("Content-Type", "text/html; charset=utf-8") buf.WriteTo(w) return http.StatusOK, nil } // Didn't qualify; pass-thru return b.Next.ServeHTTP(w, r) }
// PathMatches returns true if the path portion of the request // URL matches pattern. func (c context) PathMatches(pattern string) bool { return middleware.Path(c.req.URL.Path).Matches(pattern) }
// ServeHTTP implements the middleware.Handler interface. func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { filename := b.Root + r.URL.Path info, err := os.Stat(filename) if err != nil { return b.Next.ServeHTTP(w, r) } if !info.IsDir() { return b.Next.ServeHTTP(w, r) } // See if there's a browse configuration to match the path for _, bc := range b.Configs { if !middleware.Path(r.URL.Path).Matches(bc.PathScope) { continue } // Browsing navigation gets messed up if browsing a directory // that doesn't end in "/" (which it should, anyway) if r.URL.Path[len(r.URL.Path)-1] != '/' { http.Redirect(w, r, r.URL.Path+"/", http.StatusTemporaryRedirect) return 0, nil } // Load directory contents file, err := os.Open(b.Root + r.URL.Path) if err != nil { if os.IsPermission(err) { return http.StatusForbidden, err } return http.StatusNotFound, err } defer file.Close() files, err := file.Readdir(-1) if err != nil { return http.StatusForbidden, err } // Determine if user can browse up another folder var canGoUp bool curPath := strings.TrimSuffix(r.URL.Path, "/") for _, other := range b.Configs { if strings.HasPrefix(path.Dir(curPath), other.PathScope) { canGoUp = true break } } // Assemble listing of directory contents listing, err := directoryListing(files, r, canGoUp, b.Root, b.IgnoreIndexes, bc.Variables) 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' or 'order' is empty, check the cookies if listing.Sort == "" || listing.Order == "" { sortCookie, sortErr := r.Cookie("sort") orderCookie, orderErr := r.Cookie("order") // if there's no sorting values in the cookies, default to "name" and "asc" if sortErr != nil || orderErr != nil { listing.Sort = "name" listing.Order = "asc" } else { // if we have values in the cookies, use them listing.Sort = sortCookie.Value listing.Order = orderCookie.Value } } else { // save the query value of 'sort' and 'order' as cookies http.SetCookie(w, &http.Cookie{Name: "sort", Value: listing.Sort, Path: "/"}) http.SetCookie(w, &http.Cookie{Name: "order", Value: listing.Order, Path: "/"}) } // Apply the sorting listing.applySort() var buf bytes.Buffer // check if we should provide json acceptHeader := strings.Join(r.Header["Accept"], ",") if strings.Contains(strings.ToLower(acceptHeader), "application/json") { var marsh []byte // check if we are limited if limitQuery := r.URL.Query().Get("limit"); limitQuery != "" { limit, err := strconv.Atoi(limitQuery) if err != nil { // if the 'limit' query can't be interpreted as a number, return err return http.StatusBadRequest, err } // if `limit` is equal or less than len(listing.Items) and bigger than 0, list them if limit <= len(listing.Items) && limit > 0 { marsh, err = json.Marshal(listing.Items[:limit]) } else { // if the 'limit' query is empty, or has the wrong value, list everything marsh, err = json.Marshal(listing.Items) } if err != nil { return http.StatusInternalServerError, err } } else { // there's no 'limit' query, list them all marsh, err = json.Marshal(listing.Items) if err != nil { return http.StatusInternalServerError, err } } // write the marshaled json to buf if _, err = buf.Write(marsh); err != nil { return http.StatusInternalServerError, err } w.Header().Set("Content-Type", "application/json; charset=utf-8") } else { // there's no 'application/json' in the 'Accept' header, browse normally err = bc.Template.Execute(&buf, listing) if err != nil { return http.StatusInternalServerError, err } w.Header().Set("Content-Type", "text/html; charset=utf-8") } buf.WriteTo(w) return http.StatusOK, nil } // Didn't qualify; pass-thru return b.Next.ServeHTTP(w, r) }
// ServeHTTP satisfies the middleware.Handler interface. func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, rule := range h.Rules { // First requirement: Base path must match and the path must be allowed. if !middleware.Path(r.URL.Path).Matches(rule.Path) || !rule.AllowedPath(r.URL.Path) { continue } // In addition to matching the path, a request must meet some // other criteria before being proxied as FastCGI. For example, // we probably want to exclude static assets (CSS, JS, images...) // but we also want to be flexible for the script we proxy to. fpath := r.URL.Path if idx, ok := middleware.IndexFile(h.FileSys, fpath, rule.IndexFiles); ok { fpath = idx // Index file present. // If request path cannot be split, return error. if !rule.canSplit(fpath) { return http.StatusInternalServerError, ErrIndexMissingSplit } } else { // No index file present. // If request path cannot be split, ignore request. if !rule.canSplit(fpath) { continue } } // These criteria work well in this order for PHP sites if !h.exists(fpath) || fpath[len(fpath)-1] == '/' || strings.HasSuffix(fpath, rule.Ext) { // Create environment for CGI script env, err := h.buildEnv(r, rule, fpath) if err != nil { return http.StatusInternalServerError, err } // Connect to FastCGI gateway network, address := rule.parseAddress() fcgiBackend, err := Dial(network, address) if err != nil { return http.StatusBadGateway, err } var resp *http.Response contentLength, _ := strconv.Atoi(r.Header.Get("Content-Length")) switch r.Method { case "HEAD": resp, err = fcgiBackend.Head(env) case "GET": resp, err = fcgiBackend.Get(env) case "OPTIONS": resp, err = fcgiBackend.Options(env) default: resp, err = fcgiBackend.Post(env, r.Method, r.Header.Get("Content-Type"), r.Body, contentLength) } if resp.Body != nil { defer resp.Body.Close() } if err != nil && err != io.EOF { return http.StatusBadGateway, err } // Write response header writeHeader(w, resp) // Write the response body _, err = io.Copy(w, resp.Body) if err != nil { return http.StatusBadGateway, err } // Log any stderr output from upstream if fcgiBackend.stderr.Len() != 0 { // Remove trailing newline, error logger already does this. err = LogError(strings.TrimSuffix(fcgiBackend.stderr.String(), "\n")) } // Normally we would return the status code if it is an error status (>= 400), // however, upstream FastCGI apps don't know about our contract and have // probably already written an error page. So we just return 0, indicating // that the response body is already written. However, we do return any // error value so it can be logged. // Note that the proxy middleware works the same way, returning status=0. return 0, err } } return h.Next.ServeHTTP(w, r) }
// ServeHTTP satisfies the middleware.Handler interface. func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, upstream := range p.Upstreams { if middleware.Path(r.URL.Path).Matches(upstream.From()) && upstream.IsAllowedPath(r.URL.Path) { var replacer middleware.Replacer start := time.Now() requestHost := r.Host // Since Select() should give us "up" hosts, keep retrying // hosts until timeout (or until we get a nil host). for time.Now().Sub(start) < tryDuration { host := upstream.Select() if host == nil { return http.StatusBadGateway, errUnreachable } proxy := host.ReverseProxy r.Host = host.Name if baseURL, err := url.Parse(host.Name); err == nil { r.Host = baseURL.Host if proxy == nil { proxy = NewSingleHostReverseProxy(baseURL, host.WithoutPathPrefix) } } else if proxy == nil { return http.StatusInternalServerError, err } var extraHeaders http.Header if host.ExtraHeaders != nil { extraHeaders = make(http.Header) if replacer == nil { rHost := r.Host r.Host = requestHost replacer = middleware.NewReplacer(r, nil, "") r.Host = rHost } for header, values := range host.ExtraHeaders { for _, value := range values { extraHeaders.Add(header, replacer.Replace(value)) if header == "Host" { r.Host = replacer.Replace(value) } } } } atomic.AddInt64(&host.Conns, 1) backendErr := proxy.ServeHTTP(w, r, extraHeaders) atomic.AddInt64(&host.Conns, -1) if backendErr == nil { return 0, nil } timeout := host.FailTimeout if timeout == 0 { timeout = 10 * time.Second } atomic.AddInt32(&host.Fails, 1) go func(host *UpstreamHost, timeout time.Duration) { time.Sleep(timeout) atomic.AddInt32(&host.Fails, -1) }(host, timeout) } return http.StatusBadGateway, errUnreachable } } return p.Next.ServeHTTP(w, r) }