-/* $Cambridge: exim/src/src/string.c,v 1.10 2006/02/23 10:25:45 ph10 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2006 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
/* See the file NOTICE for conditions of use and distribution. */
/* Miscellaneous string-handling functions. Some are not required for
*/
int
-string_is_ip_address(uschar *s, int *maskptr)
+string_is_ip_address(const uschar *s, int *maskptr)
{
int i;
int yield = 4;
if (maskptr != NULL)
{
- uschar *ss = s + Ustrlen(s);
+ const uschar *ss = s + Ustrlen(s);
*maskptr = 0;
if (s != ss && isdigit(*(--ss)))
{
uschar *
string_format_size(int size, uschar *buffer)
{
-if (size == 0) Ustrcpy(CS buffer, " ");
+if (size == 0) Ustrcpy(buffer, " ");
else if (size < 1024) sprintf(CS buffer, "%5d", size);
else if (size < 10*1024)
sprintf(CS buffer, "%4.1fK", (double)size / 1024.0);
-#ifndef COMPILE_UTILITY
/*************************************************
* Interpret escape sequence *
*************************************************/
*/
int
-string_interpret_escape(uschar **pp)
+string_interpret_escape(const uschar **pp)
{
+#ifdef COMPILE_UTILITY
+const uschar *hex_digits= CUS"0123456789abcdef";
+#endif
int ch;
-uschar *p = *pp;
+const uschar *p = *pp;
ch = *(++p);
if (isdigit(ch) && ch != '8' && ch != '9')
{
}
else switch(ch)
{
+ case 'b': ch = '\b'; break;
+ case 'f': ch = '\f'; break;
case 'n': ch = '\n'; break;
case 'r': ch = '\r'; break;
case 't': ch = '\t'; break;
+ case 'v': ch = '\v'; break;
case 'x':
ch = 0;
if (isxdigit(p[1]))
*pp = p;
return ch;
}
-#endif /* COMPILE_UTILITY */
Returns: string with non-printers encoded as printing sequences
*/
-uschar *
-string_printing2(uschar *s, BOOL allow_tab)
+const uschar *
+string_printing2(const uschar *s, BOOL allow_tab)
{
int nonprintcount = 0;
int length = 0;
-uschar *t = s;
+const uschar *t = s;
uschar *ss, *tt;
while (*t != 0)
/* Get a new block of store guaranteed big enough to hold the
expanded string. */
-ss = store_get(length + nonprintcount * 4 + 1);
+ss = store_get(length + nonprintcount * 3 + 1);
/* Copy everying, escaping non printers. */
}
#endif /* COMPILE_UTILITY */
+/*************************************************
+* Undo printing escapes in string *
+*************************************************/
+
+/* This function is the reverse of string_printing2. It searches for
+backslash characters and if any are found, it makes a new copy of the
+string with escape sequences parsed. Otherwise it returns the original
+string.
+
+Arguments:
+ s the input string
+
+Returns: string with printing escapes parsed back
+*/
+
+uschar *
+string_unprinting(uschar *s)
+{
+uschar *p, *q, *r, *ss;
+int len, off;
+
+p = Ustrchr(s, '\\');
+if (!p) return s;
+
+len = Ustrlen(s) + 1;
+ss = store_get(len);
+
+q = ss;
+off = p - s;
+if (off)
+ {
+ memcpy(q, s, off);
+ q += off;
+ }
+
+while (*p)
+ {
+ if (*p == '\\')
+ {
+ *q++ = string_interpret_escape((const uschar **)&p);
+ p++;
+ }
+ else
+ {
+ r = Ustrchr(p, '\\');
+ if (!r)
+ {
+ off = Ustrlen(p);
+ memcpy(q, p, off);
+ p += off;
+ q += off;
+ break;
+ }
+ else
+ {
+ off = r - p;
+ memcpy(q, p, off);
+ q += off;
+ p = r;
+ }
+ }
+ }
+*q = '\0';
+
+return ss;
+}
+
*/
uschar *
-string_copy(uschar *s)
+string_copy(const uschar *s)
{
int len = Ustrlen(s) + 1;
uschar *ss = store_get(len);
*/
uschar *
-string_copy_malloc(uschar *s)
+string_copy_malloc(const uschar *s)
{
int len = Ustrlen(s) + 1;
uschar *ss = store_malloc(len);
*/
uschar *
-string_copylc(uschar *s)
+string_copylc(const uschar *s)
{
uschar *ss = store_get(Ustrlen(s) + 1);
uschar *p = ss;
*/
uschar *
-string_copyn(uschar *s, int n)
+string_copyn(const uschar *s, int n)
{
uschar *ss = store_get(n + 1);
Ustrncpy(ss, s, n);
+/*************************************************
+* Copy string if long, inserting newlines *
+*************************************************/
+
+/* If the given string is longer than 75 characters, it is copied, and within
+the copy, certain space characters are converted into newlines.
+
+Argument: pointer to the string
+Returns: pointer to the possibly altered string
+*/
+
+uschar *
+string_split_message(uschar *msg)
+{
+uschar *s, *ss;
+
+if (msg == NULL || Ustrlen(msg) <= 75) return msg;
+s = ss = msg = string_copy(msg);
+
+for (;;)
+ {
+ int i = 0;
+ while (i < 75 && *ss != 0 && *ss != '\n') ss++, i++;
+ if (*ss == 0) break;
+ if (*ss == '\n')
+ s = ++ss;
+ else
+ {
+ uschar *t = ss + 1;
+ uschar *tt = NULL;
+ while (--t > s + 35)
+ {
+ if (*t == ' ')
+ {
+ if (t[-1] == ':') { tt = t; break; }
+ if (tt == NULL) tt = t;
+ }
+ }
+
+ if (tt == NULL) /* Can't split behind - try ahead */
+ {
+ t = ss + 1;
+ while (*t != 0)
+ {
+ if (*t == ' ' || *t == '\n')
+ { tt = t; break; }
+ t++;
+ }
+ }
+
+ if (tt == NULL) break; /* Can't find anywhere to split */
+ *tt = '\n';
+ s = ss = tt+1;
+ }
+ }
+
+return msg;
+}
+
+
+
/*************************************************
* Copy returned DNS domain name, de-escaping *
*************************************************/
*/
uschar *
-string_dequote(uschar **sptr)
+string_dequote(const uschar **sptr)
{
-uschar *s = *sptr;
+const uschar *s = *sptr;
uschar *t, *yield;
/* First find the end of the string */
*/
uschar *
-string_sprintf(char *format, ...)
+string_sprintf(const char *format, ...)
{
va_list ap;
uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
va_start(ap, format);
if (!string_vformat(buffer, sizeof(buffer), format, ap))
log_write(0, LOG_MAIN|LOG_PANIC_DIE,
- "string_sprintf expansion was longer than %d", sizeof(buffer));
+ "string_sprintf expansion was longer than " SIZE_T_FMT
+ "; format string was (%s)\nexpansion started '%.32s'",
+ sizeof(buffer), format, buffer);
va_end(ap);
return string_copy(buffer);
}
*/
int
-strncmpic(uschar *s, uschar *t, int n)
+strncmpic(const uschar *s, const uschar *t, int n)
{
while (n--)
{
*/
int
-strcmpic(uschar *s, uschar *t)
+strcmpic(const uschar *s, const uschar *t)
{
while (*s != 0)
{
/* Leading and trailing space is removed from each item. The separator in the
list is controlled by the int pointed to by the separator argument as follows:
- If its value is > 0 it is used as the delimiter.
- (If its value is actually > UCHAR_MAX there is only one item in the list.
+ If the value is > 0 it is used as the separator. This is typically used for
+ sublists such as slash-separated options. The value is always a printing
+ character.
+
+ (If the value is actually > UCHAR_MAX there is only one item in the list.
This is used for some cases when called via functions that sometimes
plough through lists, and sometimes are given single items.)
- If its value is <= 0, the string is inspected for a leading <x, where
- x is an ispunct() value. If found, it is used as the delimiter. If not
- found: (a) if separator == 0, ':' is used
- (b) if separator <0, then -separator is used
- In all cases the value of the separator that is used is written back to
- the int so that it is used on subsequent calls as we progress through
- the list.
-The separator can always be represented in the string by doubling.
+ If the value is <= 0, the string is inspected for a leading <x, where x is an
+ ispunct() or an iscntrl() character. If found, x is used as the separator. If
+ not found:
+
+ (a) if separator == 0, ':' is used
+ (b) if separator <0, -separator is used
+
+ In all cases the value of the separator that is used is written back to the
+ int so that it is used on subsequent calls as we progress through the list.
+
+A literal ispunct() separator can be represented in an item by doubling, but
+there is no way to include an iscntrl() separator as part of the data.
Arguments:
listptr points to a pointer to the current start of the list; the
*/
uschar *
-string_nextinlist(uschar **listptr, int *separator, uschar *buffer, int buflen)
+string_nextinlist(const uschar **listptr, int *separator, uschar *buffer, int buflen)
{
-register int p = 0;
-register int sep = *separator;
-register uschar *s = *listptr;
+int sep = *separator;
+const uschar *s = *listptr;
+BOOL sep_is_special;
if (s == NULL) return NULL;
-while (isspace(*s)) s++;
+
+/* This allows for a fixed specified separator to be an iscntrl() character,
+but at the time of implementation, this is never the case. However, it's best
+to be conservative. */
+
+while (isspace(*s) && *s != sep) s++;
+
+/* A change of separator is permitted, so look for a leading '<' followed by an
+allowed character. */
if (sep <= 0)
{
- if (*s == '<' && ispunct(s[1]))
+ if (*s == '<' && (ispunct(s[1]) || iscntrl(s[1])))
{
sep = s[1];
s += 2;
- while (isspace(*s)) s++;
+ while (isspace(*s) && *s != sep) s++;
}
else
{
*separator = sep;
}
+/* An empty string has no list elements */
+
if (*s == 0) return NULL;
+/* Note whether whether or not the separator is an iscntrl() character. */
+
+sep_is_special = iscntrl(sep);
+
/* Handle the case when a buffer is provided. */
if (buffer != NULL)
{
+ int p = 0;
for (; *s != 0; s++)
{
- if (*s == sep && *(++s) != sep) break;
+ if (*s == sep && (*(++s) != sep || sep_is_special)) break;
if (p < buflen - 1) buffer[p++] = *s;
}
while (p > 0 && isspace(buffer[p-1])) p--;
else
{
+ int size = 0;
+ int ptr = 0;
+ const uschar *ss;
+
/* We know that *s != 0 at this point. However, it might be pointing to a
- separator, which could indicate an empty string, or could be doubled to
- indicate a separator character as data at the start of a string. */
+ separator, which could indicate an empty string, or (if an ispunct()
+ character) could be doubled to indicate a separator character as data at the
+ start of a string. Avoid getting working memory for an empty item. */
if (*s == sep)
{
s++;
- if (*s != sep) buffer = string_copy(US"");
+ if (*s != sep || sep_is_special)
+ {
+ *listptr = s;
+ return string_copy(US"");
+ }
}
- if (buffer == NULL)
+ /* Not an empty string; the first character is guaranteed to be a data
+ character. */
+
+ for (;;)
{
- int size = 0;
- int ptr = 0;
- uschar *ss;
- for (;;)
- {
- for (ss = s + 1; *ss != 0 && *ss != sep; ss++);
- buffer = string_cat(buffer, &size, &ptr, s, ss-s);
- s = ss;
- if (*s == 0 || *(++s) != sep) break;
- }
- while (ptr > 0 && isspace(buffer[ptr-1])) ptr--;
- buffer[ptr] = 0;
+ for (ss = s + 1; *ss != 0 && *ss != sep; ss++);
+ buffer = string_catn(buffer, &size, &ptr, s, ss-s);
+ s = ss;
+ if (*s == 0 || *(++s) != sep || sep_is_special) break;
}
+ while (ptr > 0 && isspace(buffer[ptr-1])) ptr--;
+ buffer[ptr] = 0;
}
/* Update the current pointer and return the new string */
#endif /* COMPILE_UTILITY */
+#ifndef COMPILE_UTILITY
+/************************************************
+* Add element to separated list *
+************************************************/
+/* This function is used to build a list, returning
+an allocated null-terminated growable string. The
+given element has any embedded seperator characters
+doubled.
+
+Arguments:
+ list points to the start of the list that is being built, or NULL
+ if this is a new list that has no contents yet
+ sep list seperator charactoer
+ ele new lement to be appended to the list
+
+Returns: pointer to the start of the list, changed if copied for expansion.
+*/
+
+uschar *
+string_append_listele(uschar * list, uschar sep, const uschar * ele)
+{
+uschar * new = NULL;
+int sz = 0, off = 0;
+uschar * sp;
+
+if (list)
+ {
+ new = string_cat (new, &sz, &off, list);
+ new = string_catn(new, &sz, &off, &sep, 1);
+ }
+
+while((sp = Ustrchr(ele, sep)))
+ {
+ new = string_catn(new, &sz, &off, ele, sp-ele+1);
+ new = string_catn(new, &sz, &off, &sep, 1);
+ ele = sp+1;
+ }
+new = string_cat(new, &sz, &off, ele);
+new[off] = '\0';
+return new;
+}
+
+
+static const uschar *
+Ustrnchr(const uschar * s, int c, unsigned * len)
+{
+unsigned siz = *len;
+while (siz)
+ {
+ if (!*s) return NULL;
+ if (*s == c)
+ {
+ *len = siz;
+ return s;
+ }
+ s++;
+ siz--;
+ }
+return NULL;
+}
+
+uschar *
+string_append_listele_n(uschar * list, uschar sep, const uschar * ele,
+ unsigned len)
+{
+uschar * new = NULL;
+int sz = 0, off = 0;
+const uschar * sp;
+
+if (list)
+ {
+ new = string_cat (new, &sz, &off, list);
+ new = string_catn(new, &sz, &off, &sep, 1);
+ }
+
+while((sp = Ustrnchr(ele, sep, &len)))
+ {
+ new = string_catn(new, &sz, &off, ele, sp-ele+1);
+ new = string_catn(new, &sz, &off, &sep, 1);
+ ele = sp+1;
+ len--;
+ }
+new = string_catn(new, &sz, &off, ele, len);
+new[off] = '\0';
+return new;
+}
+#endif /* COMPILE_UTILITY */
+
+
#ifndef COMPILE_UTILITY
/*************************************************
characters, updated to the new offset
s points to characters to add
count count of characters to add; must not exceed the length of s, if s
- is a C string
+ is a C string. If -1 given, strlen(s) is used.
If string is given as NULL, *size and *ptr should both be zero.
Note that a NUL is not added, though space is left for one. This is
because string_cat() is often called multiple times to build up a
string - there's no point adding the NUL till the end.
+
*/
+/* coverity[+alloc] */
uschar *
-string_cat(uschar *string, int *size, int *ptr, const uschar *s, int count)
+string_catn(uschar *string, int *size, int *ptr, const uschar *s, int count)
{
int p = *ptr;
/* Because we always specify the exact number of characters to copy, we can
use memcpy(), which is likely to be more efficient than strncopy() because the
-latter has to check for zero bytes. */
+latter has to check for zero bytes.
+
+The Coverity annotation deals with the lack of correlated variable tracking;
+common use is a null string and zero size and pointer, on first use for a
+string being built. The "if" above then allocates, but Coverity assume that
+the "if" might not happen and whines for a null-deref done by the memcpy(). */
+/* coverity[deref_parm_field_in_call] : FALSE */
memcpy(string + p, s, count);
*ptr = p + count;
return string;
}
+
+
+uschar *
+string_cat(uschar *string, int *size, int *ptr, const uschar *s)
+{
+return string_catn(string, size, ptr, s, Ustrlen(s));
+}
#endif /* COMPILE_UTILITY */
for (i = 0; i < count; i++)
{
uschar *t = va_arg(ap, uschar *);
- string = string_cat(string, size, ptr, t, Ustrlen(t));
+ string = string_cat(string, size, ptr, t);
}
va_end(ap);
as a va_list item.
The formats are the usual printf() ones, with some omissions (never used) and
-two additions for strings: %S forces lower case, and %#s or %#S prints nothing
-for a NULL string. Without the # "NULL" is printed (useful in debugging). There
-is also the addition of %D, which inserts the date in the form used for
-datestamped log files.
+three additions for strings: %S forces lower case, %T forces upper case, and
+%#s or %#S prints nothing for a NULL string. Without thr # "NULL" is printed
+(useful in debugging). There is also the addition of %D and %M, which insert
+the date in the form used for datestamped log files.
Arguments:
buffer a buffer in which to put the formatted string
*/
BOOL
-string_format(uschar *buffer, int buflen, char *format, ...)
+string_format(uschar *buffer, int buflen, const char *format, ...)
{
BOOL yield;
va_list ap;
BOOL
-string_vformat(uschar *buffer, int buflen, char *format, va_list ap)
+string_vformat(uschar *buffer, int buflen, const char *format, va_list ap)
{
-enum { L_NORMAL, L_SHORT, L_LONG, L_LONGLONG, L_LONGDOUBLE };
+/* We assume numbered ascending order, C does not guarantee that */
+enum { L_NORMAL=1, L_SHORT=2, L_LONG=3, L_LONGLONG=4, L_LONGDOUBLE=5, L_SIZE=6 };
BOOL yield = TRUE;
int width, precision;
-char *fp = format; /* Deliberately not unsigned */
+const char *fp = format; /* Deliberately not unsigned */
uschar *p = buffer;
uschar *last = buffer + buflen - 1;
string_datestamp_offset = -1; /* Datestamp not inserted */
+string_datestamp_length = 0; /* Datestamp not inserted */
+string_datestamp_type = 0; /* Datestamp not inserted */
/* Scan the format and handle the insertions */
int length = L_NORMAL;
int *nptr;
int slen;
- char *null = "NULL"; /* ) These variables */
- char *item_start, *s; /* ) are deliberately */
+ const char *null = "NULL"; /* ) These variables */
+ const char *item_start, *s; /* ) are deliberately */
char newformat[16]; /* ) not unsigned */
/* Non-% characters just get copied verbatim */
}
}
- /* Skip over 'h', 'L', 'l', and 'll', remembering the item length */
+ /* Skip over 'h', 'L', 'l', 'll' and 'z', remembering the item length */
if (*fp == 'h')
{ fp++; length = L_SHORT; }
length = L_LONG;
}
}
+ else if (*fp == 'z')
+ { fp++; length = L_SIZE; }
/* Handle each specific format type. */
case L_NORMAL: sprintf(CS p, newformat, va_arg(ap, int)); break;
case L_LONG: sprintf(CS p, newformat, va_arg(ap, long int)); break;
case L_LONGLONG: sprintf(CS p, newformat, va_arg(ap, LONGLONG_T)); break;
+ case L_SIZE: sprintf(CS p, newformat, va_arg(ap, size_t)); break;
}
while (*p) p++;
break;
*p++ = va_arg(ap, int);
break;
- case 'D': /* Insert datestamp for log file names */
- s = CS tod_stamp(tod_log_datestamp);
+ case 'D': /* Insert daily datestamp for log file names */
+ s = CS tod_stamp(tod_log_datestamp_daily);
+ string_datestamp_offset = p - buffer; /* Passed back via global */
+ string_datestamp_length = Ustrlen(s); /* Passed back via global */
+ string_datestamp_type = tod_log_datestamp_daily;
+ slen = string_datestamp_length;
+ goto INSERT_STRING;
+
+ case 'M': /* Insert monthly datestamp for log file names */
+ s = CS tod_stamp(tod_log_datestamp_monthly);
string_datestamp_offset = p - buffer; /* Passed back via global */
+ string_datestamp_length = Ustrlen(s); /* Passed back via global */
+ string_datestamp_type = tod_log_datestamp_monthly;
+ slen = string_datestamp_length;
goto INSERT_STRING;
case 's':
case 'S': /* Forces *lower* case */
+ case 'T': /* Forces *upper* case */
s = va_arg(ap, char *);
- INSERT_STRING: /* Come to from %D above */
if (s == NULL) s = null;
slen = Ustrlen(s);
+ INSERT_STRING: /* Come to from %D or %M above */
+
/* If the width is specified, check that there is a precision
set; if not, set it to the width to prevent overruns of long
strings. */
not OK, add part of the string (debugging uses this to show as
much as possible). */
+ if (p == last)
+ {
+ yield = FALSE;
+ goto END_FORMAT;
+ }
if (p >= last - width)
{
yield = FALSE;
width = precision = last - p - 1;
+ if (width < 0) width = 0;
+ if (precision < 0) precision = 0;
}
sprintf(CS p, "%*.*s", width, precision, s);
if (fp[-1] == 'S')
while (*p) { *p = tolower(*p); p++; }
+ else if (fp[-1] == 'T')
+ while (*p) { *p = toupper(*p); p++; }
else
while (*p) p++;
if (!yield) goto END_FORMAT;
*/
uschar *
-string_open_failed(int eno, char *format, ...)
+string_open_failed(int eno, const char *format, ...)
{
va_list ap;
uschar buffer[1024];
doesn't seem much we can do about that. */
(void)string_vformat(buffer+15, sizeof(buffer) - 15, format, ap);
+va_end(ap);
return (eno == EACCES)?
string_sprintf("%s: %s (euid=%ld egid=%ld)", buffer, strerror(eno),
-#ifndef COMPILE_UTILITY
-/*************************************************
-* Generate local prt for logging *
-*************************************************/
-
-/* This function is a subroutine for use in string_log_address() below.
-Arguments:
- addr the address being logged
- yield the current dynamic buffer pointer
- sizeptr points to current size
- ptrptr points to current insert pointer
-Returns: the new value of the buffer pointer
-*/
+#ifndef COMPILE_UTILITY
+/* qsort(3), currently used to sort the environment variables
+for -bP environment output, needs a function to compare two pointers to string
+pointers. Here it is. */
-static uschar *
-string_get_localpart(address_item *addr, uschar *yield, int *sizeptr,
- int *ptrptr)
+int
+string_compare_by_pointer(const void *a, const void *b)
{
-if (testflag(addr, af_include_affixes) && addr->prefix != NULL)
- yield = string_cat(yield, sizeptr, ptrptr, addr->prefix,
- Ustrlen(addr->prefix));
-yield = string_cat(yield, sizeptr, ptrptr, addr->local_part,
- Ustrlen(addr->local_part));
-if (testflag(addr, af_include_affixes) && addr->suffix != NULL)
- yield = string_cat(yield, sizeptr, ptrptr, addr->suffix,
- Ustrlen(addr->suffix));
-return yield;
+return Ustrcmp(* CUSS a, * CUSS b);
}
-
-
-/*************************************************
-* Generate log address list *
-*************************************************/
-
-/* This function generates a list consisting of an address and its parents, for
-use in logging lines. For saved onetime aliased addresses, the onetime parent
-field is used. If the address was delivered by a transport with rcpt_include_
-affixes set, the af_include_affixes bit will be set in the address. In that
-case, we include the affixes here too.
-
-Arguments:
- addr bottom (ultimate) address
- all_parents if TRUE, include all parents
- success TRUE for successful delivery
-
-Returns: a string in dynamic store
-*/
-
-uschar *
-string_log_address(address_item *addr, BOOL all_parents, BOOL success)
-{
-int size = 64;
-int ptr = 0;
-BOOL add_topaddr = TRUE;
-uschar *yield = store_get(size);
-address_item *topaddr;
-
-/* Find the ultimate parent */
-
-for (topaddr = addr; topaddr->parent != NULL; topaddr = topaddr->parent);
-
-/* We start with just the local part for pipe, file, and reply deliveries, and
-for successful local deliveries from routers that have the log_as_local flag
-set. File deliveries from filters can be specified as non-absolute paths in
-cases where the transport is goin to complete the path. If there is an error
-before this happens (expansion failure) the local part will not be updated, and
-so won't necessarily look like a path. Add extra text for this case. */
-
-if (testflag(addr, af_pfr) ||
- (success &&
- addr->router != NULL && addr->router->log_as_local &&
- addr->transport != NULL && addr->transport->info->local))
- {
- if (testflag(addr, af_file) && addr->local_part[0] != '/')
- yield = string_cat(yield, &size, &ptr, CUS"save ", 5);
- yield = string_get_localpart(addr, yield, &size, &ptr);
- }
-
-/* Other deliveries start with the full address. It we have split it into local
-part and domain, use those fields. Some early failures can happen before the
-splitting is done; in those cases use the original field. */
-
-else
- {
- if (addr->local_part != NULL)
- {
- yield = string_get_localpart(addr, yield, &size, &ptr);
- yield = string_cat(yield, &size, &ptr, US"@", 1);
- yield = string_cat(yield, &size, &ptr, addr->domain,
- Ustrlen(addr->domain) );
- }
- else
- {
- yield = string_cat(yield, &size, &ptr, addr->address, Ustrlen(addr->address));
- }
- yield[ptr] = 0;
-
- /* If the address we are going to print is the same as the top address,
- and all parents are not being included, don't add on the top address. First
- of all, do a caseless comparison; if this succeeds, do a caseful comparison
- on the local parts. */
-
- if (strcmpic(yield, topaddr->address) == 0 &&
- Ustrncmp(yield, topaddr->address, Ustrchr(yield, '@') - yield) == 0 &&
- addr->onetime_parent == NULL &&
- (!all_parents || addr->parent == NULL || addr->parent == topaddr))
- add_topaddr = FALSE;
- }
-
-/* If all parents are requested, or this is a local pipe/file/reply, and
-there is at least one intermediate parent, show it in brackets, and continue
-with all of them if all are wanted. */
-
-if ((all_parents || testflag(addr, af_pfr)) &&
- addr->parent != NULL &&
- addr->parent != topaddr)
- {
- uschar *s = US" (";
- address_item *addr2;
- for (addr2 = addr->parent; addr2 != topaddr; addr2 = addr2->parent)
- {
- yield = string_cat(yield, &size, &ptr, s, 2);
- yield = string_cat(yield, &size, &ptr, addr2->address, Ustrlen(addr2->address));
- if (!all_parents) break;
- s = US", ";
- }
- yield = string_cat(yield, &size, &ptr, US")", 1);
- }
-
-/* Add the top address if it is required */
-
-if (add_topaddr)
- {
- yield = string_cat(yield, &size, &ptr, US" <", 2);
-
- if (addr->onetime_parent == NULL)
- yield = string_cat(yield, &size, &ptr, topaddr->address,
- Ustrlen(topaddr->address));
- else
- yield = string_cat(yield, &size, &ptr, addr->onetime_parent,
- Ustrlen(addr->onetime_parent));
-
- yield = string_cat(yield, &size, &ptr, US">", 1);
- }
-
-yield[ptr] = 0; /* string_cat() leaves space */
-return yield;
-}
-#endif /* COMPILE_UTILITY */
-
-
+#endif /* COMPILE_UTILITY */