1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2018 Colin Leroy <colin@colino.net>
4  * and the Claws Mail team
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 /* Partial download:
22  * A mail which has been completely downloaded will have no special headers,
23  * and its entry in the uidl file will end by 0 (POP3_TOTALLY_RECEIVED);
24  *
25  * A mail which has been partially downloaded will have some special headers,
26  * and its entry in the uidl file will first be 1 (POP3_PARTIALLY_RECEIVED);
27  * the special headers will be including "SC-Marked-For-Download" which can
28  * have three values:
29  * 0 (POP3_PARTIAL_DLOAD_UNKN) meaning that the user has not yet chosen to
30  *  download the mail or let it be deleted - this header is absent until the
31  *  user first chooses an action
32  * 1 (POP3_PARTIAL_DLOAD_DLOAD) meaning that the user wants to finish
33  *  downloading the mail
34  * 2 (POP3_PARTIAL_DLOAD_DELE) meaning that the user does not want to finish
35  *  downloading the mail
36  * When updating this header to POP3_PARTIAL_DLOAD_DLOAD, the uidl line of
37  * this mail will end with the mail's physical path, which Claws Mail will remove
38  * after having downloaded the complete mail. msg->partial_recv will equal
39  * 2 (POP3_MUST_COMPLETE_RECV).
40  * When updating this header to POP3_PARTIAL_DLOAD_DELE, the uidl line of
41  * this mail will be 0 (POP3_TOTALLY_RECEIVED), which will let Claws Mail delete
42  * this mail from the server as soon as the leave_time preference specifies.
43  */
44 
45 #ifdef HAVE_CONFIG_H
46 #  include "config.h"
47 #include "claws-features.h"
48 #endif
49 
50 #include <glib.h>
51 #include <glib/gi18n.h>
52 #include <stdio.h>
53 #include <string.h>
54 #include <stdarg.h>
55 #include <ctype.h>
56 #include <unistd.h>
57 #include <time.h>
58 #include <errno.h>
59 
60 #include "partial_download.h"
61 #include "utils.h"
62 #include "pop.h"
63 #include "folder.h"
64 #include "procheader.h"
65 #include "msgcache.h"
66 #include "file-utils.h"
67 
partial_msg_in_uidl_list(MsgInfo * msginfo)68 int partial_msg_in_uidl_list(MsgInfo *msginfo)
69 {
70 	gchar *path;
71 	FILE *fp;
72 	gchar buf[POPBUFSIZE];
73 	gchar uidl[POPBUFSIZE];
74 	time_t recv_time;
75 	time_t now;
76 	gchar *sanitized_uid = NULL;
77 
78 	if (!msginfo->extradata)
79 		return FALSE;
80 
81 	sanitized_uid = g_strdup(msginfo->extradata->account_login);
82 
83 	subst_for_filename(sanitized_uid);
84 
85 	if (!msginfo->extradata->account_server
86 	||  !msginfo->extradata->account_login
87 	||  !msginfo->extradata->partial_recv)
88 		return FALSE;
89 
90 	path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
91 			   "uidl", G_DIR_SEPARATOR_S, msginfo->extradata->account_server,
92 			   "-", msginfo->extradata->account_login, NULL);
93 	if ((fp = claws_fopen(path, "rb")) == NULL) {
94 		if (ENOENT != errno) FILE_OP_ERROR(path, "claws_fopen");
95 		g_free(path);
96 		path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
97 				   "uidl-", msginfo->extradata->account_server,
98 				   "-", sanitized_uid, NULL);
99 		if ((fp = claws_fopen(path, "rb")) == NULL) {
100 			if (ENOENT != errno) FILE_OP_ERROR(path, "claws_fopen");
101 			g_free(sanitized_uid);
102 			g_free(path);
103 			return FALSE;
104 		}
105 	}
106 	g_free(sanitized_uid);
107 	g_free(path);
108 
109 	now = time(NULL);
110 
111 	while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
112 		gchar tmp[POPBUFSIZE];
113 		strretchomp(buf);
114 		recv_time = RECV_TIME_NONE;
115 
116 		if (sscanf(buf, "%s\t%ld\t%s", uidl, (long int *) &recv_time,
117 			   tmp) < 2) {
118 			if (sscanf(buf, "%s", uidl) != 1)
119 				continue;
120 			else {
121 				recv_time = now;
122 			}
123 		}
124 		if (!strcmp(uidl, msginfo->extradata->partial_recv)) {
125 			claws_fclose(fp);
126 			return TRUE;
127 		}
128 	}
129 
130 	claws_fclose(fp);
131 	return FALSE;
132 }
133 
partial_uidl_mark_mail(MsgInfo * msginfo,int download)134 static int partial_uidl_mark_mail(MsgInfo *msginfo, int download)
135 {
136 	gchar *path;
137 	gchar *pathnew;
138 	FILE *fp;
139 	FILE *fpnew;
140 	gchar buf[POPBUFSIZE];
141 	gchar uidl[POPBUFSIZE];
142 	time_t recv_time;
143 	time_t now;
144 	gchar partial_recv[POPBUFSIZE];
145 	int err = -1;
146 	gchar *filename;
147 	MsgInfo *tinfo;
148 	gchar *sanitized_uid = NULL;
149 
150 	filename = procmsg_get_message_file_path(msginfo);
151 	if (!filename) {
152 		g_warning("can't get message file path.");
153 		return err;
154 	}
155 	tinfo = procheader_parse_file(filename, msginfo->flags, TRUE, TRUE);
156 
157 	if (!tinfo->extradata) {
158 		g_free(filename);
159 		return err;
160 	}
161 
162 	sanitized_uid = g_strdup(tinfo->extradata->account_login);
163 	subst_for_filename(sanitized_uid);
164 
165 	if (!tinfo->extradata->account_server
166 	||  !tinfo->extradata->account_login
167 	||  !tinfo->extradata->partial_recv) {
168 		goto bail;
169 	}
170 	path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
171 			   "uidl", G_DIR_SEPARATOR_S, tinfo->extradata->account_server,
172 			   "-", sanitized_uid, NULL);
173 
174 	if ((fp = claws_fopen(path, "rb")) == NULL) {
175 		if (ENOENT != errno) FILE_OP_ERROR(path, "claws_fopen");
176 		g_free(path);
177 		path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
178 				   "uidl-", tinfo->extradata->account_server,
179 				   "-", tinfo->extradata->account_login, NULL);
180 		if ((fp = claws_fopen(path, "rb")) == NULL) {
181 			if (ENOENT != errno) FILE_OP_ERROR(path, "claws_fopen");
182 			g_free(path);
183 			goto bail;
184 		}
185 	}
186 
187 	pathnew = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
188 			   "uidl", G_DIR_SEPARATOR_S, tinfo->extradata->account_server,
189 			   "-", sanitized_uid, ".new", NULL);
190 
191 	g_free(sanitized_uid);
192 
193 	if ((fpnew = claws_fopen(pathnew, "wb")) == NULL) {
194 		FILE_OP_ERROR(pathnew, "claws_fopen");
195 		claws_fclose(fp);
196 		g_free(pathnew);
197 		goto bail;
198 	}
199 
200 	now = time(NULL);
201 
202 	while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
203 		strretchomp(buf);
204 		recv_time = RECV_TIME_NONE;
205 		sprintf(partial_recv,"0");
206 
207 		if (sscanf(buf, "%s\t%ld\t%s",
208 			   uidl, (long int *) &recv_time, partial_recv) < 2) {
209 			if (sscanf(buf, "%s", uidl) != 1)
210 				continue;
211 			else {
212 				recv_time = now;
213 			}
214 		}
215 		if (strcmp(tinfo->extradata->partial_recv, uidl)) {
216 			if (fprintf(fpnew, "%s\t%ld\t%s\n",
217 				uidl, (long int) recv_time, partial_recv) < 0) {
218 				FILE_OP_ERROR(pathnew, "fprintf");
219 				claws_fclose(fpnew);
220 				claws_fclose(fp);
221 				g_free(path);
222 				g_free(pathnew);
223 				goto bail;
224 			}
225 		} else {
226 			gchar *stat = NULL;
227 			if (download == POP3_PARTIAL_DLOAD_DLOAD) {
228 				gchar *folder_id = folder_item_get_identifier(
229 							msginfo->folder);
230 				stat = g_strdup_printf("%s:%d",
231 					folder_id, msginfo->msgnum);
232 				g_free(folder_id);
233 			}
234 			else if (download == POP3_PARTIAL_DLOAD_UNKN)
235 				stat = g_strdup("1");
236 			else if (download == POP3_PARTIAL_DLOAD_DELE)
237 				stat = g_strdup("0");
238 
239 			if (fprintf(fpnew, "%s\t%ld\t%s\n",
240 				uidl, (long int) recv_time, stat) < 0) {
241 				FILE_OP_ERROR(pathnew, "fprintf");
242 				claws_fclose(fpnew);
243 				claws_fclose(fp);
244 				g_free(path);
245 				g_free(pathnew);
246 				goto bail;
247 			}
248 			g_free(stat);
249 		}
250 	}
251 	if (claws_safe_fclose(fpnew) == EOF) {
252 		FILE_OP_ERROR(pathnew, "claws_fclose");
253 		claws_fclose(fp);
254 		g_free(path);
255 		g_free(pathnew);
256 		goto bail;
257 	}
258 	claws_fclose(fp);
259 
260 	move_file(pathnew, path, TRUE);
261 
262 	g_free(path);
263 	g_free(pathnew);
264 
265 	if ((fp = claws_fopen(filename,"rb")) == NULL) {
266 		FILE_OP_ERROR(filename, "claws_fopen");
267 		goto bail;
268 	}
269 	pathnew = g_strdup_printf("%s.new", filename);
270 	if ((fpnew = claws_fopen(pathnew, "wb")) == NULL) {
271 		FILE_OP_ERROR(pathnew, "claws_fopen");
272 		claws_fclose(fp);
273 		g_free(pathnew);
274 		goto bail;
275 	}
276 
277 	if (fprintf(fpnew, "SC-Marked-For-Download: %d\n",
278 			download) < 0) {
279 		FILE_OP_ERROR(pathnew, "fprintf");
280 		claws_fclose(fpnew);
281 		claws_fclose(fp);
282 		g_free(pathnew);
283 		goto bail;
284 	}
285 	while (claws_fgets(buf, sizeof(buf)-1, fp) != NULL) {
286 		if(strlen(buf) > strlen("SC-Marked-For-Download: x\n")
287 		&& !strncmp(buf, "SC-Marked-For-Download:",
288 		            strlen("SC-Marked-For-Download:"))) {
289 			if (fprintf(fpnew, "%s",
290 			 buf+strlen("SC-Marked-For-Download: x\n")) < 0) {
291 				FILE_OP_ERROR(pathnew, "fprintf");
292 				claws_fclose(fpnew);
293 				claws_fclose(fp);
294 				g_free(pathnew);
295 				goto bail;
296 			}
297 			continue;
298 		} else if (strlen(buf) == strlen("SC-Marked-For-Download: x\n")
299 		&& !strncmp(buf, "SC-Marked-For-Download:",
300 		            strlen("SC-Marked-For-Download:"))) {
301 			continue;
302 		}
303 		if (fprintf(fpnew, "%s", buf) < 0) {
304 			FILE_OP_ERROR(pathnew, "fprintf");
305 			claws_fclose(fpnew);
306 			claws_fclose(fp);
307 			g_free(pathnew);
308 			goto bail;
309 		}
310 	}
311 	if (claws_safe_fclose(fpnew) == EOF) {
312 		FILE_OP_ERROR(pathnew, "claws_fclose");
313 		claws_fclose(fp);
314 		g_free(pathnew);
315 		goto bail;
316 	}
317 
318 	claws_fclose(fp);
319 	if (rename_force(pathnew, filename) != 0) {
320 		g_free(pathnew);
321 		goto bail;
322 	}
323 
324 	g_free(pathnew);
325 	msginfo->planned_download = download;
326 	msgcache_update_msg(msginfo->folder->cache, msginfo);
327 
328 	err = 0;
329 bail:
330 	g_free(filename);
331 	procmsg_msginfo_free(&tinfo);
332 
333 	return err;
334 }
335 
partial_mark_for_delete(MsgInfo * msginfo)336 int partial_mark_for_delete(MsgInfo *msginfo)
337 {
338 	return partial_uidl_mark_mail(msginfo, POP3_PARTIAL_DLOAD_DELE);
339 }
340 
partial_mark_for_download(MsgInfo * msginfo)341 int partial_mark_for_download(MsgInfo *msginfo)
342 {
343 	return partial_uidl_mark_mail(msginfo, POP3_PARTIAL_DLOAD_DLOAD);
344 }
345 
partial_unmark(MsgInfo * msginfo)346 int partial_unmark(MsgInfo *msginfo)
347 {
348 	return partial_uidl_mark_mail(msginfo, POP3_PARTIAL_DLOAD_UNKN);
349 }
350 
partial_delete_old(const gchar * file)351 void partial_delete_old(const gchar *file)
352 {
353 	gchar *id = g_strdup(file);
354 	gchar *snum = strrchr(file, ':');
355 	int num = 0;
356 	FolderItem *item = NULL;
357 
358 	debug_print("too big message updated, should remove %s\n", file?file:"(null)");
359 
360 	if (snum) {
361 		snum++;
362 	} else {
363 		g_free(id);
364 		return; /* not a real problem */
365 	}
366 
367 	num = atoi(snum);
368 
369 	if (strrchr(id, ':'))
370 		*(strrchr(id, ':'))='\0';
371 
372 	item = folder_find_item_from_identifier(id);
373 	if (item) {
374 		debug_print("removing %d in %s\n", num, id);
375 		folder_item_remove_msg(item, num);
376 	}
377 	g_free(id);
378 }
379 
partial_get_filename(const gchar * server,const gchar * login,const gchar * muidl)380 gchar *partial_get_filename(const gchar *server, const gchar *login,
381 				   const gchar *muidl)
382 {
383 	gchar *path;
384 	gchar *result = NULL;
385 	FILE *fp;
386 	gchar buf[POPBUFSIZE];
387 	gchar uidl[POPBUFSIZE];
388 	time_t recv_time;
389 	time_t now;
390 	gchar *sanitized_uid = g_strdup(login);
391 
392 	subst_for_filename(sanitized_uid);
393 
394 	path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
395 			   "uidl", G_DIR_SEPARATOR_S,
396 			   server, "-", sanitized_uid, NULL);
397 	if ((fp = claws_fopen(path, "rb")) == NULL) {
398 		if (ENOENT != errno) FILE_OP_ERROR(path, "claws_fopen");
399 		g_free(path);
400 		path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
401 				   "uidl-", server,
402 				   "-", sanitized_uid, NULL);
403 		if ((fp = claws_fopen(path, "rb")) == NULL) {
404 			if (ENOENT != errno) FILE_OP_ERROR(path, "claws_fopen");
405 			g_free(sanitized_uid);
406 			g_free(path);
407 			return result;
408 		}
409 	}
410 	g_free(sanitized_uid);
411 	g_free(path);
412 
413 	now = time(NULL);
414 
415 	while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
416 		gchar tmp[POPBUFSIZE];
417 		strretchomp(buf);
418 		recv_time = RECV_TIME_NONE;
419 
420 		if (sscanf(buf, "%s\t%ld\t%s", uidl, (long int *) &recv_time,
421 			   tmp) < 2) {
422 			if (sscanf(buf, "%s", uidl) != 1)
423 				continue;
424 			else {
425 				recv_time = now;
426 			}
427 		}
428 		if (!strcmp(muidl, uidl)) {
429 			result = g_strdup(tmp);
430 			break;
431 		}
432 	}
433 
434 	claws_fclose(fp);
435 
436 	return result;
437 }
438 
439