func mimeParse(c *caddy.Controller) (Config, error) { configs := Config{} for c.Next() { // At least one extension is required args := c.RemainingArgs() switch len(args) { case 2: if err := validateExt(configs, args[0]); err != nil { return configs, err } configs[args[0]] = args[1] case 1: return configs, c.ArgErr() case 0: for c.NextBlock() { ext := c.Val() if err := validateExt(configs, ext); err != nil { return configs, err } if !c.NextArg() { return configs, c.ArgErr() } configs[ext] = c.Val() } } } return configs, nil }
// ParseRoller parses roller contents out of c. func ParseRoller(c *caddy.Controller) (*LogRoller, error) { var size, age, keep int // This is kind of a hack to support nested blocks: // As we are already in a block: either log or errors, // c.nesting > 0 but, as soon as c meets a }, it thinks // the block is over and return false for c.NextBlock. for c.NextBlock() { what := c.Val() if !c.NextArg() { return nil, c.ArgErr() } value := c.Val() var err error switch what { case "size": size, err = strconv.Atoi(value) case "age": age, err = strconv.Atoi(value) case "keep": keep, err = strconv.Atoi(value) } if err != nil { return nil, err } } return &LogRoller{ MaxSize: size, MaxAge: age, MaxBackups: keep, LocalTime: true, }, nil }
func parse(m *module, c *caddy.Controller) (err error) { args := c.RemainingArgs() if len(args) == 1 && args[0] == "cloudflare" { addCloudflareIps(m) if c.NextBlock() { return c.Err("No realip subblocks allowed if using preset.") } } else if len(args) != 0 { return c.ArgErr() } for c.NextBlock() { var err error switch c.Val() { case "header": m.Header, err = StringArg(c) case "from": var cidr *net.IPNet cidr, err = CidrArg(c) m.From = append(m.From, cidr) case "strict": m.Strict, err = BoolArg(c) default: return c.Errf("Unknown realip arg: %s", c.Val()) } if err != nil { return err } } return nil }
// statusParse parses status directive func statusParse(c *caddy.Controller) ([]httpserver.HandlerConfig, error) { var rules []httpserver.HandlerConfig for c.Next() { hadBlock := false args := c.RemainingArgs() switch len(args) { case 1: status, err := strconv.Atoi(args[0]) if err != nil { return rules, c.Errf("Expecting a numeric status code, got '%s'", args[0]) } for c.NextBlock() { hadBlock = true basePath := c.Val() for _, cfg := range rules { rule := cfg.(*Rule) if rule.Base == basePath { return rules, c.Errf("Duplicate path: '%s'", basePath) } } rule := NewRule(basePath, status) rules = append(rules, rule) if c.NextArg() { return rules, c.ArgErr() } } if !hadBlock { return rules, c.ArgErr() } case 2: status, err := strconv.Atoi(args[0]) if err != nil { return rules, c.Errf("Expecting a numeric status code, got '%s'", args[0]) } basePath := args[1] for _, cfg := range rules { rule := cfg.(*Rule) if rule.Base == basePath { return rules, c.Errf("Duplicate path: '%s'", basePath) } } rule := NewRule(basePath, status) rules = append(rules, rule) default: return rules, c.ArgErr() } } return rules, nil }
func templatesParse(c *caddy.Controller) ([]Rule, error) { var rules []Rule for c.Next() { var rule Rule rule.Path = defaultTemplatePath rule.Extensions = defaultTemplateExtensions args := c.RemainingArgs() switch len(args) { case 0: // Optional block for c.NextBlock() { switch c.Val() { case "path": args := c.RemainingArgs() if len(args) != 1 { return nil, c.ArgErr() } rule.Path = args[0] case "ext": args := c.RemainingArgs() if len(args) == 0 { return nil, c.ArgErr() } rule.Extensions = args case "between": args := c.RemainingArgs() if len(args) != 2 { return nil, c.ArgErr() } rule.Delims[0] = args[0] rule.Delims[1] = args[1] } } default: // First argument would be the path rule.Path = args[0] // Any remaining arguments are extensions rule.Extensions = args[1:] if len(rule.Extensions) == 0 { rule.Extensions = defaultTemplateExtensions } } for _, ext := range rule.Extensions { rule.IndexFiles = append(rule.IndexFiles, "index"+ext) } rules = append(rules, rule) } return rules, nil }
func setupMaxRequestBody(c *caddy.Controller) error { config := httpserver.GetConfig(c) if !c.Next() { return c.ArgErr() } args := c.RemainingArgs() argList := []pathLimitUnparsed{} switch len(args) { case 0: // Format: { <path> <limit> ... } for c.NextBlock() { path := c.Val() if !c.NextArg() { // Uneven pairing of path/limit return c.ArgErr() } argList = append(argList, pathLimitUnparsed{ Path: path, Limit: c.Val(), }) } case 1: // Format: <limit> argList = []pathLimitUnparsed{{ Path: "/", Limit: args[0], }} case 2: // Format: <path> <limit> argList = []pathLimitUnparsed{{ Path: args[0], Limit: args[1], }} default: return c.ArgErr() } pathLimit, err := parseArguments(argList) if err != nil { return c.ArgErr() } SortPathLimits(pathLimit) config.MaxRequestBodySizes = pathLimit return nil }
// nlgidsParse will parse the following directives. // recipients [email protected] [email protected] // subject [email protected] // secret /opt/etc/NLgids-fcbeb7928cdb.json func nlgidsParse(c *caddy.Controller) (*Config, error) { config := new(Config) config.Tours = "/opt/www/nlgids.london/tours.json" for c.Next() { for c.NextBlock() { switch c.Val() { case "recipients": rcpts := c.RemainingArgs() if len(rcpts) == 0 { return nil, c.ArgErr() } config.Recipients = append(config.Recipients, rcpts...) case "subject": if !c.NextArg() { return nil, c.ArgErr() } config.Subject = c.Val() if !strings.Contains(config.Subject, "@") { return nil, fmt.Errorf("nlgids: subject must contain @-sign: %s", c.Val()) } case "secret": if !c.NextArg() { return nil, c.ArgErr() } config.Secret = c.Val() _, err := os.Open(config.Secret) if err != nil { return nil, fmt.Errorf("nlgids: secret file must be readable: %s", err) } case "template": if !c.NextArg() { return nil, c.ArgErr() } config.Template = c.Val() case "tours": if !c.NextArg() { return nil, c.ArgErr() } config.Tours = c.Val() } } } return config, nil }
func markdownParse(c *caddy.Controller) ([]*Config, error) { var mdconfigs []*Config for c.Next() { md := &Config{ Renderer: blackfriday.HtmlRenderer(0, "", ""), Extensions: make(map[string]struct{}), Template: GetDefaultTemplate(), } // Get the path scope args := c.RemainingArgs() switch len(args) { case 0: md.PathScope = "/" case 1: md.PathScope = args[0] default: return mdconfigs, c.ArgErr() } // Load any other configuration parameters for c.NextBlock() { if err := loadParams(c, md); err != nil { return mdconfigs, err } } // If no extensions were specified, assume some defaults if len(md.Extensions) == 0 { md.Extensions[".md"] = struct{}{} md.Extensions[".markdown"] = struct{}{} md.Extensions[".mdown"] = struct{}{} } mdconfigs = append(mdconfigs, md) } return mdconfigs, nil }
// setup returns a new instance of a pprof handler. It accepts no arguments or options. func setup(c *caddy.Controller) error { found := false for c.Next() { if found { return c.Err("pprof can only be specified once") } if len(c.RemainingArgs()) != 0 { return c.ArgErr() } if c.NextBlock() { return c.ArgErr() } found = true } httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { return &Handler{Next: next, Mux: NewMux()} }) return nil }
func basicAuthParse(c *caddy.Controller) ([]Rule, error) { var rules []Rule cfg := httpserver.GetConfig(c) var err error for c.Next() { var rule Rule args := c.RemainingArgs() switch len(args) { case 2: rule.Username = args[0] if rule.Password, err = passwordMatcher(rule.Username, args[1], cfg.Root); err != nil { return rules, c.Errf("Get password matcher from %s: %v", c.Val(), err) } for c.NextBlock() { rule.Resources = append(rule.Resources, c.Val()) if c.NextArg() { return rules, c.Errf("Expecting only one resource per line (extra '%s')", c.Val()) } } case 3: rule.Resources = append(rule.Resources, args[0]) rule.Username = args[1] if rule.Password, err = passwordMatcher(rule.Username, args[2], cfg.Root); err != nil { return rules, c.Errf("Get password matcher from %s: %v", c.Val(), err) } default: return rules, c.ArgErr() } rules = append(rules, rule) } return rules, nil }
func logParse(c *caddy.Controller) ([]Rule, error) { var rules []Rule for c.Next() { args := c.RemainingArgs() var logRoller *httpserver.LogRoller if c.NextBlock() { if c.Val() == "rotate" { if c.NextArg() { if c.Val() == "{" { var err error logRoller, err = httpserver.ParseRoller(c) if err != nil { return nil, err } // This part doesn't allow having something after the rotate block if c.Next() { if c.Val() != "}" { return nil, c.ArgErr() } } } } } } if len(args) == 0 { // Nothing specified; use defaults rules = append(rules, Rule{ PathScope: "/", OutputFile: DefaultLogFilename, Format: DefaultLogFormat, Roller: logRoller, }) } else if len(args) == 1 { // Only an output file specified rules = append(rules, Rule{ PathScope: "/", OutputFile: args[0], Format: DefaultLogFormat, Roller: logRoller, }) } else { // Path scope, output file, and maybe a format specified format := DefaultLogFormat if len(args) > 2 { switch args[2] { case "{common}": format = CommonLogFormat case "{combined}": format = CombinedLogFormat default: format = args[2] } } rules = append(rules, Rule{ PathScope: args[0], OutputFile: args[1], Format: format, Roller: logRoller, }) } } return rules, nil }
func fastcgiParse(c *caddy.Controller) ([]Rule, error) { var rules []Rule for c.Next() { var rule Rule args := c.RemainingArgs() switch len(args) { case 0: return rules, c.ArgErr() case 1: rule.Path = "/" rule.Address = args[0] case 2: rule.Path = args[0] rule.Address = args[1] case 3: rule.Path = args[0] rule.Address = args[1] err := fastcgiPreset(args[2], &rule) if err != nil { return rules, c.Err("Invalid fastcgi rule preset '" + args[2] + "'") } } network, address := parseAddress(rule.Address) rule.dialer = basicDialer{network: network, address: address} for c.NextBlock() { switch c.Val() { case "ext": if !c.NextArg() { return rules, c.ArgErr() } rule.Ext = c.Val() case "split": if !c.NextArg() { return rules, c.ArgErr() } rule.SplitPath = c.Val() case "index": args := c.RemainingArgs() if len(args) == 0 { return rules, c.ArgErr() } rule.IndexFiles = args case "env": envArgs := c.RemainingArgs() if len(envArgs) < 2 { return rules, c.ArgErr() } rule.EnvVars = append(rule.EnvVars, [2]string{envArgs[0], envArgs[1]}) case "except": ignoredPaths := c.RemainingArgs() if len(ignoredPaths) == 0 { return rules, c.ArgErr() } rule.IgnoredSubPaths = ignoredPaths case "pool": if !c.NextArg() { return rules, c.ArgErr() } pool, err := strconv.Atoi(c.Val()) if err != nil { return rules, err } if pool >= 0 { rule.dialer = &persistentDialer{size: pool, network: network, address: address} } else { return rules, c.Errf("positive integer expected, found %d", pool) } } } rules = append(rules, rule) } return rules, nil }
func redirParse(c *caddy.Controller) ([]Rule, error) { var redirects []Rule cfg := httpserver.GetConfig(c.Key) // setRedirCode sets the redirect code for rule if it can, or returns an error setRedirCode := func(code string, rule *Rule) error { if code == "meta" { rule.Meta = true } else if codeNumber, ok := httpRedirs[code]; ok { rule.Code = codeNumber } else { return c.Errf("Invalid redirect code '%v'", code) } return nil } // checkAndSaveRule checks the rule for validity (except the redir code) // and saves it if it's valid, or returns an error. checkAndSaveRule := func(rule Rule) error { if rule.FromPath == rule.To { return c.Err("'from' and 'to' values of redirect rule cannot be the same") } for _, otherRule := range redirects { if otherRule.FromPath == rule.FromPath { return c.Errf("rule with duplicate 'from' value: %s -> %s", otherRule.FromPath, otherRule.To) } } redirects = append(redirects, rule) return nil } for c.Next() { args := c.RemainingArgs() var hadOptionalBlock bool for c.NextBlock() { hadOptionalBlock = true var rule Rule if cfg.TLS.Enabled { rule.FromScheme = "https" } else { rule.FromScheme = "http" } // Set initial redirect code // BUG: If the code is specified for a whole block and that code is invalid, // the line number will appear on the first line inside the block, even if that // line overwrites the block-level code with a valid redirect code. The program // still functions correctly, but the line number in the error reporting is // misleading to the user. if len(args) == 1 { err := setRedirCode(args[0], &rule) if err != nil { return redirects, err } } else { rule.Code = http.StatusMovedPermanently // default code } // RemainingArgs only gets the values after the current token, but in our // case we want to include the current token to get an accurate count. insideArgs := append([]string{c.Val()}, c.RemainingArgs()...) switch len(insideArgs) { case 1: // To specified (catch-all redirect) // Not sure why user is doing this in a table, as it causes all other redirects to be ignored. // As such, this feature remains undocumented. rule.FromPath = "/" rule.To = insideArgs[0] case 2: // From and To specified rule.FromPath = insideArgs[0] rule.To = insideArgs[1] case 3: // From, To, and Code specified rule.FromPath = insideArgs[0] rule.To = insideArgs[1] err := setRedirCode(insideArgs[2], &rule) if err != nil { return redirects, err } default: return redirects, c.ArgErr() } err := checkAndSaveRule(rule) if err != nil { return redirects, err } } if !hadOptionalBlock { var rule Rule if cfg.TLS.Enabled { rule.FromScheme = "https" } else { rule.FromScheme = "http" } rule.Code = http.StatusMovedPermanently // default switch len(args) { case 1: // To specified (catch-all redirect) rule.FromPath = "/" rule.To = args[0] case 2: // To and Code specified (catch-all redirect) rule.FromPath = "/" rule.To = args[0] err := setRedirCode(args[1], &rule) if err != nil { return redirects, err } case 3: // From, To, and Code specified rule.FromPath = args[0] rule.To = args[1] err := setRedirCode(args[2], &rule) if err != nil { return redirects, err } default: return redirects, c.ArgErr() } err := checkAndSaveRule(rule) if err != nil { return redirects, err } } } return redirects, nil }
// Parse parses the configuration set by the user so it can be // used by the middleware func parse(c *caddy.Controller, root string) (*Config, *filemanager.FileManager, error) { var ( cfg *Config fm *filemanager.FileManager err error tokens string ) cfg = new(Config) if cfg.Hugo, err = exec.LookPath("hugo"); err != nil { fmt.Println(HugoNotFound) return cfg, fm, errors.New(HugoNotFound) } for c.Next() { cfg.Public = strings.Replace(root, "./", "", -1) cfg.BaseURL = "/admin" cfg.Root = "./" args := c.RemainingArgs() if len(args) >= 1 { cfg.Root = args[0] cfg.Root = strings.TrimSuffix(cfg.Root, "/") cfg.Root += "/" } if len(args) >= 2 { cfg.BaseURL = args[1] cfg.BaseURL = strings.TrimPrefix(cfg.BaseURL, "/") cfg.BaseURL = "/" + cfg.BaseURL } for c.NextBlock() { switch c.Val() { case "flag": if !c.NextArg() { return cfg, &filemanager.FileManager{}, c.ArgErr() } values := strings.Split(c.Val(), " ") if len(values) == 0 { return cfg, fm, errors.New("Not enough arguments for 'flag' option.") } value := "true" if len(values) > 1 { value = values[1] } cfg.Args = append(cfg.Args, "--"+values[0]+"="+value) case "before_publish": if cfg.BeforePublish, err = config.CommandRunner(c); err != nil { return cfg, &filemanager.FileManager{}, err } case "after_publish": if cfg.AfterPublish, err = config.CommandRunner(c); err != nil { return cfg, &filemanager.FileManager{}, err } default: line := "\n\t" + c.Val() if c.NextArg() { line += " " + c.Val() } tokens += line } } } tokens = "filemanager " + cfg.BaseURL + " {\n\tshow " + cfg.Root + tokens tokens += "\n}" fmConfig, err := config.Parse(caddy.NewTestController("http", tokens)) if err != nil { return cfg, fm, err } fm = &filemanager.FileManager{Configs: fmConfig} fm.Configs[0].HugoEnabled = true format := getFrontMatter(cfg) cfg.WebDavURL = fm.Configs[0].WebDavURL for _, user := range fm.Configs[0].Users { user.FrontMatter = format } if err != nil { return cfg, fm, err } return cfg, fm, nil }
// ipfilterParseSingle parses a single ipfilter {} block from the caddy config. func ipfilterParseSingle(config *IPFConfig, c *caddy.Controller) (IPPath, error) { var cPath IPPath // Get PathScopes cPath.PathScopes = c.RemainingArgs() if len(cPath.PathScopes) == 0 { return cPath, c.ArgErr() } // Sort PathScopes by length (the longest is always the most specific so should be tested first) sort.Sort(sort.Reverse(ByLength(cPath.PathScopes))) for c.NextBlock() { value := c.Val() switch value { case "rule": if !c.NextArg() { return cPath, c.ArgErr() } rule := c.Val() if rule == "block" { cPath.IsBlock = true } else if rule != "allow" { return cPath, c.Err("ipfilter: Rule should be 'block' or 'allow'") } case "database": if !c.NextArg() { return cPath, c.ArgErr() } // Check if a database has already been opened if config.DBHandler != nil { return cPath, c.Err("ipfilter: A database is already opened") } database := c.Val() // Open the database. var err error config.DBHandler, err = maxminddb.Open(database) if err != nil { return cPath, c.Err("ipfilter: Can't open database: " + database) } case "blockpage": if !c.NextArg() { return cPath, c.ArgErr() } // check if blockpage exists. blockpage := c.Val() if _, err := os.Stat(blockpage); os.IsNotExist(err) { return cPath, c.Err("ipfilter: No such file: " + blockpage) } cPath.BlockPage = blockpage case "country": cPath.CountryCodes = c.RemainingArgs() if len(cPath.CountryCodes) == 0 { return cPath, c.ArgErr() } case "ip": ips := c.RemainingArgs() if len(ips) == 0 { return cPath, c.ArgErr() } for _, ip := range ips { ipRange, err := parseIP(ip) if err != nil { return cPath, c.Err("ipfilter: " + err.Error()) } cPath.Nets = append(cPath.Nets, ipRange...) } case "strict": cPath.Strict = true } } return cPath, nil }
func rewriteParse(c *caddy.Controller) ([]Rule, error) { var simpleRules []Rule var regexpRules []Rule for c.Next() { var rule Rule var err error var base = "/" var pattern, to string var status int var ext []string args := c.RemainingArgs() var matcher httpserver.RequestMatcher switch len(args) { case 1: base = args[0] fallthrough case 0: // Integrate request matcher for 'if' conditions. matcher, err = httpserver.SetupIfMatcher(c.Dispenser) if err != nil { return nil, err } block: for c.NextBlock() { switch c.Val() { case "r", "regexp": if !c.NextArg() { return nil, c.ArgErr() } pattern = c.Val() case "to": args1 := c.RemainingArgs() if len(args1) == 0 { return nil, c.ArgErr() } to = strings.Join(args1, " ") case "ext": args1 := c.RemainingArgs() if len(args1) == 0 { return nil, c.ArgErr() } ext = args1 case "status": if !c.NextArg() { return nil, c.ArgErr() } status, _ = strconv.Atoi(c.Val()) if status < 200 || (status > 299 && status < 400) || status > 499 { return nil, c.Err("status must be 2xx or 4xx") } default: if httpserver.IfMatcherKeyword(c.Val()) { continue block } return nil, c.ArgErr() } } // ensure to or status is specified if to == "" && status == 0 { return nil, c.ArgErr() } if rule, err = NewComplexRule(base, pattern, to, status, ext, matcher); err != nil { return nil, err } regexpRules = append(regexpRules, rule) // the only unhandled case is 2 and above default: rule = NewSimpleRule(args[0], strings.Join(args[1:], " ")) simpleRules = append(simpleRules, rule) } } // put simple rules in front to avoid regexp computation for them return append(simpleRules, regexpRules...), nil }
func parse(c *caddy.Controller) (mc *config, _ error) { // This parses the following config blocks mc = newConfig() for c.Next() { args := c.RemainingArgs() switch len(args) { case 1: mc.endpoint = args[0] } for c.NextBlock() { var err error switch c.Val() { case "publickeyAttachmentFileName": if !c.NextArg() { return nil, c.ArgErr() } mc.pgpAttachmentName = c.Val() case "maillog": if !c.NextArg() { return nil, c.ArgErr() } if mc.maillog.IsNil() { mc.maillog = maillog.New(c.Val(), "") } else { mc.maillog.MailDir = c.Val() } case "errorlog": if !c.NextArg() { return nil, c.ArgErr() } if mc.maillog.IsNil() { mc.maillog = maillog.New("", c.Val()) } else { mc.maillog.ErrDir = c.Val() } case "to": if !c.NextArg() { return nil, c.ArgErr() } mc.to, err = splitEmailAddresses(c.Val()) if err != nil { return nil, err } case "cc": if !c.NextArg() { return nil, c.ArgErr() } mc.cc, err = splitEmailAddresses(c.Val()) if err != nil { return nil, err } case "bcc": if !c.NextArg() { return nil, c.ArgErr() } mc.bcc, err = splitEmailAddresses(c.Val()) if err != nil { return nil, err } case "subject": if !c.NextArg() { return nil, c.ArgErr() } mc.subject = c.Val() case "body": if !c.NextArg() { return nil, c.ArgErr() } mc.body = c.Val() case "username": if !c.NextArg() { return nil, c.ArgErr() } mc.username = c.Val() case "password": if !c.NextArg() { return nil, c.ArgErr() } mc.password = c.Val() case "host": if !c.NextArg() { return nil, c.ArgErr() } mc.host = c.Val() case "port": if !c.NextArg() { return nil, c.ArgErr() } mc.portRaw = c.Val() case "ratelimit_interval": if !c.NextArg() { return nil, c.ArgErr() } var rli time.Duration rli, err = time.ParseDuration(c.Val()) if err != nil { return nil, err } if rli.Nanoseconds() != 0 { mc.rateLimitInterval = rli } case "ratelimit_capacity": if !c.NextArg() { return nil, c.ArgErr() } var rlc int64 rlc, err = strconv.ParseInt(c.Val(), 10, 64) if err != nil { return nil, err } if rlc > 0 { mc.rateLimitCapacity = rlc } default: anyKey := c.Val() if isValidEmail(anyKey) { if !c.NextArg() { return nil, c.ArgErr() } pgpPublicKey := c.Val() mc.pgpEmailKeys = append(mc.pgpEmailKeys, anyKey, pgpPublicKey) } } } } return }
func fastcgiParse(c *caddy.Controller) ([]Rule, error) { var rules []Rule for c.Next() { var rule Rule args := c.RemainingArgs() if len(args) < 2 || len(args) > 3 { return rules, c.ArgErr() } rule.Path = args[0] upstreams := []string{args[1]} if len(args) == 3 { if err := fastcgiPreset(args[2], &rule); err != nil { return rules, err } } var dialers []dialer var poolSize = -1 for c.NextBlock() { switch c.Val() { case "ext": if !c.NextArg() { return rules, c.ArgErr() } rule.Ext = c.Val() case "split": if !c.NextArg() { return rules, c.ArgErr() } rule.SplitPath = c.Val() case "index": args := c.RemainingArgs() if len(args) == 0 { return rules, c.ArgErr() } rule.IndexFiles = args case "upstream": args := c.RemainingArgs() if len(args) != 1 { return rules, c.ArgErr() } upstreams = append(upstreams, args[0]) case "env": envArgs := c.RemainingArgs() if len(envArgs) < 2 { return rules, c.ArgErr() } rule.EnvVars = append(rule.EnvVars, [2]string{envArgs[0], envArgs[1]}) case "except": ignoredPaths := c.RemainingArgs() if len(ignoredPaths) == 0 { return rules, c.ArgErr() } rule.IgnoredSubPaths = ignoredPaths case "pool": if !c.NextArg() { return rules, c.ArgErr() } pool, err := strconv.Atoi(c.Val()) if err != nil { return rules, err } if pool >= 0 { poolSize = pool } else { return rules, c.Errf("positive integer expected, found %d", pool) } } } for _, rawAddress := range upstreams { network, address := parseAddress(rawAddress) if poolSize >= 0 { dialers = append(dialers, &persistentDialer{size: poolSize, network: network, address: address}) } else { dialers = append(dialers, basicDialer{network: network, address: address}) } } rule.dialer = &loadBalancingDialer{dialers: dialers} rule.Address = strings.Join(upstreams, ",") rules = append(rules, rule) } return rules, nil }
func errorsParse(c *caddy.Controller) (*ErrorHandler, error) { // Very important that we make a pointer because the startup // function that opens the log file must have access to the // same instance of the handler, not a copy. handler := &ErrorHandler{ErrorPages: make(map[int]string)} cfg := httpserver.GetConfig(c) optionalBlock := func() (bool, error) { var hadBlock bool for c.NextBlock() { hadBlock = true what := c.Val() if !c.NextArg() { return hadBlock, c.ArgErr() } where := c.Val() if what == "log" { if where == "visible" { handler.Debug = true } else { handler.LogFile = where if c.NextArg() { if c.Val() == "{" { c.IncrNest() logRoller, err := httpserver.ParseRoller(c) if err != nil { return hadBlock, err } handler.LogRoller = logRoller } } } } else { // Error page; ensure it exists where = filepath.Join(cfg.Root, where) f, err := os.Open(where) if err != nil { log.Printf("[WARNING] Unable to open error page '%s': %v", where, err) } f.Close() if what == "*" { if handler.GenericErrorPage != "" { return hadBlock, c.Errf("Duplicate status code entry: %s", what) } handler.GenericErrorPage = where } else { whatInt, err := strconv.Atoi(what) if err != nil { return hadBlock, c.Err("Expecting a numeric status code or '*', got '" + what + "'") } if _, exists := handler.ErrorPages[whatInt]; exists { return hadBlock, c.Errf("Duplicate status code entry: %s", what) } handler.ErrorPages[whatInt] = where } } } return hadBlock, nil } for c.Next() { // weird hack to avoid having the handler values overwritten. if c.Val() == "}" { continue } // Configuration may be in a block hadBlock, err := optionalBlock() if err != nil { return handler, err } // Otherwise, the only argument would be an error log file name or 'visible' if !hadBlock { if c.NextArg() { if c.Val() == "visible" { handler.Debug = true } else { handler.LogFile = c.Val() } } } } return handler, nil }
func parse(c *caddy.Controller) (Git, error) { var git Git config := httpserver.GetConfig(c) for c.Next() { repo := &Repo{Branch: "master", Interval: DefaultInterval, Path: config.Root} args := c.RemainingArgs() clonePath := func(s string) string { if filepath.IsAbs(s) { return filepath.Clean(s) } return filepath.Join(config.Root, s) } switch len(args) { case 2: repo.Path = clonePath(args[1]) fallthrough case 1: u, err := validateURL(args[0]) if err != nil { return nil, err } repo.URL = u } for c.NextBlock() { switch c.Val() { case "repo": if !c.NextArg() { return nil, c.ArgErr() } u, err := validateURL(c.Val()) if err != nil { return nil, err } repo.URL = u case "path": if !c.NextArg() { return nil, c.ArgErr() } repo.Path = clonePath(c.Val()) case "branch": if !c.NextArg() { return nil, c.ArgErr() } repo.Branch = c.Val() case "key": if !c.NextArg() { return nil, c.ArgErr() } repo.KeyPath = c.Val() case "interval": if !c.NextArg() { return nil, c.ArgErr() } t, _ := strconv.Atoi(c.Val()) if t > 0 { repo.Interval = time.Duration(t) * time.Second } case "args": repo.Args = c.RemainingArgs() case "hook": if !c.NextArg() { return nil, c.ArgErr() } repo.Hook.URL = c.Val() // optional secret for validation if c.NextArg() { repo.Hook.Secret = c.Val() } case "hook_type": if !c.NextArg() { return nil, c.ArgErr() } t := c.Val() if _, ok := handlers[t]; !ok { return nil, c.Errf("invalid hook type %v", t) } repo.Hook.Type = t case "then": if !c.NextArg() { return nil, c.ArgErr() } command := c.Val() args := c.RemainingArgs() repo.Then = append(repo.Then, NewThen(command, args...)) case "then_long": if !c.NextArg() { return nil, c.ArgErr() } command := c.Val() args := c.RemainingArgs() repo.Then = append(repo.Then, NewLongThen(command, args...)) default: return nil, c.ArgErr() } } // if repo is not specified, return error if repo.URL == "" { return nil, c.ArgErr() } // if private key is not specified, convert repository URL to https // to avoid ssh authentication // else validate git URL // Note: private key support not yet available on Windows var err error if repo.KeyPath == "" { repo.URL, repo.Host, err = sanitizeHTTP(repo.URL) } else { repo.URL, repo.Host, err = sanitizeSSH(repo.URL) // TODO add Windows support for private repos if runtime.GOOS == "windows" { return nil, fmt.Errorf("private repository not yet supported on Windows") } } if err != nil { return nil, err } // validate git requirements if err = Init(); err != nil { return nil, err } // prepare repo for use if err = repo.Prepare(); err != nil { return nil, err } git = append(git, repo) } return git, nil }
func fastcgiParse(c *caddy.Controller) ([]Rule, error) { var rules []Rule for c.Next() { var rule Rule args := c.RemainingArgs() switch len(args) { case 0: return rules, c.ArgErr() case 1: rule.Path = "/" rule.Address = args[0] case 2: rule.Path = args[0] rule.Address = args[1] case 3: rule.Path = args[0] rule.Address = args[1] err := fastcgiPreset(args[2], &rule) if err != nil { return rules, c.Err("Invalid fastcgi rule preset '" + args[2] + "'") } } for c.NextBlock() { switch c.Val() { case "ext": if !c.NextArg() { return rules, c.ArgErr() } rule.Ext = c.Val() case "split": if !c.NextArg() { return rules, c.ArgErr() } rule.SplitPath = c.Val() case "index": args := c.RemainingArgs() if len(args) == 0 { return rules, c.ArgErr() } rule.IndexFiles = args case "env": envArgs := c.RemainingArgs() if len(envArgs) < 2 { return rules, c.ArgErr() } rule.EnvVars = append(rule.EnvVars, [2]string{envArgs[0], envArgs[1]}) case "except": ignoredPaths := c.RemainingArgs() if len(ignoredPaths) == 0 { return rules, c.ArgErr() } rule.IgnoredSubPaths = ignoredPaths } } rules = append(rules, rule) } return rules, nil }
func headersParse(c *caddy.Controller) ([]Rule, error) { var rules []Rule for c.NextLine() { var head Rule head.Headers = http.Header{} var isNewPattern bool if !c.NextArg() { return rules, c.ArgErr() } pattern := c.Val() // See if we already have a definition for this Path pattern... for _, h := range rules { if h.Path == pattern { head = h break } } // ...otherwise, this is a new pattern if head.Path == "" { head.Path = pattern isNewPattern = true } for c.NextBlock() { // A block of headers was opened... name := c.Val() value := "" if c.NextArg() { value = c.Val() } head.Headers.Add(name, value) } if c.NextArg() { // ... or single header was defined as an argument instead. name := c.Val() value := c.Val() if c.NextArg() { value = c.Val() } head.Headers.Add(name, value) } if isNewPattern { rules = append(rules, head) } else { for i := 0; i < len(rules); i++ { if rules[i].Path == pattern { rules[i] = head break } } } } return rules, nil }
// ParseSearchConfig controller information to create a IndexSearch config func ParseSearchConfig(c *caddy.Controller, cnf *httpserver.SiteConfig) (*Config, error) { hosthash := md5.New() hosthash.Write([]byte(cnf.Host())) conf := &Config{ HostName: hex.EncodeToString(hosthash.Sum(nil)), Engine: `bleve`, IndexDirectory: `/tmp/caddyIndex`, IncludePaths: []*regexp.Regexp{}, ExcludePaths: []*regexp.Regexp{}, Endpoint: `/search`, SiteRoot: cnf.Root, Expire: 60 * time.Second, Template: nil, } _, err := os.Stat(conf.SiteRoot) if err != nil { return nil, c.Err("[search]: `invalid root directory`") } incPaths := []string{} excPaths := []string{} for c.Next() { args := c.RemainingArgs() switch len(args) { case 2: conf.Endpoint = args[1] fallthrough case 1: incPaths = append(incPaths, args[0]) } for c.NextBlock() { switch c.Val() { case "engine": if !c.NextArg() { return nil, c.ArgErr() } conf.Engine = c.Val() case "+path": if !c.NextArg() { return nil, c.ArgErr() } incPaths = append(incPaths, c.Val()) incPaths = append(incPaths, c.RemainingArgs()...) case "-path": if !c.NextArg() { return nil, c.ArgErr() } excPaths = append(excPaths, c.Val()) excPaths = append(excPaths, c.RemainingArgs()...) case "endpoint": if !c.NextArg() { return nil, c.ArgErr() } conf.Endpoint = c.Val() case "expire": if !c.NextArg() { return nil, c.ArgErr() } exp, err := strconv.Atoi(c.Val()) if err != nil { return nil, err } conf.Expire = time.Duration(exp) * time.Second case "datadir": if !c.NextArg() { return nil, c.ArgErr() } conf.IndexDirectory = c.Val() case "template": var err error if c.NextArg() { conf.Template, err = template.ParseFiles(filepath.Join(conf.SiteRoot, c.Val())) if err != nil { return nil, err } } } } } if len(incPaths) == 0 { incPaths = append(incPaths, "^/") } conf.IncludePaths = ConvertToRegExp(incPaths) conf.ExcludePaths = ConvertToRegExp(excPaths) dir := conf.IndexDirectory if _, err := os.Stat(dir); os.IsNotExist(err) { if err := os.MkdirAll(dir, os.ModePerm); err != nil { return nil, c.Err("[search] Given 'datadir' not a valid path.") } } if conf.Template == nil { var err error conf.Template, err = template.New("search-results").Parse(defaultTemplate) if err != nil { return nil, err } } return conf, nil }
func webSocketParse(c *caddy.Controller) ([]Config, error) { var websocks []Config var respawn bool optionalBlock := func() (hadBlock bool, err error) { for c.NextBlock() { hadBlock = true if c.Val() == "respawn" { respawn = true } else { return true, c.Err("Expected websocket configuration parameter in block") } } return } for c.Next() { var val, path, command string // Path or command; not sure which yet if !c.NextArg() { return nil, c.ArgErr() } val = c.Val() // Extra configuration may be in a block hadBlock, err := optionalBlock() if err != nil { return nil, err } if !hadBlock { // The next argument on this line will be the command or an open curly brace if c.NextArg() { path = val command = c.Val() } else { path = "/" command = val } // Okay, check again for optional block _, err = optionalBlock() if err != nil { return nil, err } } // Split command into the actual command and its arguments cmd, args, err := caddy.SplitCommandAndArgs(command) if err != nil { return nil, err } websocks = append(websocks, Config{ Path: path, Command: cmd, Arguments: args, Respawn: respawn, // TODO: This isn't used currently }) } return websocks, nil }
// setupTLS sets up the TLS configuration and installs certificates that // are specified by the user in the config file. All the automatic HTTPS // stuff comes later outside of this function. func setupTLS(c *caddy.Controller) error { configGetter, ok := configGetters[c.ServerType()] if !ok { return fmt.Errorf("no caddytls.ConfigGetter for %s server type; must call RegisterConfigGetter", c.ServerType()) } config := configGetter(c) if config == nil { return fmt.Errorf("no caddytls.Config to set up for %s", c.Key) } config.Enabled = true for c.Next() { var certificateFile, keyFile, loadDir, maxCerts string args := c.RemainingArgs() switch len(args) { case 1: // even if the email is one of the special values below, // it is still necessary for future analysis that we store // that value in the ACMEEmail field. config.ACMEEmail = args[0] // user can force-disable managed TLS this way if args[0] == "off" { config.Enabled = false return nil } // user might want a temporary, in-memory, self-signed cert if args[0] == "self_signed" { config.SelfSigned = true } case 2: certificateFile = args[0] keyFile = args[1] config.Manual = true } // Optional block with extra parameters var hadBlock bool for c.NextBlock() { hadBlock = true switch c.Val() { case "key_type": arg := c.RemainingArgs() value, ok := supportedKeyTypes[strings.ToUpper(arg[0])] if !ok { return c.Errf("Wrong key type name or key type not supported: '%s'", c.Val()) } config.KeyType = value case "protocols": args := c.RemainingArgs() if len(args) == 1 { value, ok := supportedProtocols[strings.ToLower(args[0])] if !ok { return c.Errf("Wrong protocol name or protocol not supported: '%s'", args[0]) } config.ProtocolMinVersion, config.ProtocolMaxVersion = value, value } else { value, ok := supportedProtocols[strings.ToLower(args[0])] if !ok { return c.Errf("Wrong protocol name or protocol not supported: '%s'", args[0]) } config.ProtocolMinVersion = value value, ok = supportedProtocols[strings.ToLower(args[1])] if !ok { return c.Errf("Wrong protocol name or protocol not supported: '%s'", args[1]) } config.ProtocolMaxVersion = value if config.ProtocolMinVersion > config.ProtocolMaxVersion { return c.Errf("Minimum protocol version cannot be higher than maximum (reverse the order)") } } case "ciphers": for c.NextArg() { value, ok := supportedCiphersMap[strings.ToUpper(c.Val())] if !ok { return c.Errf("Wrong cipher name or cipher not supported: '%s'", c.Val()) } config.Ciphers = append(config.Ciphers, value) } case "clients": clientCertList := c.RemainingArgs() if len(clientCertList) == 0 { return c.ArgErr() } listStart, mustProvideCA := 1, true switch clientCertList[0] { case "request": config.ClientAuth = tls.RequestClientCert mustProvideCA = false case "require": config.ClientAuth = tls.RequireAnyClientCert mustProvideCA = false case "verify_if_given": config.ClientAuth = tls.VerifyClientCertIfGiven default: config.ClientAuth = tls.RequireAndVerifyClientCert listStart = 0 } if mustProvideCA && len(clientCertList) <= listStart { return c.ArgErr() } config.ClientCerts = clientCertList[listStart:] case "load": c.Args(&loadDir) config.Manual = true case "max_certs": c.Args(&maxCerts) config.OnDemand = true case "dns": args := c.RemainingArgs() if len(args) != 1 { return c.ArgErr() } dnsProvName := args[0] if _, ok := dnsProviders[dnsProvName]; !ok { return c.Errf("Unsupported DNS provider '%s'", args[0]) } config.DNSProvider = args[0] case "storage": args := c.RemainingArgs() if len(args) != 1 { return c.ArgErr() } storageProvName := args[0] if _, ok := storageProviders[storageProvName]; !ok { return c.Errf("Unsupported Storage provider '%s'", args[0]) } config.StorageProvider = args[0] default: return c.Errf("Unknown keyword '%s'", c.Val()) } } // tls requires at least one argument if a block is not opened if len(args) == 0 && !hadBlock { return c.ArgErr() } // set certificate limit if on-demand TLS is enabled if maxCerts != "" { maxCertsNum, err := strconv.Atoi(maxCerts) if err != nil || maxCertsNum < 1 { return c.Err("max_certs must be a positive integer") } config.OnDemandState.MaxObtain = int32(maxCertsNum) } // don't try to load certificates unless we're supposed to if !config.Enabled || !config.Manual { continue } // load a single certificate and key, if specified if certificateFile != "" && keyFile != "" { err := cacheUnmanagedCertificatePEMFile(certificateFile, keyFile) if err != nil { return c.Errf("Unable to load certificate and key files for '%s': %v", c.Key, err) } log.Printf("[INFO] Successfully loaded TLS assets from %s and %s", certificateFile, keyFile) } // load a directory of certificates, if specified if loadDir != "" { err := loadCertsInDir(c, loadDir) if err != nil { return err } } } SetDefaultTLSParams(config) // generate self-signed cert if needed if config.SelfSigned { err := makeSelfSignedCert(config) if err != nil { return fmt.Errorf("self-signed: %v", err) } } return nil }
func parseRules(c *caddy.Controller) ([]*corsRule, error) { rules := []*corsRule{} for c.Next() { rule := &corsRule{Path: "/", Conf: cors.Default()} args := c.RemainingArgs() anyOrigins := false if len(args) > 0 { rule.Path = args[0] } for i := 1; i < len(args); i++ { if !anyOrigins { rule.Conf.AllowedOrigins = nil } rule.Conf.AllowedOrigins = append(rule.Conf.AllowedOrigins, strings.Split(args[i], ",")...) anyOrigins = true } for c.NextBlock() { switch c.Val() { case "origin": if !anyOrigins { rule.Conf.AllowedOrigins = nil } args := c.RemainingArgs() for _, domain := range args { rule.Conf.AllowedOrigins = append(rule.Conf.AllowedOrigins, strings.Split(domain, ",")...) } anyOrigins = true case "methods": if arg, err := singleArg(c, "methods"); err != nil { return nil, err } else { rule.Conf.AllowedMethods = arg } case "allow_credentials": if arg, err := singleArg(c, "allow_credentials"); err != nil { return nil, err } else { var b bool if arg == "true" { b = true } else if arg != "false" { return nil, c.Errf("allow_credentials must be true or false.") } rule.Conf.AllowCredentials = &b } case "max_age": if arg, err := singleArg(c, "max_age"); err != nil { return nil, err } else { i, err := strconv.Atoi(arg) if err != nil { return nil, c.Err("max_age must be valid int") } rule.Conf.MaxAge = i } case "allowed_headers": if arg, err := singleArg(c, "allowed_headers"); err != nil { return nil, err } else { rule.Conf.AllowedHeaders = arg } case "exposed_headers": if arg, err := singleArg(c, "exposed_headers"); err != nil { return nil, err } else { rule.Conf.ExposedHeaders = arg } default: return nil, c.Errf("Unknown cors config item: %s", c.Val()) } } rules = append(rules, rule) } return rules, nil }
func gzipParse(c *caddy.Controller) ([]Config, error) { var configs []Config for c.Next() { config := Config{} // Request Filters pathFilter := PathFilter{IgnoredPaths: make(Set)} extFilter := ExtFilter{Exts: make(Set)} // Response Filters lengthFilter := LengthFilter(0) // No extra args expected if len(c.RemainingArgs()) > 0 { return configs, c.ArgErr() } for c.NextBlock() { switch c.Val() { case "ext": exts := c.RemainingArgs() if len(exts) == 0 { return configs, c.ArgErr() } for _, e := range exts { if !strings.HasPrefix(e, ".") && e != ExtWildCard && e != "" { return configs, fmt.Errorf(`gzip: invalid extension "%v" (must start with dot)`, e) } extFilter.Exts.Add(e) } case "not": paths := c.RemainingArgs() if len(paths) == 0 { return configs, c.ArgErr() } for _, p := range paths { if p == "/" { return configs, fmt.Errorf(`gzip: cannot exclude path "/" - remove directive entirely instead`) } if !strings.HasPrefix(p, "/") { return configs, fmt.Errorf(`gzip: invalid path "%v" (must start with /)`, p) } pathFilter.IgnoredPaths.Add(p) } case "level": if !c.NextArg() { return configs, c.ArgErr() } level, _ := strconv.Atoi(c.Val()) config.Level = level case "min_length": if !c.NextArg() { return configs, c.ArgErr() } length, err := strconv.ParseInt(c.Val(), 10, 64) if err != nil { return configs, err } else if length == 0 { return configs, fmt.Errorf(`gzip: min_length must be greater than 0`) } lengthFilter = LengthFilter(length) default: return configs, c.ArgErr() } } // Request Filters config.RequestFilters = []RequestFilter{} // If ignored paths are specified, put in front to filter with path first if len(pathFilter.IgnoredPaths) > 0 { config.RequestFilters = []RequestFilter{pathFilter} } // Then, if extensions are specified, use those to filter. // Otherwise, use default extensions filter. if len(extFilter.Exts) > 0 { config.RequestFilters = append(config.RequestFilters, extFilter) } else { config.RequestFilters = append(config.RequestFilters, DefaultExtFilter()) } // Response Filters // If min_length is specified, use it. if int64(lengthFilter) != 0 { config.ResponseFilters = append(config.ResponseFilters, lengthFilter) } configs = append(configs, config) } return configs, nil }
func redirParse(c *caddy.Controller) ([]Rule, error) { var redirects []Rule cfg := httpserver.GetConfig(c) initRule := func(rule *Rule, defaultCode string, args []string) error { if cfg.TLS.Enabled { rule.FromScheme = "https" } else { rule.FromScheme = "http" } var ( from = "/" to string code = defaultCode ) switch len(args) { case 1: // To specified (catch-all redirect) // Not sure why user is doing this in a table, as it causes all other redirects to be ignored. // As such, this feature remains undocumented. to = args[0] case 2: // From and To specified from = args[0] to = args[1] case 3: // From, To, and Code specified from = args[0] to = args[1] code = args[2] default: return c.ArgErr() } rule.FromPath = from rule.To = to if code == "meta" { rule.Meta = true code = defaultCode } if codeNumber, ok := httpRedirs[code]; ok { rule.Code = codeNumber } else { return c.Errf("Invalid redirect code '%v'", code) } return nil } // checkAndSaveRule checks the rule for validity (except the redir code) // and saves it if it's valid, or returns an error. checkAndSaveRule := func(rule Rule) error { if rule.FromPath == rule.To { return c.Err("'from' and 'to' values of redirect rule cannot be the same") } for _, otherRule := range redirects { if otherRule.FromPath == rule.FromPath { return c.Errf("rule with duplicate 'from' value: %s -> %s", otherRule.FromPath, otherRule.To) } } redirects = append(redirects, rule) return nil } const initDefaultCode = "301" for c.Next() { args := c.RemainingArgs() matcher, err := httpserver.SetupIfMatcher(c) if err != nil { return nil, err } var hadOptionalBlock bool for c.NextBlock() { if httpserver.IfMatcherKeyword(c) { continue } hadOptionalBlock = true rule := Rule{ RequestMatcher: matcher, } defaultCode := initDefaultCode // Set initial redirect code if len(args) == 1 { defaultCode = args[0] } // RemainingArgs only gets the values after the current token, but in our // case we want to include the current token to get an accurate count. insideArgs := append([]string{c.Val()}, c.RemainingArgs()...) err := initRule(&rule, defaultCode, insideArgs) if err != nil { return redirects, err } err = checkAndSaveRule(rule) if err != nil { return redirects, err } } if !hadOptionalBlock { rule := Rule{ RequestMatcher: matcher, } err := initRule(&rule, initDefaultCode, args) if err != nil { return redirects, err } err = checkAndSaveRule(rule) if err != nil { return redirects, err } } } return redirects, nil }
func rewriteParse(c *caddy.Controller) ([]httpserver.HandlerConfig, error) { var rules []httpserver.HandlerConfig for c.Next() { var rule Rule var err error var base = "/" var pattern, to string var ext []string args := c.RemainingArgs() var matcher httpserver.RequestMatcher switch len(args) { case 1: base = args[0] fallthrough case 0: // Integrate request matcher for 'if' conditions. matcher, err = httpserver.SetupIfMatcher(c) if err != nil { return nil, err } for c.NextBlock() { if httpserver.IfMatcherKeyword(c) { continue } switch c.Val() { case "r", "regexp": if !c.NextArg() { return nil, c.ArgErr() } pattern = c.Val() case "to": args1 := c.RemainingArgs() if len(args1) == 0 { return nil, c.ArgErr() } to = strings.Join(args1, " ") case "ext": args1 := c.RemainingArgs() if len(args1) == 0 { return nil, c.ArgErr() } ext = args1 default: return nil, c.ArgErr() } } // ensure to is specified if to == "" { return nil, c.ArgErr() } if rule, err = NewComplexRule(base, pattern, to, ext, matcher); err != nil { return nil, err } rules = append(rules, rule) // the only unhandled case is 2 and above default: rule = NewSimpleRule(args[0], strings.Join(args[1:], " ")) rules = append(rules, rule) } } return rules, nil }
func rewriteParse(c *caddy.Controller) ([]Rule, error) { var simpleRules []Rule var regexpRules []Rule for c.Next() { var rule Rule var err error var base = "/" var pattern, to string var status int var ext []string args := c.RemainingArgs() var ifs []If switch len(args) { case 1: base = args[0] fallthrough case 0: for c.NextBlock() { switch c.Val() { case "r", "regexp": if !c.NextArg() { return nil, c.ArgErr() } pattern = c.Val() case "to": args1 := c.RemainingArgs() if len(args1) == 0 { return nil, c.ArgErr() } to = strings.Join(args1, " ") case "ext": args1 := c.RemainingArgs() if len(args1) == 0 { return nil, c.ArgErr() } ext = args1 case "if": args1 := c.RemainingArgs() if len(args1) != 3 { return nil, c.ArgErr() } ifCond, err := NewIf(args1[0], args1[1], args1[2]) if err != nil { return nil, err } ifs = append(ifs, ifCond) case "status": if !c.NextArg() { return nil, c.ArgErr() } status, _ = strconv.Atoi(c.Val()) if status < 200 || (status > 299 && status < 400) || status > 499 { return nil, c.Err("status must be 2xx or 4xx") } default: return nil, c.ArgErr() } } // ensure to or status is specified if to == "" && status == 0 { return nil, c.ArgErr() } if rule, err = NewComplexRule(base, pattern, to, status, ext, ifs); err != nil { return nil, err } regexpRules = append(regexpRules, rule) // the only unhandled case is 2 and above default: rule = NewSimpleRule(args[0], strings.Join(args[1:], " ")) simpleRules = append(simpleRules, rule) } } // put simple rules in front to avoid regexp computation for them return append(simpleRules, regexpRules...), nil }