/* -*- 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.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ /* 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[] = "
"; 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). So I just changed MSG_LoadMessage() to // do nothing for XP_MAC except set its m_Key to MSG_MESSAGEKEYNONE and exit. This transfers // the responsibility for clearing the message area to the front end. So far, so good. // // Now, it's no good just painting the area, because the next refresh will redraw using the // existing history entry. 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, // 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