summaryrefslogtreecommitdiffstats
path: root/src/global/quote_822_local.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/global/quote_822_local.c')
-rw-r--r--src/global/quote_822_local.c294
1 files changed, 294 insertions, 0 deletions
diff --git a/src/global/quote_822_local.c b/src/global/quote_822_local.c
new file mode 100644
index 0000000..c19ee57
--- /dev/null
+++ b/src/global/quote_822_local.c
@@ -0,0 +1,294 @@
+/*++
+/* NAME
+/* quote_822_local 3
+/* SUMMARY
+/* quote local part of mailbox
+/* SYNOPSIS
+/* #include <quote_822_local.h>
+/*
+/* VSTRING *quote_822_local(dst, src)
+/* VSTRING *dst;
+/* const char *src;
+/*
+/* VSTRING *quote_822_local_flags(dst, src, flags)
+/* VSTRING *dst;
+/* const char *src;
+/* int flags;
+/*
+/* VSTRING *unquote_822_local(dst, src)
+/* VSTRING *dst;
+/* const char *src;
+/* DESCRIPTION
+/* quote_822_local() quotes the local part of a mailbox and
+/* returns a result that can be used in message headers as
+/* specified by RFC 822 (actually, an 8-bit clean version of
+/* RFC 822). It implements an 8-bit clean version of RFC 822.
+/*
+/* quote_822_local_flags() provides finer control.
+/*
+/* unquote_822_local() transforms the local part of a mailbox
+/* address to unquoted (internal) form.
+/*
+/* Arguments:
+/* .IP dst
+/* The result.
+/* .IP src
+/* The input address.
+/* .IP flags
+/* Bit-wise OR of zero or more of the following.
+/* .RS
+/* .IP QUOTE_FLAG_8BITCLEAN
+/* In violation with RFCs, treat 8-bit text as ordinary text.
+/* .IP QUOTE_FLAG_EXPOSE_AT
+/* In violation with RFCs, treat `@' as an ordinary character.
+/* .IP QUOTE_FLAG_APPEND
+/* Append to the result buffer, instead of overwriting it.
+/* .IP QUOTE_FLAG_BARE_LOCALPART
+/* The input is a localpart without @domain part.
+/* .RE
+/* STANDARDS
+/* RFC 822 (ARPA Internet Text Messages)
+/* BUGS
+/* The code assumes that the domain is RFC 822 clean.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+
+/* Global library. */
+
+/* Application-specific. */
+
+#include "quote_822_local.h"
+
+/* Local stuff. */
+
+#define YES 1
+#define NO 0
+
+/* is_822_dot_string - is this local-part an rfc 822 dot-string? */
+
+static int is_822_dot_string(const char *local_part, const char *end, int flags)
+{
+ const char *cp;
+ int ch;
+
+ /*
+ * Detect any deviations from a sequence of atoms separated by dots. We
+ * could use lookup tables to speed up some of the work, but hey, how
+ * large can a local-part be anyway?
+ *
+ * RFC 822 expects 7-bit data. Rather than quoting every 8-bit character
+ * (and still passing it on as 8-bit data) we leave 8-bit data alone.
+ */
+ if (local_part == end || local_part[0] == 0 || local_part[0] == '.')
+ return (NO);
+ for (cp = local_part; cp < end && (ch = *(unsigned char *) cp) != 0; cp++) {
+ if (ch == '.' && (cp + 1) < end && cp[1] == '.')
+ return (NO);
+ if (ch > 127 && !(flags & QUOTE_FLAG_8BITCLEAN))
+ return (NO);
+ if (ch == ' ')
+ return (NO);
+ if (ISCNTRL(ch))
+ return (NO);
+ if (ch == '(' || ch == ')'
+ || ch == '<' || ch == '>'
+ || (ch == '@' && !(flags & QUOTE_FLAG_EXPOSE_AT)) || ch == ','
+ || ch == ';' || ch == ':'
+ || ch == '\\' || ch == '"'
+ || ch == '[' || ch == ']')
+ return (NO);
+ }
+ if (cp[-1] == '.')
+ return (NO);
+ return (YES);
+}
+
+/* make_822_quoted_string - make quoted-string from local-part */
+
+static VSTRING *make_822_quoted_string(VSTRING *dst, const char *local_part,
+ const char *end, int flags)
+{
+ const char *cp;
+ int ch;
+
+ /*
+ * Put quotes around the result, and prepend a backslash to characters
+ * that need quoting when they occur in a quoted-string.
+ */
+ VSTRING_ADDCH(dst, '"');
+ for (cp = local_part; cp < end && (ch = *(unsigned char *) cp) != 0; cp++) {
+ if ((ch > 127 && !(flags & QUOTE_FLAG_8BITCLEAN))
+ || ch == '"' || ch == '\\' || ch == '\r')
+ VSTRING_ADDCH(dst, '\\');
+ VSTRING_ADDCH(dst, ch);
+ }
+ VSTRING_ADDCH(dst, '"');
+ return (dst);
+}
+
+/* quote_822_local_flags - quote local part of mailbox according to rfc 822 */
+
+VSTRING *quote_822_local_flags(VSTRING *dst, const char *mbox, int flags)
+{
+ const char *start; /* first byte of localpart */
+ const char *end; /* first byte after localpart */
+ const char *colon;
+
+ /*
+ * According to RFC 822, a local-part is a dot-string or a quoted-string.
+ * We first see if the local-part is a dot-string. If it is not, we turn
+ * it into a quoted-string. Anything else would be too painful. But
+ * first, skip over any source route that precedes the local-part.
+ */
+ if (mbox[0] == '@' && (colon = strchr(mbox, ':')) != 0)
+ start = colon + 1;
+ else
+ start = mbox;
+ if ((flags & QUOTE_FLAG_BARE_LOCALPART) != 0
+ || (end = strrchr(start, '@')) == 0)
+ end = start + strlen(start);
+ if ((flags & QUOTE_FLAG_APPEND) == 0)
+ VSTRING_RESET(dst);
+ if (is_822_dot_string(start, end, flags)) {
+ return (vstring_strcat(dst, mbox));
+ } else {
+ vstring_strncat(dst, mbox, start - mbox);
+ make_822_quoted_string(dst, start, end, flags & QUOTE_FLAG_8BITCLEAN);
+ return (vstring_strcat(dst, end));
+ }
+}
+
+/* unquote_822_local - unquote local part of mailbox according to rfc 822 */
+
+VSTRING *unquote_822_local(VSTRING *dst, const char *mbox)
+{
+ const char *start; /* first byte of localpart */
+ const char *colon;
+ const char *cp;
+ int in_quote = 0;
+ const char *bare_at_src;
+ int bare_at_dst_pos = -1;
+
+ /* Don't unquote a routing prefix. Is this still possible? */
+ if (mbox[0] == '@' && (colon = strchr(mbox, ':')) != 0) {
+ start = colon + 1;
+ vstring_strncpy(dst, mbox, start - mbox);
+ } else {
+ start = mbox;
+ VSTRING_RESET(dst);
+ }
+ /* Locate the last unquoted '@'. */
+ for (cp = start; *cp; cp++) {
+ if (*cp == '"') {
+ in_quote = !in_quote;
+ continue;
+ } else if (*cp == '@') {
+ if (!in_quote) {
+ bare_at_dst_pos = VSTRING_LEN(dst);
+ bare_at_src = cp;
+ }
+ } else if (*cp == '\\') {
+ if (cp[1] == 0)
+ continue;
+ cp++;
+ }
+ VSTRING_ADDCH(dst, *cp);
+ }
+ /* Don't unquote text after the last unquoted '@'. */
+ if (bare_at_dst_pos >= 0) {
+ vstring_truncate(dst, bare_at_dst_pos);
+ vstring_strcat(dst, bare_at_src);
+ } else
+ VSTRING_TERMINATE(dst);
+ return (dst);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program. Read an unquoted address from stdin, and
+ * show the quoted and unquoted results. Specify <> to test behavior for an
+ * empty unquoted address.
+ */
+#include <ctype.h>
+#include <string.h>
+
+#include <msg.h>
+#include <name_mask.h>
+#include <stringops.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+#define STR vstring_str
+
+int main(int unused_argc, char **argv)
+{
+ VSTRING *in = vstring_alloc(100);
+ VSTRING *out = vstring_alloc(100);
+ char *cmd;
+ char *bp;
+ int flags;
+
+ while (vstring_fgets_nonl(in, VSTREAM_IN)) {
+ bp = STR(in);
+ if ((cmd = mystrtok(&bp, CHARS_SPACE)) != 0) {
+ while (ISSPACE(*bp))
+ bp++;
+ if (*bp == 0) {
+ msg_warn("missing argument");
+ continue;
+ }
+ if (strcmp(bp, "<>") == 0)
+ bp = "";
+ if (strcmp(cmd, "quote") == 0) {
+ quote_822_local(out, bp);
+ vstream_printf("'%s' quoted '%s'\n", bp, STR(out));
+ } else if (strcmp(cmd, "quote_with_flags") == 0) {
+ if ((cmd = mystrtok(&bp, CHARS_SPACE)) == 0) {
+ msg_warn("missing flags");
+ continue;
+ }
+ while (ISSPACE(*bp))
+ bp++;
+ flags = quote_flags_from_string(cmd);
+ quote_822_local_flags(out, bp, flags);
+ vstream_printf("'%s' quoted flags=%s '%s'\n",
+ bp, quote_flags_to_string((VSTRING *) 0, flags), STR(out));
+ } else if (strcmp(cmd, "unquote") == 0) {
+ unquote_822_local(out, bp);
+ vstream_printf("'%s' unquoted '%s'\n", bp, STR(out));
+ } else {
+ msg_warn("unknown command: %s", cmd);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+ }
+ vstring_free(in);
+ vstring_free(out);
+ return (0);
+}
+
+#endif