Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * @file |
3 | | * POP network mailbox |
4 | | * |
5 | | * @authors |
6 | | * Copyright (C) 2017-2023 Richard Russon <rich@flatcap.org> |
7 | | * Copyright (C) 2018-2021 Pietro Cerutti <gahr@gahr.ch> |
8 | | * Copyright (C) 2019 Ian Zimmerman <itz@no-use.mooo.com> |
9 | | * |
10 | | * @copyright |
11 | | * This program is free software: you can redistribute it and/or modify it under |
12 | | * the terms of the GNU General Public License as published by the Free Software |
13 | | * Foundation, either version 2 of the License, or (at your option) any later |
14 | | * version. |
15 | | * |
16 | | * This program is distributed in the hope that it will be useful, but WITHOUT |
17 | | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
18 | | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
19 | | * details. |
20 | | * |
21 | | * You should have received a copy of the GNU General Public License along with |
22 | | * this program. If not, see <http://www.gnu.org/licenses/>. |
23 | | */ |
24 | | |
25 | | /** |
26 | | * @page pop_pop POP network mailbox |
27 | | * |
28 | | * POP network mailbox |
29 | | * |
30 | | * Implementation: #MxPopOps |
31 | | */ |
32 | | |
33 | | #include "config.h" |
34 | | #include <errno.h> |
35 | | #include <limits.h> |
36 | | #include <stdbool.h> |
37 | | #include <stdio.h> |
38 | | #include <stdlib.h> |
39 | | #include <string.h> |
40 | | #include <unistd.h> |
41 | | #include "private.h" |
42 | | #include "mutt/lib.h" |
43 | | #include "config/lib.h" |
44 | | #include "email/lib.h" |
45 | | #include "core/lib.h" |
46 | | #include "conn/lib.h" |
47 | | #include "lib.h" |
48 | | #include "bcache/lib.h" |
49 | | #include "ncrypt/lib.h" |
50 | | #include "progress/lib.h" |
51 | | #include "question/lib.h" |
52 | | #include "adata.h" |
53 | | #include "edata.h" |
54 | | #include "hook.h" |
55 | | #include "mutt_header.h" |
56 | | #include "mutt_logging.h" |
57 | | #include "mutt_socket.h" |
58 | | #include "mx.h" |
59 | | #ifdef ENABLE_NLS |
60 | | #include <libintl.h> |
61 | | #endif |
62 | | #ifdef USE_HCACHE |
63 | | #include "hcache/lib.h" |
64 | | #endif |
65 | | |
66 | | struct BodyCache; |
67 | | struct stat; |
68 | | |
69 | | #define HC_FNAME "neomutt" /* filename for hcache as POP lacks paths */ |
70 | | #define HC_FEXT "hcache" /* extension for hcache as POP lacks paths */ |
71 | | |
72 | | /** |
73 | | * cache_id - Make a message-cache-compatible id |
74 | | * @param id POP message id |
75 | | * @retval ptr Sanitised string |
76 | | * |
77 | | * The POP message id may contain '/' and other awkward characters. |
78 | | * |
79 | | * @note This function returns a pointer to a static buffer. |
80 | | */ |
81 | | static const char *cache_id(const char *id) |
82 | 0 | { |
83 | 0 | static char clean[128]; |
84 | 0 | mutt_str_copy(clean, id, sizeof(clean)); |
85 | 0 | mutt_file_sanitize_filename(clean, true); |
86 | 0 | return clean; |
87 | 0 | } |
88 | | |
89 | | /** |
90 | | * fetch_message - Parse a Message response - Implements ::pop_fetch_t - @ingroup pop_fetch_api |
91 | | * @param line String to write |
92 | | * @param data FILE pointer to write to |
93 | | * @retval 0 Success |
94 | | * @retval -1 Failure |
95 | | * |
96 | | * Save a Message to a file. |
97 | | */ |
98 | | static int fetch_message(const char *line, void *data) |
99 | 0 | { |
100 | 0 | FILE *fp = data; |
101 | |
|
102 | 0 | fputs(line, fp); |
103 | 0 | if (fputc('\n', fp) == EOF) |
104 | 0 | return -1; |
105 | | |
106 | 0 | return 0; |
107 | 0 | } |
108 | | |
109 | | /** |
110 | | * pop_read_header - Read header |
111 | | * @param adata POP Account data |
112 | | * @param e Email |
113 | | * @retval 0 Success |
114 | | * @retval -1 Connection lost |
115 | | * @retval -2 Invalid command or execution error |
116 | | * @retval -3 Error writing to tempfile |
117 | | */ |
118 | | static int pop_read_header(struct PopAccountData *adata, struct Email *e) |
119 | 0 | { |
120 | 0 | FILE *fp = mutt_file_mkstemp(); |
121 | 0 | if (!fp) |
122 | 0 | { |
123 | 0 | mutt_perror(_("Can't create temporary file")); |
124 | 0 | return -3; |
125 | 0 | } |
126 | | |
127 | 0 | int index = 0; |
128 | 0 | size_t length = 0; |
129 | 0 | char buf[1024] = { 0 }; |
130 | |
|
131 | 0 | struct PopEmailData *edata = pop_edata_get(e); |
132 | |
|
133 | 0 | snprintf(buf, sizeof(buf), "LIST %d\r\n", edata->refno); |
134 | 0 | int rc = pop_query(adata, buf, sizeof(buf)); |
135 | 0 | if (rc == 0) |
136 | 0 | { |
137 | 0 | sscanf(buf, "+OK %d %zu", &index, &length); |
138 | |
|
139 | 0 | snprintf(buf, sizeof(buf), "TOP %d 0\r\n", edata->refno); |
140 | 0 | rc = pop_fetch_data(adata, buf, NULL, fetch_message, fp); |
141 | |
|
142 | 0 | if (adata->cmd_top == 2) |
143 | 0 | { |
144 | 0 | if (rc == 0) |
145 | 0 | { |
146 | 0 | adata->cmd_top = 1; |
147 | |
|
148 | 0 | mutt_debug(LL_DEBUG1, "set TOP capability\n"); |
149 | 0 | } |
150 | |
|
151 | 0 | if (rc == -2) |
152 | 0 | { |
153 | 0 | adata->cmd_top = 0; |
154 | |
|
155 | 0 | mutt_debug(LL_DEBUG1, "unset TOP capability\n"); |
156 | 0 | snprintf(adata->err_msg, sizeof(adata->err_msg), "%s", |
157 | 0 | _("Command TOP is not supported by server")); |
158 | 0 | } |
159 | 0 | } |
160 | 0 | } |
161 | |
|
162 | 0 | switch (rc) |
163 | 0 | { |
164 | 0 | case 0: |
165 | 0 | { |
166 | 0 | rewind(fp); |
167 | 0 | e->env = mutt_rfc822_read_header(fp, e, false, false); |
168 | 0 | e->body->length = length - e->body->offset + 1; |
169 | 0 | rewind(fp); |
170 | 0 | while (!feof(fp)) |
171 | 0 | { |
172 | 0 | e->body->length--; |
173 | 0 | if (!fgets(buf, sizeof(buf), fp)) |
174 | 0 | break; |
175 | 0 | } |
176 | 0 | break; |
177 | 0 | } |
178 | 0 | case -2: |
179 | 0 | { |
180 | 0 | mutt_error("%s", adata->err_msg); |
181 | 0 | break; |
182 | 0 | } |
183 | 0 | case -3: |
184 | 0 | { |
185 | 0 | mutt_error(_("Can't write header to temporary file")); |
186 | 0 | break; |
187 | 0 | } |
188 | 0 | } |
189 | | |
190 | 0 | mutt_file_fclose(&fp); |
191 | 0 | return rc; |
192 | 0 | } |
193 | | |
194 | | /** |
195 | | * fetch_uidl - Parse UIDL response - Implements ::pop_fetch_t - @ingroup pop_fetch_api |
196 | | * @param line String to parse |
197 | | * @param data Mailbox |
198 | | * @retval 0 Success |
199 | | * @retval -1 Failure |
200 | | */ |
201 | | static int fetch_uidl(const char *line, void *data) |
202 | 0 | { |
203 | 0 | struct Mailbox *m = data; |
204 | 0 | struct PopAccountData *adata = pop_adata_get(m); |
205 | 0 | char *endp = NULL; |
206 | |
|
207 | 0 | errno = 0; |
208 | 0 | int index = strtol(line, &endp, 10); |
209 | 0 | if (errno) |
210 | 0 | return -1; |
211 | 0 | while (*endp == ' ') |
212 | 0 | endp++; |
213 | 0 | line = endp; |
214 | | |
215 | | /* uid must be at least be 1 byte */ |
216 | 0 | if (strlen(line) == 0) |
217 | 0 | return -1; |
218 | | |
219 | 0 | int i; |
220 | 0 | for (i = 0; i < m->msg_count; i++) |
221 | 0 | { |
222 | 0 | struct PopEmailData *edata = pop_edata_get(m->emails[i]); |
223 | 0 | if (mutt_str_equal(line, edata->uid)) |
224 | 0 | break; |
225 | 0 | } |
226 | |
|
227 | 0 | if (i == m->msg_count) |
228 | 0 | { |
229 | 0 | mutt_debug(LL_DEBUG1, "new header %d %s\n", index, line); |
230 | |
|
231 | 0 | mx_alloc_memory(m, i); |
232 | |
|
233 | 0 | m->msg_count++; |
234 | 0 | m->emails[i] = email_new(); |
235 | |
|
236 | 0 | m->emails[i]->edata = pop_edata_new(line); |
237 | 0 | m->emails[i]->edata_free = pop_edata_free; |
238 | 0 | } |
239 | 0 | else if (m->emails[i]->index != index - 1) |
240 | 0 | { |
241 | 0 | adata->clear_cache = true; |
242 | 0 | } |
243 | |
|
244 | 0 | m->emails[i]->index = index - 1; |
245 | |
|
246 | 0 | struct PopEmailData *edata = pop_edata_get(m->emails[i]); |
247 | 0 | edata->refno = index; |
248 | |
|
249 | 0 | return 0; |
250 | 0 | } |
251 | | |
252 | | /** |
253 | | * pop_bcache_delete - Delete an entry from the message cache - Implements ::bcache_list_t - @ingroup bcache_list_api |
254 | | */ |
255 | | static int pop_bcache_delete(const char *id, struct BodyCache *bcache, void *data) |
256 | 0 | { |
257 | 0 | struct Mailbox *m = data; |
258 | 0 | if (!m) |
259 | 0 | return -1; |
260 | | |
261 | 0 | struct PopAccountData *adata = pop_adata_get(m); |
262 | 0 | if (!adata) |
263 | 0 | return -1; |
264 | | |
265 | | #ifdef USE_HCACHE |
266 | | /* keep hcache file if hcache == bcache */ |
267 | | if (mutt_str_equal(HC_FNAME "." HC_FEXT, id)) |
268 | | return 0; |
269 | | #endif |
270 | | |
271 | 0 | for (int i = 0; i < m->msg_count; i++) |
272 | 0 | { |
273 | 0 | struct PopEmailData *edata = pop_edata_get(m->emails[i]); |
274 | | /* if the id we get is known for a header: done (i.e. keep in cache) */ |
275 | 0 | if (edata->uid && mutt_str_equal(edata->uid, id)) |
276 | 0 | return 0; |
277 | 0 | } |
278 | | |
279 | | /* message not found in context -> remove it from cache |
280 | | * return the result of bcache, so we stop upon its first error */ |
281 | 0 | return mutt_bcache_del(bcache, cache_id(id)); |
282 | 0 | } |
283 | | |
284 | | #ifdef USE_HCACHE |
285 | | /** |
286 | | * pop_hcache_namer - Create a header cache filename for a POP mailbox - Implements ::hcache_namer_t - @ingroup hcache_namer_api |
287 | | */ |
288 | | static void pop_hcache_namer(const char *path, struct Buffer *dest) |
289 | | { |
290 | | buf_printf(dest, "%s." HC_FEXT, path); |
291 | | } |
292 | | |
293 | | /** |
294 | | * pop_hcache_open - Open the header cache |
295 | | * @param adata POP Account data |
296 | | * @param path Path to the mailbox |
297 | | * @retval ptr Header cache |
298 | | */ |
299 | | static struct HeaderCache *pop_hcache_open(struct PopAccountData *adata, const char *path) |
300 | | { |
301 | | const char *const c_header_cache = cs_subset_path(NeoMutt->sub, "header_cache"); |
302 | | if (!adata || !adata->conn) |
303 | | return hcache_open(c_header_cache, path, NULL, true); |
304 | | |
305 | | struct Url url = { 0 }; |
306 | | char p[1024] = { 0 }; |
307 | | |
308 | | account_to_url(&adata->conn->account, &url); |
309 | | url.path = HC_FNAME; |
310 | | url_tostring(&url, p, sizeof(p), U_PATH); |
311 | | return hcache_open(c_header_cache, p, pop_hcache_namer, true); |
312 | | } |
313 | | #endif |
314 | | |
315 | | /** |
316 | | * pop_fetch_headers - Read headers |
317 | | * @param m Mailbox |
318 | | * @retval 0 Success |
319 | | * @retval -1 Connection lost |
320 | | * @retval -2 Invalid command or execution error |
321 | | * @retval -3 Error writing to tempfile |
322 | | */ |
323 | | static int pop_fetch_headers(struct Mailbox *m) |
324 | 0 | { |
325 | 0 | if (!m) |
326 | 0 | return -1; |
327 | | |
328 | 0 | struct PopAccountData *adata = pop_adata_get(m); |
329 | 0 | struct Progress *progress = NULL; |
330 | |
|
331 | | #ifdef USE_HCACHE |
332 | | struct HeaderCache *hc = pop_hcache_open(adata, mailbox_path(m)); |
333 | | #endif |
334 | |
|
335 | 0 | adata->check_time = mutt_date_now(); |
336 | 0 | adata->clear_cache = false; |
337 | |
|
338 | 0 | for (int i = 0; i < m->msg_count; i++) |
339 | 0 | { |
340 | 0 | struct PopEmailData *edata = pop_edata_get(m->emails[i]); |
341 | 0 | edata->refno = -1; |
342 | 0 | } |
343 | |
|
344 | 0 | const int old_count = m->msg_count; |
345 | 0 | int rc = pop_fetch_data(adata, "UIDL\r\n", NULL, fetch_uidl, m); |
346 | 0 | const int new_count = m->msg_count; |
347 | 0 | m->msg_count = old_count; |
348 | |
|
349 | 0 | if (adata->cmd_uidl == 2) |
350 | 0 | { |
351 | 0 | if (rc == 0) |
352 | 0 | { |
353 | 0 | adata->cmd_uidl = 1; |
354 | |
|
355 | 0 | mutt_debug(LL_DEBUG1, "set UIDL capability\n"); |
356 | 0 | } |
357 | |
|
358 | 0 | if ((rc == -2) && (adata->cmd_uidl == 2)) |
359 | 0 | { |
360 | 0 | adata->cmd_uidl = 0; |
361 | |
|
362 | 0 | mutt_debug(LL_DEBUG1, "unset UIDL capability\n"); |
363 | 0 | snprintf(adata->err_msg, sizeof(adata->err_msg), "%s", |
364 | 0 | _("Command UIDL is not supported by server")); |
365 | 0 | } |
366 | 0 | } |
367 | |
|
368 | 0 | if (m->verbose) |
369 | 0 | { |
370 | 0 | progress = progress_new(MUTT_PROGRESS_READ, new_count - old_count); |
371 | 0 | progress_set_message(progress, _("Fetching message headers...")); |
372 | 0 | } |
373 | |
|
374 | 0 | if (rc == 0) |
375 | 0 | { |
376 | 0 | int i, deleted; |
377 | 0 | for (i = 0, deleted = 0; i < old_count; i++) |
378 | 0 | { |
379 | 0 | struct PopEmailData *edata = pop_edata_get(m->emails[i]); |
380 | 0 | if (edata->refno == -1) |
381 | 0 | { |
382 | 0 | m->emails[i]->deleted = true; |
383 | 0 | deleted++; |
384 | 0 | } |
385 | 0 | } |
386 | 0 | if (deleted > 0) |
387 | 0 | { |
388 | 0 | mutt_error(ngettext("%d message has been lost. Try reopening the mailbox.", |
389 | 0 | "%d messages have been lost. Try reopening the mailbox.", deleted), |
390 | 0 | deleted); |
391 | 0 | } |
392 | |
|
393 | 0 | bool hcached = false; |
394 | 0 | for (i = old_count; i < new_count; i++) |
395 | 0 | { |
396 | 0 | progress_update(progress, i + 1 - old_count, -1); |
397 | 0 | struct PopEmailData *edata = pop_edata_get(m->emails[i]); |
398 | | #ifdef USE_HCACHE |
399 | | struct HCacheEntry hce = hcache_fetch_email(hc, edata->uid, strlen(edata->uid), 0); |
400 | | if (hce.email) |
401 | | { |
402 | | /* Detach the private data */ |
403 | | m->emails[i]->edata = NULL; |
404 | | |
405 | | int index = m->emails[i]->index; |
406 | | /* - POP dynamically numbers headers and relies on e->refno |
407 | | * to map messages; so restore header and overwrite restored |
408 | | * refno with current refno, same for index |
409 | | * - e->data needs to a separate pointer as it's driver-specific |
410 | | * data freed separately elsewhere |
411 | | * (the old e->data should point inside a malloc'd block from |
412 | | * hcache so there shouldn't be a memleak here) */ |
413 | | email_free(&m->emails[i]); |
414 | | m->emails[i] = hce.email; |
415 | | m->emails[i]->index = index; |
416 | | |
417 | | /* Reattach the private data */ |
418 | | m->emails[i]->edata = edata; |
419 | | m->emails[i]->edata_free = pop_edata_free; |
420 | | rc = 0; |
421 | | hcached = true; |
422 | | } |
423 | | else |
424 | | #endif |
425 | 0 | if ((rc = pop_read_header(adata, m->emails[i])) < 0) |
426 | 0 | break; |
427 | | #ifdef USE_HCACHE |
428 | | else |
429 | | { |
430 | | hcache_store_email(hc, edata->uid, strlen(edata->uid), m->emails[i], 0); |
431 | | } |
432 | | #endif |
433 | | |
434 | | /* faked support for flags works like this: |
435 | | * - if 'hcached' is true, we have the message in our hcache: |
436 | | * - if we also have a body: read |
437 | | * - if we don't have a body: old |
438 | | * (if $mark_old is set which is maybe wrong as |
439 | | * $mark_old should be considered for syncing the |
440 | | * folder and not when opening it XXX) |
441 | | * - if 'hcached' is false, we don't have the message in our hcache: |
442 | | * - if we also have a body: read |
443 | | * - if we don't have a body: new */ |
444 | 0 | const bool bcached = (mutt_bcache_exists(adata->bcache, cache_id(edata->uid)) == 0); |
445 | 0 | m->emails[i]->old = false; |
446 | 0 | m->emails[i]->read = false; |
447 | 0 | if (hcached) |
448 | 0 | { |
449 | 0 | const bool c_mark_old = cs_subset_bool(NeoMutt->sub, "mark_old"); |
450 | 0 | if (bcached) |
451 | 0 | m->emails[i]->read = true; |
452 | 0 | else if (c_mark_old) |
453 | 0 | m->emails[i]->old = true; |
454 | 0 | } |
455 | 0 | else |
456 | 0 | { |
457 | 0 | if (bcached) |
458 | 0 | m->emails[i]->read = true; |
459 | 0 | } |
460 | |
|
461 | 0 | m->msg_count++; |
462 | 0 | } |
463 | 0 | } |
464 | 0 | progress_free(&progress); |
465 | |
|
466 | | #ifdef USE_HCACHE |
467 | | hcache_close(&hc); |
468 | | #endif |
469 | |
|
470 | 0 | if (rc < 0) |
471 | 0 | { |
472 | 0 | for (int i = m->msg_count; i < new_count; i++) |
473 | 0 | email_free(&m->emails[i]); |
474 | 0 | return rc; |
475 | 0 | } |
476 | | |
477 | | /* after putting the result into our structures, |
478 | | * clean up cache, i.e. wipe messages deleted outside |
479 | | * the availability of our cache */ |
480 | 0 | const bool c_message_cache_clean = cs_subset_bool(NeoMutt->sub, "message_cache_clean"); |
481 | 0 | if (c_message_cache_clean) |
482 | 0 | mutt_bcache_list(adata->bcache, pop_bcache_delete, m); |
483 | |
|
484 | 0 | mutt_clear_error(); |
485 | 0 | return new_count - old_count; |
486 | 0 | } |
487 | | |
488 | | /** |
489 | | * pop_clear_cache - Delete all cached messages |
490 | | * @param adata POP Account data |
491 | | */ |
492 | | static void pop_clear_cache(struct PopAccountData *adata) |
493 | 0 | { |
494 | 0 | if (!adata->clear_cache) |
495 | 0 | return; |
496 | | |
497 | 0 | mutt_debug(LL_DEBUG1, "delete cached messages\n"); |
498 | |
|
499 | 0 | for (int i = 0; i < POP_CACHE_LEN; i++) |
500 | 0 | { |
501 | 0 | if (adata->cache[i].path) |
502 | 0 | { |
503 | 0 | unlink(adata->cache[i].path); |
504 | 0 | FREE(&adata->cache[i].path); |
505 | 0 | } |
506 | 0 | } |
507 | 0 | } |
508 | | |
509 | | /** |
510 | | * pop_fetch_mail - Fetch messages and save them in $spool_file |
511 | | */ |
512 | | void pop_fetch_mail(void) |
513 | 0 | { |
514 | 0 | const char *const c_pop_host = cs_subset_string(NeoMutt->sub, "pop_host"); |
515 | 0 | if (!c_pop_host) |
516 | 0 | { |
517 | 0 | mutt_error(_("POP host is not defined")); |
518 | 0 | return; |
519 | 0 | } |
520 | | |
521 | 0 | char buf[1024] = { 0 }; |
522 | 0 | char msgbuf[128] = { 0 }; |
523 | 0 | int last = 0, msgs = 0, bytes = 0, rset = 0, rc; |
524 | 0 | struct ConnAccount cac = { { 0 } }; |
525 | |
|
526 | 0 | char *p = MUTT_MEM_CALLOC(strlen(c_pop_host) + 7, char); |
527 | 0 | char *url = p; |
528 | 0 | if (url_check_scheme(c_pop_host) == U_UNKNOWN) |
529 | 0 | { |
530 | 0 | strcpy(url, "pop://"); |
531 | 0 | p = strchr(url, '\0'); |
532 | 0 | } |
533 | 0 | strcpy(p, c_pop_host); |
534 | |
|
535 | 0 | rc = pop_parse_path(url, &cac); |
536 | 0 | FREE(&url); |
537 | 0 | if (rc) |
538 | 0 | { |
539 | 0 | mutt_error(_("%s is an invalid POP path"), c_pop_host); |
540 | 0 | return; |
541 | 0 | } |
542 | | |
543 | 0 | struct Connection *conn = mutt_conn_find(&cac); |
544 | 0 | if (!conn) |
545 | 0 | return; |
546 | | |
547 | 0 | struct PopAccountData *adata = pop_adata_new(); |
548 | 0 | adata->conn = conn; |
549 | |
|
550 | 0 | if (pop_open_connection(adata) < 0) |
551 | 0 | { |
552 | 0 | pop_adata_free((void **) &adata); |
553 | 0 | return; |
554 | 0 | } |
555 | | |
556 | 0 | mutt_message(_("Checking for new messages...")); |
557 | | |
558 | | /* find out how many messages are in the mailbox. */ |
559 | 0 | mutt_str_copy(buf, "STAT\r\n", sizeof(buf)); |
560 | 0 | rc = pop_query(adata, buf, sizeof(buf)); |
561 | 0 | if (rc == -1) |
562 | 0 | goto fail; |
563 | 0 | if (rc == -2) |
564 | 0 | { |
565 | 0 | mutt_error("%s", adata->err_msg); |
566 | 0 | goto finish; |
567 | 0 | } |
568 | | |
569 | 0 | sscanf(buf, "+OK %d %d", &msgs, &bytes); |
570 | | |
571 | | /* only get unread messages */ |
572 | 0 | const bool c_pop_last = cs_subset_bool(NeoMutt->sub, "pop_last"); |
573 | 0 | if ((msgs > 0) && c_pop_last) |
574 | 0 | { |
575 | 0 | mutt_str_copy(buf, "LAST\r\n", sizeof(buf)); |
576 | 0 | rc = pop_query(adata, buf, sizeof(buf)); |
577 | 0 | if (rc == -1) |
578 | 0 | goto fail; |
579 | 0 | if (rc == 0) |
580 | 0 | sscanf(buf, "+OK %d", &last); |
581 | 0 | } |
582 | | |
583 | 0 | if (msgs <= last) |
584 | 0 | { |
585 | 0 | mutt_message(_("No new mail in POP mailbox")); |
586 | 0 | goto finish; |
587 | 0 | } |
588 | | |
589 | 0 | const char *const c_spool_file = cs_subset_string(NeoMutt->sub, "spool_file"); |
590 | 0 | struct Mailbox *m_spool = mx_path_resolve(c_spool_file); |
591 | |
|
592 | 0 | if (!mx_mbox_open(m_spool, MUTT_OPEN_NO_FLAGS)) |
593 | 0 | { |
594 | 0 | mailbox_free(&m_spool); |
595 | 0 | goto finish; |
596 | 0 | } |
597 | 0 | bool old_append = m_spool->append; |
598 | 0 | m_spool->append = true; |
599 | |
|
600 | 0 | enum QuadOption delanswer = query_quadoption(_("Delete messages from server?"), |
601 | 0 | NeoMutt->sub, "pop_delete"); |
602 | |
|
603 | 0 | snprintf(msgbuf, sizeof(msgbuf), |
604 | 0 | ngettext("Reading new messages (%d byte)...", |
605 | 0 | "Reading new messages (%d bytes)...", bytes), |
606 | 0 | bytes); |
607 | 0 | mutt_message("%s", msgbuf); |
608 | |
|
609 | 0 | for (int i = last + 1; i <= msgs; i++) |
610 | 0 | { |
611 | 0 | struct Message *msg = mx_msg_open_new(m_spool, NULL, MUTT_ADD_FROM); |
612 | 0 | if (msg) |
613 | 0 | { |
614 | 0 | snprintf(buf, sizeof(buf), "RETR %d\r\n", i); |
615 | 0 | rc = pop_fetch_data(adata, buf, NULL, fetch_message, msg->fp); |
616 | 0 | if (rc == -3) |
617 | 0 | rset = 1; |
618 | |
|
619 | 0 | if ((rc == 0) && (mx_msg_commit(m_spool, msg) != 0)) |
620 | 0 | { |
621 | 0 | rset = 1; |
622 | 0 | rc = -3; |
623 | 0 | } |
624 | |
|
625 | 0 | mx_msg_close(m_spool, &msg); |
626 | 0 | } |
627 | 0 | else |
628 | 0 | { |
629 | 0 | rc = -3; |
630 | 0 | } |
631 | |
|
632 | 0 | if ((rc == 0) && (delanswer == MUTT_YES)) |
633 | 0 | { |
634 | | /* delete the message on the server */ |
635 | 0 | snprintf(buf, sizeof(buf), "DELE %d\r\n", i); |
636 | 0 | rc = pop_query(adata, buf, sizeof(buf)); |
637 | 0 | } |
638 | |
|
639 | 0 | if (rc == -1) |
640 | 0 | { |
641 | 0 | m_spool->append = old_append; |
642 | 0 | mx_mbox_close(m_spool); |
643 | 0 | goto fail; |
644 | 0 | } |
645 | 0 | if (rc == -2) |
646 | 0 | { |
647 | 0 | mutt_error("%s", adata->err_msg); |
648 | 0 | break; |
649 | 0 | } |
650 | 0 | if (rc == -3) |
651 | 0 | { |
652 | 0 | mutt_error(_("Error while writing mailbox")); |
653 | 0 | break; |
654 | 0 | } |
655 | | |
656 | | /* L10N: The plural is picked by the second numerical argument, i.e. |
657 | | the %d right before 'messages', i.e. the total number of messages. */ |
658 | 0 | mutt_message(ngettext("%s [%d of %d message read]", |
659 | 0 | "%s [%d of %d messages read]", msgs - last), |
660 | 0 | msgbuf, i - last, msgs - last); |
661 | 0 | } |
662 | | |
663 | 0 | m_spool->append = old_append; |
664 | 0 | mx_mbox_close(m_spool); |
665 | |
|
666 | 0 | if (rset) |
667 | 0 | { |
668 | | /* make sure no messages get deleted */ |
669 | 0 | mutt_str_copy(buf, "RSET\r\n", sizeof(buf)); |
670 | 0 | if (pop_query(adata, buf, sizeof(buf)) == -1) |
671 | 0 | goto fail; |
672 | 0 | } |
673 | | |
674 | 0 | finish: |
675 | | /* exit gracefully */ |
676 | 0 | mutt_str_copy(buf, "QUIT\r\n", sizeof(buf)); |
677 | 0 | if (pop_query(adata, buf, sizeof(buf)) == -1) |
678 | 0 | goto fail; |
679 | 0 | mutt_socket_close(conn); |
680 | 0 | pop_adata_free((void **) &adata); |
681 | 0 | return; |
682 | | |
683 | 0 | fail: |
684 | 0 | mutt_error(_("Server closed connection")); |
685 | 0 | mutt_socket_close(conn); |
686 | 0 | pop_adata_free((void **) &adata); |
687 | 0 | } |
688 | | |
689 | | /** |
690 | | * pop_ac_owns_path - Check whether an Account owns a Mailbox path - Implements MxOps::ac_owns_path() - @ingroup mx_ac_owns_path |
691 | | */ |
692 | | static bool pop_ac_owns_path(struct Account *a, const char *path) |
693 | 0 | { |
694 | 0 | struct Url *url = url_parse(path); |
695 | 0 | if (!url) |
696 | 0 | return false; |
697 | | |
698 | 0 | struct PopAccountData *adata = a->adata; |
699 | 0 | struct ConnAccount *cac = &adata->conn->account; |
700 | |
|
701 | 0 | const bool rc = mutt_istr_equal(url->host, cac->host) && |
702 | 0 | mutt_istr_equal(url->user, cac->user); |
703 | 0 | url_free(&url); |
704 | 0 | return rc; |
705 | 0 | } |
706 | | |
707 | | /** |
708 | | * pop_ac_add - Add a Mailbox to an Account - Implements MxOps::ac_add() - @ingroup mx_ac_add |
709 | | */ |
710 | | static bool pop_ac_add(struct Account *a, struct Mailbox *m) |
711 | 0 | { |
712 | 0 | if (a->adata) |
713 | 0 | return true; |
714 | | |
715 | 0 | struct ConnAccount cac = { { 0 } }; |
716 | 0 | if (pop_parse_path(mailbox_path(m), &cac)) |
717 | 0 | { |
718 | 0 | mutt_error(_("%s is an invalid POP path"), mailbox_path(m)); |
719 | 0 | return false; |
720 | 0 | } |
721 | | |
722 | 0 | struct PopAccountData *adata = pop_adata_new(); |
723 | 0 | adata->conn = mutt_conn_new(&cac); |
724 | 0 | if (!adata->conn) |
725 | 0 | { |
726 | 0 | pop_adata_free((void **) &adata); |
727 | 0 | return false; |
728 | 0 | } |
729 | 0 | a->adata = adata; |
730 | 0 | a->adata_free = pop_adata_free; |
731 | |
|
732 | 0 | return true; |
733 | 0 | } |
734 | | |
735 | | /** |
736 | | * pop_mbox_open - Open a Mailbox - Implements MxOps::mbox_open() - @ingroup mx_mbox_open |
737 | | * |
738 | | * Fetch only headers |
739 | | */ |
740 | | static enum MxOpenReturns pop_mbox_open(struct Mailbox *m) |
741 | 0 | { |
742 | 0 | if (!m->account) |
743 | 0 | return MX_OPEN_ERROR; |
744 | | |
745 | 0 | char buf[PATH_MAX] = { 0 }; |
746 | 0 | struct ConnAccount cac = { { 0 } }; |
747 | 0 | struct Url url = { 0 }; |
748 | |
|
749 | 0 | if (pop_parse_path(mailbox_path(m), &cac)) |
750 | 0 | { |
751 | 0 | mutt_error(_("%s is an invalid POP path"), mailbox_path(m)); |
752 | 0 | return MX_OPEN_ERROR; |
753 | 0 | } |
754 | | |
755 | 0 | account_to_url(&cac, &url); |
756 | 0 | url.path = NULL; |
757 | 0 | url_tostring(&url, buf, sizeof(buf), U_NO_FLAGS); |
758 | |
|
759 | 0 | buf_strcpy(&m->pathbuf, buf); |
760 | 0 | mutt_str_replace(&m->realpath, mailbox_path(m)); |
761 | |
|
762 | 0 | struct PopAccountData *adata = m->account->adata; |
763 | 0 | if (!adata) |
764 | 0 | { |
765 | 0 | adata = pop_adata_new(); |
766 | 0 | m->account->adata = adata; |
767 | 0 | m->account->adata_free = pop_adata_free; |
768 | 0 | } |
769 | |
|
770 | 0 | struct Connection *conn = adata->conn; |
771 | 0 | if (!conn) |
772 | 0 | { |
773 | 0 | adata->conn = mutt_conn_new(&cac); |
774 | 0 | conn = adata->conn; |
775 | 0 | if (!conn) |
776 | 0 | return MX_OPEN_ERROR; |
777 | 0 | } |
778 | | |
779 | 0 | if (conn->fd < 0) |
780 | 0 | mutt_account_hook(m->realpath); |
781 | |
|
782 | 0 | if (pop_open_connection(adata) < 0) |
783 | 0 | return MX_OPEN_ERROR; |
784 | | |
785 | 0 | adata->bcache = mutt_bcache_open(&cac, NULL); |
786 | | |
787 | | /* init (hard-coded) ACL rights */ |
788 | 0 | m->rights = MUTT_ACL_SEEN | MUTT_ACL_DELETE; |
789 | | #ifdef USE_HCACHE |
790 | | /* flags are managed using header cache, so it only makes sense to |
791 | | * enable them in that case */ |
792 | | m->rights |= MUTT_ACL_WRITE; |
793 | | #endif |
794 | |
|
795 | 0 | while (true) |
796 | 0 | { |
797 | 0 | if (pop_reconnect(m) < 0) |
798 | 0 | return MX_OPEN_ERROR; |
799 | | |
800 | 0 | m->size = adata->size; |
801 | |
|
802 | 0 | mutt_message(_("Fetching list of messages...")); |
803 | |
|
804 | 0 | const int rc = pop_fetch_headers(m); |
805 | |
|
806 | 0 | if (rc >= 0) |
807 | 0 | return MX_OPEN_OK; |
808 | | |
809 | 0 | if (rc < -1) |
810 | 0 | return MX_OPEN_ERROR; |
811 | 0 | } |
812 | 0 | } |
813 | | |
814 | | /** |
815 | | * pop_mbox_check - Check for new mail - Implements MxOps::mbox_check() - @ingroup mx_mbox_check |
816 | | */ |
817 | | static enum MxStatus pop_mbox_check(struct Mailbox *m) |
818 | 0 | { |
819 | 0 | if (!m || !m->account) |
820 | 0 | return MX_STATUS_ERROR; |
821 | | |
822 | 0 | struct PopAccountData *adata = pop_adata_get(m); |
823 | |
|
824 | 0 | const short c_pop_check_interval = cs_subset_number(NeoMutt->sub, "pop_check_interval"); |
825 | 0 | if ((adata->check_time + c_pop_check_interval) > mutt_date_now()) |
826 | 0 | return MX_STATUS_OK; |
827 | | |
828 | 0 | pop_logout(m); |
829 | |
|
830 | 0 | mutt_socket_close(adata->conn); |
831 | |
|
832 | 0 | if (pop_open_connection(adata) < 0) |
833 | 0 | return MX_STATUS_ERROR; |
834 | | |
835 | 0 | m->size = adata->size; |
836 | |
|
837 | 0 | mutt_message(_("Checking for new messages...")); |
838 | |
|
839 | 0 | int old_msg_count = m->msg_count; |
840 | 0 | int rc = pop_fetch_headers(m); |
841 | 0 | pop_clear_cache(adata); |
842 | 0 | if (m->msg_count > old_msg_count) |
843 | 0 | mailbox_changed(m, NT_MAILBOX_INVALID); |
844 | |
|
845 | 0 | if (rc < 0) |
846 | 0 | return MX_STATUS_ERROR; |
847 | | |
848 | 0 | if (rc > 0) |
849 | 0 | return MX_STATUS_NEW_MAIL; |
850 | | |
851 | 0 | return MX_STATUS_OK; |
852 | 0 | } |
853 | | |
854 | | /** |
855 | | * pop_mbox_sync - Save changes to the Mailbox - Implements MxOps::mbox_sync() - @ingroup mx_mbox_sync |
856 | | * |
857 | | * Update POP mailbox, delete messages from server |
858 | | */ |
859 | | static enum MxStatus pop_mbox_sync(struct Mailbox *m) |
860 | 0 | { |
861 | 0 | int i, j, rc = 0; |
862 | 0 | char buf[1024] = { 0 }; |
863 | 0 | struct PopAccountData *adata = pop_adata_get(m); |
864 | | #ifdef USE_HCACHE |
865 | | struct HeaderCache *hc = NULL; |
866 | | #endif |
867 | |
|
868 | 0 | adata->check_time = 0; |
869 | |
|
870 | 0 | int num_deleted = 0; |
871 | 0 | for (i = 0; i < m->msg_count; i++) |
872 | 0 | { |
873 | 0 | if (m->emails[i]->deleted) |
874 | 0 | num_deleted++; |
875 | 0 | } |
876 | |
|
877 | 0 | while (true) |
878 | 0 | { |
879 | 0 | if (pop_reconnect(m) < 0) |
880 | 0 | return MX_STATUS_ERROR; |
881 | | |
882 | | #ifdef USE_HCACHE |
883 | | hc = pop_hcache_open(adata, mailbox_path(m)); |
884 | | #endif |
885 | | |
886 | 0 | struct Progress *progress = NULL; |
887 | 0 | if (m->verbose) |
888 | 0 | { |
889 | 0 | progress = progress_new(MUTT_PROGRESS_WRITE, num_deleted); |
890 | 0 | progress_set_message(progress, _("Marking messages deleted...")); |
891 | 0 | } |
892 | |
|
893 | 0 | for (i = 0, j = 0, rc = 0; (rc == 0) && (i < m->msg_count); i++) |
894 | 0 | { |
895 | 0 | struct PopEmailData *edata = pop_edata_get(m->emails[i]); |
896 | 0 | if (m->emails[i]->deleted && (edata->refno != -1)) |
897 | 0 | { |
898 | 0 | j++; |
899 | 0 | progress_update(progress, j, -1); |
900 | 0 | snprintf(buf, sizeof(buf), "DELE %d\r\n", edata->refno); |
901 | 0 | rc = pop_query(adata, buf, sizeof(buf)); |
902 | 0 | if (rc == 0) |
903 | 0 | { |
904 | 0 | mutt_bcache_del(adata->bcache, cache_id(edata->uid)); |
905 | | #ifdef USE_HCACHE |
906 | | hcache_delete_email(hc, edata->uid, strlen(edata->uid)); |
907 | | #endif |
908 | 0 | } |
909 | 0 | } |
910 | |
|
911 | | #ifdef USE_HCACHE |
912 | | if (m->emails[i]->changed) |
913 | | { |
914 | | hcache_store_email(hc, edata->uid, strlen(edata->uid), m->emails[i], 0); |
915 | | } |
916 | | #endif |
917 | 0 | } |
918 | 0 | progress_free(&progress); |
919 | |
|
920 | | #ifdef USE_HCACHE |
921 | | hcache_close(&hc); |
922 | | #endif |
923 | |
|
924 | 0 | if (rc == 0) |
925 | 0 | { |
926 | 0 | mutt_str_copy(buf, "QUIT\r\n", sizeof(buf)); |
927 | 0 | rc = pop_query(adata, buf, sizeof(buf)); |
928 | 0 | } |
929 | |
|
930 | 0 | if (rc == 0) |
931 | 0 | { |
932 | 0 | adata->clear_cache = true; |
933 | 0 | pop_clear_cache(adata); |
934 | 0 | adata->status = POP_DISCONNECTED; |
935 | 0 | return MX_STATUS_OK; |
936 | 0 | } |
937 | | |
938 | 0 | if (rc == -2) |
939 | 0 | { |
940 | 0 | mutt_error("%s", adata->err_msg); |
941 | 0 | return MX_STATUS_ERROR; |
942 | 0 | } |
943 | 0 | } |
944 | 0 | } |
945 | | |
946 | | /** |
947 | | * pop_mbox_close - Close a Mailbox - Implements MxOps::mbox_close() - @ingroup mx_mbox_close |
948 | | */ |
949 | | static enum MxStatus pop_mbox_close(struct Mailbox *m) |
950 | 0 | { |
951 | 0 | struct PopAccountData *adata = pop_adata_get(m); |
952 | 0 | if (!adata) |
953 | 0 | return MX_STATUS_OK; |
954 | | |
955 | 0 | pop_logout(m); |
956 | |
|
957 | 0 | if (adata->status != POP_NONE) |
958 | 0 | { |
959 | 0 | mutt_socket_close(adata->conn); |
960 | 0 | } |
961 | |
|
962 | 0 | adata->status = POP_NONE; |
963 | |
|
964 | 0 | adata->clear_cache = true; |
965 | 0 | pop_clear_cache(adata); |
966 | |
|
967 | 0 | mutt_bcache_close(&adata->bcache); |
968 | |
|
969 | 0 | return MX_STATUS_OK; |
970 | 0 | } |
971 | | |
972 | | /** |
973 | | * pop_msg_open - Open an email message in a Mailbox - Implements MxOps::msg_open() - @ingroup mx_msg_open |
974 | | */ |
975 | | static bool pop_msg_open(struct Mailbox *m, struct Message *msg, struct Email *e) |
976 | 0 | { |
977 | 0 | char buf[1024] = { 0 }; |
978 | 0 | struct PopAccountData *adata = pop_adata_get(m); |
979 | 0 | struct PopEmailData *edata = pop_edata_get(e); |
980 | 0 | bool bcache = true; |
981 | 0 | bool success = false; |
982 | 0 | struct Buffer *path = NULL; |
983 | | |
984 | | /* see if we already have the message in body cache */ |
985 | 0 | msg->fp = mutt_bcache_get(adata->bcache, cache_id(edata->uid)); |
986 | 0 | if (msg->fp) |
987 | 0 | return true; |
988 | | |
989 | | /* see if we already have the message in our cache in |
990 | | * case $message_cache_dir is unset */ |
991 | 0 | struct PopCache *cache = &adata->cache[e->index % POP_CACHE_LEN]; |
992 | |
|
993 | 0 | if (cache->path) |
994 | 0 | { |
995 | 0 | if (cache->index == e->index) |
996 | 0 | { |
997 | | /* yes, so just return a pointer to the message */ |
998 | 0 | msg->fp = mutt_file_fopen(cache->path, "r"); |
999 | 0 | if (msg->fp) |
1000 | 0 | return true; |
1001 | | |
1002 | 0 | mutt_perror("%s", cache->path); |
1003 | 0 | return false; |
1004 | 0 | } |
1005 | 0 | else |
1006 | 0 | { |
1007 | | /* clear the previous entry */ |
1008 | 0 | unlink(cache->path); |
1009 | 0 | FREE(&cache->path); |
1010 | 0 | } |
1011 | 0 | } |
1012 | | |
1013 | 0 | path = buf_pool_get(); |
1014 | |
|
1015 | 0 | while (true) |
1016 | 0 | { |
1017 | 0 | if (pop_reconnect(m) < 0) |
1018 | 0 | goto cleanup; |
1019 | | |
1020 | | /* verify that massage index is correct */ |
1021 | 0 | if (edata->refno < 0) |
1022 | 0 | { |
1023 | 0 | mutt_error(_("The message index is incorrect. Try reopening the mailbox.")); |
1024 | 0 | goto cleanup; |
1025 | 0 | } |
1026 | | |
1027 | | /* see if we can put in body cache; use our cache as fallback */ |
1028 | 0 | msg->fp = mutt_bcache_put(adata->bcache, cache_id(edata->uid)); |
1029 | 0 | if (!msg->fp) |
1030 | 0 | { |
1031 | | /* no */ |
1032 | 0 | bcache = false; |
1033 | 0 | buf_mktemp(path); |
1034 | 0 | msg->fp = mutt_file_fopen(buf_string(path), "w+"); |
1035 | 0 | if (!msg->fp) |
1036 | 0 | { |
1037 | 0 | mutt_perror("%s", buf_string(path)); |
1038 | 0 | goto cleanup; |
1039 | 0 | } |
1040 | 0 | } |
1041 | | |
1042 | 0 | snprintf(buf, sizeof(buf), "RETR %d\r\n", edata->refno); |
1043 | |
|
1044 | 0 | struct Progress *progress = progress_new(MUTT_PROGRESS_NET, |
1045 | 0 | e->body->length + e->body->offset - 1); |
1046 | 0 | progress_set_message(progress, _("Fetching message...")); |
1047 | 0 | const int rc = pop_fetch_data(adata, buf, progress, fetch_message, msg->fp); |
1048 | 0 | progress_free(&progress); |
1049 | |
|
1050 | 0 | if (rc == 0) |
1051 | 0 | break; |
1052 | | |
1053 | 0 | mutt_file_fclose(&msg->fp); |
1054 | | |
1055 | | /* if RETR failed (e.g. connection closed), be sure to remove either |
1056 | | * the file in bcache or from POP's own cache since the next iteration |
1057 | | * of the loop will re-attempt to put() the message */ |
1058 | 0 | if (!bcache) |
1059 | 0 | unlink(buf_string(path)); |
1060 | |
|
1061 | 0 | if (rc == -2) |
1062 | 0 | { |
1063 | 0 | mutt_error("%s", adata->err_msg); |
1064 | 0 | goto cleanup; |
1065 | 0 | } |
1066 | | |
1067 | 0 | if (rc == -3) |
1068 | 0 | { |
1069 | 0 | mutt_error(_("Can't write message to temporary file")); |
1070 | 0 | goto cleanup; |
1071 | 0 | } |
1072 | 0 | } |
1073 | | |
1074 | | /* Update the header information. Previously, we only downloaded a |
1075 | | * portion of the headers, those required for the main display. */ |
1076 | 0 | if (bcache) |
1077 | 0 | { |
1078 | 0 | mutt_bcache_commit(adata->bcache, cache_id(edata->uid)); |
1079 | 0 | } |
1080 | 0 | else |
1081 | 0 | { |
1082 | 0 | cache->index = e->index; |
1083 | 0 | cache->path = buf_strdup(path); |
1084 | 0 | } |
1085 | 0 | rewind(msg->fp); |
1086 | | |
1087 | | /* Detach the private data */ |
1088 | 0 | e->edata = NULL; |
1089 | | |
1090 | | /* we replace envelope, key in subj_hash has to be updated as well */ |
1091 | 0 | if (m->subj_hash && e->env->real_subj) |
1092 | 0 | mutt_hash_delete(m->subj_hash, e->env->real_subj, e); |
1093 | 0 | mutt_label_hash_remove(m, e); |
1094 | 0 | mutt_env_free(&e->env); |
1095 | 0 | e->env = mutt_rfc822_read_header(msg->fp, e, false, false); |
1096 | 0 | if (m->subj_hash && e->env->real_subj) |
1097 | 0 | mutt_hash_insert(m->subj_hash, e->env->real_subj, e); |
1098 | 0 | mutt_label_hash_add(m, e); |
1099 | | |
1100 | | /* Reattach the private data */ |
1101 | 0 | e->edata = edata; |
1102 | 0 | e->edata_free = pop_edata_free; |
1103 | |
|
1104 | 0 | e->lines = 0; |
1105 | 0 | while (fgets(buf, sizeof(buf), msg->fp) && !feof(msg->fp)) |
1106 | 0 | { |
1107 | 0 | e->lines++; |
1108 | 0 | } |
1109 | |
|
1110 | 0 | e->body->length = ftello(msg->fp) - e->body->offset; |
1111 | | |
1112 | | /* This needs to be done in case this is a multipart message */ |
1113 | 0 | if (!WithCrypto) |
1114 | 0 | e->security = crypt_query(e->body); |
1115 | |
|
1116 | 0 | mutt_clear_error(); |
1117 | 0 | rewind(msg->fp); |
1118 | |
|
1119 | 0 | success = true; |
1120 | |
|
1121 | 0 | cleanup: |
1122 | 0 | buf_pool_release(&path); |
1123 | 0 | return success; |
1124 | 0 | } |
1125 | | |
1126 | | /** |
1127 | | * pop_msg_close - Close an email - Implements MxOps::msg_close() - @ingroup mx_msg_close |
1128 | | * @retval 0 Success |
1129 | | * @retval EOF Error, see errno |
1130 | | */ |
1131 | | static int pop_msg_close(struct Mailbox *m, struct Message *msg) |
1132 | 0 | { |
1133 | 0 | return mutt_file_fclose(&msg->fp); |
1134 | 0 | } |
1135 | | |
1136 | | /** |
1137 | | * pop_msg_save_hcache - Save message to the header cache - Implements MxOps::msg_save_hcache() - @ingroup mx_msg_save_hcache |
1138 | | */ |
1139 | | static int pop_msg_save_hcache(struct Mailbox *m, struct Email *e) |
1140 | 0 | { |
1141 | 0 | int rc = 0; |
1142 | | #ifdef USE_HCACHE |
1143 | | struct PopAccountData *adata = pop_adata_get(m); |
1144 | | struct PopEmailData *edata = e->edata; |
1145 | | struct HeaderCache *hc = pop_hcache_open(adata, mailbox_path(m)); |
1146 | | rc = hcache_store_email(hc, edata->uid, strlen(edata->uid), e, 0); |
1147 | | hcache_close(&hc); |
1148 | | #endif |
1149 | |
|
1150 | 0 | return rc; |
1151 | 0 | } |
1152 | | |
1153 | | /** |
1154 | | * pop_path_probe - Is this a POP Mailbox? - Implements MxOps::path_probe() - @ingroup mx_path_probe |
1155 | | */ |
1156 | | enum MailboxType pop_path_probe(const char *path, const struct stat *st) |
1157 | 0 | { |
1158 | 0 | if (mutt_istr_startswith(path, "pop://")) |
1159 | 0 | return MUTT_POP; |
1160 | | |
1161 | 0 | if (mutt_istr_startswith(path, "pops://")) |
1162 | 0 | return MUTT_POP; |
1163 | | |
1164 | 0 | return MUTT_UNKNOWN; |
1165 | 0 | } |
1166 | | |
1167 | | /** |
1168 | | * pop_path_canon - Canonicalise a Mailbox path - Implements MxOps::path_canon() - @ingroup mx_path_canon |
1169 | | */ |
1170 | | static int pop_path_canon(struct Buffer *path) |
1171 | 0 | { |
1172 | 0 | return 0; |
1173 | 0 | } |
1174 | | |
1175 | | /** |
1176 | | * MxPopOps - POP Mailbox - Implements ::MxOps - @ingroup mx_api |
1177 | | */ |
1178 | | const struct MxOps MxPopOps = { |
1179 | | // clang-format off |
1180 | | .type = MUTT_POP, |
1181 | | .name = "pop", |
1182 | | .is_local = false, |
1183 | | .ac_owns_path = pop_ac_owns_path, |
1184 | | .ac_add = pop_ac_add, |
1185 | | .mbox_open = pop_mbox_open, |
1186 | | .mbox_open_append = NULL, |
1187 | | .mbox_check = pop_mbox_check, |
1188 | | .mbox_check_stats = NULL, |
1189 | | .mbox_sync = pop_mbox_sync, |
1190 | | .mbox_close = pop_mbox_close, |
1191 | | .msg_open = pop_msg_open, |
1192 | | .msg_open_new = NULL, |
1193 | | .msg_commit = NULL, |
1194 | | .msg_close = pop_msg_close, |
1195 | | .msg_padding_size = NULL, |
1196 | | .msg_save_hcache = pop_msg_save_hcache, |
1197 | | .tags_edit = NULL, |
1198 | | .tags_commit = NULL, |
1199 | | .path_probe = pop_path_probe, |
1200 | | .path_canon = pop_path_canon, |
1201 | | .path_is_empty = NULL, |
1202 | | // clang-format on |
1203 | | }; |