mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-12-09 13:46:07 +08:00
fixup!
This commit is contained in:
parent
b8f2167bf9
commit
84898bb0cd
8 changed files with 253 additions and 125 deletions
|
|
@ -22,8 +22,7 @@ Example:
|
|||
{
|
||||
"bind": {
|
||||
"TYPE": "BIND",
|
||||
"directory": "myzones",
|
||||
"filenameformat": "%U.zone"
|
||||
"directory": "myzones"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -85,43 +84,60 @@ DNSControl does not handle special serial number math such as "looping through z
|
|||
# filenameformat
|
||||
|
||||
The `filenameformat` parameter specifies the file name to be used when
|
||||
writing the zone file. The default (`%U.zone`) is acceptable in most cases: the
|
||||
writing the zone file. The default (`%c.zone`) is acceptable in most cases: the
|
||||
file name is the name as specified in the `D()` function plus ".zone".
|
||||
|
||||
The filenameformat is a string with a few printf-like `%` verbs:
|
||||
|
||||
* The domain name without tag (the `example.com` part of `example.com!tag`):
|
||||
* `%D` as specified in `D()` (no IDN conversion, but downcased)
|
||||
* `%I` converted to IDN/Punycode (`xn--...`) and downcased.
|
||||
* `%N` converted to Unicode (downcased first)
|
||||
* `%T` the split horizon tag, or "" (the `tag` part of `example.com!tag`)
|
||||
* `%?x` this returns `x` if the split horizon tag is non-null, otherwise nothing. `x` can be any printable but is usually `!`.
|
||||
* `%U` short for "%I%?!%T". This is the universal, canonical, name for the domain used for comparisons within DNSControl. This is best for filenames which is why it is used in the default.
|
||||
* `%%` `%`
|
||||
* ordinary characters (not `%`) are copied unchanged to the output stream
|
||||
* FYI: format strings must not end with an incomplete `%` or `%?`
|
||||
| Verb | Description | `EXAMple.com` | `EXAMple.com!MyTag` | `рф.com!myTag` |
|
||||
| ------- | ------------------------------------------------- | ------------- | ------------------- | -------------------- |
|
||||
| `%T` | the tag | `` | `myTag` | `myTag` |
|
||||
| `%c` | canonical name, globally unique and comparable | `example.com` | `example.com!myTag` | `xn--p1ai.com!myTag` |
|
||||
| `%a` | ASCII domain (Punycode, downcased) | `example.com` | `example.com` | `xn--p1ai.com` |
|
||||
| `%u` | Unicode domain (non-Unicode parts downcased) | `example.com` | `example.com` | `рф.com` |
|
||||
| `%r` | Raw (unmodified) Domain from `D()` (risky!) | `EXAMple.com` | `EXAMple.com` | `рф.com` |
|
||||
| `%f` | like `%c` but better for filenames (`%a%?_%T`) | `example.com` | `example.com_myTag` | `xn--p1ai.com_myTag` |
|
||||
| `%F` | like `%f` but reversed order (`%T%?_%a`) | `example.com` | `myTag_example.com` | `myTag_xn--p1ai.com` |
|
||||
| `%?x` | returns `x` if tag exists, otherwise "" | `` | `x` | `x` |
|
||||
| `%%` | a literal percent sign | `%` | `%` | `%` |
|
||||
| `a-Z./` | other printable characters are copied exactly | `a-Z./` | `a-Z./` | `a-Z./` |
|
||||
| `%U` | (deprecated, use `%c`) Same as `%D%?!%T` (risky!) | `example.com` | `example.com!myTag` | `рф.com!myTag` |
|
||||
| `%D` | (deprecated, use `%r`) mangles Unicode (risky!) | `example.com` | `example.com` | `рф.com` |
|
||||
|
||||
Typical values:
|
||||
* `%?x` is typically used to generate an optional `!` or `_` if there is a tag.
|
||||
* `%r` is considered "risky" because it can produce a domain name that is not
|
||||
canonical. For example, if you use `D("FOO.com")` and later change it to `D("foo.com")`, your file names will change.
|
||||
* Format strings must not end with an incomplete `%` or `%?`
|
||||
* Generating a filename without a tag is risky. For example, if the same
|
||||
`dnsconfig.js` has `D("example.com!inside", DSP_BIND)` and
|
||||
`D("example.com!outside", DSP_BIND)`, both will use the same filename.
|
||||
DNSControl will write both zone files to the same file, flapping between the
|
||||
two. No error or warning will be output.
|
||||
|
||||
* `%U.zone` (The default)
|
||||
* `example.com.zone` or `example.com!tag.zone`
|
||||
* `%T%?_%I.zone` (optional tag and `_` + domain + `.zone`)
|
||||
* `tag_example.com.zone` or `example.com.zone`
|
||||
* `db_%T%?_%D`
|
||||
* `db_inside_example.com` or `db_example.com`
|
||||
Compatibility notes:
|
||||
|
||||
{% hint style="warning" %}
|
||||
**Warning** DNSControl will not warn you if two zones generate the same
|
||||
filename. Instead, each will write to the same place. The content would end up
|
||||
flapping back and forth between the two. The best way to prevent this is to
|
||||
always include the tag (`%T`) or use `%U` which includes the tag.
|
||||
{% endhint %}
|
||||
* `%D` should not be used. It downcases the string in a way that is probably
|
||||
incompatible with Unicode characters. It is retained for compatibility with
|
||||
pre-v4.28 releases. If your domain has capital Unicode characters, backwards
|
||||
compatibility is not guaranteed.
|
||||
* `%U` relies on `%D` which is deprecated. Use `%c` instead.
|
||||
* As of v4.28 the default format string changed from `%U.zone` to `%c.zone`. This
|
||||
should only matter if your `D()` statements included non-ASCII (Unicode)
|
||||
runes that were capitalized.
|
||||
|
||||
Useful examples:
|
||||
|
||||
| Verb | Description | `EXAMple.com` | `EXAMple.com!MyTag` | `рф.com!myTag` |
|
||||
| ------------ | ----------------------------------- | ------------------ | ------------------------ | ------------------------- |
|
||||
| `%c.zone` | Default format (v4.28 and later) | `example.com.zone` | `example.com!myTag.zone` | `xn--p1ai.com!myTag.zone` |
|
||||
| `%U.zone` | Default format (pre-v4.28) (risky!) | `example.com.zone` | `example.com!myTag.zone` | `рф.com!myTag.zone` |
|
||||
| `db_%f` | Recommended in a popular DNS book | `db_example.com` | `db_example.com_myTag` | `db_xn--p1ai.com_myTag` |
|
||||
| `db_%a%?_%T` | same as above but using `%?_` | `db_example.com` | `db_example.com_myTag` | `db_xn--p1ai.com_myTag` |
|
||||
|
||||
(new in v4.2.0) `dnscontrol push` will create subdirectories along the path to
|
||||
the filename. This includes both the portion of the path created by the
|
||||
`directory` setting and the `filenameformat` setting. The automatic creation of
|
||||
subdirectories is disabled if `dnscontrol` is running as root for security
|
||||
reasons.
|
||||
`directory` setting and the `filenameformat` setting. For security reasons, the automatic creation of
|
||||
subdirectories is disabled if `dnscontrol` is running as root.
|
||||
|
||||
# FYI: get-zones
|
||||
|
||||
|
|
@ -134,5 +150,5 @@ dnscontrol get-zones --format=nameonly - BIND all
|
|||
|
||||
If `filenameformat` is defined, `dnscontrol` makes a guess at which
|
||||
filenames are zones but doesn't try to hard to get it right, which is
|
||||
mathematically impossible in some cases. Feel free to file an issue if
|
||||
mathematically impossible to do correctly in all chase. Feel free to file an issue if
|
||||
your format string doesn't work. I love a challenge!
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const (
|
|||
DomainTag = "dnscontrol_tag" // A copy of DomainConfig.Tag
|
||||
DomainUniqueName = "dnscontrol_uniquename" // A copy of DomainConfig.UniqueName
|
||||
DomainNameRaw = "dnscontrol_nameraw" // A copy of DomainConfig.NameRaw
|
||||
DomainNameIDN = "dnscontrol_nameidn" // A copy of DomainConfig.NameIDN
|
||||
DomainNameASCII = "dnscontrol_nameascii" // A copy of DomainConfig.NameASCII
|
||||
DomainNameUnicode = "dnscontrol_nameunicode" // A copy of DomainConfig.NameUnicode
|
||||
)
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ func (dc *DomainConfig) PostProcess() {
|
|||
|
||||
// Turn the user-supplied name into the fixed forms.
|
||||
ff := domaintags.MakeDomainFixForms(dc.Name)
|
||||
dc.Tag, dc.NameRaw, dc.Name, dc.NameUnicode, dc.UniqueName = ff.Tag, ff.NameRaw, ff.NameIDN, ff.NameUnicode, ff.UniqueName
|
||||
dc.Tag, dc.NameRaw, dc.Name, dc.NameUnicode, dc.UniqueName = ff.Tag, ff.NameRaw, ff.NameASCII, ff.NameUnicode, ff.UniqueName
|
||||
|
||||
// Store the FixForms is Metadata so we don't have to change the signature of every function that might need them.
|
||||
// This is a bit ugly but avoids a huge refactor. Please avoid using these to make the future refactor easier.
|
||||
|
|
@ -79,7 +79,7 @@ func (dc *DomainConfig) PostProcess() {
|
|||
dc.Metadata[DomainTag] = dc.Tag
|
||||
}
|
||||
//dc.Metadata[DomainNameRaw] = dc.NameRaw
|
||||
//dc.Metadata[DomainNameIDN] = dc.Name
|
||||
//dc.Metadata[DomainNameASCII] = dc.Name
|
||||
//dc.Metadata[DomainNameUnicode] = dc.NameUnicode
|
||||
dc.Metadata[DomainUniqueName] = dc.UniqueName
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
// DomainFixedForms stores the various fixed forms of a domain name and tag.
|
||||
type DomainFixedForms struct {
|
||||
NameRaw string // "originalinput.com" (name as input by the user, lowercased (no tag))
|
||||
NameIDN string // "punycode.com"
|
||||
NameASCII string // "punycode.com"
|
||||
NameUnicode string // "unicode.com" (converted to downcase BEFORE unicode conversion)
|
||||
UniqueName string // "punycode.com!tag"
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ type DomainFixedForms struct {
|
|||
// * .UniqueName: "example.com!tag" unique across the entire config.
|
||||
func MakeDomainFixForms(n string) DomainFixedForms {
|
||||
var err error
|
||||
var tag, nameRaw, nameIDN, nameUnicode, uniqueName string
|
||||
var tag, nameRaw, nameASCII, nameUnicode, uniqueName string
|
||||
var hasBang bool
|
||||
|
||||
// Split tag from name.
|
||||
|
|
@ -38,42 +38,45 @@ func MakeDomainFixForms(n string) DomainFixedForms {
|
|||
hasBang = false
|
||||
}
|
||||
|
||||
nameRaw = strings.ToLower(p[0])
|
||||
nameRaw = p[0]
|
||||
if strings.HasPrefix(n, nameRaw) {
|
||||
// Avoid pointless duplication.
|
||||
nameRaw = n[0:len(nameRaw)]
|
||||
}
|
||||
|
||||
nameIDN, err = idna.ToASCII(nameRaw)
|
||||
nameASCII, err = idna.ToASCII(nameRaw)
|
||||
if err != nil {
|
||||
nameIDN = nameRaw // Fallback to raw name on error.
|
||||
nameASCII = nameRaw // Fallback to raw name on error.
|
||||
} else {
|
||||
nameASCII = strings.ToLower(nameASCII)
|
||||
// Avoid pointless duplication.
|
||||
if nameIDN == nameRaw {
|
||||
nameIDN = nameRaw
|
||||
if strings.HasPrefix(n, nameASCII) {
|
||||
// Avoid pointless duplication.
|
||||
nameASCII = n[0:len(nameASCII)]
|
||||
}
|
||||
}
|
||||
|
||||
nameUnicode, err = idna.ToUnicode(nameRaw)
|
||||
nameUnicode, err = idna.ToUnicode(nameASCII) // We use nameASCII since it is already lowercased.
|
||||
if err != nil {
|
||||
nameUnicode = nameRaw // Fallback to raw name on error.
|
||||
} else {
|
||||
// Avoid pointless duplication.
|
||||
if nameUnicode == nameRaw {
|
||||
nameUnicode = nameRaw
|
||||
if strings.HasPrefix(n, nameUnicode) {
|
||||
// Avoid pointless duplication.
|
||||
nameUnicode = n[0:len(nameUnicode)]
|
||||
}
|
||||
}
|
||||
|
||||
if hasBang {
|
||||
uniqueName = nameIDN + "!" + tag
|
||||
uniqueName = nameASCII + "!" + tag
|
||||
} else {
|
||||
uniqueName = nameIDN
|
||||
uniqueName = nameASCII
|
||||
}
|
||||
|
||||
return DomainFixedForms{
|
||||
Tag: tag,
|
||||
NameRaw: nameRaw,
|
||||
NameIDN: nameIDN,
|
||||
NameASCII: nameASCII,
|
||||
NameUnicode: nameUnicode,
|
||||
UniqueName: uniqueName,
|
||||
HasBang: hasBang,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
|||
input string
|
||||
wantTag string
|
||||
wantNameRaw string
|
||||
wantNameIDN string
|
||||
wantNameASCII string
|
||||
wantNameUnicode string
|
||||
wantUniqueName string
|
||||
wantHasBang bool
|
||||
|
|
@ -20,7 +20,7 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
|||
input: "example.com",
|
||||
wantTag: "",
|
||||
wantNameRaw: "example.com",
|
||||
wantNameIDN: "example.com",
|
||||
wantNameASCII: "example.com",
|
||||
wantNameUnicode: "example.com",
|
||||
wantUniqueName: "example.com",
|
||||
wantHasBang: false,
|
||||
|
|
@ -30,7 +30,7 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
|||
input: "example.com!mytag",
|
||||
wantTag: "mytag",
|
||||
wantNameRaw: "example.com",
|
||||
wantNameIDN: "example.com",
|
||||
wantNameASCII: "example.com",
|
||||
wantNameUnicode: "example.com",
|
||||
wantUniqueName: "example.com!mytag",
|
||||
wantHasBang: true,
|
||||
|
|
@ -40,7 +40,7 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
|||
input: "example.com!",
|
||||
wantTag: "",
|
||||
wantNameRaw: "example.com",
|
||||
wantNameIDN: "example.com",
|
||||
wantNameASCII: "example.com",
|
||||
wantNameUnicode: "example.com",
|
||||
wantUniqueName: "example.com!",
|
||||
wantHasBang: true,
|
||||
|
|
@ -50,7 +50,7 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
|||
input: "उदाहरण.com",
|
||||
wantTag: "",
|
||||
wantNameRaw: "उदाहरण.com",
|
||||
wantNameIDN: "xn--p1b6ci4b4b3a.com",
|
||||
wantNameASCII: "xn--p1b6ci4b4b3a.com",
|
||||
wantNameUnicode: "उदाहरण.com",
|
||||
wantUniqueName: "xn--p1b6ci4b4b3a.com",
|
||||
wantHasBang: false,
|
||||
|
|
@ -60,7 +60,7 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
|||
input: "उदाहरण.com!mytag",
|
||||
wantTag: "mytag",
|
||||
wantNameRaw: "उदाहरण.com",
|
||||
wantNameIDN: "xn--p1b6ci4b4b3a.com",
|
||||
wantNameASCII: "xn--p1b6ci4b4b3a.com",
|
||||
wantNameUnicode: "उदाहरण.com",
|
||||
wantUniqueName: "xn--p1b6ci4b4b3a.com!mytag",
|
||||
wantHasBang: true,
|
||||
|
|
@ -70,17 +70,29 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
|||
input: "xn--p1b6ci4b4b3a.com",
|
||||
wantTag: "",
|
||||
wantNameRaw: "xn--p1b6ci4b4b3a.com",
|
||||
wantNameIDN: "xn--p1b6ci4b4b3a.com",
|
||||
wantNameASCII: "xn--p1b6ci4b4b3a.com",
|
||||
wantNameUnicode: "उदाहरण.com",
|
||||
wantUniqueName: "xn--p1b6ci4b4b3a.com",
|
||||
wantHasBang: false,
|
||||
},
|
||||
{
|
||||
// Unicode chars should be left alone (as far as case folding goes)
|
||||
// Here are some Armenian characters https://tools.lgm.cl/lettercase.html
|
||||
name: "mixed case unicode",
|
||||
input: "fooԷէԸըԹ.com!myTag",
|
||||
wantTag: "myTag",
|
||||
wantNameRaw: "fooԷէԸըԹ.com",
|
||||
wantNameASCII: "xn--foo-b7dfg43aja.com",
|
||||
wantNameUnicode: "fooԷէԸըԹ.com",
|
||||
wantUniqueName: "xn--foo-b7dfg43aja.com!myTag",
|
||||
wantHasBang: true,
|
||||
},
|
||||
{
|
||||
name: "punycode domain with tag",
|
||||
input: "xn--p1b6ci4b4b3a.com!mytag",
|
||||
wantTag: "mytag",
|
||||
wantNameRaw: "xn--p1b6ci4b4b3a.com",
|
||||
wantNameIDN: "xn--p1b6ci4b4b3a.com",
|
||||
wantNameASCII: "xn--p1b6ci4b4b3a.com",
|
||||
wantNameUnicode: "उदाहरण.com",
|
||||
wantUniqueName: "xn--p1b6ci4b4b3a.com!mytag",
|
||||
wantHasBang: true,
|
||||
|
|
@ -89,8 +101,8 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
|||
name: "mixed case domain",
|
||||
input: "Example.COM",
|
||||
wantTag: "",
|
||||
wantNameRaw: "example.com",
|
||||
wantNameIDN: "example.com",
|
||||
wantNameRaw: "Example.COM",
|
||||
wantNameASCII: "example.com",
|
||||
wantNameUnicode: "example.com",
|
||||
wantUniqueName: "example.com",
|
||||
wantHasBang: false,
|
||||
|
|
@ -99,12 +111,34 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
|||
name: "mixed case domain with tag",
|
||||
input: "Example.COM!MyTag",
|
||||
wantTag: "MyTag",
|
||||
wantNameRaw: "example.com",
|
||||
wantNameIDN: "example.com",
|
||||
wantNameRaw: "Example.COM",
|
||||
wantNameASCII: "example.com",
|
||||
wantNameUnicode: "example.com",
|
||||
wantUniqueName: "example.com!MyTag",
|
||||
wantHasBang: true,
|
||||
},
|
||||
// This is used in the documentation for the BIND provider, thus we test
|
||||
// it to make sure we got it right.
|
||||
{
|
||||
name: "BIND example 1",
|
||||
input: "рф.com!myTag",
|
||||
wantTag: "myTag",
|
||||
wantNameRaw: "рф.com",
|
||||
wantNameASCII: "xn--p1ai.com",
|
||||
wantNameUnicode: "рф.com",
|
||||
wantUniqueName: "xn--p1ai.com!myTag",
|
||||
wantHasBang: true,
|
||||
},
|
||||
{
|
||||
name: "BIND example 2",
|
||||
input: "рф.com!myTag",
|
||||
wantTag: "myTag",
|
||||
wantNameRaw: "рф.com",
|
||||
wantNameASCII: "xn--p1ai.com",
|
||||
wantNameUnicode: "рф.com",
|
||||
wantUniqueName: "xn--p1ai.com!myTag",
|
||||
wantHasBang: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
@ -116,8 +150,8 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
|||
if got.NameRaw != tt.wantNameRaw {
|
||||
t.Errorf("MakeDomainFixForms() gotNameRaw = %v, want %v", got.NameRaw, tt.wantNameRaw)
|
||||
}
|
||||
if got.NameIDN != tt.wantNameIDN {
|
||||
t.Errorf("MakeDomainFixForms() gotNameIDN = %v, want %v", got.NameIDN, tt.wantNameIDN)
|
||||
if got.NameASCII != tt.wantNameASCII {
|
||||
t.Errorf("MakeDomainFixForms() gotNameASCII = %v, want %v", got.NameASCII, tt.wantNameASCII)
|
||||
}
|
||||
if got.NameUnicode != tt.wantNameUnicode {
|
||||
t.Errorf("MakeDomainFixForms() gotNameUnicode = %v, want %v", got.NameUnicode, tt.wantNameUnicode)
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ func CompilePermitList(s string) PermitList {
|
|||
continue
|
||||
}
|
||||
ff := MakeDomainFixForms(l)
|
||||
if ff.HasBang && ff.NameIDN == "" { // Treat empty name as wildcard.
|
||||
ff.NameIDN = "*"
|
||||
if ff.HasBang && ff.NameASCII == "" { // Treat empty name as wildcard.
|
||||
ff.NameASCII = "*"
|
||||
}
|
||||
sl.items = append(sl.items, ff)
|
||||
}
|
||||
|
|
@ -65,24 +65,24 @@ func (pl *PermitList) Permitted(domToCheck string) bool {
|
|||
// Now that we know the tag matches, we can focus on the name.
|
||||
|
||||
// `*!tag` or `*` matches everything.
|
||||
if filterItem.NameIDN == "*" {
|
||||
if filterItem.NameASCII == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
// If the name starts with "*." then match the suffix.
|
||||
if strings.HasPrefix(filterItem.NameIDN, "*.") {
|
||||
if strings.HasPrefix(filterItem.NameASCII, "*.") {
|
||||
// example.com matches *.example.com
|
||||
if domToCheckFF.NameIDN == filterItem.NameIDN[2:] || domToCheckFF.NameUnicode == filterItem.NameUnicode[2:] {
|
||||
if domToCheckFF.NameASCII == filterItem.NameASCII[2:] || domToCheckFF.NameUnicode == filterItem.NameUnicode[2:] {
|
||||
return true
|
||||
}
|
||||
// foo.example.com matches *.example.com
|
||||
if strings.HasSuffix(domToCheckFF.NameIDN, filterItem.NameIDN[1:]) || strings.HasSuffix(domToCheckFF.NameUnicode, filterItem.NameUnicode[1:]) {
|
||||
if strings.HasSuffix(domToCheckFF.NameASCII, filterItem.NameASCII[1:]) || strings.HasSuffix(domToCheckFF.NameUnicode, filterItem.NameUnicode[1:]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// No wildcards? Exact match.
|
||||
if filterItem.NameIDN == domToCheckFF.NameIDN || filterItem.NameUnicode == domToCheckFF.NameUnicode {
|
||||
if filterItem.NameASCII == domToCheckFF.NameASCII || filterItem.NameUnicode == domToCheckFF.NameUnicode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ func initBind(config map[string]string, providermeta json.RawMessage) (providers
|
|||
api.directory = "zones"
|
||||
}
|
||||
if api.filenameformat == "" {
|
||||
api.filenameformat = "%U.zone"
|
||||
api.filenameformat = "%c.zone"
|
||||
}
|
||||
if len(providermeta) != 0 {
|
||||
err := json.Unmarshal(providermeta, api)
|
||||
|
|
@ -171,7 +171,7 @@ func (c *bindProvider) GetZoneRecords(domain string, meta map[string]string) (mo
|
|||
ff := domaintags.DomainFixedForms{
|
||||
Tag: meta[models.DomainTag],
|
||||
NameRaw: meta[models.DomainNameRaw],
|
||||
NameIDN: domain,
|
||||
NameASCII: domain,
|
||||
NameUnicode: meta[models.DomainNameUnicode],
|
||||
UniqueName: meta[models.DomainUniqueName],
|
||||
}
|
||||
|
|
@ -181,9 +181,9 @@ func (c *bindProvider) GetZoneRecords(domain string, meta map[string]string) (mo
|
|||
ff,
|
||||
),
|
||||
)
|
||||
//fmt.Printf("DEBUG: Reading zonefile %q\n", zonefile)
|
||||
//fmt.Printf("DEBUG: Meta %+v\n", meta)
|
||||
//fmt.Printf("DEBUG: Domain Names %+v\n", ff)
|
||||
fmt.Printf("DEBUG: Reading zonefile %q\n", zonefile)
|
||||
fmt.Printf("DEBUG: Meta %+v\n", meta)
|
||||
fmt.Printf("DEBUG: Domain Names %+v\n", ff)
|
||||
|
||||
content, err := os.ReadFile(zonefile)
|
||||
if os.IsNotExist(err) {
|
||||
|
|
@ -282,7 +282,7 @@ func (c *bindProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, foundR
|
|||
domaintags.DomainFixedForms{
|
||||
Tag: dc.Tag,
|
||||
NameRaw: dc.NameRaw,
|
||||
NameIDN: dc.Name,
|
||||
NameASCII: dc.Name,
|
||||
NameUnicode: dc.NameUnicode,
|
||||
UniqueName: dc.UniqueName,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,11 +13,6 @@ import (
|
|||
// makeFileName uses format to generate a zone's filename. See the
|
||||
func makeFileName(format string, ff domaintags.DomainFixedForms) string {
|
||||
//fmt.Printf("DEBUG: makeFileName(%q, %+v)\n", format, ff)
|
||||
nameRaw := ff.NameRaw
|
||||
nameIDN := ff.NameIDN
|
||||
nameUnicode := ff.NameUnicode
|
||||
uniquename := ff.UniqueName
|
||||
tag := ff.Tag
|
||||
if format == "" {
|
||||
panic("BUG: makeFileName called with null format")
|
||||
}
|
||||
|
|
@ -40,16 +35,31 @@ func makeFileName(format string, ff domaintags.DomainFixedForms) string {
|
|||
pos++
|
||||
tok = tokens[pos]
|
||||
switch tok {
|
||||
case "D":
|
||||
b.WriteString(nameRaw)
|
||||
case "T":
|
||||
b.WriteString(tag)
|
||||
case "U":
|
||||
b.WriteString(uniquename)
|
||||
case "I":
|
||||
b.WriteString(nameIDN)
|
||||
case "N":
|
||||
b.WriteString(nameUnicode)
|
||||
|
||||
// v4.28 names
|
||||
case "r": // NameRaw "originalinput.com" (i for input)
|
||||
b.WriteString(ff.NameRaw)
|
||||
case "a": // NameASCII "punycode.com" (a for ascii)
|
||||
b.WriteString(ff.NameASCII)
|
||||
case "u": // NameUnicode "unicode.com" (u for unicode)
|
||||
b.WriteString(ff.NameUnicode)
|
||||
case "c": // UniqueName "punycode.com!tag" or "punycode.com" if no tag (c for canonical)
|
||||
b.WriteString(ff.UniqueName)
|
||||
case "f": //
|
||||
b.WriteString(ff.NameASCII)
|
||||
if ff.Tag != "" {
|
||||
b.WriteString("_")
|
||||
b.WriteString(ff.Tag)
|
||||
}
|
||||
case "F": //
|
||||
if ff.Tag != "" {
|
||||
b.WriteString(ff.Tag)
|
||||
b.WriteString("_")
|
||||
}
|
||||
b.WriteString(ff.NameASCII)
|
||||
case "T": // Tag The tag portion of `example.com!tag`
|
||||
b.WriteString(ff.Tag)
|
||||
|
||||
case "%":
|
||||
b.WriteString("%")
|
||||
case "?":
|
||||
|
|
@ -59,9 +69,20 @@ func makeFileName(format string, ff domaintags.DomainFixedForms) string {
|
|||
}
|
||||
pos++
|
||||
tok = tokens[pos]
|
||||
if tag != "" {
|
||||
if ff.Tag != "" {
|
||||
b.WriteString(tok)
|
||||
}
|
||||
|
||||
// Legacy names kept for compatibility
|
||||
case "U": // the domain name as specified in `D()`
|
||||
b.WriteString(strings.ToLower(ff.NameRaw))
|
||||
if ff.Tag != "" {
|
||||
b.WriteString("!")
|
||||
b.WriteString(ff.Tag)
|
||||
}
|
||||
case "D": // domain (without tag) as specified in D() (no IDN conversion, but downcased)
|
||||
b.WriteString(strings.ToLower(ff.NameRaw))
|
||||
|
||||
default:
|
||||
fmt.Fprintf(&b, "%%(unknown %%verb %%%s)", tok)
|
||||
}
|
||||
|
|
@ -156,14 +177,14 @@ func makeExtractor(format string) (string, error) {
|
|||
pos++
|
||||
tok = tokens[pos]
|
||||
switch tok {
|
||||
case "D":
|
||||
case "D", "a", "u", "r":
|
||||
b.WriteString(`(.*)`)
|
||||
case "T":
|
||||
if pass == 0 {
|
||||
// On the second pass, nothing is generated.
|
||||
b.WriteString(`.*`)
|
||||
}
|
||||
case "U":
|
||||
case "U", "c":
|
||||
if pass == 0 {
|
||||
b.WriteString(`(.*)!.+`)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package bind
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
|
|
@ -8,29 +9,28 @@ import (
|
|||
)
|
||||
|
||||
func Test_makeFileName(t *testing.T) {
|
||||
ff := domaintags.DomainFixedForms{
|
||||
NameRaw: "raw",
|
||||
NameIDN: "idn",
|
||||
NameUnicode: "unicode",
|
||||
UniqueName: "unique!taga",
|
||||
Tag: "tagb",
|
||||
}
|
||||
tagless := domaintags.DomainFixedForms{
|
||||
NameRaw: "raw",
|
||||
NameIDN: "idn",
|
||||
NameUnicode: "unicode",
|
||||
UniqueName: "unique",
|
||||
Tag: "",
|
||||
}
|
||||
fmtDefault := "%U.zone"
|
||||
fmtBasic := "%U - %T - %D"
|
||||
fmtBk1 := "db_%U" // Something I've seen in books on DNS
|
||||
fmtBk2 := "db_%T_%D" // Something I've seen in books on DNS
|
||||
fmtFancy := "%T%?_%D.zone" // Include the tag_ only if there is a tag
|
||||
fmtErrorPct := "literal%"
|
||||
fmtErrorOpt := "literal%?"
|
||||
fmtErrorUnk := "literal%o" // Unknown % verb
|
||||
|
||||
ff := domaintags.DomainFixedForms{
|
||||
NameRaw: "domy",
|
||||
NameASCII: "idn",
|
||||
NameUnicode: "uni",
|
||||
UniqueName: "unique!taga",
|
||||
Tag: "tagy",
|
||||
}
|
||||
tagless := domaintags.DomainFixedForms{
|
||||
NameRaw: "domy",
|
||||
NameASCII: "idn",
|
||||
NameUnicode: "uni",
|
||||
UniqueName: "unique",
|
||||
Tag: "",
|
||||
}
|
||||
|
||||
type args struct {
|
||||
format string
|
||||
ff domaintags.DomainFixedForms
|
||||
|
|
@ -40,25 +40,15 @@ func Test_makeFileName(t *testing.T) {
|
|||
args args
|
||||
want string
|
||||
}{
|
||||
// Test corner cases and common cases.
|
||||
{"literal", args{"literal", ff}, "literal"},
|
||||
{"middle", args{"mid%Dle", ff}, "midrawle"},
|
||||
{"D", args{"%D", ff}, "raw"},
|
||||
{"I", args{"%I", ff}, "idn"},
|
||||
{"N", args{"%N", ff}, "unicode"},
|
||||
{"T", args{"%T", ff}, "tagb"},
|
||||
{"x1", args{"XX%?xYY", ff}, "XXxYY"},
|
||||
{"x2", args{"AA%?xBB", tagless}, "AABB"},
|
||||
{"U", args{"%U", ff}, "unique!taga"},
|
||||
{"percent", args{"%%", ff}, "%"},
|
||||
//
|
||||
{"default", args{fmtDefault, ff}, "unique!taga.zone"},
|
||||
{"basic", args{fmtBasic, ff}, "unique!taga - tagb - raw"},
|
||||
{"front", args{"%Daaa", ff}, "rawaaa"},
|
||||
{"tail", args{"bbb%D", ff}, "bbbraw"},
|
||||
{"bk1", args{fmtBk1, ff}, "db_unique!taga"},
|
||||
{"bk2", args{fmtBk2, ff}, "db_tagb_raw"},
|
||||
{"fanWI", args{fmtFancy, ff}, "tagb_raw.zone"},
|
||||
{"fanWO", args{fmtFancy, tagless}, "raw.zone"},
|
||||
{"basic", args{fmtBasic, ff}, "domy!tagy - tagy - domy"},
|
||||
{"solo", args{"%D", ff}, "domy"},
|
||||
{"front", args{"%Daaa", ff}, "domyaaa"},
|
||||
{"tail", args{"bbb%D", ff}, "bbbdomy"},
|
||||
{"def", args{fmtDefault, ff}, "domy!tagy.zone"},
|
||||
{"fanWI", args{fmtFancy, ff}, "tagy_domy.zone"},
|
||||
{"fanWO", args{fmtFancy, tagless}, "domy.zone"},
|
||||
{"errP", args{fmtErrorPct, ff}, "literal%(format may not end in %)"},
|
||||
{"errQ", args{fmtErrorOpt, ff}, "literal%(format may not end in %?)"},
|
||||
{"errU", args{fmtErrorUnk, ff}, "literal%(unknown %verb %o)"},
|
||||
|
|
@ -73,6 +63,66 @@ func Test_makeFileName(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_makeFileName_2(t *testing.T) {
|
||||
ff1 := domaintags.MakeDomainFixForms(`EXAMple.com`)
|
||||
ff2 := domaintags.MakeDomainFixForms(`EXAMple.com!myTag`)
|
||||
ff3 := domaintags.MakeDomainFixForms(`рф.com!myTag`)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
format string
|
||||
want1 string
|
||||
want2 string
|
||||
want3 string
|
||||
descr string
|
||||
}{
|
||||
// NOTE: "Domain" in these descriptions means the domain name without any split horizon tag. Technically the "Zone".
|
||||
{`T`, `%T`, ``, `myTag`, `myTag`, `the tag`},
|
||||
{`c`, `%c`, `example.com`, `example.com!myTag`, `xn--p1ai.com!myTag`, `canonical name, globally unique and comparable`},
|
||||
{`a`, `%a`, `example.com`, `example.com`, `xn--p1ai.com`, `ASCII domain (Punycode, downcased)`},
|
||||
{`u`, `%u`, `example.com`, `example.com`, `рф.com`, `Unicode domain (non-Unicode parts downcased)`},
|
||||
{`r`, `%r`, `EXAMple.com`, `EXAMple.com`, `рф.com`, "Raw (unmodified) Domain from `D()` (risky!)"},
|
||||
{`f`, `%f`, `example.com`, `example.com_myTag`, `xn--p1ai.com_myTag`, "like `%c` but better for filenames (`%a%?_%T`)"},
|
||||
{`F`, `%F`, `example.com`, `myTag_example.com`, `myTag_xn--p1ai.com`, "like `%f` but reversed order (`%T%?_%a`)"},
|
||||
{`%?x`, `%?x`, ``, `x`, `x`, "returns `x` if tag exists, otherwise \"\""},
|
||||
{`%`, `%%`, `%`, `%`, `%`, `a literal percent sign`},
|
||||
|
||||
// Pre-v4.28 names kept for compatibility (note: pre v4.28 did not permit mixed case domain names, we downcased them here for the tests)
|
||||
{`U`, `%U`, `example.com`, `example.com!myTag`, `рф.com!myTag`, "(deprecated, use `%c`) Same as `%D%?!%T` (risky!)"},
|
||||
{`D`, `%D`, `example.com`, `example.com`, `рф.com`, "(deprecated, use `%r`) mangles Unicode (risky!)"},
|
||||
{`%T%?_%D.zone`, `%T%?_%D.zone`, `example.com.zone`, `myTag_example.com.zone`, `myTag_рф.com.zone`, `mentioned in the docs`},
|
||||
{`db_%T%?_%D`, `db_%T%?_%D`, `db_example.com`, `db_myTag_example.com`, `db_myTag_рф.com`, `mentioned in the docs`},
|
||||
{`db_%D`, `db_%D`, `db_example.com`, `db_example.com`, `db_рф.com`, `mentioned in the docs`},
|
||||
|
||||
// Examples used in the documentation for the BIND provider
|
||||
{`%c.zone`, `%c.zone`, "example.com.zone", "example.com!myTag.zone", "xn--p1ai.com!myTag.zone", "Default format (v4.28 and later)"},
|
||||
{`%U.zone`, `%U.zone`, `example.com.zone`, `example.com!myTag.zone`, `рф.com!myTag.zone`, "Default format (pre-v4.28) (risky!)"},
|
||||
{`db_%f`, `db_%f`, `db_example.com`, `db_example.com_myTag`, `db_xn--p1ai.com_myTag`, "Recommended in a popular DNS book"},
|
||||
{`db_a%?_%T`, `db_%a%?_%T`, `db_example.com`, `db_example.com_myTag`, `db_xn--p1ai.com_myTag`, "same as above but using `%?_`"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got1 := makeFileName(tt.format, ff1)
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("makeFileName(%q) = ff1 %q, want %q", tt.format, got1, tt.want1)
|
||||
}
|
||||
got2 := makeFileName(tt.format, ff2)
|
||||
if got2 != tt.want2 {
|
||||
t.Errorf("makeFileName(%q) = ff2 %q, want %q", tt.format, got2, tt.want2)
|
||||
}
|
||||
got3 := makeFileName(tt.format, ff3)
|
||||
if got3 != tt.want3 {
|
||||
t.Errorf("makeFileName(%q) = ff3 %q, want %q", tt.format, got3, tt.want3)
|
||||
}
|
||||
// Uncomment to regenerate the table in the docs:
|
||||
fmt.Printf("MD | `%s` | %s | `%s` | `%s` | `%s` |\n", tt.format, tt.descr, got1, got2, got3)
|
||||
// Uncomment to regenerate the above test cases:
|
||||
//fmt.Printf("{`%s`, `%s`, `%s`, `%s`, `%s`, %q},\n", tt.name, tt.format, got1, got2, got3, tt.descr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_makeExtractor(t *testing.T) {
|
||||
type args struct {
|
||||
format string
|
||||
|
|
@ -83,6 +133,10 @@ func Test_makeExtractor(t *testing.T) {
|
|||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{"c", args{"%c.zone"}, `(.*)!.+\.zone|(.*)\.zone`, false},
|
||||
{"a", args{"%a.zone"}, `(.*)\.zone`, false},
|
||||
{"u", args{"%u.zone"}, `(.*)\.zone`, false},
|
||||
{"r", args{"%r.zone"}, `(.*)\.zone`, false},
|
||||
// TODO: Add test cases.
|
||||
{"u", args{"%U.zone"}, `(.*)!.+\.zone|(.*)\.zone`, false},
|
||||
{"d", args{"%D.zone"}, `(.*)\.zone`, false},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue