pax_global_header00006660000000000000000000000064147422714230014520gustar00rootroot0000000000000052 comment=42c0f82ebf7a66402296a5ad3266a49c11e3a6c4 rdap-0.9.1/000077500000000000000000000000001474227142300124555ustar00rootroot00000000000000rdap-0.9.1/.github/000077500000000000000000000000001474227142300140155ustar00rootroot00000000000000rdap-0.9.1/.github/workflows/000077500000000000000000000000001474227142300160525ustar00rootroot00000000000000rdap-0.9.1/.github/workflows/go.yml000066400000000000000000000010621474227142300172010ustar00rootroot00000000000000name: Go on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: name: Go Build runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - name: Set up Go 1.x uses: actions/setup-go@v2 with: go-version: '>= 1.20.4' id: go - name: Check out code into the Go module directory uses: actions/checkout@v3 - name: Linters run: | go vet ./... - name: Test run: | go test ./... rdap-0.9.1/.gitignore000066400000000000000000000000071474227142300144420ustar00rootroot00000000000000.*.un~ rdap-0.9.1/LICENSE000066400000000000000000000020371474227142300134640ustar00rootroot00000000000000Copyright (c) 2017 Tom Harwood Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rdap-0.9.1/README.md000066400000000000000000000075351474227142300137460ustar00rootroot00000000000000 OpenRDAP is an command line [RDAP](https://datatracker.ietf.org/wg/weirds/documents/) client implementation in Go. [![Build Status](https://travis-ci.org/openrdap/rdap.svg?branch=master)](https://travis-ci.org/openrdap/rdap) https://www.openrdap.org - homepage https://www.openrdap.org/demo - live demo ## Features * Command line RDAP client * Query types supported: * ip * domain * autnum * nameserver * entity * help * url * domain-search * domain-search-by-nameserver * domain-search-by-nameserver-ip * nameserver-search * nameserver-search-by-ip * entity-search * entity-search-by-handle * Query bootstrapping (automatic RDAP server URL detection for ip/domain/autnum/(experimental) entity queries) * Bootstrap cache (optional, uses ~/.openrdap by default) * X.509 client authentication * Output formats: text, JSON, WHOIS style * Experimental [object tagging](https://datatracker.ietf.org/doc/draft-ietf-regext-rdap-object-tag/) support ## Installation This program uses Go. The Go compiler is available from https://golang.org/. To install: go install github.com/openrdap/rdap/cmd/rdap@master This will install the "rdap" binary in your $GOPATH/go/bin directory. Try running: ~/go/bin/rdap google.com ## Usage | Query type | Usage | | --- | --- | | Domain (.com) | rdap -v example.com | | Network | rdap -v 2001:db8:: | | Autnum | rdap -v AS15169 | | Nameserver | rdap -v -t nameserver -s https://rdap.verisign.com/com/v1 ns1.google.com | | Help | rdap -v -t help -s https://rdap.verisign.com/com/v1 | | Domain Search | rdap -v -t domain-search -s $SERVER_URL example*.gtld | | Domain Search (by NS) | rdap -v -t domain-search-by-nameserver -s $SERVER_URL ns1.example.gtld | | Domain Search (by NS IP) | rdap -v -t domain-search-by-nameserver-ip -s $SERVER_URL 192.0.2.0 | | Nameserver Search | rdap -v -t nameserver-search -s $SERVER_URL ns1.example.gtld | | Nameserver Search (by IP) | rdap -v -t nameserver-search-by-ip -s $SERVER_URL 192.0.2.0 | | Entity Search | rdap -v -t entity-search -s $SERVER_URL ENTITY-TAG | | Entity Search (by handle) | rdap -v -t entity-search-by-handle -s $SERVER_URL ENTITY-TAG | See https://www.openrdap.org/docs. ## Go docs [![godoc](https://godoc.org/github.com/openrdap/rdap?status.png)](https://godoc.org/github.com/openrdap/rdap) ## Uses Go 1.20+ ## Links - Wikipedia - [Registration Data Access Protocol](https://en.wikipedia.org/wiki/Registration_Data_Access_Protocol) - [ICANN RDAP pilot](https://www.icann.org/rdap) - [OpenRDAP](https://www.openrdap.org) - https://data.iana.org/rdap/ - Official IANA bootstrap information - https://test.rdap.net/rdap/ - Test alternate bootstrap service with more experimental RDAP servers - [RFC 7480 HTTP Usage in the Registration Data Access Protocol (RDAP)](https://tools.ietf.org/html/rfc7480) - [RFC 7481 Security Services for the Registration Data Access Protocol (RDAP)](https://tools.ietf.org/html/rfc7481) - [RFC 7482 Registration Data Access Protocol (RDAP) Query Format](https://tools.ietf.org/html/rfc7482) - [RFC 7483 JSON Responses for the Registration Data Access Protocol (RDAP)](https://tools.ietf.org/html/rfc7483) - [RFC 7484 Finding the Authoritative Registration Data (RDAP) Service](https://tools.ietf.org/html/rfc7484) rdap-0.9.1/autnum.go000066400000000000000000000012221474227142300143120ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap // Autnum represents information of Autonomous System registrations. // // Autnum is a topmost RDAP response object. type Autnum struct { DecodeData *DecodeData Common Conformance []string `rdap:"rdapConformance"` ObjectClassName string Notices []Notice Handle string StartAutnum *uint32 EndAutnum *uint32 IPVersion string `rdap:"ipVersion"` Name string Type string Status []string Country string Entities []Entity Remarks []Remark Links []Link Port43 string Events []Event } rdap-0.9.1/bootstrap/000077500000000000000000000000001474227142300144725ustar00rootroot00000000000000rdap-0.9.1/bootstrap/answer.go000066400000000000000000000010451474227142300163200ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package bootstrap import "net/url" // Answer represents the result of bootstrapping a single query. type Answer struct { // Query looked up in the registry. // // This includes any canonicalisation performed to match the Service // Registry's data format. e.g. lowercasing of domain names, and removal of // "AS" from AS numbers. Query string // Matching service entry. Empty string if no match. Entry string // List of RDAP base URLs. URLs []*url.URL } rdap-0.9.1/bootstrap/asn_registry.go000066400000000000000000000065401474227142300175370ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package bootstrap import ( "errors" "fmt" "net/url" "sort" "strconv" "strings" ) type ASNRegistry struct { // List of ASNs & their RDAP base URLs. // // Stored in a sorted order for fast search. asns []asnRange file *File } // asnRange represents a range of AS numbers and their RDAP base URLs. // // Represents a single AS number when MinASN==MaxASN. type asnRange struct { MinASN uint32 // First AS number. MaxASN uint32 // Last AS number. URLs []*url.URL // RDAP base URLs. } // String returns "ASxxxx" for a single AS, or "ASxxxx-ASyyyy" for a range. func (a asnRange) String() string { if a.MinASN == a.MaxASN { return fmt.Sprintf("AS%d", a.MinASN) } return fmt.Sprintf("AS%d-AS%d", a.MinASN, a.MaxASN) } type asnRangeSorter []asnRange func (a asnRangeSorter) Len() int { return len(a) } func (a asnRangeSorter) Swap(i int, j int) { a[i], a[j] = a[j], a[i] } func (a asnRangeSorter) Less(i int, j int) bool { return a[i].MinASN < a[j].MinASN } // NewASNRegistry creates an ASNRegistry from an ASN registry JSON document. // // The document format is specified in https://tools.ietf.org/html/rfc7484#section-5.3. func NewASNRegistry(json []byte) (*ASNRegistry, error) { var registry *File registry, err := NewFile(json) if err != nil { return nil, fmt.Errorf("Error parsing ASN registry: %s\n", err) } a := make([]asnRange, 0, len(registry.Entries)) var asn string var urls []*url.URL for asn, urls = range registry.Entries { minASN, maxASN, err := parseASNRange(asn) if err != nil { continue } a = append(a, asnRange{MinASN: minASN, MaxASN: maxASN, URLs: urls}) } sort.Sort(asnRangeSorter(a)) return &ASNRegistry{ asns: a, file: registry, }, nil } // Lookup returns the RDAP base URLs for the AS number question |question|. // // Example queries are: "AS1234", "as1234", and "1234". func (a *ASNRegistry) Lookup(question *Question) (*Answer, error) { var asn uint32 asn, err := parseASN(question.Query) if err != nil { return nil, err } index := sort.Search(len(a.asns), func(i int) bool { return asn <= a.asns[i].MaxASN }) var entry string var urls []*url.URL if index != len(a.asns) && (asn >= a.asns[index].MinASN && asn <= a.asns[index].MaxASN) { entry = a.asns[index].String() urls = a.asns[index].URLs } return &Answer{ Query: fmt.Sprintf("%d", asn), Entry: entry, URLs: urls, }, nil } // File returns a struct describing the registry's JSON document. func (a *ASNRegistry) File() *File { return a.file } func parseASN(asn string) (uint32, error) { asn = strings.ToLower(asn) asn = strings.TrimLeft(asn, "as") result, err := strconv.ParseUint(asn, 10, 32) if err != nil { return 0, err } return uint32(result), nil } func parseASNRange(asnRange string) (uint32, uint32, error) { var minASN uint64 var maxASN uint64 var err error asns := strings.Split(asnRange, "-") if len(asns) != 1 && len(asns) != 2 { return 0, 0, errors.New("Malformed ASN range") } minASN, err = strconv.ParseUint(asns[0], 10, 32) if err != nil { return 0, 0, err } if len(asns) == 2 { maxASN, err = strconv.ParseUint(asns[1], 10, 32) if err != nil { return 0, 0, err } } else { maxASN = minASN } if minASN > maxASN { minASN, maxASN = maxASN, minASN } return uint32(minASN), uint32(maxASN), nil } rdap-0.9.1/bootstrap/asn_registry_test.go000066400000000000000000000016121474227142300205710ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package bootstrap import ( "testing" "github.com/openrdap/rdap/test" ) func TestNetRegistryLookupsASN(t *testing.T) { test.Start(test.Bootstrap) defer test.Finish() var bytes []byte = test.Get("https://data.iana.org/rdap/asn.json") var n *ASNRegistry n, err := NewASNRegistry(bytes) if err != nil { t.Fatal(err) } tests := []registryTest{ { "as287", false, "AS287", []string{"https://rdap.arin.net/registry", "http://rdap.arin.net/registry"}, }, { "As1768", false, "AS1768-AS1769", []string{"https://rdap.apnic.net/"}, }, { "266652", false, "AS265629-AS266652", []string{"https://rdap.lacnic.net/rdap/"}, }, { "not-a-number", true, "", []string{}, }, { "999999", false, "", []string{}, }, } runRegistryTests(t, tests, n) } rdap-0.9.1/bootstrap/cache/000077500000000000000000000000001474227142300155355ustar00rootroot00000000000000rdap-0.9.1/bootstrap/cache/disk_cache.go000066400000000000000000000074471474227142300201550ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package cache import ( "errors" "fmt" "io/ioutil" "os" "path/filepath" "time" homedir "github.com/mitchellh/go-homedir" ) const ( defaultCacheDirName = ".openrdap" ) // A DiskCache caches Service Registry files on disk. // // By default they're saved as $HOME/.openrdap/{asn,dns,ipv4,ipv6}.json. File // mtimes are used to calculate cache expiry. // // The cache directory is created automatically as needed. type DiskCache struct { // Duration files are stored before they're considered expired. // // The default is 24 hours. Timeout time.Duration // Directory to store cached files in. // // The default is $HOME/.openrdap. Dir string lastLoadedModTime map[string]time.Time } // NewDiskCache creates a new DiskCache. func NewDiskCache() *DiskCache { d := &DiskCache{ lastLoadedModTime: make(map[string]time.Time), Timeout: time.Hour * 24, } dir, err := homedir.Dir() if err != nil { panic("Can't determine your home directory") } d.Dir = filepath.Join(dir, defaultCacheDirName) return d } // InitDir creates the cache directory if it does not already exist. // // Returns true if the directory was created, or false if it already exists/or // on error. func (d *DiskCache) InitDir() (bool, error) { fileInfo, err := os.Stat(d.Dir) if err == nil { if fileInfo.IsDir() { return false, nil } else { return false, errors.New("Cache dir is not a dir") } } if os.IsNotExist(err) { err := os.Mkdir(d.Dir, 0775) if err == nil { return true, nil } else { return false, err } } else { return false, err } } // SetTimeout sets the duration each Service Registry file can be stored before // its State() is Expired. func (d *DiskCache) SetTimeout(timeout time.Duration) { d.Timeout = timeout } // Save saves the file |filename| with |data| to disk. // // The cache directory is created if necessary. func (d *DiskCache) Save(filename string, data []byte) error { _, err := d.InitDir() if err != nil { return err } err = ioutil.WriteFile(d.cacheDirPath(filename), data, 0664) if err != nil { return err } fileModTime, err := d.modTime(filename) if err == nil { d.lastLoadedModTime[filename] = fileModTime } else { return fmt.Errorf("File %s failed to save correctly: %s", filename, err) } return nil } // Load loads the file |filename| from disk. // // Since Service Registry files do not change much, the file is returned even // if its State() is Expired. // // An error is returned if the file is not on disk. func (d *DiskCache) Load(filename string) ([]byte, error) { fileModTime, err := d.modTime(filename) if err != nil { return nil, fmt.Errorf("Unable to load %s: %s", filename, err) } var bytes []byte bytes, err = ioutil.ReadFile(d.cacheDirPath(filename)) if err != nil { return nil, err } d.lastLoadedModTime[filename] = fileModTime return bytes, nil } // State returns the cache state of the file |filename|. // // The returned state is one of: Absent, Good, ShouldReload, Expired. func (d *DiskCache) State(filename string) FileState { var expiry time.Time = time.Now().Add(-d.Timeout) var state FileState = Absent fileModTime, err := d.modTime(filename) if err == nil { if fileModTime.After(expiry) { state = ShouldReload lastLoadedModTime, haveLoaded := d.lastLoadedModTime[filename] if haveLoaded && !fileModTime.After(lastLoadedModTime) { state = Good } } else { state = Expired } } return state } func (d *DiskCache) modTime(filename string) (time.Time, error) { var fileInfo os.FileInfo fileInfo, err := os.Stat(d.cacheDirPath(filename)) if err != nil { return time.Time{}, err } return fileInfo.ModTime(), nil } func (d *DiskCache) cacheDirPath(filename string) string { return filepath.Join(d.Dir, filename) } rdap-0.9.1/bootstrap/cache/disk_cache_test.go000066400000000000000000000044711474227142300212060ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package cache import ( "bytes" "io/ioutil" "os" "path/filepath" "testing" "time" ) func TestDiskCache(t *testing.T) { dir, err := ioutil.TempDir("", "test") if err != nil { t.Fatal(err) } defer os.RemoveAll(dir) rdapDir := filepath.Join(dir, ".openrdap") m1 := NewDiskCache() m1.Dir = rdapDir m2 := NewDiskCache() m2.Dir = rdapDir asn1 := []byte(string("file 1")) asn2 := []byte(string("file 2")) if m1.State("asn.json") != Absent { t.Fatalf("asn.json expected absent in m1") } else if m2.State("asn.json") != Absent { t.Fatalf("asn.json expected absent in m2") } if err := m1.Save("asn.json", asn1); err != nil { t.Fatalf("Save failed: %s", err) } if m1.State("asn.json") != Good { t.Fatalf("asn.json expected good in m1") } else if m2.State("asn.json") != ShouldReload { t.Fatalf("asn.json expected shouldreload in m2") } loaded1, err := m1.Load("asn.json") loaded2, err := m2.Load("asn.json") if m1.State("asn.json") != Good { t.Fatalf("asn.json expected good in m1") } else if m2.State("asn.json") != Good { t.Fatalf("asn.json expected good in m2") } if bytes.Compare(loaded1, asn1) != 0 { t.Fatalf("loaded1(%v) != asn1(%v)", loaded1, asn1) } else if bytes.Compare(loaded2, asn1) != 0 { t.Fatalf("loaded2(%v) != asn1(%v)", loaded2, asn1) } time.Sleep(time.Second) if err := m2.Save("asn.json", asn2); err != nil { t.Fatalf("Save failed: %s", err) } if m1.State("asn.json") != ShouldReload { t.Fatalf("asn.json expected shouldreload in m1") } else if m2.State("asn.json") != Good { t.Fatalf("asn.json expected good in m2") } m1.Timeout = 0 m2.Timeout = 0 if m1.State("asn.json") != Expired { t.Fatal("m1 timeout broken") } else if m2.State("asn.json") != Expired { t.Fatal("m2 timeout broken") } m1.Timeout = time.Hour m2.Timeout = time.Hour loaded1, err = m1.Load("asn.json") loaded2, err = m2.Load("asn.json") if m1.State("asn.json") != Good { t.Fatalf("asn.json expected good in m1") } else if m2.State("asn.json") != Good { t.Fatalf("asn.json expected good in m2") } if bytes.Compare(loaded1, asn2) != 0 { t.Fatalf("loaded1(%v) != asn2(%v)", loaded1, asn2) } else if bytes.Compare(loaded2, asn2) != 0 { t.Fatalf("loaded2(%v) != asn2(%v)", loaded2, asn2) } } rdap-0.9.1/bootstrap/cache/memory_cache.go000066400000000000000000000033201474227142300205150ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package cache import ( "fmt" "time" ) // A MemoryCache caches Service Registry files in memory. type MemoryCache struct { Timeout time.Duration cache map[string][]byte mtime map[string]time.Time } // NewMemoryCache creates a new MemoryCache. func NewMemoryCache() *MemoryCache { return &MemoryCache{ cache: make(map[string][]byte), mtime: make(map[string]time.Time), Timeout: time.Hour * 24, } } // SetTimeout sets the duration each Service Registry file can be stored before // its State() is Expired. func (m *MemoryCache) SetTimeout(timeout time.Duration) { m.Timeout = timeout } // Save saves the file |filename| with |data| to the cache. func (m *MemoryCache) Save(filename string, data []byte) error { m.cache[filename] = make([]byte, len(data)) copy(m.cache[filename], data) m.mtime[filename] = time.Now() return nil } // Load returns the file |filename| from the cache. // // Since Service Registry files do not change much, the file is returned even // if its State() is Expired. // // An error is returned if the file is not in the cache. func (m *MemoryCache) Load(filename string) ([]byte, error) { data, ok := m.cache[filename] if !ok { return nil, fmt.Errorf("File %s not in cache", filename) } result := make([]byte, len(data)) copy(result, data) return result, nil } // State returns the cache state of the file |filename|. // // The returned state is one of: Absent, Good, Expired. func (m *MemoryCache) State(filename string) FileState { mtime, ok := m.mtime[filename] if !ok { return Absent } expiry := mtime.Add(m.Timeout) if expiry.Before(time.Now()) { return Expired } return Good } rdap-0.9.1/bootstrap/cache/memory_cache_test.go000066400000000000000000000021061474227142300215550ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package cache import ( "bytes" "testing" "time" ) func TestMemoryCache(t *testing.T) { m := NewMemoryCache() if m.State("not-in-cache.json") != Absent { t.Fatal("m.State() returned non-Absent for absent file") } var data []byte var err error data, err = m.Load("not-in-cache.json") if err == nil { t.Fatal("Load of not-in-cache.json unexpected result") } var testData []byte = []byte("test") err = m.Save("file.json", testData) if err != nil { t.Fatal("Save failed") } data, err = m.Load("file.json") if len(data) == 0 || err != nil || bytes.Compare(data, testData) != 0 { t.Fatal("Load of file.json unexpected result") } testData[0] = 'x' if data[0] != 't' { t.Fatalf("Cache doesn't contain a copy, contains %s", data) } if m.State("file.json") != Good { t.Fatal("m.State() returned non-Good for cached file") } m.Timeout = 0 time.Sleep(time.Millisecond) if m.State("file.json") != Expired { t.Fatal("m.State() returned non-Expired for expired file") } } rdap-0.9.1/bootstrap/cache/registry_cache.go000066400000000000000000000021441474227142300210600ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. // Package cache implements RDAP Service Registry file caching. // // There are two separate implementations: MemoryCache and DiskCache. package cache import "time" type FileState int const ( // File is not in the cache. Absent FileState = iota // File is in the cache. The latest version has already accessed (Load or Saved()). Good // File is in the cache. A newer version of is available to be Load()'ed. // // This is used by DiskCache, which uses a shared cache directory. ShouldReload // File is in the cache, but has expired. It still can be Load()'ed. Expired ) func (f FileState) String() string { switch f { case Absent: return "not cached" case Good, ShouldReload: return "good" case Expired: return "expired" default: panic("Unknown FileState") } } // A RegistryCache implements a cache of Service Registry files. type RegistryCache interface { Load(filename string) ([]byte, error) Save(filename string, data []byte) error State(filename string) FileState SetTimeout(timeout time.Duration) } rdap-0.9.1/bootstrap/client.go000066400000000000000000000270771474227142300163140ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. // Package bootstrap implements an RDAP bootstrap client. // // All RDAP queries are handled by an RDAP server. To help clients discover // RDAP servers, IANA publishes Service Registry files // (https://data.iana.org/rdap) for several query types: Domain names, IP // addresses, and Autonomous Systems. // // Given an RDAP query, this package finds the list of RDAP server URLs which // can answer it. This includes downloading & parsing the Service Registry // files. // // Basic usage: // question := &bootstrap.Question{ // RegistryType: bootstrap.DNS, // Query: "example.cz", // } // // b := &bootstrap.Client{} // // var answer *bootstrap.Answer // answer, err := b.Lookup(question) // // if err == nil { // for _, url := range answer.URLs { // fmt.Println(url) // } // } // // Download and list the contents of the DNS Service Registry: // b := &bootstrap.Client{} // // // Before you can use a Registry, you need to download it first. // err := b.Download(bootstrap.DNS) // Downloads https://data.iana.org/rdap/dns.json. // // if err == nil { // var dns *DNSRegistry = b.DNS() // // // Print TLDs with RDAP service. // for tld, _ := range dns.File().Entries { // fmt.Println(tld) // } // } // // You can configure bootstrap.Client{} with a custom http.Client, base URL // (default https://data.iana.org/rdap), and custom cache. bootstrap.Question{} // support Contexts (for timeout, etc.). // // A bootstrap.Client caches the Service Registry files in memory for both // performance, and courtesy to data.iana.org. The functions which make network // requests are: // - Download() - force download one of Service Registry file. // - DownloadWithContext() - force download one of Service Registry file. // - Lookup() - download one Service Registry file if missing, or if the cached file is over (by default) 24 hours old. // // Lookup() is intended for repeated usage: A long lived bootstrap.Client will // download each of {asn,dns,ipv4,ipv6}.json once per 24 hours only, regardless // of the number of calls made to Lookup(). You can still refresh them manually // using Download() if required. // // By default, Service Registry files are cached in memory. bootstrap.Client // also supports caching the Service Registry files on disk. The default cache // location is // $HOME/.openrdap/. // // Disk cache usage: // // b := bootstrap.NewClient() // b.Cache = cache.NewDiskCache() // // dsr := b.DNS() // Tries to load dns.json from disk cache, doesn't exist yet, so returns nil. // b.Download(bootstrap.DNS) // Downloads dns.json, saves to disk cache. // // b2 := bootstrap.NewClient() // b2.Cache = cache.NewDiskCache() // // dsr2 := b.DNS() // Loads dns.json from disk cache. // // This package also implements the experimental Service Provider registry. Due // to the experimental nature, no Service Registry file exists on data.iana.org // yet, additionally the filename isn't known. The current filename used is // serviceprovider-draft-03.json. // // RDAP bootstrapping is defined in https://tools.ietf.org/html/rfc7484. package bootstrap import ( "context" "crypto/sha256" "encoding/hex" "fmt" "io/ioutil" "net/http" "net/url" "time" "github.com/openrdap/rdap/bootstrap/cache" ) // A RegistryType represents a bootstrap registry type. type RegistryType int const ( DNS RegistryType = iota IPv4 IPv6 ASN ServiceProvider ) func (r RegistryType) String() string { switch r { case DNS: return "dns" case IPv4: return "ipv4" case IPv6: return "ipv6" case ASN: return "asn" case ServiceProvider: return "serviceprovider" default: panic("Unknown RegistryType") } } const ( // Default URL of the Service Registry files. DefaultBaseURL = "https://data.iana.org/rdap/" // Default cache timeout of Service Registries. DefaultCacheTimeout = time.Hour * 24 ) // Client implements an RDAP bootstrap client. type Client struct { HTTP *http.Client // HTTP client. BaseURL *url.URL // Base URL of the Service Registry files. Default is DefaultBaseURL. Cache cache.RegistryCache // Service Registry cache. Default is a MemoryCache. // Optional callback function for verbose messages. Verbose func(text string) registries map[RegistryType]Registry } // A Registry implements bootstrap lookups. type Registry interface { Lookup(question *Question) (*Answer, error) File() *File } func (c *Client) init() { if c.HTTP == nil { c.HTTP = &http.Client{} } if c.Cache == nil { c.Cache = cache.NewMemoryCache() c.Cache.SetTimeout(DefaultCacheTimeout) } if c.registries == nil { c.registries = make(map[RegistryType]Registry) } if c.BaseURL == nil { c.BaseURL, _ = url.Parse(DefaultBaseURL) } } // Download downloads a single bootstrap registry file. // // On success, the relevant Registry is refreshed. Use the matching accessor (ASN(), DNS(), IPv4(), or IPv6()) to access it. func (c *Client) Download(registry RegistryType) error { return c.DownloadWithContext(context.Background(), registry) } // DownloadWithContext downloads a single bootstrap registry file, with context |context|. // // On success, the relevant Registry is refreshed. Use the matching accessor (ASN(), DNS(), IPv4(), or IPv6()) to access it. func (c *Client) DownloadWithContext(ctx context.Context, registry RegistryType) error { c.init() var json []byte var s Registry json, s, err := c.download(ctx, registry) if err != nil { return err } err = c.Cache.Save(c.filenameFor(registry), json) if err != nil { return err } c.registries[registry] = s return nil } func (c *Client) download(ctx context.Context, registry RegistryType) ([]byte, Registry, error) { u, err := url.Parse(registry.Filename()) if err != nil { return nil, nil, err } baseURL := new(url.URL) *baseURL = *c.BaseURL if baseURL.Path != "" && baseURL.Path[len(baseURL.Path)-1] != '/' { baseURL.Path += "/" } var fetchURL *url.URL = baseURL.ResolveReference(u) req, err := http.NewRequest("GET", fetchURL.String(), nil) if err != nil { return nil, nil, err } req = req.WithContext(ctx) resp, err := c.HTTP.Do(req) if err != nil { return nil, nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, nil, fmt.Errorf("Server returned non-200 status code: %s", resp.Status) } json, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, nil, err } var s Registry s, err = newRegistry(registry, json) if err != nil { return json, nil, err } return json, s, nil } func (c *Client) freshenFromCache(registry RegistryType) { if c.Cache.State(c.filenameFor(registry)) == cache.ShouldReload { c.reloadFromCache(registry) } } func (c *Client) reloadFromCache(registry RegistryType) error { json, err := c.Cache.Load(c.filenameFor(registry)) if err != nil { return err } var s Registry s, err = newRegistry(registry, json) if err != nil { return err } c.registries[registry] = s return nil } func newRegistry(registry RegistryType, json []byte) (Registry, error) { var s Registry var err error switch registry { case ASN: s, err = NewASNRegistry(json) case DNS: s, err = NewDNSRegistry(json) case IPv4: s, err = NewNetRegistry(json, 4) case IPv6: s, err = NewNetRegistry(json, 6) case ServiceProvider: s, err = NewServiceProviderRegistry(json) default: panic("Unknown Registrytype") } return s, err } // Lookup returns the RDAP base URLs for the bootstrap question |question|. func (c *Client) Lookup(question *Question) (*Answer, error) { c.init() if c.Verbose == nil { c.Verbose = func(text string) {} } c.Verbose(" bootstrap: Looking up...") c.Verbose(fmt.Sprintf(" bootstrap: Question type : %s", question.RegistryType)) c.Verbose(fmt.Sprintf(" bootstrap: Question query: %s", question.Query)) registry := question.RegistryType var state cache.FileState = c.Cache.State(c.filenameFor(registry)) c.Verbose(fmt.Sprintf(" bootstrap: Cache state: %s: %s", c.filenameFor(registry), state)) var forceDownload bool if state == cache.ShouldReload { if err := c.reloadFromCache(registry); err != nil { forceDownload = true c.Verbose(fmt.Sprintf(" bootstrap: Cache load error (%s), downloading...", err)) } } if c.registries[registry] == nil || forceDownload { c.Verbose(fmt.Sprintf(" bootstrap: Downloading %s", registry.Filename())) err := c.DownloadWithContext(question.Context(), registry) if err != nil { return nil, err } } else { c.Verbose(" bootstrap: Using cached Service Registry file") } answer, err := c.registries[registry].Lookup(question) if answer != nil { c.Verbose(fmt.Sprintf(" bootstrap: Looked up '%s'", answer.Query)) if answer.Entry != "" { c.Verbose(fmt.Sprintf(" bootstrap: Matching entry '%s'", answer.Entry)) } else { c.Verbose(fmt.Sprintf(" bootstrap: No match")) } for i, url := range answer.URLs { c.Verbose(fmt.Sprintf(" bootstrap: Service URL #%d: '%s'", i+1, url)) } } return answer, err } // ASN returns the current ASN Registry (or nil if the registry file hasn't been Download()ed). // // This function never initiates a network transfer. func (c *Client) ASN() *ASNRegistry { c.init() c.freshenFromCache(ServiceProvider) s, _ := c.registries[ASN].(*ASNRegistry) return s } // // DNS returns the current DNS Registry (or nil if the registry file hasn't been Download()ed). // // This function never initiates a network transfer. func (c *Client) DNS() *DNSRegistry { c.init() c.freshenFromCache(ServiceProvider) s, _ := c.registries[DNS].(*DNSRegistry) return s } // IPv4 returns the current IPv4 Registry (or nil if the registry file hasn't been Download()ed). // // This function never initiates a network transfer. func (c *Client) IPv4() *NetRegistry { c.init() c.freshenFromCache(ServiceProvider) s, _ := c.registries[IPv4].(*NetRegistry) return s } // IPv6 returns the current IPv6 Registry (or nil if the registry file hasn't been Download()ed). // // This function never initiates a network transfer. func (c *Client) IPv6() *NetRegistry { c.init() c.freshenFromCache(ServiceProvider) s, _ := c.registries[IPv6].(*NetRegistry) return s } // ServiceProvider returns the current ServiceProvider Registry (or nil if the registry file hasn't been Download()ed). // // This function never initiates a network transfer. func (c *Client) ServiceProvider() *ServiceProviderRegistry { c.init() c.freshenFromCache(ServiceProvider) s, _ := c.registries[ServiceProvider].(*ServiceProviderRegistry) return s } // fileFor returns a filename to save the bootstrap registry file |r| as. // // For the official IANA bootstrap service, this is the exact filename, e.g. // dns.json. // // For custom bootstrap services, a 6 character hash of the bootstrap service // URL is prepended to the filename (e.g. 012def_dns.json), to prevent mixing // them up. func (c *Client) filenameFor(r RegistryType) string { filename := r.Filename() if c.BaseURL.String() != DefaultBaseURL { hasher := sha256.New() hasher.Write([]byte(c.BaseURL.String())) sha256Hash := hex.EncodeToString(hasher.Sum(nil)) filename = sha256Hash[0:6] + "_" + filename } return filename } // Filename returns the JSON document filename: One of {asn,dns,ipv4,ipv6,service_provider}.json. func (r RegistryType) Filename() string { switch r { case ASN: return "asn.json" case DNS: return "dns.json" case IPv4: return "ipv4.json" case IPv6: return "ipv6.json" case ServiceProvider: // This is a guess and will need fixing to match whatever IANA chooses. return "serviceprovider-draft-03.json" default: panic("Unknown RegistryType") } } rdap-0.9.1/bootstrap/client_test.go000066400000000000000000000044631474227142300173450ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package bootstrap import ( "net/url" "testing" "github.com/openrdap/rdap/test" ) func TestDownload(t *testing.T) { test.Start(test.Bootstrap) defer test.Finish() c := &Client{} err := c.Download(DNS) if err != nil { t.Fatalf("Download() error: %s", err) } if c.ASN() != nil || c.DNS() == nil || c.IPv4() != nil || c.IPv6() != nil { t.Fatalf("Download() bad") } } func TestLookups(t *testing.T) { tests := []struct { Registry RegistryType Input string Success bool URLs []string }{ { ASN, "as1768", true, []string{"https://rdap.apnic.net/"}, }, { DNS, "example.br", true, []string{"https://rdap.registro.br/"}, }, { IPv4, "41.0.0.0", true, []string{ "https://rdap.afrinic.net/rdap/", "http://rdap.afrinic.net/rdap/", }, }, { IPv6, "2001:1400::", true, []string{ "https://rdap.db.ripe.net/", }, }, { ServiceProvider, "12345~VRSN", true, []string{"https://rdap.verisignlabs.com/rdap/v1"}, }, { ServiceProvider, "12345-VRSN", true, []string{"https://rdap.verisignlabs.com/rdap/v1"}, }, } test.Start(test.Bootstrap) test.Start(test.BootstrapExperimental) defer test.Finish() c := &Client{} for _, test := range tests { var r *Answer if test.Registry == ServiceProvider { c.BaseURL, _ = url.Parse("https://test.rdap.net/rdap/") } question := &Question{ RegistryType: test.Registry, Query: test.Input, } r, err := c.Lookup(question) if test.Success != (err == nil) { t.Errorf("Lookup %s: expected success=%v, got opposite, err=%v", test.Input, test.Success, err) continue } if r == nil { t.Errorf("Lookup %s: unexpected nil result", test.Input) continue } for i, url := range test.URLs { if r.URLs[i].String() != url { t.Errorf("Lookup %s, URL #%d, expected %s, got %s\n", test.Input, i, url, r.URLs[i]) continue } } } } func TestLookupWithDownloadError(t *testing.T) { test.Start(test.BootstrapHTTPError) defer test.Finish() c := &Client{} question := &Question{ RegistryType: DNS, Query: "example.br", } _, err := c.Lookup(question) if err == nil { t.Errorf("Unexpected success") } t.Logf("Error was: %s", err) } rdap-0.9.1/bootstrap/dns_registry.go000066400000000000000000000027771474227142300175520ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package bootstrap import ( "fmt" "net/url" "strings" ) type DNSRegistry struct { // Map of domain labels (e.g. "br") to RDAP base URLs. dns map[string][]*url.URL file *File } // NewDNSRegistry creates a DNSRegistry from a DNS registry JSON document. // // The document format is specified in https://tools.ietf.org/html/rfc7484#section-4. func NewDNSRegistry(json []byte) (*DNSRegistry, error) { var r *File r, err := NewFile(json) if err != nil { return nil, fmt.Errorf("Error parsing DNS bootstrap: %s", err) } return &DNSRegistry{ dns: r.Entries, file: r, }, nil } // Lookup returns the RDAP base URLs for the domain name question |question|. func (d *DNSRegistry) Lookup(question *Question) (*Answer, error) { input := question.Query input = strings.TrimSuffix(input, ".") input = strings.ToLower(input) fqdn := input // Lookup the FQDN. // e.g. for an.example.com, the following lookups could occur: // - "an.example.com" // - "example.com" // - "com" // - "" (the root zone) var urls []*url.URL for { var ok bool urls, ok = d.dns[fqdn] if ok { break } else if fqdn == "" { break } index := strings.IndexByte(fqdn, '.') if index == -1 { fqdn = "" } else { fqdn = fqdn[index+1:] } } return &Answer{ URLs: urls, Query: input, Entry: fqdn, }, nil } // File returns a struct describing the registry's JSON document. func (d *DNSRegistry) File() *File { return d.file } rdap-0.9.1/bootstrap/dns_registry_test.go000066400000000000000000000030411474227142300205720ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package bootstrap import ( "testing" "github.com/openrdap/rdap/test" ) func TestNetRegistryLookupsDNSNested(t *testing.T) { test.Start(test.BootstrapComplex) defer test.Finish() var bytes []byte = test.Get("https://rdap.example.org/dns.json") var d *DNSRegistry d, err := NewDNSRegistry(bytes) if err != nil { t.Fatal(err) } tests := []registryTest{ { "", false, "", []string{"https://example.root", "http://example.root"}, }, { "example.com", false, "com", []string{"https://example.com", "http://example.com"}, }, { "sub.example.com", false, "sub.example.com", []string{"https://example.com/sub", "http://example.com/sub"}, }, { "sub.sub.example.com", false, "sub.example.com", []string{"https://example.com/sub", "http://example.com/sub"}, }, { "example.xyz", false, "", []string{"https://example.root", "http://example.root"}, }, } runRegistryTests(t, tests, d) } func TestNetRegistryLookupsDNS(t *testing.T) { test.Start(test.Bootstrap) defer test.Finish() var bytes []byte = test.Get("https://data.iana.org/rdap/dns.json") var d *DNSRegistry d, err := NewDNSRegistry(bytes) if err != nil { t.Fatal(err) } tests := []registryTest{ { "", false, "", []string{}, }, { "www.EXAMPLE.BR", false, "br", []string{"https://rdap.registro.br/"}, }, { "example.xyz", false, "", []string{}, }, } runRegistryTests(t, tests, d) } rdap-0.9.1/bootstrap/file.go000066400000000000000000000030351474227142300157410ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package bootstrap import ( "encoding/json" "errors" "net/url" ) // File represents a bootstrap registry file (i.e. {asn,dns,ipv4,ipv6}.json). type File struct { // Fields from the JSON document. Description string Publication string Version string // Map of service entries to RDAP base URLs. // // e.g. in ipv6.json, the following mapping: // "2c00::/12" => https://rdap.afrinic.net/rdap/, // http://rdap.afrinic.net/rdap/. Entries map[string][]*url.URL // The file's JSON document. JSON []byte } // NewFile constructs a File from a bootstrap registry file. func NewFile(jsonDocument []byte) (*File, error) { var doc struct { Description string Publication string Version string Services [][][]string } err := json.Unmarshal(jsonDocument, &doc) if err != nil { return nil, err } f := &File{} f.Description = doc.Description f.Publication = doc.Publication f.Version = doc.Version f.JSON = jsonDocument f.Entries = make(map[string][]*url.URL) for _, s := range doc.Services { if len(s) != 2 { return nil, errors.New("Malformed bootstrap (bad services array)") } entries := s[0] rawURLs := s[1] var urls []*url.URL for _, rawURL := range rawURLs { url, err := url.Parse(rawURL) // Ignore unparsable URLs. if err != nil { continue } urls = append(urls, url) } if len(urls) > 0 { for _, entry := range entries { f.Entries[entry] = urls } } } return f, nil } rdap-0.9.1/bootstrap/file_test.go000066400000000000000000000033461474227142300170050ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package bootstrap import ( "testing" "github.com/openrdap/rdap/test" ) func TestParseValid(t *testing.T) { test.Start(test.Bootstrap) defer test.Finish() var bytes []byte = test.Get("https://data.iana.org/rdap/dns.json") var r *File r, err := NewFile(bytes) if err != nil { t.Fatal(err) } if len(r.Entries) != 3 { t.Fatalf("Expected 3 entries, got %d: %v\n", len(r.Entries), r) } } func TestParseEmpty(t *testing.T) { test.Start(test.BootstrapMalformed) defer test.Finish() var bytes []byte = test.Get("https://www.example.org/dns_empty.json") _, err := NewFile(bytes) if err == nil { t.Fatal("Unexpected success parsing empty file") } } func TestParseSyntaxError(t *testing.T) { test.Start(test.BootstrapMalformed) defer test.Finish() var bytes []byte = test.Get("https://www.example.org/dns_syntax_error.json") _, err := NewFile(bytes) if err == nil { t.Fatal("Unexpected success parsing file with syntax error") } } func TestParseBadServices(t *testing.T) { test.Start(test.BootstrapMalformed) defer test.Finish() var bytes []byte = test.Get("https://www.example.org/dns_bad_services.json") _, err := NewFile(bytes) if err == nil { t.Fatal("Unexpected success parsing file with bad services array") } } func TestParseBadURL(t *testing.T) { test.Start(test.BootstrapMalformed) defer test.Finish() var bytes []byte = test.Get("https://www.example.org/dns_bad_url.json") var r *File r, err := NewFile(bytes) if err != nil { t.Fatal(err) } if err != nil { t.Fatal("Unexpected error parsing file with bad URL") } if len(r.Entries) != 3 { t.Fatalf("Expected 3 entries, got %d: %v\n", len(r.Entries), r) } } rdap-0.9.1/bootstrap/net_registry.go000066400000000000000000000066671474227142300175560ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package bootstrap import ( "bytes" "errors" "fmt" "net" "net/url" "sort" "strings" ) type NetRegistry struct { // Map of netmask size (0-32 for IPv4, 0-128 for IPv6) to list of NetEntries. networks map[int][]netEntry numIPBytes int // Length in bytes of each IP address (4 for IPv4, 16 for IPv6). file *File } // A netEntry is a network and its RDAP base URLs. type netEntry struct { Net *net.IPNet URLs []*url.URL } type netEntrySorter []netEntry func (a netEntrySorter) Len() int { return len(a) } func (a netEntrySorter) Swap(i int, j int) { a[i], a[j] = a[j], a[i] } func (a netEntrySorter) Less(i int, j int) bool { return bytes.Compare(a[i].Net.IP, a[j].Net.IP) <= 0 } // NewNetRegistry creates a NetRegistry from an IPv4 or IPv6 registry JSON document. ipVersion must be 4 or 6. // // The document formats are specified in https://tools.ietf.org/html/rfc7484#section-5.1 and https://tools.ietf.org/html/rfc7484#section-5.2. func NewNetRegistry(json []byte, ipVersion int) (*NetRegistry, error) { if ipVersion != 4 && ipVersion != 6 { return nil, fmt.Errorf("Unknown IP version %d", ipVersion) } var registry *File registry, err := NewFile(json) if err != nil { return nil, fmt.Errorf("Error parsing net registry file: %s", err) } n := &NetRegistry{ networks: map[int][]netEntry{}, numIPBytes: numIPBytesForVersion(ipVersion), file: registry, } var cidr string var urls []*url.URL for cidr, urls = range registry.Entries { _, ipNet, err := net.ParseCIDR(cidr) if err != nil { continue } else if len(ipNet.IP) != n.numIPBytes { continue } size, _ := ipNet.Mask.Size() n.networks[size] = append(n.networks[size], netEntry{Net: ipNet, URLs: urls}) } for _, nets := range n.networks { sort.Sort(netEntrySorter(nets)) } return n, nil } // Lookup returns the RDAP base URLs for the IP address or CIDR range question |Question|. // // Example queries are: "192.0.2.0", "192.0.2.0/25". "2001:db8::", "2001::db8::/62". func (n *NetRegistry) Lookup(question *Question) (*Answer, error) { input := question.Query if !strings.ContainsAny(input, "/") { // Convert IP address to CIDR format, with a /32 or /128 mask. input = fmt.Sprintf("%s/%d", input, n.numIPBytes*8) } _, lookupNet, err := net.ParseCIDR(input) if err != nil { return nil, err } if len(lookupNet.IP) != n.numIPBytes { return nil, errors.New("Lookup address has wrong IP protocol") } lookupMask, _ := lookupNet.Mask.Size() var bestEntry string var bestURLs []*url.URL var bestMask int var mask int var nets []netEntry for mask, nets = range n.networks { if mask < bestMask || mask > lookupMask { continue } index := sort.Search(len(nets), func(i int) bool { net := nets[i].Net return net.Contains(lookupNet.IP) || bytes.Compare(net.IP, lookupNet.IP) >= 0 }) if index == len(nets) || !nets[index].Net.Contains(lookupNet.IP) { continue } bestEntry = nets[index].Net.String() bestMask = mask bestURLs = nets[index].URLs } return &Answer{ Query: input, Entry: bestEntry, URLs: bestURLs, }, nil } func numIPBytesForVersion(ipVersion int) int { len := 0 switch ipVersion { case 4: len = net.IPv4len case 6: len = net.IPv6len default: panic("Unknown IP version") } return len } // File returns a struct describing the registry's JSON document. func (n *NetRegistry) File() *File { return n.file } rdap-0.9.1/bootstrap/net_registry_test.go000066400000000000000000000031701474227142300205770ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package bootstrap import ( "testing" "github.com/openrdap/rdap/test" ) func TestNetRegistryLookupsIPv4(t *testing.T) { test.Start(test.Bootstrap) defer test.Finish() var bytes []byte = test.Get("https://data.iana.org/rdap/ipv4.json") var n *NetRegistry n, err := NewNetRegistry(bytes, 4) if err != nil { t.Fatal(err) } tests := []registryTest{ { "255.0.0.0", false, "", []string{}, }, { "41.0.0.0", false, "41.0.0.0/8", []string{ "https://rdap.afrinic.net/rdap/", "http://rdap.afrinic.net/rdap/", }, }, { "41.255.255.255", false, "41.0.0.0/8", []string{ "https://rdap.afrinic.net/rdap/", "http://rdap.afrinic.net/rdap/", }, }, { "41.", true, "", []string{}, }, } runRegistryTests(t, tests, n) } func TestNetRegistryLookupsIPv6(t *testing.T) { test.Start(test.Bootstrap) defer test.Finish() var bytes []byte = test.Get("https://data.iana.org/rdap/ipv6.json") var n *NetRegistry n, err := NewNetRegistry(bytes, 6) if err != nil { t.Fatal(err) } tests := []registryTest{ { "4000::", false, "", []string{}, }, { "2001:1400::", false, "2001:1400::/23", []string{ "https://rdap.db.ripe.net/", }, }, { "2001:1400::5/128", false, "2001:1400::/23", []string{ "https://rdap.db.ripe.net/", }, }, { "2001:1400::/23", false, "2001:1400::/23", []string{ "https://rdap.db.ripe.net/", }, }, { "2001/129", true, "", []string{}, }, } runRegistryTests(t, tests, n) } rdap-0.9.1/bootstrap/question.go000066400000000000000000000014751474227142300166770ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package bootstrap import "context" // Question represents a bootstrap query. // // question := &bootstrap.Question{ // RegistryType: bootstrap.DNS, // Query: "example.cz", // } type Question struct { // Bootstrap registry to query. RegistryType // Query text. Query string ctx context.Context } // WithContext returns a copy of the Question, with context |ctx|. func (q *Question) WithContext(ctx context.Context) *Question { q2 := new(Question) *q2 = *q q2.ctx = ctx return q2 } // Context returns the Question's context. // // The returned context is always non-nil; it defaults to the background context. func (q *Question) Context() context.Context { if q.ctx == nil { return context.Background() } return q.ctx } rdap-0.9.1/bootstrap/service_provider_registry.go000066400000000000000000000035561474227142300223340ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package bootstrap import ( "fmt" "net/url" "strings" ) type ServiceProviderRegistry struct { // Map of service tag (e.g. "VRSN") to RDAP base URLs. services map[string][]*url.URL // The registry's JSON document. file *File } // NewServiceProviderRegistry creates a ServiceProviderRegistry from a Service // Provider JSON document. // // The document format is specified in // https://datatracker.ietf.org/doc/draft-hollenbeck-regext-rdap-object-tag/. func NewServiceProviderRegistry(json []byte) (*ServiceProviderRegistry, error) { var r *File r, err := NewFile(json) if err != nil { return nil, fmt.Errorf("Error parsing Service Provider bootstrap: %s", err) } return &ServiceProviderRegistry{ services: r.Entries, file: r, }, nil } // Lookup returns a list of RDAP base URLs for the entity question |question|. // // e.g. for the handle "53774930-VRSN", the RDAP base URLs for "VRSN" are returned. // // Missing/malformed/unknown service tags are not treated as errors. An empty // list of URLs is returned in these cases. // // Deprecated: Previously service tags used a TILDE char (e.g. ~VRSN) instead, // these are still supported. func (s *ServiceProviderRegistry) Lookup(question *Question) (*Answer, error) { input := question.Query // Valid input looks like 12345-VRSN. offset := strings.LastIndexByte(input, '~') if offset == -1 { offset = strings.LastIndexByte(input, '-') } if offset == -1 || offset == len(input)-1 { return &Answer{ Query: input, }, nil } service := input[offset+1:] urls, ok := s.services[service] if !ok { service = "" } return &Answer{ URLs: urls, Query: input, Entry: service, }, nil } // File returns a struct describing the registry's JSON document. func (s *ServiceProviderRegistry) File() *File { return s.file } rdap-0.9.1/bootstrap/service_provider_registry_test.go000066400000000000000000000025451474227142300233700ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package bootstrap import ( "testing" "github.com/openrdap/rdap/test" ) func TestServiceProviderRegistryLookups(t *testing.T) { test.Start(test.BootstrapExperimental) defer test.Finish() var bytes []byte = test.Get("https://test.rdap.net/rdap/serviceprovider-draft-03.json") var s *ServiceProviderRegistry s, err := NewServiceProviderRegistry(bytes) if err != nil { t.Fatal(err) } tests := []registryTest{ { "", false, "", []string{}, }, { "~", false, "", []string{}, }, { "X~VRSN~", false, "", []string{}, }, { "12345~VRSN", false, "VRSN", []string{"https://rdap.verisignlabs.com/rdap/v1"}, }, { "*~VRSN", false, "VRSN", []string{"https://rdap.verisignlabs.com/rdap/v1"}, }, { "~VRSN", false, "VRSN", []string{"https://rdap.verisignlabs.com/rdap/v1"}, }, { "12345-VRSN", false, "VRSN", []string{"https://rdap.verisignlabs.com/rdap/v1"}, }, { "*-VRSN", false, "VRSN", []string{"https://rdap.verisignlabs.com/rdap/v1"}, }, { "-VRSN", false, "VRSN", []string{"https://rdap.verisignlabs.com/rdap/v1"}, }, { "A-B-VRSN", false, "VRSN", []string{"https://rdap.verisignlabs.com/rdap/v1"}, }, } runRegistryTests(t, tests, s) } rdap-0.9.1/bootstrap/util_test.go000066400000000000000000000023511474227142300170360ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package bootstrap import "testing" type registryTest struct { Query string Error bool Entry string URLs []string } func runRegistryTests(t *testing.T, tests []registryTest, reg Registry) { for _, test := range tests { question := &Question{ Query: test.Query, } var r *Answer r, err := reg.Lookup(question) if test.Error && err == nil { t.Errorf("Query: %s, expected error, didn't get one\n", test.Query) continue } else if !test.Error && err != nil { t.Errorf("Query: %s, unexpected error: %s\n", test.Query, err) continue } if test.Error { continue } if r == nil { t.Errorf("Query: %s, unexpected nil Answer, err=%v\n", test.Query, err) continue } if r.Entry != test.Entry { t.Errorf("Query: %s, expected Entry %s, got %s\n", test.Query, test.Entry, r.Entry) continue } if len(r.URLs) != len(test.URLs) { t.Errorf("Query: %s, expected %d urls, got %d\n", test.Query, len(test.URLs), len(r.URLs)) continue } for i, url := range test.URLs { if r.URLs[i].String() != url { t.Errorf("Query %s, URL #%d, expected %s, got %s\n", test.Query, i, url, r.URLs[i]) continue } } } } rdap-0.9.1/cli.go000066400000000000000000000400541474227142300135560ustar00rootroot00000000000000package rdap import ( "bytes" "context" "crypto/tls" "encoding/json" "encoding/pem" "fmt" "io" "io/ioutil" "net" "net/http" "net/url" "os" "strconv" "strings" "time" "github.com/openrdap/rdap/bootstrap" "github.com/openrdap/rdap/bootstrap/cache" "github.com/openrdap/rdap/sandbox" "golang.org/x/crypto/pkcs12" kingpin "github.com/alecthomas/kingpin/v2" ) var ( version = "OpenRDAP v0.9.1" usageText = version + ` (www.openrdap.org) Usage: rdap [OPTIONS] DOMAIN|IP|ASN|ENTITY|NAMESERVER|RDAP-URL e.g. rdap example.cz rdap 192.0.2.0 rdap 2001:db8:: rdap AS2856 rdap https://rdap.nic.cz/domain/example.cz rdap -f registrant -f administrative -f billing amazon.com.br rdap --json https://rdap.nic.cz/domain/example.cz rdap -s https://rdap.nic.cz -t help Options: -h, --help Show help message. -v, --verbose Print verbose messages on STDERR. -T, --timeout=SECS Timeout after SECS seconds (default: 30). -k, --insecure Disable SSL certificate verification. -e, --experimental Enable some experimental options: - Use the bootstrap service https://test.rdap.net/rdap - Enable object tag support Authentication options: -P, --p12=cert.p12[:password] Use client certificate & private key (PKCS#12 format) or: -C, --cert=cert.pem Use client certificate (PEM format) -K, --key=cert.key Use client private key (PEM format) Output Options: --text Output RDAP, plain text "tree" format (default). -w, --whois Output WHOIS style (domain queries only). -j, --json Output JSON, pretty-printed format. -r, --raw Output the raw server response. Advanced options (query): -s --server=URL RDAP server to query. -t --type=TYPE RDAP query type. Normally auto-detected. The types are: - ip - domain - autnum - nameserver - entity - help - url - domain-search - domain-search-by-nameserver - domain-search-by-nameserver-ip - nameserver-search - nameserver-search-by-ip - entity-search - entity-search-by-handle The servers for domain, ip, autnum, url queries can be determined automatically. Otherwise, the RDAP server (--server=URL) must be specified. Advanced options (bootstrapping): --cache-dir=DIR Bootstrap cache directory to use. Specify empty string to disable bootstrap caching. The directory is created automatically as needed. (default: $HOME/.openrdap). --bs-url=URL Bootstrap service URL (default: https://data.iana.org/rdap) --bs-ttl=SECS Bootstrap cache time in seconds (default: 3600) Advanced options (experiments): --exp=test_rdap_net Use the bootstrap service https://test.rdap.net/rdap --exp=object_tag Enable object tag support (draft-hollenbeck-regext-rdap-object-tag) ` ) const ( experimentalBootstrapURL = "https://test.rdap.net/rdap" ) // CLIOptions specifies options for the command line client. type CLIOptions struct { // Sandbox mode disables the --cache-dir option, to prevent arbitrary writes to // the file system. // // This is used for https://www.openrdap.org/demo. Sandbox bool } // RunCLI runs the OpenRDAP command line client. // // |args| are the command line arguments to use (normally os.Args[1:]). // |stdout| and |stderr| are the io.Writers for STDOUT/STDERR. // |options| specifies extra options. // // Returns the program exit code. func RunCLI(args []string, stdout io.Writer, stderr io.Writer, options CLIOptions) int { // For duration timer (in --verbose output). start := time.Now() // Setup command line arguments parser. app := kingpin.New("rdap", "RDAP command-line client") app.HelpFlag.Short('h') app.UsageTemplate(usageText) app.UsageWriter(stdout) app.ErrorWriter(stderr) // Instead of letting kingpin call os.Exit(), flag if it requests to exit // here. // // This lets the function be called in libraries/tests without exiting them. terminate := false app.Terminate(func(int) { terminate = true }) // Command line options. verboseFlag := app.Flag("verbose", "").Short('v').Bool() timeoutFlag := app.Flag("timeout", "").Short('T').Default("30").Uint16() insecureFlag := app.Flag("insecure", "").Short('k').Bool() queryType := app.Flag("type", "").Short('t').String() fetchRolesFlag := app.Flag("fetch", "").Short('f').Strings() serverFlag := app.Flag("server", "").Short('s').String() experimentalFlag := app.Flag("experimental", "").Short('e').Bool() experimentsFlag := app.Flag("exp", "").Strings() cacheDirFlag := app.Flag("cache-dir", "").Default("default").String() bootstrapURLFlag := app.Flag("bs-url", "").Default("default").String() bootstrapTimeoutFlag := app.Flag("bs-ttl", "").Default("3600").Uint32() clientP12FilenameAndPassword := app.Flag("p12", "").Short('P').String() clientCertFilename := app.Flag("cert", "").Short('C').String() clientKeyFilename := app.Flag("key", "").Short('K').String() outputFormatText := app.Flag("text", "").Bool() outputFormatWhois := app.Flag("whois", "").Short('w').Bool() outputFormatJSON := app.Flag("json", "").Short('j').Bool() outputFormatRaw := app.Flag("raw", "").Short('r').Bool() // Command line query (any remaining non-option arguments). queryArgs := app.Arg("", "").Strings() // Parse command line arguments. // The help messages for -h/--help are printed directly by app.Parse(). _, err := app.Parse(args) if err != nil { printError(stderr, fmt.Sprintf("Error: %s\n\n%s", err, usageText)) return 1 } else if terminate { // Occurs when kingpin prints the --help message. return 1 } var verbose func(text string) if *verboseFlag { verbose = func(text string) { fmt.Fprintf(stderr, "# %s\n", text) } } else { verbose = func(text string) { } } verbose(version) verbose("") verbose("rdap: Configuring query...") // Supported experimental options. experiments := map[string]bool{ "test_rdap_net": false, "object_tag": false, "sandbox": false, } // Enable experimental options. for _, e := range *experimentsFlag { if _, ok := experiments[e]; ok { experiments[e] = true verbose(fmt.Sprintf("rdap: Enabled experiment '%s'", e)) } else { printError(stderr, fmt.Sprintf("Error: unknown experiment '%s'", e)) return 1 } } // Enable the -e selection of experiments? if *experimentalFlag { verbose("rdap: Enabled -e/--experiments: test_rdap_net, object_tag") experiments["test_rdap_net"] = true experiments["object_tag"] = true } // Forced sandbox mode? if experiments["sandbox"] { options.Sandbox = true } // Exactly one argument is required (i.e. the domain/ip/url/etc), unless // we're making a help query. if *queryType != "help" && len(*queryArgs) == 0 { printError(stderr, fmt.Sprintf("Error: %s\n\n%s", "Query object required, e.g. rdap example.cz", usageText)) return 1 } // Grab the query text. queryText := "" if len(*queryArgs) > 0 { queryText = (*queryArgs)[0] } // Construct the request. var req *Request switch *queryType { case "": req = NewAutoRequest(queryText) case "help": req = NewHelpRequest() case "domain", "dns": req = NewDomainRequest(queryText) case "autnum", "as", "asn": autnum := strings.ToUpper(queryText) autnum = strings.TrimPrefix(autnum, "AS") result, err := strconv.ParseUint(autnum, 10, 32) if err != nil { printError(stderr, fmt.Sprintf("Invalid ASN '%s'", queryText)) return 1 } req = NewAutnumRequest(uint32(result)) case "ip": ip := net.ParseIP(queryText) if ip == nil { printError(stderr, fmt.Sprintf("Invalid IP '%s'", queryText)) return 1 } req = NewIPRequest(ip) case "nameserver", "ns": req = NewNameserverRequest(queryText) case "entity": req = NewEntityRequest(queryText) case "url": fullURL, err := url.Parse(queryText) if err != nil { printError(stderr, fmt.Sprintf("Unable to parse URL '%s': %s", queryText, err)) return 1 } req = NewRawRequest(fullURL) case "entity-search": req = NewRequest(EntitySearchRequest, queryText) case "entity-search-by-handle": req = NewRequest(EntitySearchByHandleRequest, queryText) case "domain-search": req = NewRequest(DomainSearchRequest, queryText) case "domain-search-by-nameserver": req = NewRequest(DomainSearchByNameserverRequest, queryText) case "domain-search-by-nameserver-ip": req = NewRequest(DomainSearchByNameserverIPRequest, queryText) case "nameserver-search": req = NewRequest(NameserverSearchRequest, queryText) case "nameserver-search-by-ip": req = NewRequest(NameserverSearchByNameserverIPRequest, queryText) default: printError(stderr, fmt.Sprintf("Unknown query type '%s'", *queryType)) return 1 } // Determine the server. if req.Server != nil { if *serverFlag != "" { printError(stderr, fmt.Sprintf("--server option cannot be used with query type %s", req.Type)) return 1 } } // Server URL specified (--server)? if *serverFlag != "" { serverURL, err := url.Parse(*serverFlag) if err != nil { printError(stderr, fmt.Sprintf("--server error: %s", err)) return 1 } if serverURL.Scheme == "" { serverURL.Scheme = "http" } req = req.WithServer(serverURL) verbose(fmt.Sprintf("rdap: Using server '%s'", serverURL)) } // Custom TLS config. tlsConfig := &tls.Config{InsecureSkipVerify: *insecureFlag} bs := &bootstrap.Client{} // Custom bootstrap cache type/directory? if *cacheDirFlag == "" { // Disk cache disabled, use memory cache. bs.Cache = cache.NewMemoryCache() verbose("rdap: Using in-memory cache") } else { dc := cache.NewDiskCache() if *cacheDirFlag != "default" { if !options.Sandbox { dc.Dir = *cacheDirFlag } else { verbose(fmt.Sprintf("rdap: Ignored --cache-dir option (sandbox mode enabled)")) } } verbose(fmt.Sprintf("rdap: Using disk cache (%s)", dc.Dir)) created, err := dc.InitDir() if created { verbose(fmt.Sprintf("rdap: Cache dir %s mkdir'ed", dc.Dir)) } else if err != nil { printError(stderr, fmt.Sprintf("rdap: Error making cache dir %s", dc.Dir)) return 1 } bs.Cache = dc } // Use experimental bootstrap service URL? if experiments["test_rdap_net"] && *bootstrapURLFlag == "default" { *bootstrapURLFlag = experimentalBootstrapURL verbose("rdap: Using test.rdap.net bootstrap service (test_rdap_net experiment)") } // Custom bootstrap service URL? if *bootstrapURLFlag != "default" { baseURL, err := url.Parse(*bootstrapURLFlag) if err != nil { printError(stderr, fmt.Sprintf("Bootstrap URL error: %s", err)) return 1 } bs.BaseURL = baseURL verbose(fmt.Sprintf("rdap: Bootstrap URL set to '%s'", baseURL)) } else { verbose(fmt.Sprintf("rdap: Bootstrap URL is default '%s'", bootstrap.DefaultBaseURL)) } // Custom bootstrap cache timeout? if bootstrapTimeoutFlag != nil { bs.Cache.SetTimeout(time.Duration(*bootstrapTimeoutFlag) * time.Second) verbose(fmt.Sprintf("rdap: Bootstrap cache TTL set to %d seconds", *bootstrapTimeoutFlag)) } var clientCert tls.Certificate if *clientCertFilename != "" || *clientKeyFilename != "" { if *clientP12FilenameAndPassword != "" { printError(stderr, fmt.Sprintf("rdap: Error: Can't use both --cert/--key and --p12 together")) return 1 } else if *clientCertFilename == "" || *clientKeyFilename == "" { printError(stderr, fmt.Sprintf("rdap: Error: --cert and --key must be used together")) return 1 } else if options.Sandbox { verbose(fmt.Sprintf("rdap: Ignored --cert and --key options (sandbox mode enabled)")) } else { var err error clientCert, err = tls.LoadX509KeyPair(*clientCertFilename, *clientKeyFilename) if err != nil { printError(stderr, fmt.Sprintf("rdap: Error: cannot load client certificate/key: %s", err)) return 1 } verbose(fmt.Sprintf("rdap: Loaded client certificate from '%s'", *clientCertFilename)) tlsConfig.Certificates = append(tlsConfig.Certificates, clientCert) } } else if *clientP12FilenameAndPassword != "" { // Split the filename and optional password. // [0] is the filename, [1] is the optional password. var p12FilenameAndPassword []string = strings.SplitAfterN(*clientP12FilenameAndPassword, ":", 2) p12FilenameAndPassword[0] = strings.TrimSuffix(p12FilenameAndPassword[0], ":") // Use a blank password if none was specified. if len(p12FilenameAndPassword) == 1 { p12FilenameAndPassword = append(p12FilenameAndPassword, "") } var p12 []byte var err error // Load the file from disk, or the sandbox. if options.Sandbox { p12, err = sandbox.LoadFile(p12FilenameAndPassword[0]) } else { p12, err = ioutil.ReadFile(p12FilenameAndPassword[0]) } // Check the file was read correctly. if err != nil { printError(stderr, fmt.Sprintf("rdap: Error: cannot load client certificate: %s", err)) return 1 } // Convert P12 to PEM blocks. var blocks []*pem.Block blocks, err = pkcs12.ToPEM(p12, p12FilenameAndPassword[1]) if err != nil { printError(stderr, fmt.Sprintf("rdap: Error: cannot read client certificate: %s", err)) return 1 } // Build single concatenated PEM block. var pemData []byte for _, b := range blocks { pemData = append(pemData, pem.EncodeToMemory(b)...) } clientCert, err = tls.X509KeyPair(pemData, pemData) if err != nil { printError(stderr, fmt.Sprintf("rdap: Error: cannot read client certificate: %s", err)) return 1 } verbose(fmt.Sprintf("rdap: Loaded client certificate from '%s'", p12FilenameAndPassword[0])) tlsConfig.Certificates = append(tlsConfig.Certificates, clientCert) } // Custom HTTP client. Used to disable TLS certificate verification. transport := &http.Transport{ TLSClientConfig: tlsConfig, } // Setup http.RoundTripper for http clients bs.HTTP = &http.Client{ Transport: transport, } httpClient := &http.Client{ Transport: transport, } client := &Client{ HTTP: httpClient, Bootstrap: bs, Verbose: verbose, UserAgent: version, ServiceProviderExperiment: experiments["object_tag"], } if *insecureFlag { verbose(fmt.Sprintf("rdap: SSL certificate validation disabled")) } // Set the request timeout. ctx, cancelFunc := context.WithTimeout(context.Background(), time.Duration(*timeoutFlag)*time.Second) defer cancelFunc() req = req.WithContext(ctx) verbose(fmt.Sprintf("rdap: Timeout is %d seconds", *timeoutFlag)) // Run the request. var resp *Response resp, err = client.Do(req) verbose("") verbose(fmt.Sprintf("rdap: Finished in %s", time.Since(start))) if err != nil { printError(stderr, fmt.Sprintf("Error: %s", err)) return 1 } // Insert a blank line to seperate verbose messages/proper output. if *verboseFlag { fmt.Fprintln(stderr, "") } // Output formatting. if !(*outputFormatText || *outputFormatWhois || *outputFormatJSON || *outputFormatRaw) { *outputFormatText = true } // Print the response out in text format? if *outputFormatText { printer := &Printer{ Writer: stdout, BriefLinks: true, } printer.Print(resp.Object) } // Print the raw response out? if *outputFormatRaw { fmt.Printf("%s", resp.HTTP[0].Body) } // Print the response, JSON pretty-printed? if *outputFormatJSON { var out bytes.Buffer json.Indent(&out, resp.HTTP[0].Body, "", " ") out.WriteTo(os.Stdout) } // Print WHOIS style response out? if *outputFormatWhois { w := resp.ToWhoisStyleResponse() for _, key := range w.KeyDisplayOrder { for _, value := range w.Data[key] { fmt.Fprintf(stdout, "%s: %s\n", key, safePrint(value)) } } } _ = fetchRolesFlag return 0 } func safePrint(v string) string { removeBadChars := func(r rune) rune { switch { case r == '\000': return -1 case r == '\n': return ' ' default: return r } } return strings.Map(removeBadChars, v) } func printError(stderr io.Writer, text string) { fmt.Fprintf(stderr, "# %s\n", text) } rdap-0.9.1/client.go000066400000000000000000000224341474227142300142670ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap import ( "context" "fmt" "io/ioutil" "net/http" "strings" "time" "github.com/openrdap/rdap/bootstrap" ) // Client implements an RDAP client. // // This client executes RDAP requests, and returns the responses as Go values. // // Quick usage: // client := &rdap.Client{} // domain, err := client.QueryDomain("example.cz") // // if err == nil { // fmt.Printf("Handle=%s Domain=%s\n", domain.Handle, domain.LDHName) // } // The QueryDomain(), QueryAutnum(), and QueryIP() methods all provide full contact information, and timeout after 30s. // // Normal usage: // // Query example.cz. // req := &rdap.Request{ // Type: rdap.DomainRequest, // Query: "example.cz", // } // // client := &rdap.Client{} // resp, err := client.Do(req) // // if domain, ok := resp.Object.(*rdap.Domain); ok { // fmt.Printf("Handle=%s Domain=%s\n", domain.Handle, domain.LDHName) // } // // Advanced usage: // // This demonstrates custom FetchRoles, a custom Context, a custom HTTP client, // a custom Bootstrapper, and a custom timeout. // // Nameserver query on rdap.nic.cz. // server, _ := url.Parse("https://rdap.nic.cz") // req := &rdap.Request{ // Type: rdap.NameserverRequest, // Query: "a.ns.nic.cz", // FetchRoles: []string{"all"}, // Timeout: time.Second * 45, // Custom timeout. // // Server: server, // } // // req = req.WithContext(ctx) // Custom context (see https://blog.golang.org/context). // // client := &rdap.Client{} // client.HTTP = &http.Client{} // Custom HTTP client. // client.Bootstrap = &bootstrap.Client{} // Custom bootstapper. // // resp, err := client.Do(req) // // if ns, ok := resp.Object.(*rdap.Nameserver); ok { // fmt.Printf("Handle=%s Domain=%s\n", ns.Handle, ns.LDHName) // } type Client struct { HTTP *http.Client Bootstrap *bootstrap.Client // Optional callback function for verbose messages. Verbose func(text string) ServiceProviderExperiment bool UserAgent string } func (c *Client) Do(req *Request) (*Response, error) { // Response struct. resp := &Response{} // Bad query? if req == nil { return nil, &ClientError{ Type: InputError, Text: "nil Request", } } // Init HTTP client? if c.HTTP == nil { c.HTTP = &http.Client{} } // Init Bootstrap client? if c.Bootstrap == nil { c.Bootstrap = &bootstrap.Client{} } // Init Verbose callback? if c.Verbose == nil { c.Verbose = func(text string) {} } c.Verbose("") c.Verbose(fmt.Sprintf("client: Running...")) c.Verbose(fmt.Sprintf("client: Request type : %s", req.Type)) c.Verbose(fmt.Sprintf("client: Request query : %s", req.Query)) var reqs []*Request // Need to bootstrap the query? if req.Server != nil { c.Verbose(fmt.Sprintf("client: Request URL : %s", req.URL())) reqs = []*Request{req} } else if req.Server == nil { c.Verbose("client: Request URL : TBD, bootstrap required") var bootstrapType *bootstrap.RegistryType = bootstrapTypeFor(req) if bootstrapType == nil || (*bootstrapType == bootstrap.ServiceProvider && !c.ServiceProviderExperiment) { return nil, &ClientError{ Type: BootstrapNotSupported, Text: fmt.Sprintf("Cannot run query type '%s' without a server URL, "+ "the server must be specified", req.Type), } } origBootstrapVerbose := c.Bootstrap.Verbose c.Bootstrap.Verbose = c.Verbose defer func() { c.Bootstrap.Verbose = origBootstrapVerbose }() question := &bootstrap.Question{ RegistryType: *bootstrapType, Query: req.Query, } question = question.WithContext(req.Context()) var answer *bootstrap.Answer var err error answer, err = c.Bootstrap.Lookup(question) resp.BootstrapAnswer = answer if err != nil { return resp, err } // No URLs to query? if len(answer.URLs) == 0 { return resp, &ClientError{ Type: BootstrapNoMatch, Text: fmt.Sprintf("No RDAP servers found for '%s'", question.Query), } } for _, u := range answer.URLs { reqs = append(reqs, req.WithServer(u)) } } for i, r := range reqs { c.Verbose(fmt.Sprintf("client: RDAP URL #%d is %s", i, r.URL())) } for _, r := range reqs { c.Verbose(fmt.Sprintf("client: GET %s", r.URL())) httpResponse := c.get(r) resp.HTTP = append(resp.HTTP, httpResponse) if httpResponse.Error != nil { c.Verbose(fmt.Sprintf("client: error: %s", httpResponse.Error)) if r.Context().Err() == context.DeadlineExceeded { return resp, httpResponse.Error } // Continues to the next RDAP server. } else { hrr := httpResponse.Response c.Verbose(fmt.Sprintf("client: status-code=%d, content-type=%s, length=%d bytes, duration=%s", hrr.StatusCode, hrr.Header.Get("Content-Type"), len(httpResponse.Body), httpResponse.Duration)) if len(httpResponse.Body) > 0 && hrr.StatusCode >= 200 && hrr.StatusCode <= 299 { // Decode the response. decoder := NewDecoder(httpResponse.Body) resp.Object, httpResponse.Error = decoder.Decode() if httpResponse.Error != nil { c.Verbose(fmt.Sprintf("client: Error decoding response: %s", httpResponse.Error)) continue } c.Verbose("client: Successfully decoded response") // Implement additional fetches here. return resp, nil } else if hrr.StatusCode == 404 { return resp, &ClientError{ Type: ObjectDoesNotExist, Text: fmt.Sprintf("RDAP server returned 404, object does not exist."), } } } } return resp, &ClientError{ Type: NoWorkingServers, Text: fmt.Sprintf("No RDAP servers responded successfully (tried %d server(s))", len(reqs)), } } func (c *Client) get(rdapReq *Request) *HTTPResponse { // HTTPResponse stores the URL, http.Response, response body... httpResponse := &HTTPResponse{ URL: rdapReq.URL().String(), } start := time.Now() // Setup the HTTP request. req, err := http.NewRequest("GET", httpResponse.URL, nil) if err != nil { httpResponse.Error = err httpResponse.Duration = time.Since(start) return httpResponse } // Optionally add User-Agent header. if c.UserAgent != "" { req.Header.Add("User-Agent", c.UserAgent) } // HTTP Accept header. req.Header.Add("Accept", "application/rdap+json, application/json") // Add context for timeout. req = req.WithContext(rdapReq.Context()) // Make the HTTP request. resp, err := c.HTTP.Do(req) httpResponse.Response = resp // Handle errors such as "remote doesn't speak HTTP"... if err != nil { httpResponse.Error = err httpResponse.Duration = time.Since(start) return httpResponse } defer resp.Body.Close() httpResponse.Body, httpResponse.Error = ioutil.ReadAll(resp.Body) httpResponse.Duration = time.Since(start) return httpResponse } // QueryDomain makes an RDAP request for the |domain|. // // Full contact information (where available) is provided. The timeout is 30s. func (c *Client) QueryDomain(domain string) (*Domain, error) { req := &Request{ Type: DomainRequest, Query: domain, } resp, err := c.doQuickRequest(req) if err != nil { return nil, err } if domain, ok := resp.Object.(*Domain); ok { return domain, nil } else if respError, ok := resp.Object.(*Error); ok { return nil, clientErrorFromRDAPError(respError) } return nil, &ClientError{ Type: WrongResponseType, Text: "The server returned a non-Domain RDAP response", } } func (c *Client) doQuickRequest(req *Request) (*Response, error) { ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*30) defer cancelFunc() req = req.WithContext(ctx) resp, err := c.Do(req) return resp, err } // QueryAutnum makes an RDAP request for the Autonomous System Number (ASN) |autnum|. // // |autnum| is an ASN string, e.g. "AS2856" or "5400". // // Full contact information (where available) is provided. The timeout is 30s. func (c *Client) QueryAutnum(autnum string) (*Autnum, error) { req := &Request{ Type: AutnumRequest, Query: autnum, } resp, err := c.doQuickRequest(req) if err != nil { return nil, err } if autnum, ok := resp.Object.(*Autnum); ok { return autnum, nil } else if respError, ok := resp.Object.(*Error); ok { return nil, clientErrorFromRDAPError(respError) } return nil, &ClientError{ Type: WrongResponseType, Text: "The server returned a non-Autnum RDAP response", } } // QueryIP makes an RDAP request for the IPv4/6 address |ip|, e.g. "192.0.2.0" or "2001:db8::". // // Full contact information (where available) is provided. The timeout is 30s. func (c *Client) QueryIP(ip string) (*IPNetwork, error) { req := &Request{ Type: IPRequest, Query: ip, } resp, err := c.doQuickRequest(req) if err != nil { return nil, err } if ipNet, ok := resp.Object.(*IPNetwork); ok { return ipNet, nil } else if respError, ok := resp.Object.(*Error); ok { return nil, clientErrorFromRDAPError(respError) } return nil, &ClientError{ Type: WrongResponseType, Text: "The server returned a non-IPNetwork RDAP response", } } func bootstrapTypeFor(req *Request) *bootstrap.RegistryType { b := new(bootstrap.RegistryType) switch req.Type { case DomainRequest: *b = bootstrap.DNS case AutnumRequest: *b = bootstrap.ASN case EntityRequest: *b = bootstrap.ServiceProvider case IPRequest: if strings.Contains(req.Query, ":") { *b = bootstrap.IPv6 } else { *b = bootstrap.IPv4 } default: b = nil } return b } rdap-0.9.1/client_error.go000066400000000000000000000015201474227142300154710ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap import ( "fmt" "strings" ) type ClientErrorType uint const ( _ ClientErrorType = iota InputError BootstrapNotSupported BootstrapNoMatch WrongResponseType NoWorkingServers ObjectDoesNotExist RDAPServerError ) type ClientError struct { Type ClientErrorType Text string } func (c ClientError) Error() string { return c.Text } func isClientError(t ClientErrorType, err error) bool { if ce, ok := err.(*ClientError); ok { if ce.Type == t { return true } } return false } func clientErrorFromRDAPError(e *Error) *ClientError { return &ClientError{ Type: RDAPServerError, Text: fmt.Sprintf("Server returned error code %d, title='%s', description='%s'", e.ErrorCode, e.Title, strings.Join(e.Description, " ")), } } rdap-0.9.1/client_test.go000066400000000000000000000037241474227142300153270ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap import ( "fmt" "testing" "github.com/openrdap/rdap/test" ) func verboseFunc() func(text string) { if testing.Verbose() { return func(text string) { fmt.Printf("# %s\n", text) } } return func(text string) { } } func TestClientQueryDomain(t *testing.T) { test.Start(test.Bootstrap) test.Start(test.Responses) defer test.Finish() client := &Client{ Verbose: verboseFunc(), } domain, err := client.QueryDomain("example.cz") if err != nil { t.Errorf("Unexpected error: %s", err) } else if domain == nil { t.Errorf("Unexpected nil Domain") } else if domain.LDHName != "example.cz" { t.Errorf("Unexpected LDHName %s", domain.LDHName) } } func TestClientQueryDomain404(t *testing.T) { test.Start(test.Bootstrap) test.Start(test.Responses) defer test.Finish() client := &Client{ Verbose: verboseFunc(), } _, err := client.QueryDomain("non-existent.cz") if err == nil { t.Errorf("Unexpected success") } else if !isClientError(ObjectDoesNotExist, err) { t.Errorf("Unexpected err %s", err) } } func TestClientQueryDomainWrongType(t *testing.T) { test.Start(test.Bootstrap) test.Start(test.Responses) defer test.Finish() client := &Client{ Verbose: verboseFunc(), } _, err := client.QueryDomain("wrong-response-type.cz") if err == nil { t.Errorf("Unexpected success") } else if !isClientError(WrongResponseType, err) { t.Errorf("Unexpected err %s", err) } } func TestClientQueryDomainMalformed(t *testing.T) { test.Start(test.Bootstrap) test.Start(test.Responses) defer test.Finish() client := &Client{ Verbose: verboseFunc(), } _, err := client.QueryDomain("malformed.cz") if err == nil { t.Errorf("Unexpected success") } else if !isClientError(NoWorkingServers, err) { t.Errorf("Unexpected err %s", err) } } // test Do() // 1) success, 1 of each query // 2) bootstrap not supported // 3) bootstrap no match // test Help... rdap-0.9.1/cmd/000077500000000000000000000000001474227142300132205ustar00rootroot00000000000000rdap-0.9.1/cmd/rdap/000077500000000000000000000000001474227142300141465ustar00rootroot00000000000000rdap-0.9.1/cmd/rdap/main.go000066400000000000000000000002601474227142300154170ustar00rootroot00000000000000package main import ( "os" "github.com/openrdap/rdap" ) func main() { exitCode := rdap.RunCLI(os.Args[1:], os.Stdout, os.Stderr, rdap.CLIOptions{}) os.Exit(exitCode) } rdap-0.9.1/common.go000066400000000000000000000044241474227142300143000ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap // RDAP Conformance // Appears in topmost JSON objects only, embedded (no separate type): // Conformance []string `rdap:"rdapConformance"` // // https://tools.ietf.org/html/rfc7483#section-4.1 // Link signifies a link another resource on the Internet. // // https://tools.ietf.org/html/rfc7483#section-4.2 type Link struct { DecodeData *DecodeData Value string Rel string Href string HrefLang []string `rdap:"hreflang"` Title string Media string Type string } // Notice contains information about the entire RDAP response. // // https://tools.ietf.org/html/rfc7483#section-4.3 type Notice struct { DecodeData *DecodeData Title string Type string Description []string Links []Link } // Remark contains information about the containing RDAP object. // // https://tools.ietf.org/html/rfc7483#section-4.3 type Remark struct { DecodeData *DecodeData Title string Type string Description []string Links []Link } // Language Identifier // Appears in anywhere, embedded (no separate type): // Lang string // // https://tools.ietf.org/html/rfc7483#section-4.4 // Event represents some event which has occured/may occur in the future.. // // https://tools.ietf.org/html/rfc7483#section-4.5 type Event struct { DecodeData *DecodeData Action string `rdap:"eventAction"` Actor string `rdap:"eventActor"` Date string `rdap:"eventDate"` Links []Link } // Status indicates the state of a registered object. // Embedded (no separate type): // Status []string // // https://tools.ietf.org/html/rfc7483#section-4.6 // Port43 indicates the IP/FQDN of a WHOIS server. // Embedded (no separate type): // Port43 string // // https://tools.ietf.org/html/rfc7483#section-4.7 // PublicID maps a public identifier to an object class. // // https://tools.ietf.org/html/rfc7483#section-4.8 type PublicID struct { DecodeData *DecodeData Type string Identifier string } // ObjectClassName specifies the object type as a string. // Embedded (no separate type): // ObjectClassName string // // https://tools.ietf.org/html/rfc7483#section-4.9 // Common contains fields which may appear anywhere in an RDAP response. type Common struct { Lang string } rdap-0.9.1/decode_data.go000066400000000000000000000054131474227142300152230ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap // DecodeData stores a snapshot of all fields in an RDAP object (in raw // interface{} form), at the time of decoding. This allows the values of unknown // fields to be retrieved. // // DecodeData also stores minor warnings/errors generated during decoding. // // DecodeData appears in each RDAP struct (e.g. rdap.Domain{}), and is populated // during decoding. For manually constructed RDAP structs (d := // &rdap.Domain{Handle: "x"}...), DecodeData is not relevant and can be ignored. // // The snapshot values are entirely independent from other fields, and thus are // not synchronised in any way. type DecodeData struct { isKnown map[string]bool values map[string]interface{} overrideKnownValue map[string]bool notes map[string][]string } // TODO (temporary, using for spew output) func (r DecodeData) String() string { result := "[" for name, notes := range r.notes { for _, note := range notes { result += "\n !!!" + name + ": " + note } } result += "\n" return result } // Notes returns a list of minor warnings/errors encountered while decoding the // field |name|. // // |name| is the RDAP field name (not the Go field name), so "port43", not // "Port43". For a full list of decoded field names, use Fields(). // // The warnings/errors returned look like: "invalid JSON type, expecting float". func (r DecodeData) Notes(name string) []string { if notes, ok := r.notes[name]; ok { return notes } return nil } //func (r DecodeData) OverrideValue(key string, value interface{}) { // r.values[key] = value // r.overrideKnownValue[key] = true //} // Value returns the value of the field |name| as an interface{}. // // |name| is the RDAP field name (not the Go field name), so "port43", not // "Port43". For a full list of decoded field names, use Fields(). // func (r DecodeData) Value(name string) interface{} { if v, ok := r.values[name]; ok { return v } return nil } // Fields returns a list of all RDAP field names decoded. // // This includes both known/unknown fields. // // The names returned are the RDAP field names (not the Go field names), so // "port43", not "Port43". func (r DecodeData) Fields() []string { var fields []string for f := range r.values { fields = append(fields, f) } return fields } // UnknownFields returns a list of unknown RDAP fields decoded. func (r DecodeData) UnknownFields() []string { var fields []string for f := range r.values { if _, isKnown := r.isKnown[f]; !isKnown { fields = append(fields, f) } } return fields } func (r *DecodeData) init() { r.isKnown = map[string]bool{} r.values = map[string]interface{}{} r.overrideKnownValue = map[string]bool{} r.notes = map[string][]string{} } rdap-0.9.1/decoder.go000066400000000000000000000521041474227142300144130ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap import ( "encoding/json" "math" "reflect" "strconv" "strings" ) // Decoder decodes an RDAP response (https://tools.ietf.org/html/rfc7483) into a Go value. // // An RDAP response describes an object such as a Domain (data resembling "whois // example.com"), or an IP Network (data resembling "whois 2001:db8::"). For a // live example, see https://rdap.nic.cz/domain/ctk.cz. // // To decode an RDAP response: // // jsonBlob := []byte(` // { // "objectClassName": "domain", // "rdapConformance": ["rdap_level_0"], // "handle": "EXAMPLECOM", // "ldhName": "example.com", // "entities": [] // } // `) // // d := rdap.NewDecoder(jsonBlob) // result, err := d.Decode() // // if err != nil { // if domain, ok := result.(*rdap.Domain); ok { // fmt.Printf("Domain name = %s\n", domain.LDHName) // } // } // // RDAP responses are decoded into the following types: // &rdap.Error{} - Responses with an errorCode value. // &rdap.Autnum{} - Responses with objectClassName="autnum". // &rdap.Domain{} - Responses with objectClassName="domain". // &rdap.Entity{} - Responses with objectClassName="entity". // &rdap.IPNetwork{} - Responses with objectClassName="ip network". // &rdap.Nameserver{} - Responses with objectClassName="nameserver". // &rdap.DomainSearchResults{} - Responses with a domainSearchResults array. // &rdap.EntitySearchResults{} - Responses with a entitySearchResults array. // &rdap.NameserverSearchResults{} - Responses with a nameserverSearchResults array. // &rdap.Help{} - All other valid JSON responses. // // Note that an RDAP server may return a different response type than expected. // // The decoder supports and stores unknown RDAP fields. See the DecodeData // documentation for accessing them. // // Decoding is performed on a best-effort basis, with "minor error"s ignored. // This avoids minor errors rendering a response undecodable. type Decoder struct { data []byte target interface{} } // DecoderOption sets a Decoder option. type DecoderOption func(*Decoder) // DecoderError represents a fatal error encountered while decoding. type DecoderError struct { text string } func (d DecoderError) Error() string { return d.text } // NewDecoder creates a new Decoder to decode the RDAP response |jsonBlob|. // // |opts| is an optional list of DecoderOptions. func NewDecoder(jsonBlob []byte, opts ...DecoderOption) *Decoder { d := &Decoder{ data: jsonBlob, } // Run the DecoderOption func()s. for _, o := range opts { o(d) } return d } // Decode decodes the JSON document. On success, one of several values is // returned. // // The possible results are: // &rdap.Error{} - Responses with an errorCode value. // &rdap.Autnum{} - Responses with objectClassName="autnum". // &rdap.Domain{} - Responses with objectClassName="domain". // &rdap.Entity{} - Responses with objectClassName="entity". // &rdap.IPNetwork{} - Responses with objectClassName="ip network". // &rdap.Nameserver{} - Responses with objectClassName="nameserver". // &rdap.DomainSearchResults{} - Responses with a domainSearchResults array. // &rdap.EntitySearchResults{} - Responses with a entitySearchResults array. // &rdap.NameserverSearchResults{} - Responses with a nameserverSearchResults array. // &rdap.Help{} - All other valid JSON responses. // // On serious errors (e.g. JSON syntax error) an error is returned. Otherwise, // decoding is performed on a best-effort basis, and "minor errors" (such as // incorrect JSON types) are ignored. This avoids minor errors rendering the // whole response undecodable. // // Minor error messages (e.g. type conversions, type errors) are embedded within // each result struct, see the DecodeData fields. func (d *Decoder) Decode() (interface{}, error) { var s map[string]interface{} var err error // Unmarshal the JSON document. err = json.Unmarshal(d.data, &s) if err != nil { return nil, err } // Decode the RDAP response. var result interface{} result, err = d.decodeTopLevel(s) return result, err } // decodeTopLevel decodes the top level object |src|. func (d *Decoder) decodeTopLevel(src map[string]interface{}) (interface{}, error) { // Choose the target struct type. if d.target != nil { // Target already selected, e.g. tests use this. } else if _, exists := src["errorCode"]; exists { d.target = &Error{} } else if o, exists := src["objectClassName"]; exists { if objectClassName, ok := o.(string); ok { switch objectClassName { case "autnum": d.target = &Autnum{} case "domain": d.target = &Domain{} case "entity": d.target = &Entity{} case "ip network": d.target = &IPNetwork{} case "nameserver": d.target = &Nameserver{} default: return nil, DecoderError{text: "objectClassName is not recognised"} } } else { return nil, DecoderError{text: "objectClassName is not a string"} } } else if _, exists := src["domainSearchResults"]; exists { d.target = &DomainSearchResults{} } else if _, exists := src["entitySearchResults"]; exists { d.target = &EntitySearchResults{} } else if _, exists := src["nameserverSearchResults"]; exists { d.target = &NameserverSearchResults{} } // Default to returning a Help{}. // // All remaining JSON documents are assumed to be Help responses. There's no // way of distinguishing a Help response and a valid non-RDAP response. if d.target == nil { d.target = &Help{} } // Construct the result type. result := reflect.New(reflect.TypeOf(d.target).Elem()) // Decode the response into the result type. _, err := d.decode("", src, result, nil) return result.Interface(), err } // decode decodes the JSON structure |src| into the value |dst|. // // The type of |dst| is predetermined, |src| must match it, or be convertable to // it. |dst| can be a bool, float64, struct, ptr, string, slice, map, etc. // // |decodeData| is optional, and is used to store raw values/note minor errors. // |keyName| is used while storing minor errors. // // Returns true if |dst| was set successfully. func (d *Decoder) decode(keyName string, src interface{}, dst reflect.Value, decodeData *DecodeData) (bool, error) { var success bool var err error // Choose and run the correct decoder for |dst|'s type. switch dst.Kind() { case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: success, err = d.decodeUint(keyName, src, dst, decodeData) case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: success, err = d.decodeInt(keyName, src, dst, decodeData) case reflect.Float64: success, err = d.decodeFloat64(keyName, src, dst, decodeData) case reflect.Bool: success, err = d.decodeBool(keyName, src, dst, decodeData) case reflect.Struct: success, err = d.decodeStruct(keyName, src, dst, decodeData) case reflect.Ptr: success, err = d.decodePtr(keyName, src, dst, decodeData) case reflect.String: success, err = d.decodeString(keyName, src, dst, decodeData) case reflect.Slice: success, err = d.decodeSlice(keyName, src, dst, decodeData) case reflect.Map: success, err = d.decodeMap(keyName, src, dst, decodeData) default: panic("BUG: unknown destination type") } return success, err } // decodeSlice decodes |src| into the slice |dst|. // // If a minor error is encountered while decoding a value, it is ignored and not // returned in the resulting slice. // // The parameters and return variables are as per decode(). func (d *Decoder) decodeSlice(keyName string, src interface{}, dst reflect.Value, decodeData *DecodeData) (bool, error) { // Cast the input to a slice. srcSlice, ok := src.([]interface{}) if !ok { d.addDecodeNote(decodeData, keyName, "invalid JSON type, expecting array") return false, nil } // Construct the result slice. result := reflect.MakeSlice(dst.Type(), 0, len(srcSlice)) // Foreach value in the input slice... for _, v := range srcSlice { // Construct a result value for it. vdst := reflect.New(dst.Type().Elem()) // Decode into the result value. success, err := d.decode(keyName, v, reflect.Indirect(vdst), decodeData) if err != nil { return false, err } // Only if the decode was successful, append to result slice. if success { result = reflect.Append(result, reflect.Indirect(vdst)) } } dst.Set(result) return true, nil } // decodeMap decodes |src| into the map |dst|. // // Only destination maps with a string key are supported. // // If a minor error is encountered while decoding a map value, it is ignored and // not returned in the resulting map. // // The parameters and return variables are as per decode(). func (d *Decoder) decodeMap(keyName string, src interface{}, dst reflect.Value, decodeData *DecodeData) (bool, error) { if dst.Type().Key().Kind() != reflect.String { panic("BUG: map key is not string") } srcMap, ok := src.(map[string]interface{}) if !ok { d.addDecodeNote(decodeData, keyName, "invalid JSON type, expecting object") return false, nil } // Construct the result map. result := reflect.MakeMap(dst.Type()) // Foreach |src| map key/value... for k, v := range srcMap { // Construct the result value. vdst := reflect.New(dst.Type().Elem()) // Decode into the result value. success, err := d.decode(keyName+":"+k, v, reflect.Indirect(vdst), decodeData) if err != nil { return false, err } // If the decode was successful, add to the result map. if success { result.SetMapIndex(reflect.ValueOf(k), reflect.Indirect(vdst)) } } dst.Set(result) return true, nil } // decodeUint decodes |src| into the uint8/16/32/64 |dst|. // // This function can perform type conversions, warnings/errors are noted for // these. // // The parameters and return variables are as per decode(). func (d *Decoder) decodeUint(keyName string, src interface{}, dst reflect.Value, decodeData *DecodeData) (bool, error) { var err error var result uint64 success := true switch src.(type) { case bool: if src.(bool) { result = 1 } d.addDecodeNote(decodeData, keyName, "bool to uint conversion") case float64: result = uint64(src.(float64)) d.addDecodeNote(decodeData, keyName, "float64 to uint conversion") case string: var convError error result, convError = strconv.ParseUint(src.(string), 10, 64) if convError != nil { result = 0 success = false d.addDecodeNote(decodeData, keyName, "error converting string to uint") } else { d.addDecodeNote(decodeData, keyName, "string to uint conversion") } case nil: result = 0 d.addDecodeNote(decodeData, keyName, "null to uint conversion") default: d.addDecodeNote(decodeData, keyName, "invalid JSON type, expecting float") success = false } if success { var maxVal uint64 // Check the result number is within range of the target type. switch dst.Kind() { case reflect.Uint8: maxVal = math.MaxUint8 case reflect.Uint16: maxVal = math.MaxUint16 case reflect.Uint32: maxVal = math.MaxUint32 case reflect.Uint64: maxVal = math.MaxUint64 default: panic("Unexpected int type") } if result > maxVal { d.addDecodeNote(decodeData, keyName, "error: number too large") success = false } else { dst.SetUint(result) } } return success, err } // decodeInt decodes |src| into the int8/16/32/64 |dst|. // // This function can perform type conversions, warnings/errors are noted for // these. // // The parameters and return variables are as per decode(). func (d *Decoder) decodeInt(keyName string, src interface{}, dst reflect.Value, decodeData *DecodeData) (bool, error) { var err error var result int64 success := true switch src.(type) { case bool: if src.(bool) { result = 1 } d.addDecodeNote(decodeData, keyName, "bool to int conversion") case float64: result = int64(src.(float64)) d.addDecodeNote(decodeData, keyName, "float64 to int conversion") case string: var convError error result, convError = strconv.ParseInt(src.(string), 10, 64) if convError != nil { result = 0 success = false d.addDecodeNote(decodeData, keyName, "error converting string to int") } else { d.addDecodeNote(decodeData, keyName, "string to int conversion") } case nil: result = 0 d.addDecodeNote(decodeData, keyName, "null to int conversion") default: d.addDecodeNote(decodeData, keyName, "invalid JSON type, expecting float") success = false } if success { var minVal int64 var maxVal int64 // Check the result number is within range of the target type. switch dst.Kind() { case reflect.Int8: minVal = math.MinInt8 maxVal = math.MaxInt8 case reflect.Int16: minVal = math.MinInt16 maxVal = math.MaxInt16 case reflect.Int32: minVal = math.MinInt32 maxVal = math.MaxInt32 case reflect.Int64: minVal = math.MinInt64 maxVal = math.MaxInt64 default: panic("Unexpected int type") } if result < minVal || result > maxVal { d.addDecodeNote(decodeData, keyName, "error: number too small or large") success = false } else { dst.SetInt(result) } } return success, err } // decodeFloat64 decodes |src| into the float64 |dst|. // // This function can perform type conversions, warnings/errors are noted for // these. // // The parameters and return variables are as per decode(). func (d *Decoder) decodeFloat64(keyName string, src interface{}, dst reflect.Value, decodeData *DecodeData) (bool, error) { var err error var result float64 success := true switch src.(type) { case bool: if src.(bool) { result = 1.0 } d.addDecodeNote(decodeData, keyName, "bool to float64 conversion") case float64: result = src.(float64) case string: var convError error result, convError = strconv.ParseFloat(src.(string), 64) if convError != nil { result = 0.0 success = false d.addDecodeNote(decodeData, keyName, "error converting string to float64") } else { d.addDecodeNote(decodeData, keyName, "string to float64 conversion") } case nil: result = 0.0 d.addDecodeNote(decodeData, keyName, "null to float64 conversion") default: d.addDecodeNote(decodeData, keyName, "invalid JSON type, expecting float") success = false } dst.SetFloat(result) return success, err } // decodeString decodes |src| into the string |dst|. // // This function can perform type conversions, warnings/errors are noted for // these. // // The parameters and return variables are as per decode(). func (d *Decoder) decodeString(keyName string, src interface{}, dst reflect.Value, decodeData *DecodeData) (bool, error) { var err error var result string success := true switch src.(type) { case bool: result = strconv.FormatBool(src.(bool)) d.addDecodeNote(decodeData, keyName, "bool to string conversion") case float64: result = strconv.FormatFloat(src.(float64), 'f', -1, 64) d.addDecodeNote(decodeData, keyName, "float64 to string conversion") case string: result = src.(string) case nil: result = "" d.addDecodeNote(decodeData, keyName, "null to empty string conversion") default: d.addDecodeNote(decodeData, keyName, "invalid JSON type, expecting string") success = false } dst.SetString(result) return success, err } // decodeBool decodes |src| into the bool |dst|. // // This function can perform type conversions, warnings/errors are noted for // these. // // The parameters and return variables are as per decode(). func (d *Decoder) decodeBool(keyName string, src interface{}, dst reflect.Value, decodeData *DecodeData) (bool, error) { var err error var result bool success := true switch src.(type) { case bool: result = src.(bool) case float64: f := src.(float64) if f != 0 { result = true } d.addDecodeNote(decodeData, keyName, "float64 to bool conversion") case string: var convError error result, convError = strconv.ParseBool(src.(string)) if convError != nil { d.addDecodeNote(decodeData, keyName, "error converting string to bool") result = false success = false } else { d.addDecodeNote(decodeData, keyName, "string to bool conversion") } case nil: result = false d.addDecodeNote(decodeData, keyName, "null to bool conversion") default: d.addDecodeNote(decodeData, keyName, "invalid JSON type, expecting bool") success = false } dst.SetBool(result) return success, err } // decodeStruct decodes |src| into the struct |dst|. // // The parameters and return variables are as per decode(). func (d *Decoder) decodeStruct(keyName string, src interface{}, dst reflect.Value, decodeData *DecodeData) (bool, error) { var err error // |src| must be a JSON object. srcMap, ok := src.(map[string]interface{}) if !ok { d.addDecodeNote(decodeData, keyName, "invalid JSON type, expecting object") return false, nil } // Identify the fields in the struct we'll decode into. // e.g. fields["port43"] => [some reflect.Value] var fields map[string]reflect.Value var myDecodeData *DecodeData fields, myDecodeData = d.chooseFields(dst) // If the result struct has a DecodeData... if myDecodeData != nil { // Save a snapshot of each field. for name, value := range srcMap { myDecodeData.values[name] = value } // Note the fields we know about, so unknown fields can be identified. for name := range fields { myDecodeData.isKnown[name] = true } } // Foreach field in |srcMap|... for name, value := range srcMap { // If there's a matching Go field, decode into it... if _, ok := fields[name]; ok { _, err := d.decode(name, value, fields[name], myDecodeData) if err != nil { return false, err } } } return true, err } func (d *Decoder) chooseFields(v reflect.Value) (map[string]reflect.Value, *DecodeData) { if v.Kind() != reflect.Struct { panic("BUG: chooseFields called on non-struct") } var decodeData *DecodeData fields := map[string]reflect.Value{} vt := v.Type() for i := 0; i < vt.NumField(); i++ { structField := vt.Field(i) if structField.Type.Kind() == reflect.Ptr && structField.Type.Elem().Name() == "DecodeData" { if decodeData != nil { panic("BUG: Multiple DecodeData fields in struct") } else { decodeData = &DecodeData{} decodeData.init() v.Field(i).Set(reflect.ValueOf(decodeData)) } } else { if structField.Anonymous { subFields, subDecodeData := d.chooseFields(v.Field(i)) if subDecodeData != nil { if decodeData != nil { panic("BUG: Multiple DecodeData fields in struct") } else { decodeData = subDecodeData } } for k, v := range subFields { if _, exists := fields[k]; exists { panic("BUG: Duplicate field " + k + " in struct") } fields[k] = v } } else if name, ok := d.getFieldName(structField); ok { if _, exists := fields[name]; exists { panic("BUG: Duplicate field " + name + " in struct") } fields[name] = v.Field(i) switch fields[name].Kind() { case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64, reflect.Bool, reflect.Struct, reflect.Ptr, reflect.String, reflect.Slice, reflect.Map: // These types are all supported. default: panic("BUG: Unsupported field type for " + name) } } } } return fields, decodeData } // getFieldName returns the RDAP field name (if any) of |sf|. // // Returns the field name and true if |sf| has an RDAP field name. Otherwise // returns empty string and false. func (d *Decoder) getFieldName(sf reflect.StructField) (string, bool) { // Handle non-exported fields. if sf.Name[0:1] != strings.ToUpper(sf.Name[0:1]) { if sf.Tag.Get("rdap") != "" { panic("BUG: rdap tag on non-exported struct field") } return "", false } // The "rdap" struct tag specifies a custom RDAP field name. name := sf.Tag.Get("rdap") // Otherwise, the RDAP field name is the Go field name, with the first // character lowercased. // // e.g. domain.Port43 => RDAP field name "port43". if name == "" { name = strings.ToLower(sf.Name[0:1]) + sf.Name[1:] } return name, true } // decodePtr decodes |src| into the ptr |dst|. The ptr is initialised to a new // value if nil. // // The parameters and return variables are as per decode(). func (d *Decoder) decodePtr(keyName string, src interface{}, dst reflect.Value, decodeData *DecodeData) (bool, error) { var success bool var err error if dst.Type().Elem().Name() == "VCard" { vcard, vcardError := newVCardImpl(src) if vcardError == nil { dst.Set(reflect.ValueOf(vcard)) success = true } else { d.addDecodeNote(decodeData, keyName, vcardError.Error()) } } else { if dst.IsNil() { value := reflect.New(dst.Type().Elem()) dst.Set(value) } success, err = d.decode(keyName, src, reflect.Indirect(dst), decodeData) } return success, err } // addDecodeNote adds a DecodeData note |msg| for the field |key|. func (d *Decoder) addDecodeNote(decodeData *DecodeData, key string, msg string) { if decodeData == nil { return } if _, ok := decodeData.notes[key]; !ok { decodeData.notes[key] = []string{} } decodeData.notes[key] = append(decodeData.notes[key], msg) } rdap-0.9.1/decoder_test.go000066400000000000000000000125761474227142300154630ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap import ( "reflect" "testing" "github.com/davecgh/go-spew/spew" "github.com/openrdap/rdap/test" ) func TestDecodeEmpty(t *testing.T) { type Empty struct { } runDecodeAndCompareTest(t, &Empty{}, ` {} `, &Empty{}) } func TestDecodeDecodeData(t *testing.T) { type XYZ struct { DecodeData *DecodeData S1 string S2 string `rdap:"s2Name"` SF string } result, ok := runDecode(t, &XYZ{}, ` { "s1": "S1", "s2Name": "S2", "sF": 1.5, "unknown" : "value" }`) if !ok { return } x := result.(*XYZ) if x.S1 != "S1" || x.S2 != "S2" || x.SF != "1.5" { t.Errorf("Decode values bad %v", x) } if x.DecodeData == nil { t.Errorf("DecodeData not instantiated") } else if len(x.DecodeData.Notes("sF")) != 1 { t.Errorf("DecodeData notes not added") } else if len(x.DecodeData.Fields()) != 4 { t.Errorf("DecodeData Fields() bad") } else if len(x.DecodeData.UnknownFields()) != 1 { t.Errorf("DecodeData UnknownFields() bad") } else if !reflect.DeepEqual(x.DecodeData.Value("unknown"), "value") { t.Errorf("DecodeData bad Value()") } } func TestDecodeVCard(t *testing.T) { type XYZ struct { VCard *VCard } result, ok := runDecode(t, &XYZ{}, ` { "vCard": [ "vcard", [ ["version", {}, "text", "4.0"], ["fn", {}, "text", "First Last"] ] ] } `) if !ok { return } x := result.(*XYZ) if x.VCard == nil { t.Errorf("VCard not decoded") } else if len(x.VCard.Properties) != 2 { t.Errorf("VCard properties not decoded") } } func TestDecodeSlice(t *testing.T) { type XYZ struct { S []string } runDecodeAndCompareTest(t, &XYZ{}, ` { "s": ["a", "b"] } `, &XYZ{ S: []string{"a", "b"}, }) } func TestDecodeMap(t *testing.T) { type XYZ struct { M map[string]string } runDecodeAndCompareTest(t, &XYZ{}, ` { "m": {"a": "av", "b": "bv"} } `, &XYZ{ M: map[string]string{"a": "av", "b": "bv"}, }) } func TestDecodeUints(t *testing.T) { type XYZ struct { A uint8 AOverflow uint8 B uint16 C uint32 D uint64 S uint8 BF uint8 BT uint8 N uint8 } runDecodeAndCompareTest(t, &XYZ{}, ` { "a": 100, "aOverflow": 256, "b": 200, "c": 42, "d": 43, "s": "10", "bF": false, "bT": true, "n": null } `, &XYZ{ A: 100, AOverflow: 0, B: 200, C: 42, D: 43, S: 10, BF: 0, BT: 1, N: 0, }) } func TestDecodeInts(t *testing.T) { type XYZ struct { A int8 AUnderflow int8 AOverflow int8 B int16 C int32 D int64 S int8 BF int8 BT int8 N int8 } runDecodeAndCompareTest(t, &XYZ{}, ` { "a": 100, "aUnderflow": -129, "aOverflow": 128, "b": 200, "c": 42, "d": 43, "s": "10", "bF": false, "bT": true, "n": null } `, &XYZ{ A: 100, AUnderflow: 0, AOverflow: 0, B: 200, C: 42, D: 43, S: 10, BF: 0, BT: 1, N: 0, }) } func TestDecodeFloat64(t *testing.T) { type XYZ struct { F float64 FPtr *float64 S1 float64 S2 float64 BF float64 BT float64 N float64 } fptr := 1.5 runDecodeAndCompareTest(t, &XYZ{}, ` { "f": 1.5, "fPtr": 1.5, "s1": "1.5", "s2": "-1.5", "bF": false, "bT": true, "n": null } `, &XYZ{ F: 1.5, FPtr: &fptr, S1: 1.5, S2: -1.5, BF: 0.0, BT: 1.0, N: 0.0, }) } func TestDecodeBool(t *testing.T) { type XYZ struct { B bool BPtr *bool SF bool ST bool FF bool FT bool N bool } bptr := true runDecodeAndCompareTest(t, &XYZ{}, ` { "b": true, "bPtr": true, "sF": "false", "sT": "true", "fF": 0, "fT": 1, "n": null } `, &XYZ{ B: true, BPtr: &bptr, ST: true, SF: false, FF: false, FT: true, N: false, }) } func TestDecodeString(t *testing.T) { type XYZ struct { S string SPtr *string BT string BF string F1 string F2 string N string } sptr := "sptr" runDecodeAndCompareTest(t, &XYZ{}, ` { "s": "test", "sPtr": "sptr", "bT": true, "bF": false, "f1": 1.0, "f2": -3.14, "n2": null } `, &XYZ{ S: "test", SPtr: &sptr, BT: "true", BF: "false", F1: "1", F2: "-3.14", N: "", }) } func TestDecodeBug1(t *testing.T) { jsonBlob := test.LoadFile("rdap/rdap-pilot.verisignlabs.com/entity-1-VRSN") d := NewDecoder([]byte(jsonBlob)) result, err := d.Decode() t.Logf("%s %s\n", result, err) } func TestDecodeMismatchedTypes(t *testing.T) { type C struct { } type XYZ struct { A []string B map[string]string C C } runDecodeAndCompareTest(t, &XYZ{}, ` { "a": {}, "b": [1,2,3], "c": false } `, &XYZ{ A: nil, B: nil, C: C{}, }) } func runDecode(t *testing.T, target interface{}, jsonBlob string) (interface{}, bool) { d := NewDecoder([]byte(jsonBlob)) d.target = target result, err := d.Decode() if err != nil { t.Errorf("While decoding '%s', got error: %s", jsonBlob, err) return result, false } return result, true } func runDecodeAndCompareTest(t *testing.T, target interface{}, jsonBlob string, expected interface{}) { result, ok := runDecode(t, target, jsonBlob) if !ok { return } if !reflect.DeepEqual(expected, result) { t.Errorf("While decoding '%s':\nexpected %s\ngot %s", jsonBlob, spew.Sdump(expected), spew.Sdump(result)) } } rdap-0.9.1/doc.go000066400000000000000000000027411474227142300135550ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. // Package rdap implements a client for the Registration Data Access Protocol (RDAP). // // RDAP is a modern replacement for the text-based WHOIS (port 43) protocol. It provides registration data for domain names/IP addresses/AS numbers, and more, in a structured format. // // This client executes RDAP queries and returns the responses as Go values. // // Quick usage: // client := &rdap.Client{} // domain, err := client.QueryDomain("example.cz") // // if err == nil { // fmt.Printf("Handle=%s Domain=%s\n", domain.Handle, domain.LDHName) // } // The QueryDomain(), QueryAutnum(), and QueryIP() methods all provide full contact information, and timeout after 30s. // // Normal usage: // // Query example.cz. // req := &rdap.Request{ // Type: rdap.DomainRequest, // Query: "example.cz", // } // // client := &rdap.Client{} // resp, err := client.Do(req) // // if domain, ok := resp.Object.(*rdap.Domain); ok { // fmt.Printf("Handle=%s Domain=%s\n", domain.Handle, domain.LDHName) // } // // As of June 2017, all five number registries (AFRINIC, ARIN, APNIC, LANIC, // RIPE) run RDAP servers. A small number of TLDs (top level domains) support // RDAP so far, listed on https://data.iana.org/rdap/dns.json. // // The RDAP protocol uses HTTP, with responses in a JSON format. A bootstrapping mechanism (http://data.iana.org/rdap/) is used to determine the server to query. package rdap rdap-0.9.1/domain.go000066400000000000000000000031711474227142300142550ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap // Domain represents information about a DNS name and point of delegation. // // Domain is a topmost RDAP response object. type Domain struct { DecodeData *DecodeData Common Conformance []string `rdap:"rdapConformance"` ObjectClassName string Notices []Notice Handle string LDHName string `rdap:"ldhName"` UnicodeName string Variants []Variant Nameservers []Nameserver SecureDNS *SecureDNS Entities []Entity Status []string PublicIDs []PublicID `rdap:"publicIds"` Remarks []Remark Links []Link Port43 string Events []Event Network *IPNetwork } // Variant is a subfield of Domain. type Variant struct { DecodeData *DecodeData Common Relation []string IDNTable string `rdap:"idnTable"` VariantNames []VariantName } // VariantName is a subfield of Variant. type VariantName struct { DecodeData *DecodeData Common LDHName string `rdap:"ldhName"` UnicodeName string } // SecureDNS is ia subfield of Domain. type SecureDNS struct { DecodeData *DecodeData Common ZoneSigned *bool DelegationSigned *bool MaxSigLife *uint64 DS []DSData `rdap:"dsData"` Keys []KeyData `rdap:"keyData"` } // DSData is a subfield of Domain. type DSData struct { DecodeData *DecodeData Common KeyTag *uint64 Algorithm *uint8 Digest string DigestType *uint8 Events []Event Links []Link } type KeyData struct { DecodeData *DecodeData Flags *uint16 Protocol *uint8 Algorithm *uint8 PublicKey string Events []Event Links []Link } rdap-0.9.1/entity.go000066400000000000000000000012711474227142300143210ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap // Entity represents information of an organisation or person. // // Entity is a topmost RDAP response object. type Entity struct { DecodeData *DecodeData Common Conformance []string `rdap:"rdapConformance"` ObjectClassName string Notices []Notice Handle string VCard *VCard `rdap:"vcardArray"` Roles []string PublicIDs []PublicID `rdap:"publicIds"` Entities []Entity Remarks []Remark Links []Link Events []Event AsEventActor []Event Status []string Port43 string Networks []IPNetwork Autnums []Autnum } rdap-0.9.1/error.go000066400000000000000000000005561474227142300141430ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap // Error represents an error response. // // Error is a topmost RDAP response object. type Error struct { DecodeData *DecodeData Common Conformance []string `rdap:"rdapConformance"` Notices []Notice ErrorCode *uint16 Title string Description []string } rdap-0.9.1/example_test.go000066400000000000000000000061031474227142300154760ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap_test import ( "fmt" "github.com/openrdap/rdap" ) func Example() { var jsonBlob = []byte(` { "objectClassName": "domain", "rdapConformance": ["rdap_level_0"], "handle": "EXAMPLECOM", "ldhName": "example.com", "status": ["active"], "entities": [ { "objectClassName": "entity", "handle": "EXAMPLECOMREG", "roles": ["registrant"], "vcardArray": [ "vcard", [ ["version", {}, "text", "4.0"], ["fn", {}, "text", "John Smith"], ["adr", {}, "text", [ "Box 1", "Suite 29", "1234 Fake St", "Toronto", "ON", "M5E 1W5", "Canada" ] ], ["tel", {}, "uri", "tel:+1-555-555-5555"], ["email", {}, "text", "hi@example.com"] ] ] } ] } `) // Decode the response. d := rdap.NewDecoder(jsonBlob) result, err := d.Decode() // Print the response. if err != nil { fmt.Printf("%s\n", err) } else { domain := result.(*rdap.Domain) PrintDomain(domain) } // Output: // Handle=EXAMPLECOM // LDHName=example.com // Status=[active] // Contact 0, roles: [registrant]: // Name : 'John Smith' // POBox : 'Box 1' // Ext : 'Suite 29' // Street : '1234 Fake St' // Locality : 'Toronto' // Region : 'ON' // PostalCode: 'M5E 1W5' // Country : 'Canada' // Tel : 'tel:+1-555-555-5555' // Fax : '' // Email : 'hi@example.com' } // PrintDomain prints some basic rdap.Domain fields. func PrintDomain(d *rdap.Domain) { // Registry unique identifier for the domain. Here, "example.cz". fmt.Printf("Handle=%s\n", d.Handle) // Domain name (LDH = letters, digits, hyphen). Here, "example.cz". fmt.Printf("LDHName=%s\n", d.LDHName) // Domain registration status. Here, "active". // See https://tools.ietf.org/html/rfc7483#section-10.2.2. fmt.Printf("Status=%v\n", d.Status) // Contact information. for i, e := range d.Entities { // Contact roles, such as "registrant", "administrative", "billing". // See https://tools.ietf.org/html/rfc7483#section-10.2.4. fmt.Printf("Contact %d, roles: %v:\n", i, e.Roles) // RDAP uses VCard for contact information, including name, address, // telephone, and e-mail address. if e.VCard != nil { v := e.VCard // Name. fmt.Printf(" Name : '%s'\n", v.Name()) // Address. fmt.Printf(" POBox : '%s'\n", v.POBox()) fmt.Printf(" Ext : '%s'\n", v.ExtendedAddress()) fmt.Printf(" Street : '%s'\n", v.StreetAddress()) fmt.Printf(" Locality : '%s'\n", v.Locality()) fmt.Printf(" Region : '%s'\n", v.Region()) fmt.Printf(" PostalCode: '%s'\n", v.PostalCode()) fmt.Printf(" Country : '%s'\n", v.Country()) // Phone numbers. fmt.Printf(" Tel : '%s'\n", v.Tel()) fmt.Printf(" Fax : '%s'\n", v.Fax()) // Email address. fmt.Printf(" Email : '%s'\n", v.Email()) // The raw VCard fields are also accessible. } } } rdap-0.9.1/go.mod000066400000000000000000000005721474227142300135670ustar00rootroot00000000000000module github.com/openrdap/rdap go 1.19 require ( github.com/alecthomas/kingpin/v2 v2.3.2 github.com/davecgh/go-spew v1.1.1 github.com/jarcoal/httpmock v1.3.0 github.com/mitchellh/go-homedir v1.1.0 golang.org/x/crypto v0.9.0 ) require ( github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect ) rdap-0.9.1/go.sum000066400000000000000000000041521474227142300136120ustar00rootroot00000000000000github.com/alecthomas/kingpin/v2 v2.3.2 h1:H0aULhgmSzN8xQ3nX1uxtdlTHYoPLu5AhHxWrKI6ocU= github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= rdap-0.9.1/help.go000066400000000000000000000004541474227142300137370ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap // Help represents a help response. // // Help is a topmost RDAP response object. type Help struct { DecodeData *DecodeData Common Conformance []string `rdap:"rdapConformance"` Notices []Notice } rdap-0.9.1/ipnetwork.go000066400000000000000000000012511474227142300150250ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap // IPNetwork represents information of an IP Network. // // IPNetwork is a topmost RDAP response object. type IPNetwork struct { DecodeData *DecodeData Common Conformance []string `rdap:"rdapConformance"` ObjectClassName string Notices []Notice Handle string StartAddress string EndAddress string IPVersion string `rdap:"ipVersion"` Name string Type string Country string ParentHandle string Status []string Entities []Entity Remarks []Remark Links []Link Port43 string Events []Event } rdap-0.9.1/nameserver.go000066400000000000000000000013411474227142300151520ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap // Nameserver represents information of a DNS nameserver. // // Nameserver is a topmost RDAP response object. type Nameserver struct { DecodeData *DecodeData Common Conformance []string `rdap:"rdapConformance"` ObjectClassName string Notices []Notice Handle string LDHName string `rdap:"ldhName"` UnicodeName string IPAddresses *IPAddressSet `rdap:"ipAddresses"` Entities []Entity Status []string Remarks []Remark Links []Link Port43 string Events []Event } // IPAddressSet is a subfield of Nameserver. type IPAddressSet struct { DecodeData *DecodeData Common V6 []string V4 []string } rdap-0.9.1/print.go000066400000000000000000000433041474227142300141440ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap import ( "fmt" "io" "os" "strconv" "strings" ) // Printer formats RDAP response objects as human readable text, and writes them // to an io.Writer. // // The format resembles a WHOIS response. type Printer struct { // Output io.Writer. // // Defaults to os.Stdout. Writer io.Writer // RDAP responses typically consist of a nested set of objects, // these are represented using indentation. // Character to ident responses with. // // Defaults to ' ' (space character). IndentChar rune // Number of characters per indentation. // // Defaults to 2. IndentSize uint // OmitNotices prevents RDAP Notices from being printed. OmitNotices bool // OmitNotices prevents RDAP Remarks from being printed. OmitRemarks bool // BriefOutput shortens the output by omitting various objects. These are: // // Conformance, Notices, Remarks, Events, Port43, Variants, SecureDNS. BriefOutput bool // BriefLinks causes Link objects to be printed as a single line (the link), // rather than as a multi-line object. BriefLinks bool } func (p *Printer) Print(obj RDAPObject) { if p.Writer == nil { p.Writer = os.Stdout } if p.IndentSize == 0 { p.IndentSize = 2 } if p.IndentChar == '\000' { p.IndentChar = ' ' } p.printObject(obj, 0) } func (p *Printer) printObject(obj RDAPObject, indentLevel uint) { if obj == nil { return } switch v := obj.(type) { case *Domain: p.printDomain(v, indentLevel) case *Entity: p.printEntity(v, indentLevel) case *Nameserver: p.printNameserver(v, indentLevel) case *Autnum: p.printAutnum(v, indentLevel) case *IPNetwork: p.printIPNetwork(v, indentLevel) case *Help: p.printHelp(v, indentLevel) case *Error: p.printError(v, indentLevel) case *DomainSearchResults: p.printDomainSearchResults(v, indentLevel) case *EntitySearchResults: p.printEntitySearchResults(v, indentLevel) case *NameserverSearchResults: p.printNameserverSearchResults(v, indentLevel) } } func (p *Printer) printNameserverSearchResults(sr *NameserverSearchResults, indentLevel uint) { p.printHeading("Nameserver Search Results", indentLevel) indentLevel++ if !p.BriefOutput { for _, c := range sr.Conformance { p.printValue("Conformance", c, indentLevel) } } if !p.BriefOutput || p.OmitNotices { for _, n := range sr.Notices { p.printNotice(n, indentLevel) } } for _, n := range sr.Nameservers { p.printNameserver(&n, indentLevel) } p.printUnknowns(sr.DecodeData, indentLevel) } func (p *Printer) printEntitySearchResults(sr *EntitySearchResults, indentLevel uint) { p.printHeading("Entity Search Results", indentLevel) indentLevel++ if !p.BriefOutput { for _, c := range sr.Conformance { p.printValue("Conformance", c, indentLevel) } } if !p.BriefOutput || p.OmitNotices { for _, n := range sr.Notices { p.printNotice(n, indentLevel) } } for _, e := range sr.Entities { p.printEntity(&e, indentLevel) } p.printUnknowns(sr.DecodeData, indentLevel) } func (p *Printer) printDomainSearchResults(sr *DomainSearchResults, indentLevel uint) { p.printHeading("Domain Search Results", indentLevel) indentLevel++ if !p.BriefOutput { for _, c := range sr.Conformance { p.printValue("Conformance", c, indentLevel) } } if !p.BriefOutput || p.OmitNotices { for _, n := range sr.Notices { p.printNotice(n, indentLevel) } } for _, d := range sr.Domains { p.printDomain(&d, indentLevel) } p.printUnknowns(sr.DecodeData, indentLevel) } func (p *Printer) printError(e *Error, indentLevel uint) { p.printHeading("Error", indentLevel) indentLevel++ if !p.BriefOutput { for _, c := range e.Conformance { p.printValue("Conformance", c, indentLevel) } } if !p.BriefOutput || p.OmitNotices { for _, n := range e.Notices { p.printNotice(n, indentLevel) } } if e.ErrorCode != nil { p.printValue("Error Code", strconv.FormatUint(uint64(*e.ErrorCode), 10), indentLevel) } p.printValue("Title", e.Title, indentLevel) for _, d := range e.Description { p.printValue("Description", d, indentLevel) } p.printUnknowns(e.DecodeData, indentLevel) } func (p *Printer) printHelp(h *Help, indentLevel uint) { p.printHeading("Help", indentLevel) indentLevel++ if !p.BriefOutput { for _, c := range h.Conformance { p.printValue("Conformance", c, indentLevel) } } if !p.BriefOutput || p.OmitNotices { for _, n := range h.Notices { p.printNotice(n, indentLevel) } } p.printUnknowns(h.DecodeData, indentLevel) } func (p *Printer) printDomain(d *Domain, indentLevel uint) { p.printHeading("Domain", indentLevel) indentLevel++ p.printValue("Domain Name", d.LDHName, indentLevel) p.printValue("Domain Name (Unicode)", d.UnicodeName, indentLevel) p.printValue("Handle", d.Handle, indentLevel) for _, s := range d.Status { p.printValue("Status", s, indentLevel) } if !p.BriefOutput { p.printValue("Port43", d.Port43, indentLevel) } for _, pid := range d.PublicIDs { p.printPublicID(pid, indentLevel) } if !p.BriefOutput { for _, c := range d.Conformance { p.printValue("Conformance", c, indentLevel) } } if !p.BriefOutput || p.OmitNotices { for _, n := range d.Notices { p.printNotice(n, indentLevel) } } if !p.BriefOutput || p.OmitRemarks { for _, r := range d.Remarks { p.printRemark(r, indentLevel) } } for _, l := range d.Links { p.printLink(l, indentLevel) } if !p.BriefOutput { for _, e := range d.Events { p.printEvent(e, indentLevel, false) } for _, v := range d.Variants { p.printVariant(v, indentLevel) } if d.SecureDNS != nil { p.printSecureDNS(d.SecureDNS, indentLevel) } } for _, e := range d.Entities { p.printEntity(&e, indentLevel) } for _, n := range d.Nameservers { p.printNameserver(&n, indentLevel) } if d.Network != nil { p.printIPNetwork(d.Network, indentLevel) } p.printUnknowns(d.DecodeData, indentLevel) } func (p *Printer) printAutnum(a *Autnum, indentLevel uint) { p.printHeading("Autnum", indentLevel) indentLevel++ p.printValue("Handle", a.Handle, indentLevel) p.printValue("Name", a.Name, indentLevel) p.printValue("Type", a.Type, indentLevel) for _, s := range a.Status { p.printValue("Status", s, indentLevel) } p.printValue("IP Version", a.IPVersion, indentLevel) p.printValue("Country", a.Country, indentLevel) if a.StartAutnum != nil { p.printValue("StartAutnum", strconv.FormatUint(uint64(*a.StartAutnum), 10), indentLevel) } if a.EndAutnum != nil { p.printValue("EndAutnum", strconv.FormatUint(uint64(*a.EndAutnum), 10), indentLevel) } if !p.BriefOutput { for _, c := range a.Conformance { p.printValue("Conformance", c, indentLevel) } } if !p.BriefOutput { p.printValue("Port43", a.Port43, indentLevel) } if !p.BriefOutput || p.OmitNotices { for _, n := range a.Notices { p.printNotice(n, indentLevel) } } if !p.BriefOutput || p.OmitRemarks { for _, r := range a.Remarks { p.printRemark(r, indentLevel) } } for _, l := range a.Links { p.printLink(l, indentLevel) } if !p.BriefOutput { for _, e := range a.Events { p.printEvent(e, indentLevel, false) } } for _, e := range a.Entities { p.printEntity(&e, indentLevel) } p.printUnknowns(a.DecodeData, indentLevel) } func (p *Printer) printNameserver(n *Nameserver, indentLevel uint) { p.printHeading("Nameserver", indentLevel) indentLevel++ p.printValue("Nameserver", n.LDHName, indentLevel) p.printValue("Nameserver (Unicode)", n.UnicodeName, indentLevel) p.printValue("Handle", n.Handle, indentLevel) for _, s := range n.Status { p.printValue("Status", s, indentLevel) } if !p.BriefOutput { p.printValue("Port43", n.Port43, indentLevel) } if !p.BriefOutput { for _, c := range n.Conformance { p.printValue("Conformance", c, indentLevel) } } if !p.BriefOutput || p.OmitNotices { for _, n := range n.Notices { p.printNotice(n, indentLevel) } } if !p.BriefOutput || p.OmitRemarks { for _, r := range n.Remarks { p.printRemark(r, indentLevel) } } for _, l := range n.Links { p.printLink(l, indentLevel) } if !p.BriefOutput { for _, e := range n.Events { p.printEvent(e, indentLevel, false) } } if n.IPAddresses != nil { p.printIPAddressSet(n.IPAddresses, indentLevel) } for _, e := range n.Entities { p.printEntity(&e, indentLevel) } p.printUnknowns(n.DecodeData, indentLevel) } func (p *Printer) printIPAddressSet(s *IPAddressSet, indentLevel uint) { p.printHeading("IP Addresses", indentLevel) indentLevel++ for _, ip := range s.V6 { p.printValue("IPv6", ip, indentLevel) } for _, ip := range s.V4 { p.printValue("IPv4", ip, indentLevel) } p.printUnknowns(s.DecodeData, indentLevel) } func (p *Printer) printEntity(e *Entity, indentLevel uint) { p.printHeading("Entity", indentLevel) indentLevel++ p.printValue("Handle", e.Handle, indentLevel) for _, s := range e.Status { p.printValue("Status", s, indentLevel) } if !p.BriefOutput { p.printValue("Port43", e.Port43, indentLevel) } for _, pid := range e.PublicIDs { p.printPublicID(pid, indentLevel) } if !p.BriefOutput { for _, c := range e.Conformance { p.printValue("Conformance", c, indentLevel) } } if !p.BriefOutput || p.OmitNotices { for _, n := range e.Notices { p.printNotice(n, indentLevel) } } if !p.BriefOutput || p.OmitRemarks { for _, r := range e.Remarks { p.printRemark(r, indentLevel) } } for _, l := range e.Links { p.printLink(l, indentLevel) } if !p.BriefOutput { for _, e := range e.Events { p.printEvent(e, indentLevel, false) } for _, e := range e.AsEventActor { p.printEvent(e, indentLevel, true) } } for _, r := range e.Roles { p.printValue("Role", r, indentLevel) } if e.VCard != nil { for _, property := range e.VCard.Properties { for _, str := range property.Values() { p.printValue("vCard "+property.Name, str, indentLevel) } } } if !p.BriefOutput { for _, ipn := range e.Networks { p.printIPNetwork(&ipn, indentLevel) } for _, asn := range e.Autnums { p.printAutnum(&asn, indentLevel) } for _, e := range e.Entities { p.printEntity(&e, indentLevel) } } p.printUnknowns(e.DecodeData, indentLevel) } func (p *Printer) printIPNetwork(n *IPNetwork, indentLevel uint) { p.printHeading("IP Network", indentLevel) indentLevel++ p.printValue("Handle", n.Handle, indentLevel) p.printValue("Start Address", n.StartAddress, indentLevel) p.printValue("End Address", n.EndAddress, indentLevel) p.printValue("IP Version", n.IPVersion, indentLevel) p.printValue("Name", n.Name, indentLevel) p.printValue("Type", n.Type, indentLevel) p.printValue("Country", n.Country, indentLevel) p.printValue("ParentHandle", n.ParentHandle, indentLevel) for _, s := range n.Status { p.printValue("Status", s, indentLevel) } if !p.BriefOutput { p.printValue("Port43", n.Port43, indentLevel) } if !p.BriefOutput || p.OmitNotices { for _, no := range n.Notices { p.printNotice(no, indentLevel) } } if !p.BriefOutput || p.OmitRemarks { for _, r := range n.Remarks { p.printRemark(r, indentLevel) } } for _, e := range n.Entities { p.printEntity(&e, indentLevel) } for _, l := range n.Links { p.printLink(l, indentLevel) } if !p.BriefOutput { for _, e := range n.Events { p.printEvent(e, indentLevel, false) } } p.printUnknowns(n.DecodeData, indentLevel) } func (p *Printer) printPublicID(pid PublicID, indentLevel uint) { p.printHeading("Public ID", indentLevel) indentLevel++ p.printValue("Type", pid.Type, indentLevel) p.printValue("Identifier", pid.Identifier, indentLevel) p.printUnknowns(pid.DecodeData, indentLevel) } func (p *Printer) printSecureDNS(s *SecureDNS, indentLevel uint) { p.printHeading("Secure DNS", indentLevel) indentLevel++ if s.ZoneSigned != nil { p.printValue("Zone Signed", strconv.FormatBool(*s.ZoneSigned), indentLevel) } if s.DelegationSigned != nil { p.printValue("Delegation Signed", strconv.FormatBool(*s.DelegationSigned), indentLevel) } if s.MaxSigLife != nil { p.printValue("Max Signature Life", strconv.FormatUint(*s.MaxSigLife, 10), indentLevel) } for _, ds := range s.DS { p.printDSData(ds, indentLevel) } for _, key := range s.Keys { p.printKeyData(key, indentLevel) } p.printUnknowns(s.DecodeData, indentLevel) } func (p *Printer) printKeyData(k KeyData, indentLevel uint) { p.printHeading("Key", indentLevel) indentLevel++ if k.Flags != nil { p.printValue("Flags", strconv.FormatUint(uint64(*k.Flags), 10), indentLevel) } if k.Protocol != nil { p.printValue("Protocol", strconv.FormatUint(uint64(*k.Protocol), 10), indentLevel) } if k.Algorithm != nil { p.printValue("Algorithm", strconv.FormatUint(uint64(*k.Algorithm), 10), indentLevel) } p.printValue("Public Key", k.PublicKey, indentLevel) if !p.BriefOutput { for _, e := range k.Events { p.printEvent(e, indentLevel, false) } } for _, l := range k.Links { p.printLink(l, indentLevel) } p.printUnknowns(k.DecodeData, indentLevel) } func (p *Printer) printDSData(d DSData, indentLevel uint) { p.printHeading("DSData", indentLevel) indentLevel++ if d.KeyTag != nil { p.printValue("Key Tag", strconv.FormatUint(uint64(*d.KeyTag), 10), indentLevel) } if d.Algorithm != nil { p.printValue("Algorithm", strconv.FormatUint(uint64(*d.Algorithm), 10), indentLevel) } p.printValue("Digest", d.Digest, indentLevel) if d.DigestType != nil { p.printValue("DigestType", strconv.FormatUint(uint64(*d.DigestType), 10), indentLevel) } if !p.BriefOutput { for _, e := range d.Events { p.printEvent(e, indentLevel, false) } } for _, l := range d.Links { p.printLink(l, indentLevel) } p.printUnknowns(d.DecodeData, indentLevel) } func (p *Printer) printVariant(v Variant, indentLevel uint) { p.printHeading("Variant", indentLevel) indentLevel++ for _, r := range v.Relation { p.printValue("Relation", r, indentLevel) } p.printValue("IDN Table", v.IDNTable, indentLevel) for _, vn := range v.VariantNames { p.printVariantName(vn, indentLevel) } p.printUnknowns(v.DecodeData, indentLevel) } func (p *Printer) printVariantName(vn VariantName, indentLevel uint) { p.printHeading("Variant Name", indentLevel) indentLevel++ p.printValue("Domain Name", vn.LDHName, indentLevel) p.printValue("Domain Name (Unicode)", vn.UnicodeName, indentLevel) p.printUnknowns(vn.DecodeData, indentLevel) } func (p *Printer) printRemark(r Remark, indentLevel uint) { p.printHeading("Remark", indentLevel) indentLevel++ p.printValue("Title", r.Title, indentLevel) p.printValue("Type", r.Type, indentLevel) for _, d := range r.Description { p.printValue("Description", d, indentLevel) } for _, l := range r.Links { p.printLink(l, indentLevel) } p.printUnknowns(r.DecodeData, indentLevel) } func (p *Printer) printNotice(n Notice, indentLevel uint) { p.printHeading("Notice", indentLevel) indentLevel++ p.printValue("Title", n.Title, indentLevel) p.printValue("Type", n.Type, indentLevel) for _, d := range n.Description { p.printValue("Description", d, indentLevel) } for _, l := range n.Links { p.printLink(l, indentLevel) } p.printUnknowns(n.DecodeData, indentLevel) } func (p *Printer) printLink(l Link, indent uint) { if p.BriefLinks { p.printValue("Link", l.Href, indent) return } p.printHeading("Link", indent) indent++ p.printValue("Title", l.Title, indent) p.printValue("Href", l.Href, indent) p.printValue("Value", l.Value, indent) p.printValue("Rel", l.Rel, indent) p.printValue("Media", l.Media, indent) p.printValue("Type", l.Type, indent) for _, h := range l.HrefLang { p.printValue("HrefLang", h, indent) } p.printUnknowns(l.DecodeData, indent) } func (p *Printer) printHeading(heading string, indentLevel uint) { fmt.Fprintf(p.Writer, "%s%s:\n", strings.Repeat(string(p.IndentChar), int(indentLevel*p.IndentSize)), p.cleanString(heading)) } func (p *Printer) printValue(name string, value string, indentLevel uint) { if value == "" { return } fmt.Fprintf(p.Writer, "%s%s: %s\n", strings.Repeat(string(p.IndentChar), int(indentLevel*p.IndentSize)), p.cleanString(name), p.cleanString(value)) } func (p *Printer) printEvent(e Event, indentLevel uint, asEventActor bool) { if p.BriefOutput { return } if asEventActor { p.printHeading("AsEventActor", indentLevel) } else { p.printHeading("Event", indentLevel) } indentLevel++ p.printValue("Action", e.Action, indentLevel) p.printValue("Actor", e.Actor, indentLevel) p.printValue("Date", e.Date, indentLevel) for _, l := range e.Links { p.printLink(l, indentLevel) } p.printUnknowns(e.DecodeData, indentLevel) } func (p *Printer) printUnknowns(d *DecodeData, indentLevel uint) { if d == nil { return } for k, v := range d.values { isKnown, _ := d.isKnown[k] isOverrided, _ := d.overrideKnownValue[k] if !(isKnown && !isOverrided) { p.printUnknown(k, v, indentLevel) } } } func (p *Printer) printUnknown(key string, value interface{}, indentLevel uint) { switch value.(type) { case bool: p.printValue(key, strconv.FormatBool(value.(bool)), indentLevel) case float64: p.printValue(key, strconv.FormatFloat(value.(float64), 'f', -1, 64), indentLevel) case string: p.printValue(key, value.(string), indentLevel) case []interface{}: for _, value2 := range value.([]interface{}) { p.printUnknown(key, value2, indentLevel) } case map[string]interface{}: p.printHeading(key, indentLevel) indentLevel++ for key2, value2 := range value.(map[string]interface{}) { p.printUnknown(key2, value2, indentLevel) } default: p.printValue(key, "[unprintable value]", indentLevel) } } func (p *Printer) cleanString(str string) string { return strings.Map(removeBadRunes, str) } func removeBadRunes(r rune) rune { switch r { case '\n', '\r', '\000': return -1 default: return r } } rdap-0.9.1/print_test.go000066400000000000000000000010701474227142300151750ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap import ( "testing" "github.com/openrdap/rdap/test" ) func TestPrintDomain(t *testing.T) { obj := loadObject("rdap/rdap.nic.cz/domain-example.cz.json") printer := &Printer{ BriefLinks: true, } _ = obj _ = printer //printer.Print(obj) } func loadObject(filename string) RDAPObject { jsonBlob := test.LoadFile(filename) d := NewDecoder([]byte(jsonBlob)) result, err := d.Decode() if err != nil { panic("Decode unexpectedly failed") } return result } rdap-0.9.1/request.go000066400000000000000000000317731474227142300145070ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap import ( "context" "fmt" "net" "net/url" "strconv" "strings" "time" ) // A RequestType specifies an RDAP request type. type RequestType uint8 const ( _ RequestType = iota AutnumRequest DomainRequest EntityRequest HelpRequest IPRequest NameserverRequest DomainSearchRequest DomainSearchByNameserverRequest DomainSearchByNameserverIPRequest NameserverSearchRequest NameserverSearchByNameserverIPRequest EntitySearchRequest EntitySearchByHandleRequest // RawRequest is a request with a fixed RDAP URL. RawRequest ) // String returns the RequestType as a string. // e.g. "autnum", "domain", "help". func (r RequestType) String() string { switch r { case AutnumRequest: return "autnum" case DomainRequest: return "domain" case EntityRequest: return "entity" case HelpRequest: return "help" case IPRequest: return "ip" case NameserverRequest: return "nameserver" case DomainSearchRequest: return "domain-search" case DomainSearchByNameserverRequest: return "domain-search-by-nameserver" case DomainSearchByNameserverIPRequest: return "domain-search-by-nameserver-ip" case NameserverSearchRequest: return "nameserver-search" case NameserverSearchByNameserverIPRequest: return "nameserver-search-by-ip" case EntitySearchRequest: return "entity-search" case EntitySearchByHandleRequest: return "entity-search-by-handle" case RawRequest: return "url" default: panic("Unknown RequestType") } } // A Request represents an RDAP request. // // req := &rdap.Request{ // Type: rdap.DomainRequest, // Query: "example.cz", // } // // RDAP supports many request types. These are: // // RequestType | Bootstrapped? | HTTP request path | Example Query // -------------------------------------------+---------------+-------------------------+---------------- // rdap.AutnumRequest | Yes | autnum/QUERY | AS2846 // rdap.DomainRequest | Yes | domain/QUERY | example.cz // rdap.EntityRequest | Experimental | entity/QUERY | 86860670-VRSN // rdap.HelpRequest | No | help | N/A // rdap.IPRequest | Yes | ip/QUERY | 2001:db8::1 // rdap.NameserverRequest | No | nameserver/QUERY | ns1.skip.org // | | | // rdap.DomainSearchRequest | No | domains?name=QUERY | exampl*.com // rdap.DomainSearchByNameserverRequest | No | domains?nsLdhName=QUERY | ns1.exampl*.com // rdap.DomainSearchByNameserverIPRequest | No | domains?nsIp=QUERY | 192.0.2.0 // rdap.NameserverSearchRequest | No | nameservers?name=QUERY | ns1.exampl*.com // rdap.NameserverSearchByNameserverIPRequest | No | nameservers?ip=QUERY | 192.0.2.0 // rdap.EntitySearchRequest | No | entities?fn=QUERY | ABC*-VRSN // rdap.EntitySearchByHandleRequest | No | entities?handle=QUERY | ABC*-VRSN // | | | // rdap.RawRequest | N/A | N/A | N/A // // See https://tools.ietf.org/html/rfc7482 for more information on RDAP request // types. // // Requests are executed by a Client. To execute a Request, an RDAP server is // required. The servers for Autnum, IP, and Domain queries are determined // automatically via bootstrapping (a lookup at https://data.iana.org/rdap/). // // For other Request types, you must specify the RDAP server: // // // Nameserver query on rdap.nic.cz. // server, _ := url.Parse("https://rdap.nic.cz") // req := &rdap.Request{ // Type: rdap.NameserverRequest, // Query: "a.ns.nic.cz", // // Server: server, // } // // RawRequest is a special case for existing RDAP request URLs: // rdapURL, _ := url.Parse("https://rdap.example/mystery/query?ip=192.0.2.0") // req := &rdap.Request{ // Type: rdap.RawRequest, // Server: rdapURL, // } type Request struct { // Request type. Type RequestType // Request query text. Query string // Optional URL query parameters to include in the RDAP request. // // These are added to the URL returned by URL(). Params url.Values // Optional RDAP server URL. // // If present, specifies the RDAP server to execute the Request on. // Otherwise, nil enables bootstrapping. // // For Type=RawRequest, this specifies the full RDAP URL instead (with the // Query/Params fields not used). Server *url.URL // Optional list of contact roles. This enables additional HTTP requests for // these contact roles, to obtain full contact information. // // The common WHOIS contact roles are "registrant", "administrative", and // "billing". // // RDAP responses may contain full contact information (such as domain // registrant name & address), or just a URL to it. For convenience, // applications may prefer to receive the full contact information. // // The FetchRoles option enables additional HTTP requests for contact // information. Additional HTTP requests are made for URL-only contact roles // matching the FetchRoles list. Additional information is then merged into // the Response. // // Specify a list of contact roles for which additional HTTP requests may be // made. The default is no extra fetches. Use the special string "all" to // fetch all available contact information. FetchRoles []string // Maximum request duration before timeout. // // The default is no timeout. Timeout time.Duration ctx context.Context } func (r *Request) pathAndValues() (string, url.Values) { path := "" values := url.Values{} switch r.Type { case AutnumRequest: path = fmt.Sprintf("autnum/%s", escapePath(r.Query)) case DomainRequest: path = fmt.Sprintf("domain/%s", escapePath(r.Query)) case EntityRequest: path = fmt.Sprintf("entity/%s", escapePath(r.Query)) case HelpRequest: path = "help" case IPRequest: // TODO: escape IP address/nets? path = fmt.Sprintf("ip/%s", r.Query) case NameserverRequest: path = fmt.Sprintf("nameserver/%s", escapePath(r.Query)) case DomainSearchRequest: path = "domains" values["name"] = []string{r.Query} case DomainSearchByNameserverRequest: path = "domains" values["nsLdhName"] = []string{r.Query} case DomainSearchByNameserverIPRequest: path = "domains" values["nsIp"] = []string{r.Query} case NameserverSearchRequest: path = "nameservers" values["name"] = []string{r.Query} case NameserverSearchByNameserverIPRequest: path = "nameservers" values["ip"] = []string{r.Query} case EntitySearchRequest: path = "entities" values["fn"] = []string{r.Query} case EntitySearchByHandleRequest: path = "entities" values["handle"] = []string{r.Query} case RawRequest: // Server URL(s) are the entire request. default: panic("unknown QueryType") } return path, values } // URL constructs and returns the RDAP Request URL. // // As an example: // server, _ := url.Parse("https://rdap.nic.cz") // req := &rdap.Request{ // Type: rdap.NameserverRequest, // Query: "a.ns.nic.cz", // // Server: server, // } // // fmt.Println(req.URL()) // Prints https://rdap.nic.cz/nameserver/a.ns.nic.cz. // // Returns nil if the Server field is nil. // // For Type=RawRequest, the Server field is returned unmodified. func (r *Request) URL() *url.URL { if r.Server == nil { return nil } path, values := r.pathAndValues() var resultURL *url.URL if r.Type == RawRequest { resultURL = new(url.URL) *resultURL = *r.Server } else { tempURL := &*r.Server tempURL.RawQuery = "" tempURL.Fragment = "" tempURLString := tempURL.String() if len(tempURLString) == 0 || tempURLString[len(tempURLString)-1] != '/' { tempURLString += "/" } tempURLString += path var err error resultURL, err = url.Parse(tempURLString) if err != nil { return nil } query := r.Server.Query() for k, v := range r.Params { query[k] = v } for k, v := range values { query[k] = v } resultURL.RawQuery = query.Encode() resultURL.Fragment = r.Server.Fragment } return resultURL } // WithContext returns a copy of the Request, with Context |ctx|. func (r *Request) WithContext(ctx context.Context) *Request { r2 := new(Request) *r2 = *r r2.ctx = ctx return r2 } // Context returns the Request's context. // // The returned context is always non-nil; it defaults to the background context. func (r *Request) Context() context.Context { if r.ctx == nil { return context.Background() } return r.ctx } // WithServer returns a copy of the Request, with the Server set to |server|. func (r *Request) WithServer(server *url.URL) *Request { r2 := new(Request) *r2 = *r r2.Server = server return r2 } func escapePath(text string) string { var escaped []byte for i := 0; i < len(text); i++ { b := text[i] if !shouldPathEscape(b) { escaped = append(escaped, b) } else { escaped = append(escaped, '%', "0123456789ABCDEF"[b>>4], "0123456789ABCDEF"[b&0xF], ) } } return string(escaped) } func shouldPathEscape(b byte) bool { if ('A' <= b && b <= 'Z') || ('a' <= b && b <= 'z') || ('0' <= b && b <= '9') { return false } switch b { case '-', '_', '.', '~', '$', '&', '+', ':', '=', '@': return false } return true } // NewHelpRequest creates a new help Request. // // The RDAP server must be specified. func NewHelpRequest() *Request { return &Request{ Type: HelpRequest, } } // NewAutnumRequest creates a new Request for the AS number |asn|. func NewAutnumRequest(asn uint32) *Request { return &Request{ Type: AutnumRequest, Query: fmt.Sprintf("%d", asn), } } // NewIPRequest creates a new Request for the IP address |ip|. func NewIPRequest(ip net.IP) *Request { return &Request{ Type: IPRequest, Query: ip.String(), } } // NewIPNetRequest creates a new Request for the IP network |net|. func NewIPNetRequest(net *net.IPNet) *Request { return &Request{ Type: IPRequest, Query: net.String(), } } // NewDomainRequest creates a new Request for the domain name |domain|. func NewDomainRequest(domain string) *Request { return &Request{ Type: DomainRequest, Query: domain, } } // NewEntityRequest creates a new Request for the entity name |entity|. // // The RDAP server must be specified. func NewEntityRequest(entity string) *Request { return &Request{ Type: EntityRequest, Query: entity, } } // NewNameserverRequest creates a new Request for the nameserver |nameserver|. // // The RDAP server must be specified. func NewNameserverRequest(nameserver string) *Request { return &Request{ Type: NameserverRequest, Query: nameserver, } } // NewRawRequest creates a Request from the URL |rdapURL|. // // When a client executes the Request, it will fetch |rdapURL|. func NewRawRequest(rdapURL *url.URL) *Request { return &Request{ Type: RawRequest, Server: rdapURL, } } // NewRequest creates a new Request with type |requestType| and |query| text. // // Depending on the |requestType|, the RDAP server may need to be specified. func NewRequest(requestType RequestType, query string) *Request { return &Request{ Type: requestType, Query: query, } } // NewAutoRequest creates a Request by guessing the type required for |queryText|. // // The following types are suppported: // - RawRequest - e.g. https://example.com/domain/example2.com // - DomainRequest - e.g. example.com, https://example.com, http://example.com/ // - IPRequest - e.g. 192.0.2.0, 2001:db8::, 192.0.2.0/24, 2001:db8::/128 // - AutnumRequest - e.g. AS2856, 5400 // - EntityRequest - all other queries. // // Returns a Request. Use r.Type to find the RequestType chosen. func NewAutoRequest(queryText string) *Request { // Full RDAP URL? fullURL, err := url.Parse(queryText) if err == nil && (fullURL.Scheme == "http" || fullURL.Scheme == "https") { // Parse "http://example.com/" as a domain query for convenience. if fullURL.Path == "" || fullURL.Path == "/" { return NewDomainRequest(fullURL.Host) } return NewRawRequest(fullURL) } // IP address? ip := net.ParseIP(queryText) if ip != nil { return NewIPRequest(ip) } // IP network? _, ipNet, err := net.ParseCIDR(queryText) if ipNet != nil { return NewIPNetRequest(ipNet) } // AS number? (formats: AS1234, as1234, 1234). autnum, err := parseAutnum(queryText) if err == nil { return NewAutnumRequest(autnum) } // Looks like a domain name? if strings.Contains(queryText, ".") { return NewDomainRequest(queryText) } // Otherwise call it an entity query. return NewEntityRequest(queryText) } func parseAutnum(autnum string) (uint32, error) { autnum = strings.ToUpper(autnum) autnum = strings.TrimPrefix(autnum, "AS") result, err := strconv.ParseUint(autnum, 10, 32) if err != nil { return 0, err } return uint32(result), nil } rdap-0.9.1/request_test.go000066400000000000000000000122551474227142300155400ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap import ( "net" "net/url" "testing" ) const ( ExampleServer = "https://test.rdap.example/rdap" ) func testRequestURL(t *testing.T, r *Request, path string) { expectedURL := ExampleServer + "/" + path server, _ := url.Parse(ExampleServer) r2 := r.WithServer(server) actualURL := r2.URL() if actualURL == nil { t.Errorf("Nil URL\n") return } if actualURL.String() != expectedURL { t.Errorf("Got URL %s, expected %s\n", actualURL.String(), expectedURL) return } } func TestNewAutnumRequest(t *testing.T) { r := NewAutnumRequest(123456) testRequestURL(t, r, "autnum/123456") } func TestNewIPv4Request(t *testing.T) { r := NewIPRequest(net.ParseIP("192.0.2.0")) testRequestURL(t, r, "ip/192.0.2.0") } func TestNewIPv6Request(t *testing.T) { r := NewIPRequest(net.ParseIP("2001:DB8::a")) testRequestURL(t, r, "ip/2001:db8::a") } func TestNewIPv4NetRequest(t *testing.T) { _, ipNet, _ := net.ParseCIDR("192.0.2.0/24") r := NewIPNetRequest(ipNet) testRequestURL(t, r, "ip/192.0.2.0/24") } func TestNewIPv6NetRequest(t *testing.T) { _, ipNet, _ := net.ParseCIDR("2001:DB8::1/128") r := NewIPNetRequest(ipNet) testRequestURL(t, r, "ip/2001:db8::1/128") } func TestNewNameserverRequest(t *testing.T) { r := NewNameserverRequest("ns.example") testRequestURL(t, r, "nameserver/ns.example") } func TestNewDomainRequest(t *testing.T) { tests := []struct { Query string ExpectedPath string }{ {"example.com", "domain/example.com"}, {"example/../com", "domain/example%2F..%2Fcom"}, } for _, test := range tests { r := NewDomainRequest(test.Query) testRequestURL(t, r, test.ExpectedPath) } } func TestNewEntityRequest(t *testing.T) { tests := []struct { Query string ExpectedPath string }{ {"MY-HANDLE", "entity/MY-HANDLE"}, {"MY-HANDLE/../com", "entity/MY-HANDLE%2F..%2Fcom"}, } for _, test := range tests { r := NewEntityRequest(test.Query) testRequestURL(t, r, test.ExpectedPath) } } func TestNewHelpRequest(t *testing.T) { r := NewHelpRequest() testRequestURL(t, r, "help") } func TestNewRawRequest(t *testing.T) { urlString := "https://example.com/domain/example.com" url, _ := url.Parse(urlString) r := NewRawRequest(url) actualURL := r.URL() if actualURL.String() != urlString { t.Errorf("Raw query for %s got %s, expected %s\n", urlString, actualURL.String(), urlString, ) } } func TestNewSearchRequests(t *testing.T) { tests := []struct { RequestType RequestType Query string ExpectedPath string }{ { DomainSearchRequest, "example*.com&x=1", "domains?name=example%2A.com%26x%3D1", }, { DomainSearchByNameserverRequest, "example*.com&x=1", "domains?nsLdhName=example%2A.com%26x%3D1", }, { DomainSearchByNameserverIPRequest, "192.0.2.*.com&x=1", "domains?nsIp=192.0.2.%2A.com%26x%3D1", }, { NameserverSearchRequest, "ns1.example*.com&x=1", "nameservers?name=ns1.example%2A.com%26x%3D1", }, { NameserverSearchByNameserverIPRequest, "192.0.2.*.com&x=1", "nameservers?ip=192.0.2.%2A.com%26x%3D1", }, { EntitySearchRequest, "MY-FN*&x=1", "entities?fn=MY-FN%2A%26x%3D1", }, { EntitySearchByHandleRequest, "MY-HANDLE*&x=1", "entities?handle=MY-HANDLE%2A%26x%3D1", }, } for _, test := range tests { r := NewRequest(test.RequestType, test.Query) testRequestURL(t, r, test.ExpectedPath) } } func TestRequestURLConstruction(t *testing.T) { tests := []struct { Server string Domain string ExpectedURL string }{ {"http://example.com", "example.org", "http://example.com/domain/example.org"}, {"http://example.com/", "example.org", "http://example.com/domain/example.org"}, {"http://example.com/1/", "example.org", "http://example.com/1/domain/example.org"}, {"http://example.com/1/", "example.org/", "http://example.com/1/domain/example.org%2F"}, {"https://example.com/x/", "example.com*.&x=1?z=1", "https://example.com/x/domain/example.com%2A.&x=1%3Fz=1"}, } for _, test := range tests { server, _ := url.Parse(test.Server) r := NewDomainRequest(test.Domain) r2 := r.WithServer(server) actualURL := r2.URL() if actualURL == nil { t.Errorf("nil url") } else if actualURL.String() != test.ExpectedURL { t.Errorf("Got URL %s, expected %s\n", actualURL.String(), test.ExpectedURL) } } } func TestNewAutoRequest(t *testing.T) { tests := []struct { Query string ExpectedType RequestType }{ {"http://example.com/", DomainRequest}, {"https://example.com", DomainRequest}, {"http://example.com/domain/example.com", RawRequest}, {"https://example.com/domain/example.com", RawRequest}, {"192.0.2.0", IPRequest}, {"192.0.2.0/24", IPRequest}, {"2001:db8::", IPRequest}, {"2001:db8::/128", IPRequest}, {"AS1", AutnumRequest}, {"as12", AutnumRequest}, {"aS123", AutnumRequest}, {"1234", AutnumRequest}, {"example.com", DomainRequest}, {"example", EntityRequest}, } for _, test := range tests { r := NewAutoRequest(test.Query) if r.Type != test.ExpectedType { t.Errorf("AutoQuery detected %s as type %s, expected %s\n", r.Query, r.Type, test.ExpectedType) } } } rdap-0.9.1/response.go000066400000000000000000000061031474227142300146420ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap import ( "net/http" "time" "github.com/openrdap/rdap/bootstrap" ) type Response struct { Object RDAPObject BootstrapAnswer *bootstrap.Answer HTTP []*HTTPResponse } type RDAPObject interface{} type HTTPResponse struct { URL string Response *http.Response Body []byte Error error Duration time.Duration } type WhoisStyleResponse struct { KeyDisplayOrder []string Data map[string][]string } func (w *WhoisStyleResponse) add(key string, value string) { if value == "" { return } if _, exists := w.Data[key]; !exists { w.KeyDisplayOrder = append(w.KeyDisplayOrder, key) w.Data[key] = []string{value} } else { w.Data[key] = append(w.Data[key], value) } } func newWhoisStyleResponse() *WhoisStyleResponse { w := &WhoisStyleResponse{} w.Data = make(map[string][]string) return w } func (r *Response) ToWhoisStyleResponse() *WhoisStyleResponse { w := newWhoisStyleResponse() // Only support domain whois so far. d, ok := r.Object.(*Domain) if !ok { return w } // "Domain Name" w.add("Domain Name", d.LDHName) // Registry Domain ID w.add("Handle", d.Handle) // "Registrar WHOIS Server" w.add("Registrar WHOIS Server", d.Port43) // Events. for _, e := range d.Events { switch e.Action { case "last changed": w.add("Updated Date", e.Date) case "registration": w.add("Creation Date", e.Date) case "expiration": w.add("Expiration Date", e.Date) } } // Registrar fields. registrar := findFirstEntity("registrar", d.Entities) if registrar != nil { vcard := registrar.VCard if vcard != nil { // "Registrar" w.add("Registrar", vcard.Name()) } // "Registrar IANA ID" for _, id := range registrar.PublicIDs { if id.Type == "IANA Registrar ID" { w.add("Registrar IANA ID", id.Identifier) } } // "Registrar Abuse Contact Email" // "Registrar Abuse Contact Phone" } // "Domain Status" for _, s := range d.Status { w.add("Domain Status", s) } addEntityFields(w, "Registrant", findFirstEntity("registrant", d.Entities)) addEntityFields(w, "Admin", findFirstEntity("administrative", d.Entities)) addEntityFields(w, "Tech", findFirstEntity("technical", d.Entities)) addEntityFields(w, "Abuse", findFirstEntity("abuse", d.Entities)) // "Name Server" for _, n := range d.Nameservers { w.add("Name Server", n.LDHName) } return w } func addEntityFields(w *WhoisStyleResponse, t string, e *Entity) { if e == nil { return } v := e.VCard if v == nil { return } w.add(t+" Name", v.Name()) w.add(t+" PO Box", v.POBox()) w.add(t+" Extended Address", v.ExtendedAddress()) w.add(t+" Street", v.StreetAddress()) w.add(t+" Locality", v.Locality()) w.add(t+" Post Code", v.PostalCode()) w.add(t+" Country", v.Country()) w.add(t+" Tel", v.Tel()) w.add(t+" Fax", v.Fax()) w.add(t+" Email", v.Email()) } func findFirstEntity(role string, entities []Entity) *Entity { for _, e := range entities { for _, r := range e.Roles { if r == role { return &e } } } return nil } rdap-0.9.1/sandbox/000077500000000000000000000000001474227142300141135ustar00rootroot00000000000000rdap-0.9.1/sandbox/DigiCert_RDAP_Pilot_Client_Certificate.p12000066400000000000000000000054171474227142300237150ustar00rootroot000000000000000‚ 0‚ Õ *†H†÷  ‚ Æ‚ Â0‚ ¾0‚ *†H†÷  ‚0‚0‚ý *†H†÷ 0 *†H†÷  0`›S= ôOD€‚Ð×ß<4HòÙ¢Û‚º¤Ql¥Kp€ëÓtfm6ÍÒ°rÒ)¸DFŸÕoUÝ 2¶}áÖuª@ tðêè™7å²>†®n†D6‡¦#©^Ù 2GQ5Êf®nRF9¶XMƒžK<´ïÇ $6e|U.’µè³¼F‘(- d±dm7Õ­œç`2Õ&æ÷A‹ÆŽ“ÿ>ý,N¢PC 4FSD&Öå\9ù:"_IûÖ‚Y­4&µ06<¬ÏQ8oxÖ‡K§WKt÷Z©hB¦«.Zn`‰A©Í¬YÛÉ Fü•GDc93q7ŒW·kÐê¸ùËxÆq ôkýýÓ`Oœ7ÿ|Û•tÜA L;­‚'¤_kõìÅ“VáÍÝnMÞS‡LÎãÁ+‹\®™'[ë0²åH¿ƒíWá)p7ïèéÂ×XaäA¼Ëp=e¦KÂfG‚ñcþWÝR¦þ¢€k­f-’¦ù5,{vü²UŽ‘±‰ÆG–À n Y»Ü»]4NL”7 ¬Y¹åÿ‹‹VƒŠ'†àÛDìyÙ ¦ Ý}âIÀÔα;ÎóÂWfbû|WµÉ[‰\ ›¼×<û79V!–º;Ü.œ§§í_ébwm/À‘V`ý=:¥Š¢½;gÔ´­CƒÄ Uâ@ËuË£N!½÷Ñ„†?Õó/Ž™H•‚c|«×_ðISÿ¯(W¸©SÌä’Î ­ùBgŸeUàÙ͆ÅOëmÊ0ˆµISqeÚ½T…HI »ï0Mhˆ2ÆSúdÀ™ÝäØÊ›)ÓLwWÅ.E •!Ê.Ô®Ù.}%¾óŸ,M­B€ïæÆ­ÞMP23Ó9EÚNlG¨*À[AªÝâ¨2¸sïy@qèàê>@Ýrj€_tTY &‰ùGÊÄ­Њïq ÓüÝRSØê詆Æ7Hd„å³1«â×Zè§yPLifq¢œ©2!BN Ø|5‡í®¥Rï@'d¹Â¢Ý6: 䘆À—þåÿ›¢¾‚JÀµjJ¡…¸W¦½õ× 8»h©!OÐt t¦{ÂfJr¼”p‰£wÞuúqÀ–©á‚u¦Rû\EÜû·§ëx õº_Ý YcÒâ^Q³;­ÅÑ$,­¿ìãê7Ââ€^Â…“6ñà¿JxhPhkGBƹ¼6EÍèd§¢hîP²aîvœðéÔL˜*0+Œdás D4¼£¬òÍ|x´J§”ù“"_æ®Ð§%ÕÈÝ•^NEùßÃ#ß‚¸Ö.ä¬Å–:÷øx0‚Ÿ *†H†÷  ‚‚Œ0‚ˆ0‚„ *†H†÷   ‚î0‚ê0 *†H†÷  0.sr–5É'‚È ™Ù Ç4ñxÕ-ëùºÑ‚¨°ª¹äS«e¦¹tuüH€ËÉç/ɵNÎ*sùÒ _S#jDÅ7Ñš-\¹¡„b`“^œFªÞÐâNLNÌ¥øÏEù9gÎQ§fÝ~!|åŽ26uÒNÒŵh^=uÂióg‹®îNÔv¾ò¢l6ý; 0 ,õ®^£¸Á¬_ªÜÿ€­€Ðú49ŠïôPÝ‚G)‡Ž¤IUÞ 1ys–ËA‘p­h²„üa!Ê|•Eli‰éß¡ÊՅ瘟Ðs‡%ÞA’~ÐD™¯÷k¨uT’Ùì©ÃÜc=æMÅ(ê\L즪±vˆÜhœ¾k‚þ´'Yzb‘­¯0Â>õ’ÐTW+®^‹9…/.±„ÎgÎàòyë¸ÙŸ¾¡;Gûùò®˜ÜõÔ^ÂáUŒãç2c"ïiõˆºo$Zë>ó,3Õ p⪖uÖßEpÊ ÄÁM[_a”t@Í }™Ã0ãÿàb–˜o.µJ«ñk–¶yÕ6àÆ7Z´Ý¨pü}›¿/œ±—ac\â5’LL@Ãg"G§wgÝO\m›/Óf»DUš«±ÇTLìÔ Æ€t¿ ±i¢û=â¦[d~e‚í¡¨ìep!ÃÊù¢-6µÅêþìÑÒ§Àþ²ü"{&˜=ÏšGXªó8é´Ý s ‰6­É|ÛY¿yPGB· \ÿ³¨1Bÿ/>èYíçÂø9­4Àâë³JF¥hõ­S¬H´:®ÁhæÖ!$©¥W¦ÈÜ¡“‚ßÄ@PN¾± 5&ç€è£øå6Þ6£~ Q%RBnµ‚„"Á ÷Zea ,2þðZ]†³ù²4MOëYÈKYI‹Ìa­5…²QF­³÷ÂùaET¶ž#¹Z0ŒˆD)”ábªlœñpî5öX|6dÍùa*¸ë$·¹•érý&3›ºº!='™©œ±M[î/ï¡ÞW…GOš3Î'YsœsQeä¡#ßW‹ô‰Š¨‡ºàÒÜÝŠq*EÁCWç J ¬¤Ý*g¨räpDÁ-G‰¼tu?N‡Ô÷·" iš‚àI Œ¯È”ŒàÓbÂ¥6WƒàöŒOžÔÃ`J/Ϋ^ãÌ÷_à;_ž §Õ®î,¦‡2þ£…Âß_³º¤äLl;‡){í/žá’„"­K†vÁ¶ Ý•# \yÚ?l>sX€Âbry¼}±6O®ñ.ãýÈyš1º²,ñPp2‰n`­AîÒšïÛÔÖ¶D P&ÝüxÚü3ëågúVU@®݃F”#÷ˆz剪1æìÝGb)°Ñ©YO”vUÛ'­ŸI+ O´±ôµ;õ|ã¤Xà–ø­n޹Êð1’+±ÚV¸B±2%ߥ"Õ‹e<“°òTjt0z7ÁxÉœq~xÅÔ¦›¶¿W|tÉsÓj²!Ç1‚0# *†H†÷  1âÖrÐ4N6ƒ (8x¹Ël0[ *†H†÷  1NLDigiCert RDAP Pilot Client Certificate0-0!0 +Ös†5ÆÙýc0¶ÛÜ'wÆ:O7)s ÄÉ6»Érdap-0.9.1/sandbox/DigiCert_RDAP_Pilot_Client_Certificate_Expired.p12000066400000000000000000000054671474227142300254020ustar00rootroot000000000000000‚ 30‚ ý *†H†÷  ‚ î‚ ê0‚ æ0‚/ *†H†÷  ‚ 0‚0‚ *†H†÷ 0 *†H†÷  0 |z_06õ쀂èý[ssso^êI?}ÞÛ9ã– B3æi€ÓÖQ£œkî“Cª8b¹À©WHÏäsä“oИšõñ‹—'îÈÔ‡§-ßÜæäZüàÿ>Œ·}£§'ÒÌd½ÐBq4=[>—Øù>D!áb†Gv.$ˆxm/´[ Chi Þ:Ó®m>)Èqv« ªÕÏÂd#Tu…<ž¡¤ìˆù¦J£cm<6ÛPÿÉø2Ôˆ \Ã\UàrN>Š-Ô°È‘–òÐ'fH`arÏ z*ÕÛ‹lÀ‡ÁQ…&]E€O`Ô³±Ö Þ—$ –Ú®sÑ‘õÄòª’óo†çõrª!À’“l™«P0U6š¼ÛÒ ü aáYÈóÛç6¤#$ÔcÒ -š:½šö£ œúoì1MÑo’æììÒàè3/żÚ&ëªynŸâ~$D[‹¿ŽlM&øÏ ltiT3¼ Ì;r°î#ÊDÿý––D^-…«õö¿b‘\–‘y]t©Ž]aEÓ£üÉ…¾’dÖßä¤K%°- ŠÃØæc‘üOhÍîïÄùðÿ6âbz¾"¬xZ2?w’(•@2˜YcƒQ&\ÿ¯#âC_Qö°†4SÚ0þô/À⪠s78·5J‘s 8ªsàÖ»¯%fJÇÆ0Ñ»µžÙDZA¸m2Ôr,€xâdõ³5å†<žÑ2ÿû÷²ç” h°ãv3õ*v6ä,¾ÁÁ¼ÄûéH U"•í—iƒèIã\ûÖ銰‰ІÕ8ÖÚcžŠ­ÔDoGǦ~ ÕÄi0Šù<Õ¢|y¹»Ü¾½1=çœÜï,3ûFáþhyŽYòI”e· ]ÅNŽ–ò€ØÆ}àæMQlLÃ#ÒsÄRJ¶L€½ES´òhü *]:àêP¤ÝbàxpD|oþ˜Û—ú¡¿<Ÿé¼„ŠLá@Á¢ñ‹äËö„›JúaoÒý¡$·úSCïLTþö¡õæ/ñ(éTÀ&þW2“y'3Ù‚Ä«UIåý¯´§7,'<×½`¯6‡:TOïîõ•‘ŒcÉkNñDbŸ¬8ù]ÂD¤…2¾K^šqŒ÷ºzàW EÍ~vÄÆ¸[¶§jå, Ø7›ç ›Â8ê°WMûlåÊQ³Î–öMÔ÷ ÄåÍåÇþê8,¥ÕG “‚‹Iï¯ÆÒPbû!tjÁ¹çPç*ŒI'ÿy¦p¶wÒ¸'ý6?ÆË):­4Aœ,K!ŠïR³kq ûGÕµ_À ~K¶:#LjÜù€*Æüo^D¸÷Œ h Z€¢6»i1²ÌSYÝ9ØVI+Fî  ûkLìt’ôu_Q ±õ15P“-®Æ"}?¬ÔQF/ûO ÉÆÛó“—õ9ú¾frâo·ýYép9(Vå´ïšßâ-„”ÿHSƒ(H9Zb^…o‚fí@·:8.M2 ê- ’¾7.žÆ³oÅPS?•½ ÈDZ¦Gd$;üÍPB1žäQ'¦U‘ÉU2Àþ_UóQýËì0s£jtñ!@Ob«FêZ!nݹ`‚F _#1­T €¨ªžåŒ°™é¾õ–ê_ŸÌïa(Çr“Áý!fÚæ¬ +—æWvL[¼GzÍãø0‚¯ *†H†÷  ‚ ‚œ0‚˜0‚” *†H†÷   ‚î0‚ê0 *†H†÷  0ÃaÛ‰˜©Ç‚ÈýðÜþϦR29‚3Í+ƒ¾mÅ'8ÅÛMñ aèf(p¢âÊì¦gh Z¼²øc˜]K¼²º±*áP/¼´R…ûê]u§ºÈí_eBWÐÐ>ªÞ™’Çä8xi'jÄ«ã}|Í3Joìû€ñ¦BØD)+9Ix f£­l<º:ØhÕÌÇ#¦†Så¡XQ40¿þì%iÅÅÙK@‡2YçªAþñ±:í ÚN÷Úe“ŒPè¨|1P‹é҇閶òL†XnOñ+õZMf€KôÛ N œ1 ,ψˆüä6”îë’ü Û¾h0Ú‰Ûæ|&”vªÚmw?z‹_–n4š1ÖbéJ_ïaKùZdû_`;YƒawÚ£¦™ëœ©€qØbVâGç ç¦sžƒ+$™ø¹)±ú¦-]›CWàÐ1žP*_‰G6ô!]‹n·´»ÀùÏÇÿX~O"ÔŠ[Þ¬·€¥†oÄ‹ô¨‰¬¿Ø›ü¢¦“24žõæ¼gÎíKö$êÎx˜ëðû¬x©²›zMMÀ'EÔÖy¸Ùà­tb©Þï²Ö1LÆ.j™ë¸Oñ¸]9oÑš‘ŒVÓ:‚ÍYõSs Ç»1ïø¬.…_‹v°;> ¤Ud±Y™(øñD²EAr‹Ÿ‘÷­•ßçguÈX滦§Î båÌ2&\¤m€¼õÚh ¥™ïCx5œÓÇ:EùÀí²u„¤†ÜŽV”8ü³¡Ôi^®¸ùè~ðÒlÒ`bô¡ lbج…Ü!Ž63Ccw8UÖFhȇh–gû.ášÚžYe˜“Ëè©/>¬¸‡±7"4Z¥8à˜îNf)¹š\4Cøá¸Éu=ñœuê¨`]S¤eò Å¿ºƒ/W$°Ù$j´oÖhdÈëï¡©Û Ly¡rB ›¢mÞ[tâ] ™º$ Ýo¦W@| À:¬—¢ÆÜ¶€ØkEX—h+ÞÊqÕÊpËí£ÖŒ$']©Ó»É¬A‘5i9— è¢mN~!á„M'B¹Ðyp˜ÇŒ ,„cô¡†>¥Mr·©F0ú ÑRõ­ÂP`,ÿñ, "ùööìX˜!â,WxQd²{üæ'û*ãé·ÙñýHn€«kùX k¦]0ò´¼>½w®ßBÍ©0¨!¡ùskG£Í1™?õZ¥ÓÓ•A _—´Î>'/\ìÖÞ¶áI¤à÷oÉRKóЗoG¨Û€•À¤¯÷êëóÿêÒá”°ãdÏ_«عq¥âaàèD5€•NìØºt…úË'–æž ªwµÑ£YÁãœì^[¨Ö»¯».æéh½”Àÿ—í³¬gö6i£©±/Žˆ¸y)»ûøûtä<¯‹ÊYµÄ:6¶ ­Ž{fzDí­ 6U7{¯wÀ…=å™mï„@†Á†.öÇE®r°gKž‘–nZR™Z` Ú@µ‘_]‘7óÒ tfjüìExq€á Ö•¨ªõqÙ7Ï,–m¸{H¤ÝE±ÉšGˆ©‹õ•äѺ>ôÓã™$gCŽ`=Œ|"óÆÆiÛ.o®0®BÒGÎC¹ÃPÕŒ^:ÐxûR&YabAÜ—{§‡Ÿ7 G­#ᢡÌüôçÜçN¥«Ã>ôÃ3=¤O2€«·„lÂ(D´ýd¯v£œ¦§ª_[‰^¿h@Ä•N€{…4j ŸÆ©€û ËÍ­ à†Â%Rv²V7Ë^- ø(žnxHåÁ2ï3+4¸Ó³aÑÌ~%È•ÎT‡µ}{£qCÅÔBíСþ–vÙÞ©,YEîøZ)›¼¦Ô÷ÿTX*³Ójx”òseÕ¶ÃîQ`É…Ÿ{Ã.‹ Ífº¼ §™f0¿S÷¦Òþ¾¿ºJ™|—ïR'Är1 ¥Ã&°¾=@Ô{ù‹ ðC¨·†ÄàrIï¾½Uo|{CP”…däsgz &Iø±5;XÂuØÏÂa1Ð$¤nÎ:ù7+žqä;>t¿îý5 ò¬‘Â·Ê ¥ DÞÚ*\¤3?aKöE¨ò¬GôT ¿«Éh*X[’ºQ7Ñí.¡£ì©‹ÏO@"J;ŸÀ”hIl¡¿ Cèžã+öJ{-fmS2Õ:æÇ”è7täb‹×ƒî{‚» ˆ¬ƒ7qZÄÝ G\+am嵚ÁÙ:»MNé—´­*kMÄF{gªÀ <œ>Q‡hØ-x^;ŸE0LÞ~bÛs@GLl9,o#E˜À-@†[Û°À•9"ŸúŽAÛÆVìA$L“À‰ªùJíCgÒÊ¢“¨ã±Çô/I♨!ñôˆ3tÿCÀ T=nÄBlNƒtšMžUEÍz¶% '$mçÆo÷ Æ h0}.ljó£‰=ÓåSK2ƒÛÂtîþ£9ô†UÒP‘)$rMÒÈ»øWŒ¤)AÀnE”Â2`Í sÇŽJž’t‘=ÈØ·6f8¼ÃyÞÄed~ŠmŒM<ß ÅOPr3>‡ügÀËѺîÕ';£_ŠýÃq›³#pJûk_aú“~ëõÁ\Ó BIâ>óÝO«Ž‹#û1N:åRªË¿ÌA3Ld!ª#=IýDñóÉ/¤>ÿ„‡èù1”¸%— ZýÍx YvÓtïƒ^ºC—úµ¡j9kš4`‡RóSŸ<^*McðÙ½ªE!› 3ƒkЗ!È2Eª†ÈJPñ~³Ò®¤»tÞÛ“¡þÒ·ƒ\N­6ÐEB–ÍL4”“å0‚¯ *†H†÷  ‚ ‚œ0‚˜0‚” *†H†÷   ‚î0‚ê0 *†H†÷  0íY¶«ñ*ß‚È<‹äÔRGÙbb²A²"&çž×ÄÄÝ`X®ýFËAÇ2#¥£êi뤱í9s¢@Ò½wXm6ƒ(Ma£¡.-—ŠàhA÷ H &ÀïýCFQâò,»¦ä'˜‹™¶(;”8¬wîÂ[Wðgu<"»úDeš IÌÔö—b‡H+6Qx8F8:du"àt~³îGª»`ôr™ËÏ :4ܳŽxàÑ UÍÄ< 4Ú"~Àb|4øãxfÎ@"*!l2Â了ÿ:­äá="âË‚æ x*¿&n'äÊIfªV¨»Ÿœ»O·ÜCX±®éyÀTù´äŸA3Ì FU3"•Â,69ÿ)€qc|¹&ŒÃšÉ~ŒðTÚÂðì?m›Þòü#EQFÕí‰Ð·Ü±P¾ßq$JéS#¤ç2.5ùp»EàhþW}R’ÝA‹LhÅo½x€½1Ê€£]ŠQ‘|ÅyÀ«#¼|Ø|˜ˆ](A2pB‘¦|ùö²ÌJÕ ¤Ð(‚_6”ïQYÛ®Ô¯½-"uy<¡8eé;çõWå¾$WêÐ8u£ÔäC FwëCJŸš1ÿ!|PŽtñz6ÏÍjFô»’ŠŽÆI†$¶eïujšuè£ôý3?ž`‚Û§ô!i«¸¥KŸWwjÑ’˜³¢ªqd}Ì ½ÛQ¸ƒÂ¥¼¦ÌðfÛj=(Y¹;ôaáwn“äé9¯“æøÍs2vÅ"„„:›Ctµˆq̺`Bx“É\aQYÆZ™ B,u²ŽZÓgnÕßñ,ûöþV ?[ÓlÙÇÙ·ýÒ’¹sxE(CšŽ( ÀÖñOy‰­w5v®{[ã V²‡öœ6¡|¿¡]×)ò$ù_T¹2Ó@ÞÌŠp0Žnp¥/ê¶l ¬°Åte€ª”‘N£ìÂýDzL[rÚt@ó³A¢ŰS”y"K´ŽË}µ§ãö±Oô²¥+E\£¼A¨yh§ü„˳wñh–ÛZÈ¿Ÿ»<‡™¤sø[çþH¶g&Œ¾sêöÕWkZï\µã^Ï$þ¼ö*ö»ÑZò8*IJs±%«ÇØ Ëí`Ù@ Ü¢‘Õ”´$Åþ@Á ¥X³üxÓ}”ó–Û…KÁK¹§$Ëò¸×{ *v²ÃÌ:ÊõMÛN÷¼Tùà´²è§&W¡¨°?‘'>_Ù½‡ *÷Æ[®%IXŒyΟ•b÷g±W,¯×Aƒñv¥$óu¡˜ :&hÁÏ_B#FËÿ§;y¹A:m§ ¼"З ×CkAnûºÌKjÖ³9:è†äÙª*r\‰Ó(‰Õ™ìÚË^g‡1‰3€_Ÿ Ÿ]'žÎ- ÉÊlâ¼Þ¦æÊ®àvŒûÊú%ÁŒEè ØDˆ nã¸Q£>CÏ|áÂßxgE¶ ,s¼—çs4˜@÷e *„2?vNf‘ZÚ’4ƒ¹#®¿DÿZ^™óÅ>«É–rž‡§»”£Á‹9+=«ò}†‚R¢mÝäN›ø[ 4Âs"Ù%£G* 3±÷Ëw_£¡z“e޽ý7sì(j‡9¥Ë T¶štê§°â h뽘1’0# *†H†÷  1eÁ ׂe´bç?Ô©-žXLÇ'0k *†H†÷  1^\DigiCert RDAP Pilot Client Certificate Revoked0-0!0 +e`1—9 ªþtÒé’N[ :Á}v©æÊ­èrdap-0.9.1/sandbox/file.go000066400000000000000000000017211474227142300153620ustar00rootroot00000000000000// OpenRDAP // Copyright 2018 Tom Harwood // MIT License, see the LICENSE file. package sandbox import ( "errors" "io/ioutil" "log" "path" "runtime" ) var sandboxPath string func LoadFile(filename string) ([]byte, error) { if !IsFileInSandbox(filename) { return nil, errors.New("File not found in sandbox") } var body []byte if len(sandboxPath) == 0 { sandboxPath = findPackagePath() } body, err := ioutil.ReadFile(path.Join(sandboxPath, filename)) if err != nil { log.Panic(err) } return body, nil } func findPackagePath() string { _, filename, _, ok := runtime.Caller(0) if !ok { log.Panic("runtime.Caller() failed") } dir, _ := path.Split(filename) return dir } func IsFileInSandbox(filename string) bool { switch filename { case "DigiCert_RDAP_Pilot_Client_Certificate.p12", "DigiCert_RDAP_Pilot_Client_Certificate_Expired.p12", "DigiCert_RDAP_Pilot_Client_Certificate_Revoked.p12": return true default: return false } } rdap-0.9.1/search_results.go000066400000000000000000000020301474227142300160250ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap // DomainSearchResults represents a domain search response. // // DomainSearchResults is a topmost RDAP response object. type DomainSearchResults struct { DecodeData *DecodeData Common Conformance []string `rdap:"rdapConformance"` Notices []Notice Domains []Domain `rdap:"domainSearchResults"` } // NameserverSearchResults represents a nameserver search response. // // NameserverSearchResults is a topmost RDAP response object. type NameserverSearchResults struct { DecodeData *DecodeData Common Conformance []string `rdap:"rdapConformance"` Notices []Notice Nameservers []Nameserver `rdap:"nameserverSearchResults"` } // EntitySearchResults represents an entity search response. // // EntitySearchResults is a topmost RDAP response object. type EntitySearchResults struct { DecodeData *DecodeData Common Conformance []string `rdap:"rdapConformance"` Notices []Notice Entities []Entity `rdap:"entitySearchResults"` } rdap-0.9.1/test/000077500000000000000000000000001474227142300134345ustar00rootroot00000000000000rdap-0.9.1/test/file.go000066400000000000000000000011501474227142300146770ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package test import ( "io/ioutil" "log" "path" "runtime" ) var testDataPath string func LoadFile(filename string) []byte { var body []byte if len(testDataPath) == 0 { testDataPath = findTestDataPath() } body, err := ioutil.ReadFile(path.Join(testDataPath, filename)) if err != nil { log.Panic(err) } return body } func findTestDataPath() string { _, filename, _, ok := runtime.Caller(0) if !ok { log.Panic("runtime.Caller() failed") } dir, _ := path.Split(filename) return path.Join(dir, "testdata") } rdap-0.9.1/test/http.go000066400000000000000000000070721474227142300147500ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package test import ( "io/ioutil" "log" "net/http" "github.com/jarcoal/httpmock" ) type TestDataset int const ( Bootstrap TestDataset = iota BootstrapExperimental BootstrapMalformed BootstrapComplex BootstrapHTTPError Responses ) type response struct { Status int URL string Body string } var responses map[TestDataset][]response var activatedURLs map[string]bool func Start(set TestDataset) { httpmock.Activate() for _, r := range responses[set] { if _, ok := activatedURLs[r.URL]; ok { log.Panicf("Test sets conflict on URL %s\n", r.URL) } activatedURLs[r.URL] = true httpmock.RegisterResponder("GET", r.URL, httpmock.NewStringResponder(r.Status, r.Body)) } } func Finish() { activatedURLs = make(map[string]bool) httpmock.DeactivateAndReset() } func Get(url string) []byte { resp, err := http.Get(url) if err != nil { log.Panic(err) } data, err := ioutil.ReadAll(resp.Body) defer resp.Body.Close() if err != nil { log.Panic(err) } return data } func init() { responses = make(map[TestDataset][]response) activatedURLs = make(map[string]bool) loadTestDatasets() } func loadTestDatasets() { // Valid snapshot of the IANA bootstrap files. load(Bootstrap, 200, "https://data.iana.org/rdap/asn.json", "bootstrap/asn.json") load(Bootstrap, 200, "https://data.iana.org/rdap/dns.json", "bootstrap/dns.json") load(Bootstrap, 200, "https://data.iana.org/rdap/ipv4.json", "bootstrap/ipv4.json") load(Bootstrap, 200, "https://data.iana.org/rdap/ipv6.json", "bootstrap/ipv6.json") // Experimental bootstrap file for service providers. // https://datatracker.ietf.org/doc/draft-hollenbeck-regext-rdap-object-tag/ . load(BootstrapExperimental, 200, "https://test.rdap.net/rdap/serviceprovider-draft-03.json", "bootstrap_experimental/service_provider.json") // Malformed bootstrap files. load(BootstrapMalformed, 200, "https://www.example.org/dns_bad_services.json", "bootstrap_malformed/dns_bad_services.json") load(BootstrapMalformed, 200, "https://www.example.org/dns_bad_url.json", "bootstrap_malformed/dns_bad_url.json") load(BootstrapMalformed, 200, "https://www.example.org/dns_empty.json", "bootstrap_malformed/dns_empty.json") load(BootstrapMalformed, 200, "https://www.example.org/dns_syntax_error.json", "bootstrap_malformed/dns_syntax_error.json") // Valid bootstrap files testing more features than yet used by IANA. load(BootstrapComplex, 200, "https://rdap.example.org/dns.json", "bootstrap_complex/dns.json") // Bootstrap HTTP errors. load(BootstrapHTTPError, 404, "https://data.iana.org/rdap/asn.json", "bootstrap_http_error/404.html") load(BootstrapHTTPError, 404, "https://data.iana.org/rdap/dns.json", "bootstrap_http_error/404.html") load(BootstrapHTTPError, 404, "https://data.iana.org/rdap/ipv4.json", "bootstrap_http_error/404.html") load(BootstrapHTTPError, 404, "https://data.iana.org/rdap/ipv6.json", "bootstrap_http_error/404.html") // RDAP responses. load(Responses, 200, "https://rdap.nic.cz/domain/example.cz", "rdap/rdap.nic.cz/domain-example.cz.json") load(Responses, 404, "https://rdap.nic.cz/domain/non-existent.cz", "misc/empty.html") load(Responses, 200, "https://rdap.nic.cz/domain/wrong-response-type.cz", "rdap/rdap.nic.cz/nameserver-ns2.pipni.cz.json") load(Responses, 200, "https://rdap.nic.cz/domain/malformed.cz", "misc/malformed.json") } func load(set TestDataset, status int, url string, filename string) { var body []byte = LoadFile(filename) responses[set] = append(responses[set], response{status, url, string(body)}) } rdap-0.9.1/test/http_test.go000066400000000000000000000006021474227142300157770ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package test import ( "testing" "strings" ) func TestSmoke(t *testing.T) { Start(Bootstrap) defer Finish() var bytes []byte bytes = Get("https://data.iana.org/rdap/asn.json") if !strings.Contains(string(bytes), "ripe.net") { t.Fatalf("ASN doesn't contain ripe.net: %s\n", string(bytes)) } } rdap-0.9.1/test/testdata/000077500000000000000000000000001474227142300152455ustar00rootroot00000000000000rdap-0.9.1/test/testdata/bootstrap/000077500000000000000000000000001474227142300172625ustar00rootroot00000000000000rdap-0.9.1/test/testdata/bootstrap/asn.json000066400000000000000000001310051474227142300207360ustar00rootroot00000000000000{ "description": "RDAP bootstrap file for Autonomous System Number allocations", "publication": "2016-09-08T18:00:00Z", "services": [ [ [ "1228-1232", "2018", "2561", "2905", "3067-3068", "3208", "3741", "4178", "4571", "5536", "5713", "5734", "6083", "6089", "6127", "6149", "6180", "6187", "6351", "6529", "6560", "6713", "6879", "6968", "7020", "7154", "7231", "7390", "7420", "7460", "7971-7972", "8094", "8524", "8770", "9129", "10247", "10262", "10331", "10393", "10474", "10505", "10540", "10575", "10798", "10803", "10898", "11125", "11157", "11201", "11259", "11265", "11380", "11569", "11645", "11744", "11845", "11909", "12091", "12143", "12258", "12455", "12556", "13224", "13402", "13519", "13569", "13854", "14029", "14115", "14331", "14429", "14516", "14988", "15022", "15159", "15399", "15475", "15706", "15804", "15825", "15834", "15964", "16058", "16214", "16284", "16416", "16547", "16630", "16637", "16800", "16853", "16907", "17148", "17220", "17260", "17312", "17400", "18775", "18922", "18931", "19136", "19232", "19676", "19711", "19832", "19847", "20011", "20086", "20095", "20180", "20294", "20459", "20484", "20858", "20928", "21003", "21152", "21242", "21271", "21278", "21280", "21391", "21452", "21739", "21819", "22354-22355", "22386", "22572", "22690", "22735", "22750", "22939", "23058", "23549", "24736", "24757", "24788", "24801", "24835", "24863", "24878", "24987", "25163", "25250", "25362", "25364", "25543", "25568", "25576", "25695", "25726", "25793", "25818", "26106", "26130", "26422", "26625", "26754", "27576", "27598", "28683", "28698", "28913", "29091", "29338", "29340", "29428", "29495", "29544", "29571", "29614", "29674", "29918", "29975", "30073", "30306", "30429", "30619", "30896", "30980", "30982-30999", "31065", "31245", "31619", "31810", "31856", "31960", "32017", "32279", "32398", "32437", "32653", "32714", "32717", "32842", "32860", "33567", "33579", "33762-33791", "36864-37887", "327680-328703" ], [ "https://rdap.afrinic.net/rdap/", "http://rdap.afrinic.net/rdap/" ] ], [ [ "173", "681", "1221", "1233", "1237", "1250", "1659", "1704", "1768-1769", "1781", "1851", "2042", "2144", "2385", "2497-2528", "2537", "2554", "2563", "2569-2570", "2697", "2706", "2713", "2756", "2764", "2772", "2823", "2907", "2915", "2925-2926", "3357", "3363", "3382", "3391", "3395", "3460", "3462", "3488", "3510", "3550", "3558-3559", "3583", "3605", "3608", "3661-3662", "3689-3693", "3711", "3717", "3747-3748", "3757-3758", "3773", "3775", "3784", "3786-3787", "3813", "3825", "3836", "3839-3840", "3929", "3969", "3976", "4007", "4040", "4049", "4058", "4060", "4134", "4142", "4158", "4174-4175", "4197", "4202", "4251", "4274", "4352", "4381-4382", "4431", "4433-4434", "4515", "4528", "4538", "4594", "4605", "4608-4865", "4961", "5017-5018", "5051", "5085", "5087", "5709", "6068", "6163", "6262", "6619", "6648", "7131", "7175", "7467-7722", "7855", "7901", "9216-10239", "10807", "11467", "17408-18431", "19705", "23552-24575", "37888-38911", "45056-46079", "55296-56319", "58368-59391", "63488-63999", "64000-64098", "64297-64395", "131072-132095", "132096-133119", "133120-133631", "133632-134556", "134557-135580", "135581-136505", "136506-137529" ], [ "https://rdap.apnic.net/" ] ], [ [ "1-6", "8-27", "29-136", "138-172", "174-223", "225-247", "252-260", "262-277", "279-285", "287", "289-293", "295-374", "376-377", "379-512", "514-516", "518-527", "530-539", "540", "541-543", "545-552", "554-558", "560-564", "566-579", "580", "581-589", "591-592", "594-668", "670-675", "677-678", "682-694", "698-708", "711", "713-718", "720-759", "762-763", "765", "767-773", "784-785", "787-788", "791-1100", "1201-1202", "1204", "1206-1212", "1214-1220", "1222-1227", "1236", "1238-1240", "1242-1247", "1249", "1252", "1254-1256", "1258-1266", "1276-1278", "1280-1289", "1291", "1293-1295", "1298", "1310-1317", "1319-1341", "1343-1351", "1354-1546", "1548-1652", "1655-1657", "1658", "1660-1662", "1664-1679", "1681-1703", "1705-1706", "1727-1728", "1730-1731", "1733-1737", "1740", "1742-1747", "1749-1751", "1753", "1757-1758", "1760-1763", "1765-1767", "1772-1773", "1775", "1777-1779", "1782-1796", "1798-1830", "1832", "1834", "1838-1839", "1842-1848", "1852", "1855-1876", "1904-1915", "1917-1920", "1924-1925", "1927-1929", "1931-1934", "1956-1959", "1963-1966", "1968-2003", "2005-2011", "2013-2015", "2019-2025", "2030-2035", "2037", "2041", "2044", "2046", "2048", "2050-2056", "2137-2143", "2145", "2149-2173", "2274-2276", "2378-2379", "2381-2384", "2386", "2489-2493", "2495-2496", "2531-2536", "2538-2540", "2542-2545", "2548", "2550-2553", "2555-2560", "2562", "2564-2568", "2571-2577", "2579-2584", "2615-2637", "2639-2642", "2644-2646", "2648-2682", "2684-2696", "2698-2705", "2707", "2709-2712", "2714", "2717-2738", "2740-2755", "2757-2763", "2765", "2767-2771", "2824-2829", "2880-2894", "2896-2903", "2906", "2908-2914", "2916", "2918-2920", "2922-2924", "2927-3057", "3059-3066", "3069-3082", "3110-3131", "3133-3140", "3142-3150", "3152-3153", "3354-3356", "3358-3362", "3364-3381", "3383-3390", "3392-3394", "3396-3411", "3416-3448", "3450", "3451-3453", "3455-3459", "3461", "3463-3483", "3485-3486", "3489-3495", "3497-3509", "3511-3547", "3549", "3552-3555", "3557", "3560-3582", "3584-3595", "3598-3602", "3604", "3606-3607", "3609-3623", "3625-3630", "3633-3635", "3637-3639", "3641-3660", "3663-3688", "3694-3710", "3712-3716", "3718-3740", "3742-3746", "3749-3756", "3759-3772", "3774", "3776-3783", "3785", "3788-3789", "3791-3812", "3814-3815", "3817-3824", "3826-3835", "3837-3838", "3841-3842", "3844-3904", "3906-3916", "3919-3928", "3930-3967", "3970-3975", "3977-4006", "4008-4039", "4041-4048", "4050-4057", "4059", "4061-4133", "4135-4140", "4143-4147", "4149-4157", "4159-4173", "4176-4177", "4179", "4180-4196", "4198-4201", "4203-4208", "4210-4229", "4231-4241", "4243", "4245-4250", "4252-4269", "4271-4273", "4275-4351", "4353-4380", "4383-4386", "4388-4404", "4432", "4435-4456", "4459-4492", "4494-4514", "4516-4523", "4525-4527", "4529-4534", "4536-4537", "4539-4570", "4572-4587", "4590-4593", "4595-4604", "4606-4607", "4866-4913", "4915-4925", "4927-4943", "4945-4960", "4962-4963", "4965-4966", "4968-4973", "4975-4994", "4996-5004", "5006-5016", "5019-5050", "5052-5084", "5086", "5088", "5090-5376", "5632", "5634-5638", "5640-5647", "5649-5691", "5693-5707", "5710-5712", "5714-5721", "5723-5733", "5735-5744", "5746-5771", "5773-6056", "6058-6062", "6066", "6069-6082", "6086-6088", "6090-6120", "6122-6124", "6126", "6128-6132", "6134", "6136-6146", "6150-6162", "6164-6167", "6169-6179", "6181-6186", "6188-6192", "6194-6239", "6241-6261", "6263-6305", "6307-6319", "6321-6331", "6333-6341", "6343-6350", "6352-6399", "6401-6411", "6413-6428", "6430-6457", "6459-6470", "6472-6486", "6488-6494", "6496-6502", "6504", "6506-6528", "6530-6534", "6536-6542", "6544", "6546-6559", "6561-6567", "6569-6589", "6591-6618", "6620-6647", "6649-6655", "6912-6926", "6928-6944", "6946-6956", "6958-6967", "6969-7001", "7003", "7006-7019", "7021-7037", "7039-7047", "7050-7055", "7057-7062", "7064-7079", "7081-7086", "7088-7102", "7104-7119", "7121-7124", "7126-7130", "7132-7136", "7138", "7139-7148", "7150-7153", "7155-7156", "7158-7161", "7163-7166", "7168-7172", "7174", "7176-7183", "7185-7194", "7196-7198", "7200-7230", "7232-7235", "7237", "7238-7297", "7299-7302", "7304-7312", "7314", "7316-7324", "7326-7339", "7341-7364", "7366-7389", "7391-7398", "7400-7407", "7409-7413", "7415-7416", "7419", "7421-7427", "7429-7436", "7439-7459", "7461-7464", "7466", "7723-7726", "7728-7737", "7739-7802", "7804-7854", "7856-7863", "7865-7889", "7891-7900", "7902-7905", "7907", "7909", "7911-7926", "7928-7933", "7935-7952", "7954-7964", "7966-7970", "7973", "7975-7979", "7981-7983", "7985-7992", "7996", "7998-8006", "8008-8023", "8025", "8027-8047", "8049-8052", "8057-8064", "8067-8092", "8095", "8097-8139", "8142-8150", "8152-8162", "8164-8166", "8168-8177", "8179-8191", "10240-10246", "10248-10261", "10263-10268", "10270-10276", "10278-10284", "10286-10292", "10294-10298", "10300", "10302-10317", "10319-10330", "10332-10361", "10363-10390", "10392", "10394-10411", "10413-10416", "10418-10419", "10421-10428", "10430-10435", "10437-10451", "10453", "10455-10462", "10464-10473", "10475", "10477-10478", "10480", "10482-10494", "10496-10501", "10503-10504", "10506-10530", "10532-10539", "10541-10559", "10561-10568", "10570-10574", "10576-10585", "10587-10599", "10601-10604", "10607-10616", "10618-10619", "10621-10623", "10625-10629", "10631-10639", "10641-10648", "10650-10669", "10672-10687", "10689-10690", "10692-10696", "10698-10703", "10705", "10707-10714", "10716-10732", "10734-10756", "10758-10777", "10779-10784", "10786-10794", "10796-10797", "10799-10802", "10804-10806", "10808-10823", "10825-10833", "10835-10840", "10842-10846", "10848-10874", "10876-10880", "10882-10894", "10896", "10899-10905", "10907-10921", "10922", "10923-10937", "10939-10953", "10955-10963", "10965-10982", "10984-10985", "10987-10991", "10993-11007", "11009-11013", "11015-11052", "11054-11057", "11059-11062", "11064-11080", "11082", "11084-11086", "11088-11096", "11098-11124", "11126-11135", "11137-11156", "11158-11171", "11173-11192", "11194-11200", "11202-11236", "11238-11241", "11243-11253", "11255", "11257-11258", "11260-11264", "11266-11270", "11272-11283", "11285-11294", "11296-11310", "11312-11314", "11316-11334", "11336-11337", "11339", "11342-11355", "11357-11372", "11374-11379", "11381-11389", "11391", "11393-11410", "11412-11414", "11416-11418", "11420-11430", "11433-11446", "11448-11449", "11452-11466", "11468-11496", "11499-11502", "11504-11513", "11515-11518", "11520-11555", "11557-11561", "11563-11568", "11570", "11572-11580", "11582-11584", "11586-11591", "11593-11598", "11600-11616", "11618-11641", "11643", "11646-11659", "11661-11663", "11665-11672", "11674-11676", "11678-11693", "11695-11705", "11707-11743", "11745-11749", "11753-11785", "11787-11799", "11803-11814", "11817-11829", "11831-11834", "11836-11843", "11846-11887", "11889-11895", "11897-11908", "11910-11920", "11922-11946", "11948-11959", "11961-11992", "11994-12033", "12035-12045", "12047-12065", "12067-12090", "12092-12126", "12128-12134", "12137-12139", "12141-12142", "12144-12145", "12147-12149", "12151-12247", "12249-12251", "12253-12257", "12259-12263", "12265-12287", "13312-13315", "13317", "13319", "13321-13352", "13354-13356", "13358-13380", "13382-13401", "13403-13423", "13425-13439", "13441-13458", "13460-13473", "13475-13488", "13490-13494", "13496-13513", "13515-13518", "13520", "13523-13543", "13545-13568", "13570-13578", "13580-13583", "13586-13590", "13592-13642", "13644-13678", "13680-13681", "13683-13760", "13762-13773", "13775-13834", "13836-13853", "13855-13873", "13875-13877", "13880-13913", "13915-13928", "13930-13933", "13937-13990", "13992-13998", "14001-14025", "14027-14028", "14031-14068", "14070-14079", "14081-14083", "14085-14086", "14088-14110", "14112-14114", "14116", "14118-14121", "14123-14177", "14180-14185", "14188-14201", "14203", "14205-14230", "14233", "14235-14248", "14251-14258", "14260-14281", "14283-14284", "14287-14315", "14317", "14319-14330", "14332-14338", "14340-14345", "14347-14376", "14378-14419", "14421-14428", "14430-14456", "14458-14460", "14461-14462", "14464-14515", "14517-14521", "14523-14534", "14536-14552", "14554-14559", "14561-14570", "14572-14623", "14625-14649", "14651-14663", "14665-14673", "14675-14691", "14693-14707", "14710-14722", "14724-14753", "14755-14758", "14760-14768", "14770-14794", "14796-14839", "14841-14844", "14846-14866", "14869-14885", "14887-14965", "14967-14969", "14971-14987", "14989-15021", "15023-15029", "15031-15033", "15035-15063", "15065", "15067-15074", "15076-15077", "15079-15106", "15108-15124", "15126-15150", "15152-15158", "15160-15179", "15181-15200", "15202-15207", "15209-15235", "15237-15240", "15242-15245", "15247-15251", "15253-15255", "15257-15273", "15275-15310", "15312-15359", "16384-16393", "16394-16396", "16398-16415", "16417", "16419-16470", "16472-16505", "16507-16521", "16523-16527", "16529-16530", "16532-16546", "16548-16591", "16593", "16595", "16597-16605", "16608-16628", "16631-16636", "16638-16662", "16664-16684", "16686-16688", "16690-16700", "16702-16711", "16713-16731", "16733-16734", "16737", "16738-16741", "16743-16761", "16763-16771", "16773-16779", "16781-16799", "16801-16813", "16815-16846", "16848", "16850-16852", "16854-16863", "16865-16873", "16875-16884", "16886-16890", "16892-16905", "16908-16910", "16912-16959", "16961-16972", "16974", "16976-16989", "16991-17068", "17070-17071", "17073-17078", "17080-17085", "17087-17107", "17109-17125", "17127-17146", "17149-17181", "17183-17204", "17206-17207", "17209-17219", "17221", "17223-17248", "17251-17254", "17256", "17258-17259", "17261-17286", "17288-17311", "17313-17328", "17330-17375", "17377-17378", "17380-17398", "17402-17407", "18432-18448", "18450-18454", "18456-18465", "18467-18478", "18480-18491", "18493-18495", "18497-18531", "18533-18546", "18548-18575", "18577-18578", "18580-18591", "18593-18643", "18645-18666", "18668-18677", "18679-18731", "18733", "18735-18738", "18740-18774", "18776-18781", "18783-18808", "18810-18821", "18823-18835", "18837-18839", "18841-18845", "18847-18868", "18870-18880", "18882-18921", "18923-18930", "18932-18940", "18942-18997", "18999-19032", "19034-19036", "19039-19063", "19065-19076", "19078-19088", "19091-19108", "19110-19113", "19115-19131", "19133-19135", "19137-19168", "19170-19177", "19179", "19181", "19183-19191", "19193-19195", "19197-19199", "19201-19227", "19229-19231", "19233-19243", "19245-19258", "19260-19277", "19279-19314", "19316-19331", "19333-19337", "19339-19360", "19362-19372", "19374-19375", "19377-19398", "19400-19410", "19412-19421", "19423-19428", "19430-19446", "19448-19518", "19520-19552", "19554-19581", "19584-19610", "19612-19631", "19633-19675", "19677-19687", "19689-19704", "19706-19710", "19712-19722", "19724-19730", "19732-19762", "19764-19766", "19768-19831", "19833-19846", "19848-19862", "19864-19872", "19874-19888", "19890-19959", "19961-19977", "19979-19988", "19991-20001", "20003-20010", "20012-20014", "20016-20031", "20033-20042", "20045-20085", "20087-20094", "20096-20105", "20107-20115", "20118-20120", "20122-20141", "20143-20172", "20174-20179", "20181-20190", "20192-20206", "20208-20231", "20233-20243", "20245-20254", "20257-20265", "20267-20293", "20295-20296", "20298", "20300-20304", "20306-20311", "20313-20320", "20322-20344", "20346-20360", "20362-20362", "20364-20417", "20419-20458", "20460-20479", "21504-21505", "21507-21519", "21521-21570", "21572-21573", "21576-21577", "21579-21589", "21591-21598", "21600-21602", "21604-21611", "21613", "21615-21673", "21675-21691", "21693-21738", "21740", "21742-21752", "21754-21755", "21757-21764", "21766-21767", "21769-21818", "21820-21823", "21825-21825", "21827-21837", "21839-21861", "21863-21882", "21884-21887", "21889-21910", "21912-21916", "21918-21979", "21981-22009", "22012-22018", "22020-22046", "22048-22054", "22056-22079", "22081-22084", "22086-22091", "22093-22107", "22109-22121", "22123-22127", "22130-22132", "22134-22147", "22149-22176", "22178-22184", "22186-22226", "22228-22249", "22251-22304", "22306-22312", "22314-22340", "22342-22353", "22357-22367", "22369-22370", "22372-22380", "22383-22385", "22387-22406", "22408-22410", "22412-22430", "22432-22452", "22454-22500", "22502-22507", "22509-22514", "22516-22528", "22530-22540", "22542-22547", "22549-22565", "22567-22571", "22573-22626", "22629-22660", "22662-22677", "22679-22682", "22684-22688", "22691-22697", "22700-22705", "22707-22723", "22725", "22727-22734", "22736-22744", "22746-22749", "22751-22797", "22799-22817", "22820-22832", "22834-22859", "22861-22868", "22870-22875", "22877-22881", "22883", "22885-22888", "22890-22893", "22895-22907", "22909-22923", "22925-22926", "22928-22938", "22940-22974", "22976-23001", "23003-23006", "23008-23019", "23021-23030", "23032-23057", "23059-23073", "23075-23090", "23092-23104", "23107-23112", "23114-23127", "23129-23139", "23141-23200", "23203-23215", "23217-23241", "23244-23245", "23247-23288", "23290-23352", "23354-23359", "23361-23381", "23384-23415", "23417-23455", "23457-23486", "23489-23494", "23496-23540", "23542-23548", "23550-23551", "25600-25606", "25608-25619", "25621-25694", "25696-25700", "25702-25704", "25706-25717", "25719-25725", "25727-25733", "25735-25792", "25794-25811", "25813-25817", "25819-25831", "25833-25879", "25881-25907", "25909-25926", "25928-25932", "25934-25997", "25999-26047", "26049-26060", "26062-26089", "26091-26103", "26108-26111", "26113-26117", "26120-26129", "26131-26135", "26137-26161", "26163-26172", "26174-26193", "26195-26209", "26211-26217", "26219-26316", "26318-26417", "26419-26421", "26423-26425", "26427-26433", "26435-26472", "26474-26504", "26506-26591", "26624-26624", "26626-26753", "26755-27575", "27577-27597", "27599-27647", "29696-29917", "29919-29974", "29976-30072", "30074-30305", "30307-30428", "30430-30618", "30620-30719", "31744-31809", "31811-31855", "31857-31959", "31961-32016", "32018-32278", "32280-32397", "32399-32436", "32438-32652", "32654-32713", "32715-32716", "32718-32841", "32843-32859", "32861-33566", "33568-33578", "33580-33761", "35840-36863", "39936-40959", "46080-47103", "53248-54271", "54272-55295", "62464-63487", "64198-64296", "393216-394239", "394240-395164", "395165-396188", "396189-397212" ], [ "https://rdap.arin.net/registry", "http://rdap.arin.net/registry" ] ], [ [ "7", "28", "137", "224", "248-251", "261", "286", "288", "294", "375", "378", "513", "517", "528-529", "544", "553", "559", "565", "590", "593", "669", "679-680", "695-697", "709-710", "712", "719", "760-761", "764", "766", "774-783", "786", "789-790", "1101-1200", "1203", "1205", "1213", "1234-1235", "1241", "1248", "1253", "1257", "1267-1275", "1279", "1290", "1297", "1299-1309", "1318", "1342", "1352-1353", "1547-1547", "1653-1654", "1663", "1680", "1707-1726", "1729", "1732", "1738-1739", "1741", "1748", "1752", "1754-1756", "1759", "1764", "1770-1771", "1774", "1776", "1780", "1833", "1835-1837", "1841", "1849-1850", "1853-1854", "1877-1901", "1902-1903", "1921-1923", "1926", "1930", "1935-1955", "1960-1962", "1967", "2004", "2012", "2016-2017", "2026-2029", "2036", "2038-2040", "2043", "2045", "2047", "2049", "2057-2106", "2107-2136", "2147-2148", "2174-2273", "2278-2377", "2380", "2387-2488", "2494", "2529-2530", "2541", "2546-2547", "2578", "2585-2614", "2643", "2647", "2683", "2766", "2773-2822", "2830-2879", "2895", "2917", "2921", "3058", "3083-3109", "3151", "3154-3207", "3209-3353", "3412-3415", "3624", "3843", "3917-3918", "4148", "4405-4430", "4457-4458", "4524", "4588-4589", "4974", "5089", "5377-5535", "5537-5631", "6067", "6085", "6168", "6320", "6412", "6656-6712", "6714-6878", "6880-6911", "8093", "8192-8523", "8525-8769", "8771-9128", "9130-9215", "11341", "11660", "12046", "12288-12454", "12456-12555", "12557-13223", "13225-13311", "13879", "15360-15398", "15400-15474", "15476-15705", "15707-15803", "15805-15824", "15826-15833", "15835-15963", "15965-16057", "16059-16213", "16215-16283", "16285-16383", "18732", "19178", "19376", "19399", "20480-20483", "20485-20857", "20859-20927", "20929-21002", "21004-21151", "21153-21241", "21243-21270", "21272-21277", "21279", "21281-21390", "21392-21451", "21453-21503", "22108", "22627", "22683", "23242", "24576-24735", "24737-24756", "24758-24787", "24789-24800", "24802-24834", "24836-24862", "24864-24877", "24879-24986", "24988-25162", "25164-25249", "25251-25361", "25363", "25365-25542", "25544-25567", "25569-25575", "25577-25599", "25880", "28672-28682", "28684-28697", "28699-28912", "28914-29090", "29092-29337", "29339", "29341-29427", "29429-29494", "29496-29543", "29545-29570", "29572-29613", "29615-29673", "29675-29695", "30720-30895", "30897-30979", "30981", "31000-31064", "31066-31244", "31246-31618", "31620-31743", "33792-34815", "34816-35839", "38912-39935", "40960-41983", "41984-43007", "43008-44031", "44032-45055", "47104-48127", "48128-49151", "49152-50175", "50176-51199", "51200-52223", "56320-57343", "57344-58367", "59392-60415", "60416-61439", "61952-62463", "64396-64495", "196608-197631", "197632-198655", "198656-199679", "199680-200191", "200192-201215", "201216-202239", "202240-203263", "203264-204287", "204288-205211", "205212-206235", "206236-207259" ], [ "https://rdap.db.ripe.net/" ] ], [ [ "278", "676", "1251", "1292", "1296", "1797", "1831", "1840", "1916", "2146", "2277", "2549", "2638", "2708", "2715-2716", "2739", "2904", "3132", "3141", "3449", "3454", "3484", "3487", "3496", "3548", "3551", "3556", "3596-3597", "3603", "3631-3632", "3636", "3640", "3790", "3816", "3905", "3968", "4141", "4209", "4230", "4242", "4244", "4270", "4387", "4493", "4535", "4914", "4926", "4944", "4964", "4967", "4995", "5005", "5633", "5639", "5648", "5692", "5708", "5722", "5745", "5772", "6057", "6063-6065", "6084", "6121", "6125", "6133", "6135", "6147-6148", "6193", "6240", "6306", "6332", "6342", "6400", "6429", "6458", "6471", "6487", "6495", "6503", "6505", "6535", "6543", "6545", "6568", "6590", "6927", "6945", "6957", "7002", "7004-7005", "7038", "7048-7049", "7056", "7063", "7080", "7087", "7103", "7120", "7125", "7137", "7149", "7157", "7162", "7167", "7173", "7184", "7195", "7199", "7236", "7298", "7303", "7313", "7315", "7325", "7340", "7365", "7399", "7408", "7414", "7417-7418", "7428", "7437-7438", "7465", "7727", "7738", "7803", "7864", "7890", "7906", "7908", "7910", "7927", "7934", "7953", "7965", "7974", "7980", "7984", "7993-7995", "7997", "8007", "8024", "8026", "8048", "8053-8056", "8065-8066", "8096", "8140-8141", "8151", "8163", "8167", "8178", "10269", "10277", "10285", "10293", "10299", "10301", "10318", "10362", "10391", "10412", "10417", "10420", "10429", "10436", "10452", "10454", "10463", "10476", "10479", "10481", "10495", "10502", "10531", "10560", "10569", "10586", "10600", "10605-10606", "10617", "10620", "10624", "10630", "10640", "10649", "10670-10671", "10688", "10691", "10697", "10704", "10706", "10715", "10733", "10757", "10778", "10785", "10795", "10824", "10834", "10841", "10847", "10875", "10881", "10895", "10897", "10906", "10938", "10954", "10964", "10983", "10986", "10992", "11008", "11014", "11053", "11058", "11063", "11081", "11083", "11087", "11097", "11136", "11172", "11193", "11237", "11242", "11254", "11256", "11271", "11284", "11295", "11311", "11315", "11335", "11338", "11340", "11356", "11373", "11390", "11392", "11411", "11415", "11419", "11431-11432", "11447", "11450-11451", "11497-11498", "11503", "11514", "11519", "11556", "11562", "11571", "11581", "11585", "11592", "11599", "11617", "11642", "11644", "11664", "11673", "11677", "11694", "11706", "11750-11752", "11786", "11800-11802", "11815-11816", "11830", "11835", "11844", "11888", "11896", "11921", "11947", "11960", "11993", "12034", "12066", "12127", "12135-12136", "12140", "12146", "12150", "12248", "12252", "12264", "13316", "13318", "13320", "13353", "13357", "13381", "13424", "13440", "13459", "13474", "13489", "13495", "13514", "13521-13522", "13544", "13579", "13584-13585", "13591", "13643", "13679", "13682", "13761", "13774", "13835", "13874", "13878", "13914", "13929", "13934-13936", "13991", "13999-14000", "14026", "14030", "14069", "14080", "14084", "14087", "14111", "14117", "14122", "14178-14179", "14186-14187", "14202", "14204", "14231-14232", "14234", "14249-14250", "14259", "14282", "14285-14286", "14316", "14318", "14339", "14346", "14377", "14420", "14457", "14463", "14522", "14535", "14553", "14560", "14571", "14624", "14650", "14664", "14674", "14692", "14708-14709", "14723", "14754", "14759", "14769", "14795", "14840", "14845", "14867-14868", "14886", "14966", "14970", "15030", "15034", "15064", "15066", "15075", "15078", "15107", "15125", "15151", "15180", "15201", "15208", "15236", "15241", "15246", "15252", "15256", "15274", "15311", "16397", "16418", "16471", "16506", "16522", "16528", "16531", "16592", "16594", "16596", "16606-16607", "16629", "16663", "16685", "16689", "16701", "16712", "16732", "16735-16736", "16742", "16762", "16772", "16780", "16814", "16847", "16849", "16864", "16874", "16885", "16891", "16906", "16911", "16960", "16973", "16975", "16990", "17069", "17072", "17079", "17086", "17108", "17126", "17147", "17182", "17205", "17208", "17222", "17249-17250", "17255", "17257", "17287", "17329", "17376", "17379", "17399", "17401", "18449", "18455", "18466", "18479", "18492", "18496", "18532", "18547", "18576", "18579", "18592", "18644", "18667", "18678", "18734", "18739", "18782", "18809", "18822", "18836", "18840", "18846", "18869", "18881", "18941", "18998", "19033", "19037-19038", "19064", "19077", "19089-19090", "19109", "19114", "19132", "19169", "19180", "19182", "19192", "19196", "19200", "19228", "19244", "19259", "19278", "19315", "19332", "19338", "19361", "19373", "19411", "19422", "19429", "19447", "19519", "19553", "19582-19583", "19611", "19632", "19688", "19723", "19731", "19763", "19767", "19863", "19873", "19889", "19960", "19978", "19989-19990", "20002", "20015", "20032", "20043-20044", "20106", "20116-20117", "20121", "20142", "20173", "20191", "20207", "20232", "20244", "20255-20256", "20266", "20297", "20299", "20305", "20312", "20321", "20345", "20361", "20363", "20418", "21506", "21520", "21571", "21574-21575", "21578", "21590", "21599", "21603", "21612", "21614", "21674", "21692", "21741", "21753", "21756", "21765", "21768", "21824", "21826", "21838", "21862", "21883", "21888", "21911", "21917", "21980", "22010-22011", "22019", "22047", "22055", "22080", "22085", "22092", "22122", "22128-22129", "22133", "22148", "22177", "22185", "22227", "22250", "22305", "22313", "22341", "22356", "22368", "22371", "22381-22382", "22407", "22411", "22431", "22453", "22501", "22508", "22515", "22529", "22541", "22548", "22566", "22628", "22661", "22678", "22689", "22698-22699", "22706", "22724", "22726", "22745", "22798", "22818-22819", "22833", "22860", "22869", "22876", "22882", "22884", "22889", "22894", "22908", "22924", "22927", "22975", "23002", "23007", "23020", "23031", "23074", "23091", "23105-23106", "23113", "23128", "23140", "23201-23202", "23216", "23243", "23246", "23289", "23353", "23360", "23382-23383", "23416", "23487-23488", "23495", "23541", "25607", "25620", "25701", "25705", "25718", "25734", "25812", "25832", "25908", "25927", "25933", "25998", "26048", "26061", "26090", "26104-26105", "26107", "26112", "26118-26119", "26136", "26162", "26173", "26194", "26210", "26218", "26317", "26418", "26426", "26434", "26473", "26505", "26592-26623", "27648-28671", "52224-53247", "61440-61951", "64099-64197", "262144-263167", "263168-263679", "263680-264604", "264605-265628", "265629-266652" ], [ "https://rdap.lacnic.net/rdap/" ] ] ], "version": "1.0" }rdap-0.9.1/test/testdata/bootstrap/dns.json000066400000000000000000000006611474227142300207440ustar00rootroot00000000000000{ "description": "RDAP bootstrap file for Domain Name System registrations", "publication": "2017-03-15T21:26:24Z", "services": [ [ [ "ar" ], [ "https://rdap.nic.ar" ] ], [ [ "cz" ], [ "https://rdap.nic.cz" ] ], [ [ "br" ], [ "https://rdap.registro.br/" ] ] ], "version": "1.0" }rdap-0.9.1/test/testdata/bootstrap/ipv4.json000066400000000000000000000127731474227142300210510ustar00rootroot00000000000000{ "description": "RDAP bootstrap file for IPv4 address allocations", "publication": "2015-08-11T00:09:31Z", "services": [ [ [ "41.0.0.0/8", "102.0.0.0/8", "105.0.0.0/8", "154.0.0.0/8", "196.0.0.0/8", "197.0.0.0/8" ], [ "https://rdap.afrinic.net/rdap/", "http://rdap.afrinic.net/rdap/" ] ], [ [ "1.0.0.0/8", "14.0.0.0/8", "27.0.0.0/8", "36.0.0.0/8", "39.0.0.0/8", "42.0.0.0/8", "43.0.0.0/8", "49.0.0.0/8", "58.0.0.0/8", "59.0.0.0/8", "60.0.0.0/8", "61.0.0.0/8", "101.0.0.0/8", "103.0.0.0/8", "106.0.0.0/8", "110.0.0.0/8", "111.0.0.0/8", "112.0.0.0/8", "113.0.0.0/8", "114.0.0.0/8", "115.0.0.0/8", "116.0.0.0/8", "117.0.0.0/8", "118.0.0.0/8", "119.0.0.0/8", "120.0.0.0/8", "121.0.0.0/8", "122.0.0.0/8", "123.0.0.0/8", "124.0.0.0/8", "125.0.0.0/8", "126.0.0.0/8", "133.0.0.0/8", "150.0.0.0/8", "153.0.0.0/8", "163.0.0.0/8", "171.0.0.0/8", "175.0.0.0/8", "180.0.0.0/8", "182.0.0.0/8", "183.0.0.0/8", "202.0.0.0/8", "203.0.0.0/8", "210.0.0.0/8", "211.0.0.0/8", "218.0.0.0/8", "219.0.0.0/8", "220.0.0.0/8", "221.0.0.0/8", "222.0.0.0/8", "223.0.0.0/8" ], [ "https://rdap.apnic.net/" ] ], [ [ "3.0.0.0/8", "4.0.0.0/8", "6.0.0.0/8", "7.0.0.0/8", "8.0.0.0/8", "9.0.0.0/8", "11.0.0.0/8", "12.0.0.0/8", "13.0.0.0/8", "15.0.0.0/8", "16.0.0.0/8", "17.0.0.0/8", "18.0.0.0/8", "19.0.0.0/8", "20.0.0.0/8", "21.0.0.0/8", "22.0.0.0/8", "23.0.0.0/8", "24.0.0.0/8", "26.0.0.0/8", "28.0.0.0/8", "29.0.0.0/8", "30.0.0.0/8", "32.0.0.0/8", "33.0.0.0/8", "34.0.0.0/8", "35.0.0.0/8", "38.0.0.0/8", "40.0.0.0/8", "44.0.0.0/8", "45.0.0.0/8", "47.0.0.0/8", "48.0.0.0/8", "50.0.0.0/8", "52.0.0.0/8", "54.0.0.0/8", "55.0.0.0/8", "56.0.0.0/8", "63.0.0.0/8", "64.0.0.0/8", "65.0.0.0/8", "66.0.0.0/8", "67.0.0.0/8", "68.0.0.0/8", "69.0.0.0/8", "70.0.0.0/8", "71.0.0.0/8", "72.0.0.0/8", "73.0.0.0/8", "74.0.0.0/8", "75.0.0.0/8", "76.0.0.0/8", "96.0.0.0/8", "97.0.0.0/8", "98.0.0.0/8", "99.0.0.0/8", "100.0.0.0/8", "104.0.0.0/8", "107.0.0.0/8", "108.0.0.0/8", "128.0.0.0/8", "129.0.0.0/8", "130.0.0.0/8", "131.0.0.0/8", "132.0.0.0/8", "134.0.0.0/8", "135.0.0.0/8", "136.0.0.0/8", "137.0.0.0/8", "138.0.0.0/8", "139.0.0.0/8", "140.0.0.0/8", "142.0.0.0/8", "143.0.0.0/8", "144.0.0.0/8", "146.0.0.0/8", "147.0.0.0/8", "148.0.0.0/8", "149.0.0.0/8", "152.0.0.0/8", "155.0.0.0/8", "156.0.0.0/8", "157.0.0.0/8", "158.0.0.0/8", "159.0.0.0/8", "160.0.0.0/8", "161.0.0.0/8", "162.0.0.0/8", "164.0.0.0/8", "165.0.0.0/8", "166.0.0.0/8", "167.0.0.0/8", "168.0.0.0/8", "169.0.0.0/8", "170.0.0.0/8", "172.0.0.0/8", "173.0.0.0/8", "174.0.0.0/8", "184.0.0.0/8", "192.0.0.0/8", "198.0.0.0/8", "199.0.0.0/8", "204.0.0.0/8", "205.0.0.0/8", "206.0.0.0/8", "207.0.0.0/8", "208.0.0.0/8", "209.0.0.0/8", "214.0.0.0/8", "215.0.0.0/8", "216.0.0.0/8" ], [ "https://rdap.arin.net/registry", "http://rdap.arin.net/registry" ] ], [ [ "2.0.0.0/8", "5.0.0.0/8", "25.0.0.0/8", "31.0.0.0/8", "37.0.0.0/8", "46.0.0.0/8", "51.0.0.0/8", "53.0.0.0/8", "57.0.0.0/8", "62.0.0.0/8", "77.0.0.0/8", "78.0.0.0/8", "79.0.0.0/8", "80.0.0.0/8", "81.0.0.0/8", "82.0.0.0/8", "83.0.0.0/8", "84.0.0.0/8", "85.0.0.0/8", "86.0.0.0/8", "87.0.0.0/8", "88.0.0.0/8", "89.0.0.0/8", "90.0.0.0/8", "91.0.0.0/8", "92.0.0.0/8", "93.0.0.0/8", "94.0.0.0/8", "95.0.0.0/8", "109.0.0.0/8", "141.0.0.0/8", "145.0.0.0/8", "151.0.0.0/8", "176.0.0.0/8", "178.0.0.0/8", "185.0.0.0/8", "188.0.0.0/8", "193.0.0.0/8", "194.0.0.0/8", "195.0.0.0/8", "212.0.0.0/8", "213.0.0.0/8", "217.0.0.0/8" ], [ "https://rdap.db.ripe.net/" ] ], [ [ "177.0.0.0/8", "179.0.0.0/8", "181.0.0.0/8", "186.0.0.0/8", "187.0.0.0/8", "189.0.0.0/8", "190.0.0.0/8", "191.0.0.0/8", "200.0.0.0/8", "201.0.0.0/8" ], [ "https://rdap.lacnic.net/rdap/" ] ] ], "version": "1.0" }rdap-0.9.1/test/testdata/bootstrap/ipv6.json000066400000000000000000000027521474227142300210470ustar00rootroot00000000000000{ "description": "RDAP bootstrap file for IPv6 address allocations", "publication": "2016-03-22T15:40:01Z", "services": [ [ [ "2001:4200::/23", "2c00::/12" ], [ "https://rdap.afrinic.net/rdap/", "http://rdap.afrinic.net/rdap/" ] ], [ [ "2001:200::/23", "2001:4400::/23", "2001:8000::/19", "2001:a000::/20", "2001:b000::/20", "2001:c00::/23", "2001:e00::/23", "2400::/12" ], [ "https://rdap.apnic.net/" ] ], [ [ "2001:1800::/23", "2001:400::/23", "2001:4800::/23", "2600::/12", "2610::/23", "2620::/23" ], [ "https://rdap.arin.net/registry", "http://rdap.arin.net/registry" ] ], [ [ "2001:1400::/23", "2001:1600::/23", "2001:1a00::/23", "2001:1c00::/22", "2001:2000::/20", "2001:3000::/21", "2001:3800::/22", "2001:4000::/23", "2001:4600::/23", "2001:4a00::/23", "2001:4c00::/23", "2001:5000::/20", "2001:600::/23", "2001:800::/23", "2001:a00::/23", "2003::/18", "2a00::/12" ], [ "https://rdap.db.ripe.net/" ] ], [ [ "2001:1200::/23", "2800::/12" ], [ "https://rdap.lacnic.net/rdap/" ] ] ], "version": "1.0" }rdap-0.9.1/test/testdata/bootstrap_complex/000077500000000000000000000000001474227142300210115ustar00rootroot00000000000000rdap-0.9.1/test/testdata/bootstrap_complex/dns.json000066400000000000000000000010341474227142300224660ustar00rootroot00000000000000{ "description": "RDAP bootstrap file for Domain Name System registrations", "publication": "2017-03-15T21:26:24Z", "services": [ [ [ "" ], [ "https://example.root", "http://example.root" ] ], [ [ "com" ], [ "https://example.com", "http://example.com" ] ], [ [ "sub.example.com" ], [ "https://example.com/sub", "http://example.com/sub" ] ] ], "version": "1.0" } rdap-0.9.1/test/testdata/bootstrap_experimental/000077500000000000000000000000001474227142300220375ustar00rootroot00000000000000rdap-0.9.1/test/testdata/bootstrap_experimental/service_provider.json000066400000000000000000000003751474227142300263110ustar00rootroot00000000000000{ "version": "1.0", "publication": "2017-04-26T00:00:00Z", "description": "RDAP service provider bootstrap values (experimental, hosted on openrdap.org)", "services": [ [ ["VRSN"], [ "https://rdap.verisignlabs.com/rdap/v1" ] ] ] } rdap-0.9.1/test/testdata/bootstrap_http_error/000077500000000000000000000000001474227142300215325ustar00rootroot00000000000000rdap-0.9.1/test/testdata/bootstrap_http_error/404.html000066400000000000000000000026631474227142300227360ustar00rootroot00000000000000 Object not found!

Object not found!

The requested URL was not found on this server. If you entered the URL manually please check your spelling and try again.

Error 404

IANA

rdap-0.9.1/test/testdata/bootstrap_malformed/000077500000000000000000000000001474227142300213105ustar00rootroot00000000000000rdap-0.9.1/test/testdata/bootstrap_malformed/dns_bad_services.json000066400000000000000000000006771474227142300255120ustar00rootroot00000000000000{ "description": "RDAP bootstrap file for Domain Name System registrations", "publication": "2017-03-15T21:26:24Z", "services": [ [ [ "ar" ], [ "https://rdap.nic.ar" ] ], [ [ "cz" ], [ "https://rdap.nic.cz" ] ], [ [ "br" ], [ "https://rdap.registro.br/" ] ], [ ] ], "version": "1.0" } rdap-0.9.1/test/testdata/bootstrap_malformed/dns_bad_url.json000066400000000000000000000010631474227142300244570ustar00rootroot00000000000000{ "description": "RDAP bootstrap file for Domain Name System registrations", "publication": "2017-03-15T21:26:24Z", "services": [ [ [ "ar" ], [ "https://rdap.nic.ar" ] ], [ [ "cz" ], [ "https://rdap.nic.cz" ] ], [ [ "br" ], [ "https://rdap.registro.br/", "http://example.org/%%" ] ], [ [ "example" ], [ "http://example.org/%%" ] ] ], "version": "1.0" } rdap-0.9.1/test/testdata/bootstrap_malformed/dns_empty.json000066400000000000000000000000001474227142300241730ustar00rootroot00000000000000rdap-0.9.1/test/testdata/bootstrap_malformed/dns_syntax_error.json000066400000000000000000000001251474227142300256040ustar00rootroot00000000000000{ "description": "RDAP bootstrap file for Domain Name System registrations", " } rdap-0.9.1/test/testdata/jcard/000077500000000000000000000000001474227142300163305ustar00rootroot00000000000000rdap-0.9.1/test/testdata/jcard/error_bad_properties_array.json000066400000000000000000000000351474227142300246320ustar00rootroot00000000000000 ["vcard", false ] rdap-0.9.1/test/testdata/jcard/error_bad_property_name.json000066400000000000000000000001021474227142300241170ustar00rootroot00000000000000 ["vcard", [ [false, {}, "text", "4.0"] ] ] rdap-0.9.1/test/testdata/jcard/error_bad_property_nest_depth.json000066400000000000000000000001431474227142300253410ustar00rootroot00000000000000 ["vcard", [ ["version", {}, "text", ["ok",["ok",["nested too deep"]]]]] ] ] rdap-0.9.1/test/testdata/jcard/error_bad_property_parameters.json000066400000000000000000000001111474227142300253420ustar00rootroot00000000000000 ["vcard", [ ["version", false, "text", "4.0"] ] ] rdap-0.9.1/test/testdata/jcard/error_bad_property_parameters_2.json000066400000000000000000000001131474227142300255650ustar00rootroot00000000000000 ["vcard", [ [false, {"a" => 42}, "text", "4.0"] ] ] rdap-0.9.1/test/testdata/jcard/error_bad_property_size.json000066400000000000000000000000771474227142300241640ustar00rootroot00000000000000 ["vcard", [ ["version", {}, "text"] ] ] rdap-0.9.1/test/testdata/jcard/error_bad_property_type.json000066400000000000000000000001021474227142300241600ustar00rootroot00000000000000 ["vcard", [ ["version", {}, 42, "4.0"] ] ] rdap-0.9.1/test/testdata/jcard/error_bad_top_type.json000066400000000000000000000000061474227142300231010ustar00rootroot00000000000000 [] rdap-0.9.1/test/testdata/jcard/error_bad_vcard_label.json000066400000000000000000000000411474227142300234730ustar00rootroot00000000000000 ["vcardX", [ ] ] rdap-0.9.1/test/testdata/jcard/error_invalid_json.json000066400000000000000000000000741474227142300231140ustar00rootroot00000000000000 ["vcard", [ ["version", {}, "text", "4.0"], rdap-0.9.1/test/testdata/jcard/example.json000066400000000000000000000030001474227142300206470ustar00rootroot00000000000000 ["vcard", [ ["version", {}, "text", "4.0"], ["fn", {}, "text", "Simon Perreault"], ["n", {}, "text", ["Perreault", "Simon", "", "", ["ing. jr", "M.Sc."]] ], ["bday", {}, "date-and-or-time", "--02-03"], ["anniversary", {}, "date-and-or-time", "2009-08-08T14:30:00-05:00" ], ["gender", {}, "text", "M"], ["lang", { "pref": "1" }, "language-tag", "fr"], ["lang", { "pref": "2" }, "language-tag", "en"], ["org", { "type": "work" }, "text", "Viagenie"], ["adr", { "type": "work" }, "text", [ "", "Suite D2-630", "2875 Laurier", "Quebec", "QC", "G1V 2M2", "Canada" ] ], ["tel", { "type": ["work", "voice"], "pref": "1" }, "uri", "tel:+1-418-656-9254;ext=102" ], ["tel", { "type": ["work", "cell", "voice", "video", "text"] }, "uri", "tel:+1-418-262-6501" ], ["email", { "type": "work" }, "text", "simon.perreault@viagenie.ca" ], ["geo", { "type": "work" }, "uri", "geo:46.772673,-71.282945"], ["key", { "type": "work" }, "uri", "http://www.viagenie.ca/simon.perreault/simon.asc" ], ["tz", {}, "utc-offset", "-05:00"], ["url", { "type": "home" }, "uri", "http://nomis80.org"] ] ] rdap-0.9.1/test/testdata/jcard/mixed.json000066400000000000000000000002171474227142300203310ustar00rootroot00000000000000 ["vcard", [ ["version", {}, "text", "4.0"], ["mixed", {}, "text", "abc", true, 42, null, ["def", false, 43]] ] ] rdap-0.9.1/test/testdata/misc/000077500000000000000000000000001474227142300162005ustar00rootroot00000000000000rdap-0.9.1/test/testdata/misc/empty.html000066400000000000000000000000001474227142300202120ustar00rootroot00000000000000rdap-0.9.1/test/testdata/misc/malformed.json000066400000000000000000000000111474227142300210310ustar00rootroot00000000000000{ rdap-0.9.1/test/testdata/rdap/000077500000000000000000000000001474227142300161735ustar00rootroot00000000000000rdap-0.9.1/test/testdata/rdap/rdap-pilot.verisignlabs.com/000077500000000000000000000000001474227142300235125ustar00rootroot00000000000000rdap-0.9.1/test/testdata/rdap/rdap-pilot.verisignlabs.com/entity-1-VRSN000066400000000000000000000015321474227142300256760ustar00rootroot00000000000000{"objectClassName":"entity","notices":{"description":["Service subject to Terms of Use."],"links":[{"href":"http:\/\/rdap-pilot.verisignlabs.com\/terms_of_use","type":"text\/html"}],"title":"Terms of Use"},"publicIds":[{"identifier":"1","type":"IANA Registrar ID"}],"vcardArray":["vcard",[["version",{},"text","4.0"],["fn",{},"text","Verisign, Inc.~VRSN"],["adr",{"type":"work"},"text",["","","21345 Ridgetop Circle","Dulles","VA","20166","US"]],["tel",{"pref":"1","type":["work","voice"]},"uri","tel:"],["tel",{"pref":"1","type":["work","fax"]},"uri","tel:"],["email",{"type":"work"},"text","namestore-admin@verisign.com"]]],"rdapConformance":["rdap_level_0"],"roles":["registrar"],"handle":"1~VRSN","lang":"en-US","events":[{"eventAction":"registration","eventDate":"2004-12-14T08:29:42"},{"eventAction":"last changed","eventDate":"2007-04-28T22:01:52"}]} rdap-0.9.1/test/testdata/rdap/rdap.nic.cz/000077500000000000000000000000001474227142300203045ustar00rootroot00000000000000rdap-0.9.1/test/testdata/rdap/rdap.nic.cz/domain-example.cz.json000066400000000000000000000066551474227142300245260ustar00rootroot00000000000000{"status": ["active"], "fred_nsset": {"nameservers": [{"objectClassName": "nameserver", "handle": "ns2.pipni.cz", "links": [{"href": "https://rdap.nic.cz/nameserver/ns2.pipni.cz", "type": "application/rdap+json", "rel": "self", "value": "https://rdap.nic.cz/nameserver/ns2.pipni.cz"}], "ldhName": "ns2.pipni.cz"}, {"objectClassName": "nameserver", "handle": "ns3.pipni.cz", "links": [{"href": "https://rdap.nic.cz/nameserver/ns3.pipni.cz", "type": "application/rdap+json", "rel": "self", "value": "https://rdap.nic.cz/nameserver/ns3.pipni.cz"}], "ldhName": "ns3.pipni.cz"}, {"objectClassName": "nameserver", "handle": "ns.pipni.cz", "links": [{"href": "https://rdap.nic.cz/nameserver/ns.pipni.cz", "type": "application/rdap+json", "rel": "self", "value": "https://rdap.nic.cz/nameserver/ns.pipni.cz"}], "ldhName": "ns.pipni.cz"}], "objectClassName": "fred_nsset", "handle": "NSS:PIPNI:1", "links": [{"href": "https://rdap.nic.cz/fred_nsset/NSS:PIPNI:1", "type": "application/rdap+json", "rel": "self", "value": "https://rdap.nic.cz/fred_nsset/NSS:PIPNI:1"}]}, "handle": "example.cz", "links": [{"href": "https://rdap.nic.cz/domain/example.cz", "type": "application/rdap+json", "rel": "self", "value": "https://rdap.nic.cz/domain/example.cz"}], "port43": "whois.nic.cz", "nameservers": [{"objectClassName": "nameserver", "handle": "ns2.pipni.cz", "links": [{"href": "https://rdap.nic.cz/nameserver/ns2.pipni.cz", "type": "application/rdap+json", "rel": "self", "value": "https://rdap.nic.cz/nameserver/ns2.pipni.cz"}], "ldhName": "ns2.pipni.cz"}, {"objectClassName": "nameserver", "handle": "ns3.pipni.cz", "links": [{"href": "https://rdap.nic.cz/nameserver/ns3.pipni.cz", "type": "application/rdap+json", "rel": "self", "value": "https://rdap.nic.cz/nameserver/ns3.pipni.cz"}], "ldhName": "ns3.pipni.cz"}, {"objectClassName": "nameserver", "handle": "ns.pipni.cz", "links": [{"href": "https://rdap.nic.cz/nameserver/ns.pipni.cz", "type": "application/rdap+json", "rel": "self", "value": "https://rdap.nic.cz/nameserver/ns.pipni.cz"}], "ldhName": "ns.pipni.cz"}], "ldhName": "example.cz", "entities": [{"objectClassName": "entity", "handle": "SB:EXAMPLE", "links": [{"href": "https://rdap.nic.cz/entity/SB:EXAMPLE", "type": "application/rdap+json", "rel": "self", "value": "https://rdap.nic.cz/entity/SB:EXAMPLE"}], "roles": ["registrant"]}, {"objectClassName": "entity", "handle": "REG-INTERNET-CZ", "roles": ["registrar"]}, {"objectClassName": "entity", "handle": "EXAMPLE", "links": [{"href": "https://rdap.nic.cz/entity/EXAMPLE", "type": "application/rdap+json", "rel": "self", "value": "https://rdap.nic.cz/entity/EXAMPLE"}], "roles": ["administrative"]}], "rdapConformance": ["rdap_level_0", "fred_version_0"], "notices": [{"description": ["(c) 2015 CZ.NIC, z.s.p.o.\n\nIntended use of supplied data and information\n\nData contained in the domain name register, as well as information supplied through public information services of CZ.NIC association, are appointed only for purposes connected with Internet network administration and operation, or for the purpose of legal or other similar proceedings, in process as regards a matter connected particularly with holding and using a concrete domain name.\n"], "title": "Disclaimer"}], "objectClassName": "domain", "events": [{"eventAction": "registration", "eventDate": "2004-08-30T22:55:00+00:00"}, {"eventAction": "expiration", "eventDate": "2019-08-30T12:00:00+00:00"}, {"eventAction": "transfer", "eventDate": "2007-01-25T02:05:00+00:00"}]}rdap-0.9.1/test/testdata/rdap/rdap.nic.cz/nameserver-ns2.pipni.cz.json000066400000000000000000000014651474227142300256050ustar00rootroot00000000000000{"handle": "ns2.pipni.cz", "links": [{"href": "https://rdap.nic.cz/nameserver/ns2.pipni.cz", "type": "application/rdap+json", "rel": "self", "value": "https://rdap.nic.cz/nameserver/ns2.pipni.cz"}], "ldhName": "ns2.pipni.cz", "rdapConformance": ["rdap_level_0"], "notices": [{"description": ["(c) 2015 CZ.NIC, z.s.p.o.\n\nIntended use of supplied data and information\n\nData contained in the domain name register, as well as information supplied through public information services of CZ.NIC association, are appointed only for purposes connected with Internet network administration and operation, or for the purpose of legal or other similar proceedings, in process as regards a matter connected particularly with holding and using a concrete domain name.\n"], "title": "Disclaimer"}], "objectClassName": "nameserver"}rdap-0.9.1/test/testdata/x509_auth/000077500000000000000000000000001474227142300167735ustar00rootroot00000000000000rdap-0.9.1/test/testdata/x509_auth/client.key000066400000000000000000000032131474227142300207620ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAtnanET5YMcWLLbS/rBqp9w0ili9X9vFALC9UblI1YvvDQ4ua nylqvi+s4QFI3byRaHN/tOPiQUUy/OEoKqlnj9snD3TwBSxvmOgZyofULJ05LTu/ E3Jfe8oo6cto0heDTo4SgBNdchqfj5VH3igjuZmpeX4Q/9yGfn9qXon02BVThfyg LhGPtdBXVCGWm/ulLTqXUk4ZjCCJIApJtCl33GrTe5sVPi494/cdXToHIme/jK8P SJouVMqWgq5SttGqmCqfGlPQA17FVVUwzGLmVKUvu09RDWRNHRHAusPzqW4+gQHT VJf9zQqDx9CtugCriPdIR+wdobS3eZthuenbNQIDAQABAoIBAAFES56kByq5S2ES 2y3PtZRgg/f09jBhtmdYIMbvTS2Vv+JNKtKmD/aoEBQz1RStBXlrK9zOpDh9yX/V 9FhveqeWBuxljHEMwO2X1u4ACWoR4V6+BnKVHbKXUbdl0SF8Rk4aUGiROdnb83Wk vK3/K1hTh8ivJmEQX+Kq8cscPBXv9PN19POd5Jmo0LZnKGlmpADtLhs27Dd5dldB JhfiGitqN1RIHVvNqg8AyIibGV8nRvuwE5LLQEecqaIhdzXB1twIkLZ5LayRYf8P jf9g/BbzbH4RwBIaXov06qMLZ5uGsUG8JFtkFtPwYPhfdz6UDxs73dFGUVDuMApw d3ECpQECgYEA7p/pvszXI1SHfl/vbWKF46N8GaXZwrtgUlqUWKGVzSWewlVg+P9L ETFIeG6PDNiz6cLdIx056SmGc2ZWVVBpEijRLw9Kb1HCaJ/wiZKeZWL3UHPMJt/N Si5f2akuWktXmTfj/674vTTV2s01UjcDC1PM5eRDZ4dQX5KuGjMGhHUCgYEAw7/d wqy1hNNEIxlnJzjeLc7YWpFueFmm/8rPxJswDOJ3QLGC5Jc1ZbYlnxZCVvshePkM SjPqEyvceU987qVlb28cQMKEgyhLlmkgipGMifuEjNtZc2dLQKOagCDAjidYCKHJ 8Sq3UpVGty8/3OS4CUGPEUpDqrX1hR93wVnUI8ECgYBrazVQHOokD2NHMmyXsyhO h0PQT+atUKhVqkzpcSP8S5kiJkkXDeV/Ac572FkxEUQ8UOo3amqtWXIa9NCcayxB bnpsUtfRMN7xNj6Tz5raTSjD4LzgKxNA94tScmRZZV0zrgNHED8M/YHfk00Ti4wl Rz4PpyforPMzctZJGPswGQKBgQC6KWOGe+gZjS5UzxjqFUw/dmMOJxdPf7uxsrjL eudEqa/OJ8ObEC7pL0QyOuIWhLj9qqTEgQDRALqp6C2hbEy+oIXXFOcfMRhJ0Grx PaDRrREPQKCefxLzQ2RxDDT0PHidpPg/0mcMNAlPt7Ddq+tWajHcuKsH1ArOcvHa QUreQQKBgDrgAharKt75z15pM1lz/Uhei+gsalR26tJ+CIpjHNo0xINT00ePaBy0 /N3vc67Lzqw9UfHiGclTAQ3tL5wvpdLnmvVx/3XAP/k+yuqw1Kh36dhBOS8Wz5G/ kAyni32Zttud99lTE8Zg5U+stIcE4EDD91ERKlIWKaSiL1g+CQqc -----END RSA PRIVATE KEY----- rdap-0.9.1/test/testdata/x509_auth/client.pem000066400000000000000000000101261474227142300207540ustar00rootroot00000000000000Certificate: Data: Version: 3 (0x2) Serial Number: 1 (0x1) Signature Algorithm: sha256WithRSAEncryption Issuer: C=GB, CN=OpenRDAP test root Validity Not Before: Apr 21 17:24:41 2018 GMT Not After : Jan 18 17:24:41 2028 GMT Subject: C=GB, CN=OpenRDAP test client certificate Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: 00:b6:76:a7:11:3e:58:31:c5:8b:2d:b4:bf:ac:1a: a9:f7:0d:22:96:2f:57:f6:f1:40:2c:2f:54:6e:52: 35:62:fb:c3:43:8b:9a:9f:29:6a:be:2f:ac:e1:01: 48:dd:bc:91:68:73:7f:b4:e3:e2:41:45:32:fc:e1: 28:2a:a9:67:8f:db:27:0f:74:f0:05:2c:6f:98:e8: 19:ca:87:d4:2c:9d:39:2d:3b:bf:13:72:5f:7b:ca: 28:e9:cb:68:d2:17:83:4e:8e:12:80:13:5d:72:1a: 9f:8f:95:47:de:28:23:b9:99:a9:79:7e:10:ff:dc: 86:7e:7f:6a:5e:89:f4:d8:15:53:85:fc:a0:2e:11: 8f:b5:d0:57:54:21:96:9b:fb:a5:2d:3a:97:52:4e: 19:8c:20:89:20:0a:49:b4:29:77:dc:6a:d3:7b:9b: 15:3e:2e:3d:e3:f7:1d:5d:3a:07:22:67:bf:8c:af: 0f:48:9a:2e:54:ca:96:82:ae:52:b6:d1:aa:98:2a: 9f:1a:53:d0:03:5e:c5:55:55:30:cc:62:e6:54:a5: 2f:bb:4f:51:0d:64:4d:1d:11:c0:ba:c3:f3:a9:6e: 3e:81:01:d3:54:97:fd:cd:0a:83:c7:d0:ad:ba:00: ab:88:f7:48:47:ec:1d:a1:b4:b7:79:9b:61:b9:e9: db:35 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Authority Key Identifier: keyid:8C:7E:96:F0:2B:43:F6:65:40:DF:91:64:EB:0F:16:00:BD:0D:38:0F X509v3 Basic Constraints: CA:FALSE X509v3 Key Usage: Digital Signature X509v3 Extended Key Usage: TLS Web Client Authentication Signature Algorithm: sha256WithRSAEncryption ad:9a:2e:28:1e:c6:a2:95:51:53:bb:f9:53:cc:1f:3d:e5:df: 3a:c5:f2:74:1e:56:2a:4f:62:64:1b:5c:12:c2:11:bc:2e:50: ee:48:e8:5b:40:f8:17:5e:49:af:de:b3:8b:32:da:42:33:d7: 1d:39:2f:29:93:95:7a:4e:37:ad:cb:f6:1e:ba:18:c0:55:07: f1:5b:02:68:08:25:67:4d:41:44:5c:c0:c6:d6:5d:e5:26:d3: 30:f6:f0:a0:cd:cd:2a:18:db:5c:59:1b:79:df:29:33:e4:f5: 28:08:fc:9b:7f:64:74:27:01:5c:e9:ed:ef:37:b9:10:70:b7: f9:22:08:76:02:63:43:94:06:c6:21:44:e2:7e:5a:78:ce:5e: 86:f9:0d:ee:3a:3d:b5:42:11:d4:83:ed:32:b5:81:da:64:f9: 4a:3b:e9:20:b3:18:70:4d:57:11:ed:4e:b8:98:88:03:2a:b1: 06:96:f3:0d:da:92:5a:0f:63:1d:f7:b7:03:9e:0f:7c:c8:5c: 8c:f4:01:ae:08:58:47:02:5a:44:b6:c9:db:c2:fa:ae:61:6d: 4f:90:6d:67:2f:66:33:ae:32:72:4f:b9:8a:6c:06:83:e3:2e: 71:45:ee:27:37:0e:5d:95:39:7e:7f:9a:9a:19:f2:98:2a:80: b5:4d:eb:eb -----BEGIN CERTIFICATE----- MIIDLTCCAhWgAwIBAgIBATANBgkqhkiG9w0BAQsFADAqMQswCQYDVQQGEwJHQjEb MBkGA1UEAwwST3BlblJEQVAgdGVzdCByb290MB4XDTE4MDQyMTE3MjQ0MVoXDTI4 MDExODE3MjQ0MVowODELMAkGA1UEBhMCR0IxKTAnBgNVBAMMIE9wZW5SREFQIHRl c3QgY2xpZW50IGNlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAtnanET5YMcWLLbS/rBqp9w0ili9X9vFALC9UblI1YvvDQ4uanylqvi+s 4QFI3byRaHN/tOPiQUUy/OEoKqlnj9snD3TwBSxvmOgZyofULJ05LTu/E3Jfe8oo 6cto0heDTo4SgBNdchqfj5VH3igjuZmpeX4Q/9yGfn9qXon02BVThfygLhGPtdBX VCGWm/ulLTqXUk4ZjCCJIApJtCl33GrTe5sVPi494/cdXToHIme/jK8PSJouVMqW gq5SttGqmCqfGlPQA17FVVUwzGLmVKUvu09RDWRNHRHAusPzqW4+gQHTVJf9zQqD x9CtugCriPdIR+wdobS3eZthuenbNQIDAQABo1AwTjAfBgNVHSMEGDAWgBSMfpbw K0P2ZUDfkWTrDxYAvQ04DzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIHgDATBgNVHSUE DDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEArZouKB7GopVRU7v5U8wf PeXfOsXydB5WKk9iZBtcEsIRvC5Q7kjoW0D4F15Jr96zizLaQjPXHTkvKZOVek43 rcv2HroYwFUH8VsCaAglZ01BRFzAxtZd5SbTMPbwoM3NKhjbXFkbed8pM+T1KAj8 m39kdCcBXOnt7ze5EHC3+SIIdgJjQ5QGxiFE4n5aeM5ehvkN7jo9tUIR1IPtMrWB 2mT5SjvpILMYcE1XEe1OuJiIAyqxBpbzDdqSWg9jHfe3A54PfMhcjPQBrghYRwJa RLbJ28L6rmFtT5BtZy9mM64yck+5imwGg+MucUXuJzcOXZU5fn+amhnymCqAtU3r 6w== -----END CERTIFICATE----- rdap-0.9.1/test/testdata/x509_auth/root.pem000066400000000000000000000022031474227142300204560ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDJzCCAg+gAwIBAgIJAOJ8iTteb9F7MA0GCSqGSIb3DQEBCwUAMCoxCzAJBgNV BAYTAkdCMRswGQYDVQQDDBJPcGVuUkRBUCB0ZXN0IHJvb3QwHhcNMTgwNDIxMTcw NjQ1WhcNMjgwNDE4MTcwNjQ1WjAqMQswCQYDVQQGEwJHQjEbMBkGA1UEAwwST3Bl blJEQVAgdGVzdCByb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA 1wpq5LcaQnTEoMXYj7bd415laFvUeq8nYE4lk/bjZIA6FVMjWvwpEbVsc80nID/k nv+uaMLCOsFV7lnoo6Fd/SGCCNN0pmGTuOUfnyRgvQYuN3xMO0OY98jGh6XLHR6P vqTUZP1ukiQc6HQ/1dEXKQCoKUjaF+lZ/AbWtKt00HcD9OYdwfX8yJNBwVtltPGv fDzyZz7gUmsXyaAo30qbvJLp3saeSSWNshn+ODyDpqxjoC+WRMYRWt1tnGjN99VI WgEvm8iSbueXKSmfTPKgZmOE9gqIuiC3sEjFZE2cQMXcXG7wkXK22AWnAUERMNRv kdrr0ZzF+RXWiAFQb0aa6wIDAQABo1AwTjAdBgNVHQ4EFgQUjH6W8CtD9mVA35Fk 6w8WAL0NOA8wHwYDVR0jBBgwFoAUjH6W8CtD9mVA35Fk6w8WAL0NOA8wDAYDVR0T BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAnaOlhFvNwr0J7hBCrDqIfpSQaLH2 ooo++ZofOxkQV25Daei659Kek/Jyb+FmuHqnZ5Tkb7PSAdh4b4eopiUp8JTENbCE T+LITADI8zxIWIQcx78FG1nv6AewaiGmp6SuFfzySlkM/pht0TdbrELcX2rtD7O+ I0cgfMIKijWGRWFr3sL2Ab8rF2BuImnljPXqo+QCQDA9dy86QAIQAoQVM8pQHiS+ YbDYWHl9UQHvD4RGvh/38SNFCYJjhIQ20Jk/vXnPACOQ8zo5jgj8Xo84UIKNB5ue UWlbWs9IQQMPS001sv7lliQnPJCi7ULKGAQWX7jgardWeSwQedKKQMYi5Q== -----END CERTIFICATE----- rdap-0.9.1/vcard.go000066400000000000000000000244771474227142300141210ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap import ( "encoding/json" "fmt" "strconv" "strings" ) // VCard represents a vCard. // // A vCard represents information about an individual or entity. It can include // a name, telephone number, e-mail, delivery address, and other information. // // There are several vCard text formats. This implementation encodes/decodes the // jCard format used by RDAP, as defined in https://tools.ietf.org/html/rfc7095. // // A jCard consists of an array of properties (e.g. "fn", "tel") describing the // individual or entity. Properties may be repeated, e.g. to represent multiple // telephone numbers. RFC6350 documents a set of standard properties. // // RFC7095 describes the JSON document format, which looks like: // ["vcard", [ // [ // ["version", {}, "text", "4.0"], // ["fn", {}, "text", "Joe Appleseed"], // ["tel", { // "type":["work", "voice"], // }, // "uri", // "tel:+1-555-555-1234;ext=555" // ], // ... // ] // ] type VCard struct { Properties []*VCardProperty } // VCardProperty represents a single vCard property. // // Each vCard property has four fields, these are: // Name Parameters Type Value // ----- -------------------------- ----- ----------------------------- // ["tel", {"type":["work", "voice"]}, "uri", "tel:+1-555-555-1234;ext=555"] type VCardProperty struct { Name string // vCard parameters can be a string, or array of strings. // // To simplify our usage, single strings are represented as an array of // length one. Parameters map[string][]string Type string // A property value can be a simple type (string/float64/bool/nil), or be // an array. Arrays can be nested, and can contain a mixture of types. // // Value is one of the following: // * string // * float64 // * bool // * nil // * []interface{}. Can contain a mixture of these five types. // // To retrieve the property value flattened into a []string, use Values(). Value interface{} } // Values returns a simplified representation of the VCardProperty value. // // This is convenient for accessing simple unstructured data (e.g. "fn", "tel"). // // The simplified []string representation is created by flattening the // (potentially nested) VCardProperty value, and converting all values to strings. func (p *VCardProperty) Values() []string { strings := make([]string, 0, 1) p.appendValueStrings(p.Value, &strings) return strings } func (p *VCardProperty) appendValueStrings(v interface{}, strings *[]string) { switch v := v.(type) { case nil: *strings = append(*strings, "") case bool: *strings = append(*strings, strconv.FormatBool(v)) case float64: *strings = append(*strings, strconv.FormatFloat(v, 'f', -1, 64)) case string: *strings = append(*strings, v) case []interface{}: for _, v2 := range v { p.appendValueStrings(v2, strings) } default: panic("Unknown type") } } // String returns the vCard as a multiline human readable string. For example: // // vCard[ // version (type=text, parameters=map[]): [4.0] // mixed (type=text, parameters=map[]): [abc true 42 [def false 43]] // ] // // This is intended for debugging only, and is not machine parsable. func (v *VCard) String() string { s := make([]string, 0, len(v.Properties)) for _, s2 := range v.Properties { s = append(s, s2.String()) } return "vCard[\n" + strings.Join(s, "\n") + "\n]" } // String returns the VCardProperty as a human readable string. For example: // // mixed (type=text, parameters=map[]): [abc true 42 [def false 43]] // // This is intended for debugging only, and is not machine parsable. func (p *VCardProperty) String() string { return fmt.Sprintf(" %s (type=%s, parameters=%v): %v", p.Name, p.Type, p.Parameters, p.Value) } // NewVCard creates a VCard from jsonBlob. func NewVCard(jsonBlob []byte) (*VCard, error) { var top []interface{} err := json.Unmarshal(jsonBlob, &top) if err != nil { return nil, err } var vcard *VCard vcard, err = newVCardImpl(top) return vcard, err } func newVCardImpl(src interface{}) (*VCard, error) { top, ok := src.([]interface{}) if !ok || len(top) != 2 { return nil, vCardError("structure is not a jCard (expected len=2 top level array)") } else if s, ok := top[0].(string); !(ok && s == "vcard") { return nil, vCardError("structure is not a jCard (missing 'vcard')") } var properties []interface{} properties, ok = top[1].([]interface{}) if !ok { return nil, vCardError("structure is not a jCard (bad properties array)") } v := &VCard{ Properties: make([]*VCardProperty, 0, len(properties)), } var p interface{} for _, p = range top[1].([]interface{}) { var a []interface{} var ok bool a, ok = p.([]interface{}) if !ok { return nil, vCardError("jCard property was not an array") } else if len(a) < 4 { return nil, vCardError("jCard property too short (>=4 array elements required)") } name, ok := a[0].(string) if !ok { return nil, vCardError("jCard property name invalid") } var parameters map[string][]string var err error parameters, err = readParameters(a[1]) if err != nil { return nil, err } propertyType, ok := a[2].(string) if !ok { return nil, vCardError("jCard property type invalid") } var value interface{} if len(a) == 4 { value, err = readValue(a[3], 0) } else { value, err = readValue(a[3:], 0) } if err != nil { return nil, err } property := &VCardProperty{ Name: name, Type: propertyType, Parameters: parameters, Value: value, } v.Properties = append(v.Properties, property) } return v, nil } // Get returns a list of the vCard Properties with VCardProperty name |name|. func (v *VCard) Get(name string) []*VCardProperty { var properties []*VCardProperty for _, p := range v.Properties { if p.Name == name { properties = append(properties, p) } } return properties } // GetFirst returns the first vCard Property with name |name|. // // TODO(tfh): Implement "pref" ordering, instead of taking the first listed property? func (v *VCard) GetFirst(name string) *VCardProperty { properties := v.Get(name) if len(properties) == 0 { return nil } return properties[0] } func vCardError(e string) error { return fmt.Errorf("jCard error: %s", e) } func readParameters(p interface{}) (map[string][]string, error) { params := map[string][]string{} if _, ok := p.(map[string]interface{}); !ok { return nil, vCardError("jCard parameters invalid") } for k, v := range p.(map[string]interface{}) { if s, ok := v.(string); ok { params[k] = append(params[k], s) } else if arr, ok := v.([]interface{}); ok { for _, value := range arr { if s, ok := value.(string); ok { params[k] = append(params[k], s) } } } } return params, nil } func readValue(value interface{}, depth int) (interface{}, error) { switch value := value.(type) { case nil: return nil, nil case string: return value, nil case bool: return value, nil case float64: return value, nil case []interface{}: if depth == 3 { return "", vCardError("Structured value too deep") } result := make([]interface{}, 0, len(value)) for _, v2 := range value { v3, err := readValue(v2, depth+1) if err != nil { return nil, err } result = append(result, v3) } return result, nil default: return nil, vCardError("Unknown JSON datatype in jCard value") } } func (v *VCard) getFirstPropertySingleString(name string) string { property := v.GetFirst(name) if property == nil { return "" } return strings.Join(property.Values(), " ") } // Name returns the VCard's name. e.g. "John Smith". func (v *VCard) Name() string { return v.getFirstPropertySingleString("fn") } // POBox returns the address's PO Box. // // Returns empty string if no address is present. func (v *VCard) POBox() string { return v.getFirstAddressField(0) } // ExtendedAddress returns the "extended address", e.g. an apartment // or suite number. // // Returns empty string if no address is present. func (v *VCard) ExtendedAddress() string { return v.getFirstAddressField(1) } // StreetAddress returns the street address. // // Returns empty string if no address is present. func (v *VCard) StreetAddress() string { return v.getFirstAddressField(2) } // Locality returns the address locality. // // Returns empty string if no address is present. func (v *VCard) Locality() string { return v.getFirstAddressField(3) } // Region returns the address region (e.g. state or province). // // Returns empty string if no address is present. func (v *VCard) Region() string { return v.getFirstAddressField(4) } // PostalCode returns the address postal code (e.g. zip code). // // Returns empty string if no address is present. func (v *VCard) PostalCode() string { return v.getFirstAddressField(5) } // Country returns the address country name. // // This is the full country name. // // Returns empty string if no address is present. func (v *VCard) Country() string { return v.getFirstAddressField(6) } // Tel returns the VCard's first (voice) telephone number. // // Returns empty string if the VCard contains no suitable telephone number. func (v *VCard) Tel() string { properties := v.Get("tel") for _, p := range properties { isVoice := false if types, ok := p.Parameters["type"]; ok { for _, t := range types { if t == "voice" { isVoice = true break } } } else { isVoice = true } if isVoice && len(p.Values()) > 0 { return (p.Values())[0] } } return "" } // Fax returns the VCard's first fax number. // // Returns empty string if the VCard contains no fax number. func (v *VCard) Fax() string { properties := v.Get("tel") for _, p := range properties { if types, ok := p.Parameters["type"]; ok { for _, t := range types { if t == "fax" { if len(p.Values()) > 0 { return (p.Values())[0] } } } } } return "" } // Email returns the VCard's first email address. // // Returns empty string if the VCard contains no email addresses. func (v *VCard) Email() string { return v.getFirstPropertySingleString("email") } func (v *VCard) getFirstAddressField(index int) string { adr := v.GetFirst("adr") if adr == nil { return "" } values := adr.Values() if index >= len(values) { return "" } return values[index] } rdap-0.9.1/vcard_test.go000066400000000000000000000071241474227142300151460ustar00rootroot00000000000000// OpenRDAP // Copyright 2017 Tom Harwood // MIT License, see the LICENSE file. package rdap import ( "reflect" "testing" "github.com/openrdap/rdap/test" ) func TestVCardErrors(t *testing.T) { filenames := []string{ "jcard/error_invalid_json.json", "jcard/error_bad_top_type.json", "jcard/error_bad_vcard_label.json", "jcard/error_bad_properties_array.json", "jcard/error_bad_property_size.json", "jcard/error_bad_property_name.json", "jcard/error_bad_property_type.json", "jcard/error_bad_property_parameters.json", "jcard/error_bad_property_parameters_2.json", "jcard/error_bad_property_nest_depth.json", } for _, filename := range filenames { j, err := NewVCard(test.LoadFile(filename)) if j != nil || err == nil { t.Errorf("jCard with error unexpectedly parsed %s %v %s\n", filename, j, err) } } } func TestVCardExample(t *testing.T) { j, err := NewVCard(test.LoadFile("jcard/example.json")) if j == nil || err != nil { t.Errorf("jCard parse failed %v %s\n", j, err) } numProperties := 17 if len(j.Properties) != numProperties { t.Errorf("Got %d properties expected %d", len(j.Properties), numProperties) } expectedVersion := &VCardProperty{ Name: "version", Parameters: make(map[string][]string), Type: "text", Value: "4.0", } if !reflect.DeepEqual(j.Get("version")[0], expectedVersion) { t.Errorf("version field incorrect") } expectedN := &VCardProperty{ Name: "n", Parameters: make(map[string][]string), Type: "text", Value: []interface{}{"Perreault", "Simon", "", "", []interface{}{"ing. jr", "M.Sc."}}, } expectedFlatN := []string{ "Perreault", "Simon", "", "", "ing. jr", "M.Sc.", } if !reflect.DeepEqual(j.Get("n")[0], expectedN) { t.Errorf("n field incorrect") } if !reflect.DeepEqual(j.Get("n")[0].Values(), expectedFlatN) { t.Errorf("n flat value incorrect") } expectedTel0 := &VCardProperty{ Name: "tel", Parameters: map[string][]string{"type": []string{"work", "voice"}, "pref": []string{"1"}}, Type: "uri", Value: "tel:+1-418-656-9254;ext=102", } if !reflect.DeepEqual(j.Get("tel")[0], expectedTel0) { t.Errorf("tel[0] field incorrect") } } func TestVCardMixedDatatypes(t *testing.T) { j, err := NewVCard(test.LoadFile("jcard/mixed.json")) if j == nil || err != nil { t.Errorf("jCard parse failed %v %s\n", j, err) } expectedMixed := &VCardProperty{ Name: "mixed", Parameters: make(map[string][]string), Type: "text", Value: []interface{}{"abc", true, float64(42), nil, []interface{}{"def", false, float64(43)}}, } expectedFlatMixed := []string{ "abc", "true", "42", "", "def", "false", "43", } if !reflect.DeepEqual(j.Get("mixed")[0], expectedMixed) { t.Errorf("mixed field incorrect") } flattened := j.Get("mixed")[0].Values() if !reflect.DeepEqual(flattened, expectedFlatMixed) { t.Errorf("mixed flat value incorrect %v", flattened) } } func TestVCardQuickAccessors(t *testing.T) { j, err := NewVCard(test.LoadFile("jcard/example.json")) if j == nil || err != nil { t.Errorf("jCard parse failed %v %s\n", j, err) } got := []string{ j.Name(), j.POBox(), j.ExtendedAddress(), j.StreetAddress(), j.Locality(), j.Region(), j.PostalCode(), j.Country(), j.Tel(), j.Fax(), j.Email(), } expected := []string{ "Simon Perreault", "", "Suite D2-630", "2875 Laurier", "Quebec", "QC", "G1V 2M2", "Canada", "tel:+1-418-656-9254;ext=102", "", "simon.perreault@viagenie.ca", } if !reflect.DeepEqual(got, expected) { t.Errorf("Got %v expected %v\n", got, expected) } }