diff --git a/README.md b/README.md index 3ce0a36..be0ad88 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ https://www.openrdap.org - homepage https://www.openrdap.org/demo - live demo ## Features + * Command line RDAP client * Output formats: text, JSON, WHOIS style * Query types supported: @@ -27,7 +28,7 @@ https://www.openrdap.org/demo - live demo * entity-search-by-handle * Automatic server detection for ip/domain/autnum/entities * Object tags support -* Bootstrap cache (optional, uses ~/.openrdap by default) +* Bootstrap cache (optional, uses $XDG_CACHE_HOME/openrdap by default, falling back to ~/.cache/openrdap) * X.509 client authentication ## Installation @@ -44,17 +45,18 @@ This will install the "rdap" binary in your $GOPATH/go/bin directory. Try runnin ## Usage -| Query type | Usage | -| ------------------------- | ------------------------------------------------------------------------ | -| Domain (.com) | rdap -v example.com | -| 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 | +| Query type | Usage | +|--------------------------|---------------------| +| Domain (.com) | rdap -v example.com | +| 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 | @@ -724,12 +726,15 @@ Click the examples to see the output: ## Go docs + [![godoc](https://godoc.org/github.com/openrdap/rdap?status.png)](https://godoc.org/github.com/openrdap/rdap) ## Uses + Go 1.20+ ## Links + - Wikipedia - [Registration Data Access Protocol](https://en.wikipedia.org/wiki/Registration_Data_Access_Protocol) - ICANN - [RDAP](https://www.icann.org/rdap) diff --git a/bootstrap/cache/disk_cache.go b/bootstrap/cache/disk_cache.go index 613f526..9c66aba 100644 --- a/bootstrap/cache/disk_cache.go +++ b/bootstrap/cache/disk_cache.go @@ -11,17 +11,17 @@ import ( "path/filepath" "time" - homedir "github.com/mitchellh/go-homedir" + "github.com/mitchellh/go-homedir" ) const ( - defaultCacheDirName = ".openrdap" + defaultCacheDirName = "openrdap" ) // A DiskCache caches Service Registry files on disk. // -// By default they're saved as $HOME/.openrdap/{asn,dns,ipv4,ipv6}.json. File -// mtimes are used to calculate cache expiry. +// By default they're saved as $XDG_CACHE_HOME/openrdap/{asn,dns,ipv4,ipv6}.json. +// File mtimes are used to calculate cache expiry. // // The cache directory is created automatically as needed. type DiskCache struct { @@ -32,7 +32,8 @@ type DiskCache struct { // Directory to store cached files in. // - // The default is $HOME/.openrdap. + // The default is $XDG_CACHE_HOME/openrdap (falling back to + // $HOME/.cache/openrdap). Dir string lastLoadedModTime map[string]time.Time @@ -41,17 +42,23 @@ type DiskCache struct { // NewDiskCache creates a new DiskCache. func NewDiskCache() *DiskCache { d := &DiskCache{ - lastLoadedModTime: make(map[string]time.Time), Timeout: time.Hour * 24, + lastLoadedModTime: make(map[string]time.Time), } - dir, err := homedir.Dir() + // Honor $XDG_CACHE_HOME, falling back to $HOME/.cache. Relative values are + // ignored, per the XDG Base Directory spec. + cacheDir := os.Getenv("XDG_CACHE_HOME") + if !filepath.IsAbs(cacheDir) { + home, err := homedir.Dir() + if err != nil { + panic("Cannot determine home directory: HOME environment variable not set or inaccessible") + } - if err != nil { - panic("Can't determine your home directory") + cacheDir = filepath.Join(home, ".cache") } - d.Dir = filepath.Join(dir, defaultCacheDirName) + d.Dir = filepath.Join(cacheDir, defaultCacheDirName) return d } @@ -65,21 +72,20 @@ func (d *DiskCache) InitDir() (bool, error) { if err == nil { if fileInfo.IsDir() { return false, nil - } else { - return false, errors.New("Cache dir is not a dir") } + + return false, errors.New("Cache dir is not a dir") } if os.IsNotExist(err) { - err := os.Mkdir(d.Dir, 0775) - if err == nil { - return true, nil - } else { + if err = os.MkdirAll(d.Dir, 0775); err != nil { return false, err } - } else { - return false, err + + return true, nil } + + return false, err } // SetTimeout sets the duration each Service Registry file can be stored before @@ -92,23 +98,21 @@ func (d *DiskCache) SetTimeout(timeout time.Duration) { // // The cache directory is created if necessary. func (d *DiskCache) Save(filename string, data []byte) error { - _, err := d.InitDir() - if err != nil { + if _, err := d.InitDir(); err != nil { return err } - err = os.WriteFile(d.cacheDirPath(filename), data, 0664) - if err != nil { + if err := os.WriteFile(d.cacheDirPath(filename), data, 0664); err != nil { return err } fileModTime, err := d.modTime(filename) - if err == nil { - d.lastLoadedModTime[filename] = fileModTime - } else { + if err != nil { return fmt.Errorf("File %s failed to save correctly: %s", filename, err) } + d.lastLoadedModTime[filename] = fileModTime + return nil } @@ -125,8 +129,8 @@ func (d *DiskCache) Load(filename string) ([]byte, error) { } var bytes []byte - bytes, err = os.ReadFile(d.cacheDirPath(filename)) + bytes, err = os.ReadFile(d.cacheDirPath(filename)) if err != nil { return nil, err } @@ -140,8 +144,8 @@ func (d *DiskCache) Load(filename string) ([]byte, error) { // // The returned state is one of: Absent, Good, ShouldReload, Expired. func (d *DiskCache) State(filename string) FileState { - var expiry time.Time = time.Now().Add(-d.Timeout) - var state FileState = Absent + var expiry = time.Now().Add(-d.Timeout) + var state = Absent fileModTime, err := d.modTime(filename) if err == nil { diff --git a/bootstrap/cache/disk_cache_test.go b/bootstrap/cache/disk_cache_test.go index 5106624..b4e96a1 100644 --- a/bootstrap/cache/disk_cache_test.go +++ b/bootstrap/cache/disk_cache_test.go @@ -10,8 +10,42 @@ import ( "path/filepath" "testing" "time" + + "github.com/mitchellh/go-homedir" ) +func TestNewDiskCacheDir(t *testing.T) { + homedir.DisableCache = true + defer func() { homedir.DisableCache = false }() + + home, err := os.MkdirTemp("", "home") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(home) + + t.Setenv("HOME", home) + + // XDG_CACHE_HOME set (absolute). + xdg := filepath.Join(home, "custom-cache") + t.Setenv("XDG_CACHE_HOME", xdg) + if got, want := NewDiskCache().Dir, filepath.Join(xdg, "openrdap"); got != want { + t.Errorf("with XDG_CACHE_HOME set: got %q, want %q", got, want) + } + + // XDG_CACHE_HOME unset -> $HOME/.cache/openrdap. + t.Setenv("XDG_CACHE_HOME", "") + if got, want := NewDiskCache().Dir, filepath.Join(home, ".cache", "openrdap"); got != want { + t.Errorf("with XDG_CACHE_HOME unset: got %q, want %q", got, want) + } + + // Relative XDG_CACHE_HOME is ignored per the XDG spec. + t.Setenv("XDG_CACHE_HOME", "relative/path") + if got, want := NewDiskCache().Dir, filepath.Join(home, ".cache", "openrdap"); got != want { + t.Errorf("with relative XDG_CACHE_HOME: got %q, want %q", got, want) + } +} + func TestDiskCache(t *testing.T) { dir, err := os.MkdirTemp("", "test") if err != nil { diff --git a/bootstrap/client.go b/bootstrap/client.go index bdd4ac9..9ccd28a 100644 --- a/bootstrap/client.go +++ b/bootstrap/client.go @@ -66,7 +66,7 @@ // By default, Service Registry files are cached in memory. bootstrap.Client // also supports caching the Service Registry files on disk. The default cache // location is -// $HOME/.openrdap/. +// $XDG_CACHE_HOME/openrdap/ (falling back to $HOME/.cache/openrdap/). // // Disk cache usage: // diff --git a/cli.go b/cli.go index e5de349..9d62526 100644 --- a/cli.go +++ b/cli.go @@ -80,7 +80,9 @@ Advanced options (query): Advanced options (bootstrapping): --cache-dir=DIR Bootstrap cache directory to use. Specify empty string to disable bootstrap caching. The directory is created - automatically as needed. (default: $HOME/.openrdap). + automatically as needed. + (default: $XDG_CACHE_HOME/openrdap, falling back to + $HOME/.cache/openrdap). --bs-url=URL Bootstrap service URL (default: https://data.iana.org/rdap) --bs-ttl=SECS Bootstrap cache time in seconds (default: 3600) diff --git a/rdap.1 b/rdap.1 index 6b8e14f..75fec8c 100644 --- a/rdap.1 +++ b/rdap.1 @@ -73,7 +73,7 @@ Specify RDAP query type (e.g., domain, ip, autnum, nameserver, etc.). .SH BOOTSTRAPPING OPTIONS .TP \fB--cache-dir\fR=\fIDIR\fR -Specify bootstrap cache directory (default: $HOME/.openrdap). +Specify bootstrap cache directory (default: $XDG_CACHE_HOME/openrdap, falling back to $HOME/.cache/openrdap). .TP \fB--bs-url\fR=\fIURL\fR Set bootstrap service URL (default: https://data.iana.org/rdap).