ObfuscateScript

From GnuCash
Jump to: navigation, search

Sometimes users hit a bug in GnuCash that a developer can't reproduce. This bug may be triggered by something in the user's data file, but if the developer doesn't have access to the user's data file it can be very hard to figure out what the problem is exactly.

Of course, a user's data file contains private information that he probably likes to keep private.

The solution is to obfuscate the data in your data file. For the GnuCash xml data files, you can use the script by Adam Buchbinder below for that purpose.

Warning 
Only use this script on a copy of your data !
#!/usr/bin/perl

# gnucash-obfuscate.pl
# Adam Buchbinder

use strict;
use warnings;

use Digest::MD5 qw(md5_hex);
use XML::DOM;

my $dictionary = '/usr/share/dict/american-english';
my $hex_digits = 4;

unless (-f $dictionary) {
	print STDERR "Default dictionary $dictionary not found.\n";
	$dictionary = '/usr/share/dict/words';
	unless (-f $dictionary) {
		die("Cannot open dictionary $dictionary");
	}
}

$ARGV[0] || die("Usage: $0 filename");
(-f $ARGV[0]) || die("Unable to open $ARGV[0]");
(-f $dictionary) || die("Dictionary file $dictionary does not exist");
open DICT, $dictionary || die("Unable to open $dictionary");

my @dict = <DICT>;
close DICT;

sub get_a_name {
	my $scalar = shift;
	return '' if !$scalar;
	my $index = hex substr(md5_hex($scalar),-$hex_digits);
	my $result = $dict[$index];
	chop $result;
	return $result;
}

sub replace_text_element {
	my $doc = shift;
	my $tag = shift;
	my $elements = $doc->getElementsByTagName($tag);

	my $i; my $n = $elements->getLength;
	for ($i=0; $i<$n; $i++) {
		my $text = $elements->item($i)->getFirstChild;
		$text->setData(get_a_name($text->getData));
	}
}

my $parser = new XML::DOM::Parser;
print STDERR "Parsing XML document.\n";
my $doc = $parser->parsefile($ARGV[0]);

# first, clean up strings in account names and transaction names
print STDERR "Obfuscating account names.\n";
replace_text_element($doc,'act:name');
print STDERR "Obfuscating account descriptions.\n";
replace_text_element($doc,'act:description');
print STDERR "Obfuscating transaction descriptions.\n";
replace_text_element($doc,'trn:description');
print STDERR "Obfuscating check numbers.\n";
replace_text_element($doc,'trn:num');
print STDERR "Obfuscating split memos.\n";
replace_text_element($doc,'split:memo');
print STDERR "Obfuscating transaction notes.\n";
my $transactions = $doc->getElementsByTagName('trn:slots');
for (my $i=0; $i<$transactions->getLength; $i++) {
	replace_text_element($transactions->item($i),'slot:value');
}

sub get_child_value {
	my $parent = shift;
	my $childname = shift;
	my $index = shift;
	
	my $return;
	my $elements = $parent->getElementsByTagName($childname);
	my $child;

	unless (defined($index)) {
		$child = $elements->item(0)->getFirstChild;
	} else {
		$child = $elements->item($index)->getFirstChild;
	}
	$return = $child->getData;
	return $return;
}

sub set_child_value {
	my $parent = shift;
	my $childname = shift;
	my $value = shift;
	
	my $return;
	my $elements = $parent->getElementsByTagName($childname);
	my $child;

	$child = $elements->item(0)->getFirstChild;

	$return = $child->setData($value);
	return $return;
}

# replace transaction values
print STDERR "Obfuscating money amounts.\n";
my $elements = $doc->getElementsByTagName('trn:splits');
my $i; my $n = $elements->getLength;
for ($i=0; $i<$n; $i++) {
	my $parent = $elements->item($i);
	my $splits = $parent->getElementsByTagName('trn:split');
	my $count = $splits->getLength;

	foreach my $j (0..$count-1) {
		my $split = $splits->item($j);
		my $valuestring;
		my $sum = 0;
		my ($num,$denom);
		if (get_child_value($split,'split:value') eq
				get_child_value($split,'split:quantity')) {
			$valuestring = get_child_value($split,'split:value');
			($num,$denom) = $valuestring =~ /(\-?\d+)\/(\d+)/;
			$num = int rand 10*$denom; $sum += $num;
			set_child_value($split,'split:value',"$num/$denom");
			set_child_value($split,'split:quantity',"$num/$denom");
		} else {
			$valuestring = get_child_value($split,'split:value');
			($num,$denom) = $valuestring =~ /(\-?\d+)\/(\d+)/;
			$num = int rand 10*$denom; $sum = $num;
			set_child_value($split,'split:value',"$num/$denom");

			$valuestring = get_child_value($split,'split:quantity');
			($num,$denom) = $valuestring =~ /(\-?\d+)\/(\d+)/;
			$num = int rand 10*$denom;
			set_child_value($split,'split:quantity',"$num/$denom");
		}
		# if we're on the last split, balance split:value's to 0
		if ($j == $count-1) {
			$valuestring = get_child_value($split,'split:value');
			($num,$denom) = $valuestring =~ /(\d+)\/(\d+)/;
			$num = $sum;
			set_child_value($split,'split:value',"$num/$denom");
		}
	}
}

print STDERR "Writing output.\n";
print $doc->toString;

$doc->dispose;