/* $NetBSD: quote_822_local.c,v 1.3 2022/10/08 16:12:45 christos Exp $ */
/*++
/* 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