summaryrefslogtreecommitdiffhomepage
path: root/config
diff options
context:
space:
mode:
Diffstat (limited to 'config')
-rw-r--r--config/config.go153
-rw-r--r--config/controller.go48
-rw-r--r--config/controller_test.go37
-rw-r--r--config/directives.go164
-rw-r--r--config/dispenser.go159
-rw-r--r--config/dispenser_test.go310
-rw-r--r--config/import_test.txt1
-rw-r--r--config/lexer.go114
-rw-r--r--config/lexer_test.go139
-rw-r--r--config/middleware.go82
-rw-r--r--config/parser.go208
-rw-r--r--config/parser_test.go330
-rw-r--r--config/parsing.go318
-rw-r--r--config/parsing_test.go1
14 files changed, 101 insertions, 1963 deletions
diff --git a/config/config.go b/config/config.go
index f069f4f05..bde7854eb 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1,13 +1,13 @@
-// Package config contains utilities and types necessary for
-// launching specially-configured server instances.
package config
import (
+ "io"
"log"
- "net"
- "os"
+ "github.com/mholt/caddy/config/parse"
+ "github.com/mholt/caddy/config/setup"
"github.com/mholt/caddy/middleware"
+ "github.com/mholt/caddy/server"
)
const (
@@ -19,110 +19,89 @@ const (
DefaultConfigFile = "Caddyfile"
)
-// Host and Port are configurable via command line flag
+// These three defaults are configurable through the command line
var (
+ Root = DefaultRoot
Host = DefaultHost
Port = DefaultPort
)
-// config represents a server configuration. It
-// is populated by parsing a config file (via the
-// Load function).
-type Config struct {
- // The hostname or IP on which to serve
- Host string
-
- // The port to listen on
- Port string
-
- // The directory from which to serve files
- Root string
-
- // HTTPS configuration
- TLS TLSConfig
-
- // Middleware stack
- Middleware map[string][]middleware.Middleware
-
- // Functions (or methods) to execute at server start; these
- // are executed before any parts of the server are configured,
- // and the functions are blocking
- Startup []func() error
-
- // Functions (or methods) to execute when the server quits;
- // these are executed in response to SIGINT and are blocking
- Shutdown []func() error
-
- // The path to the configuration file from which this was loaded
- ConfigFile string
-}
-
-// Address returns the host:port of c as a string.
-func (c Config) Address() string {
- return net.JoinHostPort(c.Host, c.Port)
-}
-
-// TLSConfig describes how TLS should be configured and used,
-// if at all. A certificate and key are both required.
-type TLSConfig struct {
- Enabled bool
- Certificate string
- Key string
-}
-
-// Load loads a configuration file, parses it,
-// and returns a slice of Config structs which
-// can be used to create and configure server
-// instances.
-func Load(filename string) ([]Config, error) {
- file, err := os.Open(filename)
- if err != nil {
- return nil, err
- }
- defer file.Close()
+func Load(filename string, input io.Reader) ([]server.Config, error) {
+ var configs []server.Config
// turn off timestamp for parsing
flags := log.Flags()
log.SetFlags(0)
- p, err := newParser(file)
+ serverBlocks, err := parse.ServerBlocks(filename, input)
if err != nil {
- return nil, err
+ return configs, err
}
- cfgs, err := p.parse()
- if err != nil {
- return []Config{}, err
- }
-
- for i := 0; i < len(cfgs); i++ {
- cfgs[i].ConfigFile = filename
+ // Each server block represents a single server/address.
+ // Iterate each server block and make a config for each one,
+ // executing the directives that were parsed.
+ for _, sb := range serverBlocks {
+ config := server.Config{
+ Host: sb.Host,
+ Port: sb.Port,
+ Middleware: make(map[string][]middleware.Middleware),
+ }
+
+ // It is crucial that directives are executed in the proper order.
+ for _, dir := range directiveOrder {
+ // Execute directive if it is in the server block
+ if tokens, ok := sb.Tokens[dir.name]; ok {
+ // Each setup function gets a controller, which is the
+ // server config and the dispenser containing only
+ // this directive's tokens.
+ controller := &setup.Controller{
+ Config: &config,
+ Dispenser: parse.NewDispenserTokens(filename, tokens),
+ }
+
+ midware, err := dir.setup(controller)
+ if err != nil {
+ return configs, err
+ }
+ if midware != nil {
+ // TODO: For now, we only support the default path scope /
+ config.Middleware["/"] = append(config.Middleware["/"], midware)
+ }
+ }
+ }
+
+ if config.Port == "" {
+ config.Port = Port
+ }
+
+ configs = append(configs, config)
}
// restore logging settings
log.SetFlags(flags)
- return cfgs, nil
+ return configs, nil
}
-// IsNotFound returns whether or not the error is
-// one which indicates that the configuration file
-// was not found. (Useful for checking the error
-// returned from Load).
-func IsNotFound(err error) bool {
- return os.IsNotExist(err)
+// validDirective returns true if d is a valid
+// directive; false otherwise.
+func validDirective(d string) bool {
+ for _, dir := range directiveOrder {
+ if dir.name == d {
+ return true
+ }
+ }
+ return false
}
-// Default makes a default configuration
-// that's empty except for root, host, and port,
-// which are essential for serving the cwd.
-func Default() []Config {
- cfg := []Config{
- Config{
- Root: DefaultRoot,
- Host: Host,
- Port: Port,
- },
+// Default makes a default configuration which
+// is empty except for root, host, and port,
+// which are essentials for serving the cwd.
+func Default() server.Config {
+ return server.Config{
+ Root: Root,
+ Host: Host,
+ Port: Port,
}
- return cfg
}
diff --git a/config/controller.go b/config/controller.go
deleted file mode 100644
index 1ce6db7f5..000000000
--- a/config/controller.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package config
-
-import "github.com/mholt/caddy/middleware"
-
-// controller is a dispenser of tokens and also
-// facilitates setup with the server by providing
-// access to its configuration. It implements
-// the middleware.Controller interface.
-type controller struct {
- dispenser
- parser *parser
- pathScope string
-}
-
-// newController returns a new controller.
-func newController(p *parser) *controller {
- return &controller{
- dispenser: dispenser{
- cursor: -1,
- filename: p.filename,
- },
- parser: p,
- }
-}
-
-// Startup registers a function to execute when the server starts.
-func (c *controller) Startup(fn func() error) {
- c.parser.cfg.Startup = append(c.parser.cfg.Startup, fn)
-}
-
-// Shutdown registers a function to execute when the server exits.
-func (c *controller) Shutdown(fn func() error) {
- c.parser.cfg.Shutdown = append(c.parser.cfg.Shutdown, fn)
-}
-
-// Root returns the server root file path.
-func (c *controller) Root() string {
- if c.parser.cfg.Root == "" {
- return "."
- } else {
- return c.parser.cfg.Root
- }
-}
-
-// Context returns the path scope that the Controller is in.
-func (c *controller) Context() middleware.Path {
- return middleware.Path(c.pathScope)
-}
diff --git a/config/controller_test.go b/config/controller_test.go
deleted file mode 100644
index 7dccf0f14..000000000
--- a/config/controller_test.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package config
-
-import "testing"
-
-func TestController(t *testing.T) {
- p := &parser{filename: "test"}
- c := newController(p)
-
- if c == nil || c.parser == nil {
- t.Fatal("Expected newController to return a non-nil controller with a non-nil parser")
- }
- if c.dispenser.cursor != -1 {
- t.Errorf("Dispenser not initialized properly; expecting cursor at -1, got %d", c.dispenser.cursor)
- }
- if c.dispenser.filename != p.filename {
- t.Errorf("Dispenser's filename should be same as parser's (%s); got '%s'", p.filename, c.dispenser.filename)
- }
-
- c.Startup(func() error { return nil })
- if n := len(c.parser.cfg.Startup); n != 1 {
- t.Errorf("Expected length of startup functions to be 1, got %d", n)
- }
-
- if root := c.Root(); root != "." {
- t.Errorf("Expected defualt root path to be '.', got '%s'", root)
- }
-
- c.parser.cfg.Root = "foobar/test"
- if root := c.Root(); root != c.parser.cfg.Root {
- t.Errorf("Expected established root path to be '%s', got '%s'", c.parser.cfg.Root, root)
- }
-
- c.pathScope = "unused"
- if context := c.Context(); string(context) != c.pathScope {
- t.Errorf("Expected context to be '%s', got '%s'", c.pathScope, context)
- }
-}
diff --git a/config/directives.go b/config/directives.go
index e39eae235..76f29af73 100644
--- a/config/directives.go
+++ b/config/directives.go
@@ -1,139 +1,45 @@
package config
import (
- "fmt"
- "log"
- "os"
- "os/exec"
-
+ "github.com/mholt/caddy/config/parse"
+ "github.com/mholt/caddy/config/setup"
"github.com/mholt/caddy/middleware"
)
-// dirFunc is a type of parsing function which processes
-// a particular directive and populates the config.
-type dirFunc func(*parser) error
-
-// validDirectives is a map of valid, built-in directive names
-// to their parsing function. Built-in directives cannot be
-// ordered, so they should only be used for internal server
-// configuration; not directly handling requests.
-var validDirectives map[string]dirFunc
-
func init() {
- // This has to be in the init function
- // to avoid an initialization loop error because
- // the 'import' directive (key) in this map
- // invokes a method that uses this map.
- validDirectives = map[string]dirFunc{
- "root": func(p *parser) error {
- if !p.nextArg() {
- return p.argErr()
- }
- p.cfg.Root = p.tkn()
-
- // Ensure root folder exists
- _, err := os.Stat(p.cfg.Root)
- if err != nil {
- if os.IsNotExist(err) {
- // Allow this, because the folder might appear later.
- // But make sure the user knows!
- log.Printf("Warning: Root path %s does not exist", p.cfg.Root)
- } else {
- return p.err("Path", fmt.Sprintf("Unable to access root path '%s': %s", p.cfg.Root, err.Error()))
- }
- }
- return nil
- },
- "import": func(p *parser) error {
- if !p.nextArg() {
- return p.argErr()
- }
-
- filename := p.tkn()
- file, err := os.Open(filename)
- if err != nil {
- return p.err("Parse", err.Error())
- }
- defer file.Close()
- p2, err := newParser(file)
- if err != nil {
- return p.err("Parse", "Could not import "+filename+"; "+err.Error())
- }
-
- p2.cfg = p.cfg
- err = p2.directives()
- if err != nil {
- return err
- }
- p.cfg = p2.cfg
-
- return nil
- },
- "tls": func(p *parser) error {
- tls := TLSConfig{Enabled: true}
-
- if !p.nextArg() {
- return p.argErr()
- }
- tls.Certificate = p.tkn()
-
- if !p.nextArg() {
- return p.argErr()
- }
- tls.Key = p.tkn()
-
- p.cfg.TLS = tls
- return nil
- },
- "startup": func(p *parser) error {
- // TODO: This code is duplicated with the shutdown directive below
-
- if !p.nextArg() {
- return p.argErr()
- }
-
- command, args, err := middleware.SplitCommandAndArgs(p.tkn())
- if err != nil {
- return p.err("Parse", err.Error())
- }
-
- startupfn := func() error {
- cmd := exec.Command(command, args...)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- err := cmd.Run()
- if err != nil {
- return err
- }
- return nil
- }
-
- p.cfg.Startup = append(p.cfg.Startup, startupfn)
- return nil
- },
- "shutdown": func(p *parser) error {
- if !p.nextArg() {
- return p.argErr()
- }
-
- command, args, err := middleware.SplitCommandAndArgs(p.tkn())
- if err != nil {
- return p.err("Parse", err.Error())
- }
+ // The parse package must know which directives
+ // are valid, but it must not import the setup
+ // or config package.
+ for _, dir := range directiveOrder {
+ parse.ValidDirectives[dir.name] = struct{}{}
+ }
+}
- shutdownfn := func() error {
- cmd := exec.Command(command, args...)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- err := cmd.Run()
- if err != nil {
- return err
- }
- return nil
- }
+var directiveOrder = []directive{
+ {"root", setup.Root},
+ {"tls", setup.TLS},
+ {"startup", setup.Startup},
+ {"shutdown", setup.Shutdown},
+
+ {"log", setup.Log},
+ {"gzip", setup.Gzip},
+ {"errors", setup.Errors},
+ {"header", setup.Headers},
+ {"rewrite", setup.Rewrite},
+ {"redir", setup.Redir},
+ {"ext", setup.Ext},
+ {"basicauth", setup.BasicAuth},
+ //{"proxy", setup.Proxy},
+ // {"fastcgi", setup.FastCGI},
+ // {"websocket", setup.WebSocket},
+ // {"markdown", setup.Markdown},
+ // {"templates", setup.Templates},
+ // {"browse", setup.Browse},
+}
- p.cfg.Shutdown = append(p.cfg.Shutdown, shutdownfn)
- return nil
- },
- }
+type directive struct {
+ name string
+ setup setupFunc
}
+
+type setupFunc func(c *setup.Controller) (middleware.Middleware, error)
diff --git a/config/dispenser.go b/config/dispenser.go
deleted file mode 100644
index 13cd85425..000000000
--- a/config/dispenser.go
+++ /dev/null
@@ -1,159 +0,0 @@
-package config
-
-import (
- "errors"
- "fmt"
-)
-
-// dispenser is a type that dispenses tokens, similarly to
-// a lexer, except that it can do so with some notion of
-// structure. Its methods implement part of the
-// middleware.Controller interface, so refer to that
-// documentation for more info.
-type dispenser struct {
- filename string
- cursor int
- nesting int
- tokens []token
-}
-
-// Next loads the next token. Returns true if a token
-// was loaded; false otherwise. If false, all tokens
-// have already been consumed.
-func (d *dispenser) Next() bool {
- if d.cursor < len(d.tokens)-1 {
- d.cursor++
- return true
- }
- return false
-}
-
-// NextArg loads the next token if it is on the same
-// line. Returns true if a token was loaded; false
-// otherwise. If false, all tokens on the line have
-// been consumed.
-func (d *dispenser) NextArg() bool {
- if d.cursor < 0 {
- d.cursor++
- return true
- }
- if d.cursor >= len(d.tokens) {
- return false
- }
- if d.cursor < len(d.tokens)-1 &&
- d.tokens[d.cursor].line == d.tokens[d.cursor+1].line {
- d.cursor++
- return true
- }
- return false
-}
-
-// NextLine loads the next token only if it is not on the same
-// line as the current token, and returns true if a token was
-// loaded; false otherwise. If false, there is not another token
-// or it is on the same line.
-func (d *dispenser) NextLine() bool {
- if d.cursor < 0 {
- d.cursor++
- return true
- }
- if d.cursor >= len(d.tokens) {
- return false
- }
- if d.cursor < len(d.tokens)-1 &&
- d.tokens[d.cursor].line < d.tokens[d.cursor+1].line {
- d.cursor++
- return true
- }
- return false
-}
-
-// NextBlock can be used as the condition of a for loop
-// to load the next token as long as it opens a block or
-// is already in a block. It returns true if a token was
-// loaded, or false when the block's closing curly brace
-// was loaded and thus the block ended. Nested blocks are
-// not (currently) supported.
-func (d *dispenser) NextBlock() bool {
- if d.nesting > 0 {
- d.Next()
- if d.Val() == "}" {
- d.nesting--
- return false
- }
- return true
- }
- if !d.NextArg() { // block must open on same line
- return false
- }
- if d.Val() != "{" {
- d.cursor-- // roll back if not opening brace
- return false
- }
- d.Next()
- d.nesting++
- return true
-}
-
-// Val gets the text of the current token. If there is no token
-// loaded, it returns empty string.
-func (d *dispenser) Val() string {
- if d.cursor < 0 || d.cursor >= len(d.tokens) {
- return ""
- } else {
- return d.tokens[d.cursor].text
- }
-}
-
-// Args is a convenience function that loads the next arguments
-// (tokens on the same line) into an arbitrary number of strings
-// pointed to in targets. If there are fewer tokens available
-// than string pointers, the remaining strings will not be changed
-// and false will be returned. If there were enough tokens available
-// to fill the arguments, then true will be returned.
-func (d *dispenser) Args(targets ...*string) bool {
- enough := true
- for i := 0; i < len(targets); i++ {
- if !d.NextArg() {
- enough = false
- break
- }
- *targets[i] = d.Val()
- }
- return enough
-}
-
-// RemainingArgs loads any more arguments (tokens on the same line)
-// into a slice and returns them. Open curly brace tokens also indicate
-// the end of arguments, and the curly brace is not included in
-// the return value nor is it loaded.
-func (d *dispenser) RemainingArgs() []string {
- var args []string
-
- for d.NextArg() {
- if d.Val() == "{" {
- d.cursor--
- break
- }
- args = append(args, d.Val())
- }
-
- return args
-}
-
-// ArgErr returns an argument error, meaning that another
-// argument was expected but not found. In other words,
-// a line break or open curly brace was encountered instead of
-// an argument.
-func (d *dispenser) ArgErr() error {
- if d.Val() == "{" {
- return d.Err("Unexpected token '{', expecting argument")
- }
- return d.Err("Wrong argument count or unexpected line ending after '" + d.Val() + "'")
-}
-
-// Err generates a custom parse error with a message of msg.
-func (d *dispenser) Err(msg string) error {
- msg = fmt.Sprintf("%s:%d - Parse error: %s", d.filename, d.tokens[d.cursor].line, msg)
- return errors.New(msg)
-}
diff --git a/config/dispenser_test.go b/config/dispenser_test.go
deleted file mode 100644
index 2940feb1c..000000000
--- a/config/dispenser_test.go
+++ /dev/null
@@ -1,310 +0,0 @@
-package config
-
-import (
- "reflect"
- "strings"
- "testing"
-)
-
-func TestDispenser_Val_Next(t *testing.T) {
- input := `host:port
- dir1 arg1
- dir2 arg2 arg3
- dir3`
- d := makeTestDispenser("test", input)
-
- if val := d.Val(); val != "" {
- t.Fatalf("Val(): Should return empty string when no token loaded; got '%s'", val)
- }
-
- assertNext := func(shouldLoad bool, expectedCursor int, expectedVal string) {
- if loaded := d.Next(); loaded != shouldLoad {
- t.Errorf("Next(): Expected %v but got %v instead (val '%s')", shouldLoad, loaded, d.Val())
- }
- if d.cursor != expectedCursor {
- t.Errorf("Expected cursor to be %d, but was %d", expectedCursor, d.cursor)
- }
- if d.nesting != 0 {
- t.Errorf("Nesting should be 0, was %d instead", d.nesting)
- }
- if val := d.Val(); val != expectedVal {
- t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val)
- }
- }
-
- assertNext(true, 0, "host:port")
- assertNext(true, 1, "dir1")
- assertNext(true, 2, "arg1")
- assertNext(true, 3, "dir2")
- assertNext(true, 4, "arg2")
- assertNext(true, 5, "arg3")
- assertNext(true, 6, "dir3")
- // Note: This next test simply asserts existing behavior.
- // If desired, we may wish to empty the token value after
- // reading past the EOF. Open an issue if you want this change.
- assertNext(false, 6, "dir3")
-}
-
-func TestDispenser_NextArg(t *testing.T) {
- input := `dir1 arg1
- dir2 arg2 arg3
- dir3`
- d := makeTestDispenser("test", input)
-
- assertNext := func(shouldLoad bool, expectedVal string, expectedCursor int) {
- if d.Next() != shouldLoad {
- t.Errorf("Next(): Should load token but got false instead (val: '%s')", d.Val())
- }
- if d.cursor != expectedCursor {
- t.Errorf("Next(): Expected cursor to be at %d, but it was %d", expectedCursor, d.cursor)
- }
- if val := d.Val(); val != expectedVal {
- t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val)
- }
- }
-
- assertNextArg := func(expectedVal string, loadAnother bool, expectedCursor int) {
- if d.NextArg() != true {
- t.Error("NextArg(): Should load next argument but got false instead")
- }
- if d.cursor != expectedCursor {
- t.Errorf("NextArg(): Expected cursor to be at %d, but it was %d", expectedCursor, d.cursor)
- }
- if val := d.Val(); val != expectedVal {
- t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val)
- }
- if !loadAnother {
- if d.NextArg() != false {
- t.Fatalf("NextArg(): Should NOT load another argument, but got true instead (val: '%s')", d.Val())
- }
- if d.cursor != expectedCursor {
- t.Errorf("NextArg(): Expected cursor to remain at %d, but it was %d", expectedCursor, d.cursor)
- }
- }
- }
-
- assertNext(true, "dir1", 0)
- assertNextArg("arg1", false, 1)
- assertNext(true, "dir2", 2)
- assertNextArg("arg2", true, 3)
- assertNextArg("arg3", false, 4)
- assertNext(true, "dir3", 5)
- assertNext(false, "dir3", 5)
-}
-
-func TestDispenser_NextLine(t *testing.T) {
- input := `host:port
- dir1 arg1
- dir2 arg2 arg3`
- d := makeTestDispenser("test", input)
-
- assertNextLine := func(shouldLoad bool, expectedVal string, expectedCursor int) {
- if d.NextLine() != shouldLoad {
- t.Errorf("NextLine(): Should load token but got false instead (val: '%s')", d.Val())
- }
- if d.cursor != expectedCursor {
- t.Errorf("NextLine(): Expected cursor to be %d, instead was %d", expectedCursor, d.cursor)
- }
- if val := d.Val(); val != expectedVal {
- t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val)
- }
- }
-
- assertNextLine(true, "host:port", 0)
- assertNextLine(true, "dir1", 1)
- assertNextLine(false, "dir1", 1)
- d.Next() // arg1
- assertNextLine(true, "dir2", 3)
- assertNextLine(false, "dir2", 3)
- d.Next() // arg2
- assertNextLine(false, "arg2", 4)
- d.Next() // arg3
- assertNextLine(false, "arg3", 5)
-}
-
-func TestDispenser_NextBlock(t *testing.T) {
- input := `foobar1 {
- sub1 arg1
- sub2
- }
- foobar2 {
- }`
- d := makeTestDispenser("test", input)
-
- assertNextBlock := func(shouldLoad bool, expectedCursor, expectedNesting int) {
- if loaded := d.NextBlock(); loaded != shouldLoad {
- t.Errorf("NextBlock(): Should return %v but got %v", shouldLoad, loaded)
- }
- if d.cursor != expectedCursor {
- t.Errorf("NextBlock(): Expected cursor to be %d, was %d", expectedCursor, d.cursor)
- }
- if d.nesting != expectedNesting {
- t.Errorf("NextBlock(): Nesting should be %d, not %d", expectedNesting, d.nesting)
- }
- }
-
- assertNextBlock(false, -1, 0)
- d.Next() // foobar1
- assertNextBlock(true, 2, 1)
- assertNextBlock(true, 3, 1)
- assertNextBlock(true, 4, 1)
- assertNextBlock(false, 5, 0)
- d.Next() // foobar2
- assertNextBlock(true, 8, 1)
- assertNextBlock(false, 8, 0)
-}
-
-func TestDispenser_Args(t *testing.T) {
- var s1, s2, s3 string
- input := `dir1 arg1 arg2 arg3
- dir2 arg4 arg5
- dir3 arg6 arg7
- dir4`
- d := makeTestDispenser("test", input)
-
- d.Next() // dir1
-
- // As many strings as arguments
- if all := d.Args(&s1, &s2, &s3); !all {
- t.Error("Args(): Expected true, got false")
- }
- if s1 != "arg1" {
- t.Errorf("Args(): Expected s1 to be 'arg1', got '%s'", s1)
- }
- if s2 != "arg2" {
- t.Errorf("Args(): Expected s2 to be 'arg2', got '%s'", s2)
- }
- if s3 != "arg3" {
- t.Errorf("Args(): Expected s3 to be 'arg3', got '%s'", s3)
- }
-
- d.Next() // dir2
-
- // More strings than arguments
- if all := d.Args(&s1, &s2, &s3); all {
- t.Error("Args(): Expected false, got true")
- }
- if s1 != "arg4" {
- t.Errorf("Args(): Expected s1 to be 'arg4', got '%s'", s1)
- }
- if s2 != "arg5" {
- t.Errorf("Args(): Expected s2 to be 'arg5', got '%s'", s2)
- }
- if s3 != "arg3" {
- t.Errorf("Args(): Expected s3 to be unchanged ('arg3'), instead got '%s'", s3)
- }
-
- // (quick cursor check just for kicks and giggles)
- if d.cursor != 6 {
- t.Errorf("Cursor should be 6, but is %d", d.cursor)
- }
-
- d.Next() // dir3
-
- // More arguments than strings
- if all := d.Args(&s1); !all {
- t.Error("Args(): Expected true, got false")
- }
- if s1 != "arg6" {
- t.Errorf("Args(): Expected s1 to be 'arg6', got '%s'", s1)
- }
-
- d.Next() // dir4
-
- // No arguments or strings
- if all := d.Args(); !all {
- t.Error("Args(): Expected true, got false")
- }
-
- // No arguments but at least one string
- if all := d.Args(&s1); all {
- t.Error("Args(): Expected false, got true")
- }
-}
-
-func TestDispenser_RemainingArgs(t *testing.T) {
- input := `dir1 arg1 arg2 arg3
- dir2 arg4 arg5
- dir3 arg6 { arg7
- dir4`
- d := makeTestDispenser("test", input)
-
- d.Next() // dir1
-
- args := d.RemainingArgs()
- if expected := []string{"arg1", "arg2", "arg3"}; !reflect.DeepEqual(args, expected) {
- t.Errorf("RemainingArgs(): Expected %v, got %v", expected, args)
- }
-
- d.Next() // dir2
-
- args = d.RemainingArgs()
- if expected := []string{"arg4", "arg5"}; !reflect.DeepEqual(args, expected) {
- t.Errorf("RemainingArgs(): Expected %v, got %v", expected, args)
- }
-
- d.Next() // dir3
-
- args = d.RemainingArgs()
- if expected := []string{"arg6"}; !reflect.DeepEqual(args, expected) {
- t.Errorf("RemainingArgs(): Expected %v, got %v", expected, args)
- }
-
- d.Next() // {
- d.Next() // arg7
- d.Next() // dir4
-
- args = d.RemainingArgs()
- if len(args) != 0 {
- t.Errorf("RemainingArgs(): Expected %v, got %v", []string{}, args)
- }
-}
-
-func TestDispenser_ArgErr_Err(t *testing.T) {
- input := `dir1 {
- }
- dir2 arg1 arg2`
- d := makeTestDispenser("test", input)
-
- d.cursor = 1 // {
-
- if err := d.ArgErr(); err == nil || !strings.Contains(err.Error(), "{") {
- t.Errorf("ArgErr(): Expected an error message with { in it, but got '%v'", err)
- }
-
- d.cursor = 5 // arg2
-
- if err := d.ArgErr(); err == nil || !strings.Contains(err.Error(), "arg2") {
- t.Errorf("ArgErr(): Expected an error message with 'arg2' in it; got '%v'", err)
- }
-
- err := d.Err("foobar")
- if err == nil {
- t.Fatalf("Err(): Expected an error, got nil")
- }
-
- if !strings.Contains(err.Error(), "test:3") {
- t.Errorf("Expected error message with filename:line in it; got '%v'", err)
- }
-
- if !strings.Contains(err.Error(), "foobar") {
- t.Errorf("Expected error message with custom message in it ('foobar'); got '%v'", err)
- }
-}
-
-func makeTestDispenser(filename, input string) dispenser {
- return dispenser{
- filename: filename,
- cursor: -1,
- tokens: getTokens(input),
- }
-}
-
-func getTokens(input string) (tokens []token) {
- var l lexer
- l.load(strings.NewReader(input))
- for l.next() {
- tokens = append(tokens, l.token)
- }
- return
-}
diff --git a/config/import_test.txt b/config/import_test.txt
deleted file mode 100644
index 78cb08282..000000000
--- a/config/import_test.txt
+++ /dev/null
@@ -1 +0,0 @@
-root /test/imported/public_html \ No newline at end of file
diff --git a/config/lexer.go b/config/lexer.go
deleted file mode 100644
index 96c883cff..000000000
--- a/config/lexer.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package config
-
-import (
- "bufio"
- "io"
- "unicode"
-)
-
-type (
- // lexer is a utility which can get values, token by
- // token, from a reader. A token is a word, and tokens
- // are separated by whitespace. A word can be enclosed in
- // quotes if it contains whitespace.
- lexer struct {
- reader *bufio.Reader
- token token
- line int
- }
-
- // token represents a single processable unit.
- token struct {
- line int
- text string
- }
-)
-
-// load prepares the lexer to scan a file for tokens.
-func (l *lexer) load(file io.Reader) error {
- l.reader = bufio.NewReader(file)
- l.line = 1
- return nil
-}
-
-// next loads the next token into the lexer.
-// A token is delimited by whitespace, unless
-// the token starts with a quotes character (")
-// in which case the token goes until the closing
-// quotes (the enclosing quotes are not included).
-// The rest of the line is skipped if a "#"
-// character is read in. Returns true if a token
-// was loaded; false otherwise.
-func (l *lexer) next() bool {
- var val []rune
- var comment, quoted, escaped bool
-
- makeToken := func() bool {
- l.token.text = string(val)
- return true
- }
-
- for {
- ch, _, err := l.reader.ReadRune()
- if err != nil {
- if len(val) > 0 {
- return makeToken()
- }
- if err == io.EOF {
- return false
- } else {
- panic(err)
- }
- }
-
- if quoted {
- if !escaped {
- if ch == '\\' {
- escaped = true
- continue
- } else if ch == '"' {
- quoted = false
- return makeToken()
- }
- }
- if ch == '\n' {
- l.line++
- }
- val = append(val, ch)
- escaped = false
- continue
- }
-
- if unicode.IsSpace(ch) {
- if ch == '\r' {
- continue
- }
- if ch == '\n' {
- l.line++
- comment = false
- }
- if len(val) > 0 {
- return makeToken()
- }
- continue
- }
-
- if ch == '#' {
- comment = true
- }
-
- if comment {
- continue
- }
-
- if len(val) == 0 {
- l.token = token{line: l.line}
- if ch == '"' {
- quoted = true
- continue
- }
- }
-
- val = append(val, ch)
- }
-}
diff --git a/config/lexer_test.go b/config/lexer_test.go
deleted file mode 100644
index 5b1cd2c25..000000000
--- a/config/lexer_test.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package config
-
-import (
- "strings"
- "testing"
-)
-
-type lexerTestCase struct {
- input string
- expected []token
-}
-
-func TestLexer(t *testing.T) {
- testCases := []lexerTestCase{
- {
- input: `host:123`,
- expected: []token{
- {line: 1, text: "host:123"},
- },
- },
- {
- input: `host:123
-
- directive`,
- expected: []token{
- {line: 1, text: "host:123"},
- {line: 3, text: "directive"},
- },
- },
- {
- input: `host:123 {
- directive
- }`,
- expected: []token{
- {line: 1, text: "host:123"},
- {line: 1, text: "{"},
- {line: 2, text: "directive"},
- {line: 3, text: "}"},
- },
- },
- {
- input: `host:123 { directive }`,
- expected: []token{
- {line: 1, text: "host:123"},
- {line: 1, text: "{"},
- {line: 1, text: "directive"},
- {line: 1, text: "}"},
- },
- },
- {
- input: `host:123 {
- #comment
- directive
- # comment
- foobar # another comment
- }`,
- expected: []token{
- {line: 1, text: "host:123"},
- {line: 1, text: "{"},
- {line: 3, text: "directive"},
- {line: 5, text: "foobar"},
- {line: 6, text: "}"},
- },
- },
- {
- input: `a "quoted value" b
- foobar`,
- expected: []token{
- {line: 1, text: "a"},
- {line: 1, text: "quoted value"},
- {line: 1, text: "b"},
- {line: 2, text: "foobar"},
- },
- },
- {
- input: `A "quoted \"value\" inside" B`,
- expected: []token{
- {line: 1, text: "A"},
- {line: 1, text: `quoted "value" inside`},
- {line: 1, text: "B"},
- },
- },
- {
- input: `A "quoted value with line
- break inside" {
- foobar
- }`,
- expected: []token{
- {line: 1, text: "A"},
- {line: 1, text: "quoted value with line\n\t\t\t\t\tbreak inside"},
- {line: 2, text: "{"},
- {line: 3, text: "foobar"},
- {line: 4, text: "}"},
- },
- },
- {
- input: "skip those\r\nCR characters",
- expected: []token{
- {line: 1, text: "skip"},
- {line: 1, text: "those"},
- {line: 2, text: "CR"},
- {line: 2, text: "characters"},
- },
- },
- }
-
- for i, testCase := range testCases {
- actual := tokenize(testCase.input)
- lexerCompare(t, i, testCase.expected, actual)
- }
-}
-
-func tokenize(input string) (tokens []token) {
- l := lexer{}
- l.load(strings.NewReader(input))
- for l.next() {
- tokens = append(tokens, l.token)
- }
- return
-}
-
-func lexerCompare(t *testing.T, n int, expected, actual []token) {
- if len(expected) != len(actual) {
- t.Errorf("Test case %d: expected %d token(s) but got %d", n, len(expected), len(actual))
- }
-
- for i := 0; i < len(actual) && i < len(expected); i++ {
- if actual[i].line != expected[i].line {
- t.Errorf("Test case %d token %d ('%s'): expected line %d but was line %d",
- n, i, expected[i].text, expected[i].line, actual[i].line)
- break
- }
- if actual[i].text != expected[i].text {
- t.Errorf("Test case %d token %d: expected text '%s' but was '%s'",
- n, i, expected[i].text, actual[i].text)
- break
- }
- }
-}
diff --git a/config/middleware.go b/config/middleware.go
deleted file mode 100644
index 41af1826d..000000000
--- a/config/middleware.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package config
-
-import (
- "github.com/mholt/caddy/middleware"
- "github.com/mholt/caddy/middleware/basicauth"
- "github.com/mholt/caddy/middleware/browse"
- "github.com/mholt/caddy/middleware/errors"
- "github.com/mholt/caddy/middleware/extensions"
- "github.com/mholt/caddy/middleware/fastcgi"
- "github.com/mholt/caddy/middleware/git"
- "github.com/mholt/caddy/middleware/gzip"
- "github.com/mholt/caddy/middleware/headers"
- "github.com/mholt/caddy/middleware/log"
- "github.com/mholt/caddy/middleware/markdown"
- "github.com/mholt/caddy/middleware/proxy"
- "github.com/mholt/caddy/middleware/redirect"
- "github.com/mholt/caddy/middleware/rewrite"
- "github.com/mholt/caddy/middleware/templates"
- "github.com/mholt/caddy/middleware/websockets"
-)
-
-// This init function registers middleware. Register
-// middleware in the order they should be executed
-// during a request (A, B, C...). Middleware execute
-// in the order A-B-C-*-C-B-A, assuming they call
-// the Next handler in the chain.
-//
-// Note: Ordering is VERY important. Every middleware
-// will feel the effects of all other middleware below
-// (after) them, but must not care what middleware above
-// them are doing.
-//
-// For example, log needs to know the status code and exactly
-// how many bytes were written to the client, which every
-// other middleware can affect, so it gets registered first.
-// The errors middleware does not care if gzip or log modifies
-// its response, so it gets registered below them. Gzip, on the
-// other hand, DOES care what errors does to the response since
-// it must compress every output to the client, even error pages,
-// so it must be registered before the errors middleware and any
-// others that would write to the response.
-func init() {
- register("log", log.New)
- register("gzip", gzip.New)
- register("errors", errors.New)
- register("header", headers.New)
- register("rewrite", rewrite.New)
- register("redir", redirect.New)
- register("ext", extensions.New)
- register("basicauth", basicauth.New)
- register("proxy", proxy.New)
- register("git", git.New)
- register("fastcgi", fastcgi.New)
- register("websocket", websockets.New)
- register("markdown", markdown.New)
- register("templates", templates.New)
- register("browse", browse.New)
-}
-
-// registry stores the registered middleware:
-// both the order and the directives to which they
-// are bound.
-var registry = struct {
- directiveMap map[string]middleware.Generator
- ordered []string
-}{
- directiveMap: make(map[string]middleware.Generator),
-}
-
-// register binds a middleware generator (outer function)
-// to a directive. Upon each request, middleware will be
-// executed in the order they are registered.
-func register(directive string, generator middleware.Generator) {
- registry.directiveMap[directive] = generator
- registry.ordered = append(registry.ordered, directive)
-}
-
-// middlewareRegistered returns whether or not a directive is registered.
-func middlewareRegistered(directive string) bool {
- _, ok := registry.directiveMap[directive]
- return ok
-}
diff --git a/config/parser.go b/config/parser.go
deleted file mode 100644
index 1c8cc2c87..000000000
--- a/config/parser.go
+++ /dev/null
@@ -1,208 +0,0 @@
-package config
-
-import (
- "errors"
- "fmt"
- "os"
-
- "github.com/mholt/caddy/middleware"
-)
-
-type (
- // parser is a type which can parse config files.
- parser struct {
- filename string // the name of the file that we're parsing
- lexer lexer // the lexer that is giving us tokens from the raw input
- hosts []hostPort // the list of host:port combinations current tokens apply to
- cfg Config // each virtual host gets one Config; this is the one we're currently building
- cfgs []Config // after a Config is created, it may need to be copied for multiple hosts
- other []locationContext // tokens to be 'parsed' later by middleware generators
- scope *locationContext // the current location context (path scope) being populated
- unused *token // sometimes a token will be read but not immediately consumed
- eof bool // if we encounter a valid EOF in a hard place
- }
-
- // locationContext represents a location context
- // (path block) in a config file. If no context
- // is explicitly defined, the default location
- // context is "/".
- locationContext struct {
- path string
- directives map[string]*controller
- }
-
- // hostPort just keeps a hostname and port together
- hostPort struct {
- host, port string
- }
-)
-
-// newParser makes a new parser and prepares it for parsing, given
-// the input to parse.
-func newParser(file *os.File) (*parser, error) {
- stat, err := file.Stat()
- if err != nil {
- return nil, err
- }
-
- p := &parser{filename: stat.Name()}
- p.lexer.load(file)
-
- return p, nil
-}
-
-// Parse parses the configuration file. It produces a slice of Config
-// structs which can be used to create and configure server instances.
-func (p *parser) parse() ([]Config, error) {
- var configs []Config
-
- for p.lexer.next() {
- err := p.parseOne()
- if err != nil {
- return nil, err
- }
- configs = append(configs, p.cfgs...)
- }
-
- return configs, nil
-}
-
-// nextArg loads the next token if it is on the same line.
-// Returns true if a token was loaded; false otherwise.
-func (p *parser) nextArg() bool {
- if p.unused != nil {
- return false
- }
- line, tkn := p.line(), p.lexer.token
- if p.next() {
- if p.line() > line {
- p.unused = &tkn
- return false
- }
- return true
- }
- return false
-}
-
-// next loads the next token and returns true if a token
-// was loaded; false otherwise.
-func (p *parser) next() bool {
- if p.unused != nil {
- p.unused = nil
- return true
- } else {
- return p.lexer.next()
- }
-}
-
-// parseOne parses the contents of a configuration
-// file for a single Config object (each server or
-// virtualhost instance gets their own Config struct),
-// which is until the next address/server block.
-// Call this only when you know that the lexer has another
-// another token and you're not in another server
-// block already.
-func (p *parser) parseOne() error {
- p.cfgs = []Config{}
- p.cfg = Config{
- Middleware: make(map[string][]middleware.Middleware),
- }
- p.other = []locationContext{}
-
- err := p.begin()
- if err != nil {
- return err
- }
-
- err = p.unwrap()
- if err != nil {
- return err
- }
-
- // Make a copy of the config for each
- // address that will be using it
- for _, hostport := range p.hosts {
- cfgCopy := p.cfg
- cfgCopy.Host = hostport.host
- cfgCopy.Port = hostport.port
- p.cfgs = append(p.cfgs, cfgCopy)
- }
-
- return nil
-}
-
-// unwrap gets the middleware generators from the middleware
-// package in the order in which they are registered, and
-// executes the top-level functions (the generator function)
-// to expose the second layers which are the actual middleware.
-// This function should be called only after p has filled out
-// p.other and the entire server block has already been consumed.
-func (p *parser) unwrap() error {
- if len(p.other) == 0 {
- // no middlewares were invoked
- return nil
- }
-
- for _, directive := range registry.ordered {
- // TODO: For now, we only support the first and default path scope ("/", held in p.other[0])
- // but when we implement support for path scopes, we will have to change this logic
- // to loop over them and order them. We need to account for situations where multiple
- // path scopes overlap, regex (??), etc...
- if disp, ok := p.other[0].directives[directive]; ok {
- if generator, ok := registry.directiveMap[directive]; ok {
- mid, err := generator(disp)
- if err != nil {
- return err
- }
- if mid != nil {
- // TODO: Again, we assume the default path scope here...
- p.cfg.Middleware[p.other[0].path] = append(p.cfg.Middleware[p.other[0].path], mid)
- }
- } else {
- return errors.New("No middleware bound to directive '" + directive + "'")
- }
- }
- }
-
- return nil
-}
-
-// tkn is shorthand to get the text/value of the current token.
-func (p *parser) tkn() string {
- if p.unused != nil {
- return p.unused.text
- }
- return p.lexer.token.text
-}
-
-// line is shorthand to get the line number of the current token.
-func (p *parser) line() int {
- if p.unused != nil {
- return p.unused.line
- }
- return p.lexer.token.line
-}
-
-// syntaxErr creates a syntax error which explains what was
-// found and expected.
-func (p *parser) syntaxErr(expected string) error {
- return p.err("Syntax", fmt.Sprintf("Unexpected token '%s', expecting '%s'", p.tkn(), expected))
-}
-
-// syntaxErr creates a syntax error that explains that there
-// weren't enough arguments on the line.
-func (p *parser) argErr() error {
- return p.err("Syntax", "Unexpected line break after '"+p.tkn()+"' (missing arguments?)")
-}
-
-// eofErr creates a syntax error describing an unexpected EOF.
-func (p *parser) eofErr() error {
- return p.err("Syntax", "Unexpected EOF")
-}
-
-// err creates an error with a custom message msg: "{{kind}} error: {{msg}}". The
-// file name and line number are included in the error message.
-func (p *parser) err(kind, msg string) error {
- msg = fmt.Sprintf("%s:%d - %s error: %s", p.filename, p.line(), kind, msg)
- return errors.New(msg)
-}
diff --git a/config/parser_test.go b/config/parser_test.go
deleted file mode 100644
index 7502c317e..000000000
--- a/config/parser_test.go
+++ /dev/null
@@ -1,330 +0,0 @@
-package config
-
-import (
- "os"
- "strings"
- "testing"
-)
-
-func TestNewParser(t *testing.T) {
- filePath := "./parser_test.go"
- expected := "parser_test.go"
-
- file, err := os.Open(filePath)
- if err != nil {
- t.Fatal("Could not open file")
- }
- defer file.Close()
-
- p, err := newParser(file)
- if err != nil {
- t.Fatal(err)
- }
-
- if p.filename != expected {
- t.Errorf("Expected parser to have filename '%s' but had '%s'", expected, p.filename)
- }
-
- if p == nil {
- t.Error("Expected parser to not be nil, but it was")
- }
-}
-
-func TestParserBasic(t *testing.T) {
- p := &parser{filename: "test"}
-
- input := `localhost:1234
- root /test/www
- tls cert.pem key.pem`
-
- p.lexer.load(strings.NewReader(input))
-
- confs, err := p.parse()
- if err != nil {
- t.Fatalf("Expected no errors, but got '%s'", err)
- }
- conf := confs[0]
-
- if conf.Host != "localhost" {
- t.Errorf("Expected host to be 'localhost', got '%s'", conf.Host)
- }
- if conf.Port != "1234" {
- t.Errorf("Expected port to be '1234', got '%s'", conf.Port)
- }
- if conf.Root != "/test/www" {
- t.Errorf("Expected root to be '/test/www', got '%s'", conf.Root)
- }
- if !conf.TLS.Enabled {
- t.Error("Expected TLS to be enabled, but it wasn't")
- }
- if conf.TLS.Certificate != "cert.pem" {
- t.Errorf("Expected TLS certificate to be 'cert.pem', got '%s'", conf.TLS.Certificate)
- }
- if conf.TLS.Key != "key.pem" {
- t.Errorf("Expected TLS server key to be 'key.pem', got '%s'", conf.TLS.Key)
- }
-}
-
-func TestParserBasicWithMultipleServerBlocks(t *testing.T) {
- p := &parser{filename: "test"}
-
- input := `host1.com:443 {
- root /test/www
- tls cert.pem key.pem
- }
-
- host2:80 {
- root "/test/my site"
- }`
-
- p.lexer.load(strings.NewReader(input))
-
- confs, err := p.parse()
- if err != nil {
- t.Fatalf("Expected no errors, but got '%s'", err)
- }
- if len(confs) != 2 {
- t.Fatalf("Expected 2 configurations, but got %d: %#v", len(confs), confs)
- }
-
- // First server
- if confs[0].Host != "host1.com" {
- t.Errorf("Expected first host to be 'host1.com', got '%s'", confs[0].Host)
- }
- if confs[0].Port != "443" {
- t.Errorf("Expected first port to be '443', got '%s'", confs[0].Port)
- }
- if confs[0].Root != "/test/www" {
- t.Errorf("Expected first root to be '/test/www', got '%s'", confs[0].Root)
- }
- if !confs[0].TLS.Enabled {
- t.Error("Expected first TLS to be enabled, but it wasn't")
- }
- if confs[0].TLS.Certificate != "cert.pem" {
- t.Errorf("Expected first TLS certificate to be 'cert.pem', got '%s'", confs[0].TLS.Certificate)
- }
- if confs[0].TLS.Key != "key.pem" {
- t.Errorf("Expected first TLS server key to be 'key.pem', got '%s'", confs[0].TLS.Key)
- }
-
- // Second server
- if confs[1].Host != "host2" {
- t.Errorf("Expected second host to be 'host2', got '%s'", confs[1].Host)
- }
- if confs[1].Port != "80" {
- t.Errorf("Expected second port to be '80', got '%s'", confs[1].Port)
- }
- if confs[1].Root != "/test/my site" {
- t.Errorf("Expected second root to be '/test/my site', got '%s'", confs[1].Root)
- }
- if confs[1].TLS.Enabled {
- t.Error("Expected second TLS to be disabled, but it was enabled")
- }
- if confs[1].TLS.Certificate != "" {
- t.Errorf("Expected second TLS certificate to be '', got '%s'", confs[1].TLS.Certificate)
- }
- if confs[1].TLS.Key != "" {
- t.Errorf("Expected second TLS server key to be '', got '%s'", confs[1].TLS.Key)
- }
-}
-
-func TestParserBasicWithMultipleHostsPerBlock(t *testing.T) {
- // This test is table-driven; it is expected that each
- // input string produce the same set of configs.
- for _, input := range []string{
- `host1.com host2.com:1234
- root /public_html`, // space-separated, no block
-
- `host1.com, host2.com:1234
- root /public_html`, // comma-separated, no block
-
- `host1.com,
- host2.com:1234
- root /public_html`, // comma-separated, newlines, no block
-
- `host1.com host2.com:1234 {
- root /public_html
- }`, // space-separated, block
-
- `host1.com, host2.com:1234 {
- root /public_html
- }`, // comma-separated, block
-
- `host1.com,
- host2.com:1234 {
- root /public_html
- }`, // comma-separated, newlines, block
- } {
-
- p := &parser{filename: "test"}
- p.lexer.load(strings.NewReader(input))
-
- confs, err := p.parse()
- if err != nil {
- t.Fatalf("Expected no errors, but got '%s'", err)
- }
- if len(confs) != 2 {
- t.Fatalf("Expected 2 configurations, but got %d: %#v", len(confs), confs)
- }
-
- if confs[0].Host != "host1.com" {
- t.Errorf("Expected host of first conf to be 'host1.com', got '%s'", confs[0].Host)
- }
- if confs[0].Port != DefaultPort {
- t.Errorf("Expected port of first conf to be '%s', got '%s'", DefaultPort, confs[0].Port)
- }
- if confs[0].Root != "/public_html" {
- t.Errorf("Expected root of first conf to be '/public_html', got '%s'", confs[0].Root)
- }
-
- if confs[1].Host != "host2.com" {
- t.Errorf("Expected host of second conf to be 'host2.com', got '%s'", confs[1].Host)
- }
- if confs[1].Port != "1234" {
- t.Errorf("Expected port of second conf to be '1234', got '%s'", confs[1].Port)
- }
- if confs[1].Root != "/public_html" {
- t.Errorf("Expected root of second conf to be '/public_html', got '%s'", confs[1].Root)
- }
-
- }
-}
-
-func TestParserBasicWithAlternateAddressStyles(t *testing.T) {
- p := &parser{filename: "test"}
- input := `http://host1.com, https://host2.com,
- host3.com:http, host4.com:1234 {
- root /test/www
- }`
- p.lexer.load(strings.NewReader(input))
-
- confs, err := p.parse()
- if err != nil {
- t.Fatalf("Expected no errors, but got '%s'", err)
- }
- if len(confs) != 4 {
- t.Fatalf("Expected 4 configurations, but got %d: %#v", len(confs), confs)
- }
-
- for _, conf := range confs {
- if conf.Root != "/test/www" {
- t.Fatalf("Expected root for conf of %s to be '/test/www', but got: %s", conf.Address(), conf.Root)
- }
- }
-
- p = &parser{filename: "test"}
- input = `host:port, http://host:port, http://host, https://host:port, host`
- p.lexer.load(strings.NewReader(input))
-
- confs, err = p.parse()
- if err != nil {
- t.Fatalf("Expected no errors, but got '%s'", err)
- }
- if len(confs) != 5 {
- t.Fatalf("Expected 5 configurations, but got %d: %#v", len(confs), confs)
- }
-
- if confs[0].Host != "host" {
- t.Errorf("Expected conf[0] Host='host', got '%#v'", confs[0])
- }
- if confs[0].Port != "port" {
- t.Errorf("Expected conf[0] Port='port', got '%#v'", confs[0])
- }
-
- if confs[1].Host != "host" {
- t.Errorf("Expected conf[1] Host='host', got '%#v'", confs[1])
- }
- if confs[1].Port != "port" {
- t.Errorf("Expected conf[1] Port='port', got '%#v'", confs[1])
- }
-
- if confs[2].Host != "host" {
- t.Errorf("Expected conf[2] Host='host', got '%#v'", confs[2])
- }
- if confs[2].Port != "http" {
- t.Errorf("Expected conf[2] Port='http', got '%#v'", confs[2])
- }
-
- if confs[3].Host != "host" {
- t.Errorf("Expected conf[3] Host='host', got '%#v'", confs[3])
- }
- if confs[3].Port != "port" {
- t.Errorf("Expected conf[3] Port='port', got '%#v'", confs[3])
- }
-
- if confs[4].Host != "host" {
- t.Errorf("Expected conf[4] Host='host', got '%#v'", confs[4])
- }
- if confs[4].Port != DefaultPort {
- t.Errorf("Expected conf[4] Port='%s', got '%#v'", DefaultPort, confs[4].Port)
- }
-}
-
-func TestParserImport(t *testing.T) {
- p := &parser{filename: "test"}
-
- input := `host:123
- import import_test.txt`
-
- p.lexer.load(strings.NewReader(input))
-
- confs, err := p.parse()
- if err != nil {
- t.Fatalf("Expected no errors, but got '%s'", err)
- }
- conf := confs[0]
-
- if conf.Host != "host" {
- t.Errorf("Expected host to be 'host', got '%s'", conf.Host)
- }
- if conf.Port != "123" {
- t.Errorf("Expected port to be '123', got '%s'", conf.Port)
- }
- if conf.Root != "/test/imported/public_html" {
- t.Errorf("Expected root to be '/test/imported/public_html', got '%s'", conf.Root)
- }
- if conf.TLS.Enabled {
- t.Error("Expected TLS to be disabled, but it was enabled")
- }
- if conf.TLS.Certificate != "" {
- t.Errorf("Expected TLS certificate to be '', got '%s'", conf.TLS.Certificate)
- }
- if conf.TLS.Key != "" {
- t.Errorf("Expected TLS server key to be '', got '%s'", conf.TLS.Key)
- }
-}
-
-func TestParserLocationContext(t *testing.T) {
- p := &parser{filename: "test"}
-
- input := `host:123 {
- /scope {
- gzip
- }
- }`
-
- p.lexer.load(strings.NewReader(input))
-
- confs, err := p.parse()
- if err != nil {
- t.Fatalf("Expected no errors, but got '%s'", err)
- }
- if len(confs) != 1 {
- t.Fatalf("Expected 1 configuration, but got %d: %#v", len(confs), confs)
- }
-
- if len(p.other) != 2 {
- t.Fatalf("Expected 2 path scopes, but got %d: %#v", len(p.other), p.other)
- }
-
- if p.other[0].path != "/" {
- t.Fatalf("Expected first path scope to be default '/', but got %v: %#v", p.other[0].path, p.other)
- }
- if p.other[1].path != "/scope" {
- t.Fatalf("Expected first path scope to be '/scope', but got %v: %#v", p.other[0].path, p.other)
- }
-
- if dir, ok := p.other[1].directives["gzip"]; !ok {
- t.Fatalf("Expected scoped directive to be gzip, but got %v: %#v", dir, p.other[1].directives)
- }
-}
diff --git a/config/parsing.go b/config/parsing.go
deleted file mode 100644
index 0da600b30..000000000
--- a/config/parsing.go
+++ /dev/null
@@ -1,318 +0,0 @@
-package config
-
-import (
- "errors"
- "net"
- "strings"
-)
-
-// This file contains the recursive-descent parsing
-// functions.
-
-// begin is the top of the recursive-descent parsing.
-// It parses at most one server configuration (an address
-// and its directives).
-func (p *parser) begin() error {
- err := p.addresses()
- if err != nil {
- return err
- }
-
- err = p.addressBlock()
- if err != nil {
- return err
- }
-
- return nil
-}
-
-// addresses expects that the current token is a
-// "scheme://host:port" combination (the "scheme://"
-// and/or ":port" portions may be omitted). If multiple
-// addresses are specified, they must be space-
-// separated on the same line, or each token must end
-// with a comma.
-func (p *parser) addresses() error {
- var expectingAnother bool
- p.hosts = []hostPort{}
-
- // address gets host and port in a format accepted by net.Dial
- address := func(str string) (host, port string, err error) {
- var schemePort string
-
- if strings.HasPrefix(str, "https://") {
- schemePort = "https"
- str = str[8:]
- } else if strings.HasPrefix(str, "http://") {
- schemePort = "http"
- str = str[7:]
- } else if !strings.Contains(str, ":") {
- str += ":" + Port
- }
-
- host, port, err = net.SplitHostPort(str)
- if err != nil && schemePort != "" {
- host = str
- port = schemePort // assume port from scheme
- err = nil
- }
-
- return
- }
-
- for {
- tkn, startLine := p.tkn(), p.line()
-
- // Open brace definitely indicates end of addresses
- if tkn == "{" {
- if expectingAnother {
- return p.err("Syntax", "Expected another address but had '"+tkn+"' - check for extra comma")
- }
- break
- }
-
- // Trailing comma indicates another address will follow, which
- // may possibly be on the next line
- if tkn[len(tkn)-1] == ',' {
- tkn = tkn[:len(tkn)-1]
- expectingAnother = true
- } else {
- expectingAnother = false // but we may still see another one on this line
- }
-
- // Parse and save this address
- host, port, err := address(tkn)
- if err != nil {
- return err
- }
- p.hosts = append(p.hosts, hostPort{host, port})
-
- // Advance token and possibly break out of loop or return error
- hasNext := p.next()
- if expectingAnother && !hasNext {
- return p.eofErr()
- }
- if !expectingAnother && p.line() > startLine {
- break
- }
- if !hasNext {
- p.eof = true
- break // EOF
- }
- }
-
- return nil
-}
-
-// addressBlock leads into parsing directives, including
-// possible opening/closing curly braces around the block.
-// It handles directives enclosed by curly braces and
-// directives not enclosed by curly braces. It is expected
-// that the current token is already the beginning of
-// the address block.
-func (p *parser) addressBlock() error {
- errOpenCurlyBrace := p.openCurlyBrace()
- if errOpenCurlyBrace != nil {
- // meh, single-server configs don't need curly braces
- // but we read a token and we won't consume it; mark it unused
- p.unused = &p.lexer.token
- }
-
- // When we enter an address block, we also implicitly
- // enter a path block where the path is all paths ("/")
- p.other = append(p.other, locationContext{
- path: "/",
- directives: make(map[string]*controller),
- })
- p.scope = &p.other[0]
-
- if p.eof {
- // this happens if the Caddyfile consists of only
- // a line of addresses and nothing else
- return nil
- }
-
- err := p.directives()
- if err != nil {
- return err
- }
-
- // Only look for close curly brace if there was an opening
- if errOpenCurlyBrace == nil {
- err = p.closeCurlyBrace()
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// openCurlyBrace expects the current token to be an
-// opening curly brace. This acts like an assertion
-// because it returns an error if the token is not
-// a opening curly brace. It does not advance the token.
-func (p *parser) openCurlyBrace() error {
- if p.tkn() != "{" {
- return p.syntaxErr("{")
- }
- return nil
-}
-
-// closeCurlyBrace expects the current token to be
-// a closing curly brace. This acts like an assertion
-// because it returns an error if the token is not
-// a closing curly brace. It does not advance the token.
-func (p *parser) closeCurlyBrace() error {
- if p.tkn() != "}" {
- return p.syntaxErr("}")
- }
- return nil
-}
-
-// directives parses through all the directives
-// and it expects the current token to be the first
-// directive. It goes until EOF or closing curly
-// brace which ends the address block.
-func (p *parser) directives() error {
- for p.next() {
- if p.tkn() == "}" {
- // end of address scope
- break
- }
- if p.tkn()[0] == '/' || p.tkn()[0] == '*' {
- // Path scope (a.k.a. location context)
- // Starts with / ('starts with') or * ('ends with').
-
- // TODO: The parser can handle the syntax (obviously), but the
- // implementation is incomplete. This is intentional,
- // until we can better decide what kind of feature set we
- // want to support and how exactly we want these location
- // scopes to work. Until this is ready, we leave this
- // syntax undocumented. Some changes will need to be
- // made in parser.go also (the unwrap function) and
- // probably in server.go when we do this... see those TODOs.
-
- var scope *locationContext
-
- // If the path block is a duplicate, append to existing one
- for i := 0; i < len(p.other); i++ {
- if p.other[i].path == p.tkn() {
- scope = &p.other[i]
- break
- }
- }
-
- // Otherwise, for a new path we haven't seen before, create a new context
- if scope == nil {
- scope = &locationContext{
- path: p.tkn(),
- directives: make(map[string]*controller),
- }
- }
-
- // Consume the opening curly brace
- if !p.next() {
- return p.eofErr()
- }
- err := p.openCurlyBrace()
- if err != nil {
- return err
- }
-
- // Use this path scope as our current context for just a moment
- p.scope = scope
-
- // Consume each directive in the path block
- for p.next() {
- err := p.closeCurlyBrace()
- if err == nil {
- break
- }
-
- err = p.directive()
- if err != nil {
- return err
- }
- }
-
- // Save the new scope and put the current scope back to "/"
- p.other = append(p.other, *scope)
- p.scope = &p.other[0]
-
- } else if err := p.directive(); err != nil {
- return err
- }
- }
- return nil
-}
-
-// directive asserts that the current token is either a built-in
-// directive or a registered middleware directive; otherwise an error
-// will be returned. If it is a valid directive, tokens will be
-// collected.
-func (p *parser) directive() error {
- if fn, ok := validDirectives[p.tkn()]; ok {
- // Built-in (standard, or 'core') directive
- err := fn(p)
- if err != nil {
- return err
- }
- } else if middlewareRegistered(p.tkn()) {
- // Middleware directive
- err := p.collectTokens()
- if err != nil {
- return err
- }
- } else {
- return p.err("Syntax", "Unexpected token '"+p.tkn()+"', expecting a valid directive")
- }
- return nil
-}
-
-// collectTokens consumes tokens until the directive's scope
-// closes (either end of line or end of curly brace block).
-// It creates a controller which is stored in the parser for
-// later use by the middleware.
-func (p *parser) collectTokens() error {
- if p.scope == nil {
- return errors.New("Current scope cannot be nil")
- }
-
- directive := p.tkn()
- line := p.line()
- nesting := 0
- cont := newController(p)
-
- // Re-use a duplicate directive's controller from before
- // (the parsing logic in the middleware generator must
- // account for multiple occurrences of its directive, even
- // if that means returning an error or overwriting settings)
- if existing, ok := p.scope.directives[directive]; ok {
- cont = existing
- }
-
- // The directive is appended as a relevant token
- cont.tokens = append(cont.tokens, p.lexer.token)
-
- for p.next() {
- if p.tkn() == "{" {
- nesting++
- } else if p.line() > line && nesting == 0 {
- p.unused = &p.lexer.token
- break
- } else if p.tkn() == "}" && nesting > 0 {
- nesting--
- } else if p.tkn() == "}" && nesting == 0 {
- return p.err("Syntax", "Unexpected '}' because no matching opening brace")
- }
- cont.tokens = append(cont.tokens, p.lexer.token)
- }
-
- if nesting > 0 {
- return p.eofErr()
- }
-
- p.scope.directives[directive] = cont
- return nil
-}
diff --git a/config/parsing_test.go b/config/parsing_test.go
deleted file mode 100644
index d912156be..000000000
--- a/config/parsing_test.go
+++ /dev/null
@@ -1 +0,0 @@
-package config