gecko-dev/lib/libmsg/msgutils.c

857 lines
21 KiB
C
Raw Blame History

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
*/
/* msgutils.c --- various and sundry
*/
#include "msg.h"
#include "xp_time.h"
#include "xpgetstr.h"
#include "xplocale.h"
#include "htmldlgs.h"
#include "prefapi.h"
#include "xp_qsort.h"
extern int MK_OUT_OF_MEMORY;
extern int MK_MSG_NON_MAIL_FILE_WRITE_QUESTION;
extern int MK_MSG_HTML_DOMAINS_DIALOG;
extern int MK_MSG_HTML_DOMAINS_DIALOG_TITLE;
#ifdef XP_MAC
#pragma warn_unusedarg off
#endif
int
msg_GetURL(MWContext* context, URL_Struct* url, XP_Bool issafe)
{
XP_ASSERT(context);
/* phil & bienvenu think issafe means "allowed to start another URL even if
one is already runing". e.g. delete from msgPane, and load next msg */
if (!issafe)
msg_InterruptContext (context, TRUE);
url->internal_url = TRUE;
if (!url->open_new_window_specified)
{
url->open_new_window_specified = TRUE;
url->open_new_window = FALSE;
}
return FE_GetURL(context, url);
}
void
msg_InterruptContext(MWContext* context, XP_Bool safetoo)
{
XP_InterruptContext(context);
#ifdef NOTDEF /* ###tw */
struct MSG_Frame *msg_frame;
if (!context) return;
if (safetoo || !context->msgframe ||
!context->msgframe->safe_background_activity) {
/* save msg_frame in case context gets deleted on interruption */
msg_frame = context->msgframe;
XP_InterruptContext(context);
if (msg_frame) {
msg_frame->safe_background_activity = FALSE;
}
}
#endif
}
XP_Bool
MSG_RequiresComposeWindow (const char *url)
{
if (!url) return FALSE;
if (!strncasecomp (url, "mailto:", 7))
{
return TRUE;
}
return FALSE;
}
XP_Bool
MSG_RequiresBrowserWindow (const char *url)
{
if (!url) return FALSE;
if (MSG_RequiresNewsWindow (url) ||
MSG_RequiresMailWindow (url) ||
!strncasecomp (url, "about:", 6) ||
!strncasecomp (url, "addbook:", 8) ||
!strncasecomp (url, "addbook-ldap", 12) || /* no colon so addbook-ldap and addbook-ldaps both match */
!strncasecomp (url, "mailto:", 7) ||
!strncasecomp (url, "view-source:", 12) ||
!strncasecomp (url, "internal-callback-handler:", 26) ||
!strncasecomp (url, "internal-panel-handler", 22) ||
!strncasecomp (url, "internal-dialog-handler", 23))
return FALSE;
else if (!strncasecomp (url, "news:", 5) ||
!strncasecomp (url, "snews:", 6) ||
!strncasecomp (url, "mailbox:", 8) ||
!strncasecomp (url, "IMAP:", 5))
{
/* Mail and news messages themselves don't require browser windows,
but their attachments do. */
if (XP_STRSTR(url, "?part=") || XP_STRSTR(url, "&part="))
return TRUE;
else
return FALSE;
}
else
return TRUE;
}
/* If we're in a mail window, and clicking on a link which will itself
require a mail window, then don't allow this to show up in a different
window - since there can only be one mail window.
*/
XP_Bool
MSG_NewWindowProhibited (MWContext *context, const char *url)
{
if (!context) return FALSE;
if ((context->type == MWContextMail &&
MSG_RequiresMailWindow (url)) ||
(context->type == MWContextNews &&
MSG_RequiresNewsWindow (url)) ||
(MSG_RequiresComposeWindow (url)))
return TRUE;
else
return FALSE;
}
char *
MSG_ConvertToQuotation (const char *string)
{
int32 column = 0;
int32 newlines = 0;
int32 chars = 0;
const char *in;
char *out;
char *new_string;
if (! string) return 0;
/* First, count up the lines in the string. */
for (in = string; *in; in++)
{
chars++;
if (*in == CR || *in == LF)
{
if (in[0] == CR && in[1] == LF) {
in++;
chars++;
}
newlines++;
column = 0;
}
else
{
column++;
}
}
/* If the last line doesn't end in a newline, pretend it does. */
if (column != 0)
newlines++;
/* 2 characters for each '> ', +1 for '\0', and + potential linebreak */
new_string = (char *) XP_ALLOC (chars + (newlines * 2) + 1 + LINEBREAK_LEN);
if (! new_string)
return 0;
column = 0;
out = new_string;
/* Now copy. */
for (in = string; *in; in++)
{
if (column == 0)
{
*out++ = '>';
*out++ = ' ';
}
*out++ = *in;
if (*in == CR || *in == LF)
{
if (in[0] == CR && in[1] == LF)
*out++ = *++in;
newlines++;
column = 0;
}
else
{
column++;
}
}
/* If the last line doesn't end in a newline, add one. */
if (column != 0)
{
XP_STRCPY (out, LINEBREAK);
out += LINEBREAK_LEN;
}
*out = 0;
return new_string;
}
/* Given a string and a length, removes any "Re:" strings from the front.
It also deals with that "Re[2]:" thing that some mailers do.
Returns TRUE if it made a change, FALSE otherwise.
The string is not altered: the pointer to its head is merely advanced,
and the length correspondingly decreased.
*/
XP_Bool
msg_StripRE(const char **stringP, uint32 *lengthP)
{
const char *s, *s_end;
const char *last;
uint32 L;
XP_Bool result = FALSE;
XP_ASSERT(stringP);
if (!stringP) return FALSE;
s = *stringP;
L = lengthP ? *lengthP : XP_STRLEN(s);
s_end = s + L;
last = s;
AGAIN:
while (s < s_end && XP_IS_SPACE(*s))
s++;
if (s < (s_end-2) &&
(s[0] == 'r' || s[0] == 'R') &&
(s[1] == 'e' || s[1] == 'E'))
{
if (s[2] == ':')
{
s = s+3; /* Skip over "Re:" */
result = TRUE; /* Yes, we stripped it. */
goto AGAIN; /* Skip whitespace and try again. */
}
else if (s[2] == '[' || s[2] == '(')
{
const char *s2 = s+3; /* Skip over "Re[" */
/* Skip forward over digits after the "[". */
while (s2 < (s_end-2) && XP_IS_DIGIT(*s2))
s2++;
/* Now ensure that the following thing is "]:"
Only if it is do we alter `s'.
*/
if ((s2[0] == ']' || s2[0] == ')') && s2[1] == ':')
{
s = s2+2; /* Skip over "]:" */
result = TRUE; /* Yes, we stripped it. */
goto AGAIN; /* Skip whitespace and try again. */
}
}
}
/* Decrease length by difference between current ptr and original ptr.
Then store the current ptr back into the caller. */
if (lengthP) *lengthP -= (s - (*stringP));
*stringP = s;
return result;
}
char*
msg_GetDummyEnvelope(void)
{
static char result[75];
char *ct;
time_t now = time ((time_t *) 0);
#if defined (XP_WIN)
if (now < 0 || now > 0x7FFFFFFF)
now = 0x7FFFFFFF;
#endif
ct = ctime(&now);
XP_ASSERT(ct[24] == CR || ct[24] == LF);
ct[24] = 0;
/* This value must be in ctime() format, with English abbreviations.
strftime("... %c ...") is no good, because it is localized. */
XP_STRCPY(result, "From - ");
XP_STRCPY(result + 7, ct);
XP_STRCPY(result + 7 + 24, LINEBREAK);
return result;
}
/* #define STRICT_ENVELOPE */
XP_Bool
msg_IsEnvelopeLine(const char *buf, int32 buf_size)
{
#ifdef STRICT_ENVELOPE
/* The required format is
From jwz Fri Jul 1 09:13:09 1994
But we should also allow at least:
From jwz Fri, Jul 01 09:13:09 1994
From jwz Fri Jul 1 09:13:09 1994 PST
From jwz Fri Jul 1 09:13:09 1994 (+0700)
We can't easily call XP_ParseTimeString() because the string is not
null terminated (ok, we could copy it after a quick check...) but
XP_ParseTimeString() may be too lenient for our purposes.
DANGER!! The released version of 2.0b1 was (on some systems,
some Unix, some NT, possibly others) writing out envelope lines
like "From - 10/13/95 11:22:33" which STRICT_ENVELOPE will reject!
*/
const char *date, *end;
if (buf_size < 29) return FALSE;
if (*buf != 'F') return FALSE;
if (XP_STRNCMP(buf, "From ", 5)) return FALSE;
end = buf + buf_size;
date = buf + 5;
/* Skip horizontal whitespace between "From " and user name. */
while ((*date == ' ' || *date == '\t') && date < end)
date++;
/* If at the end, it doesn't match. */
if (XP_IS_SPACE(*date) || date == end)
return FALSE;
/* Skip over user name. */
while (!XP_IS_SPACE(*date) && date < end)
date++;
/* Skip horizontal whitespace between user name and date. */
while ((*date == ' ' || *date == '\t') && date < end)
date++;
/* Don't want this to be localized. */
# define TMP_ISALPHA(x) (((x) >= 'A' && (x) <= 'Z') || \
((x) >= 'a' && (x) <= 'z'))
/* take off day-of-the-week. */
if (date >= end - 3)
return FALSE;
if (!TMP_ISALPHA(date[0]) || !TMP_ISALPHA(date[1]) || !TMP_ISALPHA(date[2]))
return FALSE;
date += 3;
/* Skip horizontal whitespace (and commas) between dotw and month. */
if (*date != ' ' && *date != '\t' && *date != ',')
return FALSE;
while ((*date == ' ' || *date == '\t' || *date == ',') && date < end)
date++;
/* take off month. */
if (date >= end - 3)
return FALSE;
if (!TMP_ISALPHA(date[0]) || !TMP_ISALPHA(date[1]) || !TMP_ISALPHA(date[2]))
return FALSE;
date += 3;
/* Skip horizontal whitespace between month and dotm. */
if (date == end || (*date != ' ' && *date != '\t'))
return FALSE;
while ((*date == ' ' || *date == '\t') && date < end)
date++;
/* Skip over digits and whitespace. */
while (((*date >= '0' && *date <= '9') || *date == ' ' || *date == '\t') &&
date < end)
date++;
/* Next character should be a colon. */
if (date >= end || *date != ':')
return FALSE;
/* Ok, that ought to be enough... */
# undef TMP_ISALPHA
#else /* !STRICT_ENVELOPE */
if (buf_size < 5) return FALSE;
if (*buf != 'F') return FALSE;
if (XP_STRNCMP(buf, "From ", 5)) return FALSE;
#endif /* !STRICT_ENVELOPE */
return TRUE;
}
XP_Bool
msg_ConfirmMailFile (MWContext *context, const char *file_name)
{
XP_File in = XP_FileOpen (file_name, xpMailFolder, XP_FILE_READ_BIN);
char buf[100];
char *s = buf;
int L;
if (!in) return TRUE;
L = XP_FileRead(buf, sizeof(buf)-2, in);
XP_FileClose (in);
if (L < 1) return TRUE;
buf[L] = 0;
while (XP_IS_SPACE(*s))
s++, L--;
if (L > 5 && msg_IsEnvelopeLine(s, L))
return TRUE;
PR_snprintf (buf, sizeof(buf),
XP_GetString (MK_MSG_NON_MAIL_FILE_WRITE_QUESTION), file_name);
return FE_Confirm (context, buf);
}
void msg_ClearMessageArea(MWContext* context)
{
/* ###tw This needs to be replaced by stuff that notifies FE's the
right way */
#ifndef _USRDLL
/* hack. Open a stream to layout, give it
whitespace (it needs something) and then close it
right away. This has the effect of getting the front end to
clear out the HTML display area.
*/
NET_StreamClass *stream;
static PA_InitData data;
URL_Struct *url;
if (!context)
return;
data.output_func = LO_ProcessTag;
url = NET_CreateURLStruct ("", NET_NORMAL_RELOAD);
if (!url) return;
stream = PA_BeginParseMDL (FO_PRESENT, &data, url, context);
if (stream)
{
char buf[] = "<BODY></BODY>";
int status = (*stream->put_block) (stream, buf, 13);
if (status < 0)
(*stream->abort) (stream, status);
else
(*stream->complete) (stream);
XP_FREE (stream);
}
NET_FreeURLStruct (url);
FE_SetProgressBarPercent (context, 0);
#endif
/* MSG_LoadMessage, with MSG_MESSAGEKEYNONE calls msg_ClearMessageArea, which
// has a bug (drawing the background in grey).<2E> So I just changed MSG_LoadMessage() to
// do nothing for XP_MAC except set its m_Key to MSG_MESSAGEKEYNONE and exit.<2E> This transfers
// the responsibility for clearing the message area to the front end.<2E> So far, so good.
//
// Now, it's no good just painting the area, because the next refresh will redraw using the
// existing history entry.<2E> So somehow we have to remove the history entry.
// There's no API for doing this, except calling SHIST_AddDocument with an entry whose
// address string is null or empty.
//
// If this state of affairs changes, this code will break, but I put in asserts to
// notify us about it. 98/01/21
// ---------------------------------------------------------------------------
*/
#if 0
/* It would be nice to do it this way, but we need to cause a refresh in the fe */
URL_Struct* url = NET_CreateURLStruct("", NET_NORMAL_RELOAD);
History_entry* newEntry;
LO_DiscardDocument(context);
XP_ASSERT(url);
newEntry = SHIST_CreateHistoryEntry(url, "Nobody Home");
XP_FREEIF(url);
XP_ASSERT(newEntry);
/* Using an empty address string will cause "AddDocument" to do a removal of the old entry,
<09>// then delete the new entry, and exit.
*/
SHIST_AddDocument(context, newEntry);
#endif
}
static MSG_HEADER_SET standard_header_set[] = {
MSG_TO_HEADER_MASK,
MSG_REPLY_TO_HEADER_MASK,
MSG_CC_HEADER_MASK,
MSG_BCC_HEADER_MASK,
MSG_NEWSGROUPS_HEADER_MASK,
MSG_FOLLOWUP_TO_HEADER_MASK
};
#define TOTAL_HEADERS (sizeof(standard_header_set)/sizeof(MSG_HEADER_SET))
extern int MSG_ExplodeHeaderField(MSG_HEADER_SET msg_header, const char * field, MSG_HeaderEntry ** return_list)
{
XP_ASSERT(return_list);
*return_list = NULL;
if (field && strlen(field)) {
MSG_HeaderEntry *list=NULL;
char * name;
char * address ;
int count;
count = MSG_ParseRFC822Addresses (field, &name, &address);
if (count > 0) {
char * address_start = address;
char * name_start = name;
int i;
list = (MSG_HeaderEntry*)XP_ALLOC(sizeof(MSG_HeaderEntry)*count);
if (!list)
return(-1);
for (i=0; i<count; i++) {
list[i].header_type = msg_header;
list[i].header_value = XP_STRDUP(address);
if (name && strlen(name))
list[i].header_value = PR_smprintf("%s <%s>", name, address);
else
list[i].header_value = XP_STRDUP(address);
while (*address != '\0')
address++;
address++;
while (*name != '\0')
name++;
name++;
}
if (name)
XP_FREE(name_start);
if (address)
XP_FREE(address_start);
}
*return_list = list;
return count;
}
return(0);
}
extern int MSG_CompressHeaderEntries( MSG_HeaderEntry * in_list, int list_size, MSG_HeaderEntry ** return_list)
{
int total = 0;
*return_list = NULL;
if (in_list != NULL && list_size > 0) {
MSG_HeaderEntry * list = NULL;
char * new_header_value;
int i,j;
for (i=0; i<TOTAL_HEADERS; i++) {
new_header_value = NULL;
for (j=0; j<list_size; j++) {
if (in_list[j].header_type == standard_header_set[i]) {
XP_Bool zero_init = FALSE;
int header_length = 0;
if (!new_header_value)
zero_init = TRUE;
else
header_length = XP_STRLEN(new_header_value)+1;
if (XP_STRLEN(in_list[j].header_value) == 0)
continue;
new_header_value = (char*)XP_REALLOC(
new_header_value,
header_length+XP_STRLEN(in_list[j].header_value)+XP_STRLEN(",")+1);
if (new_header_value == NULL) {
if (list != NULL)
XP_FREE(list);
/* don't forget to free up previous header_value entries */
return(-1);
}
if (zero_init)
new_header_value[0]='\0';
if (new_header_value && XP_STRLEN(new_header_value))
XP_STRCAT(new_header_value,",");
XP_STRCAT(new_header_value,in_list[j].header_value);
}
}
if (new_header_value) {
total++;
list = (MSG_HeaderEntry *)XP_REALLOC(list,total*sizeof(MSG_HeaderEntry));
if (list==NULL) {
if (new_header_value)
XP_FREE(new_header_value);
return(-1);
}
list[total-1].header_type = standard_header_set[i];
list[total-1].header_value = new_header_value;
}
}
*return_list = list;
}
return(total);
}
static int
msg_qsort_domains(const void* a, const void* b)
{
return XP_STRCMP(*((const char**) a), *((const char**) b));
}
static int
msg_generate_domains_list(char* domainlist, int* numfound, char*** returnlist)
{
int num;
int i;
int j;
char* ptr;
char* endptr;
char** list = NULL;
for (num=0 , ptr = domainlist ; ptr ; num++, ptr = endptr) {
endptr = XP_STRCHR(ptr, ',');
if (endptr) endptr++;
}
if (num > 0) {
list = (char**) XP_CALLOC(num, sizeof(char*));
if (!list) return MK_OUT_OF_MEMORY;
for (i=0 , ptr = domainlist ; ptr ; i++, ptr = endptr) {
endptr = XP_STRCHR(ptr, ',');
if (endptr) *endptr++ = '\0';
XP_ASSERT(i < num);
if (i < num) list[i] = ptr;
}
XP_QSORT(list, num, sizeof(char*), msg_qsort_domains);
/* Now, remove any empty entries or duplicates. */
for (i=0, j=0 ; i<num ; i++) {
ptr = list[i];
if (ptr && (j == 0 || XP_STRCMP(ptr, list[j - 1]) != 0)) {
list[j++] = ptr;
}
}
num = j;
}
*numfound = num;
*returnlist = list;
return 0;
}
static PRBool
HTMLDomainsDialogDone(XPDialogState* state, char** argv, int argc,
unsigned int button)
{
char* domainlist = NULL;
char* gone;
char* ptr;
char* endptr;
char** list = NULL;
int num;
int i;
int status;
if (button != XP_DIALOG_OK_BUTTON) return PR_FALSE;
PREF_CopyCharPref("mail.htmldomains", &domainlist);
if (!domainlist || !*domainlist) return PR_FALSE;
gone = XP_FindValueInArgs("gone", argv, argc);
XP_ASSERT(gone);
if (!gone || !*gone) return PR_FALSE;
status = msg_generate_domains_list(domainlist, &num, &list);
if (status < 0) goto FAIL;
for (ptr = gone ; ptr ; ptr = endptr) {
endptr = XP_STRCHR(ptr, ',');
if (endptr) *endptr++ = '\0';
if (*ptr) {
i = atoi(ptr);
XP_ASSERT(i >= 0 && i < num);
if (i >= 0 && i < num) {
XP_ASSERT(list[i]);
list[i] = NULL;
}
}
}
ptr = NULL;
for (i=0 ; i<num ; i++) {
if (list[i]) {
if (ptr) StrAllocCat(ptr, ",");
StrAllocCat(ptr, list[i]);
}
}
PREF_SetCharPref("mail.htmldomains", ptr ? ptr : "");
PREF_SavePrefFile();
FREEIF(ptr);
FAIL:
FREEIF(list);
FREEIF(domainlist);
return PR_FALSE;
}
int
MSG_DisplayHTMLDomainsDialog(MWContext* context)
{
static XPDialogInfo dialogInfo = {
XP_DIALOG_OK_BUTTON | XP_DIALOG_CANCEL_BUTTON,
HTMLDomainsDialogDone,
600,
440
};
int status = 0;
char* domainlist;
char* list = NULL;
char* tmp;
XPDialogStrings* strings;
int i;
int num = 0;
char** array = NULL;
PREF_CopyCharPref("mail.htmldomains", &domainlist);
status = msg_generate_domains_list(domainlist, &num, &array);
if (status < 0) goto FAIL;
for (i=0 ; i<num ; i++) {
tmp = PR_smprintf("<option value=%d>%s\n", i, array[i]);
if (!tmp) {
status = MK_OUT_OF_MEMORY;
goto FAIL;
}
StrAllocCat(list, tmp);
XP_FREE(tmp);
}
strings = XP_GetDialogStrings(MK_MSG_HTML_DOMAINS_DIALOG);
if (!strings) {
status = MK_OUT_OF_MEMORY;
goto FAIL;
}
XP_CopyDialogString(strings, 0, list ? list : "");
XP_MakeHTMLDialog(context, &dialogInfo, MK_MSG_HTML_DOMAINS_DIALOG_TITLE,
strings, NULL,PR_FALSE);
FAIL:
FREEIF(array);
FREEIF(domainlist);
FREEIF(list);
return status;
}
/*
* Does in-place modification of input param to conform with son-of-1036 rules
*
* A newsgroup component is one portion of the newsgroup name, separated by
* dots. So mcom.dev.fouroh has three newsgroup components. This function is
* only concerned with one component because that's what we need for virtual
* newsgroups.
*/
void msg_MakeLegalNewsgroupComponent (char *name)
{
int i = 0;
char ch;
while ((ch = name[i]) != '\0')
{
/* legal chars are 0-9,a-z, and +,-,_ */
if (!(ch >= '0' && ch <= '9'))
if (!(ch >= 'a' && ch <= 'z'))
if (!(ch == '+' || ch == '-' || ch == '_'))
{
/* ch is illegal. We can lowercase an uppercase letter
* but everything else goes to some legal char, like '_'
*/
if (ch >= 'A' && ch <= 'Z')
name[i] += 32;
else
name[i] = '_';
}
i++;
/* a newsgroup component is limited to 14 chars */
if (i > 13)
{
name[i] = '\0';
break;
}
}
}
void MSG_SetPercentProgress(MWContext *context, int32 numerator, int32 denominator)
{
XP_ASSERT(numerator <= denominator && numerator >= 0 && denominator > 0);
if (numerator > denominator || numerator < 0 || denominator < 0)
{
FE_SetProgressBarPercent(context, -1);
}
else if (denominator > 0)
{
int32 percent;
if (denominator > 100L)
percent = (numerator / (denominator / 100L));
else
percent = (100L * numerator) / denominator;
FE_SetProgressBarPercent (context, percent);
}
}
const char* MSG_FormatDateFromContext(MWContext *context, time_t date)
{
/* fix i18n. Well, maybe. Isn't strftime() supposed to be i18n? */
/* ftong- Well.... strftime() in Mac and Window is not really i18n */
/* We need to use XP_StrfTime instead of strftime */
static char result[40]; /* 30 probably not enough */
time_t now = time ((time_t *) 0);
int32 offset = XP_LocalZoneOffset() * 60L; /* Number of seconds between
local and GMT. */
int32 secsperday = 24L * 60L * 60L;
int32 nowday = (now + offset) / secsperday;
int32 day = (date + offset) / secsperday;
if (day == nowday) {
XP_StrfTime(context, result, sizeof(result), XP_TIME_FORMAT,
localtime(&date));
} else if (day < nowday && day > nowday - 7) {
XP_StrfTime(context, result, sizeof(result), XP_WEEKDAY_TIME_FORMAT,
localtime(&date));
} else {
#if defined (XP_WIN)
if (date < 0 || date > 0x7FFFFFFF)
date = 0x7FFFFFFF;
#endif
XP_StrfTime(context, result, sizeof(result), XP_DATE_TIME_FORMAT,
localtime(&date));
}
return result;
}