Commit ed4148f2 authored by Karthic Rao's avatar Karthic Rao

Complete test coverage for replacer for Go

parents 679668e3 f8e2cc80
...@@ -2,6 +2,7 @@ language: go ...@@ -2,6 +2,7 @@ language: go
go: go:
- 1.4 - 1.4
- 1.5
- tip - tip
script: go test ./... script: go test ./...
...@@ -43,6 +43,7 @@ By default, Caddy serves the current directory at [localhost:2015](http://localh ...@@ -43,6 +43,7 @@ By default, Caddy serves the current directory at [localhost:2015](http://localh
Caddy accepts some flags from the command line. Run `caddy -h` to view the help for flags. You can also pipe a Caddyfile into the caddy command. Caddy accepts some flags from the command line. Run `caddy -h` to view the help for flags. You can also pipe a Caddyfile into the caddy command.
**Running as root:** We advise against this; use setcap instead, like so: `setcap cap_net_bind_service=+ep ./caddy` This will allow you to listen on ports below 1024 (like 80 and 443).
#### Docker Container #### Docker Container
......
...@@ -119,6 +119,11 @@ func (d *Dispenser) NextBlock() bool { ...@@ -119,6 +119,11 @@ func (d *Dispenser) NextBlock() bool {
return true return true
} }
func (d *Dispenser) IncrNest() {
d.nesting++
return
}
// Val gets the text of the current token. If there is no token // Val gets the text of the current token. If there is no token
// loaded, it returns empty string. // loaded, it returns empty string.
func (d *Dispenser) Val() string { func (d *Dispenser) Val() string {
......
package setup package setup
import ( import (
"strings"
"github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/basicauth" "github.com/mholt/caddy/middleware/basicauth"
) )
// BasicAuth configures a new BasicAuth middleware instance. // BasicAuth configures a new BasicAuth middleware instance.
func BasicAuth(c *Controller) (middleware.Middleware, error) { func BasicAuth(c *Controller) (middleware.Middleware, error) {
root := c.Root
rules, err := basicAuthParse(c) rules, err := basicAuthParse(c)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -16,6 +20,7 @@ func BasicAuth(c *Controller) (middleware.Middleware, error) { ...@@ -16,6 +20,7 @@ func BasicAuth(c *Controller) (middleware.Middleware, error) {
return func(next middleware.Handler) middleware.Handler { return func(next middleware.Handler) middleware.Handler {
basic.Next = next basic.Next = next
basic.SiteRoot = root
return basic return basic
}, nil }, nil
} }
...@@ -23,6 +28,7 @@ func BasicAuth(c *Controller) (middleware.Middleware, error) { ...@@ -23,6 +28,7 @@ func BasicAuth(c *Controller) (middleware.Middleware, error) {
func basicAuthParse(c *Controller) ([]basicauth.Rule, error) { func basicAuthParse(c *Controller) ([]basicauth.Rule, error) {
var rules []basicauth.Rule var rules []basicauth.Rule
var err error
for c.Next() { for c.Next() {
var rule basicauth.Rule var rule basicauth.Rule
...@@ -31,7 +37,10 @@ func basicAuthParse(c *Controller) ([]basicauth.Rule, error) { ...@@ -31,7 +37,10 @@ func basicAuthParse(c *Controller) ([]basicauth.Rule, error) {
switch len(args) { switch len(args) {
case 2: case 2:
rule.Username = args[0] rule.Username = args[0]
rule.Password = args[1] if rule.Password, err = passwordMatcher(rule.Username, args[1], c.Root); err != nil {
return rules, c.Errf("Get password matcher from %s: %v", c.Val(), err)
}
for c.NextBlock() { for c.NextBlock() {
rule.Resources = append(rule.Resources, c.Val()) rule.Resources = append(rule.Resources, c.Val())
if c.NextArg() { if c.NextArg() {
...@@ -41,7 +50,9 @@ func basicAuthParse(c *Controller) ([]basicauth.Rule, error) { ...@@ -41,7 +50,9 @@ func basicAuthParse(c *Controller) ([]basicauth.Rule, error) {
case 3: case 3:
rule.Resources = append(rule.Resources, args[0]) rule.Resources = append(rule.Resources, args[0])
rule.Username = args[1] rule.Username = args[1]
rule.Password = args[2] if rule.Password, err = passwordMatcher(rule.Username, args[2], c.Root); err != nil {
return rules, c.Errf("Get password matcher from %s: %v", c.Val(), err)
}
default: default:
return rules, c.ArgErr() return rules, c.ArgErr()
} }
...@@ -51,3 +62,11 @@ func basicAuthParse(c *Controller) ([]basicauth.Rule, error) { ...@@ -51,3 +62,11 @@ func basicAuthParse(c *Controller) ([]basicauth.Rule, error) {
return rules, nil return rules, nil
} }
func passwordMatcher(username, passw, siteRoot string) (basicauth.PasswordMatcher, error) {
if !strings.HasPrefix(passw, "htpasswd=") {
return basicauth.PlainMatcher(passw), nil
}
return basicauth.GetHtpasswdMatcher(passw[9:], username, siteRoot)
}
...@@ -2,6 +2,9 @@ package setup ...@@ -2,6 +2,9 @@ package setup
import ( import (
"fmt" "fmt"
"io/ioutil"
"os"
"strings"
"testing" "testing"
"github.com/mholt/caddy/middleware/basicauth" "github.com/mholt/caddy/middleware/basicauth"
...@@ -30,35 +33,57 @@ func TestBasicAuth(t *testing.T) { ...@@ -30,35 +33,57 @@ func TestBasicAuth(t *testing.T) {
} }
func TestBasicAuthParse(t *testing.T) { func TestBasicAuthParse(t *testing.T) {
htpasswdPasswd := "IedFOuGmTpT8"
htpasswdFile := `sha1:{SHA}dcAUljwz99qFjYR0YLTXx0RqLww=
md5:$apr1$l42y8rex$pOA2VJ0x/0TwaFeAF9nX61`
var skipHtpassword bool
htfh, err := ioutil.TempFile("", "basicauth-")
if err != nil {
t.Logf("Error creating temp file (%v), will skip htpassword test")
skipHtpassword = true
} else {
if _, err = htfh.Write([]byte(htpasswdFile)); err != nil {
t.Fatalf("write htpasswd file %q: %v", htfh.Name(), err)
}
htfh.Close()
defer os.Remove(htfh.Name())
}
tests := []struct { tests := []struct {
input string input string
shouldErr bool shouldErr bool
password string
expected []basicauth.Rule expected []basicauth.Rule
}{ }{
{`basicauth user pwd`, false, []basicauth.Rule{ {`basicauth user pwd`, false, "pwd", []basicauth.Rule{
{Username: "user", Password: "pwd"}, {Username: "user"},
}}, }},
{`basicauth user pwd { {`basicauth user pwd {
}`, false, []basicauth.Rule{ }`, false, "pwd", []basicauth.Rule{
{Username: "user", Password: "pwd"}, {Username: "user"},
}}, }},
{`basicauth user pwd { {`basicauth user pwd {
/resource1 /resource1
/resource2 /resource2
}`, false, []basicauth.Rule{ }`, false, "pwd", []basicauth.Rule{
{Username: "user", Password: "pwd", Resources: []string{"/resource1", "/resource2"}}, {Username: "user", Resources: []string{"/resource1", "/resource2"}},
}}, }},
{`basicauth /resource user pwd`, false, []basicauth.Rule{ {`basicauth /resource user pwd`, false, "pwd", []basicauth.Rule{
{Username: "user", Password: "pwd", Resources: []string{"/resource"}}, {Username: "user", Resources: []string{"/resource"}},
}}, }},
{`basicauth /res1 user1 pwd1 {`basicauth /res1 user1 pwd1
basicauth /res2 user2 pwd2`, false, []basicauth.Rule{ basicauth /res2 user2 pwd2`, false, "pwd", []basicauth.Rule{
{Username: "user1", Password: "pwd1", Resources: []string{"/res1"}}, {Username: "user1", Resources: []string{"/res1"}},
{Username: "user2", Password: "pwd2", Resources: []string{"/res2"}}, {Username: "user2", Resources: []string{"/res2"}},
}},
{`basicauth user`, true, "", []basicauth.Rule{}},
{`basicauth`, true, "", []basicauth.Rule{}},
{`basicauth /resource user pwd asdf`, true, "", []basicauth.Rule{}},
{`basicauth sha1 htpasswd=` + htfh.Name(), false, htpasswdPasswd, []basicauth.Rule{
{Username: "sha1"},
}}, }},
{`basicauth user`, true, []basicauth.Rule{}},
{`basicauth`, true, []basicauth.Rule{}},
{`basicauth /resource user pwd asdf`, true, []basicauth.Rule{}},
} }
for i, test := range tests { for i, test := range tests {
...@@ -84,9 +109,16 @@ func TestBasicAuthParse(t *testing.T) { ...@@ -84,9 +109,16 @@ func TestBasicAuthParse(t *testing.T) {
i, j, expectedRule.Username, actualRule.Username) i, j, expectedRule.Username, actualRule.Username)
} }
if actualRule.Password != expectedRule.Password { if strings.Contains(test.input, "htpasswd=") && skipHtpassword {
continue
}
pwd := test.password
if len(actual) > 1 {
pwd = fmt.Sprintf("%s%d", pwd, j+1)
}
if !actualRule.Password(pwd) || actualRule.Password(test.password+"!") {
t.Errorf("Test %d, rule %d: Expected password '%s', got '%s'", t.Errorf("Test %d, rule %d: Expected password '%s', got '%s'",
i, j, expectedRule.Password, actualRule.Password) i, j, test.password, actualRule.Password)
} }
expectedRes := fmt.Sprintf("%v", expectedRule.Resources) expectedRes := fmt.Sprintf("%v", expectedRule.Resources)
......
...@@ -23,25 +23,35 @@ func Errors(c *Controller) (middleware.Middleware, error) { ...@@ -23,25 +23,35 @@ func Errors(c *Controller) (middleware.Middleware, error) {
// Open the log file for writing when the server starts // Open the log file for writing when the server starts
c.Startup = append(c.Startup, func() error { c.Startup = append(c.Startup, func() error {
var err error var err error
var file io.Writer var writer io.Writer
if handler.LogFile == "stdout" { if handler.LogFile == "stdout" {
file = os.Stdout writer = os.Stdout
} else if handler.LogFile == "stderr" { } else if handler.LogFile == "stderr" {
file = os.Stderr writer = os.Stderr
} else if handler.LogFile == "syslog" { } else if handler.LogFile == "syslog" {
file, err = gsyslog.NewLogger(gsyslog.LOG_ERR, "LOCAL0", "caddy") writer, err = gsyslog.NewLogger(gsyslog.LOG_ERR, "LOCAL0", "caddy")
if err != nil { if err != nil {
return err return err
} }
} else if handler.LogFile != "" { } else if handler.LogFile != "" {
var file *os.File
file, err = os.OpenFile(handler.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) file, err = os.OpenFile(handler.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil { if err != nil {
return err return err
} }
if handler.LogRoller != nil {
file.Close()
handler.LogRoller.Filename = handler.LogFile
writer = handler.LogRoller.GetLogWriter()
} else {
writer = file
}
} }
handler.Log = log.New(file, "", 0) handler.Log = log.New(writer, "", 0)
return nil return nil
}) })
...@@ -71,6 +81,16 @@ func errorsParse(c *Controller) (*errors.ErrorHandler, error) { ...@@ -71,6 +81,16 @@ func errorsParse(c *Controller) (*errors.ErrorHandler, error) {
if what == "log" { if what == "log" {
handler.LogFile = where handler.LogFile = where
if c.NextArg() {
if c.Val() == "{" {
c.IncrNest()
logRoller, err := parseRoller(c)
if err != nil {
return hadBlock, err
}
handler.LogRoller = logRoller
}
}
} else { } else {
// Error page; ensure it exists // Error page; ensure it exists
where = path.Join(c.Root, where) where = path.Join(c.Root, where)
...@@ -91,6 +111,10 @@ func errorsParse(c *Controller) (*errors.ErrorHandler, error) { ...@@ -91,6 +111,10 @@ func errorsParse(c *Controller) (*errors.ErrorHandler, error) {
} }
for c.Next() { for c.Next() {
// weird hack to avoid having the handler values overwritten.
if c.Val() == "}" {
continue
}
// Configuration may be in a block // Configuration may be in a block
hadBlock, err := optionalBlock() hadBlock, err := optionalBlock()
if err != nil { if err != nil {
......
package setup
import (
"testing"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/errors"
)
func TestErrors(t *testing.T) {
c := NewTestController(`errors`)
mid, err := Errors(c)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
if mid == nil {
t.Fatal("Expected middleware, was nil instead")
}
handler := mid(EmptyNext)
myHandler, ok := handler.(*errors.ErrorHandler)
if !ok {
t.Fatalf("Expected handler to be type ErrorHandler, got: %#v", handler)
}
if myHandler.LogFile != errors.DefaultLogFilename {
t.Errorf("Expected %s as the default LogFile", errors.DefaultLogFilename)
}
if myHandler.LogRoller != nil {
t.Errorf("Expected LogRoller to be nil, got: %v", *myHandler.LogRoller)
}
if !SameNext(myHandler.Next, EmptyNext) {
t.Error("'Next' field of handler was not set properly")
}
}
func TestErrorsParse(t *testing.T) {
tests := []struct {
inputErrorsRules string
shouldErr bool
expectedErrorHandler errors.ErrorHandler
}{
{`errors`, false, errors.ErrorHandler{
LogFile: errors.DefaultLogFilename,
}},
{`errors errors.txt`, false, errors.ErrorHandler{
LogFile: "errors.txt",
}},
{`errors { log errors.txt
404 404.html
500 500.html
}`, false, errors.ErrorHandler{
LogFile: "errors.txt",
ErrorPages: map[int]string{
404: "404.html",
500: "500.html",
},
}},
{`errors { log errors.txt { size 2 age 10 keep 3 } }`, false, errors.ErrorHandler{
LogFile: "errors.txt",
LogRoller: &middleware.LogRoller{
MaxSize: 2,
MaxAge: 10,
MaxBackups: 3,
LocalTime: true,
},
}},
{`errors { log errors.txt {
size 3
age 11
keep 5
}
404 404.html
503 503.html
}`, false, errors.ErrorHandler{
LogFile: "errors.txt",
ErrorPages: map[int]string{
404: "404.html",
503: "503.html",
},
LogRoller: &middleware.LogRoller{
MaxSize: 3,
MaxAge: 11,
MaxBackups: 5,
LocalTime: true,
},
}},
}
for i, test := range tests {
c := NewTestController(test.inputErrorsRules)
actualErrorsRule, err := errorsParse(c)
if err == nil && test.shouldErr {
t.Errorf("Test %d didn't error, but it should have", i)
} else if err != nil && !test.shouldErr {
t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
}
if actualErrorsRule.LogFile != test.expectedErrorHandler.LogFile {
t.Errorf("Test %d expected LogFile to be %s , but got %s",
i, test.expectedErrorHandler.LogFile, actualErrorsRule.LogFile)
}
if actualErrorsRule.LogRoller != nil && test.expectedErrorHandler.LogRoller == nil || actualErrorsRule.LogRoller == nil && test.expectedErrorHandler.LogRoller != nil {
t.Fatalf("Test %d expected LogRoller to be %v, but got %v",
i, test.expectedErrorHandler.LogRoller, actualErrorsRule.LogRoller)
}
if len(actualErrorsRule.ErrorPages) != len(test.expectedErrorHandler.ErrorPages) {
t.Fatalf("Test %d expected %d no of Error pages, but got %d ",
i, len(test.expectedErrorHandler.ErrorPages), len(actualErrorsRule.ErrorPages))
}
if actualErrorsRule.LogRoller != nil && test.expectedErrorHandler.LogRoller != nil {
if actualErrorsRule.LogRoller.Filename != test.expectedErrorHandler.LogRoller.Filename {
t.Fatalf("Test %d expected LogRoller Filename to be %s, but got %s",
i, test.expectedErrorHandler.LogRoller.Filename, actualErrorsRule.LogRoller.Filename)
}
if actualErrorsRule.LogRoller.MaxAge != test.expectedErrorHandler.LogRoller.MaxAge {
t.Fatalf("Test %d expected LogRoller MaxAge to be %d, but got %d",
i, test.expectedErrorHandler.LogRoller.MaxAge, actualErrorsRule.LogRoller.MaxAge)
}
if actualErrorsRule.LogRoller.MaxBackups != test.expectedErrorHandler.LogRoller.MaxBackups {
t.Fatalf("Test %d expected LogRoller MaxBackups to be %d, but got %d",
i, test.expectedErrorHandler.LogRoller.MaxBackups, actualErrorsRule.LogRoller.MaxBackups)
}
if actualErrorsRule.LogRoller.MaxSize != test.expectedErrorHandler.LogRoller.MaxSize {
t.Fatalf("Test %d expected LogRoller MaxSize to be %d, but got %d",
i, test.expectedErrorHandler.LogRoller.MaxSize, actualErrorsRule.LogRoller.MaxSize)
}
if actualErrorsRule.LogRoller.LocalTime != test.expectedErrorHandler.LogRoller.LocalTime {
t.Fatalf("Test %d expected LogRoller LocalTime to be %t, but got %t",
i, test.expectedErrorHandler.LogRoller.LocalTime, actualErrorsRule.LogRoller.LocalTime)
}
}
}
}
...@@ -22,25 +22,33 @@ func Log(c *Controller) (middleware.Middleware, error) { ...@@ -22,25 +22,33 @@ func Log(c *Controller) (middleware.Middleware, error) {
c.Startup = append(c.Startup, func() error { c.Startup = append(c.Startup, func() error {
for i := 0; i < len(rules); i++ { for i := 0; i < len(rules); i++ {
var err error var err error
var file io.Writer var writer io.Writer
if rules[i].OutputFile == "stdout" { if rules[i].OutputFile == "stdout" {
file = os.Stdout writer = os.Stdout
} else if rules[i].OutputFile == "stderr" { } else if rules[i].OutputFile == "stderr" {
file = os.Stderr writer = os.Stderr
} else if rules[i].OutputFile == "syslog" { } else if rules[i].OutputFile == "syslog" {
file, err = gsyslog.NewLogger(gsyslog.LOG_INFO, "LOCAL0", "caddy") writer, err = gsyslog.NewLogger(gsyslog.LOG_INFO, "LOCAL0", "caddy")
if err != nil { if err != nil {
return err return err
} }
} else { } else {
var file *os.File
file, err = os.OpenFile(rules[i].OutputFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) file, err = os.OpenFile(rules[i].OutputFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil { if err != nil {
return err return err
} }
if rules[i].Roller != nil {
file.Close()
rules[i].Roller.Filename = rules[i].OutputFile
writer = rules[i].Roller.GetLogWriter()
} else {
writer = file
}
} }
rules[i].Log = log.New(file, "", 0) rules[i].Log = log.New(writer, "", 0)
} }
return nil return nil
...@@ -57,12 +65,33 @@ func logParse(c *Controller) ([]caddylog.Rule, error) { ...@@ -57,12 +65,33 @@ func logParse(c *Controller) ([]caddylog.Rule, error) {
for c.Next() { for c.Next() {
args := c.RemainingArgs() args := c.RemainingArgs()
var logRoller *middleware.LogRoller
if c.NextBlock() {
if c.Val() == "rotate" {
if c.NextArg() {
if c.Val() == "{" {
var err error
logRoller, err = 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 { if len(args) == 0 {
// Nothing specified; use defaults // Nothing specified; use defaults
rules = append(rules, caddylog.Rule{ rules = append(rules, caddylog.Rule{
PathScope: "/", PathScope: "/",
OutputFile: caddylog.DefaultLogFilename, OutputFile: caddylog.DefaultLogFilename,
Format: caddylog.DefaultLogFormat, Format: caddylog.DefaultLogFormat,
Roller: logRoller,
}) })
} else if len(args) == 1 { } else if len(args) == 1 {
// Only an output file specified // Only an output file specified
...@@ -70,6 +99,7 @@ func logParse(c *Controller) ([]caddylog.Rule, error) { ...@@ -70,6 +99,7 @@ func logParse(c *Controller) ([]caddylog.Rule, error) {
PathScope: "/", PathScope: "/",
OutputFile: args[0], OutputFile: args[0],
Format: caddylog.DefaultLogFormat, Format: caddylog.DefaultLogFormat,
Roller: logRoller,
}) })
} else { } else {
// Path scope, output file, and maybe a format specified // Path scope, output file, and maybe a format specified
...@@ -91,6 +121,7 @@ func logParse(c *Controller) ([]caddylog.Rule, error) { ...@@ -91,6 +121,7 @@ func logParse(c *Controller) ([]caddylog.Rule, error) {
PathScope: args[0], PathScope: args[0],
OutputFile: args[1], OutputFile: args[1],
Format: format, Format: format,
Roller: logRoller,
}) })
} }
} }
......
...@@ -3,6 +3,7 @@ package setup ...@@ -3,6 +3,7 @@ package setup
import ( import (
"testing" "testing"
"github.com/mholt/caddy/middleware"
caddylog "github.com/mholt/caddy/middleware/log" caddylog "github.com/mholt/caddy/middleware/log"
) )
...@@ -36,6 +37,9 @@ func TestLog(t *testing.T) { ...@@ -36,6 +37,9 @@ func TestLog(t *testing.T) {
if myHandler.Rules[0].Format != caddylog.DefaultLogFormat { if myHandler.Rules[0].Format != caddylog.DefaultLogFormat {
t.Errorf("Expected %s as the default Log Format", caddylog.DefaultLogFormat) t.Errorf("Expected %s as the default Log Format", caddylog.DefaultLogFormat)
} }
if myHandler.Rules[0].Roller != nil {
t.Errorf("Expected Roller to be nil, got: %v", *myHandler.Rules[0].Roller)
}
if !SameNext(myHandler.Next, EmptyNext) { if !SameNext(myHandler.Next, EmptyNext) {
t.Error("'Next' field of handler was not set properly") t.Error("'Next' field of handler was not set properly")
} }
...@@ -78,7 +82,7 @@ func TestLogParse(t *testing.T) { ...@@ -78,7 +82,7 @@ func TestLogParse(t *testing.T) {
OutputFile: "accesslog.txt", OutputFile: "accesslog.txt",
Format: caddylog.CombinedLogFormat, Format: caddylog.CombinedLogFormat,
}}}, }}},
{`log /api1 log.txt {`log /api1 log.txt
log /api2 accesslog.txt {combined}`, false, []caddylog.Rule{{ log /api2 accesslog.txt {combined}`, false, []caddylog.Rule{{
PathScope: "/api1", PathScope: "/api1",
OutputFile: "log.txt", OutputFile: "log.txt",
...@@ -98,6 +102,17 @@ func TestLogParse(t *testing.T) { ...@@ -98,6 +102,17 @@ func TestLogParse(t *testing.T) {
OutputFile: "log.txt", OutputFile: "log.txt",
Format: "{when}", Format: "{when}",
}}}, }}},
{`log access.log { rotate { size 2 age 10 keep 3 } }`, false, []caddylog.Rule{{
PathScope: "/",
OutputFile: "access.log",
Format: caddylog.DefaultLogFormat,
Roller: &middleware.LogRoller{
MaxSize: 2,
MaxAge: 10,
MaxBackups: 3,
LocalTime: true,
},
}}},
} }
for i, test := range tests { for i, test := range tests {
c := NewTestController(test.inputLogRules) c := NewTestController(test.inputLogRules)
...@@ -128,6 +143,32 @@ func TestLogParse(t *testing.T) { ...@@ -128,6 +143,32 @@ func TestLogParse(t *testing.T) {
t.Errorf("Test %d expected %dth LogRule Format to be %s , but got %s", t.Errorf("Test %d expected %dth LogRule Format to be %s , but got %s",
i, j, test.expectedLogRules[j].Format, actualLogRule.Format) i, j, test.expectedLogRules[j].Format, actualLogRule.Format)
} }
if actualLogRule.Roller != nil && test.expectedLogRules[j].Roller == nil || actualLogRule.Roller == nil && test.expectedLogRules[j].Roller != nil {
t.Fatalf("Test %d expected %dth LogRule Roller to be %v, but got %v",
i, j, test.expectedLogRules[j].Roller, actualLogRule.Roller)
}
if actualLogRule.Roller != nil && test.expectedLogRules[j].Roller != nil {
if actualLogRule.Roller.Filename != test.expectedLogRules[j].Roller.Filename {
t.Fatalf("Test %d expected %dth LogRule Roller Filename to be %s, but got %s",
i, j, test.expectedLogRules[j].Roller.Filename, actualLogRule.Roller.Filename)
}
if actualLogRule.Roller.MaxAge != test.expectedLogRules[j].Roller.MaxAge {
t.Fatalf("Test %d expected %dth LogRule Roller MaxAge to be %d, but got %d",
i, j, test.expectedLogRules[j].Roller.MaxAge, actualLogRule.Roller.MaxAge)
}
if actualLogRule.Roller.MaxBackups != test.expectedLogRules[j].Roller.MaxBackups {
t.Fatalf("Test %d expected %dth LogRule Roller MaxBackups to be %d, but got %d",
i, j, test.expectedLogRules[j].Roller.MaxBackups, actualLogRule.Roller.MaxBackups)
}
if actualLogRule.Roller.MaxSize != test.expectedLogRules[j].Roller.MaxSize {
t.Fatalf("Test %d expected %dth LogRule Roller MaxSize to be %d, but got %d",
i, j, test.expectedLogRules[j].Roller.MaxSize, actualLogRule.Roller.MaxSize)
}
if actualLogRule.Roller.LocalTime != test.expectedLogRules[j].Roller.LocalTime {
t.Fatalf("Test %d expected %dth LogRule Roller LocalTime to be %t, but got %t",
i, j, test.expectedLogRules[j].Roller.LocalTime, actualLogRule.Roller.LocalTime)
}
}
} }
} }
......
...@@ -111,7 +111,7 @@ func TestMarkdownStaticGen(t *testing.T) { ...@@ -111,7 +111,7 @@ func TestMarkdownStaticGen(t *testing.T) {
fp := filepath.Join(c.Root, markdown.DefaultStaticDir) fp := filepath.Join(c.Root, markdown.DefaultStaticDir)
if err = os.RemoveAll(fp); err != nil { if err = os.RemoveAll(fp); err != nil {
t.Errorf("Error while removing the generated static files: ", err) t.Errorf("Error while removing the generated static files: %v", err)
} }
} }
......
package setup
import (
"strconv"
"github.com/mholt/caddy/middleware"
)
func parseRoller(c *Controller) (*middleware.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 &middleware.LogRoller{
MaxSize: size,
MaxAge: age,
MaxBackups: keep,
LocalTime: true,
}, nil
}
CHANGES CHANGES
<master>
- basicauth: Support for legacy htpasswd files
- browse: JSON response with file listing given Accept header
0.7.5 (August 5, 2015) 0.7.5 (August 5, 2015)
- core: All listeners bind to 0.0.0.0 unless 'bind' directive is used - core: All listeners bind to 0.0.0.0 unless 'bind' directive is used
- fastcgi: Set HTTPS env variable if connection is secure - fastcgi: Set HTTPS env variable if connection is secure
......
...@@ -124,7 +124,7 @@ func isLocalhost(s string) bool { ...@@ -124,7 +124,7 @@ func isLocalhost(s string) bool {
// loadConfigs loads configuration from a file or stdin (piped). // loadConfigs loads configuration from a file or stdin (piped).
// The configurations are grouped by bind address. // The configurations are grouped by bind address.
// Configuration is obtained from one of three sources, tried // Configuration is obtained from one of three sources, tried
// in this order: 1. -conf flag, 2. stdin, 3. Caddyfile. // in this order: 1. -conf flag, 2. stdin, 3. command line argument 4. Caddyfile.
// If none of those are available, a default configuration is // If none of those are available, a default configuration is
// loaded. // loaded.
func loadConfigs() (config.Group, error) { func loadConfigs() (config.Group, error) {
...@@ -155,6 +155,12 @@ func loadConfigs() (config.Group, error) { ...@@ -155,6 +155,12 @@ func loadConfigs() (config.Group, error) {
} }
} }
// Command line Arg
if flag.NArg() > 0 {
confBody := ":" + config.DefaultPort + "\n" + strings.Join(flag.Args(), "\n")
return config.Load("args", bytes.NewBufferString(confBody))
}
// Caddyfile // Caddyfile
file, err := os.Open(config.DefaultConfigFile) file, err := os.Open(config.DefaultConfigFile)
if err != nil { if err != nil {
......
...@@ -2,9 +2,17 @@ ...@@ -2,9 +2,17 @@
package basicauth package basicauth
import ( import (
"bufio"
"crypto/subtle" "crypto/subtle"
"fmt"
"io"
"net/http" "net/http"
"os"
"path/filepath"
"strings"
"sync"
"github.com/jimstudt/http-authentication/basic"
"github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware"
) )
...@@ -14,8 +22,9 @@ import ( ...@@ -14,8 +22,9 @@ import (
// security of HTTP Basic Auth is disputed. Use discretion when deciding // security of HTTP Basic Auth is disputed. Use discretion when deciding
// what to protect with BasicAuth. // what to protect with BasicAuth.
type BasicAuth struct { type BasicAuth struct {
Next middleware.Handler Next middleware.Handler
Rules []Rule SiteRoot string
Rules []Rule
} }
// ServeHTTP implements the middleware.Handler interface. // ServeHTTP implements the middleware.Handler interface.
...@@ -37,7 +46,8 @@ func (a BasicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error ...@@ -37,7 +46,8 @@ func (a BasicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
// Check credentials // Check credentials
if !ok || if !ok ||
username != rule.Username || username != rule.Username ||
subtle.ConstantTimeCompare([]byte(password), []byte(rule.Password)) != 1 { !rule.Password(password) {
//subtle.ConstantTimeCompare([]byte(password), []byte(rule.Password)) != 1 {
continue continue
} }
...@@ -64,6 +74,71 @@ func (a BasicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error ...@@ -64,6 +74,71 @@ func (a BasicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
// file or directory paths. // file or directory paths.
type Rule struct { type Rule struct {
Username string Username string
Password string Password func(string) bool
Resources []string Resources []string
} }
type PasswordMatcher func(pw string) bool
var (
htpasswords map[string]map[string]PasswordMatcher
htpasswordsMu sync.Mutex
)
func GetHtpasswdMatcher(filename, username, siteRoot string) (PasswordMatcher, error) {
filename = filepath.Join(siteRoot, filename)
htpasswordsMu.Lock()
if htpasswords == nil {
htpasswords = make(map[string]map[string]PasswordMatcher)
}
pm := htpasswords[filename]
if pm == nil {
fh, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("open %q: %v", filename, err)
}
defer fh.Close()
pm = make(map[string]PasswordMatcher)
if err = parseHtpasswd(pm, fh); err != nil {
return nil, fmt.Errorf("parsing htpasswd %q: %v", fh.Name(), err)
}
htpasswords[filename] = pm
}
htpasswordsMu.Unlock()
if pm[username] == nil {
return nil, fmt.Errorf("username %q not found in %q", username, filename)
}
return pm[username], nil
}
func parseHtpasswd(pm map[string]PasswordMatcher, r io.Reader) error {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.IndexByte(line, '#') == 0 {
continue
}
i := strings.IndexByte(line, ':')
if i <= 0 {
return fmt.Errorf("malformed line, no color: %q", line)
}
user, encoded := line[:i], line[i+1:]
for _, p := range basic.DefaultSystems {
matcher, err := p(encoded)
if err != nil {
return err
}
if matcher != nil {
pm[user] = matcher.MatchesPassword
break
}
}
}
return scanner.Err()
}
func PlainMatcher(passw string) PasswordMatcher {
return func(pw string) bool {
return subtle.ConstantTimeCompare([]byte(pw), []byte(passw)) == 1
}
}
...@@ -3,8 +3,10 @@ package basicauth ...@@ -3,8 +3,10 @@ package basicauth
import ( import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"testing" "testing"
"github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware"
...@@ -15,7 +17,7 @@ func TestBasicAuth(t *testing.T) { ...@@ -15,7 +17,7 @@ func TestBasicAuth(t *testing.T) {
rw := BasicAuth{ rw := BasicAuth{
Next: middleware.HandlerFunc(contentHandler), Next: middleware.HandlerFunc(contentHandler),
Rules: []Rule{ Rules: []Rule{
{Username: "test", Password: "ttest", Resources: []string{"/testing"}}, {Username: "test", Password: PlainMatcher("ttest"), Resources: []string{"/testing"}},
}, },
} }
...@@ -66,8 +68,8 @@ func TestMultipleOverlappingRules(t *testing.T) { ...@@ -66,8 +68,8 @@ func TestMultipleOverlappingRules(t *testing.T) {
rw := BasicAuth{ rw := BasicAuth{
Next: middleware.HandlerFunc(contentHandler), Next: middleware.HandlerFunc(contentHandler),
Rules: []Rule{ Rules: []Rule{
{Username: "t", Password: "p1", Resources: []string{"/t"}}, {Username: "t", Password: PlainMatcher("p1"), Resources: []string{"/t"}},
{Username: "t1", Password: "p2", Resources: []string{"/t/t"}}, {Username: "t1", Password: PlainMatcher("p2"), Resources: []string{"/t/t"}},
}, },
} }
...@@ -111,3 +113,31 @@ func contentHandler(w http.ResponseWriter, r *http.Request) (int, error) { ...@@ -111,3 +113,31 @@ func contentHandler(w http.ResponseWriter, r *http.Request) (int, error) {
fmt.Fprintf(w, r.URL.String()) fmt.Fprintf(w, r.URL.String())
return http.StatusOK, nil return http.StatusOK, nil
} }
func TestHtpasswd(t *testing.T) {
htpasswdPasswd := "IedFOuGmTpT8"
htpasswdFile := `sha1:{SHA}dcAUljwz99qFjYR0YLTXx0RqLww=
md5:$apr1$l42y8rex$pOA2VJ0x/0TwaFeAF9nX61`
htfh, err := ioutil.TempFile("", "basicauth-")
if err != nil {
t.Skipf("Error creating temp file (%v), will skip htpassword test")
return
}
if _, err = htfh.Write([]byte(htpasswdFile)); err != nil {
t.Fatalf("write htpasswd file %q: %v", htfh.Name(), err)
}
htfh.Close()
defer os.Remove(htfh.Name())
for i, username := range []string{"sha1", "md5"} {
rule := Rule{Username: username, Resources: []string{"/testing"}}
if rule.Password, err = GetHtpasswdMatcher(htfh.Name(), rule.Username, "/"); err != nil {
t.Fatalf("GetHtpasswdMatcher(%q, %q): %v", htfh.Name(), rule.Username, err)
}
t.Logf("%d. username=%q password=%v", i, rule.Username, rule.Password)
if !rule.Password(htpasswdPasswd) || rule.Password(htpasswdPasswd+"!") {
t.Errorf("%d (%s) password does not match.", i, rule.Username)
}
}
}
...@@ -236,7 +236,6 @@ func TestBrowseJson(t *testing.T) { ...@@ -236,7 +236,6 @@ func TestBrowseJson(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Unable to Marshal the listing ") t.Fatalf("Unable to Marshal the listing ")
} }
expectedJsonString := string(marsh) expectedJsonString := string(marsh)
if actualJsonResponseString != expectedJsonString { if actualJsonResponseString != expectedJsonString {
t.Errorf("Json response string doesnt match the expected Json response ") t.Errorf("Json response string doesnt match the expected Json response ")
......
...@@ -20,6 +20,7 @@ type ErrorHandler struct { ...@@ -20,6 +20,7 @@ type ErrorHandler struct {
ErrorPages map[int]string // map of status code to filename ErrorPages map[int]string // map of status code to filename
LogFile string LogFile string
Log *log.Logger Log *log.Logger
LogRoller *middleware.LogRoller
} }
func (h ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { func (h ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
......
...@@ -47,6 +47,7 @@ type Rule struct { ...@@ -47,6 +47,7 @@ type Rule struct {
OutputFile string OutputFile string
Format string Format string
Log *log.Logger Log *log.Logger
Roller *middleware.LogRoller
} }
const ( const (
......
...@@ -13,7 +13,7 @@ func TestNewResponseRecorder(t *testing.T) { ...@@ -13,7 +13,7 @@ func TestNewResponseRecorder(t *testing.T) {
t.Fatalf("Expected Response writer in the Recording to be same as the one sent\n") t.Fatalf("Expected Response writer in the Recording to be same as the one sent\n")
} }
if recordRequest.status != http.StatusOK { if recordRequest.status != http.StatusOK {
t.Fatalf("Expected recorded status to be http.StatusOK (%d) , but found %d\n ", recordRequest.status) t.Fatalf("Expected recorded status to be http.StatusOK (%d) , but found %d\n ", http.StatusOK, recordRequest.status)
} }
} }
func TestWriteHeader(t *testing.T) { func TestWriteHeader(t *testing.T) {
...@@ -35,6 +35,6 @@ func TestWrite(t *testing.T) { ...@@ -35,6 +35,6 @@ func TestWrite(t *testing.T) {
t.Fatalf("Expected the bytes written counter to be %d, but instead found %d\n", len(buf), recordRequest.size) t.Fatalf("Expected the bytes written counter to be %d, but instead found %d\n", len(buf), recordRequest.size)
} }
if w.Body.String() != responseTestString { if w.Body.String() != responseTestString {
t.Fatalf("Expected Response Body to be %s , but found %s\n", w.Body.String()) t.Fatalf("Expected Response Body to be %s , but found %s\n", responseTestString, w.Body.String())
} }
} }
...@@ -22,6 +22,7 @@ func TestNewReplacer(t *testing.T) { ...@@ -22,6 +22,7 @@ func TestNewReplacer(t *testing.T) {
switch v := replaceValues.(type) { switch v := replaceValues.(type) {
case replacer: case replacer:
if v.replacements["{host}"] != "caddyserver.com" { if v.replacements["{host}"] != "caddyserver.com" {
t.Errorf("Expected host to be caddyserver.com") t.Errorf("Expected host to be caddyserver.com")
} }
...@@ -36,3 +37,35 @@ func TestNewReplacer(t *testing.T) { ...@@ -36,3 +37,35 @@ func TestNewReplacer(t *testing.T) {
t.Fatalf("Return Value from New Replacer expected pass type assertion into a replacer type \n") t.Fatalf("Return Value from New Replacer expected pass type assertion into a replacer type \n")
} }
} }
func TestReplace(t *testing.T) {
w := httptest.NewRecorder()
recordRequest := NewResponseRecorder(w)
userJson := `{"username": "dennis"}`
reader := strings.NewReader(userJson) //Convert string to reader
request, err := http.NewRequest("POST", "http://caddyserver.com", reader) //Create request with JSON body
if err != nil {
t.Fatalf("Request Formation Failed \n")
}
replaceValues := NewReplacer(request, recordRequest, "")
switch v := replaceValues.(type) {
case replacer:
if v.Replace("This host is {host}") != "This host is caddyserver.com" {
t.Errorf("Expected host replacement failed")
}
if v.Replace("This request method is {method}") != "This request method is POST" {
t.Errorf("Expected method replacement failed")
}
if v.Replace("The response status is {status}") != "The response status is 200" {
t.Errorf("Expected status replacement failed")
}
default:
t.Fatalf("Return Value from New Replacer expected pass type assertion into a replacer type \n")
}
}
package middleware
import (
"io"
"gopkg.in/natefinch/lumberjack.v2"
)
type LogRoller struct {
Filename string
MaxSize int
MaxAge int
MaxBackups int
LocalTime bool
}
func (l LogRoller) GetLogWriter() io.Writer {
return &lumberjack.Logger{
Filename: l.Filename,
MaxSize: l.MaxSize,
MaxAge: l.MaxAge,
MaxBackups: l.MaxBackups,
LocalTime: l.LocalTime,
}
}
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