2 * libEtPan! -- a mail stuff library
4 * Copyright (C) 2001, 2002 - DINH Viet Hoa
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the libEtPan! project nor the names of its
16 * contributors may be used to endorse or promote products derived
17 * from this software without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
44 #include <sys/types.h>
53 #include "mailmbox_parse.h"
57 http://www.qmail.org/qmail-manual-html/man5/mbox.html
63 /* mbox is a file with a corresponding filename */
65 #define UID_HEADER "X-LibEtPan-UID:"
75 int mailmbox_write_lock(struct mailmbox_folder * folder)
79 if (folder->read_only)
80 return MAILMBOX_ERROR_READONLY;
82 r = maillock_write_lock(folder->filename, folder->fd);
84 return MAILMBOX_NO_ERROR;
86 return MAILMBOX_ERROR_FILE;
89 int mailmbox_write_unlock(struct mailmbox_folder * folder)
93 r = maillock_write_unlock(folder->filename, folder->fd);
95 return MAILMBOX_NO_ERROR;
97 return MAILMBOX_ERROR_FILE;
100 int mailmbox_read_lock(struct mailmbox_folder * folder)
104 r = maillock_read_lock(folder->filename, folder->fd);
106 return MAILMBOX_NO_ERROR;
108 return MAILMBOX_ERROR_FILE;
111 int mailmbox_read_unlock(struct mailmbox_folder * folder)
115 r = maillock_read_unlock(folder->filename, folder->fd);
117 return MAILMBOX_NO_ERROR;
119 return MAILMBOX_ERROR_FILE;
124 map the file into memory.
125 the file must be locked.
128 int mailmbox_map(struct mailmbox_folder * folder)
135 r = stat(folder->filename, &buf);
137 res = MAILMBOX_ERROR_FILE;
141 if (folder->read_only)
142 str = (char *) mmap(0, buf.st_size, PROT_READ,
143 MAP_PRIVATE, folder->fd, 0);
145 str = (char *) mmap(0, buf.st_size, PROT_READ | PROT_WRITE,
146 MAP_SHARED, folder->fd, 0);
147 if (str == MAP_FAILED) {
148 res = MAILMBOX_ERROR_FILE;
152 folder->mapping = str;
153 folder->mapping_size = buf.st_size;
155 return MAILMBOX_NO_ERROR;
165 void mailmbox_unmap(struct mailmbox_folder * folder)
167 munmap(folder->mapping, folder->mapping_size);
168 folder->mapping = NULL;
169 folder->mapping_size = 0;
172 void mailmbox_sync(struct mailmbox_folder * folder)
174 msync(folder->mapping, folder->mapping_size, MS_SYNC);
177 void mailmbox_timestamp(struct mailmbox_folder * folder)
182 r = stat(folder->filename, &buf);
184 folder->mtime = (time_t) -1;
186 folder->mtime = buf.st_mtime;
193 int mailmbox_open(struct mailmbox_folder * folder)
198 if (!folder->read_only) {
200 fd = open(folder->filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
203 if (folder->read_only || (fd < 0)) {
205 fd = open(folder->filename, O_RDONLY);
207 return MAILMBOX_ERROR_FILE_NOT_FOUND;
211 folder->read_only = read_only;
213 return MAILMBOX_NO_ERROR;
220 void mailmbox_close(struct mailmbox_folder * folder)
227 static int mailmbox_validate_lock(struct mailmbox_folder * folder,
228 int (* custom_lock)(struct mailmbox_folder *),
229 int (* custom_unlock)(struct mailmbox_folder *))
235 r = stat(folder->filename, &buf);
237 buf.st_mtime = (time_t) -1;
240 if ((buf.st_mtime != folder->mtime) ||
241 ((size_t) buf.st_size != folder->mapping_size)) {
244 mailmbox_unmap(folder);
245 mailmbox_close(folder);
247 r = mailmbox_open(folder);
248 if (r != MAILMBOX_NO_ERROR) {
253 r = custom_lock(folder);
254 if (r != MAILMBOX_NO_ERROR) {
259 r = mailmbox_map(folder);
260 if (r != MAILMBOX_NO_ERROR) {
265 r = mailmbox_parse(folder);
266 if (r != MAILMBOX_NO_ERROR) {
271 folder->mtime = buf.st_mtime;
273 return MAILMBOX_NO_ERROR;
276 r = custom_lock(folder);
277 if (r != MAILMBOX_NO_ERROR) {
283 return MAILMBOX_NO_ERROR;
286 custom_unlock(folder);
292 int mailmbox_validate_write_lock(struct mailmbox_folder * folder)
294 return mailmbox_validate_lock(folder,
296 mailmbox_write_unlock);
300 int mailmbox_validate_read_lock(struct mailmbox_folder * folder)
302 return mailmbox_validate_lock(folder,
304 mailmbox_read_unlock);
308 /* ********************************************************************** */
309 /* append messages */
311 #define MAX_FROM_LINE_SIZE 256
313 static inline size_t get_line(char * line, size_t length,
314 char ** pnext_line, size_t * pcount)
324 if (* line == '\r') {
330 if (* line == '\n') {
339 else if (* line == '\n') {
361 TODO : should strip \r\n if any
362 see also in write_fixed_line
365 static inline size_t get_fixed_line_size(char * line, size_t length,
366 char ** pnext_line, size_t * pcount,
367 size_t * pfixed_count)
373 if (!get_line(line, length, &next_line, &count))
378 if (line[0] == 'F') {
379 if (strncmp(line, "From ", 5) == 0)
384 * pnext_line = next_line;
386 * pfixed_count = fixed_count;
391 static size_t get_fixed_message_size(char * message, size_t size,
392 guint uid, int force_no_uid)
416 if (cur_token + strlen(UID_HEADER) <= size) {
417 if (message[cur_token] == 'X') {
418 if (strncasecmp(message + cur_token, UID_HEADER,
419 strlen(UID_HEADER)) == 0) {
425 r = mailimf_ignore_field_parse(message, size, &cur_token);
427 case MAILIMF_NO_ERROR:
429 fixed_size += cur_token - begin;
431 case MAILIMF_ERROR_PARSE:
441 fixed_size += strlen(UID_HEADER " \r\n");
444 while (tmp_uid >= 10) {
453 left = size - cur_token;
454 next = message + cur_token;
461 if (!get_fixed_line_size(cur, left, &next, &count, &fixed_count))
464 fixed_size += fixed_count;
471 static inline char * write_fixed_line(char * str,
472 char * line, size_t length,
473 char ** pnext_line, size_t * pcount)
478 if (!get_line(line, length, &next_line, &count))
482 if (line[0] == 'F') {
483 if (strncmp(line, "From ", 5) == 0) {
490 memcpy(str, line, count);
492 * pnext_line = next_line;
499 static char * write_fixed_message(char * str,
500 char * message, size_t size,
501 guint uid, int force_no_uid)
524 if (cur_token + strlen(UID_HEADER) <= size) {
525 if (message[cur_token] == 'X') {
526 if (strncasecmp(message + cur_token, UID_HEADER,
527 strlen(UID_HEADER)) == 0) {
533 r = mailimf_ignore_field_parse(message, size, &cur_token);
535 case MAILIMF_NO_ERROR:
537 memcpy(str, message + begin, cur_token - begin);
538 str += cur_token - begin;
541 case MAILIMF_ERROR_PARSE:
551 memcpy(str, UID_HEADER " ", strlen(UID_HEADER " "));
552 str += strlen(UID_HEADER " ");
553 numlen = snprintf(str, 20, "%i\r\n", uid);
559 cur_src = message + cur_token;
560 left = size - cur_token;
565 str = write_fixed_line(str, cur_src, left, &next, &count);
574 #define DEFAULT_FROM_LINE "From - Wed Jun 30 21:49:08 1993\n"
577 mailmbox_append_message_list_no_lock(struct mailmbox_folder * folder,
582 char from_line[MAX_FROM_LINE_SIZE] = DEFAULT_FROM_LINE;
594 if (folder->read_only) {
595 res = MAILMBOX_ERROR_READONLY;
600 from_size = strlen(DEFAULT_FROM_LINE);
601 if (localtime_r(&date, &time_info) != NULL)
602 from_size = strftime(from_line, MAX_FROM_LINE_SIZE, "From - %c\n", &time_info);
604 maxuid = /* */ folder->max_uid;
607 for(i = 0 ; i < append_tab->len ; i ++) {
608 struct mailmbox_append_info * info;
610 info = carray_get(append_tab, i);
611 extra_size += from_size;
612 extra_size += get_fixed_message_size(info->message, info->size,
613 folder->max_uid + i + 1,
615 extra_size += 2; /* CR LF */
618 left = folder->mapping_size;
621 if (folder->mapping[left - 1] == '\n') {
625 else if (folder->mapping[left - 1] == '\r') {
635 old_size = folder->mapping_size;
636 mailmbox_unmap(folder);
640 extra_size += (2 - crlf_count) * 2;
643 r = ftruncate(folder->fd, extra_size + old_size);
645 mailmbox_map(folder);
646 res = MAILMBOX_ERROR_FILE;
650 r = mailmbox_map(folder);
652 ftruncate(folder->fd, old_size);
653 return MAILMBOX_ERROR_FILE;
656 str = folder->mapping + old_size;
659 for(i = 0 ; i < 2 - crlf_count ; i ++) {
667 for(i = 0 ; i < append_tab->len ; i ++) {
668 struct mailmbox_append_info * info;
670 info = carray_get(append_tab, i);
672 memcpy(str, from_line, from_size);
674 str += strlen(from_line);
676 str = write_fixed_message(str, info->message, info->size,
677 folder->max_uid + i + 1,
686 folder->max_uid += append_tab->len;
688 return MAILMBOX_NO_ERROR;
695 mailmbox_append_message_list(struct mailmbox_folder * folder,
702 r = mailmbox_validate_write_lock(folder);
703 if (r != MAILMBOX_NO_ERROR) {
708 r = mailmbox_expunge_no_lock(folder);
709 if (r != MAILMBOX_NO_ERROR) {
714 cur_token = folder->mapping_size;
716 r = mailmbox_append_message_list_no_lock(folder, append_tab);
717 if (r != MAILMBOX_NO_ERROR) {
722 mailmbox_sync(folder);
724 r = mailmbox_parse_additionnal(folder, &cur_token);
725 if (r != MAILMBOX_NO_ERROR) {
730 mailmbox_timestamp(folder);
732 mailmbox_write_unlock(folder);
734 return MAILMBOX_NO_ERROR;
737 mailmbox_write_unlock(folder);
743 mailmbox_append_message(struct mailmbox_folder * folder,
744 char * data, size_t len)
747 struct mailmbox_append_info * append_info;
753 res = MAILMBOX_ERROR_MEMORY;
757 append_info = mailmbox_append_info_new(data, len);
758 if (append_info == NULL) {
759 res = MAILMBOX_ERROR_MEMORY;
763 r = carray_add(tab, append_info, NULL);
765 res = MAILMBOX_ERROR_MEMORY;
766 goto free_append_info;
769 r = mailmbox_append_message_list(folder, tab);
771 mailmbox_append_info_free(append_info);
777 mailmbox_append_info_free(append_info);
784 /* ********************************************************************** */
786 int mailmbox_fetch_msg_no_lock(struct mailmbox_folder * folder,
787 guint num, char ** result,
790 struct mailmbox_msg_info * info;
796 key.data = (char *) #
797 key.len = sizeof(num);
799 r = chash_get(folder->hash, &key, &data);
801 res = MAILMBOX_ERROR_MSG_NOT_FOUND;
805 info = (struct mailmbox_msg_info *) data.data;
808 res = MAILMBOX_ERROR_MSG_NOT_FOUND;
812 * result = folder->mapping + info->headers;
813 * result_len = info->size - info->start_len;
815 return MAILMBOX_NO_ERROR;
821 int mailmbox_fetch_msg_headers_no_lock(struct mailmbox_folder * folder,
822 guint num, char ** result,
825 struct mailmbox_msg_info * info;
831 key.data = (char *) #
832 key.len = sizeof(num);
834 r = chash_get(folder->hash, &key, &data);
836 res = MAILMBOX_ERROR_MSG_NOT_FOUND;
840 info = (struct mailmbox_msg_info *) data.data;
843 res = MAILMBOX_ERROR_MSG_NOT_FOUND;
847 * result = folder->mapping + info->headers;
848 * result_len = info->headers_len;
850 return MAILMBOX_NO_ERROR;
856 int mailmbox_fetch_msg(struct mailmbox_folder * folder,
857 guint num, char ** result,
868 r = mailmbox_validate_read_lock(folder);
869 if (r != MAILMBOX_NO_ERROR) {
874 r = mailmbox_fetch_msg_no_lock(folder, num, &data, &len);
875 if (r != MAILMBOX_NO_ERROR) {
880 /* size with no uid */
881 fixed_size = get_fixed_message_size(data, len, 0, 1 /* force no uid */);
883 new_data = malloc(fixed_size + 1);
884 if (new_data == NULL) {
885 res = MAILMBOX_ERROR_MEMORY;
889 end = write_fixed_message(new_data, data, len, 0, 1 /* force no uid */);
893 * result_len = fixed_size;
895 mailmbox_read_unlock(folder);
897 return MAILMBOX_NO_ERROR;
900 mailmbox_read_unlock(folder);
905 int mailmbox_fetch_msg_headers(struct mailmbox_folder * folder,
906 guint num, char ** result,
917 r = mailmbox_validate_read_lock(folder);
918 if (r != MAILMBOX_NO_ERROR) {
923 r = mailmbox_fetch_msg_headers_no_lock(folder, num, &data, &len);
924 if (r != MAILMBOX_NO_ERROR) {
929 /* size with no uid */
930 fixed_size = get_fixed_message_size(data, len, 0, 1 /* force no uid */);
932 new_data = malloc(fixed_size + 1);
933 if (new_data == NULL) {
934 res = MAILMBOX_ERROR_MEMORY;
938 end = write_fixed_message(new_data, data, len, 0, 1 /* force no uid */);
942 * result_len = fixed_size;
944 mailmbox_read_unlock(folder);
946 return MAILMBOX_NO_ERROR;
949 mailmbox_read_unlock(folder);
954 void mailmbox_fetch_result_free(char * msg)
960 int mailmbox_copy_msg_list(struct mailmbox_folder * dest_folder,
961 struct mailmbox_folder * src_folder,
969 r = mailmbox_validate_read_lock(src_folder);
970 if (r != MAILMBOX_NO_ERROR) {
975 append_tab = carray_new(tab->len);
976 if (append_tab == NULL) {
977 res = MAILMBOX_ERROR_MEMORY;
981 for(i = 0 ; i < tab->len ; i ++) {
982 struct mailmbox_append_info * append_info;
987 uid = * ((guint *) carray_get(tab, i));
989 r = mailmbox_fetch_msg_no_lock(src_folder, uid, &data, &len);
990 if (r != MAILMBOX_NO_ERROR) {
995 append_info = mailmbox_append_info_new(data, len);
996 if (append_info == NULL) {
997 res = MAILMBOX_ERROR_MEMORY;
1001 r = carray_add(append_tab, append_info, NULL);
1003 mailmbox_append_info_free(append_info);
1004 res = MAILMBOX_ERROR_MEMORY;
1009 r = mailmbox_append_message_list(dest_folder, append_tab);
1010 if (r != MAILMBOX_NO_ERROR) {
1015 for(i = 0 ; i < append_tab->len ; i ++) {
1016 struct mailmbox_append_info * append_info;
1018 append_info = carray_get(append_tab, i);
1019 mailmbox_append_info_free(append_info);
1021 carray_free(append_tab);
1023 mailmbox_read_unlock(src_folder);
1025 return MAILMBOX_NO_ERROR;
1028 for(i = 0 ; i < append_tab->len ; i ++) {
1029 struct mailmbox_append_info * append_info;
1031 append_info = carray_get(append_tab, i);
1032 mailmbox_append_info_free(append_info);
1034 carray_free(append_tab);
1036 mailmbox_read_unlock(src_folder);
1041 int mailmbox_copy_msg(struct mailmbox_folder * dest_folder,
1042 struct mailmbox_folder * src_folder,
1050 tab = carray_new(1);
1052 res = MAILMBOX_ERROR_MEMORY;
1056 puid = malloc(sizeof(* puid));
1058 res = MAILMBOX_ERROR_MEMORY;
1063 r = mailmbox_copy_msg_list(dest_folder, src_folder, tab);
1073 static int mailmbox_expunge_to_file_no_lock(char * dest_filename, int dest_fd,
1074 struct mailmbox_folder * folder,
1075 size_t * result_size)
1085 for(i = 0 ; i < folder->tab->len ; i ++) {
1086 struct mailmbox_msg_info * info;
1088 info = carray_get(folder->tab, i);
1090 if (!info->deleted) {
1091 size += info->size + info->padding;
1093 if (!folder->no_uid) {
1094 if (!info->written_uid) {
1097 size += strlen(UID_HEADER " \r\n");
1110 r = ftruncate(dest_fd, size);
1112 res = MAILMBOX_ERROR_FILE;
1116 dest = (char *) mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, dest_fd, 0);
1117 if (dest == MAP_FAILED) {
1118 res = MAILMBOX_ERROR_FILE;
1123 for(i = 0 ; i < folder->tab->len ; i ++) {
1124 struct mailmbox_msg_info * info;
1126 info = carray_get(folder->tab, i);
1128 if (!info->deleted) {
1129 memcpy(dest + cur_offset, folder->mapping + info->start,
1130 info->headers_len + info->start_len);
1131 cur_offset += info->headers_len + info->start_len;
1133 if (!folder->no_uid) {
1134 if (!info->written_uid) {
1137 memcpy(dest + cur_offset, UID_HEADER " ", strlen(UID_HEADER " "));
1138 cur_offset += strlen(UID_HEADER " ");
1139 numlen = snprintf(dest + cur_offset, size - cur_offset,
1140 "%i\r\n", info->uid);
1141 cur_offset += numlen;
1145 memcpy(dest + cur_offset,
1146 folder->mapping + info->headers + info->headers_len,
1147 info->size - (info->start_len + info->headers_len)
1150 cur_offset += info->size - (info->start_len + info->headers_len)
1158 * result_size = size;
1160 return MAILMBOX_NO_ERROR;
1166 int mailmbox_expunge_no_lock(struct mailmbox_folder * folder)
1168 char tmpfile[PATH_MAX];
1174 if (folder->read_only)
1175 return MAILMBOX_ERROR_READONLY;
1177 if (((folder->written_uid >= folder->max_uid) || folder->no_uid) &&
1178 (!folder->changed)) {
1179 /* no need to expunge */
1180 return MAILMBOX_NO_ERROR;
1183 snprintf(tmpfile, PATH_MAX, "%sXXXXXX", folder->filename);
1184 dest_fd = mkstemp(tmpfile);
1187 res = MAILMBOX_ERROR_FILE;
1191 r = mailmbox_expunge_to_file_no_lock(tmpfile, dest_fd,
1193 if (r != MAILMBOX_NO_ERROR) {
1200 r = rename(tmpfile, folder->filename);
1206 mailmbox_unmap(folder);
1207 mailmbox_close(folder);
1209 r = mailmbox_open(folder);
1210 if (r != MAILMBOX_NO_ERROR) {
1215 r = mailmbox_map(folder);
1216 if (r != MAILMBOX_NO_ERROR) {
1221 r = mailmbox_parse(folder);
1222 if (r != MAILMBOX_NO_ERROR) {
1227 mailmbox_timestamp(folder);
1229 folder->changed = FALSE;
1230 folder->deleted_count = 0;
1232 return MAILMBOX_NO_ERROR;
1241 int mailmbox_expunge(struct mailmbox_folder * folder)
1246 r = mailmbox_validate_write_lock(folder);
1247 if (r != MAILMBOX_NO_ERROR) {
1252 r = mailmbox_expunge_no_lock(folder);
1255 mailmbox_write_unlock(folder);
1260 int mailmbox_delete_msg(struct mailmbox_folder * folder, guint uid)
1262 struct mailmbox_msg_info * info;
1268 if (folder->read_only) {
1269 res = MAILMBOX_ERROR_READONLY;
1273 key.data = (char *) &uid;
1274 key.len = sizeof(uid);
1276 r = chash_get(folder->hash, &key, &data);
1278 res = MAILMBOX_ERROR_MSG_NOT_FOUND;
1282 info = (struct mailmbox_msg_info *) data.data;
1284 if (info->deleted) {
1285 res = MAILMBOX_ERROR_MSG_NOT_FOUND;
1289 info->deleted = TRUE;
1290 folder->changed = TRUE;
1291 folder->deleted_count ++;
1293 return MAILMBOX_NO_ERROR;
1313 int mailmbox_init(char * filename,
1316 guint default_written_uid,
1317 struct mailmbox_folder ** result_folder)
1319 struct mailmbox_folder * folder;
1323 folder = mailmbox_folder_new(filename);
1324 if (folder == NULL) {
1325 res = MAILMBOX_ERROR_MEMORY;
1328 folder->no_uid = force_no_uid;
1329 folder->read_only = force_readonly;
1330 folder->written_uid = default_written_uid;
1332 folder->changed = FALSE;
1333 folder->deleted_count = 0;
1335 r = mailmbox_open(folder);
1336 if (r != MAILMBOX_NO_ERROR) {
1341 r = mailmbox_map(folder);
1342 if (r != MAILMBOX_NO_ERROR) {
1347 r = mailmbox_validate_read_lock(folder);
1348 if (r != MAILMBOX_NO_ERROR) {
1353 mailmbox_read_unlock(folder);
1355 * result_folder = folder;
1357 return MAILMBOX_NO_ERROR;
1360 mailmbox_unmap(folder);
1362 mailmbox_close(folder);
1364 mailmbox_folder_free(folder);
1379 void mailmbox_done(struct mailmbox_folder * folder)
1381 if (!folder->read_only)
1382 mailmbox_expunge(folder);
1384 mailmbox_unmap(folder);
1385 mailmbox_close(folder);
1387 mailmbox_folder_free(folder);