2019-08-09 13:05:47 -05:00
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpcaddyfile
import (
"encoding/json"
"fmt"
"reflect"
2019-08-21 11:46:35 -05:00
"sort"
2019-08-09 13:05:47 -05:00
"strings"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddytls"
2019-08-21 11:46:35 -05:00
"github.com/mholt/certmagic"
2019-08-09 13:05:47 -05:00
)
func init ( ) {
caddyconfig . RegisterAdapter ( "caddyfile" , caddyfile . Adapter { ServerType : ServerType { } } )
}
// ServerType can set up a config from an HTTP Caddyfile.
type ServerType struct {
}
// Setup makes a config from the tokens.
func ( st ServerType ) Setup ( originalServerBlocks [ ] caddyfile . ServerBlock ,
2019-08-22 14:38:37 -05:00
options map [ string ] interface { } ) ( * caddy . Config , [ ] caddyconfig . Warning , error ) {
2019-08-09 13:05:47 -05:00
var warnings [ ] caddyconfig . Warning
2019-08-21 11:46:35 -05:00
var serverBlocks [ ] serverBlock
for _ , sblock := range originalServerBlocks {
serverBlocks = append ( serverBlocks , serverBlock {
block : sblock ,
pile : make ( map [ string ] [ ] ConfigValue ) ,
} )
}
2019-08-22 14:38:37 -05:00
// 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
2019-09-19 13:42:36 -05:00
disp := caddyfile . NewDispenser ( segment )
2019-09-30 10:11:30 -05:00
// TODO: make this switch into a map
2019-08-22 14:38:37 -05:00
switch dir {
case "http_port" :
2019-09-19 13:42:36 -05:00
val , err = parseOptHTTPPort ( disp )
2019-08-22 14:38:37 -05:00
case "https_port" :
2019-09-19 13:42:36 -05:00
val , err = parseOptHTTPSPort ( disp )
2019-08-22 15:26:33 -05:00
case "handler_order" :
2019-09-19 13:42:36 -05:00
val , err = parseOptHandlerOrder ( disp )
2019-09-11 18:16:21 -05:00
case "experimental_http3" :
2019-09-19 13:42:36 -05:00
val , err = parseOptExperimentalHTTP3 ( disp )
case "storage" :
val , err = parseOptStorage ( disp )
2019-09-30 10:11:30 -05:00
case "acme_ca" :
val , err = parseOptACMECA ( disp )
case "email" :
val , err = parseOptEmail ( disp )
2019-10-30 16:12:42 -05:00
case "admin" :
val , err = parseOptAdmin ( disp )
2019-08-22 14:38:37 -05:00
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 : ]
}
2019-08-21 11:46:35 -05:00
for _ , sb := range serverBlocks {
2019-08-21 12:03:50 -05:00
// replace shorthand placeholders (which are
// convenient when writing a Caddyfile) with
// their actual placeholder identifiers or
// variable names
replacer := strings . NewReplacer (
"{uri}" , "{http.request.uri}" ,
"{path}" , "{http.request.uri.path}" ,
"{host}" , "{http.request.host}" ,
"{hostport}" , "{http.request.hostport}" ,
"{method}" , "{http.request.method}" ,
"{scheme}" , "{http.request.scheme}" ,
"{file}" , "{http.request.uri.path.file}" ,
"{dir}" , "{http.request.uri.path.dir}" ,
2019-08-27 15:41:57 -05:00
"{query}" , "{http.request.uri.query_string}" ,
2019-08-21 12:03:50 -05:00
)
for _ , segment := range sb . block . Segments {
for i := 0 ; i < len ( segment ) ; i ++ {
segment [ i ] . Text = replacer . Replace ( segment [ i ] . Text )
}
}
2019-08-22 14:38:37 -05:00
if len ( sb . block . Keys ) == 0 {
return nil , warnings , fmt . Errorf ( "server block without any key is global configuration, and if used, it must be first" )
}
2019-08-21 11:46:35 -05:00
// extract matcher definitions
d := sb . block . DispenseDirective ( "matcher" )
2019-09-30 10:11:30 -05:00
matcherDefs , err := parseMatcherDefinitions ( d )
2019-08-21 11:46:35 -05:00
if err != nil {
2019-08-22 14:38:37 -05:00
return nil , warnings , err
2019-08-21 11:46:35 -05:00
}
for _ , segment := range sb . block . Segments {
dir := segment . Directive ( )
if dir == "matcher" {
// TODO: This is a special case because we pre-processed it; handle this better
continue
}
if dirFunc , ok := registeredDirectives [ dir ] ; ok {
results , err := dirFunc ( Helper {
2019-08-21 16:23:00 -05:00
Dispenser : caddyfile . NewDispenser ( segment ) ,
2019-09-30 10:11:30 -05:00
options : options ,
2019-08-21 11:46:35 -05:00
warnings : & warnings ,
matcherDefs : matcherDefs ,
2019-08-21 16:50:02 -05:00
parentBlock : sb . block ,
2019-08-21 11:46:35 -05:00
} )
if err != nil {
return nil , warnings , fmt . Errorf ( "parsing caddyfile tokens for '%s': %v" , dir , err )
}
for _ , result := range results {
result . directive = dir
sb . pile [ result . Class ] = append ( sb . pile [ result . Class ] , result )
}
} else {
2019-08-21 12:03:50 -05:00
tkn := segment [ 0 ]
return nil , warnings , fmt . Errorf ( "%s:%d: unrecognized directive: %s" , tkn . File , tkn . Line , dir )
2019-08-21 11:46:35 -05:00
}
}
}
2019-08-09 13:05:47 -05:00
// map
2019-08-21 11:46:35 -05:00
sbmap , err := st . mapAddressToServerBlocks ( serverBlocks )
2019-08-09 13:05:47 -05:00
if err != nil {
return nil , warnings , err
}
// reduce
pairings := st . consolidateAddrMappings ( sbmap )
// each pairing of listener addresses to list of server
// blocks is basically a server definition
2019-08-22 15:26:33 -05:00
servers , err := st . serversFromPairings ( pairings , options , & warnings )
2019-08-09 13:05:47 -05:00
if err != nil {
return nil , warnings , err
}
// now that each server is configured, make the HTTP app
httpApp := caddyhttp . App {
2019-08-22 14:38:37 -05:00
HTTPPort : tryInt ( options [ "http_port" ] , & warnings ) ,
HTTPSPort : tryInt ( options [ "https_port" ] , & warnings ) ,
2019-08-09 13:05:47 -05:00
Servers : servers ,
}
// now for the TLS app! (TODO: refactor into own func)
tlsApp := caddytls . TLS { Certificates : make ( map [ string ] json . RawMessage ) }
for _ , p := range pairings {
2019-09-30 10:11:30 -05:00
for i , sblock := range p . serverBlocks {
2019-08-21 11:46:35 -05:00
// tls automation policies
if mmVals , ok := sblock . pile [ "tls.automation_manager" ] ; ok {
for _ , mmVal := range mmVals {
mm := mmVal . Value . ( caddytls . ManagerMaker )
sblockHosts , err := st . autoHTTPSHosts ( sblock )
2019-08-09 13:05:47 -05:00
if err != nil {
2019-08-21 11:46:35 -05:00
return nil , warnings , err
2019-08-09 13:05:47 -05:00
}
2019-09-30 10:11:30 -05:00
if len ( sblockHosts ) > 0 {
2019-09-30 12:53:21 -05:00
if tlsApp . Automation == nil {
tlsApp . Automation = new ( caddytls . AutomationConfig )
}
2019-09-30 10:11:30 -05:00
tlsApp . Automation . Policies = append ( tlsApp . Automation . Policies , caddytls . AutomationPolicy {
Hosts : sblockHosts ,
ManagementRaw : caddyconfig . JSONModuleObject ( mm , "module" , mm . ( caddy . Module ) . CaddyModule ( ) . ID ( ) , & warnings ) ,
} )
} else {
warnings = append ( warnings , caddyconfig . Warning {
Message : fmt . Sprintf ( "Server block %d %v has no names that qualify for automatic HTTPS, so no TLS automation policy will be added." , i , sblock . block . Keys ) ,
} )
}
2019-08-09 13:05:47 -05:00
}
2019-08-21 11:46:35 -05:00
}
2019-08-09 13:05:47 -05:00
2019-08-21 11:46:35 -05:00
// tls certificate loaders
if clVals , ok := sblock . pile [ "tls.certificate_loader" ] ; ok {
for _ , clVal := range clVals {
loader := clVal . Value . ( caddytls . CertificateLoader )
2019-09-13 10:45:10 -05:00
loaderName := caddy . GetModuleID ( loader )
2019-08-09 13:05:47 -05:00
tlsApp . Certificates [ loaderName ] = caddyconfig . JSON ( loader , & warnings )
}
}
}
}
2019-09-30 10:11:30 -05:00
// if global ACME CA or email were set, append a catch-all automation
// policy that ensures they will be used if no tls directive was used
acmeCA , hasACMECA := options [ "acme_ca" ]
email , hasEmail := options [ "email" ]
if hasACMECA || hasEmail {
if tlsApp . Automation == nil {
tlsApp . Automation = new ( caddytls . AutomationConfig )
}
2019-10-28 16:08:45 -05:00
if ! hasACMECA {
acmeCA = ""
}
if ! hasEmail {
email = ""
}
2019-09-30 10:11:30 -05:00
tlsApp . Automation . Policies = append ( tlsApp . Automation . Policies , caddytls . AutomationPolicy {
ManagementRaw : caddyconfig . JSONModuleObject ( caddytls . ACMEManagerMaker {
CA : acmeCA . ( string ) ,
Email : email . ( string ) ,
} , "module" , "acme" , & warnings ) ,
} )
}
if tlsApp . Automation != nil {
// consolidate automation policies that are the exact same
tlsApp . Automation . Policies = consolidateAutomationPolicies ( tlsApp . Automation . Policies )
}
2019-08-09 13:05:47 -05:00
2019-09-11 18:16:21 -05:00
// if experimental HTTP/3 is enabled, enable it on each server
if enableH3 , ok := options [ "experimental_http3" ] . ( bool ) ; ok && enableH3 {
for _ , srv := range httpApp . Servers {
srv . ExperimentalHTTP3 = true
}
}
2019-08-09 13:05:47 -05:00
// annnd the top-level config, then we're done!
cfg := & caddy . Config { AppsRaw : make ( map [ string ] json . RawMessage ) }
if ! reflect . DeepEqual ( httpApp , caddyhttp . App { } ) {
cfg . AppsRaw [ "http" ] = caddyconfig . JSON ( httpApp , & warnings )
}
2019-09-30 10:11:30 -05:00
if ! reflect . DeepEqual ( tlsApp , caddytls . TLS { Certificates : make ( map [ string ] json . RawMessage ) } ) {
2019-08-09 13:05:47 -05:00
cfg . AppsRaw [ "tls" ] = caddyconfig . JSON ( tlsApp , & warnings )
}
2019-09-19 13:42:36 -05:00
if storageCvtr , ok := options [ "storage" ] . ( caddy . StorageConverter ) ; ok {
2019-09-26 19:06:15 -05:00
cfg . StorageRaw = caddyconfig . JSONModuleObject ( storageCvtr ,
"module" ,
storageCvtr . ( caddy . Module ) . CaddyModule ( ) . ID ( ) ,
& warnings )
2019-09-19 13:42:36 -05:00
}
2019-10-30 16:12:42 -05:00
if adminConfig , ok := options [ "admin" ] . ( string ) ; ok && adminConfig != "" {
cfg . Admin = & caddy . AdminConfig { Listen : adminConfig }
}
2019-08-09 13:05:47 -05:00
return cfg , warnings , 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.
func ( st * ServerType ) hostsFromServerBlockKeys ( sb caddyfile . ServerBlock ) ( [ ] string , error ) {
// first get each unique hostname
hostMap := make ( map [ string ] struct { } )
for _ , sblockKey := range sb . Keys {
2019-08-21 11:46:35 -05:00
addr , err := ParseAddress ( sblockKey )
2019-08-09 13:05:47 -05:00
if err != nil {
return nil , fmt . Errorf ( "parsing server block key: %v" , err )
}
2019-08-21 11:46:35 -05:00
addr = addr . Normalize ( )
2019-08-09 13:05:47 -05:00
hostMap [ addr . Host ] = struct { } { }
}
// convert map to slice
sblockHosts := make ( [ ] string , 0 , len ( hostMap ) )
for host := range hostMap {
sblockHosts = append ( sblockHosts , host )
}
return sblockHosts , nil
}
// serversFromPairings creates the servers for each pairing of addresses
// to server blocks. Each pairing is essentially a server definition.
2019-08-22 15:26:33 -05:00
func ( st * ServerType ) serversFromPairings (
pairings [ ] sbAddrAssociation ,
options map [ string ] interface { } ,
warnings * [ ] caddyconfig . Warning ,
) ( map [ string ] * caddyhttp . Server , error ) {
2019-08-09 13:05:47 -05:00
servers := make ( map [ string ] * caddyhttp . Server )
for i , p := range pairings {
srv := & caddyhttp . Server {
Listen : p . addresses ,
}
for _ , sblock := range p . serverBlocks {
2019-08-21 11:46:35 -05:00
matcherSetsEnc , err := st . compileEncodedMatcherSets ( sblock . block )
2019-08-09 13:05:47 -05:00
if err != nil {
2019-08-21 11:46:35 -05:00
return nil , fmt . Errorf ( "server block %v: compiling matcher sets: %v" , sblock . block . Keys , err )
2019-08-09 13:05:47 -05:00
}
2019-08-21 11:46:35 -05:00
// if there are user-defined variables, then siteVarSubroute will
// wrap the handlerSubroute; otherwise handlerSubroute will be the
// site's primary subroute.
2019-08-09 13:05:47 -05:00
siteVarSubroute , handlerSubroute := new ( caddyhttp . Subroute ) , new ( caddyhttp . Subroute )
2019-08-21 11:46:35 -05:00
// tls: connection policies and toggle auto HTTPS
2019-08-09 13:05:47 -05:00
2019-08-21 11:46:35 -05:00
autoHTTPSQualifiedHosts , err := st . autoHTTPSHosts ( sblock )
if err != nil {
return nil , err
2019-08-09 13:05:47 -05:00
}
2019-09-18 11:51:49 -05:00
if _ , ok := sblock . pile [ "tls.off" ] ; ok && len ( autoHTTPSQualifiedHosts ) > 0 {
2019-08-21 11:46:35 -05:00
// tls off: disable TLS (and automatic HTTPS) for server block's names
2019-09-18 11:51:49 -05:00
if srv . AutoHTTPS == nil {
2019-08-21 11:46:35 -05:00
srv . AutoHTTPS = new ( caddyhttp . AutoHTTPSConfig )
2019-08-09 13:05:47 -05:00
}
2019-08-21 11:46:35 -05:00
srv . AutoHTTPS . Skip = append ( srv . AutoHTTPS . Skip , autoHTTPSQualifiedHosts ... )
} else if cpVals , ok := sblock . pile [ "tls.connection_policy" ] ; ok {
// tls connection policies
for _ , cpVal := range cpVals {
cp := cpVal . Value . ( * caddytls . ConnectionPolicy )
// only create if there is a non-empty policy
if ! reflect . DeepEqual ( cp , new ( caddytls . ConnectionPolicy ) ) {
// make sure the policy covers all hostnames from the block
hosts , err := st . hostsFromServerBlockKeys ( sblock . block )
2019-08-09 13:05:47 -05:00
if err != nil {
return nil , err
}
2019-08-21 11:46:35 -05:00
// TODO: are matchers needed if every hostname of the config is matched?
cp . Matchers = map [ string ] json . RawMessage {
"sni" : caddyconfig . JSON ( hosts , warnings ) , // make sure to match all hosts, not just auto-HTTPS-qualified ones
2019-08-09 13:05:47 -05:00
}
2019-08-21 11:46:35 -05:00
srv . TLSConnPolicies = append ( srv . TLSConnPolicies , cp )
2019-08-09 13:05:47 -05:00
}
}
2019-08-21 11:46:35 -05:00
// TODO: consolidate equal conn policies
2019-08-09 13:05:47 -05:00
}
2019-08-21 11:46:35 -05:00
// vars: special routes that will have to wrap the normal handlers
// so that these variables can be used across their matchers too
for _ , cfgVal := range sblock . pile [ "var" ] {
siteVarSubroute . Routes = append ( siteVarSubroute . Routes , cfgVal . Value . ( caddyhttp . Route ) )
}
2019-08-22 15:26:33 -05:00
// set up each handler directive - the order of the handlers
// as they are added to the routes depends on user preference
2019-08-21 11:46:35 -05:00
dirRoutes := sblock . pile [ "route" ]
2019-08-22 15:26:33 -05:00
handlerOrder , ok := options [ "handler_order" ] . ( [ ] string )
if ! ok {
handlerOrder = defaultDirectiveOrder
}
if len ( handlerOrder ) == 1 && handlerOrder [ 0 ] == "appearance" {
handlerOrder = nil
}
if handlerOrder != nil {
2019-08-21 11:46:35 -05:00
dirPositions := make ( map [ string ] int )
2019-08-22 15:26:33 -05:00
for i , dir := range handlerOrder {
2019-08-21 11:46:35 -05:00
dirPositions [ dir ] = i
2019-08-09 13:05:47 -05:00
}
2019-08-21 11:46:35 -05:00
sort . SliceStable ( dirRoutes , func ( i , j int ) bool {
iDir , jDir := dirRoutes [ i ] . directive , dirRoutes [ j ] . directive
return dirPositions [ iDir ] < dirPositions [ jDir ]
} )
}
for _ , r := range dirRoutes {
handlerSubroute . Routes = append ( handlerSubroute . Routes , r . Value . ( caddyhttp . Route ) )
2019-08-09 13:05:47 -05:00
}
// the route that contains the site's handlers will
// be assumed to be the sub-route for this site...
siteSubroute := handlerSubroute
// ... unless, of course, there are variables that might
// be used by the site's matchers or handlers, in which
// case we need to nest the handlers in a sub-sub-route,
// and the variables go in the sub-route so the variables
// get evaluated first
if len ( siteVarSubroute . Routes ) > 0 {
subSubRoute := caddyhttp . Subroute { Routes : siteSubroute . Routes }
siteSubroute . Routes = append (
siteVarSubroute . Routes ,
caddyhttp . Route {
2019-08-21 11:46:35 -05:00
HandlersRaw : [ ] json . RawMessage {
2019-08-09 13:05:47 -05:00
caddyconfig . JSONModuleObject ( subSubRoute , "handler" , "subroute" , warnings ) ,
} ,
} ,
)
}
siteSubroute . Routes = consolidateRoutes ( siteSubroute . Routes )
srv . Routes = append ( srv . Routes , caddyhttp . Route {
2019-08-21 11:46:35 -05:00
MatcherSetsRaw : matcherSetsEnc ,
HandlersRaw : [ ] json . RawMessage {
2019-08-09 13:05:47 -05:00
caddyconfig . JSONModuleObject ( siteSubroute , "handler" , "subroute" , warnings ) ,
} ,
} )
}
srv . Routes = consolidateRoutes ( srv . Routes )
servers [ fmt . Sprintf ( "srv%d" , i ) ] = srv
}
return servers , nil
}
2019-08-21 11:46:35 -05:00
func ( st ServerType ) autoHTTPSHosts ( sb serverBlock ) ( [ ] string , error ) {
// get the hosts for this server block...
hosts , err := st . hostsFromServerBlockKeys ( sb . block )
if err != nil {
return nil , err
}
// ...and of those, which ones qualify for auto HTTPS
var autoHTTPSQualifiedHosts [ ] string
for _ , h := range hosts {
if certmagic . HostQualifies ( h ) {
autoHTTPSQualifiedHosts = append ( autoHTTPSQualifiedHosts , h )
}
}
return autoHTTPSQualifiedHosts , nil
}
2019-08-09 13:05:47 -05:00
// consolidateRoutes combines routes with the same properties
// (same matchers, same Terminal and Group settings) for a
// cleaner overall output.
func consolidateRoutes ( routes caddyhttp . RouteList ) caddyhttp . RouteList {
for i := 0 ; i < len ( routes ) - 1 ; i ++ {
2019-08-21 11:46:35 -05:00
if reflect . DeepEqual ( routes [ i ] . MatcherSetsRaw , routes [ i + 1 ] . MatcherSetsRaw ) &&
2019-08-09 13:05:47 -05:00
routes [ i ] . Terminal == routes [ i + 1 ] . Terminal &&
routes [ i ] . Group == routes [ i + 1 ] . Group {
// keep the handlers in the same order, then splice out repetitive route
2019-08-21 11:46:35 -05:00
routes [ i ] . HandlersRaw = append ( routes [ i ] . HandlersRaw , routes [ i + 1 ] . HandlersRaw ... )
2019-08-09 13:05:47 -05:00
routes = append ( routes [ : i + 1 ] , routes [ i + 2 : ] ... )
i --
}
}
return routes
}
2019-08-21 11:46:35 -05:00
// consolidateAutomationPolicies combines automation policies that are the same,
// for a cleaner overall output.
func consolidateAutomationPolicies ( aps [ ] caddytls . AutomationPolicy ) [ ] caddytls . AutomationPolicy {
for i := 0 ; i < len ( aps ) ; i ++ {
for j := 0 ; j < len ( aps ) ; j ++ {
if j == i {
continue
2019-08-09 13:05:47 -05:00
}
2019-08-21 11:46:35 -05:00
if reflect . DeepEqual ( aps [ i ] . ManagementRaw , aps [ j ] . ManagementRaw ) {
aps [ i ] . Hosts = append ( aps [ i ] . Hosts , aps [ j ] . Hosts ... )
2019-09-30 10:11:30 -05:00
aps = append ( aps [ : j ] , aps [ j + 1 : ] ... )
i --
break
2019-08-09 13:05:47 -05:00
}
}
}
2019-08-21 11:46:35 -05:00
return aps
2019-08-09 13:05:47 -05:00
}
2019-08-21 11:46:35 -05:00
func matcherSetFromMatcherToken (
2019-08-09 13:05:47 -05:00
tkn caddyfile . Token ,
matcherDefs map [ string ] map [ string ] json . RawMessage ,
warnings * [ ] caddyconfig . Warning ,
) ( map [ string ] json . RawMessage , bool , error ) {
// matcher tokens can be wildcards, simple path matchers,
// or refer to a pre-defined matcher by some name
if tkn . Text == "*" {
// match all requests == no matchers, so nothing to do
return nil , true , nil
2019-11-29 13:23:49 -05:00
} else if strings . HasPrefix ( tkn . Text , "/" ) || strings . HasPrefix ( tkn . Text , "=/" ) {
2019-08-09 13:05:47 -05:00
// convenient way to specify a single path match
return map [ string ] json . RawMessage {
"path" : caddyconfig . JSON ( caddyhttp . MatchPath { tkn . Text } , warnings ) ,
} , true , nil
} else if strings . HasPrefix ( tkn . Text , "match:" ) {
// pre-defined matcher
matcherName := strings . TrimPrefix ( tkn . Text , "match:" )
m , ok := matcherDefs [ matcherName ]
if ! ok {
return nil , false , fmt . Errorf ( "unrecognized matcher name: %+v" , matcherName )
}
return m , true , nil
}
return nil , false , nil
}
func ( st * ServerType ) compileEncodedMatcherSets ( sblock caddyfile . ServerBlock ) ( [ ] map [ string ] json . RawMessage , error ) {
type hostPathPair struct {
hostm caddyhttp . MatchHost
pathm caddyhttp . MatchPath
}
// keep routes with common host and path matchers together
var matcherPairs [ ] * hostPathPair
for _ , key := range sblock . Keys {
2019-08-21 11:46:35 -05:00
addr , err := ParseAddress ( key )
2019-08-09 13:05:47 -05:00
if err != nil {
return nil , fmt . Errorf ( "server block %v: parsing and standardizing address '%s': %v" , sblock . Keys , key , err )
}
2019-08-21 11:46:35 -05:00
addr = addr . Normalize ( )
2019-08-09 13:05:47 -05:00
// choose a matcher pair that should be shared by this
// server block; if none exists yet, create one
var chosenMatcherPair * hostPathPair
for _ , mp := range matcherPairs {
if ( len ( mp . pathm ) == 0 && addr . Path == "" ) ||
( len ( mp . pathm ) == 1 && mp . pathm [ 0 ] == addr . Path ) {
chosenMatcherPair = mp
break
}
}
if chosenMatcherPair == nil {
chosenMatcherPair = new ( hostPathPair )
if addr . Path != "" {
chosenMatcherPair . pathm = [ ] string { addr . Path }
}
matcherPairs = append ( matcherPairs , chosenMatcherPair )
}
// add this server block's keys to the matcher
// pair if it doesn't already exist
if addr . Host != "" {
var found bool
for _ , h := range chosenMatcherPair . hostm {
if h == addr . Host {
found = true
break
}
}
if ! found {
chosenMatcherPair . hostm = append ( chosenMatcherPair . hostm , addr . Host )
}
}
}
// iterate each pairing of host and path matchers and
// put them into a map for JSON encoding
var matcherSets [ ] map [ string ] caddyhttp . RequestMatcher
for _ , mp := range matcherPairs {
matcherSet := make ( map [ string ] caddyhttp . RequestMatcher )
if len ( mp . hostm ) > 0 {
matcherSet [ "host" ] = mp . hostm
}
if len ( mp . pathm ) > 0 {
matcherSet [ "path" ] = mp . pathm
}
if len ( matcherSet ) > 0 {
matcherSets = append ( matcherSets , matcherSet )
}
}
// finally, encode each of the matcher sets
var matcherSetsEnc [ ] map [ string ] json . RawMessage
for _ , ms := range matcherSets {
msEncoded , err := encodeMatcherSet ( ms )
if err != nil {
return nil , fmt . Errorf ( "server block %v: %v" , sblock . Keys , err )
}
matcherSetsEnc = append ( matcherSetsEnc , msEncoded )
}
return matcherSetsEnc , nil
}
2019-09-30 10:11:30 -05:00
func parseMatcherDefinitions ( d * caddyfile . Dispenser ) ( map [ string ] map [ string ] json . RawMessage , error ) {
matchers := make ( map [ string ] map [ string ] json . RawMessage )
for d . Next ( ) {
definitionName := d . Val ( )
for nesting := d . Nesting ( ) ; d . NextBlock ( nesting ) ; {
matcherName := d . Val ( )
mod , err := caddy . GetModule ( "http.matchers." + matcherName )
if err != nil {
return nil , fmt . Errorf ( "getting matcher module '%s': %v" , matcherName , err )
}
unm , ok := mod . New ( ) . ( caddyfile . Unmarshaler )
if ! ok {
return nil , fmt . Errorf ( "matcher module '%s' is not a Caddyfile unmarshaler" , matcherName )
}
err = unm . UnmarshalCaddyfile ( d . NewFromNextTokens ( ) )
if err != nil {
return nil , err
}
rm , ok := unm . ( caddyhttp . RequestMatcher )
if ! ok {
return nil , fmt . Errorf ( "matcher module '%s' is not a request matcher" , matcherName )
}
if _ , ok := matchers [ definitionName ] ; ! ok {
matchers [ definitionName ] = make ( map [ string ] json . RawMessage )
}
matchers [ definitionName ] [ matcherName ] = caddyconfig . JSON ( rm , nil )
}
}
return matchers , nil
}
2019-08-09 13:05:47 -05:00
func encodeMatcherSet ( matchers map [ string ] caddyhttp . RequestMatcher ) ( map [ string ] json . RawMessage , error ) {
msEncoded := make ( map [ string ] json . RawMessage )
for matcherName , val := range matchers {
jsonBytes , err := json . Marshal ( val )
if err != nil {
return nil , fmt . Errorf ( "marshaling matcher set %#v: %v" , matchers , err )
}
msEncoded [ matcherName ] = jsonBytes
}
return msEncoded , nil
}
2019-08-22 14:38:37 -05:00
// tryInt tries to convert val to an integer. If it fails,
// it downgrades the error to a warning and returns 0.
func tryInt ( val interface { } , warnings * [ ] caddyconfig . Warning ) int {
intVal , ok := val . ( int )
if val != nil && ! ok && warnings != nil {
* warnings = append ( * warnings , caddyconfig . Warning { Message : "not an integer type" } )
2019-08-09 13:05:47 -05:00
}
2019-08-22 14:38:37 -05:00
return intVal
2019-08-09 13:05:47 -05:00
}
type matcherSetAndTokens struct {
matcherSet map [ string ] json . RawMessage
tokens [ ] caddyfile . Token
}
// sbAddrAssocation is a mapping from a list of
// addresses to a list of server blocks that are
// served on those addresses.
type sbAddrAssociation struct {
addresses [ ] string
2019-08-21 11:46:35 -05:00
serverBlocks [ ] serverBlock
2019-08-09 13:05:47 -05:00
}
// Interface guard
var _ caddyfile . ServerType = ( * ServerType ) ( nil )