#!/usr/bin/env perl
# Tool to edit Evolution's camel-cert.db file.
# See https://bugzilla.gnome.org/show_bug.cgi?id=600445.
# ~ Matt McCutchen <matt@mattmccutchen.net>

sub usage() {
	die 'usage: camel-cert-tool {unpack|pack|edit} [DB-PATH]';
}

use warnings;
use strict;

use File::Temp qw(tempfile);
use Fcntl qw(SEEK_SET);

sub readUint32(*) {
	my ($fh) = @_;
	my $val = 0;
	my $b;
	do {
		read($fh, $b, 1) == 1 or die;
		$b = ord($b);
		$val = ($val << 7) + ($b & 0x7F);
	} until ($b & 0x80);
	return $val;
}
sub writeUint32(*$) {
	my ($fh, $val) = @_;
	my @bytes = ();
	do {
		unshift @bytes, $val & 0x7F;
		$val >>= 7;
	} until ($val == 0);
	$bytes[$#bytes] |= 0x80;
	print $fh map({chr($_)} @bytes);
}
sub readString(*) {
	my ($fh) = @_;
	my $lenp1 = readUint32($fh);
	my $len = $lenp1 - 1;
	$len >= 0 && $len <= 65536 or die;
	read($fh, my $str, $len) == $len or die;
	return $str;
}
sub writeString(*$) {
	my ($fh, $str) = @_;
	$str = substr($str, 0, 65536); # hmm, but evo does it
	writeUint32($fh, length($str) + 1);
	print $fh $str;
}

our $DEFAULT_DB = "$ENV{HOME}/.local/share/evolution/camel-cert.db";

scalar(@ARGV) == 1 or scalar(@ARGV) == 2 or usage();

my $dbPath = $ARGV[1] // $DEFAULT_DB;

sub doUnpack(*) {
	my ($plainfh) = @_;
	open(my $db, '<', $dbPath) or die $!;
	my $version = readUint32($db);
	my $numCerts = readUint32($db);
	print $plainfh join("\t", 'issuer', 'subject', 'hostname', 'fingerprint', 'trust'), "\n";
	for (my $i = 0; $i < $numCerts; $i++) {
		print $plainfh join("\t", readString($db), readString($db), readString($db), readString($db), readUint32($db)), "\n";
	}
	close($db);
}

sub doPack(*) {
	my ($plainfh) = @_;
	open(my $db, '>', $dbPath) or die $!;
	<$plainfh>;  # headers
	my @lines = <$plainfh>;
	writeUint32($db, 0x100);  # version
	writeUint32($db, scalar(@lines));
	foreach my $line (@lines) {
		chomp($line);
		my ($issuer, $subject, $hostname, $fingerprint, $trust) = split(/\t/, $line);
		writeString($db, $issuer);
		writeString($db, $subject);
		writeString($db, $hostname);
		writeString($db, $fingerprint);
		writeUint32($db, 0 + $trust);
	}
	close($db);
}

if ($ARGV[0] eq 'unpack') {
	doUnpack(*STDOUT);
} elsif ($ARGV[0] eq 'pack') {
	doPack(*STDIN);
} elsif ($ARGV[0] eq 'edit') {
	# XXX: tsv is more correct, but LibreOffice only registers itself for csv.
	my ($tempfh, $tempname) = tempfile('camel-cert-XXXXXX', SUFFIX => '.csv', TMPDIR => 1);
	doUnpack($tempfh);
	# XXX: How to know whether to use xdg-open or $EDITOR?
	system('xdg-open', $tempname) == 0 or die 'editor failure';
	seek($tempfh, 0, SEEK_SET);
	doPack($tempfh);
} else {
	usage();
}

