#include #include "msg.h" #include "str.h" #include "argz.h" #include msg_t *msg_create(void) { msg_t *msg; if ((msg = (msg_t *)malloc(sizeof(msg_t))) == NULL) return NULL; msg->mail_from = NULL; msg->azRcpt = NULL; msg->asRcpt = 0; msg->azNewsgroups = NULL; msg->asNewsgroups = 0; msg->azHeaders = NULL; msg->asHeaders = 0; msg->cpMsg = NULL; msg->cpHeaders = NULL; msg->cpBody = NULL; msg->cpMsgid = NULL; return msg; } void msg_destroy(msg_t *msg) { if (msg == NULL) return; //FIXME what about non-graceful aborts? if (msg->mail_from != NULL) free(msg->mail_from); if (msg->azRcpt != NULL) free(msg->azRcpt); if (msg->azHeaders != NULL) free(msg->azHeaders); if (msg->azNewsgroups != NULL) free(msg->azNewsgroups); if (msg->cpMsg != NULL) free(msg->cpMsg); if (msg->cpHeaders != NULL) free(msg->cpHeaders); if (msg->cpBody != NULL) free(msg->cpBody); free(msg); } msg_rc_t msg_split(msg_t *msg) { char *cpName; char *cpValue; char *cpRem; char *cp; /* INPUTS * * msg->cpMsg * must contain the wholly RFC822 formatted message with native * (unescaped) dots at the beginning of a line, the 'From ' envelope, * headers, double newline, body, '\0', no trailing dot; * * OUTPUTS * * msg->azHeaders, msg->asHeaders * contains the headers in argz format, one logical '\0'-terminated line * per header which might be wrapped into multiple '\n'-ended physical * lines. The "To:" and "Cc:" headers are silently. The "Newsgroups:" and * "Message-ID" headers are removed and their values are stored in * separate structures (see below). * * msg->cpBody * contains the unmodified body of the message, '\0'-terminated, no * trailing dot. * * msg->cpMsgid * contains the message id including surrounding angle brackets. * * msg->azNewsgroups, asNewsgroups * is a argz-type array of strings containing the Newsgroups based on the * header information. */ /* split message into header and body */ if (!str_parse(msg->cpMsg, "m/((?:.*?)\\n)\\n(.*)$/s", &msg->cpHeaders, &msg->cpBody)) return MSG_ERR_SPLITSPLITBODY; /* replace envelope From w/o colon by X-F: pseudotag. This eliminates the * special case of having one header, which is really an embedded * envelope, not ending with a colon while all other do. After splitting * headers into name and value pairs this action is reversed. */ if (strlen(msg->cpHeaders) < 4) return MSG_ERR_SPLITLEN; if (strncasecmp(msg->cpHeaders, "From", 4) != 0) return MSG_ERR_SPLITMISSINGFROM; memcpy(msg->cpHeaders, "X-F:", 4); /* unwrap header lines */ //FIXME poor man's s///g simulator as current str library doesn't support //global substitution while (str_parse(msg->cpHeaders, "s/(.*?)\\n[ \\t]+(.*)/$1 $2/s", &cpRem)) { free(msg->cpHeaders); msg->cpHeaders = cpRem; } /* split header lines into names and values */ //FIXME str enhancement requests and bugs to be fixed //FIXME - fix bug "not" [^...] working //FIXME - improve str_parse(foo, "...", &foo) should free foo() on it's own //FIXME - add "global" in s/search/replace/g (see above "unwrap hader lines") while (str_parse(msg->cpHeaders, "m/^([\\w-]+?:)[ \\t]*(.*?)[ \\t]*\\n(.*)/s", &cpName, &cpValue, &cpRem)) { free(msg->cpHeaders); msg->cpHeaders = cpRem; argz_add(&msg->azHeaders, &msg->asHeaders, cpName); argz_add(&msg->azHeaders, &msg->asHeaders, cpValue); } /* reverse the 'From ' to 'X-F: ' replacement */ memcpy(msg->azHeaders, "From", 4); /* replace envelope X-F: pseudotag with From w/o colon */ /* check for headers we care about and do whatever neccessary */ msg->cpMsgid = NULL; msg->azNewsgroups = NULL; msg->asNewsgroups = 0; cp = msg->azHeaders; while (cp != NULL) { if (strcasecmp("From", cp) == 0) { argz_delete(&msg->azHeaders, &msg->asHeaders, cp); /* del name */ argz_delete(&msg->azHeaders, &msg->asHeaders, cp); /* del value */ continue; } if (strcasecmp("To:", cp) == 0) { argz_delete(&msg->azHeaders, &msg->asHeaders, cp); /* del name */ argz_delete(&msg->azHeaders, &msg->asHeaders, cp); /* del value */ continue; } if (strcasecmp("Cc:", cp) == 0) { argz_delete(&msg->azHeaders, &msg->asHeaders, cp); /* del name */ argz_delete(&msg->azHeaders, &msg->asHeaders, cp); /* del value */ continue; } if (strcasecmp("Message-ID:", cp) == 0) { if (msg->cpMsgid != NULL) return MSG_ERR_SPLITIDMULTI; argz_delete(&msg->azHeaders, &msg->asHeaders, cp); /* del name */ //fprintf(stderr, "DEBUG: Message-ID cp = ***%s***\n", cp); if ((cp == NULL) || (strlen(cp) == 0)) /* get value */ return MSG_ERR_SPLITIDEMPTY; if ((msg->cpMsgid = strdup(cp)) == NULL) return MSG_ERR_MEM; argz_delete(&msg->azHeaders, &msg->asHeaders, cp); /* del value */ continue; } if (strcasecmp("Newsgroups:", cp) == 0) { argz_delete(&msg->azHeaders, &msg->asHeaders, cp); /* del name */ //fprintf(stderr, "DEBUG: Newsgroups cp = ***%s***\n", cp); if (argz_add(&msg->azNewsgroups, &msg->asNewsgroups, cp) != 0) /* get value */ return MSG_ERR_MEM; argz_delete(&msg->azHeaders, &msg->asHeaders, cp); /* del value */ continue; } if ((cp = argz_next(msg->azHeaders, msg->asHeaders, cp)) == NULL) /* next value */ break; if ((cp = argz_next(msg->azHeaders, msg->asHeaders, cp)) == NULL) /* next name */ break; } if (msg->cpMsgid == NULL) return MSG_ERR_SPLITIDNONE; if (msg->azNewsgroups != NULL) { argz_stringify(msg->azNewsgroups, msg->asNewsgroups, ','); if (argz_create_sep(msg->azNewsgroups, ',', &msg->azNewsgroups, &msg->asNewsgroups) != 0) return MSG_ERR_MEM; } return MSG_OK; } msg_rc_t msg_join(msg_t *msg) { char *cp; char *cpRem; char **aHeaders; int i; char *cpCut; char *cpWrap; char *cpNew; char c; int n; /* verify asNewsgroups */ if (msg->azNewsgroups == NULL) return MSG_ERR_JOINGROUPNONE; argz_stringify(msg->azNewsgroups, msg->asNewsgroups, ','); //fprintf(stderr, "DEBUG: join consolidated azNewsgroups = ***%s***\n", msg->azNewsgroups); if (strlen(msg->azNewsgroups) == 0) return MSG_ERR_JOINGROUPEMPTY; argz_add(&msg->azHeaders, &msg->asHeaders, "Newsgroups:"); argz_add(&msg->azHeaders, &msg->asHeaders, msg->azNewsgroups); /* verify Message-ID */ if (msg->cpMsgid == NULL) return MSG_ERR_JOINIDNONE; if (strlen(msg->cpMsgid) == 0) return MSG_ERR_JOINIDEMPTY; argz_add(&msg->azHeaders, &msg->asHeaders, "Message-ID:"); argz_add(&msg->azHeaders, &msg->asHeaders, msg->cpMsgid); /* merge name/value pairs into single string */ argz_add(&msg->azHeaders, &msg->asHeaders, ""); /* append empty string */ if ((aHeaders = (char **)malloc((argz_count(msg->azHeaders, msg->asHeaders) + 1) * sizeof(char *))) == NULL) exit(1); //FIXME argz_extract(msg->azHeaders, msg->asHeaders, aHeaders); i=0; while(1) { if ((cp = aHeaders[++i]) == NULL) break; *(cp-1) = ' '; if ((cp = aHeaders[++i]) == NULL) break; // *(cp-1) = '\n'; } /* fold headers */ //FIXME where to place this defines best #define WRAPAT 120 #define WRAPUSING "\n " cp = NULL; while ((cp = argz_next(msg->azHeaders, msg->asHeaders, cp)) != NULL) { if (strlen(cp) >= WRAPAT) { cpRem = cp; cpWrap = NULL; while (strlen(cpRem) >= WRAPAT) { for (i = WRAPAT; i >= 1 && (cpRem[i] != ' ') && (cpRem[i] != '\t'); i--); if (i == 0) i = WRAPAT; /* sorry, hard cut at non-whitespace */ if (i < WRAPAT) i++; /* we don't care about the whitespace itself */ cpCut = str_dup(cpRem, i); //FIXME 1.) continue searching downwards skipping all whitespaces and 2.) as we know the length replace str_dup/ strcat/ free with strncat only if (cpWrap == NULL) { if ((cpWrap = (char *)malloc(strlen(cpCut)+strlen(WRAPUSING)+1)) == NULL) exit(1); //FIXME *cpWrap = '\0'; } else { if ((cpWrap = (char *)realloc(cpWrap, strlen(cpWrap)+strlen(cpCut)+strlen(WRAPUSING)+1)) == NULL) exit(1); //FIXME strcat(cpWrap, WRAPUSING); } strcat(cpWrap, cpCut); free(cpCut); cpRem += i; } for (i = 0; i < strlen(cpRem) && ((cpRem[i] == ' ') || (cpRem[i] == '\t')); i++); cpRem += i; if (strlen(cpRem) > 0) { if ((cpWrap = (char *)realloc(cpWrap, strlen(cpWrap)+strlen(cpRem)+strlen(WRAPUSING)+1)) == NULL) exit(1); //FIXME strcat(cpWrap, WRAPUSING); strcat(cpWrap, cpRem); } argz_delete(&msg->azHeaders, &msg->asHeaders, cp); argz_insert(&msg->azHeaders, &msg->asHeaders, cp, cpWrap); free(cpWrap); } } argz_stringify(msg->azHeaders, msg->asHeaders, '\n'); msg->cpHeaders = msg->azHeaders; if (argz_create_sep(msg->cpBody, '\n', &msg->azBody, &msg->asBody) != 0) return MSG_ERR_MEM; /* escape dots at the beginning of each line */ cp = NULL; while ((cp =argz_next(msg->azBody, msg->asBody, cp)) != NULL) { if (*cp == '.') { if ((cpNew = malloc(strlen(cp) + 1)) == NULL) return MSG_ERR_MEM; *cpNew = '.'; cpNew++; *cpNew = '\0'; strcat(cpNew, cp); argz_delete(&msg->azBody, &msg->asBody, cp); argz_insert(&msg->azBody, &msg->asBody, cp, cpNew); } } argz_stringify(msg->azBody, msg->asBody, '\n'); msg->cpBody = msg->azBody; /******************************************************************** * header + CRLF + body + '.' + CRLF + '\0', replacing NL with CRLF * ********************************************************************/ n = 0; /* count size of headers, reserve space for NL to CRLF conversion */ for (i = 0; ((c = msg->cpHeaders[i]) != '\0'); i++) { if (c == '\n') n++; n++; } /* if headers don't end with NL, reserve space for CRLF */ if (i >= 0 && msg->cpHeaders[i - 1] != '\n') n+=2; /* reserve space for CRLF between headers and body */ n+=2; /* count size of body, reserve space for NL to CRLF conversion */ for (i = 0; ((c = msg->cpBody[i]) != '\0'); i++) { if (c == '\n') n++; n++; } /* if body doesn't end with NL, reserve space for CRLF */ if (i >= 0 && msg->cpBody[i - 1] != '\n') n+=2; /* reserve space for terminating '.'-CRLF-NUL at the end of the message */ n+=4; if ((msg->cpMsg = (char *)malloc(n)) == NULL) return MSG_ERR_MEM; n = 0; /* copy headers, do NL to CRLF conversion */ for (i = 0; ((c = msg->cpHeaders[i]) != '\0'); i++) { if (c == '\n') msg->cpMsg[n++] = '\r'; msg->cpMsg[n++] = c; } /* if headers don't end with NL, append CRLF */ if (i >= 0 && msg->cpHeaders[i - 1] != '\n') { msg->cpMsg[n++] = '\r'; msg->cpMsg[n++] = '\n'; } /* add CRLF between headers and body */ msg->cpMsg[n++] = '\r'; msg->cpMsg[n++] = '\n'; /* copy body, do NL to CRLF conversion */ for (i = 0; ((c = msg->cpBody[i]) != '\0'); i++) { if (c == '\n') msg->cpMsg[n++] = '\r'; msg->cpMsg[n++] = c; } /* if body doesn't end with NL, append CRLF */ if (i >= 0 && msg->cpBody[i - 1] != '\n') { msg->cpMsg[n++] = '\r'; msg->cpMsg[n++] = '\n'; } /* add terminating '.'-CRLF-NUL at the end of the message */ msg->cpMsg[n++] = '.'; msg->cpMsg[n++] = '\r'; msg->cpMsg[n++] = '\n'; msg->cpMsg[n] = '\0'; //fprintf(stderr, "DEBUG: Message = ***%s***\n", msg->cpMsg); return MSG_OK; } char *msg_error(msg_t *msg, msg_rc_t rc) { char *str; str = "MSG: no description"; if (rc == MSG_OK ) str = "MSG: no error"; else if (rc == MSG_ERR_MEM ) str = "MSG: memory"; else if (rc == MSG_ERR_SPLITSPLITBODY ) str = "MSG: split into header and body failed"; else if (rc == MSG_ERR_SPLITLEN ) str = "MSG: header is too short"; else if (rc == MSG_ERR_SPLITMISSINGFROM ) str = "MSG: header is missing 'From ' envelope"; else if (rc == MSG_ERR_SPLITIDNONE ) str = "MSG: header is missing 'Message-ID'"; else if (rc == MSG_ERR_SPLITIDEMPTY ) str = "MSG: header has empty 'Message-ID'"; else if (rc == MSG_ERR_SPLITIDMULTI ) str = "MSG: header has multiple 'Message-ID's"; else if (rc == MSG_ERR_JOINGROUPNONE ) str = "MSG: join with no 'Newsgroup'"; else if (rc == MSG_ERR_JOINGROUPEMPTY ) str = "MSG: join with empty 'Newsgroup'"; else if (rc == MSG_ERR_JOINIDNONE ) str = "MSG: join with no 'Message-ID'"; else if (rc == MSG_ERR_JOINIDEMPTY ) str = "MSG: join with empty 'Message-ID'"; return str; }