X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/c6c2dc1dfc3928a5f6c61024b68e011db41884ad..39d16d738122fd008efb87326058dd34cfa6ff1e:/src/src/string.c diff --git a/src/src/string.c b/src/src/string.c index 1679e8850..0e73e2c79 100644 --- a/src/src/string.c +++ b/src/src/string.c @@ -1,10 +1,8 @@ -/* $Cambridge: exim/src/src/string.c,v 1.6 2005/06/17 13:52:15 ph10 Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2005 */ +/* Copyright (c) University of Cambridge 1995 - 2012 */ /* See the file NOTICE for conditions of use and distribution. */ /* Miscellaneous string-handling functions. Some are not required for @@ -28,6 +26,7 @@ Arguments: s a string maskptr NULL if no mask is permitted to follow otherwise, points to an int where the offset of '/' is placed + if there is no / followed by trailing digits, *maskptr is set 0 Returns: 0 if the string is not a textual representation of an IP address 4 if it is an IPv4 address @@ -127,7 +126,9 @@ if (Ustrchr(s, ':') != NULL) sign, which introduces the interface specifier (scope id) of a link local address. */ - if (!v4end) return (*s == 0 || *s == '%' || *s == '/')? yield : 0; + if (!v4end) + return (*s == 0 || *s == '%' || + (*s == '/' && maskptr != NULL && *maskptr != 0))? yield : 0; } /* Test for IPv4 address, which may be the tail-end of an IPv6 address. */ @@ -139,7 +140,8 @@ for (i = 0; i < 4; i++) if (isdigit(*s) && isdigit(*(++s))) s++; } -return (*s == 0 || *s == '/')? yield : 0; +return (*s == 0 || (*s == '/' && maskptr != NULL && *maskptr != 0))? + yield : 0; } #endif /* COMPILE_UTILITY */ @@ -208,7 +210,6 @@ return yield; -#ifndef COMPILE_UTILITY /************************************************* * Interpret escape sequence * *************************************************/ @@ -225,6 +226,9 @@ Returns: the value of the character escape int string_interpret_escape(uschar **pp) { +#ifdef COMPILE_UTILITY +const uschar *hex_digits= CUS"0123456789abcdef"; +#endif int ch; uschar *p = *pp; ch = *(++p); @@ -240,9 +244,12 @@ 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])) @@ -257,7 +264,6 @@ else switch(ch) *pp = p; return ch; } -#endif /* COMPILE_UTILITY */ @@ -329,6 +335,72 @@ return ss; } #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(&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; +} + @@ -343,7 +415,7 @@ Returns: copy of string in new store */ uschar * -string_copy(uschar *s) +string_copy(const uschar *s) { int len = Ustrlen(s) + 1; uschar *ss = store_get(len); @@ -445,6 +517,67 @@ return ss; +/************************************************* +* 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 * *************************************************/ @@ -576,14 +709,14 @@ Returns: pointer to fresh piece of store containing sprintf'ed 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, sizeof(buffer)); va_end(ap); return string_copy(buffer); } @@ -604,7 +737,7 @@ Returns: < 0, = 0, or > 0, according to the comparison */ int -strncmpic(uschar *s, uschar *t, int n) +strncmpic(const uschar *s, const uschar *t, int n) { while (n--) { @@ -628,7 +761,7 @@ Returns: < 0, = 0, or > 0, according to the comparison */ int -strcmpic(uschar *s, uschar *t) +strcmpic(const uschar *s, const uschar *t) { while (*s != 0) { @@ -699,19 +832,26 @@ return NULL; /* 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 0 && isspace(buffer[p-1])) p--; @@ -769,31 +924,37 @@ if (buffer != NULL) else { + int size = 0; + int ptr = 0; + 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_cat(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 */ @@ -946,7 +1107,7 @@ 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 +is also the addition of %D and %M, which insert the date in the form used for datestamped log files. Arguments: @@ -959,7 +1120,7 @@ Returns: TRUE if the result fitted in the buffer */ BOOL -string_format(uschar *buffer, int buflen, char *format, ...) +string_format(uschar *buffer, int buflen, const char *format, ...) { BOOL yield; va_list ap; @@ -971,17 +1132,20 @@ return yield; 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 */ @@ -990,8 +1154,8 @@ while (*fp != 0) 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 */ @@ -1041,7 +1205,7 @@ while (*fp != 0) } } - /* 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; } @@ -1060,6 +1224,8 @@ while (*fp != 0) length = L_LONG; } } + else if (*fp == 'z') + { fp++; length = L_SIZE; } /* Handle each specific format type. */ @@ -1075,7 +1241,8 @@ while (*fp != 0) case 'u': case 'x': case 'X': - if (p >= last - 24) { yield = FALSE; goto END_FORMAT; } + if (p >= last - ((length > L_LONG)? 24 : 12)) + { yield = FALSE; goto END_FORMAT; } strncpy(newformat, item_start, fp - item_start); newformat[fp - item_start] = 0; @@ -1088,6 +1255,7 @@ while (*fp != 0) 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; @@ -1135,19 +1303,31 @@ while (*fp != 0) *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 */ 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. */ @@ -1173,10 +1353,17 @@ while (*fp != 0) 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') @@ -1225,7 +1412,7 @@ Returns: a message, in dynamic store */ uschar * -string_open_failed(int eno, char *format, ...) +string_open_failed(int eno, const char *format, ...) { va_list ap; uschar buffer[1024]; @@ -1467,8 +1654,10 @@ printf("Testing string_format\n"); while (fgets(CS buffer, sizeof(buffer), stdin) != NULL) { void *args[3]; + long long llargs[3]; double dargs[3]; int dflag = 0; + int llflag = 0; int n = 0; int count; int countset = 0; @@ -1499,6 +1688,11 @@ while (fgets(CS buffer, sizeof(buffer), stdin) != NULL) dflag = 1; dargs[n++] = Ustrtod(outbuf, NULL); } + else if (Ustrstr(outbuf, "ll") != NULL) + { + llflag = 1; + llargs[n++] = strtoull(CS outbuf, NULL, 10); + } else { args[n++] = (void *)Uatoi(outbuf); @@ -1521,11 +1715,16 @@ while (fgets(CS buffer, sizeof(buffer), stdin) != NULL) if (*s == ',') s++; } - if (!dflag) printf("%s\n", string_format(outbuf, sizeof(outbuf), CS format, - args[0], args[1], args[2])? "True" : "False"); + if (!dflag && !llflag) + printf("%s\n", string_format(outbuf, sizeof(outbuf), CS format, + args[0], args[1], args[2])? "True" : "False"); + + else if (dflag) + printf("%s\n", string_format(outbuf, sizeof(outbuf), CS format, + dargs[0], dargs[1], dargs[2])? "True" : "False"); else printf("%s\n", string_format(outbuf, sizeof(outbuf), CS format, - dargs[0], dargs[1], dargs[2])? "True" : "False"); + llargs[0], llargs[1], llargs[2])? "True" : "False"); printf("%s\n", CS outbuf); if (countset) printf("count=%d\n", count);