#!/usr/bin/env perl
# CVE-Client: CLI-based client / toolbox for CVE.org
# Copyright © 2021 CVE-Client Authors <https://hacktivis.me/git/cve-client/>
# SPDX-License-Identifier: AGPL-3.0-only
use strict;
use utf8;
no warnings;    # Wide Character…

use Getopt::Std;
use LWP::UserAgent;
use JSON::MaybeXS;

$Getopt::Std::STANDARD_HELP_VERSION = 1;

my $json = JSON::MaybeXS->new(utf8 => 1);

my %options = ();

sub VERSION_MESSAGE {
	print "cve-client 0.0.1\n";
}

sub HELP_MESSAGE {
	print "usage: cve-client [-r|-j|-g] <CVE-IDs ...>\n";
	print " -r  Raw output\n";
	print " -j  Pipe into jq(1)\n";
	print " -g  Gemtext format\n";
	print "Otherwise it tries to do a plain-text representation.\n";
	exit 1;
}

sub print_cve {
	my ($object, $cve_id) = @_;

	print "CVE ID: ", $cve_id, "\n";

	if ($object->{'error'} and $object->{'message'}) {
		print "Error (", $object->{'error'}, "): ", $object->{'message'}, "\n";
		exit 1;
	}

	# https://github.com/CVEProject/cve-schema/blob/master/schema/v4.0/
	if ($object->{'data_version'} != "4.0") {
		print "Warning: Got unknown CVE format \"", $object->{'data_version'},
		  "\"\n";
	}

	if ($object->{'CVE_data_meta'}->{'ID'} != $cve_id) {
		print "Warning: Got ", $object->{'CVE_data_meta'}->{'ID'},
		  " instead of ", $cve_id, "\n";
	}

	print "TITLE: ", $object->{'CVE_data_meta'}->{'TITLE'}, "\n"
	  if $object->{'CVE_data_meta'}->{'TITLE'};

	print "\n";

	if ($object->{'affects'}->{'vendor'}) {
		foreach (@{$object->{'affects'}->{'vendor'}->{'vendor_data'}}) {
			print "Vendor Name: ", $_->{'vendor_name'}, "\n"
			  if $_->{'vendor_name'};

			foreach (@{$_->{'product'}->{'product_data'}}) {
				print "Product name: ", $_->{'product_name'}, "\n";
				print "Product versions: ";

				foreach (@{$_->{'version'}->{'version_data'}}) {
					print $_->{'version_value'}, "; ";
				}

				print "\n";
			}
		}
	}

	print "\n";

	if ($object->{'description'}->{'description_data'}) {
		my $descs = $object->{'description'}->{'description_data'};

		foreach (@{$descs}) {
			print "Description Language: ", $_->{'lang'},  "\n";
			print "Description:\n",         $_->{'value'}, "\n\n";
		}
	} else {
		print "Warning: No CVE description could be found!";
	}

	if ($object->{'references'}->{'reference_data'}) {
		my $refs = $object->{'references'}->{'reference_data'};

		foreach (@{$refs}) {
			if (defined $options{g}) {
				print "Reference Source: ", $_->{'refsource'}, "\n";

				print "=> ", $_->{'url'} if $_->{'url'};
				if ($_->{'name'}) {
					print " ", $_->{'name'}, "\n\n";
				} else {
					print "\n\n";
				}
			} else {
				print "Reference Source: ", $_->{'refsource'}, "\n";
				print "- Name: ",           $_->{'name'}, "\n" if $_->{'name'};
				print "- URL: ",            $_->{'url'},  "\n" if $_->{'url'};
				print "\n";
			}
		}
	} else {
		print "Warning: No CVE references could be found!";
	}
}

sub fetch_cve {
	my ($cve_id) = @_;
	my $url = 'https://www.cve.org/api/?action=getCveById&cveId=' . $cve_id;

	#print "Fetching: ", $url, "\n";
	my $ua = LWP::UserAgent->new;
	$ua->agent("CVE-Client fetch <https://hacktivis.me/git/cve-client/>");
	my $req = HTTP::Request->new(GET => $url);

	#$req->header('Accept' => 'application/json');

	my $res = $ua->request($req);

	if ($res->is_success) {
		my $content_type  = $res->header("Content-Type");
		my $content_match = 'application/json';

		if ($content_type == $content_match) {
			if (defined $options{r}) {
				print $res->content;
			} elsif (defined $options{j}) {
				open(my $pipe_out, '|-', 'jq .')
				  or die "Couldn't open a pipe into jq: $!";
				print $pipe_out $res->content;
			} else {
				my $object = $json->decode($res->content);
				print_cve($object, $cve_id);
			}
		} else {
			print "Got $content_type instead of $content_match";
		}
	} else {
		print "Got ", $res->status_line, " instead of 2xx\n";
	}
}

getopts("rjg", \%options);

if ($#ARGV < 0) {
	HELP_MESSAGE;
	exit 1;
}

for (@ARGV) {
	fetch_cve($_) if $_ =~ /^CVE-[0-9]{4}-[0-9]{4,19}$/;
}
