From bc2e406572e9af290469b2d4d58974ac99c0b492 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Sun, 16 Feb 2020 15:28:27 -0700 Subject: httpcaddyfile: Refactor global options parsing; prevent duplicate keys --- caddyconfig/httpcaddyfile/httptype.go | 99 ++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 36 deletions(-) diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index 4773ac8..f93b8c5 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -43,49 +43,31 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock, var warnings []caddyconfig.Warning gc := counter{new(int)} + // load all the server blocks and associate them with a "pile" + // of config values; also prohibit duplicate keys because they + // can make a config confusing if more than one server block is + // chosen to handle a request - we actually will make each + // server block's route terminal so that only one will run + sbKeys := make(map[string]struct{}) var serverBlocks []serverBlock - for _, sblock := range originalServerBlocks { + for i, sblock := range originalServerBlocks { + for j, k := range sblock.Keys { + if _, ok := sbKeys[k]; ok { + return nil, warnings, fmt.Errorf("duplicate site address not allowed: '%s' in %v (site block %d, key %d)", k, sblock.Keys, i, j) + } + sbKeys[k] = struct{}{} + } serverBlocks = append(serverBlocks, serverBlock{ block: sblock, pile: make(map[string][]ConfigValue), }) } - // global configuration - if len(serverBlocks) > 0 && len(serverBlocks[0].block.Keys) == 0 { - sb := serverBlocks[0] - for _, segment := range sb.block.Segments { - dir := segment.Directive() - var val interface{} - var err error - disp := caddyfile.NewDispenser(segment) - // TODO: make this switch into a map - switch dir { - case "http_port": - val, err = parseOptHTTPPort(disp) - case "https_port": - val, err = parseOptHTTPSPort(disp) - case "order": - val, err = parseOptOrder(disp) - case "experimental_http3": - val, err = parseOptExperimentalHTTP3(disp) - case "storage": - val, err = parseOptStorage(disp) - case "acme_ca", "acme_dns", "acme_ca_root": - val, err = parseOptACME(disp) - case "email": - val, err = parseOptEmail(disp) - case "admin": - val, err = parseOptAdmin(disp) - default: - return nil, warnings, fmt.Errorf("unrecognized parameter name: %s", dir) - } - if err != nil { - return nil, warnings, fmt.Errorf("%s: %v", dir, err) - } - options[dir] = val - } - serverBlocks = serverBlocks[1:] + // apply any global options + var err error + serverBlocks, err = st.evaluateGlobalOptionsBlock(serverBlocks, options) + if err != nil { + return nil, warnings, err } for _, sb := range serverBlocks { @@ -129,6 +111,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock, } } + // evaluate each directive ("segment") in this block for _, segment := range sb.block.Segments { dir := segment.Directive() @@ -288,6 +271,50 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock, return cfg, warnings, nil } +// evaluateGlobalOptionsBlock evaluates the global options block, +// which is expected to be the first server block if it has zero +// keys. It returns the updated list of server blocks with the +// global options block removed, and updates options accordingly. +func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options map[string]interface{}) ([]serverBlock, error) { + if len(serverBlocks) == 0 || len(serverBlocks[0].block.Keys) > 0 { + return serverBlocks, nil + } + + for _, segment := range serverBlocks[0].block.Segments { + dir := segment.Directive() + var val interface{} + var err error + disp := caddyfile.NewDispenser(segment) + // TODO: make this switch into a map + switch dir { + case "http_port": + val, err = parseOptHTTPPort(disp) + case "https_port": + val, err = parseOptHTTPSPort(disp) + case "order": + val, err = parseOptOrder(disp) + case "experimental_http3": + val, err = parseOptExperimentalHTTP3(disp) + case "storage": + val, err = parseOptStorage(disp) + case "acme_ca", "acme_dns", "acme_ca_root": + val, err = parseOptACME(disp) + case "email": + val, err = parseOptEmail(disp) + case "admin": + val, err = parseOptAdmin(disp) + default: + return nil, fmt.Errorf("unrecognized parameter name: %s", dir) + } + if err != nil { + return nil, fmt.Errorf("%s: %v", dir, err) + } + options[dir] = val + } + + return serverBlocks[1:], nil +} + // hostsFromServerBlockKeys returns a list of all the // hostnames found in the keys of the server block sb. // The list may not be in a consistent order. -- cgit v1.2.3