mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
Integration Testing framework (#46)
* integration test started * details * More tests. * idn tests and punycode (not tested fully because I'm on an aiplane) * test for dual provider compatibility * readme for tests * vendor idna * fix casing
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,8 +5,8 @@ dnscontrol.exe
|
|||||||
dnscontrol
|
dnscontrol
|
||||||
dnsconfig.js
|
dnsconfig.js
|
||||||
creds.json
|
creds.json
|
||||||
integration
|
|
||||||
ExternalDNS
|
ExternalDNS
|
||||||
docs/_site
|
docs/_site
|
||||||
powershell.log
|
powershell.log
|
||||||
zones/
|
zones/
|
||||||
|
integrationTest/.env
|
||||||
|
@ -26,6 +26,8 @@ For Google cloud authentication, DNSControl requires a JSON 'Service Account Key
|
|||||||
}
|
}
|
||||||
{% endhighlight %}
|
{% endhighlight %}
|
||||||
|
|
||||||
|
**Note**: The `project_id`, `private_key`, and `client_email`, are the only fields that are strictly required, but it is sometimes easier to just paste the entire json object in. Either way is fine.
|
||||||
|
|
||||||
See [the Activation section](#activation) for some tips on obtaining these credentials.
|
See [the Activation section](#activation) for some tips on obtaining these credentials.
|
||||||
|
|
||||||
## Metadata
|
## Metadata
|
||||||
|
213
integrationTest/integration_test.go
Normal file
213
integrationTest/integration_test.go
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/StackExchange/dnscontrol/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/nameservers"
|
||||||
|
"github.com/StackExchange/dnscontrol/providers"
|
||||||
|
_ "github.com/StackExchange/dnscontrol/providers/_all"
|
||||||
|
"github.com/StackExchange/dnscontrol/providers/config"
|
||||||
|
"github.com/miekg/dns/dnsutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var providerToRun = flag.String("provider", "", "Provider to run")
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProvider(t *testing.T) (providers.DNSServiceProvider, string) {
|
||||||
|
if *providerToRun == "" {
|
||||||
|
t.Log("No provider specified with -provider")
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
jsons, err := config.LoadProviderConfigs("providers.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error loading provider configs: %s", err)
|
||||||
|
}
|
||||||
|
for name, cfg := range jsons {
|
||||||
|
if *providerToRun != name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
provider, err := providers.CreateDNSProvider(name, cfg, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return provider, cfg["domain"]
|
||||||
|
}
|
||||||
|
t.Fatalf("Provider %s not found", *providerToRun)
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDNSProviders(t *testing.T) {
|
||||||
|
provider, domain := getProvider(t)
|
||||||
|
if provider == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Run(fmt.Sprintf("%s", domain), func(t *testing.T) {
|
||||||
|
runTests(t, provider, domain)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDomainConfigWithNameservers(t *testing.T, prv providers.DNSServiceProvider, domainName string) *models.DomainConfig {
|
||||||
|
dc := &models.DomainConfig{
|
||||||
|
Name: domainName,
|
||||||
|
}
|
||||||
|
// fix up nameservers
|
||||||
|
ns, err := prv.GetNameservers(domainName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Failed getting nameservers", err)
|
||||||
|
}
|
||||||
|
dc.Nameservers = ns
|
||||||
|
nameservers.AddNSRecords(dc)
|
||||||
|
return dc
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTests(t *testing.T, prv providers.DNSServiceProvider, domainName string) {
|
||||||
|
dc := getDomainConfigWithNameservers(t, prv, domainName)
|
||||||
|
// run tests one at a time
|
||||||
|
for i, tst := range tests {
|
||||||
|
if t.Failed() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
t.Run(fmt.Sprintf("%d: %s", i, tst.Desc), func(t *testing.T) {
|
||||||
|
dom, _ := dc.Copy()
|
||||||
|
for _, r := range tst.Records {
|
||||||
|
rc := models.RecordConfig(*r)
|
||||||
|
rc.NameFQDN = dnsutil.AddOrigin(rc.Name, domainName)
|
||||||
|
dom.Records = append(dom.Records, &rc)
|
||||||
|
}
|
||||||
|
dom2, _ := dom.Copy()
|
||||||
|
// get corrections for first time
|
||||||
|
corrections, err := prv.GetDomainCorrections(dom)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if i != 0 && len(corrections) == 0 {
|
||||||
|
t.Fatalf("Expect changes for all tests, but got none")
|
||||||
|
}
|
||||||
|
for _, c := range corrections {
|
||||||
|
err = c.F()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//run a second time and expect zero corrections
|
||||||
|
corrections, err = prv.GetDomainCorrections(dom2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(corrections) != 0 {
|
||||||
|
t.Fatalf("Expected 0 corrections on second run, but found %d.", len(corrections))
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDualProviders(t *testing.T) {
|
||||||
|
p, domain := getProvider(t)
|
||||||
|
if p == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dc := getDomainConfigWithNameservers(t, p, domain)
|
||||||
|
// clear everything
|
||||||
|
run := func() {
|
||||||
|
cs, err := p.GetDomainCorrections(dc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for i, c := range cs {
|
||||||
|
t.Logf("#%d: %s", i+1, c.Msg)
|
||||||
|
if err = c.F(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Log("Clearing everything")
|
||||||
|
run()
|
||||||
|
// add bogus nameservers
|
||||||
|
dc.Records = []*models.RecordConfig{}
|
||||||
|
dc.Nameservers = append(dc.Nameservers, models.StringsToNameservers([]string{"ns1.otherdomain.tld", "ns2.otherdomain.tld"})...)
|
||||||
|
nameservers.AddNSRecords(dc)
|
||||||
|
t.Log("Adding nameservers from another provider")
|
||||||
|
run()
|
||||||
|
// run again to make sure no corrections
|
||||||
|
t.Log("Running again to ensure stability")
|
||||||
|
cs, err := p.GetDomainCorrections(dc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(cs) != 0 {
|
||||||
|
t.Fatal("Expect no corrections on second run")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestCase struct {
|
||||||
|
Desc string
|
||||||
|
Records []*rec
|
||||||
|
}
|
||||||
|
|
||||||
|
type rec models.RecordConfig
|
||||||
|
|
||||||
|
func a(name, target string) *rec {
|
||||||
|
return makeRec(name, target, "A")
|
||||||
|
}
|
||||||
|
|
||||||
|
func cname(name, target string) *rec {
|
||||||
|
return makeRec(name, target, "CNAME")
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRec(name, target, typ string) *rec {
|
||||||
|
return &rec{
|
||||||
|
Name: name,
|
||||||
|
Type: typ,
|
||||||
|
Target: target,
|
||||||
|
TTL: 300,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rec) ttl(t uint32) *rec {
|
||||||
|
r.TTL = t
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func tc(desc string, recs ...*rec) *TestCase {
|
||||||
|
return &TestCase{
|
||||||
|
Desc: desc,
|
||||||
|
Records: recs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tests = []*TestCase{
|
||||||
|
// A
|
||||||
|
tc("Empty"),
|
||||||
|
tc("Create an A record", a("@", "1.1.1.1")),
|
||||||
|
tc("Change it", a("@", "1.2.3.4")),
|
||||||
|
tc("Add another", a("@", "1.2.3.4"), a("www", "1.2.3.4")),
|
||||||
|
tc("Add another(same name)", a("@", "1.2.3.4"), a("www", "1.2.3.4"), a("www", "5.6.7.8")),
|
||||||
|
tc("Change a ttl", a("@", "1.2.3.4").ttl(100), a("www", "1.2.3.4"), a("www", "5.6.7.8")),
|
||||||
|
tc("Change single target from set", a("@", "1.2.3.4").ttl(100), a("www", "2.2.2.2"), a("www", "5.6.7.8")),
|
||||||
|
tc("Change all ttls", a("@", "1.2.3.4").ttl(500), a("www", "2.2.2.2").ttl(400), a("www", "5.6.7.8").ttl(400)),
|
||||||
|
tc("Delete one", a("@", "1.2.3.4").ttl(500), a("www", "5.6.7.8").ttl(400)),
|
||||||
|
tc("Add back and change ttl", a("www", "5.6.7.8").ttl(700), a("www", "1.2.3.4").ttl(700)),
|
||||||
|
tc("Change targets and ttls", a("www", "1.1.1.1"), a("www", "2.2.2.2")),
|
||||||
|
// CNAMES
|
||||||
|
tc("Empty"),
|
||||||
|
tc("Create a CNAME", cname("foo", "google.com.")),
|
||||||
|
tc("Change it", cname("foo", "google2.com.")),
|
||||||
|
tc("Change to A record", a("foo", "1.2.3.4")),
|
||||||
|
tc("Change back to CNAME", cname("foo", "google.com.")),
|
||||||
|
|
||||||
|
//IDNAs
|
||||||
|
tc("Internationalized name", a("ööö", "1.2.3.4")),
|
||||||
|
tc("Change IDN", a("ööö", "2.2.2.2")),
|
||||||
|
tc("Internationalized CNAME Target", cname("a", "ööö.com.")),
|
||||||
|
tc("IDN CNAME AND Target", cname("öoö", "ööö.ööö.")),
|
||||||
|
}
|
8
integrationTest/providers.json
Normal file
8
integrationTest/providers.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"GCLOUD": {
|
||||||
|
"domain": "$GCLOUD_DOMAIN",
|
||||||
|
"project_id": "$GCLOUD_PROJECT",
|
||||||
|
"private_key": "$GCLOUD_PRIVATEKEY",
|
||||||
|
"client_email": "$GCLOUD_EMAIL",
|
||||||
|
}
|
||||||
|
}
|
16
integrationTest/readme.md
Normal file
16
integrationTest/readme.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
### Integration Tests
|
||||||
|
|
||||||
|
This is a simple framework for testing dns providers by making real requests.
|
||||||
|
|
||||||
|
There is a sequence of changes that are defined in the test file that are run against your chosen provider.
|
||||||
|
|
||||||
|
For each step, it will run the config once and expect changes. It will run it again and expect no changes. This should give us much higher confidence that providers will work in real life.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
`providers.json` should have an object for each provider type under test. This is identical to the json expected in creds.json for dnscontrol, except it also has a "domain" field specified for the domain to test. The domain does not even need to be registered for most providers. Note that `providers.json` expects environment variables to be specified with the relevant info.
|
||||||
|
|
||||||
|
## Running a test
|
||||||
|
|
||||||
|
1. Define all environment variables expected for the provider you wish to run. I setup a local `.env` file with the appropriate values and use [zoo](https://github.com/jsonmaur/zoo) to run my commands.
|
||||||
|
2. run `go test -v -provider $NAME` where $NAME is the name of the provider you wish to run.
|
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/transform"
|
"github.com/StackExchange/dnscontrol/transform"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
"golang.org/x/net/idna"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DefaultTTL = uint32(300)
|
const DefaultTTL = uint32(300)
|
||||||
@ -164,6 +165,32 @@ func (r *RecordConfig) Copy() (*RecordConfig, error) {
|
|||||||
return newR, err
|
return newR, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Punycode will convert all records to punycode format.
|
||||||
|
//It will encode:
|
||||||
|
//- Name
|
||||||
|
//- NameFQDN
|
||||||
|
//- Target (CNAME and MX only)
|
||||||
|
func (dc *DomainConfig) Punycode() error {
|
||||||
|
var err error
|
||||||
|
for _, rec := range dc.Records {
|
||||||
|
rec.Name, err = idna.ToASCII(rec.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rec.NameFQDN, err = idna.ToASCII(rec.NameFQDN)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rec.Type == "MX" || rec.Type == "CNAME" {
|
||||||
|
rec.Target, err = idna.ToASCII(rec.Target)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func copyObj(input interface{}, output interface{}) error {
|
func copyObj(input interface{}, output interface{}) error {
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
enc := gob.NewEncoder(buf)
|
enc := gob.NewEncoder(buf)
|
||||||
|
@ -34,14 +34,11 @@ func LoadProviderConfigs(fname string) (map[string]map[string]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func replaceEnvVars(m map[string]map[string]string) error {
|
func replaceEnvVars(m map[string]map[string]string) error {
|
||||||
for provider, keys := range m {
|
for _, keys := range m {
|
||||||
for k, v := range keys {
|
for k, v := range keys {
|
||||||
if strings.HasPrefix(v, "$") {
|
if strings.HasPrefix(v, "$") {
|
||||||
env := v[1:]
|
env := v[1:]
|
||||||
newVal := os.Getenv(env)
|
newVal := os.Getenv(env)
|
||||||
if newVal == "" {
|
|
||||||
return fmt.Errorf("Provider %s references environment variable %s, but has no value.", provider, env)
|
|
||||||
}
|
|
||||||
keys[k] = newVal
|
keys[k] = newVal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,9 @@ func keyForRec(r *models.RecordConfig) key {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||||
|
if err := dc.Punycode(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
rrs, zoneName, err := g.getRecords(dc.Name)
|
rrs, zoneName, err := g.getRecords(dc.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -59,7 +59,7 @@ func createRegistrar(rType string, config map[string]string) (Registrar, error)
|
|||||||
return initer(config)
|
return initer(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDNSProvider(dType string, config map[string]string, meta json.RawMessage) (DNSServiceProvider, error) {
|
func CreateDNSProvider(dType string, config map[string]string, meta json.RawMessage) (DNSServiceProvider, error) {
|
||||||
initer, ok := dspTypes[dType]
|
initer, ok := dspTypes[dType]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("DSP type %s not declared", dType)
|
return nil, fmt.Errorf("DSP type %s not declared", dType)
|
||||||
@ -93,7 +93,7 @@ func CreateDsps(d *models.DNSConfig, providerConfigs map[string]map[string]strin
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("DNSServiceProvider %s not listed in -providers file", dsp.Name)
|
return nil, fmt.Errorf("DNSServiceProvider %s not listed in -providers file", dsp.Name)
|
||||||
}
|
}
|
||||||
provider, err := createDNSProvider(dsp.Type, rawMsg, dsp.Metadata)
|
provider, err := CreateDNSProvider(dsp.Type, rawMsg, dsp.Metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
68
vendor/golang.org/x/net/idna/idna.go
generated
vendored
Normal file
68
vendor/golang.org/x/net/idna/idna.go
generated
vendored
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package idna implements IDNA2008 (Internationalized Domain Names for
|
||||||
|
// Applications), defined in RFC 5890, RFC 5891, RFC 5892, RFC 5893 and
|
||||||
|
// RFC 5894.
|
||||||
|
package idna // import "golang.org/x/net/idna"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(nigeltao): specify when errors occur. For example, is ToASCII(".") or
|
||||||
|
// ToASCII("foo\x00") an error? See also http://www.unicode.org/faq/idn.html#11
|
||||||
|
|
||||||
|
// acePrefix is the ASCII Compatible Encoding prefix.
|
||||||
|
const acePrefix = "xn--"
|
||||||
|
|
||||||
|
// ToASCII converts a domain or domain label to its ASCII form. For example,
|
||||||
|
// ToASCII("bücher.example.com") is "xn--bcher-kva.example.com", and
|
||||||
|
// ToASCII("golang") is "golang".
|
||||||
|
func ToASCII(s string) (string, error) {
|
||||||
|
if ascii(s) {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
labels := strings.Split(s, ".")
|
||||||
|
for i, label := range labels {
|
||||||
|
if !ascii(label) {
|
||||||
|
a, err := encode(acePrefix, label)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
labels[i] = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(labels, "."), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUnicode converts a domain or domain label to its Unicode form. For example,
|
||||||
|
// ToUnicode("xn--bcher-kva.example.com") is "bücher.example.com", and
|
||||||
|
// ToUnicode("golang") is "golang".
|
||||||
|
func ToUnicode(s string) (string, error) {
|
||||||
|
if !strings.Contains(s, acePrefix) {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
labels := strings.Split(s, ".")
|
||||||
|
for i, label := range labels {
|
||||||
|
if strings.HasPrefix(label, acePrefix) {
|
||||||
|
u, err := decode(label[len(acePrefix):])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
labels[i] = u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(labels, "."), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ascii(s string) bool {
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if s[i] >= utf8.RuneSelf {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
200
vendor/golang.org/x/net/idna/punycode.go
generated
vendored
Normal file
200
vendor/golang.org/x/net/idna/punycode.go
generated
vendored
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package idna
|
||||||
|
|
||||||
|
// This file implements the Punycode algorithm from RFC 3492.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// These parameter values are specified in section 5.
|
||||||
|
//
|
||||||
|
// All computation is done with int32s, so that overflow behavior is identical
|
||||||
|
// regardless of whether int is 32-bit or 64-bit.
|
||||||
|
const (
|
||||||
|
base int32 = 36
|
||||||
|
damp int32 = 700
|
||||||
|
initialBias int32 = 72
|
||||||
|
initialN int32 = 128
|
||||||
|
skew int32 = 38
|
||||||
|
tmax int32 = 26
|
||||||
|
tmin int32 = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// decode decodes a string as specified in section 6.2.
|
||||||
|
func decode(encoded string) (string, error) {
|
||||||
|
if encoded == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
pos := 1 + strings.LastIndex(encoded, "-")
|
||||||
|
if pos == 1 {
|
||||||
|
return "", fmt.Errorf("idna: invalid label %q", encoded)
|
||||||
|
}
|
||||||
|
if pos == len(encoded) {
|
||||||
|
return encoded[:len(encoded)-1], nil
|
||||||
|
}
|
||||||
|
output := make([]rune, 0, len(encoded))
|
||||||
|
if pos != 0 {
|
||||||
|
for _, r := range encoded[:pos-1] {
|
||||||
|
output = append(output, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i, n, bias := int32(0), initialN, initialBias
|
||||||
|
for pos < len(encoded) {
|
||||||
|
oldI, w := i, int32(1)
|
||||||
|
for k := base; ; k += base {
|
||||||
|
if pos == len(encoded) {
|
||||||
|
return "", fmt.Errorf("idna: invalid label %q", encoded)
|
||||||
|
}
|
||||||
|
digit, ok := decodeDigit(encoded[pos])
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("idna: invalid label %q", encoded)
|
||||||
|
}
|
||||||
|
pos++
|
||||||
|
i += digit * w
|
||||||
|
if i < 0 {
|
||||||
|
return "", fmt.Errorf("idna: invalid label %q", encoded)
|
||||||
|
}
|
||||||
|
t := k - bias
|
||||||
|
if t < tmin {
|
||||||
|
t = tmin
|
||||||
|
} else if t > tmax {
|
||||||
|
t = tmax
|
||||||
|
}
|
||||||
|
if digit < t {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
w *= base - t
|
||||||
|
if w >= math.MaxInt32/base {
|
||||||
|
return "", fmt.Errorf("idna: invalid label %q", encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x := int32(len(output) + 1)
|
||||||
|
bias = adapt(i-oldI, x, oldI == 0)
|
||||||
|
n += i / x
|
||||||
|
i %= x
|
||||||
|
if n > utf8.MaxRune || len(output) >= 1024 {
|
||||||
|
return "", fmt.Errorf("idna: invalid label %q", encoded)
|
||||||
|
}
|
||||||
|
output = append(output, 0)
|
||||||
|
copy(output[i+1:], output[i:])
|
||||||
|
output[i] = n
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return string(output), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode encodes a string as specified in section 6.3 and prepends prefix to
|
||||||
|
// the result.
|
||||||
|
//
|
||||||
|
// The "while h < length(input)" line in the specification becomes "for
|
||||||
|
// remaining != 0" in the Go code, because len(s) in Go is in bytes, not runes.
|
||||||
|
func encode(prefix, s string) (string, error) {
|
||||||
|
output := make([]byte, len(prefix), len(prefix)+1+2*len(s))
|
||||||
|
copy(output, prefix)
|
||||||
|
delta, n, bias := int32(0), initialN, initialBias
|
||||||
|
b, remaining := int32(0), int32(0)
|
||||||
|
for _, r := range s {
|
||||||
|
if r < 0x80 {
|
||||||
|
b++
|
||||||
|
output = append(output, byte(r))
|
||||||
|
} else {
|
||||||
|
remaining++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h := b
|
||||||
|
if b > 0 {
|
||||||
|
output = append(output, '-')
|
||||||
|
}
|
||||||
|
for remaining != 0 {
|
||||||
|
m := int32(0x7fffffff)
|
||||||
|
for _, r := range s {
|
||||||
|
if m > r && r >= n {
|
||||||
|
m = r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delta += (m - n) * (h + 1)
|
||||||
|
if delta < 0 {
|
||||||
|
return "", fmt.Errorf("idna: invalid label %q", s)
|
||||||
|
}
|
||||||
|
n = m
|
||||||
|
for _, r := range s {
|
||||||
|
if r < n {
|
||||||
|
delta++
|
||||||
|
if delta < 0 {
|
||||||
|
return "", fmt.Errorf("idna: invalid label %q", s)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if r > n {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
q := delta
|
||||||
|
for k := base; ; k += base {
|
||||||
|
t := k - bias
|
||||||
|
if t < tmin {
|
||||||
|
t = tmin
|
||||||
|
} else if t > tmax {
|
||||||
|
t = tmax
|
||||||
|
}
|
||||||
|
if q < t {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
output = append(output, encodeDigit(t+(q-t)%(base-t)))
|
||||||
|
q = (q - t) / (base - t)
|
||||||
|
}
|
||||||
|
output = append(output, encodeDigit(q))
|
||||||
|
bias = adapt(delta, h+1, h == b)
|
||||||
|
delta = 0
|
||||||
|
h++
|
||||||
|
remaining--
|
||||||
|
}
|
||||||
|
delta++
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
return string(output), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeDigit(x byte) (digit int32, ok bool) {
|
||||||
|
switch {
|
||||||
|
case '0' <= x && x <= '9':
|
||||||
|
return int32(x - ('0' - 26)), true
|
||||||
|
case 'A' <= x && x <= 'Z':
|
||||||
|
return int32(x - 'A'), true
|
||||||
|
case 'a' <= x && x <= 'z':
|
||||||
|
return int32(x - 'a'), true
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeDigit(digit int32) byte {
|
||||||
|
switch {
|
||||||
|
case 0 <= digit && digit < 26:
|
||||||
|
return byte(digit + 'a')
|
||||||
|
case 26 <= digit && digit < 36:
|
||||||
|
return byte(digit + ('0' - 26))
|
||||||
|
}
|
||||||
|
panic("idna: internal error in punycode encoding")
|
||||||
|
}
|
||||||
|
|
||||||
|
// adapt is the bias adaptation function specified in section 6.1.
|
||||||
|
func adapt(delta, numPoints int32, firstTime bool) int32 {
|
||||||
|
if firstTime {
|
||||||
|
delta /= damp
|
||||||
|
} else {
|
||||||
|
delta /= 2
|
||||||
|
}
|
||||||
|
delta += delta / numPoints
|
||||||
|
k := int32(0)
|
||||||
|
for delta > ((base-tmin)*tmax)/2 {
|
||||||
|
delta /= base - tmin
|
||||||
|
k += base
|
||||||
|
}
|
||||||
|
return k + (base-tmin+1)*delta/(delta+skew)
|
||||||
|
}
|
6
vendor/vendor.json
vendored
6
vendor/vendor.json
vendored
@ -338,6 +338,12 @@
|
|||||||
"revision": "4971afdc2f162e82d185353533d3cf16188a9f4e",
|
"revision": "4971afdc2f162e82d185353533d3cf16188a9f4e",
|
||||||
"revisionTime": "2016-11-15T21:05:04Z"
|
"revisionTime": "2016-11-15T21:05:04Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "GIGmSrYACByf5JDIP9ByBZksY80=",
|
||||||
|
"path": "golang.org/x/net/idna",
|
||||||
|
"revision": "a6577fac2d73be281a500b310739095313165611",
|
||||||
|
"revisionTime": "2017-03-08T20:54:49Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "XH7CgbL5Z8COUc+MKrYqS3FFosY=",
|
"checksumSHA1": "XH7CgbL5Z8COUc+MKrYqS3FFosY=",
|
||||||
"path": "golang.org/x/oauth2",
|
"path": "golang.org/x/oauth2",
|
||||||
|
Reference in New Issue
Block a user