// Package filemanager provides middleware for managing files in a directory // when directory path is requested instead of a specific file. Based on browse // middleware. package filemanager import ( "fmt" "io/ioutil" "log" "net/http" "os" "os/exec" "path/filepath" "regexp" "strconv" "strings" "github.com/hacdias/filemanager" "github.com/mholt/caddy" "github.com/mholt/caddy/caddyhttp/httpserver" ) func init() { caddy.RegisterPlugin("filemanager", caddy.Plugin{ ServerType: "http", Action: setup, }) } // FileManager is an http.Handler that can show a file listing when // directories in the given paths are specified. type FileManager struct { Next httpserver.Handler Configs []*filemanager.FileManager } // ServeHTTP determines if the request is for this plugin, and if all prerequisites are met. func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for i := range f.Configs { // Checks if this Path should be handled by File Manager. if !httpserver.Path(r.URL.Path).Matches(f.Configs[i].BaseURL) { continue } return f.Configs[i].ServeHTTP(w, r) } return f.Next.ServeHTTP(w, r) } // setup configures a new FileManager middleware instance. func setup(c *caddy.Controller) error { configs, err := parse(c) if err != nil { return err } httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { return FileManager{Configs: configs, Next: next} }) return nil } func parse(c *caddy.Controller) ([]*filemanager.FileManager, error) { var ( configs []*filemanager.FileManager err error ) for c.Next() { var ( m = filemanager.New(".") u = m.User name = "" ) caddyConf := httpserver.GetConfig(c) m.SetPrefixURL(strings.TrimSuffix(caddyConf.Addr.Path, "/")) m.Commands = []string{"git", "svn", "hg"} m.Rules = append(m.Rules, &filemanager.Rule{ Regex: true, Allow: false, Regexp: regexp.MustCompile("\\/\\..+"), }) // Get the baseURL args := c.RemainingArgs() if len(args) > 0 { m.SetBaseURL(args[0]) m.SetWebDavURL("/webdav") } for c.NextBlock() { switch c.Val() { case "before_save": if m.BeforeSave, err = makeCommand(c); err != nil { return configs, err } case "after_save": if m.AfterSave, err = makeCommand(c); err != nil { return configs, err } case "webdav": if !c.NextArg() { return configs, c.ArgErr() } m.SetWebDavURL(c.Val()) case "show": if !c.NextArg() { return configs, c.ArgErr() } m.SetScope(c.Val(), name) case "styles": if !c.NextArg() { return configs, c.ArgErr() } var tplBytes []byte tplBytes, err = ioutil.ReadFile(c.Val()) if err != nil { return configs, err } u.StyleSheet = string(tplBytes) case "allow_new": if !c.NextArg() { return configs, c.ArgErr() } u.AllowNew, err = strconv.ParseBool(c.Val()) if err != nil { return configs, err } case "allow_edit": if !c.NextArg() { return configs, c.ArgErr() } u.AllowEdit, err = strconv.ParseBool(c.Val()) if err != nil { return configs, err } case "allow_commands": if !c.NextArg() { return configs, c.ArgErr() } u.AllowCommands, err = strconv.ParseBool(c.Val()) if err != nil { return configs, err } case "allow_command": if !c.NextArg() { return configs, c.ArgErr() } u.Commands = append(u.Commands, c.Val()) case "block_command": if !c.NextArg() { return configs, c.ArgErr() } index := 0 for i, val := range u.Commands { if val == c.Val() { index = i } } u.Commands = append(u.Commands[:index], u.Commands[index+1:]...) case "allow", "allow_r", "block", "block_r": ruleType := c.Val() if !c.NextArg() { return configs, c.ArgErr() } if c.Val() == "dotfiles" && !strings.HasSuffix(ruleType, "_r") { ruleType += "_r" } rule := &filemanager.Rule{ Allow: ruleType == "allow" || ruleType == "allow_r", Regex: ruleType == "allow_r" || ruleType == "block_r", } if rule.Regex && c.Val() == "dotfiles" { rule.Regexp = regexp.MustCompile("\\/\\..+") } else if rule.Regex { rule.Regexp = regexp.MustCompile(c.Val()) } else { rule.Path = c.Val() } u.Rules = append(u.Rules, rule) default: // Is it a new user? Is it? val := c.Val() // Checks if it's a new user! if !strings.HasSuffix(val, ":") { fmt.Println("Unknown option " + val) } // Get the username, sets the current user, and initializes it val = strings.TrimSuffix(val, ":") m.NewUser(val) name = val } } configs = append(configs, m) } return configs, nil } func makeCommand(c *caddy.Controller) (filemanager.Command, error) { fn := func(r *http.Request, c *filemanager.FileManager, u *filemanager.User) error { return nil } args := c.RemainingArgs() if len(args) == 0 { return fn, c.ArgErr() } nonblock := false if len(args) > 1 && args[len(args)-1] == "&" { // Run command in background; non-blocking nonblock = true args = args[:len(args)-1] } command, args, err := caddy.SplitCommandAndArgs(strings.Join(args, " ")) if err != nil { return fn, c.Err(err.Error()) } fn = func(r *http.Request, c *filemanager.FileManager, u *filemanager.User) error { path := strings.Replace(r.URL.Path, c.WebDavURL, "", 1) path = u.Scope() + "/" + path path = filepath.Clean(path) for i := range args { args[i] = strings.Replace(args[i], "{path}", path, -1) } cmd := exec.Command(command, args...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if nonblock { log.Printf("[INFO] Nonblocking Command:\"%s %s\"", command, strings.Join(args, " ")) return cmd.Start() } log.Printf("[INFO] Blocking Command:\"%s %s\"", command, strings.Join(args, " ")) return cmd.Run() } return fn, nil }