logo

cve-client

CLI-based client / toolbox for CVE.org

cve-client (2397B)


  1. #!/usr/bin/env perl
  2. # CVE-Client: CLI-based client / toolbox for CVE.org
  3. # Copyright © 2021-2023 CVE-Client Authors <https://hacktivis.me/git/cve-client/>
  4. # SPDX-License-Identifier: AGPL-3.0-only
  5. our $VERSION = 'v1.0.5';
  6. use strict;
  7. use utf8;
  8. no warnings; # Wide Character…
  9. use App::CveClient qw(print_cve);
  10. use Getopt::Std;
  11. $Getopt::Std::STANDARD_HELP_VERSION = 1;
  12. use LWP::UserAgent;
  13. use JSON::MaybeXS;
  14. =head1 NAME
  15. cve-client - CLI-based client / toolbox for CVE.org
  16. =head1 SYNOPSIS
  17. B<cve-client> [-r|-j|-g] <CVE-IDs ...>
  18. =head1 DESCRIPTION
  19. B<cve-client> fetches CVE-IDs from CVE.org API and write the result to standard output.
  20. By default it is pretty-printed into a plain-text format.
  21. =over 3
  22. =item -j
  23. Pipe into jq(1)
  24. =item -r
  25. Raw output, print server's output without any decoding
  26. =item -g
  27. Pretty-print but compatible with Gemini.
  28. =back
  29. =head1 LICENSE
  30. AGPL-3.0-only
  31. =cut
  32. my $json = JSON::MaybeXS->new(utf8 => 1);
  33. my %options = ();
  34. sub HELP_MESSAGE {
  35. print "usage: cve-client [-r|-j|-g] <CVE-IDs ...>\n";
  36. print " -r Raw output\n";
  37. print " -j Pipe into jq(1)\n";
  38. print " -g Gemtext format\n";
  39. print "Otherwise it tries to do a plain-text representation.\n";
  40. exit 1;
  41. }
  42. sub fetch_cve {
  43. my ($cve_id) = @_;
  44. my $url = 'https://www.cve.org/api/?action=getCveById&cveId=' . $cve_id;
  45. #print "Fetching: ", $url, "\n";
  46. my $ua = LWP::UserAgent->new;
  47. $ua->agent("CVE-Client fetch <https://hacktivis.me/git/cve-client/>");
  48. my $req = HTTP::Request->new(GET => $url);
  49. #$req->header('Accept' => 'application/json');
  50. my $res = $ua->request($req);
  51. my $format = $options{g} ? 'gemini' : 'plain';
  52. if ($res->is_success) {
  53. my $content_type = $res->header("Content-Type");
  54. my $content_match = 'application/json';
  55. if ($content_type == $content_match) {
  56. if (defined $options{r}) {
  57. print $res->content;
  58. } elsif (defined $options{j}) {
  59. open(my $pipe_out, '|-', 'jq .')
  60. or die "Couldn't open a pipe into jq: $!";
  61. print $pipe_out $res->content;
  62. } else {
  63. my $object = $json->decode($res->content);
  64. print_cve($object, $cve_id, $format);
  65. }
  66. } else {
  67. print "Got $content_type instead of $content_match";
  68. }
  69. } else {
  70. print "Got ", $res->status_line, " instead of 2xx\n";
  71. }
  72. }
  73. getopts("rjg", \%options);
  74. if ($#ARGV < 0) {
  75. HELP_MESSAGE;
  76. exit 1;
  77. }
  78. for (@ARGV) {
  79. fetch_cve($_) if $_ =~ /^CVE-[0-9]{4}-[0-9]{4,19}$/;
  80. }