├── Dockerfile ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── main.go └── output ├── as.go ├── as_test.go ├── as_tmpl.go ├── contact_info.go ├── contact_info_tmpl.go ├── domain.go ├── domain_test.go ├── domain_tmpl.go ├── entity.go ├── entity_test.go ├── generic.go ├── helper_test.go ├── ipnet.go ├── ipnet_test.go ├── ipnet_tmpl.go └── printer.go /Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # =========== 3 | # Build image 4 | # =========== 5 | # 6 | FROM golang:1.19 as builder 7 | COPY . /go/src/github.com/registrobr/rdap-client 8 | WORKDIR /go/src/github.com/registrobr/rdap-client 9 | RUN mkdir /apps 10 | RUN go build -ldflags="-w -s" -o /apps/rdap-client 11 | 12 | # 13 | # ==================== 14 | # Final delivery image 15 | # ==================== 16 | # 17 | FROM debian:stable-slim 18 | 19 | COPY --from=builder /apps/* /apps/ 20 | 21 | RUN apt update \ 22 | && apt -y install ca-certificates \ 23 | && rm -rf /var/lib/apt/lists/* 24 | 25 | ARG BUILD_DATE 26 | ARG BUILD_VCS_REF 27 | ARG BUILD_VERSION 28 | 29 | ENV API_VERSION ${BUILD_VCS_REF} 30 | 31 | LABEL org.label-schema.build-date=$BUILD_DATE \ 32 | org.label-schema.description="RDAP client" \ 33 | org.label-schema.name="rdap-client" \ 34 | org.label-schema.schema-version="1.0" \ 35 | org.label-schema.url="https://registro.br" \ 36 | org.label-schema.vcs-url="https://github.com/registrobr/rdap-client" \ 37 | org.label-schema.vcs-ref=$BUILD_VCS_REF \ 38 | org.label-schema.vendor="NIC.br" \ 39 | org.label-schema.version=$BUILD_VERSION 40 | 41 | ENTRYPOINT ["/apps/rdap-client"] 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015-2019 Registro.br. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 1. Redistribution of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY REGISTRO.BR ``AS IS AND ANY EXPRESS OR 13 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 14 | WARRANTIE OF FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 15 | EVENT SHALL REGISTRO.BR BE LIABLE FOR ANY DIRECT, INDIRECT, 16 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 17 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 18 | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 19 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 20 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 21 | USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 22 | DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rdap-client 2 | =========== 3 | 4 | This is a command line RDAP client. 5 | 6 | 7 | Install 8 | ------- 9 | 10 | First of all, you will need **Go** and **git** installed in your machine. 11 | Instructions for intalling Go can be found in the link bellow. 12 | 13 | http://golang.org/doc/install 14 | 15 | You must be running Go version 1.5 or above. 16 | Don't forget to create the $GOPATH environment variable. 17 | 18 | Now just retrieve and install the project with the following command: 19 | ``` 20 | go get github.com/registrobr/rdap-client 21 | ``` 22 | 23 | Remember to add your **$GOPATH/bin** to your **$PATH** environment. 24 | 25 | A Dockerfile is available and can be used as in the follow example: 26 | 27 | ``` 28 | docker build -t rdap-client:latest . 29 | 30 | docker run -it --rm --name rdap-client rdap-client:latest registro.br 31 | ``` 32 | 33 | 34 | Usage 35 | ----- 36 | 37 | To query something using bootstrap strategy: 38 | 39 | ``` 40 | rdap-client 199.71.0.160 41 | ``` 42 | 43 | Or if you want to directly query a RDAP server: 44 | 45 | ``` 46 | rdap-client -H rdap.registro.br nic.br 47 | ``` 48 | 49 | You can check more options with: 50 | 51 | ``` 52 | rdap-client -h 53 | ``` 54 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/registrobr/rdap-client 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.6 6 | 7 | require ( 8 | github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b 9 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 10 | github.com/registrobr/rdap v1.1.7 11 | github.com/urfave/cli v1.22.16 12 | ) 13 | 14 | require ( 15 | github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect 16 | github.com/google/btree v0.0.0-20161217183710-316fb6d3f031 // indirect 17 | github.com/peterbourgon/diskv v2.0.1-0.20160404093648-5dfcb07a075a+incompatible // indirect 18 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 19 | golang.org/x/net v0.38.0 // indirect 20 | golang.org/x/text v0.23.0 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 2 | github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw= 3 | github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= 4 | github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= 5 | github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/google/btree v0.0.0-20161217183710-316fb6d3f031 h1:yAx4v8FikdsGCBPzIaT2F+0WH0J+wcL7cQD9n3UbyOk= 10 | github.com/google/btree v0.0.0-20161217183710-316fb6d3f031/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 11 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= 12 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 13 | github.com/peterbourgon/diskv v2.0.1-0.20160404093648-5dfcb07a075a+incompatible h1:qhD6bS05X4eckpXB/kN+EMpIKzyXIiENfr+8c05QChc= 14 | github.com/peterbourgon/diskv v2.0.1-0.20160404093648-5dfcb07a075a+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 17 | github.com/registrobr/rdap v1.1.7 h1:ypysGkr6s5WktikbOTLnLlTZySG1CblWWJQK1g71Pwk= 18 | github.com/registrobr/rdap v1.1.7/go.mod h1:0yJB4/1iofg3s+PBxF8xPeRCJXGmWOmM4HzsP0NA7jQ= 19 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 20 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 21 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 22 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 23 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 24 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 25 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 26 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 27 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 28 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 29 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 30 | github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ= 31 | github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= 32 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 33 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 34 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 35 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 36 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 37 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 38 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 39 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 40 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 41 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "fmt" 7 | "net" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "path" 12 | "strconv" 13 | "strings" 14 | 15 | "github.com/urfave/cli" 16 | "github.com/gregjones/httpcache" 17 | "github.com/gregjones/httpcache/diskcache" 18 | "github.com/registrobr/rdap" 19 | "github.com/registrobr/rdap-client/output" 20 | "github.com/registrobr/rdap/protocol" 21 | ) 22 | 23 | const ( 24 | outputTypeDefault = "default" 25 | outputTypeRaw = "raw" 26 | ) 27 | 28 | func main() { 29 | cli.AppHelpTemplate = ` 30 | NAME: 31 | {{.Name}} - {{.Usage}} 32 | 33 | USAGE: 34 | {{.Name}} {{if .Flags}}[global options]{{end}} OBJECT 35 | 36 | VERSION: 37 | {{.Version}}{{if len .Authors}} 38 | 39 | AUTHOR(S): 40 | {{range .Authors}}{{ . }}{{end}}{{end}} 41 | 42 | GLOBAL OPTIONS: 43 | {{range .Flags}}{{.}} 44 | {{end}} 45 | ` 46 | 47 | app := cli.NewApp() 48 | app.Name = "rdap" 49 | app.Usage = "RDAP client" 50 | app.Author = "NIC.br" 51 | app.Version = "0.0.1" 52 | 53 | app.Flags = []cli.Flag{ 54 | cli.StringFlag{ 55 | Name: "cache", 56 | Value: path.Join(os.Getenv("HOME"), ".rdap"), 57 | Usage: "directory for caching bootstrap and RDAP data", 58 | }, 59 | cli.StringFlag{ 60 | Name: "bootstrap", 61 | Value: rdap.IANABootstrap, 62 | Usage: "RDAP bootstrap service URL", 63 | }, 64 | cli.BoolFlag{ 65 | Name: "no-cache", 66 | Usage: "don't cache boostrap responses", 67 | }, 68 | cli.BoolFlag{ 69 | Name: "skip-tls-verification,S", 70 | Usage: "skip TLS verification", 71 | }, 72 | cli.BoolFlag{ 73 | Name: "domain", 74 | Usage: "force query for a domain object", 75 | }, 76 | cli.BoolFlag{ 77 | Name: "asn", 78 | Usage: "force query for an ASN object", 79 | }, 80 | cli.BoolFlag{ 81 | Name: "ip", 82 | Usage: "force query for an IP or IPNetwork object", 83 | }, 84 | cli.BoolFlag{ 85 | Name: "entity", 86 | Usage: "force query for an Entity object", 87 | }, 88 | cli.StringFlag{ 89 | Name: "host,H", 90 | Value: "", 91 | Usage: "host where to send the query (bypass bootstrap)", 92 | }, 93 | cli.StringFlag{ 94 | Name: "output-type,o", 95 | Value: "default", 96 | Usage: "defines the output format, possible values are “" + outputTypeDefault + "” and “" + outputTypeRaw + "”", 97 | }, 98 | cli.StringSliceFlag{ 99 | Name: "extra,x", 100 | Value: &cli.StringSlice{}, 101 | Usage: "set some extra options using key=value format", 102 | }, 103 | } 104 | 105 | app.Commands = []cli.Command{} 106 | app.Action = action 107 | 108 | app.Run(os.Args) 109 | } 110 | 111 | func action(ctx *cli.Context) { 112 | var ( 113 | cache = ctx.String("cache") 114 | bootstrapURI = ctx.String("bootstrap") 115 | host = ctx.String("host") 116 | outputType = ctx.String("output-type") 117 | skipTLSVerification = ctx.Bool("skip-tls-verification") 118 | forceASN = ctx.Bool("asn") 119 | forceDomain = ctx.Bool("domain") 120 | forceEntity = ctx.Bool("entity") 121 | forceIP = ctx.Bool("ip") 122 | extraOptions = ctx.StringSlice("extra") 123 | ) 124 | 125 | if outputType != outputTypeDefault && outputType != outputTypeRaw { 126 | fmt.Fprintln(os.Stderr, "invalid output type") 127 | os.Exit(1) 128 | } 129 | 130 | forceCount := 0 131 | forceObjects := []bool{ 132 | forceDomain, 133 | forceIP, 134 | forceEntity, 135 | forceASN, 136 | } 137 | 138 | for _, force := range forceObjects { 139 | if force { 140 | if forceCount++; forceCount > 1 { 141 | fmt.Fprintln(os.Stderr, "you can't use -asn, -domain, -entity or -ip at the same time") 142 | os.Exit(1) 143 | } 144 | } 145 | } 146 | 147 | bsHTTPClient := &http.Client{} 148 | rdapHTTPClient := &http.Client{ 149 | Transport: &http.Transport{ 150 | TLSClientConfig: &tls.Config{ 151 | InsecureSkipVerify: skipTLSVerification, 152 | }, 153 | }, 154 | } 155 | 156 | if !ctx.Bool("no-cache") { 157 | transport := httpcache.NewTransport( 158 | diskcache.New(cache), 159 | ) 160 | 161 | transport.Transport = &http.Transport{ 162 | TLSClientConfig: &tls.Config{ 163 | InsecureSkipVerify: skipTLSVerification, 164 | }, 165 | } 166 | 167 | bsHTTPClient.Transport = transport 168 | } 169 | 170 | identifier := strings.Join(ctx.Args(), " ") 171 | if identifier == "" { 172 | cli.ShowAppHelp(ctx) 173 | os.Exit(1) 174 | } 175 | 176 | var client rdap.Client 177 | 178 | if len(host) > 0 { 179 | u, err := url.Parse(host) 180 | if err != nil { 181 | fmt.Fprintf(os.Stderr, "%s\n", err) 182 | os.Exit(1) 183 | } 184 | 185 | client.URIs = append(client.URIs, u.String()) 186 | client.Transport = rdap.NewDefaultFetcher(rdapHTTPClient) 187 | 188 | } else { 189 | cacheDetector := rdap.CacheDetector(func(resp *http.Response) bool { 190 | return resp.Header.Get(httpcache.XFromCache) == "1" 191 | }) 192 | 193 | client.Transport = rdap.NewBootstrapFetcher(bsHTTPClient, bootstrapURI, cacheDetector) 194 | } 195 | 196 | queryString := make(url.Values) 197 | 198 | for _, extraOption := range extraOptions { 199 | extraOptionParts := strings.Split(extraOption, "=") 200 | if len(extraOptionParts) != 2 { 201 | fmt.Fprintln(os.Stderr, "invalid extra option “"+extraOption+"”") 202 | os.Exit(1) 203 | } 204 | 205 | key, value := strings.TrimSpace(extraOptionParts[0]), strings.TrimSpace(extraOptionParts[1]) 206 | queryString.Add(key, value) 207 | } 208 | 209 | var err error 210 | var object interface{} 211 | 212 | switch { 213 | case forceASN: 214 | var asn uint64 215 | if asn, err = strconv.ParseUint(identifier, 10, 32); err == nil { 216 | object, _, err = client.ASN(uint32(asn), nil, queryString) 217 | } 218 | 219 | case forceDomain: 220 | object, _, err = client.Domain(identifier, nil, queryString) 221 | 222 | case forceEntity: 223 | object, _, err = client.Entity(identifier, nil, queryString) 224 | 225 | case forceIP: 226 | if ip := net.ParseIP(identifier); ip != nil { 227 | object, _, err = client.IP(ip, nil, queryString) 228 | } else { 229 | var ipnetwork *net.IPNet 230 | 231 | if _, ipnetwork, err = net.ParseCIDR(identifier); err != nil { 232 | err = fmt.Errorf("invalid ip or ip network “%s”", identifier) 233 | } else { 234 | object, _, err = client.IPNetwork(ipnetwork, nil, queryString) 235 | } 236 | } 237 | 238 | default: 239 | object, _, err = client.Query(identifier, nil, queryString) 240 | } 241 | 242 | if err != nil { 243 | fmt.Fprintln(os.Stderr, err) 244 | os.Exit(1) 245 | } 246 | 247 | switch outputType { 248 | case outputTypeDefault: 249 | var printer output.Printer 250 | 251 | switch object.(type) { 252 | case *protocol.AS: 253 | printer = &output.AS{ 254 | AS: object.(*protocol.AS), 255 | } 256 | case *protocol.Domain: 257 | printer = &output.Domain{ 258 | Domain: object.(*protocol.Domain), 259 | } 260 | case *protocol.Entity: 261 | printer = &output.Entity{ 262 | Entity: object.(*protocol.Entity), 263 | } 264 | case *protocol.IPNetwork: 265 | printer = &output.IPNetwork{ 266 | IPNetwork: object.(*protocol.IPNetwork), 267 | } 268 | } 269 | 270 | if err := printer.Print(os.Stdout); err != nil { 271 | fmt.Fprintln(os.Stderr, err) 272 | os.Exit(1) 273 | } 274 | 275 | case outputTypeRaw: 276 | output, err := json.MarshalIndent(object, "", " ") 277 | if err != nil { 278 | fmt.Fprintln(os.Stderr, err) 279 | os.Exit(1) 280 | } 281 | 282 | fmt.Println(string(output)) 283 | } 284 | 285 | os.Exit(0) 286 | } 287 | -------------------------------------------------------------------------------- /output/as.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | "text/template" 8 | 9 | "github.com/registrobr/rdap/protocol" 10 | ) 11 | 12 | type AS struct { 13 | AS *protocol.AS 14 | CreatedAt protocol.EventDate 15 | UpdatedAt protocol.EventDate 16 | IPNetworks []string 17 | ContactsInfos []contactInfo 18 | } 19 | 20 | func (a *AS) addContact(c contactInfo) { 21 | a.ContactsInfos = append(a.ContactsInfos, c) 22 | } 23 | 24 | func (a *AS) getContacts() []contactInfo { 25 | return a.ContactsInfos 26 | } 27 | 28 | func (a *AS) setContacts(c []contactInfo) { 29 | a.ContactsInfos = c 30 | } 31 | 32 | func (a *AS) setDates() { 33 | for _, e := range a.AS.Events { 34 | switch e.Action { 35 | case protocol.EventActionRegistration: 36 | a.CreatedAt = e.Date 37 | case protocol.EventActionLastChanged: 38 | a.UpdatedAt = e.Date 39 | } 40 | } 41 | } 42 | 43 | func (a *AS) setIPNetworks() { 44 | for _, l := range a.AS.Links { 45 | if l.Rel != "related" { 46 | continue 47 | } 48 | 49 | linkParts := strings.Split(l.Href, "/") 50 | if len(linkParts) >= 3 && linkParts[len(linkParts)-3] == "ip" { 51 | cidr := fmt.Sprintf("%s/%s", linkParts[len(linkParts)-2], linkParts[len(linkParts)-1]) 52 | a.IPNetworks = append(a.IPNetworks, cidr) 53 | } 54 | } 55 | } 56 | 57 | func (a *AS) Print(wr io.Writer) error { 58 | a.setDates() 59 | a.setIPNetworks() 60 | addContacts(a, a.AS.Entities) 61 | filterContacts(a) 62 | 63 | t, err := template.New("as template"). 64 | Funcs(genericFuncMap). 65 | Parse(strings.Replace(asTmpl, "\\\n", "", -1)) 66 | 67 | if err != nil { 68 | return err 69 | } 70 | 71 | return t.Execute(wr, a) 72 | } 73 | -------------------------------------------------------------------------------- /output/as_test.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | "time" 7 | 8 | "github.com/registrobr/rdap/protocol" 9 | ) 10 | 11 | func TestASPrint(t *testing.T) { 12 | as := AS{ 13 | AS: &protocol.AS{ 14 | ObjectClassName: "autnum", 15 | Handle: "123456", 16 | StartAutnum: 123456, 17 | EndAutnum: 123456, 18 | Type: "DIRECT ALLOCATION", 19 | Country: "BR", 20 | Links: []protocol.Link{ 21 | { 22 | Value: "https://rdap.registro.br/autnum/123456", 23 | Rel: "self", 24 | Href: "https://rdap.registro.br/autnum/123456", 25 | Type: "application/rdap+json", 26 | }, 27 | { 28 | Value: "https://rdap.registro.br/autnum/123456", 29 | Rel: "related", 30 | Href: "https://rdap.registro.br/ip/200.160.0.0/20", 31 | Type: "application/rdap+json", 32 | }, 33 | }, 34 | Entities: []protocol.Entity{ 35 | { 36 | ObjectClassName: "entity", 37 | Handle: "XXXX", 38 | VCardArray: []interface{}{ 39 | "vcard", 40 | []interface{}{ 41 | []interface{}{"version", struct{}{}, "text", "4.0"}, 42 | []interface{}{"fn", struct{}{}, "text", "Joe User"}, 43 | []interface{}{"kind", struct{}{}, "text", "individual"}, 44 | []interface{}{"email", struct{ Type string }{Type: "work"}, "text", "joe.user@example.com"}, 45 | []interface{}{"lang", struct{ Pref string }{Pref: "1"}, "language-tag", "pt"}, 46 | []interface{}{"adr", struct{ Type string }{Type: "work"}, "text", 47 | []interface{}{ 48 | "Av Naçoes Unidas", "11541", "7 andar", "Sao Paulo", "SP", "04578-000", "BR", 49 | }, 50 | }, 51 | []interface{}{"tel", struct{ Type string }{Type: "work"}, "uri", "tel:+55-11-5509-3506;ext=3506"}, 52 | }, 53 | }, 54 | Events: []protocol.Event{ 55 | { 56 | Action: protocol.EventActionRegistration, 57 | Date: protocol.Date(2015, 03, 01, 12, 00, 00, 00, time.UTC), 58 | }, 59 | { 60 | Action: protocol.EventActionLastChanged, 61 | Date: protocol.Date(2015, 03, 10, 14, 00, 00, 00, time.UTC), 62 | }, 63 | }, 64 | }, 65 | { 66 | ObjectClassName: "entity", 67 | Handle: "YYYY", 68 | VCardArray: []interface{}{ 69 | "vcard", 70 | []interface{}{ 71 | []interface{}{"version", struct{}{}, "text", "4.0"}, 72 | []interface{}{"fn", struct{}{}, "text", "Joe User 2"}, 73 | []interface{}{"kind", struct{}{}, "text", "individual"}, 74 | []interface{}{"email", struct{ Type string }{Type: "work"}, "text", "joe.user2@example.com"}, 75 | []interface{}{"lang", struct{ Pref string }{Pref: "1"}, "language-tag", "pt"}, 76 | []interface{}{"adr", struct{ Type string }{Type: "work"}, "text", 77 | []interface{}{ 78 | "Av Naçoes Unidas", "11541", "7 andar", "Sao Paulo", "SP", "04578-000", "BR", 79 | }, 80 | }, 81 | []interface{}{"tel", struct{ Type string }{Type: "work"}, "uri", "tel:+55-11-5509-3506;ext=3507"}, 82 | }, 83 | }, 84 | Events: []protocol.Event{ 85 | { 86 | Action: protocol.EventActionRegistration, 87 | Date: protocol.Date(2015, 03, 01, 12, 00, 00, 00, time.UTC), 88 | }, 89 | { 90 | Action: protocol.EventActionLastChanged, 91 | Date: protocol.Date(2015, 03, 10, 14, 00, 00, 00, time.UTC), 92 | }, 93 | }, 94 | }, 95 | }, 96 | Events: []protocol.Event{ 97 | { 98 | Action: protocol.EventActionRegistration, 99 | Date: protocol.Date(2015, 03, 01, 12, 00, 00, 00, time.UTC), 100 | }, 101 | { 102 | Action: protocol.EventActionLastChanged, 103 | Date: protocol.Date(2015, 03, 10, 14, 00, 00, 00, time.UTC), 104 | }, 105 | }, 106 | }, 107 | } 108 | 109 | expected := ` 110 | aut-num: 123456 111 | type: DIRECT ALLOCATION 112 | country: BR 113 | created: 20150301 114 | changed: 20150310 115 | inetnum: 200.160.0.0/20 116 | 117 | handle: XXXX 118 | person: Joe User 119 | e-mail: joe.user@example.com 120 | address: Av Naçoes Unidas, 11541, 7 andar, Sao Paulo, SP, 04578-000, BR 121 | phone: tel:+55-11-5509-3506;ext=3506 122 | created: 20150301 123 | changed: 20150310 124 | 125 | handle: YYYY 126 | person: Joe User 2 127 | e-mail: joe.user2@example.com 128 | address: Av Naçoes Unidas, 11541, 7 andar, Sao Paulo, SP, 04578-000, BR 129 | phone: tel:+55-11-5509-3506;ext=3507 130 | created: 20150301 131 | changed: 20150310 132 | 133 | ` 134 | 135 | var w WriterMock 136 | if err := as.Print(&w); err != nil { 137 | t.Fatal(err) 138 | } 139 | 140 | if string(w.Content) != expected { 141 | for _, l := range diff(expected, string(w.Content)) { 142 | t.Log(l) 143 | } 144 | t.Fatal("error") 145 | } 146 | } 147 | 148 | func TestAsToTextWithErrorOnWriter(t *testing.T) { 149 | dummyErr := errors.New("Dummy Error!") 150 | w := &WriterMock{ 151 | Err: dummyErr, 152 | } 153 | 154 | as := AS{ 155 | AS: new(protocol.AS), 156 | } 157 | 158 | if err := as.Print(w); err == nil { 159 | t.Fatal("Expecting an error") 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /output/as_tmpl.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | const asTmpl = ` 4 | {{if eq .AS.StartAutnum .AS.EndAutnum}}\ 5 | aut-num: {{.AS.StartAutnum}} 6 | {{else}}\ 7 | aut-num: {{.AS.StartAutnum}} - {{.AS.EndAutnum}} 8 | {{end}}\ 9 | {{if ne .AS.Type ""}}\ 10 | type: {{.AS.Type}} 11 | {{end}}\ 12 | {{if ne .AS.Country ""}}\ 13 | country: {{.AS.Country}} 14 | {{end}}\ 15 | {{if (isDateDefined .CreatedAt)}}\ 16 | created: {{.CreatedAt | formatDate}} 17 | {{end}}\ 18 | {{if (isDateDefined .UpdatedAt)}}\ 19 | changed: {{.UpdatedAt | formatDate}} 20 | {{end}}\ 21 | {{range .IPNetworks}}\ 22 | inetnum: {{.}} 23 | {{end}}\ 24 | {{range .AS.RoutingPolicy}}\ 25 | {{if gt .Cost 0}}\ 26 | as-in: from AS{{.Traffic}} {{.Cost}} accept {{.Policy}} 27 | {{else}}\ 28 | as-out: to AS{{.Traffic}} announce {{.Autnum}} 29 | {{end}}\ 30 | {{end}}\ 31 | 32 | ` + contactTmpl 33 | -------------------------------------------------------------------------------- /output/contact_info.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/registrobr/rdap/protocol" 8 | ) 9 | 10 | type contactInfo struct { 11 | Handle string 12 | Ids []string 13 | Persons []string 14 | Emails []string 15 | Addresses []string 16 | Phones []string 17 | Roles []string 18 | CreatedAt protocol.EventDate 19 | UpdatedAt protocol.EventDate 20 | } 21 | 22 | func (c *contactInfo) setContact(entity protocol.Entity) { 23 | c.Handle = entity.Handle 24 | for _, vCardValues := range entity.VCardArray { 25 | vCardValue, ok := vCardValues.([]interface{}) 26 | if !ok { 27 | continue 28 | } 29 | 30 | for _, value := range vCardValue { 31 | v, ok := value.([]interface{}) 32 | if !ok { 33 | continue 34 | } 35 | 36 | switch v[0] { 37 | case "fn": 38 | c.Persons = append(c.Persons, v[3].(string)) 39 | case "email": 40 | c.Emails = append(c.Emails, v[3].(string)) 41 | case "adr": 42 | var address []string 43 | 44 | addressLabel, ok := v[1].(map[string]interface{}) 45 | if ok { 46 | if label, ok := addressLabel["label"]; ok { 47 | labelStr := strings.Replace(label.(string), "\r", "", -1) 48 | labelParts := strings.Split(labelStr, "\n") 49 | for _, addr := range labelParts { 50 | address = append(address, addr) 51 | } 52 | } 53 | } 54 | 55 | addresses, ok := v[3].([]interface{}) 56 | if !ok { 57 | continue 58 | } 59 | 60 | for _, next := range addresses { 61 | switch v := next.(type) { 62 | case string: 63 | if len(v) > 0 { 64 | address = append(address, v) 65 | } 66 | 67 | case []interface {}: 68 | //according to https://tools.ietf.org/html/rfc7095#section-3.3.1.3 69 | // spec for structured values, an array of strings is allowed here 70 | for _, nestedNext := range v { 71 | vv, ok := nestedNext.(string); 72 | if !ok { 73 | continue 74 | } 75 | if len(vv) > 0 { 76 | address = append(address, vv) 77 | } 78 | 79 | } 80 | } 81 | } 82 | 83 | c.Addresses = append(c.Addresses, strings.Join(address, ", ")) 84 | case "tel": 85 | c.Phones = append(c.Phones, v[3].(string)) 86 | } 87 | } 88 | } 89 | 90 | for _, event := range entity.Events { 91 | switch event.Action { 92 | case protocol.EventActionRegistration: 93 | c.CreatedAt = event.Date 94 | case protocol.EventActionLastChanged: 95 | c.UpdatedAt = event.Date 96 | } 97 | } 98 | 99 | c.Roles = entity.Roles 100 | 101 | for _, id := range entity.PublicIds { 102 | c.Ids = append(c.Ids, fmt.Sprintf("%s (%s)", id.Identifier, id.Type)) 103 | } 104 | } 105 | 106 | type contactList interface { 107 | addContact(contactInfo) 108 | getContacts() []contactInfo 109 | setContacts(c []contactInfo) 110 | } 111 | 112 | func addContacts(c contactList, entities []protocol.Entity) { 113 | for _, entity := range entities { 114 | var contactInfo contactInfo 115 | contactInfo.setContact(entity) 116 | c.addContact(contactInfo) 117 | 118 | addContacts(c, entity.Entities) 119 | } 120 | } 121 | 122 | func filterContacts(c contactList) { 123 | contacts := make(map[string]*contactInfo) 124 | 125 | for _, contactInfo := range c.getContacts() { 126 | contactInfo := contactInfo 127 | 128 | if _, ok := contacts[contactInfo.Handle]; !ok { 129 | contacts[contactInfo.Handle] = &contactInfo 130 | continue 131 | } 132 | 133 | contacts[contactInfo.Handle].Roles = append(contacts[contactInfo.Handle].Roles, 134 | contactInfo.Roles...) 135 | } 136 | 137 | for _, contactInfo := range contacts { 138 | found := make(map[string]bool) 139 | roles := make([]string, 0) 140 | 141 | for _, role := range contactInfo.Roles { 142 | if _, ok := found[role]; ok { 143 | continue 144 | } 145 | 146 | roles = append(roles, role) 147 | found[role] = true 148 | } 149 | 150 | contactInfo.Roles = roles 151 | } 152 | 153 | filteredContacts := make([]contactInfo, 0) 154 | 155 | for _, contact := range contacts { 156 | filteredContacts = append(filteredContacts, *contact) 157 | } 158 | 159 | c.setContacts(filteredContacts) 160 | } 161 | -------------------------------------------------------------------------------- /output/contact_info_tmpl.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | const contactTmpl = `{{range .ContactsInfos}}\ 4 | handle: {{.Handle}} 5 | {{if len .Ids}}\ 6 | ids: {{.Ids | join}} 7 | {{end}}\ 8 | {{if len .Roles}}\ 9 | roles: {{.Roles | join}} 10 | {{end}}\ 11 | {{range .Persons}}\ 12 | person: {{.}} 13 | {{end}}\ 14 | {{range .Emails}}\ 15 | e-mail: {{.}} 16 | {{end}}\ 17 | {{range .Addresses}}\ 18 | address: {{.}} 19 | {{end}}\ 20 | {{range .Phones}}\ 21 | phone: {{.}} 22 | {{end}}\ 23 | {{if (isDateDefined .CreatedAt)}}\ 24 | created: {{.CreatedAt | formatDate}} 25 | {{end}}\ 26 | {{if (isDateDefined .UpdatedAt)}}\ 27 | changed: {{.UpdatedAt | formatDate}} 28 | {{end}}\ 29 | 30 | {{end}}` 31 | -------------------------------------------------------------------------------- /output/domain.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | "text/template" 7 | 8 | "github.com/registrobr/rdap/protocol" 9 | ) 10 | 11 | type Domain struct { 12 | Domain *protocol.Domain 13 | 14 | CreatedAt protocol.EventDate 15 | UpdatedAt protocol.EventDate 16 | ExpiresAt protocol.EventDate 17 | 18 | Handles map[string]string 19 | DS []ds 20 | ContactsInfos []contactInfo 21 | } 22 | 23 | type ds struct { 24 | protocol.DS 25 | CreatedAt protocol.EventDate 26 | } 27 | 28 | func (d *Domain) addContact(c contactInfo) { 29 | d.ContactsInfos = append(d.ContactsInfos, c) 30 | } 31 | 32 | func (d *Domain) getContacts() []contactInfo { 33 | return d.ContactsInfos 34 | } 35 | 36 | func (d *Domain) setContacts(c []contactInfo) { 37 | d.ContactsInfos = c 38 | } 39 | 40 | func (d *Domain) setDates() { 41 | for _, e := range d.Domain.Events { 42 | switch e.Action { 43 | case protocol.EventActionRegistration: 44 | d.CreatedAt = e.Date 45 | case protocol.EventActionLastChanged: 46 | d.UpdatedAt = e.Date 47 | case protocol.EventActionExpiration: 48 | d.ExpiresAt = e.Date 49 | } 50 | } 51 | } 52 | 53 | func (d *Domain) setDS() { 54 | if d.Domain.SecureDNS == nil { 55 | return 56 | } 57 | 58 | d.DS = make([]ds, len(d.Domain.SecureDNS.DSData)) 59 | 60 | for i, dsdatum := range d.Domain.SecureDNS.DSData { 61 | myds := ds{DS: dsdatum} 62 | 63 | for _, e := range dsdatum.Events { 64 | if e.Action == protocol.EventActionRegistration { 65 | myds.CreatedAt = e.Date 66 | } 67 | } 68 | 69 | d.DS[i] = myds 70 | } 71 | } 72 | 73 | func (d *Domain) Print(wr io.Writer) error { 74 | d.setDates() 75 | d.setDS() 76 | addContacts(d, d.Domain.Entities) 77 | filterContacts(d) 78 | 79 | t, err := template.New("domain template"). 80 | Funcs(genericFuncMap). 81 | Funcs(domainFuncMap). 82 | Parse(strings.Replace(domainTmpl, "\\\n", "", -1)) 83 | 84 | if err != nil { 85 | return err 86 | } 87 | 88 | return t.ExecuteTemplate(wr, "domain template", d) 89 | } 90 | -------------------------------------------------------------------------------- /output/domain_test.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/registrobr/rdap/protocol" 8 | ) 9 | 10 | func TestDomainPrint(t *testing.T) { 11 | domain := Domain{ 12 | Domain: &protocol.Domain{ 13 | ObjectClassName: "domain", 14 | LDHName: "example.br", 15 | Status: []protocol.Status{ 16 | "active", 17 | }, 18 | Links: []protocol.Link{ 19 | { 20 | Value: "https://rdap.registro.br/domain/example.br", 21 | Rel: "self", 22 | Href: "https://rdap.registro.br/domain/example.br", 23 | Type: "application/rdap+json", 24 | }, 25 | }, 26 | Entities: []protocol.Entity{ 27 | { 28 | ObjectClassName: "entity", 29 | Handle: "XXXX", 30 | VCardArray: []interface{}{ 31 | "vcard", 32 | []interface{}{ 33 | []interface{}{"version", struct{}{}, "text", "4.0"}, 34 | []interface{}{"fn", struct{}{}, "text", "Joe User"}, 35 | []interface{}{"kind", struct{}{}, "text", "individual"}, 36 | []interface{}{"email", struct{ Type string }{Type: "work"}, "text", "joe.user@example.com"}, 37 | []interface{}{"lang", struct{ Pref string }{Pref: "1"}, "language-tag", "pt"}, 38 | []interface{}{"adr", struct{ Type string }{Type: "work"}, "text", 39 | []interface{}{ 40 | "Av Naçoes Unidas", "11541", "7 andar", "Sao Paulo", "SP", "04578-000", "BR", 41 | }, 42 | }, 43 | []interface{}{"tel", struct{ Type string }{Type: "work"}, "uri", "tel:+55-11-5509-3506;ext=3506"}, 44 | }, 45 | }, 46 | Events: []protocol.Event{ 47 | { 48 | Action: protocol.EventActionRegistration, 49 | Date: protocol.Date(2015, 03, 01, 12, 00, 00, 00, time.UTC), 50 | }, 51 | { 52 | Action: protocol.EventActionLastChanged, 53 | Date: protocol.Date(2015, 03, 10, 14, 00, 00, 00, time.UTC), 54 | }, 55 | }, 56 | }, 57 | { 58 | ObjectClassName: "entity", 59 | Handle: "YYYY", 60 | VCardArray: []interface{}{ 61 | "vcard", 62 | []interface{}{ 63 | []interface{}{"version", struct{}{}, "text", "4.0"}, 64 | []interface{}{"fn", struct{}{}, "text", "Joe User 2"}, 65 | []interface{}{"kind", struct{}{}, "text", "individual"}, 66 | []interface{}{"email", struct{ Type string }{Type: "work"}, "text", "joe.user2@example.com"}, 67 | []interface{}{"lang", struct{ Pref string }{Pref: "1"}, "language-tag", "pt"}, 68 | []interface{}{"adr", struct{ Type string }{Type: "work"}, "text", 69 | []interface{}{ 70 | "Av Naçoes Unidas", "11541", "7 andar", "Sao Paulo", "SP", "04578-000", "BR", 71 | }, 72 | }, 73 | []interface{}{"tel", struct{ Type string }{Type: "work"}, "uri", "tel:+55-11-5509-3506;ext=3507"}, 74 | }, 75 | }, 76 | Events: []protocol.Event{ 77 | { 78 | Action: protocol.EventActionRegistration, 79 | Date: protocol.Date(2015, 03, 01, 12, 00, 00, 00, time.UTC), 80 | }, 81 | { 82 | Action: protocol.EventActionLastChanged, 83 | Date: protocol.Date(2015, 03, 10, 14, 00, 00, 00, time.UTC), 84 | }, 85 | }, 86 | }, 87 | }, 88 | Events: []protocol.Event{ 89 | { 90 | Action: protocol.EventActionRegistration, 91 | Date: protocol.Date(2015, 03, 01, 12, 00, 00, 00, time.UTC), 92 | }, 93 | { 94 | Action: protocol.EventActionLastChanged, 95 | Date: protocol.Date(2015, 03, 10, 14, 00, 00, 00, time.UTC), 96 | }, 97 | }, 98 | Nameservers: []protocol.Nameserver{ 99 | { 100 | ObjectClassName: "nameserver", 101 | LDHName: "a.dns.br", 102 | Events: []protocol.Event{ 103 | { 104 | Action: protocol.EventDelegationCheck, 105 | Status: []protocol.Status{protocol.StatusNSAA}, 106 | }, 107 | }, 108 | }, 109 | }, 110 | SecureDNS: &protocol.SecureDNS{ 111 | DSData: []protocol.DS{ 112 | { 113 | KeyTag: 12345, 114 | Digest: "0123456789ABCDEF0123456789ABCDEF01234567", 115 | Algorithm: 5, 116 | Events: []protocol.Event{ 117 | { 118 | Action: protocol.EventActionRegistration, 119 | Date: protocol.Date(2015, 03, 01, 12, 00, 00, 00, time.UTC), 120 | }, 121 | { 122 | Action: protocol.EventDelegationSignCheck, 123 | Status: []protocol.Status{protocol.StatusDSOK}, 124 | Date: protocol.Date(2015, 03, 01, 12, 00, 00, 00, time.UTC), 125 | }, 126 | }, 127 | }, 128 | }, 129 | }, 130 | }, 131 | } 132 | 133 | expected := ` 134 | domain: example.br 135 | nserver: a.dns.br 136 | dsrecord: 12345 RSASHA1 0123456789ABCDEF0123456789ABCDEF01234567 137 | dsstatus: 20150301 ds ok 138 | dslastok: 00010101 139 | created: 20150301 140 | changed: 20150310 141 | status: active 142 | 143 | handle: XXXX 144 | person: Joe User 145 | e-mail: joe.user@example.com 146 | address: Av Naçoes Unidas, 11541, 7 andar, Sao Paulo, SP, 04578-000, BR 147 | phone: tel:+55-11-5509-3506;ext=3506 148 | created: 20150301 149 | changed: 20150310 150 | 151 | handle: YYYY 152 | person: Joe User 2 153 | e-mail: joe.user2@example.com 154 | address: Av Naçoes Unidas, 11541, 7 andar, Sao Paulo, SP, 04578-000, BR 155 | phone: tel:+55-11-5509-3506;ext=3507 156 | created: 20150301 157 | changed: 20150310 158 | 159 | ` 160 | 161 | var w WriterMock 162 | if err := domain.Print(&w); err != nil { 163 | t.Fatal(err) 164 | } 165 | 166 | if string(w.Content) != expected { 167 | for _, l := range diff(expected, string(w.Content)) { 168 | t.Log(l) 169 | } 170 | t.Fatal("error") 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /output/domain_tmpl.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "text/template" 5 | 6 | "github.com/registrobr/rdap/protocol" 7 | ) 8 | 9 | const ( 10 | domainTmpl = ` 11 | domain: {{.Domain.LDHName}} 12 | {{range .Domain.Nameservers}}\ 13 | nserver: {{.LDHName}} 14 | {{$lastCheck := nsLastCheck .Events}}\ 15 | {{if (isDateDefined $lastCheck)}}\ 16 | nsstat: {{$lastCheck | formatDate}} {{nsStatus .Events}} 17 | {{end}}\ 18 | {{$lastOK := nsLastOK .Events}}\ 19 | {{if (isDateDefined $lastOK)}}\ 20 | nslastaa: {{$lastOK | formatDate}} 21 | {{end}}\ 22 | {{end}}\ 23 | {{range .DS}}\ 24 | dsrecord: {{.KeyTag}} {{.Algorithm | dsAlgorithm}} {{.Digest}} 25 | dsstatus: {{dsLastCheck .Events | formatDate}} {{dsStatus .Events}} 26 | dslastok: {{dsLastOK .Events | formatDate}} 27 | {{end}}\ 28 | {{if (isDateDefined .CreatedAt)}}\ 29 | created: {{.CreatedAt | formatDate}} 30 | {{end}}\ 31 | {{if (isDateDefined .UpdatedAt)}}\ 32 | changed: {{.UpdatedAt | formatDate}} 33 | {{end}}\ 34 | {{range .Domain.Status}}\ 35 | status: {{.}} 36 | {{end}}\ 37 | 38 | ` + contactTmpl 39 | dateFormat = "20060102" 40 | ) 41 | 42 | var ( 43 | dsAlgorithms = map[int]string{ 44 | 1: "RSAMD5", 45 | 2: "DH", 46 | 3: "DSASHA1", 47 | 4: "ECC", 48 | 5: "RSASHA1", 49 | 6: "DSASHA1NSEC3", 50 | 7: "RSASHA1NSEC3", 51 | 8: "RSASHA256", 52 | 10: "RSASHA512", 53 | 12: "ECCGOST", 54 | 13: "ECDSASHA256", 55 | 14: "ECDSASHA384", 56 | 252: "INDIRECT", 57 | 253: "PRIVATEDNS", 58 | 254: "PRIVATEOID", 59 | } 60 | 61 | domainFuncMap = template.FuncMap{ 62 | "nsStatus": func(events []protocol.Event) protocol.Status { 63 | for _, event := range events { 64 | if event.Action == protocol.EventDelegationCheck && len(event.Status) > 0 { 65 | return event.Status[0] 66 | } 67 | } 68 | 69 | return protocol.Status("") 70 | }, 71 | "nsLastCheck": func(events []protocol.Event) protocol.EventDate { 72 | for _, event := range events { 73 | if event.Action == protocol.EventDelegationCheck && len(event.Status) > 0 { 74 | return event.Date 75 | } 76 | } 77 | 78 | return protocol.EventDate{} 79 | }, 80 | "nsLastOK": func(events []protocol.Event) protocol.EventDate { 81 | for _, event := range events { 82 | if event.Action == protocol.EventLastCorrectDelegationCheck { 83 | return event.Date 84 | } 85 | } 86 | 87 | return protocol.EventDate{} 88 | }, 89 | "dsAlgorithm": func(id int) string { 90 | return dsAlgorithms[id] 91 | }, 92 | "dsStatus": func(events []protocol.Event) protocol.Status { 93 | for _, event := range events { 94 | if event.Action == protocol.EventDelegationSignCheck && len(event.Status) > 0 { 95 | return event.Status[0] 96 | } 97 | } 98 | 99 | return protocol.Status("") 100 | }, 101 | "dsLastCheck": func(events []protocol.Event) protocol.EventDate { 102 | for _, event := range events { 103 | if event.Action == protocol.EventDelegationSignCheck && len(event.Status) > 0 { 104 | return event.Date 105 | } 106 | } 107 | 108 | return protocol.EventDate{} 109 | }, 110 | "dsLastOK": func(events []protocol.Event) protocol.EventDate { 111 | for _, event := range events { 112 | if event.Action == protocol.EventLastCorrectDelegationSignCheck { 113 | return event.Date 114 | } 115 | } 116 | 117 | return protocol.EventDate{} 118 | }, 119 | } 120 | ) 121 | -------------------------------------------------------------------------------- /output/entity.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | "text/template" 7 | 8 | "github.com/registrobr/rdap/protocol" 9 | ) 10 | 11 | type Entity struct { 12 | Entity *protocol.Entity 13 | 14 | CreatedAt protocol.EventDate 15 | UpdatedAt protocol.EventDate 16 | 17 | ContactsInfos []contactInfo 18 | } 19 | 20 | func (e *Entity) AddContact(c contactInfo) { 21 | e.ContactsInfos = append(e.ContactsInfos, c) 22 | } 23 | 24 | func (e *Entity) setDates() { 25 | for _, ev := range e.Entity.Events { 26 | switch ev.Action { 27 | case protocol.EventActionRegistration: 28 | e.CreatedAt = ev.Date 29 | case protocol.EventActionLastChanged: 30 | e.UpdatedAt = ev.Date 31 | } 32 | } 33 | } 34 | 35 | func (e *Entity) Print(wr io.Writer) error { 36 | e.setDates() 37 | var contactInfo contactInfo 38 | contactInfo.setContact(*e.Entity) 39 | e.ContactsInfos = append(e.ContactsInfos, contactInfo) 40 | 41 | t, err := template.New("entity template"). 42 | Funcs(genericFuncMap). 43 | Parse(strings.Replace(contactTmpl, "\\\n", "", -1)) 44 | 45 | if err != nil { 46 | return err 47 | } 48 | 49 | return t.Execute(wr, e) 50 | } 51 | -------------------------------------------------------------------------------- /output/entity_test.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/registrobr/rdap/protocol" 8 | ) 9 | 10 | var expectedEntityOutput = `handle: XXXX 11 | person: Joe User 12 | e-mail: joe.user@example.com 13 | address: Av Naçoes Unidas, 11541, 7 andar, Sao Paulo, SP, 04578-000, BR 14 | phone: tel:+55-11-5509-3506;ext=3506 15 | created: 20150301 16 | changed: 20150310 17 | 18 | ` 19 | 20 | func TestEntityPrint(t *testing.T) { 21 | entityResponse := protocol.Entity{ 22 | ObjectClassName: "entity", 23 | Handle: "XXXX", 24 | VCardArray: []interface{}{ 25 | "vcard", 26 | []interface{}{ 27 | []interface{}{"version", struct{}{}, "text", "4.0"}, 28 | []interface{}{"fn", struct{}{}, "text", "Joe User"}, 29 | []interface{}{"kind", struct{}{}, "text", "individual"}, 30 | []interface{}{"email", struct{ Type string }{Type: "work"}, "text", "joe.user@example.com"}, 31 | []interface{}{"lang", struct{ Pref string }{Pref: "1"}, "language-tag", "pt"}, 32 | []interface{}{"adr", struct{ Type string }{Type: "work"}, "text", 33 | []interface{}{ 34 | "Av Naçoes Unidas", "11541", "7 andar", "Sao Paulo", "SP", "04578-000", "BR", 35 | }, 36 | }, 37 | []interface{}{"tel", struct{ Type string }{Type: "work"}, "uri", "tel:+55-11-5509-3506;ext=3506"}, 38 | }, 39 | }, 40 | LegalRepresentative: "Joe User", 41 | Entities: []protocol.Entity{ 42 | { 43 | ObjectClassName: "entity", 44 | Handle: "XXXX", 45 | VCardArray: []interface{}{ 46 | "vcard", 47 | []interface{}{ 48 | []interface{}{"version", struct{}{}, "text", "4.0"}, 49 | []interface{}{"fn", struct{}{}, "text", "Joe User"}, 50 | []interface{}{"kind", struct{}{}, "text", "individual"}, 51 | []interface{}{"email", struct{ Type string }{Type: "work"}, "text", "joe.user@example.com"}, 52 | []interface{}{"lang", struct{ Pref string }{Pref: "1"}, "language-tag", "pt"}, 53 | []interface{}{"adr", struct{ Type string }{Type: "work"}, "text", 54 | []interface{}{ 55 | "Av Naçoes Unidas", "11541", "7 andar", "Sao Paulo", "SP", "04578-000", "BR", 56 | }, 57 | }, 58 | []interface{}{"tel", struct{ Type string }{Type: "work"}, "uri", "tel:+55-11-5509-3506;ext=3506"}, 59 | }, 60 | }, 61 | Events: []protocol.Event{ 62 | { 63 | Action: protocol.EventActionRegistration, 64 | Date: protocol.Date(2015, 03, 01, 12, 00, 00, 00, time.UTC), 65 | }, 66 | { 67 | Action: protocol.EventActionLastChanged, 68 | Date: protocol.Date(2015, 03, 10, 14, 00, 00, 00, time.UTC), 69 | }, 70 | }, 71 | }, 72 | }, 73 | Events: []protocol.Event{ 74 | { 75 | Action: protocol.EventActionRegistration, 76 | Date: protocol.Date(2015, 03, 01, 12, 00, 00, 00, time.UTC), 77 | }, 78 | { 79 | Action: protocol.EventActionLastChanged, 80 | Date: protocol.Date(2015, 03, 10, 14, 00, 00, 00, time.UTC), 81 | }, 82 | }, 83 | } 84 | 85 | entityOutput := Entity{Entity: &entityResponse} 86 | 87 | var w WriterMock 88 | if err := entityOutput.Print(&w); err != nil { 89 | t.Fatal(err) 90 | } 91 | 92 | if string(w.Content) != expectedEntityOutput { 93 | for _, l := range diff(expectedEntityOutput, string(w.Content)) { 94 | t.Log(l) 95 | } 96 | t.Fatal("error") 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /output/generic.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "strings" 5 | "text/template" 6 | 7 | "github.com/registrobr/rdap/protocol" 8 | ) 9 | 10 | var ( 11 | genericFuncMap = template.FuncMap{ 12 | "isDateDefined": func(time protocol.EventDate) bool { 13 | return !time.IsZero() 14 | }, 15 | "formatDate": func(time protocol.EventDate) string { 16 | return time.Format(dateFormat) 17 | }, 18 | "join": func(in []string) string { 19 | return strings.Join(in, ", ") 20 | }, 21 | } 22 | ) 23 | -------------------------------------------------------------------------------- /output/helper_test.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/aryann/difflib" 8 | ) 9 | 10 | func diff(a, b interface{}) []difflib.DiffRecord { 11 | return difflib.Diff(strings.Split(fmt.Sprintf("%v", a), "\n"), 12 | strings.Split(fmt.Sprintf("%v", b), "\n")) 13 | } 14 | 15 | type WriterMock struct { 16 | Content []byte 17 | Err error 18 | 19 | MockWrite func(p []byte) (n int, err error) 20 | } 21 | 22 | func (w *WriterMock) Write(p []byte) (n int, err error) { 23 | if w.MockWrite != nil { 24 | return w.MockWrite(p) 25 | } 26 | 27 | w.Content = append(w.Content, p...) 28 | return len(p), w.Err 29 | } 30 | -------------------------------------------------------------------------------- /output/ipnet.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | "text/template" 7 | 8 | "github.com/registrobr/rdap/protocol" 9 | ) 10 | 11 | type IPNetwork struct { 12 | IPNetwork *protocol.IPNetwork 13 | CreatedAt protocol.EventDate 14 | UpdatedAt protocol.EventDate 15 | ContactsInfos []contactInfo 16 | } 17 | 18 | func (i *IPNetwork) addContact(c contactInfo) { 19 | i.ContactsInfos = append(i.ContactsInfos, c) 20 | } 21 | 22 | func (i *IPNetwork) getContacts() []contactInfo { 23 | return i.ContactsInfos 24 | } 25 | 26 | func (i *IPNetwork) setContacts(c []contactInfo) { 27 | i.ContactsInfos = c 28 | } 29 | 30 | func (i *IPNetwork) setDates() { 31 | for _, e := range i.IPNetwork.Events { 32 | switch e.Action { 33 | case protocol.EventActionRegistration: 34 | i.CreatedAt = e.Date 35 | case protocol.EventActionLastChanged: 36 | i.UpdatedAt = e.Date 37 | } 38 | } 39 | } 40 | 41 | func (i *IPNetwork) Print(wr io.Writer) error { 42 | i.setDates() 43 | addContacts(i, i.IPNetwork.Entities) 44 | filterContacts(i) 45 | 46 | t, err := template.New("ipnetwork template"). 47 | Funcs(genericFuncMap). 48 | Funcs(ipnetFuncMap). 49 | Parse(strings.Replace(ipnetTmpl, "\\\n", "", -1)) 50 | 51 | if err != nil { 52 | return err 53 | } 54 | 55 | return t.Execute(wr, i) 56 | } 57 | -------------------------------------------------------------------------------- /output/ipnet_test.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/registrobr/rdap/protocol" 8 | ) 9 | 10 | func TestIPNetPrint(t *testing.T) { 11 | ipNetwork := IPNetwork{ 12 | IPNetwork: &protocol.IPNetwork{ 13 | ObjectClassName: "ip network", 14 | Handle: "200.160.3.0/24", 15 | ParentHandle: "200.160.0.0/16", 16 | StartAddress: "200.160.3.0", 17 | EndAddress: "200.160.3.255", 18 | IPVersion: "v4", 19 | Name: "Crazy Organization", 20 | Type: "DIRECT ALLOCATION", 21 | Country: "BR", 22 | Autnum: 1234, 23 | Status: []string{"active"}, 24 | Events: []protocol.Event{ 25 | { 26 | Action: protocol.EventActionRegistration, 27 | Date: protocol.Date(2015, 03, 01, 12, 00, 00, 00, time.UTC), 28 | }, 29 | { 30 | Action: protocol.EventActionLastChanged, 31 | Date: protocol.Date(2015, 03, 10, 14, 00, 00, 00, time.UTC), 32 | }, 33 | }, 34 | Entities: []protocol.Entity{ 35 | { 36 | ObjectClassName: "entity", 37 | Handle: "XXXX", 38 | VCardArray: []interface{}{ 39 | "vcard", 40 | []interface{}{ 41 | []interface{}{"version", struct{}{}, "text", "4.0"}, 42 | []interface{}{"fn", struct{}{}, "text", "Joe User"}, 43 | []interface{}{"kind", struct{}{}, "text", "individual"}, 44 | []interface{}{"email", struct{ Type string }{Type: "work"}, "text", "joe.user@example.com"}, 45 | []interface{}{"lang", struct{ Pref string }{Pref: "1"}, "language-tag", "pt"}, 46 | []interface{}{"adr", struct{ Type string }{Type: "work"}, "text", 47 | []interface{}{ 48 | "Av Naçoes Unidas", "11541", "7 andar", "Sao Paulo", "SP", "04578-000", "BR", 49 | }, 50 | }, 51 | []interface{}{"tel", struct{ Type string }{Type: "work"}, "uri", "tel:+55-11-5509-3506;ext=3506"}, 52 | }, 53 | }, 54 | Events: []protocol.Event{ 55 | { 56 | Action: protocol.EventActionRegistration, 57 | Date: protocol.Date(2015, 03, 01, 12, 00, 00, 00, time.UTC), 58 | }, 59 | { 60 | Action: protocol.EventActionLastChanged, 61 | Date: protocol.Date(2015, 03, 10, 14, 00, 00, 00, time.UTC), 62 | }, 63 | }, 64 | }, 65 | }, 66 | ReverseDelegations: []protocol.ReverseDelegation{ 67 | { 68 | StartAddress: "200.160.3.0", 69 | EndAddress: "200.160.3.255", 70 | Nameservers: []protocol.Nameserver{ 71 | {LDHName: "a.dns.br"}, 72 | {LDHName: "b.dns.br"}, 73 | }, 74 | }, 75 | }, 76 | }, 77 | } 78 | 79 | expected := ` 80 | inetnum: 200.160.3.0/24 81 | handle: 200.160.3.0/24 82 | parent-handle: 200.160.0.0/16 83 | aut-num: 1234 84 | start-address: 200.160.3.0 85 | end-address: 200.160.3.255 86 | ip-version: v4 87 | name: Crazy Organization 88 | type: DIRECT ALLOCATION 89 | country: BR 90 | status: active 91 | inetrev: 200.160.3.0/24 92 | nserver: a.dns.br 93 | nserver: b.dns.br 94 | created: 20150301 95 | changed: 20150310 96 | 97 | handle: XXXX 98 | person: Joe User 99 | e-mail: joe.user@example.com 100 | address: Av Naçoes Unidas, 11541, 7 andar, Sao Paulo, SP, 04578-000, BR 101 | phone: tel:+55-11-5509-3506;ext=3506 102 | created: 20150301 103 | changed: 20150310 104 | 105 | ` 106 | 107 | var w WriterMock 108 | if err := ipNetwork.Print(&w); err != nil { 109 | t.Fatal(err) 110 | } 111 | 112 | if string(w.Content) != expected { 113 | for _, l := range diff(expected, string(w.Content)) { 114 | t.Log(l) 115 | } 116 | t.Fatal("error") 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /output/ipnet_tmpl.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | "text/template" 10 | 11 | rdap "github.com/registrobr/rdap/protocol" 12 | ) 13 | 14 | var ipnetTmpl = ` 15 | inetnum: {{inetnum .IPNetwork.StartAddress .IPNetwork.EndAddress}} 16 | handle: {{.IPNetwork.Handle}} 17 | {{if ne .IPNetwork.ParentHandle ""}}\ 18 | parent-handle: {{.IPNetwork.ParentHandle}} 19 | {{end}}\ 20 | {{if gt .IPNetwork.Autnum 0}}\ 21 | aut-num: {{.IPNetwork.Autnum}} 22 | {{end}}\ 23 | start-address: {{.IPNetwork.StartAddress}} 24 | end-address: {{.IPNetwork.EndAddress}} 25 | ip-version: {{.IPNetwork.IPVersion}} 26 | name: {{.IPNetwork.Name}} 27 | {{if ne .IPNetwork.Type ""}}\ 28 | type: {{.IPNetwork.Type}} 29 | {{end}}\ 30 | {{if ne .IPNetwork.Country ""}}\ 31 | country: {{.IPNetwork.Country}} 32 | {{end}}\ 33 | {{range .IPNetwork.Status}}\ 34 | status: {{.}} 35 | {{end}}\ 36 | {{range .IPNetwork.ReverseDelegations}}\ 37 | {{- $startAddress := .StartAddress -}} 38 | {{- $endAddress := .EndAddress -}} 39 | inetrev: {{inetnum $startAddress $endAddress}} 40 | {{range .Nameservers}}\ 41 | nserver: {{.LDHName}} 42 | {{end}}\ 43 | {{ if hasSecureDns .SecureDNS}}\ 44 | {{ range .SecureDNS.DSSet }}\ 45 | dsinetrev: {{reverseAddressToCIDR .Zone}} 46 | dsrecord: {{.KeyTag}} {{.Digest}} 47 | {{ range .Events }}\ 48 | {{ if and (eq .Action "delegation sign check") (gt (lenStatus .Status) 0)}}\ 49 | dsstatus: {{ .Date | formatDate }} {{dsStatusTranslate (index .Status 0)}} 50 | {{ else if eq .Action "registration"}}\ 51 | dsstatus: {{ .Date | formatDate }} OK 52 | {{ else if eq .Action "last correct delegation sign check" }}\ 53 | dslastok: {{ .Date | formatDate }} 54 | {{ end }}\ 55 | {{ end }}\ 56 | {{ end }}\ 57 | {{ end }}\ 58 | {{end}}\ 59 | {{if (isDateDefined .CreatedAt)}}\ 60 | created: {{.CreatedAt | formatDate}} 61 | {{end}}\ 62 | {{if (isDateDefined .UpdatedAt)}}\ 63 | changed: {{.UpdatedAt | formatDate}} 64 | {{end}}\ 65 | 66 | ` + contactTmpl 67 | 68 | var ( 69 | ipnetFuncMap = template.FuncMap{ 70 | "inetnum": func(startAddress, endAddress string) string { 71 | start := net.ParseIP(startAddress) 72 | end := net.ParseIP(endAddress) 73 | mask := make(net.IPMask, len(start)) 74 | 75 | for j := 0; j < len(start); j++ { 76 | mask[j] = start[j] | ^end[j] 77 | } 78 | 79 | cidr := net.IPNet{IP: start, Mask: mask} 80 | return cidr.String() 81 | }, 82 | "lenStatus": func(s []rdap.Status) int { 83 | return len(s) 84 | }, 85 | "dsStatusTranslate": func(rs rdap.Status) string { 86 | switch rs { 87 | case rdap.StatusDSOK: 88 | return "OK" 89 | case rdap.StatusDSTimeout: 90 | return "TIMEOUT" 91 | case rdap.StatusDSNoSig: 92 | return "NOSIG" 93 | case rdap.StatusDSExpiredSig: 94 | return "EXPSIG" 95 | case rdap.StatusDSInvalidSig: 96 | return "SIGERROR" 97 | case rdap.StatusDSNotFound: 98 | return "NOKEY" 99 | case rdap.StatusDSNoSEP: 100 | return "NOSEP" 101 | } 102 | 103 | return "PLAIN DNS ERROR" 104 | }, 105 | "hasSecureDns": func(secdns *rdap.ReverseDelegationSecureDNS) bool { 106 | return secdns != nil 107 | }, 108 | "reverseAddressToCIDR": func(zone string) string { 109 | var cidr string 110 | 111 | // First, check if it is an IPv4 or IPv6 reverse address 112 | ipv4ReverseAddressRX := regexp.MustCompile(`^[\d.]+in-addr\.arpa\.?$`) 113 | ipv6ReverseAddressRX := regexp.MustCompile(`[\da-f.]+ip6\.arpa\.?$`) 114 | 115 | splitZone := strings.Split(zone, ".") 116 | 117 | if ipv4ReverseAddressRX.MatchString(zone) { 118 | numOctets := len(splitZone) - 2 119 | count := 0 120 | for i := len(splitZone) - 1; i >= 0; i-- { 121 | if splitZone[i] != "in-addr" && splitZone[i] != "arpa" { 122 | cidr += splitZone[i] 123 | count++ 124 | 125 | if count < numOctets { 126 | cidr += "." 127 | } 128 | } 129 | } 130 | for i := 0; i < 4-numOctets; i++ { 131 | cidr += ".0" 132 | } 133 | cidr += "/24" 134 | } else if ipv6ReverseAddressRX.MatchString(zone) { 135 | count, nibble := 0, 0 136 | for i := len(splitZone) - 1; i >= 0; i-- { 137 | if splitZone[i] != "ip6" && splitZone[i] != "arpa" { 138 | cidr += splitZone[i] 139 | nibble++ 140 | 141 | if nibble == 4 { 142 | count++ 143 | if count < 4 { 144 | cidr += ":" 145 | } 146 | nibble = 0 147 | } 148 | } 149 | } 150 | if count < 4 { 151 | cidr += ":" 152 | } 153 | cidr += "/48" 154 | 155 | _, ipnet, err := parseCIDR(cidr) 156 | if err != nil { 157 | return "" 158 | } 159 | 160 | cidr = ipnet.String() 161 | } 162 | return cidr 163 | }, 164 | } 165 | ) 166 | 167 | func parseCIDR(cidr string) (net.IP, *net.IPNet, error) { 168 | // check IPv6 169 | if strings.Contains(cidr, ":") { 170 | return net.ParseCIDR(cidr) 171 | } 172 | 173 | cidrParts := strings.Split(cidr, "/") 174 | if len(cidrParts) != 2 { 175 | return nil, nil, &net.ParseError{Type: "CIDR address", Text: cidr} 176 | } 177 | 178 | prefix, err := strconv.Atoi(cidrParts[1]) 179 | if err != nil { 180 | return nil, nil, &net.ParseError{Type: "CIDR address prefix", Text: cidr} 181 | } 182 | 183 | var fillOctets int 184 | 185 | if prefix <= 8 { 186 | fillOctets = 3 187 | } else if prefix <= 16 { 188 | fillOctets = 2 189 | } else if prefix <= 24 { 190 | fillOctets = 1 191 | } 192 | 193 | octets := strings.Split(cidrParts[0], ".") 194 | 195 | for len(octets) < 4 { 196 | if fillOctets <= 0 { 197 | // inconsistency between missing octets and prefix 198 | return nil, nil, &net.ParseError{Type: "CIDR octets", Text: cidr} 199 | } 200 | 201 | fillOctets-- 202 | octets = append(octets, "0") 203 | } 204 | 205 | cidr = fmt.Sprintf("%s/%d", strings.Join(octets, "."), prefix) 206 | 207 | ip, ipnet, err := net.ParseCIDR(cidr) 208 | if err == nil && cidr != ipnet.String() { 209 | // This is not considered an error for net.ParseCIDR(), 210 | // but we decided to compare the exact CIDR that comes from 211 | // request with the net.ParseCIDR() result. By our own rules 212 | // we must return error for this case 213 | return net.IP{}, nil, &net.ParseError{Type: "CIDR address", Text: cidr} 214 | } 215 | 216 | return ip, ipnet, err 217 | } 218 | -------------------------------------------------------------------------------- /output/printer.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import "io" 4 | 5 | type Printer interface { 6 | Print(io.Writer) error 7 | } 8 | --------------------------------------------------------------------------------