Commit 47717fee authored by Matthew Holt's avatar Matthew Holt

Expanded index file support to other middlewares (fixes #27)

parent a9064a78
...@@ -2,6 +2,7 @@ package setup ...@@ -2,6 +2,7 @@ package setup
import ( import (
"errors" "errors"
"net/http"
"path/filepath" "path/filepath"
"github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware"
...@@ -10,7 +11,7 @@ import ( ...@@ -10,7 +11,7 @@ import (
// FastCGI configures a new FastCGI middleware instance. // FastCGI configures a new FastCGI middleware instance.
func FastCGI(c *Controller) (middleware.Middleware, error) { func FastCGI(c *Controller) (middleware.Middleware, error) {
root, err := filepath.Abs(c.Root) absRoot, err := filepath.Abs(c.Root)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -24,7 +25,9 @@ func FastCGI(c *Controller) (middleware.Middleware, error) { ...@@ -24,7 +25,9 @@ func FastCGI(c *Controller) (middleware.Middleware, error) {
return fastcgi.Handler{ return fastcgi.Handler{
Next: next, Next: next,
Rules: rules, Rules: rules,
Root: root, Root: c.Root,
AbsRoot: absRoot,
FileSys: http.Dir(c.Root),
SoftwareName: c.AppName, SoftwareName: c.AppName,
SoftwareVersion: c.AppVersion, SoftwareVersion: c.AppVersion,
ServerName: c.Host, ServerName: c.Host,
...@@ -72,10 +75,11 @@ func fastcgiParse(c *Controller) ([]fastcgi.Rule, error) { ...@@ -72,10 +75,11 @@ func fastcgiParse(c *Controller) ([]fastcgi.Rule, error) {
} }
rule.SplitPath = c.Val() rule.SplitPath = c.Val()
case "index": case "index":
if !c.NextArg() { args := c.RemainingArgs()
if len(args) == 0 {
return rules, c.ArgErr() return rules, c.ArgErr()
} }
rule.IndexFile = c.Val() rule.IndexFiles = args
case "env": case "env":
envArgs := c.RemainingArgs() envArgs := c.RemainingArgs()
if len(envArgs) < 2 { if len(envArgs) < 2 {
...@@ -98,7 +102,7 @@ func fastcgiPreset(name string, rule *fastcgi.Rule) error { ...@@ -98,7 +102,7 @@ func fastcgiPreset(name string, rule *fastcgi.Rule) error {
case "php": case "php":
rule.Ext = ".php" rule.Ext = ".php"
rule.SplitPath = ".php" rule.SplitPath = ".php"
rule.IndexFile = "index.php" rule.IndexFiles = []string{"index.php"}
default: default:
return errors.New(name + " is not a valid preset name") return errors.New(name + " is not a valid preset name")
} }
......
package setup package setup
import ( import (
"net/http"
"github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/markdown" "github.com/mholt/caddy/middleware/markdown"
"github.com/russross/blackfriday" "github.com/russross/blackfriday"
...@@ -14,8 +16,10 @@ func Markdown(c *Controller) (middleware.Middleware, error) { ...@@ -14,8 +16,10 @@ func Markdown(c *Controller) (middleware.Middleware, error) {
} }
md := markdown.Markdown{ md := markdown.Markdown{
Root: c.Root, Root: c.Root,
Configs: mdconfigs, FileSys: http.Dir(c.Root),
Configs: mdconfigs,
IndexFiles: []string{"index.md"},
} }
return func(next middleware.Handler) middleware.Handler { return func(next middleware.Handler) middleware.Handler {
......
package setup package setup
import ( import (
"net/http"
"github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/templates" "github.com/mholt/caddy/middleware/templates"
) )
...@@ -13,8 +15,9 @@ func Templates(c *Controller) (middleware.Middleware, error) { ...@@ -13,8 +15,9 @@ func Templates(c *Controller) (middleware.Middleware, error) {
} }
tmpls := templates.Templates{ tmpls := templates.Templates{
Root: c.Root, Rules: rules,
Rules: rules, Root: c.Root,
FileSys: http.Dir(c.Root),
} }
return func(next middleware.Handler) middleware.Handler { return func(next middleware.Handler) middleware.Handler {
...@@ -43,6 +46,10 @@ func templatesParse(c *Controller) ([]templates.Rule, error) { ...@@ -43,6 +46,10 @@ func templatesParse(c *Controller) ([]templates.Rule, error) {
rule.Extensions = defaultExtensions rule.Extensions = defaultExtensions
} }
for _, ext := range rule.Extensions {
rule.IndexFiles = append(rule.IndexFiles, "index"+ext)
}
rules = append(rules, rule) rules = append(rules, rule)
} }
...@@ -51,4 +58,4 @@ func templatesParse(c *Controller) ([]templates.Rule, error) { ...@@ -51,4 +58,4 @@ func templatesParse(c *Controller) ([]templates.Rule, error) {
const defaultPath = "/" const defaultPath = "/"
var defaultExtensions = []string{".html", ".htm", ".txt"} var defaultExtensions = []string{".html", ".htm", ".tmpl", ".tpl", ".txt"}
...@@ -16,9 +16,11 @@ import ( ...@@ -16,9 +16,11 @@ import (
// Handler is a middleware type that can handle requests as a FastCGI client. // Handler is a middleware type that can handle requests as a FastCGI client.
type Handler struct { type Handler struct {
Next middleware.Handler Next middleware.Handler
Root string // must be absolute path to site root Rules []Rule
Rules []Rule Root string
AbsRoot string // same as root, but absolute path
FileSys http.FileSystem
// These are sent to CGI scripts in env variables // These are sent to CGI scripts in env variables
SoftwareName string SoftwareName string
...@@ -30,26 +32,27 @@ type Handler struct { ...@@ -30,26 +32,27 @@ type Handler struct {
// ServeHTTP satisfies the middleware.Handler interface. // ServeHTTP satisfies the middleware.Handler interface.
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
for _, rule := range h.Rules { 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 // In addition to matching the path, a request must meet some
// other criteria before being proxied as FastCGI. For example, // other criteria before being proxied as FastCGI. For example,
// we probably want to exclude static assets (CSS, JS, images...) // we probably want to exclude static assets (CSS, JS, images...)
// but we also want to be flexible for the script we proxy to. // but we also want to be flexible for the script we proxy to.
path := r.URL.Path 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 // These criteria work well in this order for PHP sites
if middleware.Path(path).Matches(rule.Path) && if fpath[len(fpath)-1] == '/' || strings.HasSuffix(fpath, rule.Ext) || !h.exists(fpath) {
(path[len(path)-1] == '/' ||
strings.HasSuffix(path, rule.Ext) ||
!h.exists(path)) {
if path[len(path)-1] == '/' && h.exists(path+rule.IndexFile) {
// If index file in specified folder exists, send request to it
path += rule.IndexFile
}
// Create environment for CGI script // Create environment for CGI script
env, err := h.buildEnv(r, rule, path) env, err := h.buildEnv(r, rule, fpath)
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
...@@ -115,14 +118,12 @@ func (h Handler) exists(path string) bool { ...@@ -115,14 +118,12 @@ func (h Handler) exists(path string) bool {
return false return false
} }
func (h Handler) buildEnv(r *http.Request, rule Rule, path string) (map[string]string, error) { // buildEnv returns a set of CGI environment variables for the request.
func (h Handler) buildEnv(r *http.Request, rule Rule, fpath string) (map[string]string, error) {
var env map[string]string var env map[string]string
// Get absolute path of requested resource // Get absolute path of requested resource
absPath, err := filepath.Abs(h.Root + path) absPath := filepath.Join(h.AbsRoot, fpath)
if err != nil {
return env, err
}
// Separate remote IP and port; more lenient than net.SplitHostPort // Separate remote IP and port; more lenient than net.SplitHostPort
var ip, port string var ip, port string
...@@ -134,19 +135,19 @@ func (h Handler) buildEnv(r *http.Request, rule Rule, path string) (map[string]s ...@@ -134,19 +135,19 @@ func (h Handler) buildEnv(r *http.Request, rule Rule, path string) (map[string]s
} }
// Split path in preparation for env variables // Split path in preparation for env variables
splitPos := strings.Index(path, rule.SplitPath) splitPos := strings.Index(fpath, rule.SplitPath)
var docURI, scriptName, scriptFilename, pathInfo string var docURI, scriptName, scriptFilename, pathInfo string
if splitPos == -1 { if splitPos == -1 {
// Request doesn't have the extension, so assume index file in root // Request doesn't have the extension, so assume index file in root
docURI = "/" + rule.IndexFile docURI = "/" + rule.IndexFiles[0]
scriptName = "/" + rule.IndexFile scriptName = "/" + rule.IndexFiles[0]
scriptFilename = h.Root + "/" + rule.IndexFile scriptFilename = filepath.Join(h.AbsRoot, rule.IndexFiles[0])
pathInfo = path pathInfo = fpath
} else { } else {
// Request has the extension; path was split successfully // Request has the extension; path was split successfully
docURI = path[:splitPos+len(rule.SplitPath)] docURI = fpath[:splitPos+len(rule.SplitPath)]
pathInfo = path[splitPos+len(rule.SplitPath):] pathInfo = fpath[splitPos+len(rule.SplitPath):]
scriptName = path scriptName = fpath
scriptFilename = absPath scriptFilename = absPath
} }
...@@ -160,7 +161,7 @@ func (h Handler) buildEnv(r *http.Request, rule Rule, path string) (map[string]s ...@@ -160,7 +161,7 @@ func (h Handler) buildEnv(r *http.Request, rule Rule, path string) (map[string]s
"CONTENT_TYPE": r.Header.Get("Content-Type"), "CONTENT_TYPE": r.Header.Get("Content-Type"),
"GATEWAY_INTERFACE": "CGI/1.1", "GATEWAY_INTERFACE": "CGI/1.1",
"PATH_INFO": pathInfo, "PATH_INFO": pathInfo,
"PATH_TRANSLATED": h.Root + "/" + pathInfo, // Source for path_translated: http://www.oreilly.com/openbook/cgi/ch02_04.html "PATH_TRANSLATED": filepath.Join(h.AbsRoot, pathInfo), // Info: http://www.oreilly.com/openbook/cgi/ch02_04.html
"QUERY_STRING": r.URL.RawQuery, "QUERY_STRING": r.URL.RawQuery,
"REMOTE_ADDR": ip, "REMOTE_ADDR": ip,
"REMOTE_HOST": ip, // For speed, remote host lookups disabled "REMOTE_HOST": ip, // For speed, remote host lookups disabled
...@@ -174,7 +175,7 @@ func (h Handler) buildEnv(r *http.Request, rule Rule, path string) (map[string]s ...@@ -174,7 +175,7 @@ func (h Handler) buildEnv(r *http.Request, rule Rule, path string) (map[string]s
"SERVER_SOFTWARE": h.SoftwareName + "/" + h.SoftwareVersion, "SERVER_SOFTWARE": h.SoftwareName + "/" + h.SoftwareVersion,
// Other variables // Other variables
"DOCUMENT_ROOT": h.Root, "DOCUMENT_ROOT": h.AbsRoot,
"DOCUMENT_URI": docURI, "DOCUMENT_URI": docURI,
"HTTP_HOST": r.Host, // added here, since not always part of headers "HTTP_HOST": r.Host, // added here, since not always part of headers
"REQUEST_URI": r.URL.RequestURI(), "REQUEST_URI": r.URL.RequestURI(),
...@@ -214,8 +215,9 @@ type Rule struct { ...@@ -214,8 +215,9 @@ type Rule struct {
// PATH_INFO for the CGI script to use. // PATH_INFO for the CGI script to use.
SplitPath string SplitPath string
// If the URL does not indicate a file, an index file with this name will be assumed. // If the URL ends with '/' (which indicates a directory), these index
IndexFile string // files will be tried instead.
IndexFiles []string
// Environment Variables // Environment Variables
EnvVars [][2]string EnvVars [][2]string
......
...@@ -20,11 +20,17 @@ type Markdown struct { ...@@ -20,11 +20,17 @@ type Markdown struct {
// Server root // Server root
Root string Root string
// Jail the requests to site root with a mock file system
FileSys http.FileSystem
// Next HTTP handler in the chain // Next HTTP handler in the chain
Next middleware.Handler Next middleware.Handler
// The list of markdown configurations // The list of markdown configurations
Configs []Config Configs []Config
// The list of index files to try
IndexFiles []string
} }
// Config stores markdown middleware configurations. // Config stores markdown middleware configurations.
...@@ -52,11 +58,14 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error ...@@ -52,11 +58,14 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
continue continue
} }
for _, ext := range m.Extensions { fpath := r.URL.Path
if strings.HasSuffix(r.URL.Path, ext) { if idx, ok := middleware.IndexFile(md.FileSys, fpath, md.IndexFiles); ok {
fpath := md.Root + r.URL.Path fpath = idx
}
body, err := ioutil.ReadFile(fpath) for _, ext := range m.Extensions {
if strings.HasSuffix(fpath, ext) {
f, err := md.FileSys.Open(fpath)
if err != nil { if err != nil {
if os.IsPermission(err) { if os.IsPermission(err) {
return http.StatusForbidden, err return http.StatusForbidden, err
...@@ -64,6 +73,11 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error ...@@ -64,6 +73,11 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
return http.StatusNotFound, nil return http.StatusNotFound, nil
} }
body, err := ioutil.ReadAll(f)
if err != nil {
return http.StatusInternalServerError, err
}
content := blackfriday.Markdown(body, m.Renderer, 0) content := blackfriday.Markdown(body, m.Renderer, 0)
var scripts, styles string var scripts, styles string
......
// Package middleware provides some types and functions common among middleware. // Package middleware provides some types and functions common among middleware.
package middleware package middleware
import "net/http" import (
"net/http"
"path/filepath"
)
type ( type (
// Middleware is the middle layer which represents the traditional // Middleware is the middle layer which represents the traditional
...@@ -47,3 +50,24 @@ type ( ...@@ -47,3 +50,24 @@ type (
func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
return f(w, r) return f(w, r)
} }
// IndexFile looks for a file in /root/fpath/indexFile for each string
// in indexFiles. If an index file is found, it returns the root-relative
// path to the file and true. If no index file is found, empty string
// and false is returned. fpath must end in a forward slash '/'
// otherwise no index files will be tried (directory paths must end
// in a forward slash according to HTTP).
func IndexFile(root http.FileSystem, fpath string, indexFiles []string) (string, bool) {
if fpath[len(fpath)-1] != '/' || root == nil {
return "", false
}
for _, indexFile := range indexFiles {
fp := filepath.Join(fpath, indexFile)
f, err := root.Open(fp)
if err == nil {
f.Close()
return fp, true
}
}
return "", false
}
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"bytes" "bytes"
"net/http" "net/http"
"path" "path"
"path/filepath"
"text/template" "text/template"
"github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware"
...@@ -17,15 +18,22 @@ func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error ...@@ -17,15 +18,22 @@ func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
continue continue
} }
reqExt := path.Ext(r.URL.Path) // 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 { for _, ext := range rule.Extensions {
if reqExt == ext { if reqExt == ext {
// Create execution context // Create execution context
ctx := context{root: http.Dir(t.Root), req: r, URL: r.URL} ctx := context{root: t.FileSys, req: r, URL: r.URL}
// Build the template // Build the template
tpl, err := template.ParseFiles(t.Root + r.URL.Path) tpl, err := template.ParseFiles(filepath.Join(t.Root, fpath))
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
...@@ -48,9 +56,10 @@ func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error ...@@ -48,9 +56,10 @@ func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
// Templates is middleware to render templated files as the HTTP response. // Templates is middleware to render templated files as the HTTP response.
type Templates struct { type Templates struct {
Next middleware.Handler Next middleware.Handler
Root string Rules []Rule
Rules []Rule Root string
FileSys http.FileSystem
} }
// Rule represents a template rule. A template will only execute // Rule represents a template rule. A template will only execute
...@@ -59,4 +68,5 @@ type Templates struct { ...@@ -59,4 +68,5 @@ type Templates struct {
type Rule struct { type Rule struct {
Path string Path string
Extensions []string Extensions []string
IndexFiles []string
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment