diff options
Diffstat (limited to 'src/global/quote_822_local.c')
-rw-r--r-- | src/global/quote_822_local.c | 294 |
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 |