Skip to content

Commit 28e249b

Browse files
authored
feat(Collections): Remove items / delete collection (#46)
1 parent 0720b96 commit 28e249b

File tree

3 files changed

+139
-1
lines changed

3 files changed

+139
-1
lines changed

cmd/collection.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package cmd
33
import (
44
"fmt"
55
"io"
6+
"net"
7+
"regexp"
68
"strings"
79

810
"github.com/VirusTotal/vt-go"
@@ -12,6 +14,12 @@ import (
1214
"github.com/spf13/viper"
1315
)
1416

17+
type objectDescriptor struct {
18+
Type string `json:"type"`
19+
Id string `json:"id,omitempty"`
20+
Url string `json:"url,omitempty"`
21+
}
22+
1523
var collectionCmdHelp = `Get information about one or more collections.
1624
1725
This command receives one or more collection IDs and returns information about
@@ -49,6 +57,8 @@ func NewCollectionCmd() *cobra.Command {
4957
cmd.AddCommand(NewCollectionCreateCmd())
5058
cmd.AddCommand(NewCollectionRenameCmd())
5159
cmd.AddCommand(NewCollectionUpdateCmd())
60+
cmd.AddCommand(NewCollectionDeleteCmd())
61+
cmd.AddCommand(NewCollectionRemoveItemsCmd())
5262

5363
addRelationshipCmds(cmd, "collections", "collection", "[collection]")
5464
addThreadsFlag(cmd.Flags())
@@ -200,6 +210,82 @@ func NewCollectionUpdateCmd() *cobra.Command {
200210
}
201211
}
202212

213+
var removeCollectionItemsCmdHelp = `Remove items from a collection.
214+
215+
This command receives a collection ID and one of more IoCs
216+
(sha256 hashes, URLs, domains, IP addresses) and removes them from the collection.
217+
218+
If the command receives a single hypen (-) the IoCs will be read from the
219+
standard input.`
220+
221+
var removeCollectionItemsExample = ` vt collection remove [collection id] www.example.com
222+
vt collection remove [collection id] www.example.com 8.8.8.8
223+
cat list_of_iocs | vt collection remove [collection id] -`
224+
225+
// NewCollectionRemoveItemsCmd returns a command for removing items from a collection.
226+
func NewCollectionRemoveItemsCmd() *cobra.Command {
227+
return &cobra.Command{
228+
Use: "remove [collection id] [ioc]...",
229+
Short: "Remove items from a collection.",
230+
Args: cobra.MinimumNArgs(2),
231+
Long: removeCollectionItemsCmdHelp,
232+
Example: removeCollectionItemsExample,
233+
234+
RunE: func(cmd *cobra.Command, args []string) error {
235+
c, err := NewAPIClient()
236+
if err != nil {
237+
return err
238+
}
239+
relationshipDescriptors := descriptorsFromReader(
240+
utils.StringReaderFromCmdArgs(args[1:]))
241+
for relationshipName, descriptors := range relationshipDescriptors {
242+
url := vt.URL("collections/%s/%s", args[0], relationshipName)
243+
response, err := c.DeleteData(url, descriptors)
244+
if err != nil {
245+
return err
246+
}
247+
if response.Error.Code != "" {
248+
return response.Error
249+
}
250+
}
251+
return nil
252+
},
253+
}
254+
}
255+
256+
var deleteCollectionCmdHelp = `Delete a collection.
257+
258+
This command receives a collection ID and deletes it.`
259+
260+
var deleteCollectionExample = ` vt collection delete [collection id]`
261+
262+
// NewCollectionDeleteCmd returns a command for deleting a collection.
263+
func NewCollectionDeleteCmd() *cobra.Command {
264+
return &cobra.Command{
265+
Use: "delete [collection id]",
266+
Short: "Delete a collection.",
267+
Args: cobra.MinimumNArgs(1),
268+
Long: deleteCollectionCmdHelp,
269+
Example: deleteCollectionExample,
270+
271+
RunE: func(cmd *cobra.Command, args []string) error {
272+
c, err := NewAPIClient()
273+
if err != nil {
274+
return err
275+
}
276+
url := vt.URL("collections/%s", args[0])
277+
response, err := c.Delete(url)
278+
if err != nil {
279+
return err
280+
}
281+
if response.Error.Code != "" {
282+
return response.Error
283+
}
284+
return nil
285+
},
286+
}
287+
}
288+
203289
func rawFromReader(reader utils.StringReader) string {
204290
var lines []string
205291
for {
@@ -211,3 +297,51 @@ func rawFromReader(reader utils.StringReader) string {
211297
}
212298
return strings.Join(lines, " ")
213299
}
300+
301+
func descriptorsFromReader(reader utils.StringReader) map[string][]objectDescriptor {
302+
descriptors := make(map[string][]objectDescriptor)
303+
hashPattern := regexp.MustCompile("[0-9a-fA-F]{32,64}")
304+
urlPattern := regexp.MustCompile("[hH][tTxX]{2}[pP][sS]?://.*")
305+
// At least two domain parts.
306+
domainPattern := regexp.MustCompile(".*[^.]+\\.[^.]+.*")
307+
for {
308+
next, err := reader.ReadString()
309+
if err == io.EOF {
310+
break
311+
}
312+
if match := hashPattern.MatchString(next); match {
313+
files := descriptors["files"]
314+
files = append(files, objectDescriptor{
315+
Type: "file",
316+
Id: next,
317+
})
318+
descriptors["files"] = files
319+
} else if net.ParseIP(next) != nil {
320+
if strings.Contains(next, ".") {
321+
ipAddresses := descriptors["ip_addresses"]
322+
ipAddresses = append(ipAddresses, objectDescriptor{
323+
Type: "ip_address",
324+
Id: next,
325+
})
326+
descriptors["ip_addresses"] = ipAddresses
327+
} else {
328+
// IPv6, skip.
329+
}
330+
} else if urlPattern.MatchString(next) {
331+
urls := descriptors["urls"]
332+
urls = append(urls, objectDescriptor{
333+
Type: "url",
334+
Url: next,
335+
})
336+
descriptors["urls"] = urls
337+
} else if domainPattern.MatchString(next) {
338+
domains := descriptors["domains"]
339+
domains = append(domains, objectDescriptor{
340+
Type: "domain",
341+
Id: next,
342+
})
343+
descriptors["domains"] = domains
344+
}
345+
}
346+
return descriptors
347+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/VirusTotal/vt-cli
33
go 1.14
44

55
require (
6-
github.com/VirusTotal/vt-go v0.0.0-20211209151516-855a1e790678
6+
github.com/VirusTotal/vt-go v0.0.0-20220413144842-e010bf48aaee
77
github.com/briandowns/spinner v1.7.0
88
github.com/cavaliercoder/grab v2.0.0+incompatible
99
github.com/dustin/go-humanize v1.0.0

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ github.com/VirusTotal/vt-go v0.0.0-20211116094520-07a92e6467b7 h1:1oIDITWcezvHQX
2121
github.com/VirusTotal/vt-go v0.0.0-20211116094520-07a92e6467b7/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE=
2222
github.com/VirusTotal/vt-go v0.0.0-20211209151516-855a1e790678 h1:IVvLDz0INo1rn7wG4OQub9MkyNNaBp0sQgU1apho/mk=
2323
github.com/VirusTotal/vt-go v0.0.0-20211209151516-855a1e790678/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE=
24+
github.com/VirusTotal/vt-go v0.0.0-20220413141716-fce0077b709b h1:YdiHY6VO3InYpfasvfpF6EolaNqKNv6pmGJfBQCVuL4=
25+
github.com/VirusTotal/vt-go v0.0.0-20220413141716-fce0077b709b/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE=
26+
github.com/VirusTotal/vt-go v0.0.0-20220413144842-e010bf48aaee h1:JDhi0dS8y9QLMJZA7ezLyXHxYaMlyzX6MDkq0SSc304=
27+
github.com/VirusTotal/vt-go v0.0.0-20220413144842-e010bf48aaee/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE=
2428
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
2529
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
2630
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=

0 commit comments

Comments
 (0)