/* * Copyright 2001-2004 Brandon Long * All Rights Reserved. * * ClearSilver Templating System * * This code is made available under the terms of the ClearSilver License. * http://www.clearsilver.net/license.hdf * */ #include "cs_config.h" #include #include #include #include #include #include #include #include "neo_misc.h" #include "neo_err.h" #include "neo_str.h" #include "ulist.h" #ifndef va_copy #ifdef __va_copy # define va_copy(dest,src) __va_copy(dest,src) #else # define va_copy(dest,src) ((dest) = (src)) #endif #endif char *neos_strip (char *s) { int x; x = strlen(s) - 1; while (x>=0 && isspace(s[x])) s[x--] = '\0'; while (*s && isspace(*s)) s++; return s; } char *neos_rstrip (char *s) { int n = strlen (s)-1; while (n >= 0 && isspace(s[n])) { s[n] = '\0'; n--; } return s; } void neos_lower(char *s) { while(*s != 0) { *s = tolower(*s); s++; } } void string_init (STRING *str) { str->buf = NULL; str->len = 0; str->max = 0; } void string_clear (STRING *str) { if (str->buf != NULL) free(str->buf); string_init(str); } static NEOERR* string_check_length (STRING *str, int l) { if (str->buf == NULL) { if (l * 10 > 256) str->max = l * 10; else str->max = 256; str->buf = (char *) malloc (sizeof(char) * str->max); if (str->buf == NULL) return nerr_raise (NERR_NOMEM, "Unable to allocate render buf of size %d", str->max); /* ne_warn("Creating string %x at %d (%5.2fK)", str, str->max, (str->max / 1024.0)); */ } else if (str->len + l >= str->max) { do { str->max *= 2; } while (str->len + l >= str->max); str->buf = (char *) realloc (str->buf, sizeof(char) * str->max); if (str->buf == NULL) return nerr_raise (NERR_NOMEM, "Unable to allocate STRING buf of size %d", str->max); /* ne_warn("Growing string %x to %d (%5.2fK)", str, str->max, (str->max / 1024.0)); */ } return STATUS_OK; } NEOERR *string_set (STRING *str, const char *buf) { str->len = 0; return nerr_pass (string_append (str, buf)); } NEOERR *string_append (STRING *str, const char *buf) { NEOERR *err; int l; l = strlen(buf); err = string_check_length (str, l); if (err != STATUS_OK) return nerr_pass (err); strcpy(str->buf + str->len, buf); str->len += l; return STATUS_OK; } NEOERR *string_appendn (STRING *str, const char *buf, int l) { NEOERR *err; err = string_check_length (str, l+1); if (err != STATUS_OK) return nerr_pass (err); memcpy(str->buf + str->len, buf, l); str->len += l; str->buf[str->len] = '\0'; return STATUS_OK; } /* this is much more efficient with C99 snprintfs... */ NEOERR *string_appendvf (STRING *str, const char *fmt, va_list ap) { NEOERR *err; char buf[4096]; int bl, size; va_list tmp; va_copy(tmp, ap); /* determine length */ size = sizeof (buf); bl = vsnprintf (buf, size, fmt, tmp); if (bl > -1 && bl < size) return string_appendn (str, buf, bl); /* Handle non-C99 snprintfs (requires extra malloc/free and copy) */ if (bl == -1) { char *a_buf; va_copy(tmp, ap); a_buf = vnsprintf_alloc(size*2, fmt, tmp); if (a_buf == NULL) return nerr_raise(NERR_NOMEM, "Unable to allocate memory for formatted string"); err = string_append(str, a_buf); free(a_buf); return nerr_pass(err); } err = string_check_length (str, bl+1); if (err != STATUS_OK) return nerr_pass (err); va_copy(tmp, ap); vsprintf (str->buf + str->len, fmt, tmp); str->len += bl; str->buf[str->len] = '\0'; return STATUS_OK; } NEOERR *string_appendf (STRING *str, const char *fmt, ...) { NEOERR *err; va_list ap; va_start (ap, fmt); err = string_appendvf (str, fmt, ap); va_end (ap); return nerr_pass(err); } NEOERR *string_append_char (STRING *str, char c) { NEOERR *err; err = string_check_length (str, 1); if (err != STATUS_OK) return nerr_pass (err); str->buf[str->len] = c; str->buf[str->len + 1] = '\0'; str->len += 1; return STATUS_OK; } void string_array_init (STRING_ARRAY *arr) { arr->entries = NULL; arr->count = 0; arr->max = 0; } NEOERR *string_array_split (ULIST **list, char *s, const char *sep, int max) { NEOERR *err; char *p, *n, *f; int sl; int x = 0; if (sep[0] == '\0') return nerr_raise (NERR_ASSERT, "separator must be at least one character"); err = uListInit (list, 10, 0); if (err) return nerr_pass(err); sl = strlen(sep); p = (sl == 1) ? strchr (s, sep[0]) : strstr (s, sep); f = s; while (p != NULL) { if (x >= max) break; *p = '\0'; n = strdup(f); *p = sep[0]; if (n) err = uListAppend (*list, n); else err = nerr_raise(NERR_NOMEM, "Unable to allocate memory to split %s", s); if (err) goto split_err; f = p+sl; p = (sl == 1) ? strchr (f, sep[0]) : strstr (f, sep); x++; } /* Handle remainder */ n = strdup(f); if (n) err = uListAppend (*list, n); else err = nerr_raise(NERR_NOMEM, "Unable to allocate memory to split %s", s); if (err) goto split_err; return STATUS_OK; split_err: uListDestroy(list, ULIST_FREE); return err; } void string_array_clear (STRING_ARRAY *arr) { int x; for (x = 0; x < arr->count; x++) { if (arr->entries[x] != NULL) free (arr->entries[x]); arr->entries[x] = NULL; } free (arr->entries); arr->entries = NULL; arr->count = 0; } /* Mostly used by vprintf_alloc for non-C99 compliant snprintfs, * this is like vsprintf_alloc except it takes a "suggested" size */ int vnisprintf_alloc (char **buf, int start_size, const char *fmt, va_list ap) { int bl, size; va_list tmp; *buf = NULL; size = start_size; *buf = (char *) malloc (size * sizeof(char)); if (*buf == NULL) return 0; while (1) { va_copy(tmp, ap); bl = vsnprintf (*buf, size, fmt, tmp); if (bl > -1 && bl < size) return bl; if (bl > -1) size = bl + 1; else size *= 2; *buf = (char *) realloc (*buf, size * sizeof(char)); if (*buf == NULL) return 0; } } char *vnsprintf_alloc (int start_size, const char *fmt, va_list ap) { char *r; vnisprintf_alloc(&r, start_size, fmt, ap); return r; } /* This works better with a C99 compliant vsnprintf, but should work ok * with versions that return a -1 if it overflows the buffer */ int visprintf_alloc (char **buf, const char *fmt, va_list ap) { char ibuf[4096]; int bl, size; va_list tmp; /* PPC doesn't like you re-using a va_list... and it might not be * supposed to work at all */ va_copy(tmp, ap); size = sizeof (ibuf); bl = vsnprintf (ibuf, sizeof (ibuf), fmt, tmp); if (bl > -1 && bl < size) { *buf = (char *) calloc(bl+1, sizeof(char)); if (*buf == NULL) return 0; strncpy(*buf, ibuf, bl); return bl; } if (bl > -1) size = bl + 1; else size *= 2; return vnisprintf_alloc(buf, size, fmt, ap); } char *vsprintf_alloc (const char *fmt, va_list ap) { char *r; visprintf_alloc(&r, fmt, ap); return r; } /* technically, sprintf's can have null values, so we need to be able to * return a length also like real sprintf */ int isprintf_alloc (char **buf, const char *fmt, ...) { va_list ap; int r; va_start (ap, fmt); r = visprintf_alloc (buf, fmt, ap); va_end (ap); return r; } char *sprintf_alloc (const char *fmt, ...) { va_list ap; char *r; va_start (ap, fmt); r = vsprintf_alloc (fmt, ap); va_end (ap); return r; } /* This is mostly just here for completeness, I doubt anyone would use * this (its more efficient (time-wise) if start_size is bigger than the * resulting string. Its less efficient than sprintf_alloc if we have a * C99 snprintf and it doesn't fit in start_size. * BTW: If you are really worried about the efficiency of these * functions, maybe you shouldn't be using them in the first place... */ char *nsprintf_alloc (int start_size, const char *fmt, ...) { va_list ap; char *r; va_start (ap, fmt); r = vnsprintf_alloc (start_size, fmt, ap); va_end (ap); return r; } BOOL reg_search (const char *re, const char *str) { regex_t search_re; int errcode; char buf[256]; if ((errcode = regcomp(&search_re, re, REG_ICASE | REG_EXTENDED | REG_NOSUB))) { regerror (errcode, &search_re, buf, sizeof(buf)); ne_warn ("Unable to compile regex %s: %s", re, buf); return FALSE; } errcode = regexec (&search_re, str, 0, NULL, 0); regfree (&search_re); if (errcode == 0) return TRUE; return FALSE; } NEOERR *string_readline (STRING *str, FILE *fp) { NEOERR *err; /* minimum size for a readline is 256 above current position */ err = string_check_length (str, str->len + 256); if (err != STATUS_OK) return nerr_pass (err); while (fgets(str->buf + str->len, str->max - str->len, fp) != NULL) { str->len = strlen(str->buf); if (str->buf[str->len-1] == '\n') break; err = string_check_length (str, str->len + 256); if (err != STATUS_OK) return nerr_pass (err); } return STATUS_OK; } NEOERR* neos_escape(UINT8 *buf, int buflen, char esc_char, const char *escape, char **esc) { int nl = 0; int l = 0; int x = 0; char *s; int match = 0; while (l < buflen) { if (buf[l] == esc_char) { nl += 2; } else { x = 0; while (escape[x]) { if (escape[x] == buf[l]) { nl +=2; break; } x++; } } nl++; l++; } s = (char *) malloc (sizeof(char) * (nl + 1)); if (s == NULL) return nerr_raise (NERR_NOMEM, "Unable to allocate memory to escape %s", buf); nl = 0; l = 0; while (l < buflen) { match = 0; if (buf[l] == esc_char) { match = 1; } else { x = 0; while (escape[x]) { if (escape[x] == buf[l]) { match = 1; break; } x++; } } if (match) { s[nl++] = esc_char; s[nl++] = "0123456789ABCDEF"[buf[l] / 16]; s[nl++] = "0123456789ABCDEF"[buf[l] % 16]; l++; } else { s[nl++] = buf[l++]; } } s[nl] = '\0'; *esc = s; return STATUS_OK; } UINT8 *neos_unescape (UINT8 *s, int buflen, char esc_char) { int i = 0, o = 0; if (s == NULL) return s; while (i < buflen) { if (s[i] == esc_char && (i+2 < buflen) && isxdigit(s[i+1]) && isxdigit(s[i+2])) { UINT8 num; num = (s[i+1] >= 'A') ? ((s[i+1] & 0xdf) - 'A') + 10 : (s[i+1] - '0'); num *= 16; num += (s[i+2] >= 'A') ? ((s[i+2] & 0xdf) - 'A') + 10 : (s[i+2] - '0'); s[o++] = num; i+=3; } else { s[o++] = s[i++]; } } if (i && o) s[o] = '\0'; return s; } char *repr_string_alloc (const char *s) { int l,x,i; int nl = 0; char *rs; if (s == NULL) { return strdup("NULL"); } l = strlen(s); for (x = 0; x < l; x++) { if (isprint(s[x]) && s[x] != '"' && s[x] != '\\') { nl++; } else { if (s[x] == '\n' || s[x] == '\t' || s[x] == '\r' || s[x] == '"' || s[x] == '\\') { nl += 2; } else nl += 4; } } rs = (char *) malloc ((nl+3) * sizeof(char)); if (rs == NULL) return NULL; i = 0; rs[i++] = '"'; for (x = 0; x < l; x++) { if (isprint(s[x]) && s[x] != '"' && s[x] != '\\') { rs[i++] = s[x]; } else { rs[i++] = '\\'; switch (s[x]) { case '\n': rs[i++] = 'n'; break; case '\t': rs[i++] = 't'; break; case '\r': rs[i++] = 'r'; break; case '"': rs[i++] = '"'; break; case '\\': rs[i++] = '\\'; break; default: sprintf(&(rs[i]), "%03o", (s[x] & 0377)); i += 3; break; } } } rs[i++] = '"'; rs[i] = '\0'; return rs; } // List of all characters that must be escaped // List based on http://www.blooberry.com/indexdot/html/topics/urlencoding.htm static char EscapedChars[] = "$&+,/:;=?@ \"<>#%{}|\\^~[]`'"; // Check if a single character needs to be escaped static BOOL is_reserved_char(char c) { int i = 0; if (c < 32 || c > 122) { return TRUE; } else { while (EscapedChars[i]) { if (c == EscapedChars[i]) { return TRUE; } ++i; } } return FALSE; } NEOERR *neos_js_escape (const char *in, char **esc) { int nl = 0; int l = 0; unsigned char *buf = (unsigned char *)in; unsigned char *s; while (buf[l]) { if (buf[l] == '/' || buf[l] == '"' || buf[l] == '\'' || buf[l] == '\\' || buf[l] == '>' || buf[l] == '<' || buf[l] == '&' || buf[l] == ';' || buf[l] < 32) { nl += 3; } nl++; l++; } s = (unsigned char *) malloc (sizeof(unsigned char) * (nl + 1)); if (s == NULL) return nerr_raise (NERR_NOMEM, "Unable to allocate memory to escape %s", buf); nl = 0; l = 0; while (buf[l]) { if (buf[l] == '/' || buf[l] == '"' || buf[l] == '\'' || buf[l] == '\\' || buf[l] == '>' || buf[l] == '<' || buf[l] == '&' || buf[l] == ';' || buf[l] < 32) { s[nl++] = '\\'; s[nl++] = 'x'; s[nl++] = "0123456789ABCDEF"[(buf[l] >> 4) & 0xF]; s[nl++] = "0123456789ABCDEF"[buf[l] & 0xF]; l++; } else { s[nl++] = buf[l++]; } } s[nl] = '\0'; *esc = (char *)s; return STATUS_OK; } NEOERR *neos_url_escape (const char *in, char **esc, const char *other) { int nl = 0; int l = 0; int x = 0; unsigned char *buf = (unsigned char *)in; unsigned char *uother = (unsigned char *)other; unsigned char *s; int match = 0; while (buf[l]) { if (is_reserved_char(buf[l])) { nl += 2; } else if (uother) { x = 0; while (uother[x]) { if (uother[x] == buf[l]) { nl +=2; break; } x++; } } nl++; l++; } s = (unsigned char *) malloc (sizeof(unsigned char) * (nl + 1)); if (s == NULL) return nerr_raise (NERR_NOMEM, "Unable to allocate memory to escape %s", buf); nl = 0; l = 0; while (buf[l]) { match = 0; if (buf[l] == ' ') { s[nl++] = '+'; l++; } else { if (is_reserved_char(buf[l])) { match = 1; } else if (uother) { x = 0; while (uother[x]) { if (uother[x] == buf[l]) { match = 1; break; } x++; } } if (match) { s[nl++] = '%'; s[nl++] = "0123456789ABCDEF"[buf[l] / 16]; s[nl++] = "0123456789ABCDEF"[buf[l] % 16]; l++; } else { s[nl++] = buf[l++]; } } } s[nl] = '\0'; *esc = (char *)s; return STATUS_OK; } NEOERR *neos_html_escape (const char *src, int slen, char **out) { NEOERR *err = STATUS_OK; STRING out_s; int x; char *ptr; string_init(&out_s); err = string_append (&out_s, ""); if (err) return nerr_pass (err); *out = NULL; x = 0; while (x < slen) { ptr = strpbrk(src + x, "&<>\"'\r"); if (ptr == NULL || (ptr-src >= slen)) { err = string_appendn (&out_s, src + x, slen-x); x = slen; } else { err = string_appendn (&out_s, src + x, (ptr - src) - x); if (err != STATUS_OK) break; x = ptr - src; if (src[x] == '&') err = string_append (&out_s, "&"); else if (src[x] == '<') err = string_append (&out_s, "<"); else if (src[x] == '>') err = string_append (&out_s, ">"); else if (src[x] == '"') err = string_append (&out_s, """); else if (src[x] == '\'') err = string_append (&out_s, "'"); else if (src[x] != '\r') err = nerr_raise (NERR_ASSERT, "src[x] == '%c'", src[x]); x++; } if (err != STATUS_OK) break; } if (err) { string_clear (&out_s); return nerr_pass (err); } *out = out_s.buf; return STATUS_OK; } char *URL_PROTOCOLS[] = {"http://", "https://", "ftp://", "mailto:"}; NEOERR *neos_url_validate (const char *in, char **esc) { NEOERR *err = STATUS_OK; STRING out_s; int valid = 0; size_t i; size_t inlen; int num_protocols = sizeof(URL_PROTOCOLS) / sizeof(char*); void* slashpos; void* colonpos; inlen = strlen(in); /* * or are allowed by browsers * and ":" is treated as part of the path, while * is an invalid url * and ":" is treated as a scheme separator. * * Hence allow for ":" in the path part of a url (after /) */ slashpos = memchr(in, '/', inlen); if (slashpos == NULL) { i = inlen; } else { i = (size_t)((char*)slashpos - in); } colonpos = memchr(in, ':', i); if (colonpos == NULL) { // no scheme in 'in': so this is a relative url valid = 1; } else { for (i = 0; i < num_protocols; i++) { if ((inlen >= strlen(URL_PROTOCOLS[i])) && strncmp(in, URL_PROTOCOLS[i], strlen(URL_PROTOCOLS[i])) == 0) { // 'in' starts with one of the allowed protocols valid = 1; break; } } } if (valid) return neos_html_escape(in, inlen, esc); // 'in' contains an unsupported scheme, replace with '#' string_init(&out_s); err = string_append (&out_s, "#"); if (err) return nerr_pass (err); *esc = out_s.buf; return STATUS_OK; } NEOERR *neos_var_escape (NEOS_ESCAPE context, const char *in, char **esc) { /* Just dup and return if we do nothing. */ if (context == NEOS_ESCAPE_NONE || context == NEOS_ESCAPE_FUNCTION) { *esc = strdup(in); return STATUS_OK; } /* Now we escape based on context. This is the order of precedence: * url > script > style > html */ if (context & NEOS_ESCAPE_URL) return nerr_pass(neos_url_escape(in, esc, NULL)); else if (context & NEOS_ESCAPE_SCRIPT) return nerr_pass(neos_js_escape(in, esc)); else if (context & NEOS_ESCAPE_HTML) return nerr_pass(neos_html_escape(in, strlen(in), esc)); return nerr_raise(NERR_ASSERT, "unknown escape context supplied: %d", context); }