mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-04 10:12:29 +08:00 
			
		
		
		
	go.mod: Upgrade to libdns 1.0 beta APIs (requires upgraded DNS providers)
This is the only way we can properly, reliably support ECH.
This commit is contained in:
		
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.mod
									
									
									
									
									
								
							@ -8,7 +8,7 @@ require (
 | 
				
			|||||||
	github.com/Masterminds/sprig/v3 v3.3.0
 | 
						github.com/Masterminds/sprig/v3 v3.3.0
 | 
				
			||||||
	github.com/alecthomas/chroma/v2 v2.15.0
 | 
						github.com/alecthomas/chroma/v2 v2.15.0
 | 
				
			||||||
	github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
 | 
						github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
 | 
				
			||||||
	github.com/caddyserver/certmagic v0.22.2
 | 
						github.com/caddyserver/certmagic v0.22.3-0.20250407182622-b9399eadfbe7
 | 
				
			||||||
	github.com/caddyserver/zerossl v0.1.3
 | 
						github.com/caddyserver/zerossl v0.1.3
 | 
				
			||||||
	github.com/cloudflare/circl v1.6.0
 | 
						github.com/cloudflare/circl v1.6.0
 | 
				
			||||||
	github.com/dustin/go-humanize v1.0.1
 | 
						github.com/dustin/go-humanize v1.0.1
 | 
				
			||||||
@ -116,7 +116,7 @@ require (
 | 
				
			|||||||
	github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
 | 
						github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
 | 
				
			||||||
	github.com/jackc/pgtype v1.14.0 // indirect
 | 
						github.com/jackc/pgtype v1.14.0 // indirect
 | 
				
			||||||
	github.com/jackc/pgx/v4 v4.18.3 // indirect
 | 
						github.com/jackc/pgx/v4 v4.18.3 // indirect
 | 
				
			||||||
	github.com/libdns/libdns v0.2.3
 | 
						github.com/libdns/libdns v1.0.0-beta.1
 | 
				
			||||||
	github.com/manifoldco/promptui v0.9.0 // indirect
 | 
						github.com/manifoldco/promptui v0.9.0 // indirect
 | 
				
			||||||
	github.com/mattn/go-colorable v0.1.13 // indirect
 | 
						github.com/mattn/go-colorable v0.1.13 // indirect
 | 
				
			||||||
	github.com/mattn/go-isatty v0.0.20 // indirect
 | 
						github.com/mattn/go-isatty v0.0.20 // indirect
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										8
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.sum
									
									
									
									
									
								
							@ -93,8 +93,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 | 
				
			|||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 | 
					github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 | 
				
			||||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
 | 
					github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
 | 
				
			||||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
 | 
					github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
 | 
				
			||||||
github.com/caddyserver/certmagic v0.22.2 h1:qzZURXlrxwR5m25/jpvVeEyJHeJJMvAwe5zlMufOTQk=
 | 
					github.com/caddyserver/certmagic v0.22.3-0.20250407182622-b9399eadfbe7 h1:dZCbkHTYh2ulXpmUK4ZrYWMCTa7x4/F3w52RGQfYoIY=
 | 
				
			||||||
github.com/caddyserver/certmagic v0.22.2/go.mod h1:hbqE7BnkjhX5IJiFslPmrSeobSeZvI6ux8tyxhsd6qs=
 | 
					github.com/caddyserver/certmagic v0.22.3-0.20250407182622-b9399eadfbe7/go.mod h1:r8ZK7Y8zaUU3j5g+oTiluLtQX8fwWI6MamcRINlSsJY=
 | 
				
			||||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
 | 
					github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
 | 
				
			||||||
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
 | 
					github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
 | 
				
			||||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
 | 
					github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
 | 
				
			||||||
@ -327,8 +327,8 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
				
			|||||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 | 
					github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 | 
				
			||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
 | 
					github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
 | 
				
			||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 | 
					github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 | 
				
			||||||
github.com/libdns/libdns v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8=
 | 
					github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ=
 | 
				
			||||||
github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
 | 
					github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
 | 
				
			||||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
 | 
					github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
 | 
				
			||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 | 
					github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 | 
				
			||||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 | 
					github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 | 
				
			||||||
 | 
				
			|||||||
@ -641,10 +641,6 @@ nextName:
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		relName := libdns.RelativeName(domain+".", zone)
 | 
							relName := libdns.RelativeName(domain+".", zone)
 | 
				
			||||||
		// TODO: libdns.RelativeName should probably return "@" instead of "". (The latest commits of libdns do this, so remove this logic once upgraded.)
 | 
					 | 
				
			||||||
		if relName == "" {
 | 
					 | 
				
			||||||
			relName = "@"
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// get existing records for this domain; we need to make sure another
 | 
							// get existing records for this domain; we need to make sure another
 | 
				
			||||||
		// record exists for it so we don't accidentally trample a wildcard; we
 | 
							// record exists for it so we don't accidentally trample a wildcard; we
 | 
				
			||||||
@ -657,23 +653,25 @@ nextName:
 | 
				
			|||||||
				zap.Error(err))
 | 
									zap.Error(err))
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		var httpsRec libdns.Record
 | 
							var httpsRec libdns.ServiceBinding
 | 
				
			||||||
		var nameHasExistingRecord bool
 | 
							var nameHasExistingRecord bool
 | 
				
			||||||
		for _, rec := range recs {
 | 
							for _, rec := range recs {
 | 
				
			||||||
			// TODO: providers SHOULD normalize root-level records to be named "@"; remove the extra conditions when the transition to the new semantics is done
 | 
								rr := rec.RR()
 | 
				
			||||||
			if rec.Name == relName || (rec.Name == "" && relName == "@") {
 | 
								if rr.Name == relName {
 | 
				
			||||||
				// CNAME records are exclusive of all other records, so we cannot publish an HTTPS
 | 
									// CNAME records are exclusive of all other records, so we cannot publish an HTTPS
 | 
				
			||||||
				// record for a domain that is CNAME'd. See #6922.
 | 
									// record for a domain that is CNAME'd. See #6922.
 | 
				
			||||||
				if rec.Type == "CNAME" {
 | 
									if rr.Type == "CNAME" {
 | 
				
			||||||
					dnsPub.logger.Warn("domain has CNAME record, so unable to publish ECH data to HTTPS record",
 | 
										dnsPub.logger.Warn("domain has CNAME record, so unable to publish ECH data to HTTPS record",
 | 
				
			||||||
						zap.String("domain", domain),
 | 
											zap.String("domain", domain),
 | 
				
			||||||
						zap.String("cname_value", rec.Value))
 | 
											zap.String("cname_value", rr.Data))
 | 
				
			||||||
					continue nextName
 | 
										continue nextName
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				nameHasExistingRecord = true
 | 
									nameHasExistingRecord = true
 | 
				
			||||||
				if rec.Type == "HTTPS" && (rec.Target == "" || rec.Target == ".") {
 | 
									if svcb, ok := rec.(libdns.ServiceBinding); ok && svcb.Scheme == "https" {
 | 
				
			||||||
					httpsRec = rec
 | 
										if svcb.Target == "" || svcb.Target == "." {
 | 
				
			||||||
					break
 | 
											httpsRec = svcb
 | 
				
			||||||
 | 
											break
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -689,31 +687,24 @@ nextName:
 | 
				
			|||||||
				zap.String("zone", zone))
 | 
									zap.String("zone", zone))
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		params := make(svcParams)
 | 
							params := httpsRec.Params
 | 
				
			||||||
		if httpsRec.Value != "" {
 | 
							if params == nil {
 | 
				
			||||||
			params, err = parseSvcParams(httpsRec.Value)
 | 
								params = make(libdns.SvcParams)
 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				dnsPub.logger.Error("unable to parse existing DNS record to publish ECH data to HTTPS DNS record",
 | 
					 | 
				
			||||||
					zap.String("domain", domain),
 | 
					 | 
				
			||||||
					zap.String("https_rec_value", httpsRec.Value),
 | 
					 | 
				
			||||||
					zap.Error(err))
 | 
					 | 
				
			||||||
				continue
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// overwrite only the ech SvcParamKey
 | 
							// overwrite only the "ech" SvcParamKey
 | 
				
			||||||
		params["ech"] = []string{base64.StdEncoding.EncodeToString(configListBin)}
 | 
							params["ech"] = []string{base64.StdEncoding.EncodeToString(configListBin)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// publish record
 | 
							// publish record
 | 
				
			||||||
		_, err = dnsPub.provider.SetRecords(ctx, zone, []libdns.Record{
 | 
							_, err = dnsPub.provider.SetRecords(ctx, zone, []libdns.Record{
 | 
				
			||||||
			{
 | 
								libdns.ServiceBinding{
 | 
				
			||||||
				// HTTPS and SVCB RRs: RFC 9460 (https://www.rfc-editor.org/rfc/rfc9460)
 | 
									// HTTPS and SVCB RRs: RFC 9460 (https://www.rfc-editor.org/rfc/rfc9460)
 | 
				
			||||||
				Type:     "HTTPS",
 | 
									Scheme:   "https",
 | 
				
			||||||
				Name:     relName,
 | 
									Name:     relName,
 | 
				
			||||||
				Priority: 2, // allows a manual override with priority 1
 | 
					 | 
				
			||||||
				Target:   ".",
 | 
					 | 
				
			||||||
				Value:    params.String(),
 | 
					 | 
				
			||||||
				TTL:      1 * time.Minute, // TODO: for testing only
 | 
									TTL:      1 * time.Minute, // TODO: for testing only
 | 
				
			||||||
 | 
									Priority: 2,               // allows a manual override with priority 1
 | 
				
			||||||
 | 
									Target:   ".",
 | 
				
			||||||
 | 
									Params:   params,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
@ -952,172 +943,6 @@ func newECHConfigID(ctx caddy.Context) (uint8, error) {
 | 
				
			|||||||
	return 0, fmt.Errorf("depleted attempts to find an available config_id")
 | 
						return 0, fmt.Errorf("depleted attempts to find an available config_id")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// svcParams represents SvcParamKey and SvcParamValue pairs as
 | 
					 | 
				
			||||||
// described in https://www.rfc-editor.org/rfc/rfc9460 (section 2.1).
 | 
					 | 
				
			||||||
type svcParams map[string][]string
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// parseSvcParams parses service parameters into a structured type
 | 
					 | 
				
			||||||
// for safer manipulation.
 | 
					 | 
				
			||||||
func parseSvcParams(input string) (svcParams, error) {
 | 
					 | 
				
			||||||
	if len(input) > 4096 {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("input too long: %d", len(input))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	params := make(svcParams)
 | 
					 | 
				
			||||||
	input = strings.TrimSpace(input) + " "
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for cursor := 0; cursor < len(input); cursor++ {
 | 
					 | 
				
			||||||
		var key, rawVal string
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	keyValPair:
 | 
					 | 
				
			||||||
		for i := cursor; i < len(input); i++ {
 | 
					 | 
				
			||||||
			switch input[i] {
 | 
					 | 
				
			||||||
			case '=':
 | 
					 | 
				
			||||||
				key = strings.ToLower(strings.TrimSpace(input[cursor:i]))
 | 
					 | 
				
			||||||
				i++
 | 
					 | 
				
			||||||
				cursor = i
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				var quoted bool
 | 
					 | 
				
			||||||
				if input[cursor] == '"' {
 | 
					 | 
				
			||||||
					quoted = true
 | 
					 | 
				
			||||||
					i++
 | 
					 | 
				
			||||||
					cursor = i
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				var escaped bool
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				for j := cursor; j < len(input); j++ {
 | 
					 | 
				
			||||||
					switch input[j] {
 | 
					 | 
				
			||||||
					case '"':
 | 
					 | 
				
			||||||
						if !quoted {
 | 
					 | 
				
			||||||
							return nil, fmt.Errorf("illegal DQUOTE at position %d", j)
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
						if !escaped {
 | 
					 | 
				
			||||||
							// end of quoted value
 | 
					 | 
				
			||||||
							rawVal = input[cursor:j]
 | 
					 | 
				
			||||||
							j++
 | 
					 | 
				
			||||||
							cursor = j
 | 
					 | 
				
			||||||
							break keyValPair
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
					case '\\':
 | 
					 | 
				
			||||||
						escaped = true
 | 
					 | 
				
			||||||
					case ' ', '\t', '\n', '\r':
 | 
					 | 
				
			||||||
						if !quoted {
 | 
					 | 
				
			||||||
							// end of unquoted value
 | 
					 | 
				
			||||||
							rawVal = input[cursor:j]
 | 
					 | 
				
			||||||
							cursor = j
 | 
					 | 
				
			||||||
							break keyValPair
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
					default:
 | 
					 | 
				
			||||||
						escaped = false
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			case ' ', '\t', '\n', '\r':
 | 
					 | 
				
			||||||
				// key with no value (flag)
 | 
					 | 
				
			||||||
				key = input[cursor:i]
 | 
					 | 
				
			||||||
				params[key] = []string{}
 | 
					 | 
				
			||||||
				cursor = i
 | 
					 | 
				
			||||||
				break keyValPair
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if rawVal == "" {
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var sb strings.Builder
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var escape int // start of escape sequence (after \, so 0 is never a valid start)
 | 
					 | 
				
			||||||
		for i := 0; i < len(rawVal); i++ {
 | 
					 | 
				
			||||||
			ch := rawVal[i]
 | 
					 | 
				
			||||||
			if escape > 0 {
 | 
					 | 
				
			||||||
				// validate escape sequence
 | 
					 | 
				
			||||||
				// (RFC 9460 Appendix A)
 | 
					 | 
				
			||||||
				// escaped:   "\" ( non-digit / dec-octet )
 | 
					 | 
				
			||||||
				// non-digit: "%x21-2F / %x3A-7E"
 | 
					 | 
				
			||||||
				// dec-octet: "0-255 as a 3-digit decimal number"
 | 
					 | 
				
			||||||
				if ch >= '0' && ch <= '9' {
 | 
					 | 
				
			||||||
					// advance to end of decimal octet, which must be 3 digits
 | 
					 | 
				
			||||||
					i += 2
 | 
					 | 
				
			||||||
					if i > len(rawVal) {
 | 
					 | 
				
			||||||
						return nil, fmt.Errorf("value ends with incomplete escape sequence: %s", rawVal[escape:])
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
					decOctet, err := strconv.Atoi(rawVal[escape : i+1])
 | 
					 | 
				
			||||||
					if err != nil {
 | 
					 | 
				
			||||||
						return nil, err
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
					if decOctet < 0 || decOctet > 255 {
 | 
					 | 
				
			||||||
						return nil, fmt.Errorf("invalid decimal octet in escape sequence: %s (%d)", rawVal[escape:i], decOctet)
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
					sb.WriteRune(rune(decOctet))
 | 
					 | 
				
			||||||
					escape = 0
 | 
					 | 
				
			||||||
					continue
 | 
					 | 
				
			||||||
				} else if (ch < 0x21 || ch > 0x2F) && (ch < 0x3A && ch > 0x7E) {
 | 
					 | 
				
			||||||
					return nil, fmt.Errorf("illegal escape sequence %s", rawVal[escape:i])
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			switch ch {
 | 
					 | 
				
			||||||
			case ';', '(', ')':
 | 
					 | 
				
			||||||
				// RFC 9460 Appendix A:
 | 
					 | 
				
			||||||
				// > contiguous  = 1*( non-special / escaped )
 | 
					 | 
				
			||||||
				// > non-special is VCHAR minus DQUOTE, ";", "(", ")", and "\".
 | 
					 | 
				
			||||||
				return nil, fmt.Errorf("illegal character in value %q at position %d: %s", rawVal, i, string(ch))
 | 
					 | 
				
			||||||
			case '\\':
 | 
					 | 
				
			||||||
				escape = i + 1
 | 
					 | 
				
			||||||
			default:
 | 
					 | 
				
			||||||
				sb.WriteByte(ch)
 | 
					 | 
				
			||||||
				escape = 0
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		params[key] = strings.Split(sb.String(), ",")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return params, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// String serializes svcParams into zone presentation format.
 | 
					 | 
				
			||||||
func (params svcParams) String() string {
 | 
					 | 
				
			||||||
	var sb strings.Builder
 | 
					 | 
				
			||||||
	for key, vals := range params {
 | 
					 | 
				
			||||||
		if sb.Len() > 0 {
 | 
					 | 
				
			||||||
			sb.WriteRune(' ')
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		sb.WriteString(key)
 | 
					 | 
				
			||||||
		var hasVal, needsQuotes bool
 | 
					 | 
				
			||||||
		for _, val := range vals {
 | 
					 | 
				
			||||||
			if len(val) > 0 {
 | 
					 | 
				
			||||||
				hasVal = true
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if strings.ContainsAny(val, `" `) {
 | 
					 | 
				
			||||||
				needsQuotes = true
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if hasVal && needsQuotes {
 | 
					 | 
				
			||||||
				break
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if hasVal {
 | 
					 | 
				
			||||||
			sb.WriteRune('=')
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if needsQuotes {
 | 
					 | 
				
			||||||
			sb.WriteRune('"')
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		for i, val := range vals {
 | 
					 | 
				
			||||||
			if i > 0 {
 | 
					 | 
				
			||||||
				sb.WriteRune(',')
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			val = strings.ReplaceAll(val, `"`, `\"`)
 | 
					 | 
				
			||||||
			val = strings.ReplaceAll(val, `,`, `\,`)
 | 
					 | 
				
			||||||
			sb.WriteString(val)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if needsQuotes {
 | 
					 | 
				
			||||||
			sb.WriteRune('"')
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return sb.String()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ECHPublisher is an interface for publishing ECHConfigList values
 | 
					// ECHPublisher is an interface for publishing ECHConfigList values
 | 
				
			||||||
// so that they can be used by clients.
 | 
					// so that they can be used by clients.
 | 
				
			||||||
type ECHPublisher interface {
 | 
					type ECHPublisher interface {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,129 +0,0 @@
 | 
				
			|||||||
package caddytls
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"reflect"
 | 
					 | 
				
			||||||
	"testing"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestParseSvcParams(t *testing.T) {
 | 
					 | 
				
			||||||
	for i, test := range []struct {
 | 
					 | 
				
			||||||
		input     string
 | 
					 | 
				
			||||||
		expect    svcParams
 | 
					 | 
				
			||||||
		shouldErr bool
 | 
					 | 
				
			||||||
	}{
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			input: `alpn="h2,h3" no-default-alpn ipv6hint=2001:db8::1 port=443`,
 | 
					 | 
				
			||||||
			expect: svcParams{
 | 
					 | 
				
			||||||
				"alpn":            {"h2", "h3"},
 | 
					 | 
				
			||||||
				"no-default-alpn": {},
 | 
					 | 
				
			||||||
				"ipv6hint":        {"2001:db8::1"},
 | 
					 | 
				
			||||||
				"port":            {"443"},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			input: `key=value quoted="some string" flag`,
 | 
					 | 
				
			||||||
			expect: svcParams{
 | 
					 | 
				
			||||||
				"key":    {"value"},
 | 
					 | 
				
			||||||
				"quoted": {"some string"},
 | 
					 | 
				
			||||||
				"flag":   {},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			input: `key="nested \"quoted\" value,foobar"`,
 | 
					 | 
				
			||||||
			expect: svcParams{
 | 
					 | 
				
			||||||
				"key": {`nested "quoted" value`, "foobar"},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			input: `alpn=h3,h2 tls-supported-groups=29,23 no-default-alpn ech="foobar"`,
 | 
					 | 
				
			||||||
			expect: svcParams{
 | 
					 | 
				
			||||||
				"alpn":                 {"h3", "h2"},
 | 
					 | 
				
			||||||
				"tls-supported-groups": {"29", "23"},
 | 
					 | 
				
			||||||
				"no-default-alpn":      {},
 | 
					 | 
				
			||||||
				"ech":                  {"foobar"},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			input: `escape=\097`,
 | 
					 | 
				
			||||||
			expect: svcParams{
 | 
					 | 
				
			||||||
				"escape": {"a"},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			input: `escapes=\097\098c`,
 | 
					 | 
				
			||||||
			expect: svcParams{
 | 
					 | 
				
			||||||
				"escapes": {"abc"},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	} {
 | 
					 | 
				
			||||||
		actual, err := parseSvcParams(test.input)
 | 
					 | 
				
			||||||
		if err != nil && !test.shouldErr {
 | 
					 | 
				
			||||||
			t.Errorf("Test %d: Expected no error, but got: %v (input=%q)", i, err, test.input)
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		} else if err == nil && test.shouldErr {
 | 
					 | 
				
			||||||
			t.Errorf("Test %d: Expected an error, but got no error (input=%q)", i, test.input)
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if !reflect.DeepEqual(test.expect, actual) {
 | 
					 | 
				
			||||||
			t.Errorf("Test %d: Expected %v, got %v (input=%q)", i, test.expect, actual, test.input)
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestSvcParamsString(t *testing.T) {
 | 
					 | 
				
			||||||
	// this test relies on the parser also working
 | 
					 | 
				
			||||||
	// because we can't just compare string outputs
 | 
					 | 
				
			||||||
	// since map iteration is unordered
 | 
					 | 
				
			||||||
	for i, test := range []svcParams{
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			"alpn":            {"h2", "h3"},
 | 
					 | 
				
			||||||
			"no-default-alpn": {},
 | 
					 | 
				
			||||||
			"ipv6hint":        {"2001:db8::1"},
 | 
					 | 
				
			||||||
			"port":            {"443"},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			"key":    {"value"},
 | 
					 | 
				
			||||||
			"quoted": {"some string"},
 | 
					 | 
				
			||||||
			"flag":   {},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			"key": {`nested "quoted" value`, "foobar"},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			"alpn":                 {"h3", "h2"},
 | 
					 | 
				
			||||||
			"tls-supported-groups": {"29", "23"},
 | 
					 | 
				
			||||||
			"no-default-alpn":      {},
 | 
					 | 
				
			||||||
			"ech":                  {"foobar"},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	} {
 | 
					 | 
				
			||||||
		combined := test.String()
 | 
					 | 
				
			||||||
		parsed, err := parseSvcParams(combined)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			t.Errorf("Test %d: Expected no error, but got: %v (input=%q)", i, err, test)
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if len(parsed) != len(test) {
 | 
					 | 
				
			||||||
			t.Errorf("Test %d: Expected %d keys, but got %d", i, len(test), len(parsed))
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		for key, expectedVals := range test {
 | 
					 | 
				
			||||||
			if expected, actual := len(expectedVals), len(parsed[key]); expected != actual {
 | 
					 | 
				
			||||||
				t.Errorf("Test %d: Expected key %s to have %d values, but had %d", i, key, expected, actual)
 | 
					 | 
				
			||||||
				continue
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			for j, expected := range expectedVals {
 | 
					 | 
				
			||||||
				if actual := parsed[key][j]; actual != expected {
 | 
					 | 
				
			||||||
					t.Errorf("Test %d key %q value %d: Expected '%s' but got '%s'", i, key, j, expected, actual)
 | 
					 | 
				
			||||||
					continue
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if !reflect.DeepEqual(parsed, test) {
 | 
					 | 
				
			||||||
			t.Errorf("Test %d: Expected %#v, got %#v", i, test, combined)
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
		Reference in New Issue
	
	Block a user