diff options
author | John Wiegley <johnw@newartisans.com> | 2023-11-22 16:47:21 -0800 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2024-08-05 08:35:56 -0700 |
commit | db0661dbb51e9082e47926c31e93bdc97b491bf9 (patch) | |
tree | e60f7dc32ebe9c24cb26eeb8c8438de7e891fe6b /src/textual.cc | |
parent | e6dae78c033ea970a459b1a0ccc2f1310d1bff96 (diff) | |
download | fork-ledger-db0661dbb51e9082e47926c31e93bdc97b491bf9.tar.gz fork-ledger-db0661dbb51e9082e47926c31e93bdc97b491bf9.tar.bz2 fork-ledger-db0661dbb51e9082e47926c31e93bdc97b491bf9.zip |
Add support for hash chaining to detect modifications in postings
The following details of a posting contribute to its hash:
fullname of account
string representation of amount
Each posting hashes contributes to the transaction hash, which is compromised
of:
previous transaction’s hash (as encountered in parsing order)
actual date
optional auxiliary date
optional code
payee
hashes of all postings
Note that this means that changes in the “code” or any of the comments
Diffstat (limited to 'src/textual.cc')
-rw-r--r-- | src/textual.cc | 65 |
1 files changed, 48 insertions, 17 deletions
diff --git a/src/textual.cc b/src/textual.cc index 2da123a7..9ad62602 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -79,6 +79,7 @@ namespace { instance_t * parent; std::list<application_t> apply_stack; bool no_assertions; + bool store_hashes; #if TIMELOG_SUPPORT time_log_t timelog; #endif @@ -86,10 +87,12 @@ namespace { instance_t(parse_context_stack_t& _context_stack, parse_context_t& _context, instance_t * _parent = NULL, - const bool _no_assertions = false) + const bool _no_assertions = false, + const bool _store_hashes = false) : context_stack(_context_stack), context(_context), in(*context.stream.get()), parent(_parent), - no_assertions(_no_assertions), timelog(context) {} + no_assertions(_no_assertions), store_hashes(_store_hashes), + timelog(context) {} virtual string description() { return _("textual parser"); @@ -136,7 +139,7 @@ namespace { } #endif - void read_next_directive(bool& error_flag); + xact_t * read_next_directive(bool& error_flag, xact_t * previous_xact); #if TIMELOG_SUPPORT void clock_in_directive(char * line, bool capitalized); @@ -176,7 +179,8 @@ namespace { void apply_year_directive(char * line); void end_apply_directive(char * line); - void xact_directive(char * line, std::streamsize len); + xact_t * xact_directive(char * line, std::streamsize len, + xact_t * previous_xact); void period_xact_directive(char * line); void automated_xact_directive(char * line); void price_xact_directive(char * line); @@ -207,7 +211,8 @@ namespace { xact_t * parse_xact(char * line, std::streamsize len, - account_t * account); + account_t * account, + xact_t * previous_xact); virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, const string& name); @@ -247,10 +252,13 @@ void instance_t::parse() context.curr_pos = in.tellg(); bool error_flag = false; + xact_t * previous_xact = NULL; while (in.good() && ! in.eof()) { try { - read_next_directive(error_flag); + if (xact_t * xact = read_next_directive(error_flag, previous_xact)) { + previous_xact = xact; + } } catch (const std::exception& err) { error_flag = true; @@ -348,12 +356,12 @@ std::streamsize instance_t::read_line(char *& line) return 0; } -void instance_t::read_next_directive(bool& error_flag) +xact_t * instance_t::read_next_directive(bool& error_flag, xact_t * previous_xact) { char * line; std::streamsize len = read_line(line); if (len == 0 || line == NULL) - return; + return NULL; if (! std::isspace(line[0])) error_flag = false; @@ -389,8 +397,7 @@ void instance_t::read_next_directive(bool& error_flag) case '7': case '8': case '9': - xact_directive(line, len); - break; + return xact_directive(line, len, previous_xact); case '=': // automated xact automated_xact_directive(line); break; @@ -449,6 +456,8 @@ void instance_t::read_next_directive(bool& error_flag) } break; } + + return NULL; } #if TIMELOG_SUPPORT @@ -711,16 +720,17 @@ void instance_t::period_xact_directive(char * line) } } -void instance_t::xact_directive(char * line, std::streamsize len) +xact_t * instance_t::xact_directive(char * line, std::streamsize len, + xact_t * previous_xact) { TRACE_START(xacts, 1, "Time spent handling transactions:"); - if (xact_t * xact = parse_xact(line, len, top_account())) { + if (xact_t * xact = parse_xact(line, len, top_account(), previous_xact)) { unique_ptr<xact_t> manager(xact); if (context.journal->add_xact(xact)) { - manager.release(); // it's owned by the journal now context.count++; + return manager.release(); // it's owned by the journal now } // It's perfectly valid for the journal to reject the xact, which it // will do if the xact has no substantive effect (for example, a @@ -730,6 +740,8 @@ void instance_t::xact_directive(char * line, std::streamsize len) } TRACE_STOP(xacts, 1); + + return NULL; } void instance_t::include_directive(char * line) @@ -793,7 +805,7 @@ void instance_t::include_directive(char * line) context_stack.get_current().scope = scope; try { instance_t instance(context_stack, context_stack.get_current(), - this, no_assertions); + this, no_assertions, store_hashes); instance.apply_stack.push_front(application_t("account", master)); instance.parse(); } @@ -1818,7 +1830,8 @@ bool instance_t::parse_posts(account_t * account, xact_t * instance_t::parse_xact(char * line, std::streamsize len, - account_t * account) + account_t * account, + xact_t * previous_xact) { TRACE_START(xact_text, 1, "Time spent parsing transaction text:"); @@ -2007,6 +2020,22 @@ xact_t * instance_t::parse_xact(char * line, TRACE_STOP(xact_details, 1); + if (store_hashes) { + string expected_hash = + xact->hash(previous_xact && + previous_xact->has_tag("Hash") ? + previous_xact->get_tag("Hash")->to_string() : ""); + if (xact->has_tag("Hash")) { + string current_hash = xact->get_tag("Hash")->to_string(); + if (expected_hash != current_hash) { + throw_(parse_error, _f("Expected hash %1% != %2%") % + expected_hash % current_hash); + } + } else { + xact->set_tag("Hash", string_value(expected_hash)); + } + } + return xact.release(); } @@ -2027,12 +2056,14 @@ expr_t::ptr_op_t instance_t::lookup(const symbol_t::kind_t kind, return context.scope->lookup(kind, name); } -std::size_t journal_t::read_textual(parse_context_stack_t& context_stack) +std::size_t journal_t::read_textual(parse_context_stack_t& context_stack, + bool store_hashes) { TRACE_START(parsing_total, 1, "Total time spent parsing text:"); { instance_t instance(context_stack, context_stack.get_current(), NULL, - checking_style == journal_t::CHECK_PERMISSIVE); + checking_style == journal_t::CHECK_PERMISSIVE, + store_hashes); instance.apply_stack.push_front (application_t("account", context_stack.get_current().master)); instance.parse(); |