mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-12-09 13:46:07 +08:00
FEATURE: Fixing IDN support for domains (#3879)
# Issue The previous fix had backwards compatibility issues and treated uppercase Unicode incorrectly. # Resolution * Don't call strings.ToUpper() on Unicode strings. Only call it on the output of ToASCII. * Fix BIND's "filenameformat" to be more compatible (only breaks if you had uppercase unicode in a domain name... which you probably didn't) * Change IDN to ASCII in most places (Thanks for the suggestion, @KaiSchwarz-cnic!) * Update BIND documentation
This commit is contained in:
parent
e87f03a8a3
commit
c11a523982
9 changed files with 266 additions and 132 deletions
|
|
@ -22,13 +22,18 @@ Example:
|
||||||
{
|
{
|
||||||
"bind": {
|
"bind": {
|
||||||
"TYPE": "BIND",
|
"TYPE": "BIND",
|
||||||
"directory": "myzones",
|
"directory": "myzones"
|
||||||
"filenameformat": "%U.zone"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
{% endcode %}
|
{% endcode %}
|
||||||
|
|
||||||
|
As of 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. For security reasons, the
|
||||||
|
automatic creation of subdirectories is disabled if `dnscontrol` is running as
|
||||||
|
root. (Running DNSControl as root is not recommended in general.)
|
||||||
|
|
||||||
## Meta configuration
|
## Meta configuration
|
||||||
|
|
||||||
This provider accepts some optional metadata in the `NewDnsProvider()` call.
|
This provider accepts some optional metadata in the `NewDnsProvider()` call.
|
||||||
|
|
@ -85,43 +90,59 @@ DNSControl does not handle special serial number math such as "looping through z
|
||||||
# filenameformat
|
# filenameformat
|
||||||
|
|
||||||
The `filenameformat` parameter specifies the file name to be used when
|
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".
|
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 filenameformat is a string with a few printf-like `%` verbs:
|
||||||
|
|
||||||
* The domain name without tag (the `example.com` part of `example.com!tag`):
|
| Verb | Description | `EXAMple.com` | `EXAMple.com!MyTag` | `рф.com!myTag` |
|
||||||
* `%D` as specified in `D()` (no IDN conversion, but downcased)
|
| ------- | ------------------------------------------------- | ------------- | ------------------- | -------------------- |
|
||||||
* `%I` converted to IDN/Punycode (`xn--...`) and downcased.
|
| `%T` | the tag | "" (null) | `myTag` | `myTag` |
|
||||||
* `%N` converted to Unicode (downcased first)
|
| `%c` | canonical name, globally unique and comparable | `example.com` | `example.com!myTag` | `xn--p1ai.com!myTag` |
|
||||||
* `%T` the split horizon tag, or "" (the `tag` part of `example.com!tag`)
|
| `%a` | ASCII domain (Punycode, downcased) | `example.com` | `example.com` | `xn--p1ai.com` |
|
||||||
* `%?x` this returns `x` if the split horizon tag is non-null, otherwise nothing. `x` can be any printable but is usually `!`.
|
| `%u` | Unicode domain (non-Unicode parts downcased) | `example.com` | `example.com` | `рф.com` |
|
||||||
* `%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.
|
| `%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` |
|
||||||
* ordinary characters (not `%`) are copied unchanged to the output stream
|
| `%F` | like `%f` but reversed order (`%T%?_%a`) | `example.com` | `myTag_example.com` | `myTag_xn--p1ai.com` |
|
||||||
* FYI: format strings must not end with an incomplete `%` or `%?`
|
| `%?x` | returns `x` if tag exists, otherwise "" | "" (null) | `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)
|
Useful examples:
|
||||||
* `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`
|
|
||||||
|
|
||||||
{% hint style="warning" %}
|
| Verb | Description | `EXAMple.com` | `EXAMple.com!MyTag` | `рф.com!myTag` |
|
||||||
**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
|
| `%c.zone` | Default format (v4.28 and later) | `example.com.zone` | `example.com!myTag.zone` | `xn--p1ai.com!myTag.zone` |
|
||||||
flapping back and forth between the two. The best way to prevent this is to
|
| `%U.zone` | Default format (pre-v4.28) (risky!) | `example.com.zone` | `example.com!myTag.zone` | `рф.com!myTag.zone` |
|
||||||
always include the tag (`%T`) or use `%U` which includes the tag.
|
| `db_%f` | Recommended in a popular DNS book | `db_example.com` | `db_example.com_myTag` | `db_xn--p1ai.com_myTag` |
|
||||||
{% endhint %}
|
| `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
|
Compatibility notes:
|
||||||
the filename. This includes both the portion of the path created by the
|
|
||||||
`directory` setting and the `filenameformat` setting. The automatic creation of
|
* `%D` should not be used. It downcases the string in a way that is probably
|
||||||
subdirectories is disabled if `dnscontrol` is running as root for security
|
incompatible with Unicode characters. It is retained for compatibility with
|
||||||
reasons.
|
pre-v4.28 releases. If your domain has capital Unicode characters, backwards
|
||||||
|
compatibility is not guaranteed. Use `%r` instead.
|
||||||
|
* `%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.
|
||||||
|
* If you are using pre-v4.28 releases the above table is slightly misleading
|
||||||
|
because uppercase ASCII letters do not always work. If you are using
|
||||||
|
pre-v4.28 releases, assume the above table lists `example.com` instead
|
||||||
|
of `EXAMpl.com`.
|
||||||
|
|
||||||
# FYI: get-zones
|
# FYI: get-zones
|
||||||
|
|
||||||
|
|
@ -132,7 +153,8 @@ any files named `*.zone` and assumes they are zone files.
|
||||||
dnscontrol get-zones --format=nameonly - BIND all
|
dnscontrol get-zones --format=nameonly - BIND all
|
||||||
```
|
```
|
||||||
|
|
||||||
If `filenameformat` is defined, `dnscontrol` makes a guess at which
|
If `filenameformat` is defined, `dnscontrol` makes a guess at which filenames
|
||||||
filenames are zones but doesn't try to hard to get it right, which is
|
are zones by reversing the logic of the format string. It doesn't try very hard
|
||||||
mathematically impossible in some cases. Feel free to file an issue if
|
to get this right, as getting it right in all situations is mathematically
|
||||||
your format string doesn't work. I love a challenge!
|
impossible. Feel free to file an issue if find a situation where it doesn't
|
||||||
|
work. I love a challenge!
|
||||||
|
|
|
||||||
|
|
@ -2033,11 +2033,6 @@ func makeTests() []*TestGroup {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// This MUST be the last test.
|
|
||||||
testgroup("final",
|
|
||||||
tc("final", txt("final", `TestDNSProviders was successful!`)),
|
|
||||||
),
|
|
||||||
|
|
||||||
testgroup("SMIMEA",
|
testgroup("SMIMEA",
|
||||||
requires(providers.CanUseSMIMEA),
|
requires(providers.CanUseSMIMEA),
|
||||||
tc("SMIMEA record", smimea("_443._tcp", 3, 1, 1, sha256hash)),
|
tc("SMIMEA record", smimea("_443._tcp", 3, 1, 1, sha256hash)),
|
||||||
|
|
@ -2060,6 +2055,12 @@ func makeTests() []*TestGroup {
|
||||||
// every quarter. There may be library updates, API changes,
|
// every quarter. There may be library updates, API changes,
|
||||||
// etc.
|
// etc.
|
||||||
|
|
||||||
|
// This SHOULD be the last test. We do this so that we always
|
||||||
|
// leave zones with a single TXT record exclaming our success.
|
||||||
|
// Nothing depends on this record existing or should depend on it.
|
||||||
|
testgroup("final",
|
||||||
|
tc("final", txt("final", `TestDNSProviders was successful!`)),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
return tests
|
return tests
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ const (
|
||||||
DomainTag = "dnscontrol_tag" // A copy of DomainConfig.Tag
|
DomainTag = "dnscontrol_tag" // A copy of DomainConfig.Tag
|
||||||
DomainUniqueName = "dnscontrol_uniquename" // A copy of DomainConfig.UniqueName
|
DomainUniqueName = "dnscontrol_uniquename" // A copy of DomainConfig.UniqueName
|
||||||
DomainNameRaw = "dnscontrol_nameraw" // A copy of DomainConfig.NameRaw
|
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
|
DomainNameUnicode = "dnscontrol_nameunicode" // A copy of DomainConfig.NameUnicode
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -74,7 +74,7 @@ func (dc *DomainConfig) PostProcess() {
|
||||||
|
|
||||||
// Turn the user-supplied name into the fixed forms.
|
// Turn the user-supplied name into the fixed forms.
|
||||||
ff := domaintags.MakeDomainFixForms(dc.Name)
|
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.
|
// 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.
|
// This is a bit ugly but avoids a huge refactor. Please avoid using these to make the future refactor easier.
|
||||||
|
|
@ -82,7 +82,7 @@ func (dc *DomainConfig) PostProcess() {
|
||||||
dc.Metadata[DomainTag] = dc.Tag
|
dc.Metadata[DomainTag] = dc.Tag
|
||||||
}
|
}
|
||||||
//dc.Metadata[DomainNameRaw] = dc.NameRaw
|
//dc.Metadata[DomainNameRaw] = dc.NameRaw
|
||||||
//dc.Metadata[DomainNameIDN] = dc.Name
|
//dc.Metadata[DomainNameASCII] = dc.Name
|
||||||
//dc.Metadata[DomainNameUnicode] = dc.NameUnicode
|
//dc.Metadata[DomainNameUnicode] = dc.NameUnicode
|
||||||
dc.Metadata[DomainUniqueName] = dc.UniqueName
|
dc.Metadata[DomainUniqueName] = dc.UniqueName
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
// DomainFixedForms stores the various fixed forms of a domain name and tag.
|
// DomainFixedForms stores the various fixed forms of a domain name and tag.
|
||||||
type DomainFixedForms struct {
|
type DomainFixedForms struct {
|
||||||
NameRaw string // "originalinput.com" (name as input by the user, lowercased (no tag))
|
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)
|
NameUnicode string // "unicode.com" (converted to downcase BEFORE unicode conversion)
|
||||||
UniqueName string // "punycode.com!tag"
|
UniqueName string // "punycode.com!tag"
|
||||||
|
|
||||||
|
|
@ -25,7 +25,7 @@ type DomainFixedForms struct {
|
||||||
// * .UniqueName: "example.com!tag" unique across the entire config.
|
// * .UniqueName: "example.com!tag" unique across the entire config.
|
||||||
func MakeDomainFixForms(n string) DomainFixedForms {
|
func MakeDomainFixForms(n string) DomainFixedForms {
|
||||||
var err error
|
var err error
|
||||||
var tag, nameRaw, nameIDN, nameUnicode, uniqueName string
|
var tag, nameRaw, nameASCII, nameUnicode, uniqueName string
|
||||||
var hasBang bool
|
var hasBang bool
|
||||||
|
|
||||||
// Split tag from name.
|
// Split tag from name.
|
||||||
|
|
@ -38,42 +38,45 @@ func MakeDomainFixForms(n string) DomainFixedForms {
|
||||||
hasBang = false
|
hasBang = false
|
||||||
}
|
}
|
||||||
|
|
||||||
nameRaw = strings.ToLower(p[0])
|
nameRaw = p[0]
|
||||||
if strings.HasPrefix(n, nameRaw) {
|
if strings.HasPrefix(n, nameRaw) {
|
||||||
// Avoid pointless duplication.
|
// Avoid pointless duplication.
|
||||||
nameRaw = n[0:len(nameRaw)]
|
nameRaw = n[0:len(nameRaw)]
|
||||||
}
|
}
|
||||||
|
|
||||||
nameIDN, err = idna.ToASCII(nameRaw)
|
nameASCII, err = idna.ToASCII(nameRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
nameIDN = nameRaw // Fallback to raw name on error.
|
nameASCII = nameRaw // Fallback to raw name on error.
|
||||||
} else {
|
} else {
|
||||||
|
nameASCII = strings.ToLower(nameASCII)
|
||||||
// Avoid pointless duplication.
|
// Avoid pointless duplication.
|
||||||
if nameIDN == nameRaw {
|
if strings.HasPrefix(n, nameASCII) {
|
||||||
nameIDN = nameRaw
|
// 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 {
|
if err != nil {
|
||||||
nameUnicode = nameRaw // Fallback to raw name on error.
|
nameUnicode = nameRaw // Fallback to raw name on error.
|
||||||
} else {
|
} else {
|
||||||
// Avoid pointless duplication.
|
// Avoid pointless duplication.
|
||||||
if nameUnicode == nameRaw {
|
if strings.HasPrefix(n, nameUnicode) {
|
||||||
nameUnicode = nameRaw
|
// Avoid pointless duplication.
|
||||||
|
nameUnicode = n[0:len(nameUnicode)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasBang {
|
if hasBang {
|
||||||
uniqueName = nameIDN + "!" + tag
|
uniqueName = nameASCII + "!" + tag
|
||||||
} else {
|
} else {
|
||||||
uniqueName = nameIDN
|
uniqueName = nameASCII
|
||||||
}
|
}
|
||||||
|
|
||||||
return DomainFixedForms{
|
return DomainFixedForms{
|
||||||
Tag: tag,
|
Tag: tag,
|
||||||
NameRaw: nameRaw,
|
NameRaw: nameRaw,
|
||||||
NameIDN: nameIDN,
|
NameASCII: nameASCII,
|
||||||
NameUnicode: nameUnicode,
|
NameUnicode: nameUnicode,
|
||||||
UniqueName: uniqueName,
|
UniqueName: uniqueName,
|
||||||
HasBang: hasBang,
|
HasBang: hasBang,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
||||||
input string
|
input string
|
||||||
wantTag string
|
wantTag string
|
||||||
wantNameRaw string
|
wantNameRaw string
|
||||||
wantNameIDN string
|
wantNameASCII string
|
||||||
wantNameUnicode string
|
wantNameUnicode string
|
||||||
wantUniqueName string
|
wantUniqueName string
|
||||||
wantHasBang bool
|
wantHasBang bool
|
||||||
|
|
@ -20,7 +20,7 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
||||||
input: "example.com",
|
input: "example.com",
|
||||||
wantTag: "",
|
wantTag: "",
|
||||||
wantNameRaw: "example.com",
|
wantNameRaw: "example.com",
|
||||||
wantNameIDN: "example.com",
|
wantNameASCII: "example.com",
|
||||||
wantNameUnicode: "example.com",
|
wantNameUnicode: "example.com",
|
||||||
wantUniqueName: "example.com",
|
wantUniqueName: "example.com",
|
||||||
wantHasBang: false,
|
wantHasBang: false,
|
||||||
|
|
@ -30,7 +30,7 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
||||||
input: "example.com!mytag",
|
input: "example.com!mytag",
|
||||||
wantTag: "mytag",
|
wantTag: "mytag",
|
||||||
wantNameRaw: "example.com",
|
wantNameRaw: "example.com",
|
||||||
wantNameIDN: "example.com",
|
wantNameASCII: "example.com",
|
||||||
wantNameUnicode: "example.com",
|
wantNameUnicode: "example.com",
|
||||||
wantUniqueName: "example.com!mytag",
|
wantUniqueName: "example.com!mytag",
|
||||||
wantHasBang: true,
|
wantHasBang: true,
|
||||||
|
|
@ -40,7 +40,7 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
||||||
input: "example.com!",
|
input: "example.com!",
|
||||||
wantTag: "",
|
wantTag: "",
|
||||||
wantNameRaw: "example.com",
|
wantNameRaw: "example.com",
|
||||||
wantNameIDN: "example.com",
|
wantNameASCII: "example.com",
|
||||||
wantNameUnicode: "example.com",
|
wantNameUnicode: "example.com",
|
||||||
wantUniqueName: "example.com!",
|
wantUniqueName: "example.com!",
|
||||||
wantHasBang: true,
|
wantHasBang: true,
|
||||||
|
|
@ -50,7 +50,7 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
||||||
input: "उदाहरण.com",
|
input: "उदाहरण.com",
|
||||||
wantTag: "",
|
wantTag: "",
|
||||||
wantNameRaw: "उदाहरण.com",
|
wantNameRaw: "उदाहरण.com",
|
||||||
wantNameIDN: "xn--p1b6ci4b4b3a.com",
|
wantNameASCII: "xn--p1b6ci4b4b3a.com",
|
||||||
wantNameUnicode: "उदाहरण.com",
|
wantNameUnicode: "उदाहरण.com",
|
||||||
wantUniqueName: "xn--p1b6ci4b4b3a.com",
|
wantUniqueName: "xn--p1b6ci4b4b3a.com",
|
||||||
wantHasBang: false,
|
wantHasBang: false,
|
||||||
|
|
@ -60,7 +60,7 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
||||||
input: "उदाहरण.com!mytag",
|
input: "उदाहरण.com!mytag",
|
||||||
wantTag: "mytag",
|
wantTag: "mytag",
|
||||||
wantNameRaw: "उदाहरण.com",
|
wantNameRaw: "उदाहरण.com",
|
||||||
wantNameIDN: "xn--p1b6ci4b4b3a.com",
|
wantNameASCII: "xn--p1b6ci4b4b3a.com",
|
||||||
wantNameUnicode: "उदाहरण.com",
|
wantNameUnicode: "उदाहरण.com",
|
||||||
wantUniqueName: "xn--p1b6ci4b4b3a.com!mytag",
|
wantUniqueName: "xn--p1b6ci4b4b3a.com!mytag",
|
||||||
wantHasBang: true,
|
wantHasBang: true,
|
||||||
|
|
@ -70,17 +70,29 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
||||||
input: "xn--p1b6ci4b4b3a.com",
|
input: "xn--p1b6ci4b4b3a.com",
|
||||||
wantTag: "",
|
wantTag: "",
|
||||||
wantNameRaw: "xn--p1b6ci4b4b3a.com",
|
wantNameRaw: "xn--p1b6ci4b4b3a.com",
|
||||||
wantNameIDN: "xn--p1b6ci4b4b3a.com",
|
wantNameASCII: "xn--p1b6ci4b4b3a.com",
|
||||||
wantNameUnicode: "उदाहरण.com",
|
wantNameUnicode: "उदाहरण.com",
|
||||||
wantUniqueName: "xn--p1b6ci4b4b3a.com",
|
wantUniqueName: "xn--p1b6ci4b4b3a.com",
|
||||||
wantHasBang: false,
|
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",
|
name: "punycode domain with tag",
|
||||||
input: "xn--p1b6ci4b4b3a.com!mytag",
|
input: "xn--p1b6ci4b4b3a.com!mytag",
|
||||||
wantTag: "mytag",
|
wantTag: "mytag",
|
||||||
wantNameRaw: "xn--p1b6ci4b4b3a.com",
|
wantNameRaw: "xn--p1b6ci4b4b3a.com",
|
||||||
wantNameIDN: "xn--p1b6ci4b4b3a.com",
|
wantNameASCII: "xn--p1b6ci4b4b3a.com",
|
||||||
wantNameUnicode: "उदाहरण.com",
|
wantNameUnicode: "उदाहरण.com",
|
||||||
wantUniqueName: "xn--p1b6ci4b4b3a.com!mytag",
|
wantUniqueName: "xn--p1b6ci4b4b3a.com!mytag",
|
||||||
wantHasBang: true,
|
wantHasBang: true,
|
||||||
|
|
@ -89,8 +101,8 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
||||||
name: "mixed case domain",
|
name: "mixed case domain",
|
||||||
input: "Example.COM",
|
input: "Example.COM",
|
||||||
wantTag: "",
|
wantTag: "",
|
||||||
wantNameRaw: "example.com",
|
wantNameRaw: "Example.COM",
|
||||||
wantNameIDN: "example.com",
|
wantNameASCII: "example.com",
|
||||||
wantNameUnicode: "example.com",
|
wantNameUnicode: "example.com",
|
||||||
wantUniqueName: "example.com",
|
wantUniqueName: "example.com",
|
||||||
wantHasBang: false,
|
wantHasBang: false,
|
||||||
|
|
@ -99,12 +111,34 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
||||||
name: "mixed case domain with tag",
|
name: "mixed case domain with tag",
|
||||||
input: "Example.COM!MyTag",
|
input: "Example.COM!MyTag",
|
||||||
wantTag: "MyTag",
|
wantTag: "MyTag",
|
||||||
wantNameRaw: "example.com",
|
wantNameRaw: "Example.COM",
|
||||||
wantNameIDN: "example.com",
|
wantNameASCII: "example.com",
|
||||||
wantNameUnicode: "example.com",
|
wantNameUnicode: "example.com",
|
||||||
wantUniqueName: "example.com!MyTag",
|
wantUniqueName: "example.com!MyTag",
|
||||||
wantHasBang: true,
|
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",
|
||||||
|
wantTag: "",
|
||||||
|
wantNameRaw: "рф.com",
|
||||||
|
wantNameASCII: "xn--p1ai.com",
|
||||||
|
wantNameUnicode: "рф.com",
|
||||||
|
wantUniqueName: "xn--p1ai.com",
|
||||||
|
wantHasBang: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
@ -116,8 +150,8 @@ func Test_MakeDomainFixForms(t *testing.T) {
|
||||||
if got.NameRaw != tt.wantNameRaw {
|
if got.NameRaw != tt.wantNameRaw {
|
||||||
t.Errorf("MakeDomainFixForms() gotNameRaw = %v, want %v", got.NameRaw, tt.wantNameRaw)
|
t.Errorf("MakeDomainFixForms() gotNameRaw = %v, want %v", got.NameRaw, tt.wantNameRaw)
|
||||||
}
|
}
|
||||||
if got.NameIDN != tt.wantNameIDN {
|
if got.NameASCII != tt.wantNameASCII {
|
||||||
t.Errorf("MakeDomainFixForms() gotNameIDN = %v, want %v", got.NameIDN, tt.wantNameIDN)
|
t.Errorf("MakeDomainFixForms() gotNameASCII = %v, want %v", got.NameASCII, tt.wantNameASCII)
|
||||||
}
|
}
|
||||||
if got.NameUnicode != tt.wantNameUnicode {
|
if got.NameUnicode != tt.wantNameUnicode {
|
||||||
t.Errorf("MakeDomainFixForms() gotNameUnicode = %v, want %v", 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
|
continue
|
||||||
}
|
}
|
||||||
ff := MakeDomainFixForms(l)
|
ff := MakeDomainFixForms(l)
|
||||||
if ff.HasBang && ff.NameIDN == "" { // Treat empty name as wildcard.
|
if ff.HasBang && ff.NameASCII == "" { // Treat empty name as wildcard.
|
||||||
ff.NameIDN = "*"
|
ff.NameASCII = "*"
|
||||||
}
|
}
|
||||||
sl.items = append(sl.items, ff)
|
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.
|
// Now that we know the tag matches, we can focus on the name.
|
||||||
|
|
||||||
// `*!tag` or `*` matches everything.
|
// `*!tag` or `*` matches everything.
|
||||||
if filterItem.NameIDN == "*" {
|
if filterItem.NameASCII == "*" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the name starts with "*." then match the suffix.
|
// If the name starts with "*." then match the suffix.
|
||||||
if strings.HasPrefix(filterItem.NameIDN, "*.") {
|
if strings.HasPrefix(filterItem.NameASCII, "*.") {
|
||||||
// example.com matches *.example.com
|
// 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
|
return true
|
||||||
}
|
}
|
||||||
// foo.example.com matches *.example.com
|
// 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
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No wildcards? Exact match.
|
// No wildcards? Exact match.
|
||||||
if filterItem.NameIDN == domToCheckFF.NameIDN || filterItem.NameUnicode == domToCheckFF.NameUnicode {
|
if filterItem.NameASCII == domToCheckFF.NameASCII || filterItem.NameUnicode == domToCheckFF.NameUnicode {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ func initBind(config map[string]string, providermeta json.RawMessage) (providers
|
||||||
api.directory = "zones"
|
api.directory = "zones"
|
||||||
}
|
}
|
||||||
if api.filenameformat == "" {
|
if api.filenameformat == "" {
|
||||||
api.filenameformat = "%U.zone"
|
api.filenameformat = "%c.zone"
|
||||||
}
|
}
|
||||||
if len(providermeta) != 0 {
|
if len(providermeta) != 0 {
|
||||||
err := json.Unmarshal(providermeta, api)
|
err := json.Unmarshal(providermeta, api)
|
||||||
|
|
@ -171,7 +171,7 @@ func (c *bindProvider) GetZoneRecords(domain string, meta map[string]string) (mo
|
||||||
ff := domaintags.DomainFixedForms{
|
ff := domaintags.DomainFixedForms{
|
||||||
Tag: meta[models.DomainTag],
|
Tag: meta[models.DomainTag],
|
||||||
NameRaw: meta[models.DomainNameRaw],
|
NameRaw: meta[models.DomainNameRaw],
|
||||||
NameIDN: domain,
|
NameASCII: domain,
|
||||||
NameUnicode: meta[models.DomainNameUnicode],
|
NameUnicode: meta[models.DomainNameUnicode],
|
||||||
UniqueName: meta[models.DomainUniqueName],
|
UniqueName: meta[models.DomainUniqueName],
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +282,7 @@ func (c *bindProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, foundR
|
||||||
domaintags.DomainFixedForms{
|
domaintags.DomainFixedForms{
|
||||||
Tag: dc.Tag,
|
Tag: dc.Tag,
|
||||||
NameRaw: dc.NameRaw,
|
NameRaw: dc.NameRaw,
|
||||||
NameIDN: dc.Name,
|
NameASCII: dc.Name,
|
||||||
NameUnicode: dc.NameUnicode,
|
NameUnicode: dc.NameUnicode,
|
||||||
UniqueName: dc.UniqueName,
|
UniqueName: dc.UniqueName,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,6 @@ import (
|
||||||
// makeFileName uses format to generate a zone's filename. See the
|
// makeFileName uses format to generate a zone's filename. See the
|
||||||
func makeFileName(format string, ff domaintags.DomainFixedForms) string {
|
func makeFileName(format string, ff domaintags.DomainFixedForms) string {
|
||||||
//fmt.Printf("DEBUG: makeFileName(%q, %+v)\n", format, ff)
|
//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 == "" {
|
if format == "" {
|
||||||
panic("BUG: makeFileName called with null format")
|
panic("BUG: makeFileName called with null format")
|
||||||
}
|
}
|
||||||
|
|
@ -40,16 +35,31 @@ func makeFileName(format string, ff domaintags.DomainFixedForms) string {
|
||||||
pos++
|
pos++
|
||||||
tok = tokens[pos]
|
tok = tokens[pos]
|
||||||
switch tok {
|
switch tok {
|
||||||
case "D":
|
|
||||||
b.WriteString(nameRaw)
|
// v4.28 names
|
||||||
case "T":
|
case "r": // NameRaw "originalinput.com" (i for input)
|
||||||
b.WriteString(tag)
|
b.WriteString(ff.NameRaw)
|
||||||
case "U":
|
case "a": // NameASCII "punycode.com" (a for ascii)
|
||||||
b.WriteString(uniquename)
|
b.WriteString(ff.NameASCII)
|
||||||
case "I":
|
case "u": // NameUnicode "unicode.com" (u for unicode)
|
||||||
b.WriteString(nameIDN)
|
b.WriteString(ff.NameUnicode)
|
||||||
case "N":
|
case "c": // UniqueName "punycode.com!tag" or "punycode.com" if no tag (c for canonical)
|
||||||
b.WriteString(nameUnicode)
|
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 "%":
|
case "%":
|
||||||
b.WriteString("%")
|
b.WriteString("%")
|
||||||
case "?":
|
case "?":
|
||||||
|
|
@ -59,9 +69,20 @@ func makeFileName(format string, ff domaintags.DomainFixedForms) string {
|
||||||
}
|
}
|
||||||
pos++
|
pos++
|
||||||
tok = tokens[pos]
|
tok = tokens[pos]
|
||||||
if tag != "" {
|
if ff.Tag != "" {
|
||||||
b.WriteString(tok)
|
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:
|
default:
|
||||||
fmt.Fprintf(&b, "%%(unknown %%verb %%%s)", tok)
|
fmt.Fprintf(&b, "%%(unknown %%verb %%%s)", tok)
|
||||||
}
|
}
|
||||||
|
|
@ -156,14 +177,14 @@ func makeExtractor(format string) (string, error) {
|
||||||
pos++
|
pos++
|
||||||
tok = tokens[pos]
|
tok = tokens[pos]
|
||||||
switch tok {
|
switch tok {
|
||||||
case "D":
|
case "D", "a", "u", "r":
|
||||||
b.WriteString(`(.*)`)
|
b.WriteString(`(.*)`)
|
||||||
case "T":
|
case "T":
|
||||||
if pass == 0 {
|
if pass == 0 {
|
||||||
// On the second pass, nothing is generated.
|
// On the second pass, nothing is generated.
|
||||||
b.WriteString(`.*`)
|
b.WriteString(`.*`)
|
||||||
}
|
}
|
||||||
case "U":
|
case "U", "c":
|
||||||
if pass == 0 {
|
if pass == 0 {
|
||||||
b.WriteString(`(.*)!.+`)
|
b.WriteString(`(.*)!.+`)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -8,29 +8,28 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_makeFileName(t *testing.T) {
|
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"
|
fmtDefault := "%U.zone"
|
||||||
fmtBasic := "%U - %T - %D"
|
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
|
fmtFancy := "%T%?_%D.zone" // Include the tag_ only if there is a tag
|
||||||
fmtErrorPct := "literal%"
|
fmtErrorPct := "literal%"
|
||||||
fmtErrorOpt := "literal%?"
|
fmtErrorOpt := "literal%?"
|
||||||
fmtErrorUnk := "literal%o" // Unknown % verb
|
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 {
|
type args struct {
|
||||||
format string
|
format string
|
||||||
ff domaintags.DomainFixedForms
|
ff domaintags.DomainFixedForms
|
||||||
|
|
@ -40,25 +39,15 @@ func Test_makeFileName(t *testing.T) {
|
||||||
args args
|
args args
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
|
// Test corner cases and common cases.
|
||||||
{"literal", args{"literal", ff}, "literal"},
|
{"literal", args{"literal", ff}, "literal"},
|
||||||
{"middle", args{"mid%Dle", ff}, "midrawle"},
|
{"basic", args{fmtBasic, ff}, "domy!tagy - tagy - domy"},
|
||||||
{"D", args{"%D", ff}, "raw"},
|
{"solo", args{"%D", ff}, "domy"},
|
||||||
{"I", args{"%I", ff}, "idn"},
|
{"front", args{"%Daaa", ff}, "domyaaa"},
|
||||||
{"N", args{"%N", ff}, "unicode"},
|
{"tail", args{"bbb%D", ff}, "bbbdomy"},
|
||||||
{"T", args{"%T", ff}, "tagb"},
|
{"def", args{fmtDefault, ff}, "domy!tagy.zone"},
|
||||||
{"x1", args{"XX%?xYY", ff}, "XXxYY"},
|
{"fanWI", args{fmtFancy, ff}, "tagy_domy.zone"},
|
||||||
{"x2", args{"AA%?xBB", tagless}, "AABB"},
|
{"fanWO", args{fmtFancy, tagless}, "domy.zone"},
|
||||||
{"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"},
|
|
||||||
{"errP", args{fmtErrorPct, ff}, "literal%(format may not end in %)"},
|
{"errP", args{fmtErrorPct, ff}, "literal%(format may not end in %)"},
|
||||||
{"errQ", args{fmtErrorOpt, 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)"},
|
{"errU", args{fmtErrorUnk, ff}, "literal%(unknown %verb %o)"},
|
||||||
|
|
@ -73,6 +62,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 // Not used in test, just for documentation generation
|
||||||
|
}{
|
||||||
|
// 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 lines used in documentation/provider/bind.md 's table:
|
||||||
|
// fmt.Print(strings.ReplaceAll(fmt.Sprintf("| `%s` | %s | `%s` | `%s` | `%s` |\n", tt.format, tt.descr, got1, got2, got3), "``", "`\"\"` (null)"))
|
||||||
|
//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) {
|
func Test_makeExtractor(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
format string
|
format string
|
||||||
|
|
@ -83,6 +132,10 @@ func Test_makeExtractor(t *testing.T) {
|
||||||
want string
|
want string
|
||||||
wantErr bool
|
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.
|
// TODO: Add test cases.
|
||||||
{"u", args{"%U.zone"}, `(.*)!.+\.zone|(.*)\.zone`, false},
|
{"u", args{"%U.zone"}, `(.*)!.+\.zone|(.*)\.zone`, false},
|
||||||
{"d", args{"%D.zone"}, `(.*)\.zone`, false},
|
{"d", args{"%D.zone"}, `(.*)\.zone`, false},
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue