2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2014 Hiroyuki Yamamoto and the Claws Mail team
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>
37 #include "procheader.h"
40 #include "prefs_common.h"
44 #include "file-utils.h"
48 static gchar monthstr
[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
50 typedef char *(*getlinefunc
) (char *, size_t, void *);
51 typedef int (*peekcharfunc
) (void *);
52 typedef int (*getcharfunc
) (void *);
53 typedef gint (*get_one_field_func
) (gchar
**, void *, HeaderEntry
[]);
55 static gint
string_get_one_field(gchar
**buf
, char **str
,
56 HeaderEntry hentry
[]);
58 static char *string_getline(char *buf
, size_t len
, char **str
);
59 static int string_peekchar(char **str
);
60 static int file_peekchar(FILE *fp
);
61 static gint
generic_get_one_field(gchar
**bufptr
, void *data
,
64 peekcharfunc peekchar
,
66 static MsgInfo
*parse_stream(void *data
, gboolean isstring
, MsgFlags flags
,
67 gboolean full
, gboolean decrypted
);
70 gint
procheader_get_one_field(gchar
**buf
, FILE *fp
,
73 return generic_get_one_field(buf
, fp
, hentry
,
74 (getlinefunc
)fgets_crlf
, (peekcharfunc
)file_peekchar
,
78 static gint
string_get_one_field(gchar
**buf
, char **str
,
81 return generic_get_one_field(buf
, str
, hentry
,
82 (getlinefunc
)string_getline
,
83 (peekcharfunc
)string_peekchar
,
87 gboolean
procheader_skip_headers(FILE *fp
)
89 gchar
*buf
= g_malloc(BUFFSIZE
);
91 if (fgets_crlf(buf
, BUFFSIZE
- 1, fp
) == NULL
) {
95 if (buf
[0] == '\r' || buf
[0] == '\n') {
105 static char *string_getline(char *buf
, size_t len
, char **str
)
107 gboolean is_cr
= FALSE
;
108 gboolean last_was_cr
= FALSE
;
113 for (; **str
&& len
> 1; --len
) {
114 is_cr
= (**str
== '\r');
115 if ((*buf
++ = *(*str
)++) == '\n') {
131 static int string_peekchar(char **str
)
136 static int file_peekchar(FILE *fp
)
138 return ungetc(getc(fp
), fp
);
141 static gint
generic_get_one_field(gchar
**bufptr
, void *data
,
143 getlinefunc getline
, peekcharfunc peekchar
,
146 /* returns -1 in case of failure of any kind, whatever it's a parsing error
147 or an allocation error. if returns -1, *bufptr is always NULL, and vice-versa,
148 and if returning 0 (OK), *bufptr is always non-NULL, so callers just have to
149 test the return value
153 HeaderEntry
*hp
= NULL
;
157 cm_return_val_if_fail(bufptr
!= NULL
, -1);
162 if (hentry
!= NULL
) {
163 /* skip non-required headers */
164 /* and get hentry header line */
167 if (getline(buf
, len
, data
) == NULL
) {
168 debug_print("generic_get_one_field: getline\n");
173 if (buf
[0] == '\r' || buf
[0] == '\n') {
174 debug_print("generic_get_one_field: empty line\n");
179 } while (buf
[0] == ' ' || buf
[0] == '\t');
181 for (hp
= hentry
, hnum
= 0; hp
->name
!= NULL
;
183 if (!g_ascii_strncasecmp(hp
->name
, buf
,
187 } while (hp
->name
== NULL
);
189 /* read first line */
190 if (getline(buf
, len
, data
) == NULL
) {
191 debug_print("generic_get_one_field: getline\n");
196 if (buf
[0] == '\r' || buf
[0] == '\n') {
197 debug_print("generic_get_one_field: empty line\n");
203 /* reduce initial buffer to its useful part */
205 buf
= g_realloc(buf
, len
);
207 debug_print("generic_get_one_field: reallocation error\n");
214 nexthead
= peekchar(data
);
215 /* ([*WSP CRLF] 1*WSP) */
216 if (nexthead
== ' ' || nexthead
== '\t') {
221 gboolean skiptab
= (nexthead
== '\t');
222 /* trim previous trailing \n if requesting one header or
223 * unfolding was requested */
224 if ((!hentry
&& unfold
) || (hp
&& hp
->unfold
))
227 buflen
= strlen(buf
);
230 tmpbuf
= g_malloc(BUFFSIZE
);
232 if (getline(tmpbuf
, BUFFSIZE
, data
) == NULL
) {
236 tmplen
= strlen(tmpbuf
)+1;
238 /* extend initial buffer and concatenate next line */
240 buf
= g_realloc(buf
, len
);
242 debug_print("generic_get_one_field: reallocation error\n");
247 memcpy(buf
+buflen
, tmpbuf
, tmplen
);
249 if (skiptab
) { /* replace tab with space */
250 *(buf
+ buflen
) = ' ';
253 /* remove trailing new line */
264 gint
procheader_get_one_field_asis(gchar
**buf
, FILE *fp
)
266 return generic_get_one_field(buf
, fp
, NULL
,
267 (getlinefunc
)fgets_crlf
,
268 (peekcharfunc
)file_peekchar
,
272 GPtrArray
*procheader_get_header_array_asis(FILE *fp
)
278 cm_return_val_if_fail(fp
!= NULL
, NULL
);
280 headers
= g_ptr_array_new();
282 while (procheader_get_one_field_asis(&buf
, fp
) != -1) {
283 if ((header
= procheader_parse_header(buf
)) != NULL
)
284 g_ptr_array_add(headers
, header
);
292 void procheader_header_array_destroy(GPtrArray
*harray
)
297 cm_return_if_fail(harray
!= NULL
);
299 for (i
= 0; i
< harray
->len
; i
++) {
300 header
= g_ptr_array_index(harray
, i
);
301 procheader_header_free(header
);
304 g_ptr_array_free(harray
, TRUE
);
307 void procheader_header_free(Header
*header
)
311 g_free(header
->name
);
312 g_free(header
->body
);
317 tests whether two headers' names are equal
318 remove the trailing ':' or ' ' before comparing
321 gboolean
procheader_headername_equal(char * hdr1
, char * hdr2
)
328 if (hdr1
[len1
- 1] == ':')
330 if (hdr2
[len2
- 1] == ':')
335 return (g_ascii_strncasecmp(hdr1
, hdr2
, len1
) == 0);
339 parse headers, for example :
340 From: dinh@enseirb.fr becomes :
341 header->name = "From:"
342 header->body = "dinh@enseirb.fr"
344 static gboolean
header_is_addr_field(const gchar
*hdr
)
346 static char *addr_headers
[] = {
353 "Followup-and-Reply-To:",
354 "Disposition-Notification-To:",
355 "Return-Receipt-To:",
362 for (i
= 0; addr_headers
[i
] != NULL
; i
++)
363 if (!strcasecmp(hdr
, addr_headers
[i
]))
369 Header
* procheader_parse_header(gchar
* buf
)
373 gboolean addr_field
= FALSE
;
375 cm_return_val_if_fail(buf
!= NULL
, NULL
);
377 if ((*buf
== ':') || (*buf
== ' '))
380 for (p
= buf
; *p
; p
++) {
381 if ((*p
== ':') || (*p
== ' ')) {
382 header
= g_new(Header
, 1);
383 header
->name
= g_strndup(buf
, p
- buf
+ 1);
384 addr_field
= header_is_addr_field(header
->name
);
386 while (*p
== ' ' || *p
== '\t') p
++;
387 header
->body
= conv_unmime_header(p
, NULL
, addr_field
);
394 void procheader_get_header_fields(FILE *fp
, HeaderEntry hentry
[])
401 if (hentry
== NULL
) return;
403 while ((hnum
= procheader_get_one_field(&buf
, fp
, hentry
)) != -1) {
406 p
= buf
+ strlen(hp
->name
);
407 while (*p
== ' ' || *p
== '\t') p
++;
409 if (hp
->body
== NULL
)
410 hp
->body
= g_strdup(p
);
411 else if (procheader_headername_equal(hp
->name
, "To") ||
412 procheader_headername_equal(hp
->name
, "Cc")) {
413 gchar
*tp
= hp
->body
;
414 hp
->body
= g_strconcat(tp
, ", ", p
, NULL
);
422 MsgInfo
*procheader_parse_file(const gchar
*file
, MsgFlags flags
,
423 gboolean full
, gboolean decrypted
)
429 GError
*error
= NULL
;
437 f
= g_file_new_for_path(file
);
438 fi
= g_file_query_info(f
, "standard::size,standard::type,time::modified",
439 G_FILE_QUERY_INFO_NONE
, NULL
, &error
);
441 g_warning(error
->message
);
446 if (g_stat(file
, &s
) < 0) {
447 FILE_OP_ERROR(file
, "stat");
453 if (g_file_info_get_file_type(fi
) != G_FILE_TYPE_REGULAR
) {
459 if (!S_ISREG(s
.st_mode
))
463 if ((fp
= claws_fopen(file
, "rb")) == NULL
) {
464 FILE_OP_ERROR(file
, "claws_fopen");
468 msginfo
= procheader_parse_stream(fp
, flags
, full
, decrypted
);
473 msginfo
->size
= g_file_info_get_size(fi
);
474 g_file_info_get_modification_time(fi
, &tv
);
475 msginfo
->mtime
= tv
.tv_sec
;
477 msginfo
->size
= s
.st_size
;
478 msginfo
->mtime
= s
.st_mtime
;
490 MsgInfo
*procheader_parse_str(const gchar
*str
, MsgFlags flags
, gboolean full
,
493 return parse_stream(&str
, TRUE
, flags
, full
, decrypted
);
511 H_SC_PLANNED_DOWNLOAD
,
515 H_DISPOSITION_NOTIFICATION_TO
,
517 H_SC_PARTIALLY_RETRIEVED
,
529 static HeaderEntry hentry_full
[] = {
530 {"Date:", NULL
, FALSE
},
531 {"From:", NULL
, TRUE
},
534 {"Newsgroups:", NULL
, TRUE
},
535 {"Subject:", NULL
, TRUE
},
536 {"Message-ID:", NULL
, FALSE
},
537 {"References:", NULL
, FALSE
},
538 {"In-Reply-To:", NULL
, FALSE
},
539 {"Content-Type:", NULL
, FALSE
},
540 {"Seen:", NULL
, FALSE
},
541 {"Status:", NULL
, FALSE
},
542 {"From ", NULL
, FALSE
},
543 {"SC-Marked-For-Download:", NULL
, FALSE
},
544 {"SC-Message-Size:", NULL
, FALSE
},
545 {"Face:", NULL
, FALSE
},
546 {"X-Face:", NULL
, FALSE
},
547 {"Disposition-Notification-To:", NULL
, FALSE
},
548 {"Return-Receipt-To:", NULL
, FALSE
},
549 {"SC-Partially-Retrieved:", NULL
, FALSE
},
550 {"SC-Account-Server:", NULL
, FALSE
},
551 {"SC-Account-Login:",NULL
, FALSE
},
552 {"List-Post:", NULL
, TRUE
},
553 {"List-Subscribe:", NULL
, TRUE
},
554 {"List-Unsubscribe:",NULL
, TRUE
},
555 {"List-Help:", NULL
, TRUE
},
556 {"List-Archive:", NULL
, TRUE
},
557 {"List-Owner:", NULL
, TRUE
},
558 {"Resent-From:", NULL
, TRUE
},
559 {NULL
, NULL
, FALSE
}};
561 static HeaderEntry hentry_short
[] = {
562 {"Date:", NULL
, FALSE
},
563 {"From:", NULL
, TRUE
},
566 {"Newsgroups:", NULL
, TRUE
},
567 {"Subject:", NULL
, TRUE
},
568 {"Message-ID:", NULL
, FALSE
},
569 {"References:", NULL
, FALSE
},
570 {"In-Reply-To:", NULL
, FALSE
},
571 {"Content-Type:", NULL
, FALSE
},
572 {"Seen:", NULL
, FALSE
},
573 {"Status:", NULL
, FALSE
},
574 {"From ", NULL
, FALSE
},
575 {"SC-Marked-For-Download:", NULL
, FALSE
},
576 {"SC-Message-Size:",NULL
, FALSE
},
577 {NULL
, NULL
, FALSE
}};
579 static HeaderEntry
* procheader_get_headernames(gboolean full
)
581 return full
? hentry_full
: hentry_short
;
584 MsgInfo
*procheader_parse_stream(FILE *fp
, MsgFlags flags
, gboolean full
,
587 return parse_stream(fp
, FALSE
, flags
, full
, decrypted
);
590 static gboolean
avatar_from_some_face(gpointer source
, gpointer userdata
)
592 AvatarCaptureData
*acd
= (AvatarCaptureData
*)source
;
594 if (*(acd
->content
) == '\0') /* won't be null, but may be empty */
597 if (!strcmp(acd
->header
, hentry_full
[H_FACE
].name
)) {
598 debug_print("avatar_from_some_face: found 'Face' header\n");
599 procmsg_msginfo_add_avatar(acd
->msginfo
, AVATAR_FACE
, acd
->content
);
602 else if (!strcmp(acd
->header
, hentry_full
[H_X_FACE
].name
)) {
603 debug_print("avatar_from_some_face: found 'X-Face' header\n");
604 procmsg_msginfo_add_avatar(acd
->msginfo
, AVATAR_XFACE
, acd
->content
);
610 static gulong avatar_hook_id
= HOOK_NONE
;
612 static MsgInfo
*parse_stream(void *data
, gboolean isstring
, MsgFlags flags
,
613 gboolean full
, gboolean decrypted
)
621 void *orig_data
= data
;
623 get_one_field_func get_one_field
=
624 isstring
? (get_one_field_func
)string_get_one_field
625 : (get_one_field_func
)procheader_get_one_field
;
627 hentry
= procheader_get_headernames(full
);
629 if (MSG_IS_QUEUED(flags
) || MSG_IS_DRAFT(flags
)) {
630 while (get_one_field(&buf
, data
, NULL
) != -1) {
631 if ((!strncmp(buf
, "X-Claws-End-Special-Headers: 1",
632 strlen("X-Claws-End-Special-Headers:"))) ||
633 (!strncmp(buf
, "X-Sylpheed-End-Special-Headers: 1",
634 strlen("X-Sylpheed-End-Special-Headers:")))) {
639 /* from other mailers */
640 if (!strncmp(buf
, "Date: ", 6)
641 || !strncmp(buf
, "To: ", 4)
642 || !strncmp(buf
, "From: ", 6)
643 || !strncmp(buf
, "Subject: ", 9)) {
647 rewind((FILE *)data
);
657 msginfo
= procmsg_msginfo_new();
659 if (flags
.tmp_flags
|| flags
.perm_flags
)
660 msginfo
->flags
= flags
;
662 MSG_SET_PERM_FLAGS(msginfo
->flags
, MSG_NEW
| MSG_UNREAD
);
664 msginfo
->inreplyto
= NULL
;
666 if (avatar_hook_id
== HOOK_NONE
&&
667 (prefs_common
.enable_avatars
&& (AVATARS_ENABLE_CAPTURE
|| AVATARS_ENABLE_RENDER
))) {
668 avatar_hook_id
= hooks_register_hook(AVATAR_HEADER_UPDATE_HOOKLIST
,
669 avatar_from_some_face
, NULL
);
670 } else if (avatar_hook_id
!= HOOK_NONE
&&
671 !(prefs_common
.enable_avatars
&& AVATARS_ENABLE_CAPTURE
)) {
672 hooks_unregister_hook(AVATAR_HEADER_UPDATE_HOOKLIST
, avatar_hook_id
);
673 avatar_hook_id
= HOOK_NONE
;
676 while ((hnum
= get_one_field(&buf
, data
, hentry
)) != -1) {
677 hp
= buf
+ strlen(hentry
[hnum
].name
);
678 while (*hp
== ' ' || *hp
== '\t') hp
++;
682 if (msginfo
->date
) break;
684 procheader_date_parse(NULL
, hp
, 0);
685 if (g_utf8_validate(hp
, -1, NULL
)) {
686 msginfo
->date
= g_strdup(hp
);
688 gchar
*utf
= conv_codeset_strdup(
690 conv_get_locale_charset_str_no_utf8(),
693 !g_utf8_validate(utf
, -1, NULL
)) {
695 utf
= g_malloc(strlen(buf
)*2+1);
696 conv_localetodisp(utf
,
703 if (msginfo
->from
) break;
704 msginfo
->from
= conv_unmime_header(hp
, NULL
, TRUE
);
705 msginfo
->fromname
= procheader_get_fromname(msginfo
->from
);
706 remove_return(msginfo
->from
);
707 remove_return(msginfo
->fromname
);
710 tmp
= conv_unmime_header(hp
, NULL
, TRUE
);
715 g_strconcat(p
, ", ", tmp
, NULL
);
718 msginfo
->to
= g_strdup(tmp
);
722 tmp
= conv_unmime_header(hp
, NULL
, TRUE
);
727 g_strconcat(p
, ", ", tmp
, NULL
);
730 msginfo
->cc
= g_strdup(tmp
);
734 if (msginfo
->newsgroups
) {
735 p
= msginfo
->newsgroups
;
736 msginfo
->newsgroups
=
737 g_strconcat(p
, ",", hp
, NULL
);
740 msginfo
->newsgroups
= g_strdup(hp
);
743 if (msginfo
->subject
) break;
744 msginfo
->subject
= conv_unmime_header(hp
, NULL
, FALSE
);
745 unfold_line(msginfo
->subject
);
748 if (msginfo
->msgid
) break;
750 extract_parenthesis(hp
, '<', '>');
752 msginfo
->msgid
= g_strdup(hp
);
755 msginfo
->references
=
756 references_list_prepend(msginfo
->references
,
760 if (msginfo
->inreplyto
) break;
762 eliminate_parenthesis(hp
, '(', ')');
763 if ((p
= strrchr(hp
, '<')) != NULL
&&
764 strchr(p
+ 1, '>') != NULL
) {
765 extract_parenthesis(p
, '<', '>');
768 msginfo
->inreplyto
= g_strdup(p
);
772 if (!g_ascii_strncasecmp(hp
, "multipart/", 10))
773 MSG_SET_TMP_FLAGS(msginfo
->flags
, MSG_MULTIPART
);
775 case H_DISPOSITION_NOTIFICATION_TO
:
776 if (!msginfo
->extradata
)
777 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
778 if (msginfo
->extradata
->dispositionnotificationto
) break;
779 msginfo
->extradata
->dispositionnotificationto
= g_strdup(hp
);
781 case H_RETURN_RECEIPT_TO
:
782 if (!msginfo
->extradata
)
783 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
784 if (msginfo
->extradata
->returnreceiptto
) break;
785 msginfo
->extradata
->returnreceiptto
= g_strdup(hp
);
787 /* partial download infos */
788 case H_SC_PARTIALLY_RETRIEVED
:
789 if (!msginfo
->extradata
)
790 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
791 if (msginfo
->extradata
->partial_recv
) break;
792 msginfo
->extradata
->partial_recv
= g_strdup(hp
);
794 case H_SC_ACCOUNT_SERVER
:
795 if (!msginfo
->extradata
)
796 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
797 if (msginfo
->extradata
->account_server
) break;
798 msginfo
->extradata
->account_server
= g_strdup(hp
);
800 case H_SC_ACCOUNT_LOGIN
:
801 if (!msginfo
->extradata
)
802 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
803 if (msginfo
->extradata
->account_login
) break;
804 msginfo
->extradata
->account_login
= g_strdup(hp
);
806 case H_SC_MESSAGE_SIZE
:
807 if (msginfo
->total_size
) break;
808 msginfo
->total_size
= atoi(hp
);
810 case H_SC_PLANNED_DOWNLOAD
:
811 msginfo
->planned_download
= atoi(hp
);
813 /* end partial download infos */
815 if (msginfo
->fromspace
) break;
816 msginfo
->fromspace
= g_strdup(hp
);
817 remove_return(msginfo
->fromspace
);
821 if (!msginfo
->extradata
)
822 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
823 if (msginfo
->extradata
->list_post
) break;
824 msginfo
->extradata
->list_post
= g_strdup(hp
);
826 case H_LIST_SUBSCRIBE
:
827 if (!msginfo
->extradata
)
828 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
829 if (msginfo
->extradata
->list_subscribe
) break;
830 msginfo
->extradata
->list_subscribe
= g_strdup(hp
);
832 case H_LIST_UNSUBSCRIBE
:
833 if (!msginfo
->extradata
)
834 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
835 if (msginfo
->extradata
->list_unsubscribe
) break;
836 msginfo
->extradata
->list_unsubscribe
= g_strdup(hp
);
839 if (!msginfo
->extradata
)
840 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
841 if (msginfo
->extradata
->list_help
) break;
842 msginfo
->extradata
->list_help
= g_strdup(hp
);
845 if (!msginfo
->extradata
)
846 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
847 if (msginfo
->extradata
->list_archive
) break;
848 msginfo
->extradata
->list_archive
= g_strdup(hp
);
851 if (!msginfo
->extradata
)
852 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
853 if (msginfo
->extradata
->list_owner
) break;
854 msginfo
->extradata
->list_owner
= g_strdup(hp
);
857 if (!msginfo
->extradata
)
858 msginfo
->extradata
= g_new0(MsgInfoExtraData
, 1);
859 if (msginfo
->extradata
->resent_from
) break;
860 msginfo
->extradata
->resent_from
= g_strdup(hp
);
866 /* to avoid performance penalty hooklist is invoked only for
867 headers known to be able to generate avatars */
868 if (hnum
== H_FROM
|| hnum
== H_X_FACE
|| hnum
== H_FACE
) {
869 AvatarCaptureData
*acd
= g_new0(AvatarCaptureData
, 1);
870 /* no extra memory is wasted, hooks are expected to
871 take care of copying members when needed */
872 acd
->msginfo
= msginfo
;
873 acd
->header
= hentry_full
[hnum
].name
;
875 hooks_invoke(AVATAR_HEADER_UPDATE_HOOKLIST
, (gpointer
)acd
);
882 if (!msginfo
->inreplyto
&& msginfo
->references
)
884 g_strdup((gchar
*)msginfo
->references
->data
);
889 gchar
*procheader_get_fromname(const gchar
*str
)
893 Xstrdup_a(tmp
, str
, return NULL
);
896 extract_quote(tmp
, '\"');
898 } else if (strchr(tmp
, '<')) {
899 eliminate_parenthesis(tmp
, '<', '>');
903 extract_parenthesis(tmp
, '<', '>');
906 } else if (strchr(tmp
, '(')) {
907 extract_parenthesis(tmp
, '(', ')');
912 name
= g_strdup(str
);
914 name
= g_strdup(tmp
);
919 static gint
procheader_scan_date_string(const gchar
*str
,
920 gchar
*weekday
, gint
*day
,
921 gchar
*month
, gint
*year
,
922 gint
*hh
, gint
*mm
, gint
*ss
,
928 gint zone1
= 0, zone2
= 0;
929 gchar offset_sign
, zonestr
[7];
935 result
= sscanf(str
, "%10s %d %9s %d %2d:%2d:%2d %6s",
936 weekday
, day
, month
, year
, hh
, mm
, ss
, zone
);
937 if (result
== 8) return 0;
940 result
= sscanf(str
, "%3s,%d %9s %d %2d:%2d:%2d %6s",
941 weekday
, day
, month
, year
, hh
, mm
, ss
, zone
);
942 if (result
== 8) return 0;
944 result
= sscanf(str
, "%3s %3s %d %2d:%2d:%2d %d %6s",
945 weekday
, month
, day
, hh
, mm
, ss
, year
, zone
);
946 if (result
== 8) return 0;
948 result
= sscanf(str
, "%d %9s %d %2d:%2d:%2d %6s",
949 day
, month
, year
, hh
, mm
, ss
, zone
);
950 if (result
== 7) return 0;
953 result
= sscanf(str
, "%10s %d %9s %d %2d:%2d:%2d",
954 weekday
, day
, month
, year
, hh
, mm
, ss
);
955 if (result
== 7) return 0;
957 result
= sscanf(str
, "%3s %3s %d %2d:%2d:%2d %d",
958 weekday
, month
, day
, hh
, mm
, ss
, year
);
959 if (result
== 7) return 0;
961 result
= sscanf(str
, "%d %9s %d %2d:%2d:%2d",
962 day
, month
, year
, hh
, mm
, ss
);
963 if (result
== 6) return 0;
966 result
= sscanf(str
, "%10s %d %9s %d %2d:%2d %6s",
967 weekday
, day
, month
, year
, hh
, mm
, zone
);
968 if (result
== 7) return 0;
970 result
= sscanf(str
, "%d %9s %d %2d:%2d %5s",
971 day
, month
, year
, hh
, mm
, zone
);
972 if (result
== 6) return 0;
975 result
= sscanf(str
, "%10s %d %9s %d %2d:%2d",
976 weekday
, day
, month
, year
, hh
, mm
);
977 if (result
== 6) return 0;
979 result
= sscanf(str
, "%d %9s %d %2d:%2d",
980 day
, month
, year
, hh
, mm
);
981 if (result
== 5) return 0;
985 /* RFC3339 subset, with fraction of second */
986 result
= sscanf(str
, "%4d-%2d-%2d%c%2d:%2d:%2d.%d%6s",
987 year
, &month_n
, day
, &sep1
, hh
, mm
, ss
, &secfract
, zonestr
);
989 && (sep1
== 'T' || sep1
== 't' || sep1
== ' ')) {
990 if (month_n
>= 1 && month_n
<= 12) {
991 strncpy2(month
, monthstr
+((month_n
-1)*3), 4);
992 if (zonestr
[0] == 'z' || zonestr
[0] == 'Z') {
993 strcat(zone
, "+00:00");
994 } else if (sscanf(zonestr
, "%c%2d:%2d",
995 &offset_sign
, &zone1
, &zone2
) == 3) {
996 strcat(zone
, zonestr
);
1002 /* RFC3339 subset, no fraction of second */
1003 result
= sscanf(str
, "%4d-%2d-%2d%c%2d:%2d:%2d%6s",
1004 year
, &month_n
, day
, &sep1
, hh
, mm
, ss
, zonestr
);
1006 && (sep1
== 'T' || sep1
== 't' || sep1
== ' ')) {
1007 if (month_n
>= 1 && month_n
<= 12) {
1008 strncpy2(month
, monthstr
+((month_n
-1)*3), 4);
1009 if (zonestr
[0] == 'z' || zonestr
[0] == 'Z') {
1010 strcat(zone
, "+00:00");
1011 } else if (sscanf(zonestr
, "%c%2d:%2d",
1012 &offset_sign
, &zone1
, &zone2
) == 3) {
1013 strcat(zone
, zonestr
);
1021 /* RFC3339 subset, no fraction of second, and no timezone offset */
1022 /* This particular "subset" is invalid, RFC requires the offset */
1023 result
= sscanf(str
, "%4d-%2d-%2d %2d:%2d:%2d",
1024 year
, &month_n
, day
, hh
, mm
, ss
);
1026 if (1 <= month_n
&& month_n
<= 12) {
1027 strncpy2(month
, monthstr
+((month_n
-1)*3), 4);
1032 /* ISO8601 format with just date (YYYY-MM-DD) */
1033 result
= sscanf(str
, "%4d-%2d-%2d",
1034 year
, &month_n
, day
);
1036 *hh
= *mm
= *ss
= 0;
1037 if (1 <= month_n
&& month_n
<= 12) {
1038 strncpy2(month
, monthstr
+((month_n
-1)*3), 4);
1047 * Hiro, most UNIXen support this function:
1048 * http://www.mcsr.olemiss.edu/cgi-bin/man-cgi?getdate
1050 gboolean
procheader_date_parse_to_tm(const gchar
*src
, struct tm
*t
, char *zone
)
1063 memset(t
, 0, sizeof *t
);
1065 if (procheader_scan_date_string(src
, weekday
, &day
, month
, &year
,
1066 &hh
, &mm
, &ss
, zone
) < 0) {
1067 g_warning("Invalid date: %s", src
);
1071 /* Y2K compliant :) */
1080 if ((p
= strstr(monthstr
, month
)) != NULL
)
1081 dmonth
= (gint
)(p
- monthstr
) / 3 + 1;
1083 g_warning("Invalid month: %s", month
);
1084 dmonth
= G_DATE_BAD_MONTH
;
1091 t
->tm_mon
= dmonth
- 1;
1092 t
->tm_year
= year
- 1900;
1102 time_t procheader_date_parse(gchar
*dest
, const gchar
*src
, gint len
)
1110 GDateMonth dmonth
= G_DATE_BAD_MONTH
;
1114 if (procheader_scan_date_string(src
, weekday
, &day
, month
, &year
,
1115 &hh
, &mm
, &ss
, zone
) < 0) {
1116 if (dest
&& len
> 0)
1117 strncpy2(dest
, src
, len
);
1122 for (p
= monthstr
; *p
!= '\0'; p
+= 3) {
1123 if (!g_ascii_strncasecmp(p
, month
, 3)) {
1124 dmonth
= (gint
)(p
- monthstr
) / 3 + 1;
1131 GDateTime
*dt
, *dt2
;
1133 tz
= g_time_zone_new(zone
); // can't return NULL no need to check for it
1134 dt
= g_date_time_new(tz
, 1, 1, 1, 0, 0, 0);
1135 g_time_zone_unref(tz
);
1136 dt2
= g_date_time_add_full(dt
, year
-1, dmonth
-1, day
-1, hh
, mm
, ss
);
1137 g_date_time_unref(dt
);
1139 timer
= g_date_time_to_unix(dt2
);
1140 g_date_time_unref(dt2
);
1146 /* Y2K compliant :) */
1158 t
.tm_mon
= dmonth
- 1;
1159 t
.tm_year
= year
- 1900;
1165 tz_offset
= remote_tzoffset_sec(zone
);
1166 if (tz_offset
!= -1)
1167 timer
+= tzoffset_sec(&timer
) - tz_offset
;
1170 procheader_date_get_localtime(dest
, len
, timer
);
1176 void procheader_date_get_localtime(gchar
*dest
, gint len
, const time_t timer
)
1179 gchar
*default_format
= "%y/%m/%d(%a) %H:%M";
1181 const gchar
*src_codeset
, *dest_codeset
;
1185 lt
= localtime_r(&timer
, &buf
);
1188 lt
= localtime_r(&dummy
, &buf
);
1191 if (prefs_common
.date_format
)
1192 fast_strftime(dest
, len
, prefs_common
.date_format
, lt
);
1194 fast_strftime(dest
, len
, default_format
, lt
);
1196 if (!g_utf8_validate(dest
, -1, NULL
)) {
1197 src_codeset
= conv_get_locale_charset_str_no_utf8();
1198 dest_codeset
= CS_UTF_8
;
1199 str
= conv_codeset_strdup(dest
, src_codeset
, dest_codeset
);
1201 strncpy2(dest
, str
, len
);
1207 /* Added by Mel Hadasht on 27 Aug 2001 */
1208 /* Get a header from msginfo */
1209 gint
procheader_get_header_from_msginfo(MsgInfo
*msginfo
, gchar
**buf
, gchar
*header
)
1213 HeaderEntry hentry
[]={ { NULL
, NULL
, TRUE
},
1214 { NULL
, NULL
, FALSE
} };
1217 cm_return_val_if_fail(msginfo
!= NULL
, -1);
1218 cm_return_val_if_fail(buf
!= NULL
, -1);
1219 cm_return_val_if_fail(header
!= NULL
, -1);
1221 hentry
[0].name
= header
;
1223 file
= procmsg_get_message_file_path(msginfo
);
1224 if ((fp
= claws_fopen(file
, "rb")) == NULL
) {
1225 FILE_OP_ERROR(file
, "claws_fopen");
1231 val
= procheader_get_one_field(buf
, fp
, hentry
);
1233 if (claws_fclose(fp
) == EOF
) {
1234 FILE_OP_ERROR(file
, "claws_fclose");
1244 /* *buf is already NULL in that case, see procheader_get_one_field() */
1251 HeaderEntry
*procheader_entries_from_str(const gchar
*str
)
1253 HeaderEntry
*entries
= NULL
, *he
;
1254 int numh
= 0, i
= 0;
1255 gchar
**names
= NULL
;
1256 const gchar
*s
= str
;
1261 while (*s
!= '\0') {
1262 if (*s
== ' ') ++numh
;
1268 entries
= g_new0(HeaderEntry
, numh
+ 1); /* room for last NULL */
1270 ++s
; /* skip first space */
1271 names
= g_strsplit(s
, " ", numh
);
1274 he
->name
= g_strdup_printf("%s:", names
[i
]);
1284 void procheader_entries_free (HeaderEntry
*entries
)
1286 if (entries
!= NULL
) {
1287 HeaderEntry
*he
= entries
;
1288 while (he
->name
!= NULL
) {
1290 if (he
->body
!= NULL
)
1298 gboolean
procheader_header_is_internal(const gchar
*hdr_name
)
1300 const gchar
*internal_hdrs
[] = {
1301 "AF:", "NF:", "PS:", "SRH:", "SFN:", "DSR:", "MID:", "CFG:",
1302 "PT:", "S:", "RQ:", "SSV:", "NSV:", "SSH:", "R:", "MAID:",
1303 "SCF:", "RMID:", "FMID:", "NAID:",
1304 "X-Claws-Account-Id:",
1307 "X-Claws-Privacy-System:",
1308 "X-Claws-Auto-Wrapping:",
1309 "X-Claws-Auto-Indent:",
1310 "X-Claws-End-Special-Headers:",
1311 "X-Sylpheed-Account-Id:",
1313 "X-Sylpheed-Encrypt:",
1314 "X-Sylpheed-Privacy-System:",
1315 "X-Sylpheed-End-Special-Headers:",
1320 for (i
= 0; internal_hdrs
[i
]; i
++) {
1321 if (!strcmp(hdr_name
, internal_hdrs
[i
]))