diff options
Diffstat (limited to 'WWW/Library/Implementation/HTString.c')
-rw-r--r-- | WWW/Library/Implementation/HTString.c | 1464 |
1 files changed, 1464 insertions, 0 deletions
diff --git a/WWW/Library/Implementation/HTString.c b/WWW/Library/Implementation/HTString.c new file mode 100644 index 0000000..3cb8358 --- /dev/null +++ b/WWW/Library/Implementation/HTString.c @@ -0,0 +1,1464 @@ +/* + * $LynxId: HTString.c,v 1.82 2022/03/12 12:19:10 Gisle.Vanem Exp $ + * + * Case-independent string comparison HTString.c + * + * Original version came with listserv implementation. + * Version TBL Oct 91 replaces one which modified the strings. + * 02-Dec-91 (JFG) Added stralloccopy and stralloccat + * 23 Jan 92 (TBL) Changed strallocc* to 8 char HTSAC* for VM and suchlike + * 6 Oct 92 (TBL) Moved WWW_TraceFlag in here to be in library + * 15 Nov 98 (TD) Added HTSprintf. + */ + +#include <HTUtils.h> +#include <HTFile.h> + +#include <LYLeaks.h> +#include <LYUtils.h> +#include <LYStrings.h> + +#ifdef USE_IGNORE_RC +int ignore_unused; +#endif + +#ifndef NO_LYNX_TRACE +BOOLEAN WWW_TraceFlag = 0; /* Global trace flag for ALL W3 code */ +int WWW_TraceMask = 0; /* Global trace flag for ALL W3 code */ +#endif + +#ifdef _WINDOWS +#undef VC +#define VC "2.14FM" +#endif + +#ifndef VC +#define VC "2.14" +#endif /* !VC */ + +const char *HTLibraryVersion = VC; /* String for help screen etc */ + +/* + * strcasecomp8 is a variant of strcasecomp (below) + * ------------ ----------- + * but uses 8bit upper/lower case information + * from the current display charset. + * It returns 0 if exact match. + */ +int strcasecomp8(const char *a, + const char *b) +{ + const char *p = a; + const char *q = b; + + for (; *p && *q; p++, q++) { + int diff = UPPER8(*p, *q); + + if (diff) + return diff; + } + if (*p) + return 1; /* p was longer than q */ + if (*q) + return -1; /* p was shorter than q */ + return 0; /* Exact match */ +} + +/* + * strncasecomp8 is a variant of strncasecomp (below) + * ------------- ------------ + * but uses 8bit upper/lower case information + * from the current display charset. + * It returns 0 if exact match. + */ +int strncasecomp8(const char *a, + const char *b, + int n) +{ + const char *p = a; + const char *q = b; + + for (;; p++, q++) { + int diff; + + if (p == (a + n)) + return 0; /* Match up to n characters */ + if (!(*p && *q)) + return (*p - *q); + diff = UPPER8(*p, *q); + if (diff) + return diff; + } + /*NOTREACHED */ +} + +#ifndef VM /* VM has these already it seems */ +/* Strings of any length + * --------------------- + */ +int strcasecomp(const char *a, + const char *b) +{ + const char *p = a; + const char *q = b; + + for (; *p && *q; p++, q++) { + int diff = TOLOWER(*p) - TOLOWER(*q); + + if (diff) + return diff; + } + if (*p) + return 1; /* p was longer than q */ + if (*q) + return -1; /* p was shorter than q */ + return 0; /* Exact match */ +} + +/* With count limit + * ---------------- + */ +int strncasecomp(const char *a, + const char *b, + int n) +{ + const char *p = a; + const char *q = b; + + for (;; p++, q++) { + int diff; + + if (p == (a + n)) + return 0; /* Match up to n characters */ + if (!(*p && *q)) + return (*p - *q); + diff = TOLOWER(*p) - TOLOWER(*q); + if (diff) + return diff; + } + /*NOTREACHED */ +} +#endif /* VM */ + +#define end_component(p) (*(p) == '.' || *(p) == '\0') + +#ifdef DEBUG_ASTERISK +#define SHOW_ASTERISK CTRACE +#else +#define SHOW_ASTERISK(p) /* nothing */ +#endif + +#define SHOW_ASTERISK_NUM(a,b,c) \ + SHOW_ASTERISK((tfp, "test @%d, '%s' vs '%s' (%d)\n", __LINE__, a,b,c)) + +#define SHOW_ASTERISK_TXT(a,b,c) \ + SHOW_ASTERISK((tfp, "test @%d, '%s' vs '%s' %s\n", __LINE__, a,b,c)) + +/* + * Compare names as described in RFC 2818: ignore case, allow wildcards. + * Return zero on a match, nonzero on mismatch -TD + * + * From RFC 2818: + * Names may contain the wildcard character * which is considered to match any + * single domain name component or component fragment. E.g., *.a.com matches + * foo.a.com but not bar.foo.a.com. f*.com matches foo.com but not bar.com. + */ +int strcasecomp_asterisk(const char *a, const char *b) +{ + const char *p; + int result = 0; + int done = FALSE; + + while (!result && !done) { + SHOW_ASTERISK_TXT(a, b, "main"); + if (*a == '*') { + p = b; + for (;;) { + SHOW_ASTERISK_TXT(a, p, "loop"); + if (end_component(p)) { + if (end_component(a + 1)) { + b = p - 1; + result = 0; + } else { + result = 1; + } + break; + } else if (strcasecomp_asterisk(a + 1, p)) { + ++p; + } else { + b = p - 1; + result = 0; /* found a match starting at 'p' */ + done = TRUE; + break; + } + } + SHOW_ASTERISK_NUM(a, b, result); + } else if (*b == '*') { + result = strcasecomp_asterisk(b, a); + SHOW_ASTERISK_NUM(a, b, result); + done = (result == 0); + } else if (*a == '\0' || *b == '\0') { + result = (*a != *b); + SHOW_ASTERISK_NUM(a, b, result); + break; + } else if (TOLOWER(UCH(*a)) != TOLOWER(UCH(*b))) { + result = 1; + SHOW_ASTERISK_NUM(a, b, result); + break; + } + ++a; + ++b; + } + return result; +} + +#ifdef DEBUG_ASTERISK +void mismatch_asterisk(void) +{ + /* *INDENT-OFF* */ + static struct { + const char *a; + const char *b; + int code; + } table[] = { + { "foo.bar", "*.*", 0 }, + { "foo.bar", "*.b*", 0 }, + { "foo.bar", "*.ba*", 0 }, + { "foo.bar", "*.bar*", 0 }, + { "foo.bar", "*.*bar*", 0 }, + { "foo.bar", "*.*.", 1 }, + { "foo.bar", "fo*.b*", 0 }, + { "*oo.bar", "fo*.b*", 0 }, + { "*oo.bar.com", "fo*.b*", 1 }, + { "*oo.bar.com", "fo*.b*m", 1 }, + { "*oo.bar.com", "fo*.b*.c*", 0 }, + }; + /* *INDENT-ON* */ + + unsigned n; + int code; + + CTRACE((tfp, "mismatch_asterisk testing\n")); + for (n = 0; n < TABLESIZE(table); ++n) { + CTRACE((tfp, "-------%d\n", n)); + code = strcasecomp_asterisk(table[n].a, table[n].b); + if (code != table[n].code) { + CTRACE((tfp, "mismatch_asterisk '%s' '%s' got %d, want %d\n", + table[n].a, table[n].b, code, table[n].code)); + } + } +} +#endif + +#ifdef NOT_ASCII + +/* Case-insensitive with ASCII collating sequence + * ---------------- + */ +int AS_casecomp(const char *p, + const char *q) +{ + int diff; + + for (;; p++, q++) { + if (!(*p && *q)) + return (UCH(*p) - UCH(*q)); + diff = TOASCII(TOLOWER(*p)) + - TOASCII(TOLOWER(*q)); + if (diff) + return diff; + } + /*NOTREACHED */ +} + +/* With count limit and ASCII collating sequence + * ---------------- + * AS_cmp uses n == -1 to compare indefinite length. + */ +int AS_ncmp(const char *p, + const char *q, + unsigned int n) +{ + const char *a = p; + int diff; + + for (; (unsigned) (p - a) < n; p++, q++) { + if (!(*p && *q)) + return (UCH(*p) - UCH(*q)); + diff = TOASCII(*p) + - TOASCII(*q); + if (diff) + return diff; + } + return 0; /* Match up to n characters */ +} +#endif /* NOT_ASCII */ + +/* Allocate a new copy of a string, and returns it +*/ +char *HTSACopy(char **dest, + const char *src) +{ + if (src != 0) { + if (src != *dest) { + size_t size = strlen(src) + 1; + + FREE(*dest); + *dest = (char *) malloc(size); + if (*dest == NULL) + outofmem(__FILE__, "HTSACopy"); + MemCpy(*dest, src, size); + } + } else { + FREE(*dest); + } + return *dest; +} + +/* String Allocate and Concatenate +*/ +char *HTSACat(char **dest, + const char *src) +{ + if (src && *src && (src != *dest)) { + if (*dest) { + size_t length = strlen(*dest); + + *dest = (char *) realloc(*dest, length + strlen(src) + 1); + if (*dest == NULL) + outofmem(__FILE__, "HTSACat"); + strcpy(*dest + length, src); + } else { + *dest = (char *) malloc(strlen(src) + 1); + if (*dest == NULL) + outofmem(__FILE__, "HTSACat"); + strcpy(*dest, src); + } + } + return *dest; +} + +/* optimized for heavily realloc'd strings, store length inside */ + +#define EXTRA_TYPE size_t /* type we use for length */ +#define EXTRA_SIZE sizeof(void *) /* alignment >= sizeof(EXTRA_TYPE) */ + +void HTSAFree_extra(char *s) +{ + free(s - EXTRA_SIZE); +} + +/* never shrink */ +char *HTSACopy_extra(char **dest, + const char *src) +{ + if (src != 0) { + size_t srcsize = strlen(src) + 1; + EXTRA_TYPE size = 0; + + if (*dest != 0) { + size = *(EXTRA_TYPE *) (void *) ((*dest) - EXTRA_SIZE); + } + if ((*dest == 0) || (size < srcsize)) { + FREE_extra(*dest); + size = srcsize * 2; /* x2 step */ + *dest = (char *) malloc(size + EXTRA_SIZE); + if (*dest == NULL) + outofmem(__FILE__, "HTSACopy_extra"); + *(EXTRA_TYPE *) (void *) (*dest) = size; + *dest += EXTRA_SIZE; + } + MemCpy(*dest, src, srcsize); + } else { + Clear_extra(*dest); + } + return *dest; +} + +/* Find next Field + * --------------- + * + * On entry, + * *pstr points to a string containing white space separated + * field, optionlly quoted. + * + * On exit, + * *pstr has been moved to the first delimiter past the + * field + * THE STRING HAS BEEN MUTILATED by a 0 terminator + * + * returns a pointer to the first field + */ +char *HTNextField(char **pstr) +{ + char *p = *pstr; + char *start = NULL; /* start of field */ + + if (p != NULL) { + while (*p && WHITE(*p)) + p++; /* Strip white space */ + if (!*p) { + *pstr = p; + } else { + if (*p == '"') { /* quoted field */ + p++; + start = p; + for (; *p && *p != '"'; p++) { + if (*p == '\\' && p[1]) + p++; /* Skip escaped chars */ + } + } else { + start = p; + while (*p && !WHITE(*p)) + p++; /* Skip first field */ + } + if (*p) + *p++ = '\0'; + *pstr = p; + } + } + return start; +} + +/* Find next Token + * --------------- + * Finds the next token in a string + * On entry, + * *pstr points to a string to be parsed. + * delims lists characters to be recognized as delimiters. + * If NULL, default is white space "," ";" or "=". + * The word can optionally be quoted or enclosed with + * chars from bracks. + * Comments surrounded by '(' ')' are filtered out + * unless they are specifically requested by including + * ' ' or '(' in delims or bracks. + * bracks lists bracketing chars. Some are recognized as + * special, for those give the opening char. + * If NULL, defaults to <"> and "<" ">". + * found points to location to fill with the ending delimiter + * found, or is NULL. + * + * On exit, + * *pstr has been moved to the first delimiter past the + * field + * THE STRING HAS BEEN MUTILATED by a 0 terminator + * found points to the delimiter found unless it was NULL. + * Returns a pointer to the first word or NULL on error + */ +char *HTNextTok(char **pstr, + const char *delims, + const char *bracks, + char *found) +{ + char *p = *pstr; + char *start = NULL; + BOOL get_blanks, skip_comments; + BOOL get_comments; + BOOL get_closing_char_too = FALSE; + char closer; + + if (isEmpty(pstr)) + return NULL; + if (!delims) + delims = " ;,="; + if (!bracks) + bracks = "<\""; + + get_blanks = (BOOL) (!StrChr(delims, ' ') && !StrChr(bracks, ' ')); + get_comments = (BOOL) (StrChr(bracks, '(') != NULL); + skip_comments = (BOOL) (!get_comments && !StrChr(delims, '(') && !get_blanks); +#define skipWHITE(c) (!get_blanks && WHITE(c)) + + while (*p && skipWHITE(*p)) + p++; /* Strip white space */ + if (!*p) { + *pstr = p; + if (found) + *found = '\0'; + return NULL; /* No first field */ + } + while (1) { + /* Strip white space and other delimiters */ + while (*p && (skipWHITE(*p) || StrChr(delims, *p))) + p++; + if (!*p) { + *pstr = p; + if (found) + *found = *(p - 1); + return NULL; /* No field */ + } + + if (*p == '(' && (skip_comments || get_comments)) { /* Comment */ + int comment_level = 0; + + if (get_comments && !start) + start = p + 1; + for (; *p && (*p != ')' || --comment_level > 0); p++) { + if (*p == '(') + comment_level++; + else if (*p == '"') { /* quoted field within Comment */ + for (p++; *p && *p != '"'; p++) + if (*p == '\\' && *(p + 1)) + p++; /* Skip escaped chars */ + if (!*p) + break; /* (invalid) end of string found, leave */ + } + if (*p == '\\' && *(p + 1)) + p++; /* Skip escaped chars */ + } + if (get_comments) + break; + if (*p) + p++; + if (get_closing_char_too) { + if (!*p || (!StrChr(bracks, *p) && StrChr(delims, *p))) { + break; + } else + get_closing_char_too = (BOOL) (StrChr(bracks, *p) != NULL); + } + } else if (StrChr(bracks, *p)) { /* quoted or bracketed field */ + switch (*p) { + case '<': + closer = '>'; + break; + case '[': + closer = ']'; + break; + case '{': + closer = '}'; + break; + case ':': + closer = ';'; + break; + default: + closer = *p; + } + if (!start) + start = ++p; + for (; *p && *p != closer; p++) + if (*p == '\\' && *(p + 1)) + p++; /* Skip escaped chars */ + if (get_closing_char_too) { + p++; + if (!*p || (!StrChr(bracks, *p) && StrChr(delims, *p))) { + break; + } else + get_closing_char_too = (BOOL) (StrChr(bracks, *p) != NULL); + } else + break; /* kr95-10-9: needs to stop here */ + } else { /* Spool field */ + if (!start) + start = p; + while (*p && !skipWHITE(*p) && !StrChr(bracks, *p) && + !StrChr(delims, *p)) + p++; + if (*p && StrChr(bracks, *p)) { + get_closing_char_too = TRUE; + } else { + if (*p == '(' && skip_comments) { + *pstr = p; + HTNextTok(pstr, NULL, "(", found); /* Advance pstr */ + *p = '\0'; + if (*pstr && **pstr) + (*pstr)++; + return start; + } + break; /* Got it */ + } + } + } + if (found) + *found = *p; + + if (*p) + *p++ = '\0'; + *pstr = p; + return start; +} + +static char *HTAlloc(char *ptr, size_t length) +{ + if (ptr != 0) + ptr = (char *) realloc(ptr, length); + else + ptr = (char *) malloc(length); + if (ptr == 0) + outofmem(__FILE__, "HTAlloc"); + return ptr; +} + +/* + * If SAVE_TIME_NOT_SPACE is defined, StrAllocVsprintf will hang on to + * its temporary string buffers instead of allocating and freeing them + * in each invocation. They only grow and never shrink, and won't be + * cleaned up on exit. - kw + */ +#if defined(_REENTRANT) || defined(_THREAD_SAFE) || defined(LY_FIND_LEAKS) +#undef SAVE_TIME_NOT_SPACE +#endif + +/* + * Replacement for sprintf, allocates buffer on the fly according to what's + * needed for its arguments. Unlike sprintf, this always concatenates to the + * destination buffer, so we do not have to provide both flavors. + */ +typedef enum { + Flags, + Width, + Prec, + Type, + Format +} PRINTF; + +#define VA_INTGR(type) ival = (int) va_arg((*ap), type) +#define VA_FLOAT(type) fval = (double) va_arg((*ap), type) +#define VA_POINT(type) pval = (char *) va_arg((*ap), type) + +#define NUM_WIDTH 10 /* allow for width substituted for "*" in "%*s" */ + /* also number of chars assumed to be needed in addition + to a given precision in floating point formats */ + +#define GROW_EXPR(n) (((n) * 3) / 2) +#define GROW_SIZE 256 + +PUBLIC_IF_FIND_LEAKS char *StrAllocVsprintf(char **pstr, + size_t dst_len, + const char *fmt, + va_list *ap) +{ +#ifdef HAVE_VASPRINTF + /* + * Use vasprintf() if we have it, since it is simplest. + */ + char *result = 0; + char *temp = 0; + + /* discard old destination if no length was given */ + if (pstr && !dst_len) { + if (*pstr) + FREE(*pstr); + } + + if (vasprintf(&temp, fmt, *ap) >= 0) { + if (dst_len != 0) { + size_t src_len = strlen(temp); + size_t new_len = dst_len + src_len + 1; + + result = HTAlloc(pstr ? *pstr : 0, new_len); + if (result != 0) { + strcpy(result + dst_len, temp); + } + (free) (temp); + } else { + result = temp; + mark_malloced(temp, strlen(temp)); + } + } + + if (pstr != 0) + *pstr = result; + + return result; +#else /* !HAVE_VASPRINTF */ + /* + * If vasprintf() is not available, this works - but does not implement + * the POSIX '$' formatting character which may be used in some of the + * ".po" files. + */ +#ifdef SAVE_TIME_NOT_SPACE + static size_t tmp_len = 0; + static size_t fmt_len = 0; + static char *tmp_ptr = NULL; + static char *fmt_ptr = NULL; + +#else + size_t tmp_len = GROW_SIZE; + char *tmp_ptr = 0; + char *fmt_ptr; +#endif /* SAVE_TIME_NOT_SPACE */ + size_t have, need; + char *dst_ptr = pstr ? *pstr : NULL; + const char *format = fmt; + + if (isEmpty(fmt)) + return 0; + + need = strlen(fmt) + 1; +#ifdef SAVE_TIME_NOT_SPACE + if (!fmt_ptr || fmt_len < need * NUM_WIDTH) { + fmt_ptr = HTAlloc(fmt_ptr, fmt_len = need * NUM_WIDTH); + } + if (!tmp_ptr || tmp_len < GROW_SIZE) { + tmp_ptr = HTAlloc(tmp_ptr, tmp_len = GROW_SIZE); + } +#else + if ((fmt_ptr = malloc(need * NUM_WIDTH)) == 0 + || (tmp_ptr = malloc(tmp_len)) == 0) { + outofmem(__FILE__, "StrAllocVsprintf"); + } +#endif /* SAVE_TIME_NOT_SPACE */ + + if (dst_ptr == 0) { + dst_ptr = HTAlloc(dst_ptr, have = GROW_SIZE + need); + } else { + have = strlen(dst_ptr) + 1; + need += dst_len; + if (have < need) + dst_ptr = HTAlloc(dst_ptr, have = GROW_SIZE + need); + } + + while (*fmt != '\0') { + if (*fmt == '%') { + static char dummy[] = ""; + PRINTF state = Flags; + char *pval = dummy; /* avoid const-cast */ + double fval = 0.0; + int done = FALSE; + int ival = 0; + int prec = -1; + int type = 0; + int used = 0; + int width = -1; + size_t f = 0; + + fmt_ptr[f++] = *fmt; + while (*++fmt != '\0' && !done) { + fmt_ptr[f++] = *fmt; + + if (isdigit(UCH(*fmt))) { + int num = *fmt - '0'; + + if (state == Flags && num != 0) + state = Width; + if (state == Width) { + if (width < 0) + width = 0; + width = (width * 10) + num; + } else if (state == Prec) { + if (prec < 0) + prec = 0; + prec = (prec * 10) + num; + } + } else if (*fmt == '*') { + VA_INTGR(int); + + if (state == Flags) + state = Width; + if (state == Width) { + width = ival; + } else if (state == Prec) { + prec = ival; + } + sprintf(&fmt_ptr[--f], "%d", ival); + f = strlen(fmt_ptr); + } else if (isalpha(UCH(*fmt))) { + done = TRUE; + switch (*fmt) { + case 'Z': /* FALLTHRU */ + case 'h': /* FALLTHRU */ + case 'l': /* FALLTHRU */ + case 'L': /* FALLTHRU */ + done = FALSE; + type = *fmt; + break; + case 'o': /* FALLTHRU */ + case 'i': /* FALLTHRU */ + case 'd': /* FALLTHRU */ + case 'u': /* FALLTHRU */ + case 'x': /* FALLTHRU */ + case 'X': /* FALLTHRU */ + if (type == 'l') + VA_INTGR(long); + + else if (type == 'Z') + VA_INTGR(size_t); + + else + VA_INTGR(int); + + used = 'i'; + break; + case 'f': /* FALLTHRU */ + case 'e': /* FALLTHRU */ + case 'E': /* FALLTHRU */ + case 'g': /* FALLTHRU */ + case 'G': /* FALLTHRU */ + VA_FLOAT(double); + + used = 'f'; + break; + case 'c': + VA_INTGR(int); + + used = 'c'; + break; + case 's': + VA_POINT(char *); + + if (prec < 0) + prec = (int) strlen(pval); + used = 's'; + break; + case 'p': + VA_POINT(void *); + + used = 'p'; + break; + case 'n': + VA_POINT(int *); + + used = 0; + break; + default: + CTRACE((tfp, "unknown format character '%c' in %s\n", + *fmt, format)); + break; + } + } else if (*fmt == '.') { + state = Prec; + } else if (*fmt == '%') { + done = TRUE; + used = '%'; + } + } + fmt_ptr[f] = '\0'; + + if (prec > 0) { + switch (used) { + case 'f': + if (width < prec + NUM_WIDTH) + width = prec + NUM_WIDTH; + /* FALLTHRU */ + case 'i': + /* FALLTHRU */ + case 'p': + if (width < prec + 2) + width = prec + 2; /* leading sign/space/zero, "0x" */ + break; + case 'c': + break; + case '%': + break; + default: + if (width < prec) + width = prec; + break; + } + } + if (width >= (int) tmp_len) { + tmp_len = GROW_EXPR(tmp_len + width); + tmp_ptr = HTAlloc(tmp_ptr, tmp_len); + } + + switch (used) { + case 'i': + case 'c': + sprintf(tmp_ptr, fmt_ptr, ival); + break; + case 'f': + sprintf(tmp_ptr, fmt_ptr, fval); + break; + default: + sprintf(tmp_ptr, fmt_ptr, pval); + break; + } + need = dst_len + strlen(tmp_ptr) + 1; + if (need >= have) { + dst_ptr = HTAlloc(dst_ptr, have = GROW_EXPR(need)); + } + strcpy(dst_ptr + dst_len, tmp_ptr); + dst_len += strlen(tmp_ptr); + } else { + if ((dst_len + 2) >= have) { + dst_ptr = HTAlloc(dst_ptr, (have += GROW_SIZE)); + } + dst_ptr[dst_len++] = *fmt++; + } + } + +#ifndef SAVE_TIME_NOT_SPACE + FREE(tmp_ptr); + FREE(fmt_ptr); +#endif + dst_ptr[dst_len] = '\0'; + if (pstr) + *pstr = dst_ptr; + return (dst_ptr); +#endif /* HAVE_VASPRINTF */ +} +#undef SAVE_TIME_NOT_SPACE + +/* + * Replacement for sprintf, allocates buffer on the fly according to what's + * needed for its arguments. Unlike sprintf, this always concatenates to the + * destination buffer. + */ +/* Note: if making changes, also check the memory tracking version + * LYLeakHTSprintf in LYLeaks.c. - kw */ +#ifdef HTSprintf /* if hidden by LYLeaks stuff */ +#undef HTSprintf +#endif +char *HTSprintf(char **pstr, const char *fmt, ...) +{ + char *result = 0; + size_t inuse = 0; + va_list ap; + + LYva_start(ap, fmt); + { + if (pstr != 0 && *pstr != 0) + inuse = strlen(*pstr); + result = StrAllocVsprintf(pstr, inuse, fmt, &ap); + } + va_end(ap); + + return (result); +} + +/* + * Replacement for sprintf, allocates buffer on the fly according to what's + * needed for its arguments. Like sprintf, this always resets the destination + * buffer. + */ +/* Note: if making changes, also check the memory tracking version + * LYLeakHTSprintf0 in LYLeaks.c. - kw */ +#ifdef HTSprintf0 /* if hidden by LYLeaks stuff */ +#undef HTSprintf0 +#endif +char *HTSprintf0(char **pstr, const char *fmt, ...) +{ + char *result = 0; + va_list ap; + + LYva_start(ap, fmt); + { + result = StrAllocVsprintf(pstr, (size_t) 0, fmt, &ap); + } + va_end(ap); + + return (result); +} + +/* + * Returns a quoted or escaped form of the given parameter, suitable for use in + * a command string. + */ +#if USE_QUOTED_PARAMETER +#define S_QUOTE '\'' +#define D_QUOTE '"' +char *HTQuoteParameter(const char *parameter) +{ + size_t i; + size_t last; + size_t n = 0; + size_t quoted = 0; + char *result; + + if (parameter == 0) + parameter = ""; + + last = strlen(parameter); + for (i = 0; i < last; ++i) + if (StrChr("\\&#$^*?(){}<>\"';`|", parameter[i]) != 0 + || isspace(UCH(parameter[i]))) + ++quoted; + + result = (char *) malloc(last + 5 * quoted + 3); + if (result == NULL) + outofmem(__FILE__, "HTQuoteParameter"); + + n = 0; +#if (USE_QUOTED_PARAMETER == 1) + /* + * Only double-quotes are used in Win32/DOS -TD + */ + if (quoted) + result[n++] = D_QUOTE; + for (i = 0; i < last; i++) { + result[n++] = parameter[i]; + } + if (quoted) + result[n++] = D_QUOTE; +#else + if (quoted) + result[n++] = S_QUOTE; + for (i = 0; i < last; i++) { + if (parameter[i] == S_QUOTE) { + result[n++] = S_QUOTE; + result[n++] = D_QUOTE; + result[n++] = parameter[i]; + result[n++] = D_QUOTE; + result[n++] = S_QUOTE; + } else { + /* Note: No special handling of other characters, including + backslash, since we are constructing a single-quoted string! + Backslash has no special escape meaning within those for sh + and compatible shells, so trying to escape a backslash by + doubling it is unnecessary and would be interpreted by the + shell as an additional data character. - kw 2000-05-02 + */ + result[n++] = parameter[i]; + } + } + if (quoted) + result[n++] = S_QUOTE; +#endif + result[n] = '\0'; + return result; +} +#endif + +#define HTIsParam(string) ((string[0] == '%' && string[1] == 's')) + +/* + * Returns the number of "%s" tokens in a system command-template. + */ +int HTCountCommandArgs(const char *command) +{ + int number = 0; + + while (command[0] != 0) { + if (HTIsParam(command)) + number++; + command++; + } + return number; +} + +/* + * Returns a pointer into the given string after the given parameter number + */ +static const char *HTAfterCommandArg(const char *command, + int number) +{ + while (number > 0) { + if (command[0] != 0) { + if (HTIsParam(command)) { + number--; + command++; + } + command++; + } else { + break; + } + } + return command; +} + +#if USE_QUOTED_PARAMETER +/* + * Recursively trim possible parameters of the source until an existing file + * is found. If no file is found, return -1. If a file is found, return + * the offset to a blank just after the filename. + * + * TODO: this could be smarter about trimming, e.g., matching quotes. + */ +static int skipPathname(const char *target, const char *source) +{ + int result = -1; + const char *last; + struct stat stat_info; + + if (HTStat(target, &stat_info) == 0 + && S_ISREG(stat_info.st_mode)) { + result = 0; + } else if (*target != ' ' && (last = strrchr(target, ' ')) != NULL) { + char *temp = NULL; + int inner; + + while (last != target && last[-1] == ' ') + --last; + + StrAllocCopy(temp, target); + result = (int) (last - target); + temp[result] = '\0'; + + if ((inner = skipPathname(temp, source)) < 0) { + result = -1; + } else if (inner > 0) { + result = inner; + } + + FREE(temp); + } + CTRACE((tfp, "skip/recur %d '%s'\n", result, target)); + return result; +} +#endif + +/* + * Like HTAddParam, but the parameter may be an environment variable, which we + * will expand and append. Do this only for things like the command-verb, + * where we obtain the parameter from the user's configuration. Any quoting + * required for the environment variable has to be done within its value, e.g., + * + * setenv EDITOR 'xvile -name "No such class"' + * + * This is useful only when we quote parameters, of course. + */ +#if USE_QUOTED_PARAMETER +void HTAddXpand(char **result, + const char *command, + int number, + const char *parameter) +{ + if (parameter == NULL) + parameter = ""; + if (number > 0) { + const char *last = HTAfterCommandArg(command, number - 1); + const char *next = last; + + if (number <= 1) { + FREE(*result); + } + + while (next[0] != 0) { + if (HTIsParam(next)) { + if (next != last) { + size_t len = ((size_t) (next - last) + + ((*result != 0) + ? strlen(*result) + : 0)); + + HTSACat(result, last); + (*result)[len] = 0; + } + if (LYisAbsPath(parameter)) { + int skip = skipPathname(parameter, parameter); + char *quoted; + + if (skip > 0) { + char *temp = NULL; + + StrAllocCopy(temp, parameter); + temp[skip] = 0; + + quoted = HTQuoteParameter(temp); + HTSACat(result, quoted); + FREE(quoted); + + temp[skip] = ' '; + HTSACat(result, temp + skip); + FREE(temp); + } else { + quoted = HTQuoteParameter(parameter); + HTSACat(result, quoted); + FREE(quoted); + } + } else { + /* leave it unquoted, e.g., environment variable expanded */ + HTSACat(result, parameter); + } + CTRACE((tfp, "PARAM-EXP:%s\n", *result)); + return; + } + next++; + } + } +} +#endif /* USE_QUOTED_PARAMETER */ + +/* + * Append string to a system command that we are constructing, without quoting. + * We're given the index of the newest parameter we're processing. Zero + * indicates none, so a value of '1' indicates that we copy from the beginning + * of the command string up to the first parameter, substitute the quoted + * parameter and return the result. + * + * Parameters are substituted at "%s" tokens, like printf. Other printf-style + * tokens are not substituted; they are passed through without change. + */ +void HTAddToCmd(char **result, + const char *command, + int number, + const char *string) +{ + if (number > 0) { + const char *last = HTAfterCommandArg(command, number - 1); + const char *next = last; + + if (number <= 1) { + FREE(*result); + } + if (string == 0) + string = ""; + while (next[0] != 0) { + if (HTIsParam(next)) { + if (next != last) { + size_t len = ((size_t) (next - last) + + ((*result != 0) + ? strlen(*result) + : 0)); + + HTSACat(result, last); + (*result)[len] = 0; + } + HTSACat(result, string); + CTRACE((tfp, "PARAM-ADD:%s\n", *result)); + return; + } + next++; + } + } +} + +/* + * Append string-parameter to a system command that we are constructing. The + * string is a complete parameter (which is a necessary assumption so we can + * quote it properly). + */ +void HTAddParam(char **result, + const char *command, + int number, + const char *parameter) +{ + if (number > 0) { +#if USE_QUOTED_PARAMETER + char *quoted = HTQuoteParameter(parameter); + + HTAddToCmd(result, command, number, quoted); + FREE(quoted); +#else + HTAddToCmd(result, command, number, parameter); +#endif + } +} + +/* + * Append the remaining command-string to a system command (compare with + * HTAddParam). Any remaining "%s" tokens are copied as empty strings. + */ +void HTEndParam(char **result, + const char *command, + int number) +{ + const char *last; + int count; + + count = HTCountCommandArgs(command); + if (count < number) + number = count; + last = HTAfterCommandArg(command, number); + if (last[0] != 0) { + HTSACat(result, last); + } + CTRACE((tfp, "PARAM-END:%s\n", *result)); +} + +/* Binary-strings (may have embedded nulls). Some modules (HTGopher) assume + * there is a null on the end, anyway. + */ + +/* (Re)allocate a bstring, e.g., to increase its buffer size for ad hoc + * operations. + */ +void HTSABAlloc(bstring **dest, int len) +{ + if (*dest == 0) { + *dest = typecalloc(bstring); + + if (*dest == 0) + outofmem(__FILE__, "HTSABAlloc"); + } + + if ((*dest)->len != len) { + (*dest)->str = typeRealloc(char, (*dest)->str, len); + + if ((*dest)->str == 0) + outofmem(__FILE__, "HTSABAlloc"); + + (*dest)->len = len; + } +} + +/* Allocate a new bstring, and return it. +*/ +void HTSABCopy(bstring **dest, const char *src, + int len) +{ + bstring *t; + unsigned need = (unsigned) (len + 1); + + CTRACE2(TRACE_BSTRING, + (tfp, "HTSABCopy(%p, %p, %d)\n", + (void *) dest, (const void *) src, len)); + HTSABFree(dest); + if (src) { + if (TRACE_BSTRING) { + CTRACE((tfp, "=== %4d:", len)); + trace_bstring2(src, len); + CTRACE((tfp, "\n")); + } + if ((t = (bstring *) malloc(sizeof(bstring))) == NULL) + outofmem(__FILE__, "HTSABCopy"); + + if ((t->str = typeMallocn(char, need)) == NULL) + outofmem(__FILE__, "HTSABCopy"); + + MemCpy(t->str, src, len); + t->len = len; + t->str[t->len] = '\0'; + *dest = t; + } + if (TRACE_BSTRING) { + CTRACE((tfp, "=> %4d:", BStrLen(*dest))); + trace_bstring(*dest); + CTRACE((tfp, "\n")); + } +} + +/* + * Initialize with a null-terminated string (discards the null). + */ +void HTSABCopy0(bstring **dest, const char *src) +{ + HTSABCopy(dest, src, (int) strlen(src)); +} + +/* + * Append a block of memory to a bstring. + */ +void HTSABCat(bstring **dest, const char *src, + int len) +{ + bstring *t = *dest; + + CTRACE2(TRACE_BSTRING, + (tfp, "HTSABCat(%p, %p, %d)\n", + (void *) dest, (const void *) src, len)); + if (src) { + unsigned need = (unsigned) (len + 1); + + if (TRACE_BSTRING) { + CTRACE((tfp, "=== %4d:", len)); + trace_bstring2(src, len); + CTRACE((tfp, "\n")); + } + if (t) { + unsigned length = (unsigned) t->len + need; + + t->str = typeRealloc(char, t->str, length); + } else { + if ((t = typecalloc(bstring)) == NULL) + outofmem(__FILE__, "HTSACat"); + + t->str = typeMallocn(char, need); + } + if (t->str == NULL) + outofmem(__FILE__, "HTSACat"); + + MemCpy(t->str + t->len, src, len); + t->len += len; + t->str[t->len] = '\0'; + *dest = t; + } + if (TRACE_BSTRING) { + CTRACE((tfp, "=> %4d:", BStrLen(*dest))); + trace_bstring(*dest); + CTRACE((tfp, "\n")); + } +} + +/* + * Append a null-terminated string (discards the null). + */ +void HTSABCat0(bstring **dest, const char *src) +{ + HTSABCat(dest, src, (int) strlen(src)); +} + +/* + * Compare two bstring's for equality + */ +BOOL HTSABEql(bstring *a, bstring *b) +{ + unsigned len_a = (unsigned) ((a != 0) ? a->len : 0); + unsigned len_b = (unsigned) ((b != 0) ? b->len : 0); + + if (len_a == len_b) { + if (len_a == 0 + || MemCmp(a->str, b->str, a->len) == 0) + return TRUE; + } + return FALSE; +} + +/* + * Deallocate a bstring. + */ +void HTSABFree(bstring **ptr) +{ + if (*ptr != NULL) { + FREE((*ptr)->str); + FREE(*ptr); + *ptr = NULL; + } +} + +/* + * Use this function to perform formatted sprintf's onto the end of a bstring. + * The bstring may contain embedded nulls; the formatted portions must not. + */ +bstring *HTBprintf(bstring **pstr, const char *fmt, ...) +{ + bstring *result = 0; + char *temp = 0; + va_list ap; + + LYva_start(ap, fmt); + { + temp = StrAllocVsprintf(&temp, (size_t) 0, fmt, &ap); + if (non_empty(temp)) { + HTSABCat(pstr, temp, (int) strlen(temp)); + } + FREE(temp); + result = *pstr; + } + va_end(ap); + + return (result); +} + +/* + * Write binary-data to the logfile, making it safe for most editors to view. + * That is most, since we do not restrict line-length. Nulls and other + * non-printing characters are addressed. + */ +void trace_bstring2(const char *text, + int size) +{ + int n; + + if (text != 0) { + for (n = 0; n < size; ++n) { + int ch = UCH(text[n]); + + switch (ch) { + case '\\': + fputs("\\\\", tfp); + break; + case '\r': + fputs("\\r", tfp); + break; + case '\t': + fputs("\\t", tfp); + break; + case '\f': + fputs("\\f", tfp); + break; + default: + if (isprint(ch) || isspace(ch)) { + fputc(ch, tfp); + } else { + fprintf(tfp, "\\%03o", ch); + } + break; + } + } + } +} + +void trace_bstring(bstring *data) +{ + trace_bstring2(BStrData(data), BStrLen(data)); +} |