2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2023 the Claws Mail team and Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "claws-features.h"
26 #include <glib/gi18n.h>
33 #include "procheader.h"
36 #include "prefs_common.h"
40 #include "file-utils.h"
44 static gchar monthstr
[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
46 typedef char *(*getlinefunc
) (char *, size_t, void *);
47 typedef int (*peekcharfunc
) (void *);
48 typedef int (*getcharfunc
) (void *);
49 typedef gint (*get_one_field_func
) (gchar
**, void *, HeaderEntry
[]);
51 static gint
string_get_one_field(gchar
**buf
, char **str
,
52 HeaderEntry hentry
[]);
54 static char *string_getline(char *buf
, size_t len
, char **str
);
55 static int string_peekchar(char **str
);
56 static int file_peekchar(FILE *fp
);
57 static gint
generic_get_one_field(gchar
**bufptr
, void *data
,
60 peekcharfunc peekchar
,
62 static MsgInfo
*parse_stream(void *data
, gboolean isstring
, MsgFlags flags
,
63 gboolean full
, gboolean decrypted
);
66 gint
procheader_get_one_field(gchar
**buf
, FILE *fp
,
69 return generic_get_one_field(buf
, fp
, hentry
,
70 (getlinefunc
)fgets_crlf
, (peekcharfunc
)file_peekchar
,
74 static gint
string_get_one_field(gchar
**buf
, char **str
,
77 return generic_get_one_field(buf
, str
, hentry
,
78 (getlinefunc
)string_getline
,
79 (peekcharfunc
)string_peekchar
,
83 gboolean
procheader_skip_headers(FILE *fp
)
85 gchar
*buf
= g_malloc(BUFFSIZE
);
87 if (fgets_crlf(buf
, BUFFSIZE
- 1, fp
) == NULL
) {
91 if (buf
[0] == '\r' || buf
[0] == '\n') {
101 static char *string_getline(char *buf
, size_t len
, char **str
)
103 gboolean is_cr
= FALSE
;
104 gboolean last_was_cr
= FALSE
;
109 for (; **str
&& len
> 1; --len
) {
110 is_cr
= (**str
== '\r');
111 if ((*buf
++ = *(*str
)++) == '\n') {
127 static int string_peekchar(char **str
)
132 static int file_peekchar(FILE *fp
)
134 return ungetc(getc(fp
), fp
);
137 static gint
generic_get_one_field(gchar
**bufptr
, void *data
,
139 getlinefunc getline
, peekcharfunc peekchar
,
142 /* returns -1 in case of failure of any kind, whatever it's a parsing error
143 or an allocation error. if returns -1, *bufptr is always NULL, and vice-versa,
144 and if returning 0 (OK), *bufptr is always non-NULL, so callers just have to
145 test the return value
149 HeaderEntry
*hp
= NULL
;
153 cm_return_val_if_fail(bufptr
!= NULL
, -1);
158 if (hentry
!= NULL
) {
159 /* skip non-required headers */
160 /* and get hentry header line */
163 if (getline(buf
, len
, data
) == NULL
) {
164 debug_print("generic_get_one_field: getline\n");
169 if (buf
[0] == '\r' || buf
[0] == '\n') {
170 debug_print("generic_get_one_field: empty line\n");
175 } while (buf
[0] == ' ' || buf
[0] == '\t');
177 for (hp
= hentry
, hnum
= 0; hp
->name
!= NULL
;
179 if (!g_ascii_strncasecmp(hp
->name
, buf
,
183 } while (hp
->name
== NULL
);
185 /* read first line */
186 if (getline(buf
, len
, data
) == NULL
) {
187 debug_print("generic_get_one_field: getline\n");
192 if (buf
[0] == '\r' || buf
[0] == '\n') {
193 debug_print("generic_get_one_field: empty line\n");
199 /* reduce initial buffer to its useful part */
201 buf
= g_realloc(buf
, len
);
203 debug_print("generic_get_one_field: reallocation error\n");
210 nexthead
= peekchar(data
);
211 /* ([*WSP CRLF] 1*WSP) */
212 if (nexthead
== ' ' || nexthead
== '\t') {
217 gboolean skiptab
= (nexthead
== '\t');
218 /* trim previous trailing \n if requesting one header or
219 * unfolding was requested */
220 if ((!hentry
&& unfold
) || (hp
&& hp
->unfold
))
223 buflen
= strlen(buf
);
226 tmpbuf
= g_malloc(BUFFSIZE
);
228 if (getline(tmpbuf
, BUFFSIZE
, data
) == NULL
) {
232 tmplen
= strlen(tmpbuf
)+1;
234 /* extend initial buffer and concatenate next line */
236 buf
= g_realloc(buf
, len
);
238 debug_print("generic_get_one_field: reallocation error\n");
243 memcpy(buf
+buflen
, tmpbuf
, tmplen
);
245 if (skiptab
) { /* replace tab with space */
246 *(buf
+ buflen
) = ' ';
249 /* remove trailing new line */
260 gint
procheader_get_one_field_asis(gchar
**buf
, FILE *fp
)
262 return generic_get_one_field(buf
, fp
, NULL
,
263 (getlinefunc
)fgets_crlf
,
264 (peekcharfunc
)file_peekchar
,
268 GPtrArray
*procheader_get_header_array(FILE *fp
)
274 cm_return_val_if_fail(fp
!= NULL
, NULL
);
276 headers
= g_ptr_array_new();
278 while (procheader_get_one_field(&buf
, fp
, NULL
) != -1) {
279 if ((header
= procheader_parse_header(buf
)) != NULL
)
280 g_ptr_array_add(headers
, header
);
288 void procheader_header_array_destroy(GPtrArray
*harray
)
293 cm_return_if_fail(harray
!= NULL
);
295 for (i
= 0; i
< harray
->len
; i
++) {
296 header
= g_ptr_array_index(harray
, i
);
297 procheader_header_free(header
);
300 g_ptr_array_free(harray
, TRUE
);
303 void procheader_header_free(Header
*header
)
307 g_free(header
->name
);
308 g_free(header
->body
);
313 tests whether two headers' names are equal
314 remove the trailing ':' or ' ' before comparing
317 gboolean
procheader_headername_equal(char * hdr1
, char * hdr2
)
324 if (hdr1
[len1
- 1] == ':')
326 if (hdr2
[len2
- 1] == ':')
331 return (g_ascii_strncasecmp(hdr1
, hdr2
, len1
) == 0);
335 parse headers, for example :
336 From: dinh@enseirb.fr becomes :
337 header->name = "From:"
338 header->body = "dinh@enseirb.fr"
340 static gboolean
header_is_addr_field(const gchar
*hdr
)
342 static char *addr_headers
[] = {
349 "Followup-and-Reply-To:",
350 "Disposition-Notification-To:",
351 "Return-Receipt-To:",
358 for (i
= 0; addr_headers
[i
] != NULL
; i
++)
359 if (!strcasecmp(hdr
, addr_headers
[i
]))
365 Header
* procheader_parse_header(gchar
* buf
)
369 gboolean addr_field
= FALSE
;
371 cm_return_val_if_fail(buf
!= NULL
, NULL
);
373 if ((*buf
== ':') || (*buf
== ' '))
376 for (p
= buf
; *p
; p
++) {
377 if ((*p
== ':') || (*p
== ' ')) {
378 header
= g_new(Header
, 1);
379 header
->name
= g_strndup(buf
, p
- buf
+ 1);
380 addr_field
= header_is_addr_field(header
->name
);
382 while (*p
== ' ' || *p
== '\t') p
++;
383 header
->body
= conv_unmime_header(p
, NULL
, addr_field
);
390 void procheader_get_header_fields(FILE *fp
, HeaderEntry hentry
[])
397 if (hentry
== NULL
) return;
399 while ((hnum
= procheader_get_one_field(&buf
, fp
, hentry
)) != -1) {
402 p
= buf
+ strlen(hp
->name
);
403 while (*p
== ' ' || *p
== '\t') p
++;
405 if (hp
->body
== NULL
)
406 hp
->body
= g_strdup(p
);
407 else if (procheader_headername_equal(hp
->name
, "To") ||
408 procheader_headername_equal(hp
->name
, "Cc")) {
409 gchar
*tp
= hp
->body
;
410 hp
->body
= g_strconcat(tp
, ", ", p
, NULL
);
418 MsgInfo
*procheader_parse_file(const gchar
*file
, MsgFlags flags
,
419 gboolean full
, gboolean decrypted
)
425 GError
*error
= NULL
;
433 f
= g_file_new_for_path(file
);
434 fi
= g_file_query_info(f
, "standard::size,standard::type,time::modified",
435 G_FILE_QUERY_INFO_NONE
, NULL
, &error
);
437 g_warning(error
->message
);
442 if (g_stat(file
, &s
) < 0) {
443 FILE_OP_ERROR(file
, "stat");
449 if (g_file_info_get_file_type(fi
) != G_FILE_TYPE_REGULAR
) {
455 if (!S_ISREG(s
.st_mode
))
459 if ((fp
= claws_fopen(file
, "rb")) == NULL
) {
460 FILE_OP_ERROR(file
, "claws_fopen");
464 msginfo
= procheader_parse_stream(fp
, flags
, full
, decrypted
);
469 msginfo
->size
= g_file_info_get_size(fi
);
470 g_file_info_get_modification_time(fi
, &tv
);
471 msginfo
->mtime
= tv
.tv_sec
;
473 msginfo
->size
= s
.st_size
;
474 msginfo
->mtime
= s
.st_mtime
;
486 MsgInfo
*procheader_parse_str(const gchar
*str
, MsgFlags flags
, gboolean full
,
489 return parse_stream(&str
, TRUE
, flags
, full
, decrypted
);
507 H_SC_PLANNED_DOWNLOAD
,
511 H_DISPOSITION_NOTIFICATION_TO
,
513 H_SC_PARTIALLY_RETRIEVED
,
525 static HeaderEntry hentry_full
[] = {
526 {"Date:", NULL
, FALSE
},
527 {"From:", NULL
, TRUE
},
530 {"Newsgroups:", NULL
, TRUE
},
531 {"Subject:", NULL
, TRUE
},
532 {"Message-ID:", NULL
, FALSE
},
533 {"References:", NULL
, FALSE
},
534 {"In-Reply-To:", NULL
, FALSE
},
535 {"Content-Type:", NULL
, FALSE
},
536 {"Seen:", NULL
, FALSE
},
537 {"Status:", NULL
, FALSE
},
538 {"From ", NULL
, FALSE
},
539 {"SC-Marked-For-Download:", NULL
, FALSE
},
540 {"SC-Message-Size:", NULL
, FALSE
},
541 {"Face:", NULL
, FALSE
},
542 {"X-Face:", NULL
, FALSE
},
543 {"Disposition-Notification-To:", NULL
, FALSE
},
544 {"Return-Receipt-To:", NULL
, FALSE
},
545 {"SC-Partially-Retrieved:", NULL
, FALSE
},
546 {"SC-Account-Server:", NULL
, FALSE
},
547 {"SC-Account-Login:",NULL
, FALSE
},
548 {"List-Post:", NULL
, TRUE
},
549 {"List-Subscribe:", NULL
, TRUE
},
550 {"List-Unsubscribe:",NULL
, TRUE
},
551 {"List-Help:", NULL
, TRUE
},
552 {"List-Archive:", NULL
, TRUE
},
553 {"List-Owner:", NULL
, TRUE
},
554 {"Resent-From:", NULL
, TRUE
},
555 {NULL
, NULL
, FALSE
}};
557 static HeaderEntry hentry_short
[] = {
558 {"Date:", NULL
, FALSE
},
559 {"From:", NULL
, TRUE
},
562 {"Newsgroups:", NULL
, TRUE
},
563 {"Subject:", NULL
, TRUE
},
564 {"Message-ID:", NULL
, FALSE
},
565 {"References:", NULL
, FALSE
},
566 {"In-Reply-To:", NULL
, FALSE
},
567 {"Content-Type:", NULL
, FALSE
},
568 {"Seen:", NULL
, FALSE
},
569 {"Status:", NULL
, FALSE
},
570 {"From ", NULL
, FALSE
},
571 {"SC-Marked-For-Download:", NULL
, FALSE
},
572 {"SC-Message-Size:",NULL
, FALSE
},
573 {NULL
, NULL
, FALSE
}};
575 static HeaderEntry
* procheader_get_headernames(gboolean full
)
577 return full
? hentry_full
: hentry_short
;
580 MsgInfo
*procheader_parse_stream(FILE *fp
, MsgFlags flags
, gboolean full
,
583 return parse_stream(fp
, FALSE
, flags
, full
, decrypted
);
586 static gboolean
avatar_from_some_face(gpointer source
, gpointer userdata
)
588 AvatarCaptureData
*acd
= (AvatarCaptureData
*)source
;
590 if (*(acd
->content
) == '\0') /* won't be null, but may be empty */
593 if (!strcmp(acd
->header
, hentry_full
[H_FACE
].name
)) {
594 debug_print("avatar_from_some_face: found 'Face' header\n");
595 procmsg_msginfo_add_avatar(acd
->msginfo
, AVATAR_FACE
, acd
->content
);
598 else if (!strcmp(acd
->header
, hentry_full
[H_X_FACE
].name
)) {
599 debug_print("avatar_from_some_face: found 'X-Face' header\n");
600 procmsg_msginfo_add_avatar(acd
->msginfo
, AVATAR_XFACE
, acd
->content
);
606 static gulong avatar_hook_id
= HOOK_NONE
;
608 static MsgInfo
*parse_stream(void *data
, gboolean isstring
, MsgFlags flags
,
609 gboolean full
, gboolean decrypted
)
617 void *orig_data
= data
;
619 get_one_field_func get_one_field
=
620 isstring
? (get_one_field_func
)string_get_one_field
621 : (get_one_field_func
)procheader_get_one_field
;
623 hentry
= procheader_get_headernames(full
);
625 if (MSG_IS_QUEUED(flags
) || MSG_IS_DRAFT(flags
)) {
626 while (get_one_field(&buf
, data
, NULL
) != -1) {
627 if ((!strncmp(buf
, "X-Claws-End-Special-Headers: 1",
628 strlen("X-Claws-End-Special-Headers:"))) ||
629 (!strncmp(buf
, "X-Sylpheed-End-Special-Headers: 1",
630 strlen("X-Sylpheed-End-Special-Headers:")))) {
635 /* from other mailers */
636 if (!strncmp(buf
, "Date: ", 6)
637 || !strncmp(buf
, "To: ", 4)
638 || !strncmp(buf
, "From: ", 6)
639 || !strncmp(buf
, "Subject: ", 9)) {
643 rewind((FILE *)data
);
653 msginfo
= procmsg_msginfo_new();
655 if (flags
.tmp_flags
|| flags
.perm_flags
)
656 msginfo
->flags
= flags
;
658 MSG_SET_PERM_FLAGS(msginfo
->flags
, MSG_NEW
| MSG_UNREAD
);
660 msginfo
->inreplyto
= NULL
;
662 if (avatar_hook_id
== HOOK_NONE
&&
663 (prefs_common
.enable_avatars
&& (AVATARS_ENABLE_CAPTURE
|| AVATARS_ENABLE_RENDER
))) {
664 avatar_hook_id
= hooks_register_hook(AVATAR_HEADER_UPDATE_HOOKLIST
,
665 avatar_from_some_face
, NULL
);
666 } else if (avatar_hook_id
!= HOOK_NONE
&&
667 !(prefs_common
.enable_avatars
&& AVATARS_ENABLE_CAPTURE
)) {
668 hooks_unregister_hook(AVATAR_HEADER_UPDATE_HOOKLIST
, avatar_hook_id
);
669 avatar_hook_id
= HOOK_NONE
;
672 while ((hnum
= get_one_field(&buf
, data
, hentry
)) != -1) {
673 hp
= buf
+ strlen(hentry
[hnum
].name
);
674 while (*hp
== ' ' || *hp
== '\t') hp
++;
678 if (msginfo
->date
) break;
680 procheader_date_parse(NULL
, hp
, 0);
681 if (g_utf8_validate(hp
, -1, NULL
)) {
682 msginfo
->date
= g_strdup(hp
);
684 gchar
*utf
= conv_codeset_strdup(
686 conv_get_locale_charset_str_no_utf8(),
689 !g_utf8_validate(utf
, -1, NULL
)) {
691 utf
= g_malloc(strlen(buf
)*2+1);
692 conv_localetodisp(utf
,
699 if (msginfo
->from
) break;
700 msginfo
->from
= conv_unmime_header(hp
, NULL
, TRUE
);
701 msginfo
->fromname
= procheader_get_fromname(msginfo
->from
);
702 remove_return(msginfo
->from
);
703 remove_return(msginfo
->fromname
);
706 tmp
= conv_unmime_header(hp
, NULL
, TRUE
);
711 g_strconcat(p
, ", ", tmp
, NULL
);
714 msginfo
->to
= g_strdup(tmp
);
718 tmp
= conv_unmime_header(hp
, NULL
, TRUE
);
723 g_strconcat(p
, ", ", tmp
, NULL
);
726 msginfo
->cc
= g_strdup(tmp
);
730 if (msginfo
->newsgroups
) {
731 p
= msginfo
->newsgroups
;
732 msginfo
->newsgroups
=
733 g_strconcat(p
, ",", hp
, NULL
);
736 msginfo
->newsgroups
= g_strdup(hp
);
739 if (msginfo
->subject
) break;
740 msginfo
->subject
= conv_unmime_header(hp
, NULL
, FALSE
);
741 unfold_line(msginfo
->subject
);
744 if (msginfo
->msgid
) break;
746 extract_parenthesis(hp
, '<', '>');
748 msginfo
->msgid
= g_strdup(hp
);
751 msginfo
->references
=
752 references_list_prepend(msginfo
->references
,
756 if (msginfo
->inreplyto
) break;
758 eliminate_parenthesis(hp
, '(', ')');
759 if ((p
= strrchr(hp
, '<')) != NULL
&&
760 strchr(p
+ 1, '>') != NULL
) {
761 extract_parenthesis(p
, '<', '>');
764 msginfo
->inreplyto
= g_strdup(p
);
768 if (!g_ascii_strncasecmp(hp
, "multipart/", 10))
769 MSG_SET_TMP_FLAGS(msginfo
->flags
, MSG_MULTIPART
);
771 case H_DISPOSITION_NOTIFICATION_TO
:
772 if (!msginfo
->extradata
)
773 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
774 if (msginfo
->extradata
->dispositionnotificationto
) break;
775 msginfo
->extradata
->dispositionnotificationto
= g_strdup(hp
);
777 case H_RETURN_RECEIPT_TO
:
778 if (!msginfo
->extradata
)
779 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
780 if (msginfo
->extradata
->returnreceiptto
) break;
781 msginfo
->extradata
->returnreceiptto
= g_strdup(hp
);
783 /* partial download infos */
784 case H_SC_PARTIALLY_RETRIEVED
:
785 if (!msginfo
->extradata
)
786 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
787 if (msginfo
->extradata
->partial_recv
) break;
788 msginfo
->extradata
->partial_recv
= g_strdup(hp
);
790 case H_SC_ACCOUNT_SERVER
:
791 if (!msginfo
->extradata
)
792 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
793 if (msginfo
->extradata
->account_server
) break;
794 msginfo
->extradata
->account_server
= g_strdup(hp
);
796 case H_SC_ACCOUNT_LOGIN
:
797 if (!msginfo
->extradata
)
798 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
799 if (msginfo
->extradata
->account_login
) break;
800 msginfo
->extradata
->account_login
= g_strdup(hp
);
802 case H_SC_MESSAGE_SIZE
:
803 if (msginfo
->total_size
) break;
804 msginfo
->total_size
= atoi(hp
);
806 case H_SC_PLANNED_DOWNLOAD
:
807 msginfo
->planned_download
= atoi(hp
);
809 /* end partial download infos */
811 if (msginfo
->fromspace
) break;
812 msginfo
->fromspace
= g_strdup(hp
);
813 remove_return(msginfo
->fromspace
);
817 if (!msginfo
->extradata
)
818 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
819 if (msginfo
->extradata
->list_post
) break;
820 msginfo
->extradata
->list_post
= g_strdup(hp
);
822 case H_LIST_SUBSCRIBE
:
823 if (!msginfo
->extradata
)
824 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
825 if (msginfo
->extradata
->list_subscribe
) break;
826 msginfo
->extradata
->list_subscribe
= g_strdup(hp
);
828 case H_LIST_UNSUBSCRIBE
:
829 if (!msginfo
->extradata
)
830 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
831 if (msginfo
->extradata
->list_unsubscribe
) break;
832 msginfo
->extradata
->list_unsubscribe
= g_strdup(hp
);
835 if (!msginfo
->extradata
)
836 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
837 if (msginfo
->extradata
->list_help
) break;
838 msginfo
->extradata
->list_help
= g_strdup(hp
);
841 if (!msginfo
->extradata
)
842 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
843 if (msginfo
->extradata
->list_archive
) break;
844 msginfo
->extradata
->list_archive
= g_strdup(hp
);
847 if (!msginfo
->extradata
)
848 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
849 if (msginfo
->extradata
->list_owner
) break;
850 msginfo
->extradata
->list_owner
= g_strdup(hp
);
853 if (!msginfo
->extradata
)
854 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
855 if (msginfo
->extradata
->resent_from
) break;
856 msginfo
->extradata
->resent_from
= g_strdup(hp
);
862 /* to avoid performance penalty hooklist is invoked only for
863 headers known to be able to generate avatars */
864 if (hnum
== H_FROM
|| hnum
== H_X_FACE
|| hnum
== H_FACE
) {
865 AvatarCaptureData
*acd
= g_new0(AvatarCaptureData
, 1);
866 /* no extra memory is wasted, hooks are expected to
867 take care of copying members when needed */
868 acd
->msginfo
= msginfo
;
869 acd
->header
= hentry_full
[hnum
].name
;
871 hooks_invoke(AVATAR_HEADER_UPDATE_HOOKLIST
, (gpointer
)acd
);
878 if (!msginfo
->inreplyto
&& msginfo
->references
)
880 g_strdup((gchar
*)msginfo
->references
->data
);
885 gchar
*procheader_get_fromname(const gchar
*str
)
889 Xstrdup_a(tmp
, str
, return NULL
);
892 extract_quote(tmp
, '\"');
894 } else if (strchr(tmp
, '<')) {
895 eliminate_parenthesis(tmp
, '<', '>');
899 extract_parenthesis(tmp
, '<', '>');
902 } else if (strchr(tmp
, '(')) {
903 extract_parenthesis(tmp
, '(', ')');
908 name
= g_strdup(str
);
910 name
= g_strdup(tmp
);
915 static gint
procheader_scan_date_string(const gchar
*str
,
916 gchar
*weekday
, gint
*day
,
917 gchar
*month
, gint
*year
,
918 gint
*hh
, gint
*mm
, gint
*ss
,
924 gint zone1
= 0, zone2
= 0;
925 gchar offset_sign
, zonestr
[7];
931 result
= sscanf(str
, "%10s %d %9s %d %2d:%2d:%2d %6s",
932 weekday
, day
, month
, year
, hh
, mm
, ss
, zone
);
933 if (result
== 8) return 0;
936 result
= sscanf(str
, "%3s,%d %9s %d %2d:%2d:%2d %6s",
937 weekday
, day
, month
, year
, hh
, mm
, ss
, zone
);
938 if (result
== 8) return 0;
940 result
= sscanf(str
, "%3s %3s %d %2d:%2d:%2d %d %6s",
941 weekday
, month
, day
, hh
, mm
, ss
, year
, zone
);
942 if (result
== 8) return 0;
944 result
= sscanf(str
, "%d %9s %d %2d:%2d:%2d %6s",
945 day
, month
, year
, hh
, mm
, ss
, zone
);
946 if (result
== 7) return 0;
949 result
= sscanf(str
, "%10s %d %9s %d %2d:%2d:%2d",
950 weekday
, day
, month
, year
, hh
, mm
, ss
);
951 if (result
== 7) return 0;
953 result
= sscanf(str
, "%3s %3s %d %2d:%2d:%2d %d",
954 weekday
, month
, day
, hh
, mm
, ss
, year
);
955 if (result
== 7) return 0;
957 result
= sscanf(str
, "%d %9s %d %2d:%2d:%2d",
958 day
, month
, year
, hh
, mm
, ss
);
959 if (result
== 6) return 0;
962 result
= sscanf(str
, "%10s %d %9s %d %2d:%2d %6s",
963 weekday
, day
, month
, year
, hh
, mm
, zone
);
964 if (result
== 7) return 0;
966 result
= sscanf(str
, "%d %9s %d %2d:%2d %5s",
967 day
, month
, year
, hh
, mm
, zone
);
968 if (result
== 6) return 0;
971 result
= sscanf(str
, "%10s %d %9s %d %2d:%2d",
972 weekday
, day
, month
, year
, hh
, mm
);
973 if (result
== 6) return 0;
975 result
= sscanf(str
, "%d %9s %d %2d:%2d",
976 day
, month
, year
, hh
, mm
);
977 if (result
== 5) return 0;
981 /* RFC3339 subset, with fraction of second */
982 result
= sscanf(str
, "%4d-%2d-%2d%c%2d:%2d:%2d.%d%6s",
983 year
, &month_n
, day
, &sep1
, hh
, mm
, ss
, &secfract
, zonestr
);
985 && (sep1
== 'T' || sep1
== 't' || sep1
== ' ')) {
986 if (month_n
>= 1 && month_n
<= 12) {
987 strncpy2(month
, monthstr
+((month_n
-1)*3), 4);
988 if (zonestr
[0] == 'z' || zonestr
[0] == 'Z') {
989 strcat(zone
, "+00:00");
990 } else if (sscanf(zonestr
, "%c%2d:%2d",
991 &offset_sign
, &zone1
, &zone2
) == 3) {
992 strcat(zone
, zonestr
);
998 /* RFC3339 subset, no fraction of second */
999 result
= sscanf(str
, "%4d-%2d-%2d%c%2d:%2d:%2d%6s",
1000 year
, &month_n
, day
, &sep1
, hh
, mm
, ss
, zonestr
);
1002 && (sep1
== 'T' || sep1
== 't' || sep1
== ' ')) {
1003 if (month_n
>= 1 && month_n
<= 12) {
1004 strncpy2(month
, monthstr
+((month_n
-1)*3), 4);
1005 if (zonestr
[0] == 'z' || zonestr
[0] == 'Z') {
1006 strcat(zone
, "+00:00");
1007 } else if (sscanf(zonestr
, "%c%2d:%2d",
1008 &offset_sign
, &zone1
, &zone2
) == 3) {
1009 strcat(zone
, zonestr
);
1017 /* RFC3339 subset, no fraction of second, and no timezone offset */
1018 /* This particular "subset" is invalid, RFC requires the offset */
1019 result
= sscanf(str
, "%4d-%2d-%2d %2d:%2d:%2d",
1020 year
, &month_n
, day
, hh
, mm
, ss
);
1022 if (1 <= month_n
&& month_n
<= 12) {
1023 strncpy2(month
, monthstr
+((month_n
-1)*3), 4);
1028 /* ISO8601 format with just date (YYYY-MM-DD) */
1029 result
= sscanf(str
, "%4d-%2d-%2d",
1030 year
, &month_n
, day
);
1032 *hh
= *mm
= *ss
= 0;
1033 if (1 <= month_n
&& month_n
<= 12) {
1034 strncpy2(month
, monthstr
+((month_n
-1)*3), 4);
1043 * Hiro, most UNIXen support this function:
1044 * http://www.mcsr.olemiss.edu/cgi-bin/man-cgi?getdate
1046 gboolean
procheader_date_parse_to_tm(const gchar
*src
, struct tm
*t
, char *zone
)
1059 memset(t
, 0, sizeof *t
);
1061 if (procheader_scan_date_string(src
, weekday
, &day
, month
, &year
,
1062 &hh
, &mm
, &ss
, zone
) < 0) {
1063 g_warning("invalid date: %s", src
);
1067 /* Y2K compliant :) */
1076 if ((p
= strstr(monthstr
, month
)) != NULL
)
1077 dmonth
= (gint
)(p
- monthstr
) / 3 + 1;
1079 g_warning("invalid month: %s", month
);
1080 dmonth
= G_DATE_BAD_MONTH
;
1087 t
->tm_mon
= dmonth
- 1;
1088 t
->tm_year
= year
- 1900;
1098 time_t procheader_date_parse(gchar
*dest
, const gchar
*src
, gint len
)
1106 GDateMonth dmonth
= G_DATE_BAD_MONTH
;
1110 if (procheader_scan_date_string(src
, weekday
, &day
, month
, &year
,
1111 &hh
, &mm
, &ss
, zone
) < 0) {
1112 if (dest
&& len
> 0)
1113 strncpy2(dest
, src
, len
);
1118 for (p
= monthstr
; *p
!= '\0'; p
+= 3) {
1119 if (!g_ascii_strncasecmp(p
, month
, 3)) {
1120 dmonth
= (gint
)(p
- monthstr
) / 3 + 1;
1127 GDateTime
*dt
, *dt2
;
1129 #if GLIB_CHECK_VERSION(2,68,0)
1130 tz
= g_time_zone_new_identifier(zone
);
1132 tz
= g_time_zone_new_utc();
1134 tz
= g_time_zone_new(zone
); // can't return NULL no need to check for it
1136 dt
= g_date_time_new(tz
, 1, 1, 1, 0, 0, 0);
1137 g_time_zone_unref(tz
);
1138 dt2
= g_date_time_add_full(dt
, year
-1, dmonth
-1, day
-1, hh
, mm
, ss
);
1139 g_date_time_unref(dt
);
1141 timer
= g_date_time_to_unix(dt2
);
1142 g_date_time_unref(dt2
);
1148 /* Y2K compliant :) */
1160 t
.tm_mon
= dmonth
- 1;
1161 t
.tm_year
= year
- 1900;
1167 tz_offset
= remote_tzoffset_sec(zone
);
1168 if (tz_offset
!= -1)
1169 timer
+= tzoffset_sec(&timer
) - tz_offset
;
1173 procheader_date_get_localtime(dest
, len
, timer
);
1178 void procheader_date_get_localtime(gchar
*dest
, gint len
, const time_t timer
)
1181 gchar
*default_format
= "%y/%m/%d(%a) %H:%M";
1183 const gchar
*src_codeset
, *dest_codeset
;
1187 lt
= localtime_r(&timer
, &buf
);
1190 lt
= localtime_r(&dummy
, &buf
);
1193 if (prefs_common
.date_format
)
1194 fast_strftime(dest
, len
, prefs_common
.date_format
, lt
);
1196 fast_strftime(dest
, len
, default_format
, lt
);
1198 if (!g_utf8_validate(dest
, -1, NULL
)) {
1199 src_codeset
= conv_get_locale_charset_str_no_utf8();
1200 dest_codeset
= CS_UTF_8
;
1201 str
= conv_codeset_strdup(dest
, src_codeset
, dest_codeset
);
1203 strncpy2(dest
, str
, len
);
1209 /* Added by Mel Hadasht on 27 Aug 2001 */
1210 /* Get a header from msginfo */
1211 gint
procheader_get_header_from_msginfo(MsgInfo
*msginfo
, gchar
**buf
, gchar
*header
)
1215 HeaderEntry hentry
[]={ { NULL
, NULL
, TRUE
},
1216 { NULL
, NULL
, FALSE
} };
1219 cm_return_val_if_fail(msginfo
!= NULL
, -1);
1220 cm_return_val_if_fail(buf
!= NULL
, -1);
1221 cm_return_val_if_fail(header
!= NULL
, -1);
1223 hentry
[0].name
= header
;
1225 file
= procmsg_get_message_file_path(msginfo
);
1226 if ((fp
= claws_fopen(file
, "rb")) == NULL
) {
1227 FILE_OP_ERROR(file
, "claws_fopen");
1233 val
= procheader_get_one_field(buf
, fp
, hentry
);
1235 if (claws_fclose(fp
) == EOF
) {
1236 FILE_OP_ERROR(file
, "claws_fclose");
1246 /* *buf is already NULL in that case, see procheader_get_one_field() */
1253 HeaderEntry
*procheader_entries_from_str(const gchar
*str
)
1255 HeaderEntry
*entries
= NULL
, *he
;
1256 int numh
= 0, i
= 0;
1257 gchar
**names
= NULL
;
1258 const gchar
*s
= str
;
1263 while (*s
!= '\0') {
1264 if (*s
== ' ') ++numh
;
1270 entries
= g_new0(HeaderEntry
, numh
+ 1); /* room for last NULL */
1272 ++s
; /* skip first space */
1273 names
= g_strsplit(s
, " ", numh
);
1276 he
->name
= g_strdup_printf("%s:", names
[i
]);
1286 void procheader_entries_free (HeaderEntry
*entries
)
1288 if (entries
!= NULL
) {
1289 HeaderEntry
*he
= entries
;
1290 while (he
->name
!= NULL
) {
1292 if (he
->body
!= NULL
)
1300 gboolean
procheader_header_is_internal(const gchar
*hdr_name
)
1302 const gchar
*internal_hdrs
[] = {
1303 "AF:", "NF:", "PS:", "SRH:", "SFN:", "DSR:", "MID:", "CFG:",
1304 "PT:", "S:", "RQ:", "SSV:", "NSV:", "SSH:", "R:", "MAID:",
1305 "SCF:", "RMID:", "FMID:", "NAID:",
1306 "X-Claws-Account-Id:",
1309 "X-Claws-Privacy-System:",
1310 "X-Claws-Auto-Wrapping:",
1311 "X-Claws-Auto-Indent:",
1312 "X-Claws-End-Special-Headers:",
1313 "X-Sylpheed-Account-Id:",
1315 "X-Sylpheed-Encrypt:",
1316 "X-Sylpheed-Privacy-System:",
1317 "X-Sylpheed-End-Special-Headers:",
1322 for (i
= 0; internal_hdrs
[i
]; i
++) {
1323 if (!strcmp(hdr_name
, internal_hdrs
[i
]))