From 9137590e7a6e29f9d3d512bc8e64e38a850fbef6 Mon Sep 17 00:00:00 2001
From: Lukas Fleischer <lfleischer@calcurse.org>
Date: Thu, 7 Sep 2017 20:17:20 +0200
Subject: Do not blindly overwrite files when saving

When reading the data files, compute a cryptographic hash of the file
contents and store it. When saving the files later, ensure that the hash
still matches the current file contents. If it does not, show a warning
to the user and ask whether she wants to execute the merge tool.

Signed-off-by: Lukas Fleischer <lfleischer@calcurse.org>
---
 src/io.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 59 insertions(+)

diff --git a/src/io.c b/src/io.c
index 1233fbe..51205f7 100644
--- a/src/io.c
+++ b/src/io.c
@@ -85,6 +85,8 @@ HTABLE_PROTOTYPE(ht_keybindings, ht_keybindings_s)
 		load_keys_ht_compare)
 
 static int modified = 0;
+static char apts_sha1[SHA1_DIGESTLEN * 2 + 1];
+static char todo_sha1[SHA1_DIGESTLEN * 2 + 1];
 
 /* Draw a progress bar while saving, loading or exporting data. */
 static void progress_bar(progress_bar_t type, int progress)
@@ -467,6 +469,37 @@ static void io_merge_data(void)
 	run_hook("post-save");
 }
 
+static int resolve_save_conflict(void)
+{
+	char *msg_um_asktype = NULL;
+	const char *msg_um_prefix =
+			_("Data files were changed since reading:");
+	const char *msg_um_overwrite = _("(o)verwrite");
+	const char *msg_um_merge = _("(m)erge");
+	const char *msg_um_keep = _("(k)eep and cancel");
+	const char *msg_um_choice = _("[omk]");
+	int ret = 1;
+
+	asprintf(&msg_um_asktype, "%s %s, %s, %s", msg_um_prefix,
+		 msg_um_overwrite, msg_um_merge, msg_um_keep);
+
+	switch (status_ask_choice(msg_um_asktype, msg_um_choice, 3)) {
+	case 1:
+		ret = 0;
+		break;
+	case 2:
+		io_merge_data();
+		break;
+	case 3:
+		/* FALLTHROUGH */
+	default:
+		wins_update(FLAG_STA);
+	}
+
+	mem_free(msg_um_asktype);
+	return ret;
+}
+
 /* Save the calendar data */
 void io_save_cal(enum save_display display)
 {
@@ -475,10 +508,30 @@ void io_save_cal(enum save_display display)
 	    _("The data files were successfully saved");
 	const char *enter = _("Press [ENTER] to continue");
 	int show_bar;
+	FILE *fp;
+	char sha1_new[SHA1_DIGESTLEN * 2 + 1];
+	int conflict = 0;
 
 	if (read_only)
 		return;
 
+	if ((fp = fopen(path_apts, "r"))) {
+		sha1_stream(fp, sha1_new);
+		fclose(fp);
+		if (strncmp(sha1_new, apts_sha1, SHA1_DIGESTLEN * 2) != 0)
+			conflict = 1;
+	}
+
+	if (!conflict && (fp = fopen(path_todo, "r"))) {
+		sha1_stream(fp, sha1_new);
+		fclose(fp);
+		if (strncmp(sha1_new, todo_sha1, SHA1_DIGESTLEN * 2) != 0)
+			conflict = 1;
+	}
+
+	if (conflict && resolve_save_conflict())
+		return;
+
 	run_hook("pre-save");
 	pthread_mutex_lock(&io_save_mutex);
 
@@ -551,6 +604,9 @@ void io_load_app(struct item_filter *filter)
 	data_file = fopen(path_apts, "r");
 	EXIT_IF(data_file == NULL, _("failed to open appointment file"));
 
+	sha1_stream(data_file, apts_sha1);
+	rewind(data_file);
+
 	for (;;) {
 		LLIST_INIT(&exc);
 		is_appointment = is_event = is_recursive = 0;
@@ -723,6 +779,9 @@ void io_load_todo(struct item_filter *filter)
 	data_file = fopen(path_todo, "r");
 	EXIT_IF(data_file == NULL, _("failed to open todo file"));
 
+	sha1_stream(data_file, todo_sha1);
+	rewind(data_file);
+
 	for (;;) {
 		line++;
 		c = getc(data_file);
-- 
cgit v1.2.3-70-g09d2