From bec9b9a3f7828a88455c767fff1da91ae5b9a123 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Fri, 24 Jul 2015 12:46:03 -0600 Subject: [PATCH] redir: Redirect tables Open a redir block to bulk-specify a bunch of redirects that share a status code --- config/setup/redir.go | 165 ++++++++++++++++++++++++++++++------------ 1 file changed, 120 insertions(+), 45 deletions(-) diff --git a/config/setup/redir.go b/config/setup/redir.go index b83a8e475..d7d79ff3a 100644 --- a/config/setup/redir.go +++ b/config/setup/redir.go @@ -22,49 +22,124 @@ func Redir(c *Controller) (middleware.Middleware, error) { func redirParse(c *Controller) ([]redirect.Rule, error) { var redirects []redirect.Rule - for c.Next() { - var rule redirect.Rule - args := c.RemainingArgs() + // setRedirCode sets the redirect code for rule if it can, or returns an error + setRedirCode := func(code string, rule *redirect.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 + } - // Always set the default Code, then overwrite - rule.Code = http.StatusMovedPermanently - - switch len(args) { - case 1: - // To specified - rule.From = "/" - rule.To = args[0] - case 2: - // To and Code specified - rule.From = "/" - rule.To = args[0] - if "meta" == args[1] { - rule.Meta = true - } else if code, ok := httpRedirs[args[1]]; !ok { - return redirects, c.Err("Invalid redirect code '" + args[1] + "'") - } else { - rule.Code = code - } - case 3: - // From, To, and Code specified - rule.From = args[0] - rule.To = args[1] - if "meta" == args[2] { - rule.Meta = true - } else if code, ok := httpRedirs[args[2]]; !ok { - return redirects, c.Err("Invalid redirect code '" + args[2] + "'") - } else { - rule.Code = code - } - default: - return redirects, c.ArgErr() + // checkAndSaveRule checks the rule for validity (except the redir code) + // and saves it if it's valid, or returns an error. + checkAndSaveRule := func(rule redirect.Rule) error { + if rule.From == rule.To { + return c.Err("'from' and 'to' values of redirect rule cannot be the same") } - if rule.From == rule.To { - return redirects, c.Err("Redirect rule cannot allow From and To arguments to be the same.") + for _, otherRule := range redirects { + if otherRule.From == rule.From { + return c.Errf("rule with duplicate 'from' value: %s -> %s", otherRule.From, otherRule.To) + } } redirects = append(redirects, rule) + return nil + } + + for c.Next() { + args := c.RemainingArgs() + + var hadOptionalBlock bool + for c.NextBlock() { + hadOptionalBlock = true + + var rule redirect.Rule + + // 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) + rule.From = "/" + rule.To = insideArgs[0] + case 2: + // From and To specified + rule.From = insideArgs[0] + rule.To = insideArgs[1] + case 3: + // From, To, and Code specified + rule.From = 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 redirect.Rule + rule.Code = http.StatusMovedPermanently // default + + switch len(args) { + case 1: + // To specified (catch-all redirect) + rule.From = "/" + rule.To = args[0] + case 2: + // To and Code specified (catch-all redirect) + rule.From = "/" + rule.To = args[0] + err := setRedirCode(args[1], &rule) + if err != nil { + return redirects, err + } + case 3: + // From, To, and Code specified + rule.From = 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 @@ -72,12 +147,12 @@ func redirParse(c *Controller) ([]redirect.Rule, error) { // httpRedirs is a list of supported HTTP redirect codes. var httpRedirs = map[string]int{ - "300": 300, - "301": 301, - "302": 302, - "303": 303, - "304": 304, - "305": 305, - "307": 307, - "308": 308, + "300": 300, // Multiple Choices + "301": 301, // Moved Permanently + "302": 302, // Found (NOT CORRECT for "Temporary Redirect", see 307) + "303": 303, // See Other + "304": 304, // Not Modified + "305": 305, // Use Proxy + "307": 307, // Temporary Redirect + "308": 308, // Permanent Redirect }