diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
new file mode 100644
index 0000000..8704165
--- /dev/null
+++ b/.github/workflows/go.yml
@@ -0,0 +1,32 @@
+name: 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 ./...
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 622d6b5..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-language: go
-
-go:
- - 1.7
- - master
-
-script:
- - go test -v ./...
diff --git a/README.md b/README.md
index 288aa1f..b8d1889 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@ https://www.openrdap.org/demo - live demo
## Features
* Command line RDAP client
+* Output formats: text, JSON, WHOIS style
* Query types supported:
* ip
* domain
@@ -24,11 +25,10 @@ https://www.openrdap.org/demo - live demo
* nameserver-search-by-ip
* entity-search
* entity-search-by-handle
-* Query bootstrapping (automatic RDAP server URL detection for ip/domain/autnum/(experimental) entity queries)
+* Automatic server detection for ip/domain/autnum/entities
+* Object tags support
* 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
@@ -36,7 +36,7 @@ This program uses Go. The Go compiler is available from https://golang.org/.
To install:
- go get -u github.com/openrdap/rdap/cmd/rdap
+ go install github.com/openrdap/rdap/cmd/rdap@master
This will install the "rdap" binary in your $GOPATH/go/bin directory. Try running:
@@ -45,10 +45,16 @@ This will install the "rdap" binary in your $GOPATH/go/bin directory. Try runnin
## Usage
| Query type | Usage |
-| --- | --- |
+| ------------------------- | ------------------------------------------------------------------------ |
| Domain (.com) | rdap -v example.com |
-| Network | rdap -v 2001:db8:: |
-| Autnum | rdap -v AS15169 |
+| IPv4 Address | rdap -v 192.0.2.0 |
+| IPv6 Address | rdap -v 2001:db8:: |
+| Autonomous System (ASN) | rdap -v AS15169 |
+| Entity (with object tag) | rdap -v OPS4-RIPE |
+
+## Advanced usage (server must be specified using -s; not all servers support all query types)
+| Query type | Usage |
+| ------------------------- | ------------------------------------------------------------------------ |
| 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 |
@@ -61,24 +67,679 @@ This will install the "rdap" binary in your $GOPATH/go/bin directory. Try runnin
See https://www.openrdap.org/docs.
+## Example output
+
+Click the examples to see the output:
+
+
+rdap example.com
+
+```Domain:
+ Domain Name: EXAMPLE.COM
+ Handle: 2336799_DOMAIN_COM-VRSN
+ Status: client delete prohibited
+ Status: client transfer prohibited
+ Status: client update prohibited
+ Conformance: rdap_level_0
+ Conformance: icann_rdap_technical_implementation_guide_0
+ Conformance: icann_rdap_response_profile_0
+ Notice:
+ Title: Terms of Use
+ Description: Service subject to Terms of Use.
+ Link: https://www.verisign.com/domain-names/registration-data-access-protocol/terms-service/index.xhtml
+ Notice:
+ Title: Status Codes
+ Description: For more information on domain status codes, please visit https://icann.org/epp
+ Link: https://icann.org/epp
+ Notice:
+ Title: RDDS Inaccuracy Complaint Form
+ Description: URL of the ICANN RDDS Inaccuracy Complaint Form: https://icann.org/wicf
+ Link: https://icann.org/wicf
+ Link: https://rdap.verisign.com/com/v1/domain/EXAMPLE.COM
+ Event:
+ Action: registration
+ Date: 1995-08-14T04:00:00Z
+ Event:
+ Action: expiration
+ Date: 2023-08-13T04:00:00Z
+ Event:
+ Action: last changed
+ Date: 2023-05-12T15:13:35Z
+ Event:
+ Action: last update of RDAP database
+ Date: 2023-05-16T20:36:06Z
+ Secure DNS:
+ Delegation Signed: true
+ DSData:
+ Key Tag: 370
+ Algorithm: 13
+ Digest: BE74359954660069D5C63D200C39F5603827D7DD02B56F120EE9F3A86764247C
+ DigestType: 2
+ Entity:
+ Handle: 376
+ Public ID:
+ Type: IANA Registrar ID
+ Identifier: 376
+ Role: registrar
+ vCard version: 4.0
+ vCard fn: RESERVED-Internet Assigned Numbers Authority
+ Entity:
+ Role: abuse
+ vCard version: 4.0
+ Nameserver:
+ Nameserver: A.IANA-SERVERS.NET
+ Nameserver:
+ Nameserver: B.IANA-SERVERS.NET
+```
+
+
+
+
+rdap 8.8.8.8
+
+```IP Network:
+ Handle: NET-8-8-8-0-1
+ Start Address: 8.8.8.0
+ End Address: 8.8.8.255
+ IP Version: v4
+ Name: LVLT-GOGL-8-8-8
+ Type: ALLOCATION
+ ParentHandle: NET-8-0-0-0-1
+ Status: active
+ Port43: whois.arin.net
+ Notice:
+ Title: Terms of Service
+ Description: By using the ARIN RDAP/Whois service, you are agreeing to the RDAP/Whois Terms of Use
+ Link: https://www.arin.net/resources/registry/whois/tou/
+ Notice:
+ Title: Whois Inaccuracy Reporting
+ Description: If you see inaccuracies in the results, please visit:
+ Link: https://www.arin.net/resources/registry/whois/inaccuracy_reporting/
+ Notice:
+ Title: Copyright Notice
+ Description: Copyright 1997-2023, American Registry for Internet Numbers, Ltd.
+ Entity:
+ Handle: GOGL
+ Port43: whois.arin.net
+ Remark:
+ Title: Registration Comments
+ Description: Please note that the recommended way to file abuse complaints are located in the following links.
+ Description: To report abuse and illegal activity: https://www.google.com/contact/
+ Description: For legal requests: http://support.google.com/legal
+ Description: Regards,
+ Description: The Google Team
+ Link: https://rdap.arin.net/registry/entity/GOGL
+ Link: https://whois.arin.net/rest/org/GOGL
+ Event:
+ Action: last changed
+ Date: 2019-10-31T15:45:45-04:00
+ Event:
+ Action: registration
+ Date: 2000-03-30T00:00:00-05:00
+ Role: registrant
+ vCard version: 4.0
+ vCard fn: Google LLC
+ vCard kind: org
+ Entity:
+ Handle: ABUSE5250-ARIN
+ Status: validated
+ Port43: whois.arin.net
+ Remark:
+ Title: Registration Comments
+ Description: Please note that the recommended way to file abuse complaints are located in the following links.
+ Description: To report abuse and illegal activity: https://www.google.com/contact/
+ Description: For legal requests: http://support.google.com/legal
+ Description: Regards,
+ Description: The Google Team
+ Link: https://rdap.arin.net/registry/entity/ABUSE5250-ARIN
+ Link: https://whois.arin.net/rest/poc/ABUSE5250-ARIN
+ Event:
+ Action: last changed
+ Date: 2022-10-24T08:43:11-04:00
+ Event:
+ Action: registration
+ Date: 2015-11-06T15:36:35-05:00
+ Role: abuse
+ vCard version: 4.0
+ vCard fn: Abuse
+ vCard org: Abuse
+ vCard kind: group
+ vCard email: network-abuse@google.com
+ vCard tel: +1-650-253-0000
+ Entity:
+ Handle: ZG39-ARIN
+ Status: validated
+ Port43: whois.arin.net
+ Link: https://rdap.arin.net/registry/entity/ZG39-ARIN
+ Link: https://whois.arin.net/rest/poc/ZG39-ARIN
+ Event:
+ Action: last changed
+ Date: 2022-11-10T07:12:44-05:00
+ Event:
+ Action: registration
+ Date: 2000-11-30T13:54:08-05:00
+ Role: technical
+ Role: administrative
+ vCard version: 4.0
+ vCard fn: Google LLC
+ vCard org: Google LLC
+ vCard kind: group
+ vCard email: arin-contact@google.com
+ vCard tel: +1-650-253-0000
+ Link: https://rdap.arin.net/registry/ip/8.8.8.0
+ Link: https://whois.arin.net/rest/net/NET-8-8-8-0-1
+ Link: https://rdap.arin.net/registry/ip/8.0.0.0/9
+ Event:
+ Action: last changed
+ Date: 2014-03-14T16:52:05-04:00
+ Event:
+ Action: registration
+ Date: 2014-03-14T16:52:05-04:00
+ cidr0_cidrs:
+ v4prefix: 8.8.8.0
+ length: 24
+```
+
+
+
+
+rdap --json AS15169
+
+```
+{
+ "rdapConformance": [
+ "nro_rdap_profile_0",
+ "rdap_level_0",
+ "nro_rdap_profile_asn_flat_0"
+ ],
+ "notices": [
+ {
+ "title": "Terms of Service",
+ "description": [
+ "By using the ARIN RDAP/Whois service, you are agreeing to the RDAP/Whois Terms of Use"
+ ],
+ "links": [
+ {
+ "value": "https://rdap.arin.net/registry/autnum/15169",
+ "rel": "terms-of-service",
+ "type": "text/html",
+ "href": "https://www.arin.net/resources/registry/whois/tou/"
+ }
+ ]
+ },
+ {
+ "title": "Whois Inaccuracy Reporting",
+ "description": [
+ "If you see inaccuracies in the results, please visit: "
+ ],
+ "links": [
+ {
+ "value": "https://rdap.arin.net/registry/autnum/15169",
+ "rel": "inaccuracy-report",
+ "type": "text/html",
+ "href": "https://www.arin.net/resources/registry/whois/inaccuracy_reporting/"
+ }
+ ]
+ },
+ {
+ "title": "Copyright Notice",
+ "description": [
+ "Copyright 1997-2023, American Registry for Internet Numbers, Ltd."
+ ]
+ }
+ ],
+ "handle": "AS15169",
+ "startAutnum": 15169,
+ "endAutnum": 15169,
+ "name": "GOOGLE",
+ "events": [
+ {
+ "eventAction": "last changed",
+ "eventDate": "2012-02-24T09:44:34-05:00"
+ },
+ {
+ "eventAction": "registration",
+ "eventDate": "2000-03-30T00:00:00-05:00"
+ }
+ ],
+ "links": [
+ {
+ "value": "https://rdap.arin.net/registry/autnum/15169",
+ "rel": "self",
+ "type": "application/rdap+json",
+ "href": "https://rdap.arin.net/registry/autnum/15169"
+ },
+ {
+ "value": "https://rdap.arin.net/registry/autnum/15169",
+ "rel": "alternate",
+ "type": "application/xml",
+ "href": "https://whois.arin.net/rest/asn/AS15169"
+ }
+ ],
+ "entities": [
+ {
+ "handle": "GOGL",
+ "vcardArray": [
+ "vcard",
+ [
+ [
+ "version",
+ {},
+ "text",
+ "4.0"
+ ],
+ [
+ "fn",
+ {},
+ "text",
+ "Google LLC"
+ ],
+ [
+ "adr",
+ {
+ "label": "1600 Amphitheatre Parkway\nMountain View\nCA\n94043\nUnited States"
+ },
+ "text",
+ [
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ ""
+ ]
+ ],
+ [
+ "kind",
+ {},
+ "text",
+ "org"
+ ]
+ ]
+ ],
+ "roles": [
+ "registrant"
+ ],
+ "remarks": [
+ {
+ "title": "Registration Comments",
+ "description": [
+ "Please note that the recommended way to file abuse complaints are located in the following links. ",
+ "",
+ "To report abuse and illegal activity: https://www.google.com/contact/",
+ "",
+ "For legal requests: http://support.google.com/legal ",
+ "",
+ "Regards, ",
+ "The Google Team"
+ ]
+ }
+ ],
+ "links": [
+ {
+ "value": "https://rdap.arin.net/registry/autnum/15169",
+ "rel": "self",
+ "type": "application/rdap+json",
+ "href": "https://rdap.arin.net/registry/entity/GOGL"
+ },
+ {
+ "value": "https://rdap.arin.net/registry/autnum/15169",
+ "rel": "alternate",
+ "type": "application/xml",
+ "href": "https://whois.arin.net/rest/org/GOGL"
+ }
+ ],
+ "events": [
+ {
+ "eventAction": "last changed",
+ "eventDate": "2019-10-31T15:45:45-04:00"
+ },
+ {
+ "eventAction": "registration",
+ "eventDate": "2000-03-30T00:00:00-05:00"
+ }
+ ],
+ "entities": [
+ {
+ "handle": "ABUSE5250-ARIN",
+ "vcardArray": [
+ "vcard",
+ [
+ [
+ "version",
+ {},
+ "text",
+ "4.0"
+ ],
+ [
+ "adr",
+ {
+ "label": "1600 Amphitheatre Parkway\nMountain View\nCA\n94043\nUnited States"
+ },
+ "text",
+ [
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ ""
+ ]
+ ],
+ [
+ "fn",
+ {},
+ "text",
+ "Abuse"
+ ],
+ [
+ "org",
+ {},
+ "text",
+ "Abuse"
+ ],
+ [
+ "kind",
+ {},
+ "text",
+ "group"
+ ],
+ [
+ "email",
+ {},
+ "text",
+ "network-abuse@google.com"
+ ],
+ [
+ "tel",
+ {
+ "type": [
+ "work",
+ "voice"
+ ]
+ },
+ "text",
+ "+1-650-253-0000"
+ ]
+ ]
+ ],
+ "roles": [
+ "abuse"
+ ],
+ "remarks": [
+ {
+ "title": "Registration Comments",
+ "description": [
+ "Please note that the recommended way to file abuse complaints are located in the following links.",
+ "",
+ "To report abuse and illegal activity: https://www.google.com/contact/",
+ "",
+ "For legal requests: http://support.google.com/legal ",
+ "",
+ "Regards,",
+ "The Google Team"
+ ]
+ }
+ ],
+ "links": [
+ {
+ "value": "https://rdap.arin.net/registry/autnum/15169",
+ "rel": "self",
+ "type": "application/rdap+json",
+ "href": "https://rdap.arin.net/registry/entity/ABUSE5250-ARIN"
+ },
+ {
+ "value": "https://rdap.arin.net/registry/autnum/15169",
+ "rel": "alternate",
+ "type": "application/xml",
+ "href": "https://whois.arin.net/rest/poc/ABUSE5250-ARIN"
+ }
+ ],
+ "events": [
+ {
+ "eventAction": "last changed",
+ "eventDate": "2022-10-24T08:43:11-04:00"
+ },
+ {
+ "eventAction": "registration",
+ "eventDate": "2015-11-06T15:36:35-05:00"
+ }
+ ],
+ "status": [
+ "validated"
+ ],
+ "port43": "whois.arin.net",
+ "objectClassName": "entity"
+ },
+ {
+ "handle": "ZG39-ARIN",
+ "vcardArray": [
+ "vcard",
+ [
+ [
+ "version",
+ {},
+ "text",
+ "4.0"
+ ],
+ [
+ "adr",
+ {
+ "label": "1600 Amphitheatre Parkway\nMountain View\nCA\n94043\nUnited States"
+ },
+ "text",
+ [
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ ""
+ ]
+ ],
+ [
+ "fn",
+ {},
+ "text",
+ "Google LLC"
+ ],
+ [
+ "org",
+ {},
+ "text",
+ "Google LLC"
+ ],
+ [
+ "kind",
+ {},
+ "text",
+ "group"
+ ],
+ [
+ "email",
+ {},
+ "text",
+ "arin-contact@google.com"
+ ],
+ [
+ "tel",
+ {
+ "type": [
+ "work",
+ "voice"
+ ]
+ },
+ "text",
+ "+1-650-253-0000"
+ ]
+ ]
+ ],
+ "roles": [
+ "technical",
+ "administrative"
+ ],
+ "links": [
+ {
+ "value": "https://rdap.arin.net/registry/autnum/15169",
+ "rel": "self",
+ "type": "application/rdap+json",
+ "href": "https://rdap.arin.net/registry/entity/ZG39-ARIN"
+ },
+ {
+ "value": "https://rdap.arin.net/registry/autnum/15169",
+ "rel": "alternate",
+ "type": "application/xml",
+ "href": "https://whois.arin.net/rest/poc/ZG39-ARIN"
+ }
+ ],
+ "events": [
+ {
+ "eventAction": "last changed",
+ "eventDate": "2022-11-10T07:12:44-05:00"
+ },
+ {
+ "eventAction": "registration",
+ "eventDate": "2000-11-30T13:54:08-05:00"
+ }
+ ],
+ "status": [
+ "validated"
+ ],
+ "port43": "whois.arin.net",
+ "objectClassName": "entity"
+ }
+ ],
+ "port43": "whois.arin.net",
+ "objectClassName": "entity"
+ },
+ {
+ "handle": "ZG39-ARIN",
+ "vcardArray": [
+ "vcard",
+ [
+ [
+ "version",
+ {},
+ "text",
+ "4.0"
+ ],
+ [
+ "adr",
+ {
+ "label": "1600 Amphitheatre Parkway\nMountain View\nCA\n94043\nUnited States"
+ },
+ "text",
+ [
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ ""
+ ]
+ ],
+ [
+ "fn",
+ {},
+ "text",
+ "Google LLC"
+ ],
+ [
+ "org",
+ {},
+ "text",
+ "Google LLC"
+ ],
+ [
+ "kind",
+ {},
+ "text",
+ "group"
+ ],
+ [
+ "email",
+ {},
+ "text",
+ "arin-contact@google.com"
+ ],
+ [
+ "tel",
+ {
+ "type": [
+ "work",
+ "voice"
+ ]
+ },
+ "text",
+ "+1-650-253-0000"
+ ]
+ ]
+ ],
+ "roles": [
+ "technical"
+ ],
+ "links": [
+ {
+ "value": "https://rdap.arin.net/registry/autnum/15169",
+ "rel": "self",
+ "type": "application/rdap+json",
+ "href": "https://rdap.arin.net/registry/entity/ZG39-ARIN"
+ },
+ {
+ "value": "https://rdap.arin.net/registry/autnum/15169",
+ "rel": "alternate",
+ "type": "application/xml",
+ "href": "https://whois.arin.net/rest/poc/ZG39-ARIN"
+ }
+ ],
+ "events": [
+ {
+ "eventAction": "last changed",
+ "eventDate": "2022-11-10T07:12:44-05:00"
+ },
+ {
+ "eventAction": "registration",
+ "eventDate": "2000-11-30T13:54:08-05:00"
+ }
+ ],
+ "status": [
+ "validated"
+ ],
+ "port43": "whois.arin.net",
+ "objectClassName": "entity"
+ }
+ ],
+ "port43": "whois.arin.net",
+ "status": [
+ "active"
+ ],
+ "objectClassName": "autnum"
+}
+```
+
+
+
## Go docs
[](https://godoc.org/github.com/openrdap/rdap)
-## Requires
-Go 1.7+
+## 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)
+- ICANN - [RDAP](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)
-
+- [RFC 8521 Registration Data Access Protocol (RDAP) Object Tagging](https://datatracker.ietf.org/doc/rfc8521/)
diff --git a/bootstrap/client.go b/bootstrap/client.go
index a1955a0..4daadbe 100644
--- a/bootstrap/client.go
+++ b/bootstrap/client.go
@@ -430,7 +430,7 @@ func (c *Client) filenameFor(r RegistryType) string {
return filename
}
-// Filename returns the JSON document filename: One of {asn,dns,ipv4,ipv6,service_provider}.json.
+// Filename returns the JSON document filename: One of {asn,dns,ipv4,ipv6,object-tags}.json.
func (r RegistryType) Filename() string {
switch r {
case ASN:
@@ -442,8 +442,7 @@ func (r RegistryType) Filename() string {
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"
+ return "object-tags.json"
default:
panic("Unknown RegistryType")
}
diff --git a/bootstrap/client_test.go b/bootstrap/client_test.go
index e4983e9..b767ce6 100644
--- a/bootstrap/client_test.go
+++ b/bootstrap/client_test.go
@@ -5,7 +5,6 @@
package bootstrap
import (
- "net/url"
"testing"
"github.com/openrdap/rdap/test"
@@ -66,15 +65,9 @@ func TestLookups(t *testing.T) {
},
{
ServiceProvider,
- "12345~VRSN",
+ "12345-FRNIC",
true,
- []string{"https://rdap.verisignlabs.com/rdap/v1"},
- },
- {
- ServiceProvider,
- "12345-VRSN",
- true,
- []string{"https://rdap.verisignlabs.com/rdap/v1"},
+ []string{"https://rdap.nic.fr/"},
},
}
@@ -87,10 +80,6 @@ func TestLookups(t *testing.T) {
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,
diff --git a/bootstrap/file.go b/bootstrap/file.go
index d6e05c4..5b20a1d 100644
--- a/bootstrap/file.go
+++ b/bootstrap/file.go
@@ -52,13 +52,22 @@ func NewFile(jsonDocument []byte) (*File, error) {
f.Entries = make(map[string][]*url.URL)
for _, s := range doc.Services {
- if len(s) != 2 {
+ var entries []string
+ var rawURLs []string
+
+ switch len(s) {
+ case 2:
+ // {asn,dns,ipv4,ipv6}.json
+ entries = s[0]
+ rawURLs = s[1]
+ case 3:
+ // object-tags.json
+ entries = s[1]
+ rawURLs = s[2]
+ default:
return nil, errors.New("Malformed bootstrap (bad services array)")
}
- entries := s[0]
- rawURLs := s[1]
-
var urls []*url.URL
for _, rawURL := range rawURLs {
diff --git a/bootstrap/service_provider_registry.go b/bootstrap/service_provider_registry.go
index 89f3b01..74077dc 100644
--- a/bootstrap/service_provider_registry.go
+++ b/bootstrap/service_provider_registry.go
@@ -22,7 +22,7 @@ type ServiceProviderRegistry struct {
// Provider JSON document.
//
// The document format is specified in
-// https://datatracker.ietf.org/doc/draft-hollenbeck-regext-rdap-object-tag/.
+// https://datatracker.ietf.org/doc/rfc8521/.
func NewServiceProviderRegistry(json []byte) (*ServiceProviderRegistry, error) {
var r *File
r, err := NewFile(json)
@@ -50,11 +50,7 @@ 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, '-')
- }
+ offset := strings.LastIndexByte(input, '-')
if offset == -1 || offset == len(input)-1 {
return &Answer{
diff --git a/bootstrap/service_provider_registry_test.go b/bootstrap/service_provider_registry_test.go
index ca32261..a3ddf9a 100644
--- a/bootstrap/service_provider_registry_test.go
+++ b/bootstrap/service_provider_registry_test.go
@@ -11,10 +11,10 @@ import (
)
func TestServiceProviderRegistryLookups(t *testing.T) {
- test.Start(test.BootstrapExperimental)
+ test.Start(test.Bootstrap)
defer test.Finish()
- var bytes []byte = test.Get("https://test.rdap.net/rdap/serviceprovider-draft-03.json")
+ var bytes []byte = test.Get("https://data.iana.org/rdap/object-tags.json")
var s *ServiceProviderRegistry
s, err := NewServiceProviderRegistry(bytes)
@@ -31,58 +31,28 @@ func TestServiceProviderRegistryLookups(t *testing.T) {
[]string{},
},
{
- "~",
+ "12345-FRNIC",
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"},
+ "FRNIC",
+ []string{"https://rdap.nic.fr/"},
},
{
- "*-VRSN",
+ "*-FRNIC",
false,
- "VRSN",
- []string{"https://rdap.verisignlabs.com/rdap/v1"},
+ "FRNIC",
+ []string{"https://rdap.nic.fr/"},
},
{
- "-VRSN",
+ "-FRNIC",
false,
- "VRSN",
- []string{"https://rdap.verisignlabs.com/rdap/v1"},
+ "FRNIC",
+ []string{"https://rdap.nic.fr/"},
},
{
- "A-B-VRSN",
+ "A-B-FRNIC",
false,
- "VRSN",
- []string{"https://rdap.verisignlabs.com/rdap/v1"},
+ "FRNIC",
+ []string{"https://rdap.nic.fr/"},
},
}
diff --git a/cli.go b/cli.go
index 3038fe2..b874b97 100644
--- a/cli.go
+++ b/cli.go
@@ -12,7 +12,6 @@ import (
"net"
"net/http"
"net/url"
- "os"
"strconv"
"strings"
"time"
@@ -23,42 +22,33 @@ import (
"golang.org/x/crypto/pkcs12"
- kingpin "gopkg.in/alecthomas/kingpin.v2"
+ kingpin "github.com/alecthomas/kingpin/v2"
)
var (
- version = "OpenRDAP v0.0.1"
+ 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
+ e.g. rdap example.com
rdap 192.0.2.0
rdap 2001:db8::
rdap AS2856
+ rdap OPS4-RIPE
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, --version Print version and quit.
-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).
@@ -93,10 +83,12 @@ Advanced options (bootstrapping):
--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)
+Advanced options (authentication):
+ -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)
+
`
)
@@ -142,6 +134,7 @@ func RunCLI(args []string, stdout io.Writer, stderr io.Writer, options CLIOption
// Command line options.
verboseFlag := app.Flag("verbose", "").Short('v').Bool()
+ versionFlag := app.Flag("version", "").Short('V').Bool()
timeoutFlag := app.Flag("timeout", "").Short('T').Default("30").Uint16()
insecureFlag := app.Flag("insecure", "").Short('k').Bool()
@@ -179,6 +172,12 @@ func RunCLI(args []string, stdout io.Writer, stderr io.Writer, options CLIOption
return 1
}
+ // Print version string?
+ if *versionFlag {
+ fmt.Fprintln(stdout, version)
+ return 0
+ }
+
var verbose func(text string)
if *verboseFlag {
verbose = func(text string) {
@@ -214,9 +213,8 @@ func RunCLI(args []string, stdout io.Writer, stderr io.Writer, options CLIOption
// Enable the -e selection of experiments?
if *experimentalFlag {
- verbose("rdap: Enabled -e/--experiments: test_rdap_net, object_tag")
+ verbose("rdap: Enabled -e/--experiments: test_rdap_net")
experiments["test_rdap_net"] = true
- experiments["object_tag"] = true
}
// Forced sandbox mode?
@@ -461,6 +459,7 @@ func RunCLI(args []string, stdout io.Writer, stderr io.Writer, options CLIOption
// Custom HTTP client. Used to disable TLS certificate verification.
transport := &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
TLSClientConfig: tlsConfig,
}
@@ -476,9 +475,8 @@ func RunCLI(args []string, stdout io.Writer, stderr io.Writer, options CLIOption
HTTP: httpClient,
Bootstrap: bs,
- Verbose: verbose,
- UserAgent: version,
- ServiceProviderExperiment: experiments["object_tag"],
+ Verbose: verbose,
+ UserAgent: version,
}
if *insecureFlag {
@@ -526,14 +524,14 @@ func RunCLI(args []string, stdout io.Writer, stderr io.Writer, options CLIOption
// Print the raw response out?
if *outputFormatRaw {
- fmt.Printf("%s", resp.HTTP[0].Body)
+ fmt.Fprintf(stdout, "%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)
+ out.WriteTo(stdout)
}
// Print WHOIS style response out?
diff --git a/client.go b/client.go
index c3319c6..5b45cf3 100644
--- a/client.go
+++ b/client.go
@@ -20,54 +20,58 @@ import (
// 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)
-// }
+// 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)
+// // 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)
-// }
+// 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,
-// }
+// // 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.
//
-// req = req.WithContext(ctx) // Custom context (see https://blog.golang.org/context).
+// Server: server,
+// }
//
-// client := &rdap.Client{}
-// client.HTTP = &http.Client{} // Custom HTTP client.
-// client.Bootstrap = &bootstrap.Client{} // Custom bootstapper.
+// req = req.WithContext(ctx) // Custom context (see https://blog.golang.org/context).
//
-// resp, err := client.Do(req)
+// client := &rdap.Client{}
+// client.HTTP = &http.Client{} // Custom HTTP client.
+// client.Bootstrap = &bootstrap.Client{} // Custom bootstapper.
//
-// if ns, ok := resp.Object.(*rdap.Nameserver); ok {
-// fmt.Printf("Handle=%s Domain=%s\n", ns.Handle, ns.LDHName)
-// }
+// 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
@@ -75,8 +79,11 @@ type Client struct {
// Optional callback function for verbose messages.
Verbose func(text string)
+ UserAgent string
+
+ // Service Provider support is now always enabled.
+ // This field is ignored.
ServiceProviderExperiment bool
- UserAgent string
}
func (c *Client) Do(req *Request) (*Response, error) {
@@ -123,7 +130,7 @@ func (c *Client) Do(req *Request) (*Response, error) {
var bootstrapType *bootstrap.RegistryType = bootstrapTypeFor(req)
- if bootstrapType == nil || (*bootstrapType == bootstrap.ServiceProvider && !c.ServiceProviderExperiment) {
+ if bootstrapType == nil {
return nil, &ClientError{
Type: BootstrapNotSupported,
Text: fmt.Sprintf("Cannot run query type '%s' without a server URL, "+
diff --git a/decode_data.go b/decode_data.go
index 2cb1554..9f05a60 100644
--- a/decode_data.go
+++ b/decode_data.go
@@ -60,7 +60,6 @@ func (r DecodeData) Notes(name string) []string {
//
// |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
diff --git a/decoder.go b/decoder.go
index 6dba633..1d57d34 100644
--- a/decoder.go
+++ b/decoder.go
@@ -20,36 +20,37 @@ import (
//
// 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)
-// }
-// }
+// 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.
+//
+// &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.
//
@@ -95,16 +96,17 @@ func NewDecoder(jsonBlob []byte, opts ...DecoderOption) *Decoder {
// 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.
+//
+// &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
@@ -737,7 +739,7 @@ func (d *Decoder) decodePtr(keyName string, src interface{}, dst reflect.Value,
var err error
if dst.Type().Elem().Name() == "VCard" {
- vcard, vcardError := newVCardImpl(src)
+ vcard, vcardError := newVCardImpl(src, VCardOptions{})
if vcardError == nil {
dst.Set(reflect.ValueOf(vcard))
diff --git a/doc.go b/doc.go
index bfb07c4..1814c81 100644
--- a/doc.go
+++ b/doc.go
@@ -9,27 +9,30 @@
// 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)
-// }
+// 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)
-// }
+//
+// // 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
diff --git a/go.mod b/go.mod
index 5e7c596..ed0e5fe 100644
--- a/go.mod
+++ b/go.mod
@@ -1,14 +1,16 @@
module github.com/openrdap/rdap
-go 1.12
+go 1.19
require (
- github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
- github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 // indirect
+ github.com/alecthomas/kingpin/v2 v2.3.2
github.com/davecgh/go-spew v1.1.1
- github.com/jarcoal/httpmock v1.0.4
+ github.com/jarcoal/httpmock v1.3.0
github.com/mitchellh/go-homedir v1.1.0
- github.com/stretchr/testify v1.3.0 // indirect
- golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
- gopkg.in/alecthomas/kingpin.v2 v2.2.6
+ golang.org/x/crypto v0.17.0
+)
+
+require (
+ github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
+ github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
)
diff --git a/go.sum b/go.sum
index 6ea77af..941e832 100644
--- a/go.sum
+++ b/go.sum
@@ -1,25 +1,24 @@
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
-github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.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.0.4 h1:jp+dy/+nonJE4g4xbVtl9QdrUNbn6/3hDT5R4nDIZnA=
-github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
+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.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+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.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
+golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+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=
diff --git a/request.go b/request.go
index 20cd2c7..275467e 100644
--- a/request.go
+++ b/request.go
@@ -77,31 +77,31 @@ func (r RequestType) String() string {
// A Request represents an RDAP request.
//
-// req := &rdap.Request{
-// Type: rdap.DomainRequest,
-// Query: "example.cz",
-// }
+// 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
+// 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.
@@ -112,21 +112,22 @@ func (r RequestType) String() string {
//
// 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",
+// // 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,
-// }
+// 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,
-// }
+//
+// 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
@@ -227,15 +228,16 @@ func (r *Request) pathAndValues() (string, url.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,
-// }
+// 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.
+// fmt.Println(req.URL()) // Prints https://rdap.nic.cz/nameserver/a.ns.nic.cz.
//
// Returns nil if the Server field is nil.
//
@@ -431,11 +433,11 @@ func NewRequest(requestType RequestType, query string) *Request {
// 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.
+// - 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 {
diff --git a/test/http.go b/test/http.go
index dd9a91f..c8ca93b 100644
--- a/test/http.go
+++ b/test/http.go
@@ -82,10 +82,7 @@ func loadTestDatasets() {
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")
+ load(Bootstrap, 200, "https://data.iana.org/rdap/object-tags.json", "bootstrap/object-tags.json")
// Malformed bootstrap files.
load(BootstrapMalformed, 200, "https://www.example.org/dns_bad_services.json", "bootstrap_malformed/dns_bad_services.json")
diff --git a/test/testdata/bootstrap/object-tags.json b/test/testdata/bootstrap/object-tags.json
new file mode 100644
index 0000000..8024d76
--- /dev/null
+++ b/test/testdata/bootstrap/object-tags.json
@@ -0,0 +1,63 @@
+{
+ "description": "RDAP bootstrap file for service provider object tags",
+ "publication": "2022-12-29T04:00:02Z",
+ "services": [
+ [
+ [
+ "info@arin.net"
+ ],
+ [
+ "ARIN"
+ ],
+ [
+ "https://rdap.arin.net/registry/",
+ "http://rdap.arin.net/registry/"
+ ]
+ ],
+ [
+ [
+ "carlos@lacnic.net"
+ ],
+ [
+ "LACNIC"
+ ],
+ [
+ "https://rdap.lacnic.net/rdap/"
+ ]
+ ],
+ [
+ [
+ "bje@apnic.net"
+ ],
+ [
+ "APNIC"
+ ],
+ [
+ "https://rdap.apnic.net/"
+ ]
+ ],
+ [
+ [
+ "kranjbar@ripe.net"
+ ],
+ [
+ "RIPE"
+ ],
+ [
+ "https://rdap.db.ripe.net/"
+ ]
+ ],
+ [
+ [
+ "tld-tech@nic.fr"
+ ],
+ [
+ "FRNIC"
+ ],
+ [
+ "https://rdap.nic.fr/"
+ ]
+ ]
+ ],
+ "version": "1.0"
+}
\ No newline at end of file
diff --git a/test/testdata/bootstrap_experimental/service_provider.json b/test/testdata/bootstrap_experimental/service_provider.json
deleted file mode 100644
index c16177b..0000000
--- a/test/testdata/bootstrap_experimental/service_provider.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "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"
- ]
- ]
- ]
-}
-
diff --git a/test/testdata/jcard/error_invalid_properties.json b/test/testdata/jcard/error_invalid_properties.json
new file mode 100644
index 0000000..aa418f8
--- /dev/null
+++ b/test/testdata/jcard/error_invalid_properties.json
@@ -0,0 +1,10 @@
+["vcard",
+ [
+ ["lang", { "pref": "1" }, "language-tag", "fr"],
+ ["lang", { "pref": "2" }, "language-tag", "pt"],
+ ["lang", { "pref": "3" }, "language-tag", "de"],
+ ["lang", { "pref": "4" }, "language-tag", "es"],
+
+ ["lang", {"type": "language-tag"}, "en"]
+ ]
+]
diff --git a/vcard.go b/vcard.go
index 78a80e2..a1f3695 100644
--- a/vcard.go
+++ b/vcard.go
@@ -24,19 +24,20 @@ import (
// 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"
-// ],
-// ...
-// ]
-// ]
+//
+// ["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
}
@@ -44,9 +45,10 @@ type VCard struct {
// 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"]
+//
+// Name Parameters Type Value
+// ----- -------------------------- ----- -----------------------------
+// ["tel", {"type":["work", "voice"]}, "uri", "tel:+1-555-555-1234;ext=555"]
type VCardProperty struct {
Name string
@@ -71,6 +73,14 @@ type VCardProperty struct {
Value interface{}
}
+// VCardOptions specifies options for the VCard decoder routine.
+type VCardOptions struct {
+ // By default, any invalid VCard property causes the entire VCard decode to fail.
+ //
+ // Set IgnoreInvalidProperties to true to silently skip any invalid properties.
+ IgnoreInvalidProperties bool
+}
+
// Values returns a simplified representation of the VCardProperty value.
//
// This is convenient for accessing simple unstructured data (e.g. "fn", "tel").
@@ -107,10 +117,10 @@ func (p *VCardProperty) appendValueStrings(v interface{}, strings *[]string) {
// 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]]
-// ]
+// 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 {
@@ -125,7 +135,7 @@ func (v *VCard) String() string {
// String returns the VCardProperty as a human readable string. For example:
//
-// mixed (type=text, parameters=map[]): [abc true 42 [def false 43]]
+// 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 {
@@ -133,7 +143,20 @@ func (p *VCardProperty) String() string {
}
// NewVCard creates a VCard from jsonBlob.
+//
+// Default options are used for the VCard decoder (see NewVCardWithOptions).
func NewVCard(jsonBlob []byte) (*VCard, error) {
+ vcard, err := NewVCardWithOptions(jsonBlob, VCardOptions{})
+ return vcard, err
+}
+
+// NewVCardWithOptions creates a VCard from jsonBlob. options specifies options for
+// the VCard decoder.
+//
+// Example usage:
+//
+// vcard, err := NewVCardWithOptions(jsonBlob, VCardOptions{IgnoreInvalidProperties: true})
+func NewVCardWithOptions(jsonBlob []byte, options VCardOptions) (*VCard, error) {
var top []interface{}
err := json.Unmarshal(jsonBlob, &top)
@@ -142,12 +165,12 @@ func NewVCard(jsonBlob []byte) (*VCard, error) {
}
var vcard *VCard
- vcard, err = newVCardImpl(top)
+ vcard, err = newVCardImpl(top, options)
return vcard, err
}
-func newVCardImpl(src interface{}) (*VCard, error) {
+func newVCardImpl(src interface{}, options VCardOptions) (*VCard, error) {
top, ok := src.([]interface{})
if !ok || len(top) != 2 {
@@ -169,58 +192,72 @@ func newVCardImpl(src interface{}) (*VCard, error) {
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)")
+ property, err := decodeVCardProperty(p)
+
+ if err != nil {
+ if options.IgnoreInvalidProperties {
+ continue
+ } else {
+ return nil, err
+ }
}
- name, ok := a[0].(string)
+ v.Properties = append(v.Properties, property)
+ }
- if !ok {
- return nil, vCardError("jCard property name invalid")
- }
+ return v, nil
+}
- var parameters map[string][]string
- var err error
- parameters, err = readParameters(a[1])
+func decodeVCardProperty(p interface{}) (*VCardProperty, error) {
+ var a []interface{}
+ var ok bool
+ a, ok = p.([]interface{})
- if err != nil {
- return nil, err
- }
+ 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)")
+ }
- propertyType, ok := a[2].(string)
+ name, ok := a[0].(string)
- if !ok {
- return nil, vCardError("jCard property type invalid")
- }
+ if !ok {
+ return nil, vCardError("jCard property name invalid")
+ }
- var value interface{}
- if len(a) == 4 {
- value, err = readValue(a[3], 0)
- } else {
- value, err = readValue(a[3:], 0)
- }
+ var parameters map[string][]string
+ var err error
+ parameters, err = readParameters(a[1])
- if err != nil {
- return nil, err
- }
+ if err != nil {
+ return nil, err
+ }
- property := &VCardProperty{
- Name: name,
- Type: propertyType,
- Parameters: parameters,
- Value: value,
- }
+ propertyType, ok := a[2].(string)
- v.Properties = append(v.Properties, property)
+ if !ok {
+ return nil, vCardError("jCard property type invalid")
}
- return v, nil
+ 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,
+ }
+
+ return property, nil
}
// Get returns a list of the vCard Properties with VCardProperty name |name|.
@@ -431,6 +468,13 @@ func (v *VCard) Email() string {
return v.getFirstPropertySingleString("email")
}
+// Org returns the VCard's org.
+//
+// Returns empty string if the VCard contains no organization.
+func (v *VCard) Org() string {
+ return v.getFirstPropertySingleString("org")
+}
+
func (v *VCard) getFirstAddressField(index int) string {
adr := v.GetFirst("adr")
if adr == nil {
diff --git a/vcard_test.go b/vcard_test.go
index 754cfa7..d395524 100644
--- a/vcard_test.go
+++ b/vcard_test.go
@@ -34,6 +34,20 @@ func TestVCardErrors(t *testing.T) {
}
}
+func TestVCardIgnoreInvalidProperties(t *testing.T) {
+ json := test.LoadFile("jcard/error_invalid_properties.json")
+
+ j1, err1 := NewVCardWithOptions(json, VCardOptions{IgnoreInvalidProperties: true})
+ if j1 == nil || len(j1.Properties) != 4 || err1 != nil {
+ t.Errorf("jCard with ignored errors not parsed correctly\n")
+ }
+
+ j2, err2 := NewVCardWithOptions(json, VCardOptions{IgnoreInvalidProperties: false})
+ if j2 != nil || err2 == nil {
+ t.Errorf("jCard with errors unexpectedly parsed\n")
+ }
+}
+
func TestVCardExample(t *testing.T) {
j, err := NewVCard(test.LoadFile("jcard/example.json"))
if j == nil || err != nil {
@@ -143,6 +157,7 @@ func TestVCardQuickAccessors(t *testing.T) {
j.Tel(),
j.Fax(),
j.Email(),
+ j.Org(),
}
expected := []string{
@@ -157,6 +172,7 @@ func TestVCardQuickAccessors(t *testing.T) {
"tel:+1-418-656-9254;ext=102",
"",
"simon.perreault@viagenie.ca",
+ "Viagenie",
}
if !reflect.DeepEqual(got, expected) {