From 6c021295947c520832bd24356c49c6342a57269e Mon Sep 17 00:00:00 2001 From: Zephyr Lykos Date: Fri, 10 Jan 2025 20:11:46 +0800 Subject: [PATCH 1/2] feat: follow XDG Base Directory Specification --- cmd/init.go | 17 ++++++++++++----- cmd/privileges.go | 21 ++++++++++----------- cmd/relationship.go | 10 +++++++--- cmd/url.go | 3 +-- go.mod | 3 +-- go.sum | 6 ++---- utils/filter.go | 21 ++++++++++----------- utils/filter_test.go | 6 ++---- utils/pqueue_test.go | 2 +- vt/main.go | 17 +++++++---------- yaml/yaml.go | 2 +- yaml/yaml_test.go | 10 +++++----- 12 files changed, 59 insertions(+), 59 deletions(-) diff --git a/cmd/init.go b/cmd/init.go index a1024d3..14b440a 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -20,7 +20,6 @@ import ( "path" vt "github.com/VirusTotal/vt-go" - "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" ) @@ -51,7 +50,7 @@ func NewInitCmd() *cobra.Command { Run: func(cmd *cobra.Command, args []string) { - fmt.Printf(vtBanner) + fmt.Print(vtBanner) apiKey := cmd.Flags().Lookup("apikey").Value.String() @@ -68,13 +67,15 @@ func NewInitCmd() *cobra.Command { os.Exit(1) } - dir, err := homedir.Dir() + cacheDir, err := os.UserCacheDir() if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } - relCacheFile, err := os.Create(path.Join(dir, ".vt.relationships.cache")) + relCache := path.Join(cacheDir, "vt-cli", "relationships") + + relCacheFile, err := os.Create(relCache) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -88,7 +89,13 @@ func NewInitCmd() *cobra.Command { os.Exit(1) } - configFilePath := path.Join(dir, ".vt.toml") + configDir, err := os.UserConfigDir() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + configFilePath := path.Join(configDir, "vt-cli", "vt.toml") configFile, err := os.Create(configFilePath) if err != nil { fmt.Fprintln(os.Stderr, err) diff --git a/cmd/privileges.go b/cmd/privileges.go index 03287a6..5df7000 100644 --- a/cmd/privileges.go +++ b/cmd/privileges.go @@ -11,7 +11,7 @@ import ( func NewPrivilegeCmd(target string) *cobra.Command { cmd := &cobra.Command{ - Use: "privileges", + Use: "privileges", Short: fmt.Sprintf("Change %s privileges", target), } @@ -21,9 +21,8 @@ func NewPrivilegeCmd(target string) *cobra.Command { return cmd } - type Privilege struct { - Granted bool `json:"granted"` + Granted bool `json:"granted"` ExpirationDate int64 `json:"expiration_date"` } @@ -31,13 +30,13 @@ type Privileges map[string]Privilege func NewPrivilegeGrantCmd(target string) *cobra.Command { cmd := &cobra.Command{ - Use: fmt.Sprintf("grant [%sname] [privilege]...", target), - Short: fmt.Sprintf("Grant privileges to a %s", target), + Use: fmt.Sprintf("grant [%sname] [privilege]...", target), + Short: fmt.Sprintf("Grant privileges to a %s", target), Example: fmt.Sprintf(" vt %s privileges grant my%s intelligence downloads-tier-2", target, target), - Args: cobra.MinimumNArgs(2), + Args: cobra.MinimumNArgs(2), RunE: func(cmd *cobra.Command, args []string) error { var expirationDate int64 - if expiration:= viper.GetString("expiration"); expiration != "" { + if expiration := viper.GetString("expiration"); expiration != "" { var err error expirationDate, err = strconv.ParseInt(expiration, 10, 64) if err != nil { @@ -68,7 +67,7 @@ func NewPrivilegeGrantCmd(target string) *cobra.Command { }, } - cmd.Flags().StringP("" + + cmd.Flags().StringP(""+ "expiration", "e", "", "expiration time for the granted privileges (UNIX timestamp or YYYY-MM-DD)") return cmd @@ -76,10 +75,10 @@ func NewPrivilegeGrantCmd(target string) *cobra.Command { func NewPrivilegeRevokeCmd(target string) *cobra.Command { return &cobra.Command{ - Use: fmt.Sprintf("revoke [%sname] [privilege]...", target), - Short: fmt.Sprintf("Revoke privileges from a %s", target), + Use: fmt.Sprintf("revoke [%sname] [privilege]...", target), + Short: fmt.Sprintf("Revoke privileges from a %s", target), Example: fmt.Sprintf(" vt %s privileges revoke my%s intelligence downloads-tier-2", target, target), - Args: cobra.MinimumNArgs(2), + Args: cobra.MinimumNArgs(2), RunE: func(cmd *cobra.Command, args []string) error { privileges := Privileges{} for _, arg := range args[1:] { diff --git a/cmd/relationship.go b/cmd/relationship.go index f36b7eb..7dde596 100644 --- a/cmd/relationship.go +++ b/cmd/relationship.go @@ -23,7 +23,6 @@ import ( "sync" vt "github.com/VirusTotal/vt-go" - homedir "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -31,8 +30,13 @@ import ( var objectRelationshipsMap map[string][]vt.RelationshipMeta func init() { - home, _ := homedir.Dir() - f, err := os.Open(path.Join(home, ".vt.relationships.cache")) + cacheDir, err := os.UserCacheDir() + if err != nil { + return + } + + cachePath := path.Join(cacheDir, "vt-cli", "relationships") + f, err := os.Open(cachePath) if err == nil { defer f.Close() dec := gob.NewDecoder(f) diff --git a/cmd/url.go b/cmd/url.go index eacc4c8..ab34c4b 100644 --- a/cmd/url.go +++ b/cmd/url.go @@ -35,7 +35,6 @@ var urlCmdExample = ` vt url https://www.virustotal.com vt url f1177df4692356280844e1d5af67cc4a9eccecf77aa61c229d483b7082c70a8e cat list_of_urls | vt url -` - // Regular expressions used for validating a URL identifier. var urlID = regexp.MustCompile(`[0-9a-fA-F]{64}`) @@ -55,7 +54,7 @@ func NewURLCmd() *cobra.Command { } r := utils.NewMappedStringReader( utils.StringReaderFromCmdArgs(args), - func (url string) string { + func(url string) string { if urlID.MatchString(url) { // The user provided a URL identifier as returned by // VirusTotal's API, which consists in the URL's SHA-256. diff --git a/go.mod b/go.mod index bd19f96..910f345 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,6 @@ require ( github.com/gobwas/glob v0.2.3 github.com/gosuri/uitable v0.0.4 github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 - github.com/mitchellh/go-homedir v1.1.0 github.com/plusvic/go-ansi v0.0.0-20180516115420-9879244c4340 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 @@ -45,7 +44,7 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect golang.org/x/term v0.1.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 76f6527..a68a459 100644 --- a/go.sum +++ b/go.sum @@ -43,8 +43,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -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/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= @@ -101,8 +99,8 @@ golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= diff --git a/utils/filter.go b/utils/filter.go index 7526333..55c73cc 100644 --- a/utils/filter.go +++ b/utils/filter.go @@ -25,19 +25,18 @@ import ( // excluding keys matching any of the patterns in "exclude". The logic for // determining if a key matches the pattern goes as follow: // -// * The path for the key is computed. If the key is in the top-level map its -// path is the key itself, if the key is contained within a nested map its -// path is the concatenation of the parent's path and the key, using a dot (.) -// as a separator. The path for "key" in {a:{b:{key:val}}} is a.b.key. +// - The path for the key is computed. If the key is in the top-level map its +// path is the key itself, if the key is contained within a nested map its +// path is the concatenation of the parent's path and the key, using a dot (.) +// as a separator. The path for "key" in {a:{b:{key:val}}} is a.b.key. // -// * The path is matched against the pattern, which can contain asterisks (*) -// as a placeholder for any character different from a dot (.) and ** as a -// placeholder for any character including a dot. For more information go to: -// https://godoc.org/github.com/gobwas/glob#Compile -// -// * If the path matches any pattern in "include" the key is included in the -// resulting map, as long as it doesn't match a pattern in "exclude". +// - The path is matched against the pattern, which can contain asterisks (*) +// as a placeholder for any character different from a dot (.) and ** as a +// placeholder for any character including a dot. For more information go to: +// https://godoc.org/github.com/gobwas/glob#Compile // +// - If the path matches any pattern in "include" the key is included in the +// resulting map, as long as it doesn't match a pattern in "exclude". func FilterMap(m map[string]interface{}, include, exclude []string) map[string]interface{} { includeGlob := make([]glob.Glob, len(include)) excludeGlob := make([]glob.Glob, len(exclude)) diff --git a/utils/filter_test.go b/utils/filter_test.go index 1cfdb35..0235fc4 100644 --- a/utils/filter_test.go +++ b/utils/filter_test.go @@ -3,7 +3,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -77,9 +77,7 @@ var testCases = []testCase{ include: []string{"foo"}, exclude: []string{"**.quux"}, input: testMap, - output: map[string]interface{}{ - - }, + output: map[string]interface{}{}, }, testCase{ diff --git a/utils/pqueue_test.go b/utils/pqueue_test.go index 2f1b6ed..57e7514 100644 --- a/utils/pqueue_test.go +++ b/utils/pqueue_test.go @@ -3,7 +3,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, diff --git a/vt/main.go b/vt/main.go index 89e6a44..5c2e677 100644 --- a/vt/main.go +++ b/vt/main.go @@ -14,11 +14,10 @@ package main import ( - "fmt" "os" + "path" "github.com/VirusTotal/vt-cli/cmd" - homedir "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -26,18 +25,16 @@ import ( // initConfig reads in config file and ENV variables if set. func initConfig() { - // Find home directory. - home, err := homedir.Dir() - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) + // Find config directory. + configDir, err := os.UserConfigDir() + if err == nil { + viper.AddConfigPath(path.Join(configDir, "vt-cli")) } // Search config in home directory and current directory - viper.AddConfigPath(home) viper.AddConfigPath(".") - // Config file must be named .vt + format extension (.toml, .json, etc) - viper.SetConfigName(".vt") + // Config file must be named vt + format extension (.toml, .json, etc) + viper.SetConfigName("vt") // The prefix for all environment variables will be VTCLI_. Examples: // VTCLI_PROXY, VTCLI_APIKEY. diff --git a/yaml/yaml.go b/yaml/yaml.go index 9de45ef..04b5f95 100644 --- a/yaml/yaml.go +++ b/yaml/yaml.go @@ -145,7 +145,7 @@ func (enc *Encoder) encodeMap(m reflect.Value, indent int, prefix string) (err e // If the key is an empty string or starts with some non-letter character // let's enclose the key in double quotes. firstChar, kLen := utf8.DecodeRuneInString(k.String()) - if kLen == 0 || (firstChar != '_' && !unicode.IsLetter(firstChar)) { + if kLen == 0 || (firstChar != '_' && !unicode.IsLetter(firstChar)) { keyPrinter(enc.w, "\"%s\": ", k) } else { keyPrinter(enc.w, "%s: ", k) diff --git a/yaml/yaml_test.go b/yaml/yaml_test.go index 8fc190f..cb5a92f 100644 --- a/yaml/yaml_test.go +++ b/yaml/yaml_test.go @@ -3,7 +3,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -98,7 +98,7 @@ var tests = []T{ "uno": "1", "dos": "2", "tres": "3", - "": "", + "": "", "#foo": "foo", "|foo": "foo", "_foo": "foo", @@ -180,7 +180,7 @@ var tests = []T{ }, yaml: Y(fmt.Sprintf(` Foo_date: 10000 # %v - `, time.Unix(10000, 0))), + `, time.Unix(10000, 0))), }, { data: struct { @@ -190,7 +190,7 @@ var tests = []T{ }, yaml: Y(fmt.Sprintf(` Bar_date: 10000 # %v - `, time.Unix(10000, 0))), + `, time.Unix(10000, 0))), }, { data: struct { @@ -200,7 +200,7 @@ var tests = []T{ }, yaml: Y(fmt.Sprintf(` Baz_date: 1.618312811e+09 # %v - `, time.Unix(1618312811, 0))), + `, time.Unix(1618312811, 0))), }, } From 822e7225dded8ee840cc458a7ab6be49f25c943a Mon Sep 17 00:00:00 2001 From: Zephyr Lykos Date: Wed, 15 Jan 2025 20:42:35 +0800 Subject: [PATCH 2/2] Migrate old ~/.vt.* configuration files to UserConfigDir --- vt/main.go | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/vt/main.go b/vt/main.go index 5c2e677..da3eba0 100644 --- a/vt/main.go +++ b/vt/main.go @@ -14,6 +14,7 @@ package main import ( + "fmt" "os" "path" @@ -22,16 +23,45 @@ import ( "github.com/spf13/viper" ) +// Migrate old ~/.vt.* configuration files. +func migrateConfig(configDir string) { + home, err := os.UserHomeDir() + if err != nil { + return + } + + for _, ext := range viper.SupportedExts { + oldPath := path.Join(home, ".vt."+ext) + + f, err := os.Open(oldPath) + if f != nil { + f.Close() + } + if err != nil { + continue + } + + newPath := path.Join(configDir, path.Base(oldPath)) + err = os.Rename(oldPath, newPath) + if err != nil { + fmt.Printf("Migrated %s to %s\n", oldPath, newPath) + } else { + fmt.Printf("Failed to migrate %s to %s: %v\n", oldPath, newPath, err) + } + } +} + // initConfig reads in config file and ENV variables if set. func initConfig() { - // Find config directory. configDir, err := os.UserConfigDir() if err == nil { - viper.AddConfigPath(path.Join(configDir, "vt-cli")) - } + configDir = path.Join(configDir, "vt-cli") + migrateConfig(configDir) - // Search config in home directory and current directory + viper.AddConfigPath(configDir) + } + // Search config in current directory viper.AddConfigPath(".") // Config file must be named vt + format extension (.toml, .json, etc) viper.SetConfigName("vt")