Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright 2001-2004 Brandon Long
      3  * All Rights Reserved.
      4  *
      5  * ClearSilver Templating System
      6  *
      7  * This code is made available under the terms of the ClearSilver License.
      8  * http://www.clearsilver.net/license.hdf
      9  *
     10  */
     11 
     12 #include "cs_config.h"
     13 
     14 #include <unistd.h>
     15 #include <ctype.h>
     16 #include <stdlib.h>
     17 #include <stdio.h>
     18 #include <string.h>
     19 #include <stdarg.h>
     20 #include <regex.h>
     21 #include "neo_misc.h"
     22 #include "neo_err.h"
     23 #include "neo_str.h"
     24 #include "ulist.h"
     25 
     26 #ifndef va_copy
     27 #ifdef __va_copy
     28 # define va_copy(dest,src) __va_copy(dest,src)
     29 #else
     30 # define va_copy(dest,src) ((dest) = (src))
     31 #endif
     32 #endif
     33 
     34 char *neos_strip (char *s)
     35 {
     36   int x;
     37 
     38   x = strlen(s) - 1;
     39   while (x>=0 && isspace(s[x])) s[x--] = '\0';
     40 
     41   while (*s && isspace(*s)) s++;
     42 
     43   return s;
     44 }
     45 
     46 char *neos_rstrip (char *s)
     47 {
     48   int n = strlen (s)-1;
     49 
     50   while (n >= 0 && isspace(s[n]))
     51   {
     52     s[n] = '\0';
     53     n--;
     54   }
     55 
     56   return s;
     57 }
     58 
     59 void neos_lower(char *s)
     60 {
     61   while(*s != 0) {
     62     *s = tolower(*s);
     63     s++;
     64   }
     65 }
     66 
     67 
     68 void string_init (STRING *str)
     69 {
     70   str->buf = NULL;
     71   str->len = 0;
     72   str->max = 0;
     73 }
     74 
     75 void string_clear (STRING *str)
     76 {
     77   if (str->buf != NULL)
     78     free(str->buf);
     79   string_init(str);
     80 }
     81 
     82 static NEOERR* string_check_length (STRING *str, int l)
     83 {
     84   if (str->buf == NULL)
     85   {
     86     if (l * 10 > 256)
     87       str->max = l * 10;
     88     else
     89       str->max = 256;
     90     str->buf = (char *) malloc (sizeof(char) * str->max);
     91     if (str->buf == NULL)
     92       return nerr_raise (NERR_NOMEM, "Unable to allocate render buf of size %d",
     93 	  str->max);
     94    /*  ne_warn("Creating string %x at %d (%5.2fK)", str, str->max, (str->max / 1024.0)); */
     95   }
     96   else if (str->len + l >= str->max)
     97   {
     98     do
     99     {
    100       str->max *= 2;
    101     } while (str->len + l >= str->max);
    102     str->buf = (char *) realloc (str->buf, sizeof(char) * str->max);
    103     if (str->buf == NULL)
    104       return nerr_raise (NERR_NOMEM, "Unable to allocate STRING buf of size %d",
    105 	  str->max);
    106     /* ne_warn("Growing string %x to %d (%5.2fK)", str, str->max, (str->max / 1024.0)); */
    107   }
    108   return STATUS_OK;
    109 }
    110 
    111 NEOERR *string_set (STRING *str, const char *buf)
    112 {
    113   str->len = 0;
    114   return nerr_pass (string_append (str, buf));
    115 }
    116 
    117 NEOERR *string_append (STRING *str, const char *buf)
    118 {
    119   NEOERR *err;
    120   int l;
    121 
    122   l = strlen(buf);
    123   err = string_check_length (str, l);
    124   if (err != STATUS_OK) return nerr_pass (err);
    125   strcpy(str->buf + str->len, buf);
    126   str->len += l;
    127 
    128   return STATUS_OK;
    129 }
    130 
    131 NEOERR *string_appendn (STRING *str, const char *buf, int l)
    132 {
    133   NEOERR *err;
    134 
    135   err = string_check_length (str, l+1);
    136   if (err != STATUS_OK) return nerr_pass (err);
    137   memcpy(str->buf + str->len, buf, l);
    138   str->len += l;
    139   str->buf[str->len] = '\0';
    140 
    141   return STATUS_OK;
    142 }
    143 
    144 /* this is much more efficient with C99 snprintfs... */
    145 NEOERR *string_appendvf (STRING *str, const char *fmt, va_list ap)
    146 {
    147   NEOERR *err;
    148   char buf[4096];
    149   int bl, size;
    150   va_list tmp;
    151 
    152   va_copy(tmp, ap);
    153   /* determine length */
    154   size = sizeof (buf);
    155   bl = vsnprintf (buf, size, fmt, tmp);
    156   if (bl > -1 && bl < size)
    157     return string_appendn (str, buf, bl);
    158 
    159   /* Handle non-C99 snprintfs (requires extra malloc/free and copy) */
    160   if (bl == -1)
    161   {
    162     char *a_buf;
    163 
    164     va_copy(tmp, ap);
    165     a_buf = vnsprintf_alloc(size*2, fmt, tmp);
    166     if (a_buf == NULL)
    167       return nerr_raise(NERR_NOMEM,
    168 	  "Unable to allocate memory for formatted string");
    169     err = string_append(str, a_buf);
    170     free(a_buf);
    171     return nerr_pass(err);
    172   }
    173 
    174   err = string_check_length (str, bl+1);
    175   if (err != STATUS_OK) return nerr_pass (err);
    176   va_copy(tmp, ap);
    177   vsprintf (str->buf + str->len, fmt, tmp);
    178   str->len += bl;
    179   str->buf[str->len] = '\0';
    180 
    181   return STATUS_OK;
    182 }
    183 
    184 NEOERR *string_appendf (STRING *str, const char *fmt, ...)
    185 {
    186   NEOERR *err;
    187   va_list ap;
    188 
    189   va_start (ap, fmt);
    190   err = string_appendvf (str, fmt, ap);
    191   va_end (ap);
    192   return nerr_pass(err);
    193 }
    194 
    195 NEOERR *string_append_char (STRING *str, char c)
    196 {
    197   NEOERR *err;
    198   err = string_check_length (str, 1);
    199   if (err != STATUS_OK) return nerr_pass (err);
    200   str->buf[str->len] = c;
    201   str->buf[str->len + 1] = '\0';
    202   str->len += 1;
    203 
    204   return STATUS_OK;
    205 }
    206 
    207 void string_array_init (STRING_ARRAY *arr)
    208 {
    209   arr->entries = NULL;
    210   arr->count = 0;
    211   arr->max = 0;
    212 }
    213 
    214 NEOERR *string_array_split (ULIST **list, char *s, const char *sep,
    215                             int max)
    216 {
    217   NEOERR *err;
    218   char *p, *n, *f;
    219   int sl;
    220   int x = 0;
    221 
    222   if (sep[0] == '\0')
    223     return nerr_raise (NERR_ASSERT, "separator must be at least one character");
    224 
    225   err = uListInit (list, 10, 0);
    226   if (err) return nerr_pass(err);
    227 
    228   sl = strlen(sep);
    229   p = (sl == 1) ? strchr (s, sep[0]) : strstr (s, sep);
    230   f = s;
    231   while (p != NULL)
    232   {
    233     if (x >= max) break;
    234     *p = '\0';
    235     n = strdup(f);
    236     *p = sep[0];
    237     if (n) err = uListAppend (*list, n);
    238     else err = nerr_raise(NERR_NOMEM,
    239 	"Unable to allocate memory to split %s", s);
    240     if (err) goto split_err;
    241     f = p+sl;
    242     p = (sl == 1) ? strchr (f, sep[0]) : strstr (f, sep);
    243     x++;
    244   }
    245   /* Handle remainder */
    246   n = strdup(f);
    247   if (n) err = uListAppend (*list, n);
    248   else err = nerr_raise(NERR_NOMEM,
    249       "Unable to allocate memory to split %s", s);
    250   if (err) goto split_err;
    251   return STATUS_OK;
    252 
    253 split_err:
    254   uListDestroy(list, ULIST_FREE);
    255   return err;
    256 }
    257 
    258 void string_array_clear (STRING_ARRAY *arr)
    259 {
    260   int x;
    261 
    262   for (x = 0; x < arr->count; x++)
    263   {
    264     if (arr->entries[x] != NULL) free (arr->entries[x]);
    265     arr->entries[x] = NULL;
    266   }
    267   free (arr->entries);
    268   arr->entries = NULL;
    269   arr->count = 0;
    270 }
    271 
    272 /* Mostly used by vprintf_alloc for non-C99 compliant snprintfs,
    273  * this is like vsprintf_alloc except it takes a "suggested" size */
    274 int vnisprintf_alloc (char **buf, int start_size, const char *fmt, va_list ap)
    275 {
    276   int bl, size;
    277   va_list tmp;
    278 
    279   *buf = NULL;
    280   size = start_size;
    281 
    282   *buf = (char *) malloc (size * sizeof(char));
    283   if (*buf == NULL) return 0;
    284   while (1)
    285   {
    286     va_copy(tmp, ap);
    287     bl = vsnprintf (*buf, size, fmt, tmp);
    288     if (bl > -1 && bl < size)
    289       return bl;
    290     if (bl > -1)
    291       size = bl + 1;
    292     else
    293       size *= 2;
    294     *buf = (char *) realloc (*buf, size * sizeof(char));
    295     if (*buf == NULL) return 0;
    296   }
    297 }
    298 
    299 char *vnsprintf_alloc (int start_size, const char *fmt, va_list ap)
    300 {
    301   char *r;
    302   vnisprintf_alloc(&r, start_size, fmt, ap);
    303   return r;
    304 }
    305 
    306 /* This works better with a C99 compliant vsnprintf, but should work ok
    307  * with versions that return a -1 if it overflows the buffer */
    308 int visprintf_alloc (char **buf, const char *fmt, va_list ap)
    309 {
    310   char ibuf[4096];
    311   int bl, size;
    312   va_list tmp;
    313 
    314 /* PPC doesn't like you re-using a va_list... and it might not be
    315  * supposed to work at all */
    316   va_copy(tmp, ap);
    317 
    318   size = sizeof (ibuf);
    319   bl = vsnprintf (ibuf, sizeof (ibuf), fmt, tmp);
    320   if (bl > -1 && bl < size)
    321   {
    322     *buf = (char *) calloc(bl+1, sizeof(char));
    323     if (*buf == NULL) return 0;
    324     strncpy(*buf, ibuf, bl);
    325     return bl;
    326   }
    327 
    328   if (bl > -1)
    329     size = bl + 1;
    330   else
    331     size *= 2;
    332 
    333   return vnisprintf_alloc(buf, size, fmt, ap);
    334 }
    335 
    336 char *vsprintf_alloc (const char *fmt, va_list ap)
    337 {
    338   char *r;
    339   visprintf_alloc(&r, fmt, ap);
    340   return r;
    341 }
    342 
    343 /* technically, sprintf's can have null values, so we need to be able to
    344  * return a length also like real sprintf */
    345 int isprintf_alloc (char **buf, const char *fmt, ...)
    346 {
    347   va_list ap;
    348   int r;
    349 
    350   va_start (ap, fmt);
    351   r = visprintf_alloc (buf, fmt, ap);
    352   va_end (ap);
    353   return r;
    354 }
    355 
    356 char *sprintf_alloc (const char *fmt, ...)
    357 {
    358   va_list ap;
    359   char *r;
    360 
    361   va_start (ap, fmt);
    362   r = vsprintf_alloc (fmt, ap);
    363   va_end (ap);
    364   return r;
    365 }
    366 
    367 /* This is mostly just here for completeness, I doubt anyone would use
    368  * this (its more efficient (time-wise) if start_size is bigger than the
    369  * resulting string.  Its less efficient than sprintf_alloc if we have a
    370  * C99 snprintf and it doesn't fit in start_size.
    371  * BTW: If you are really worried about the efficiency of these
    372  * functions, maybe you shouldn't be using them in the first place... */
    373 char *nsprintf_alloc (int start_size, const char *fmt, ...)
    374 {
    375   va_list ap;
    376   char *r;
    377 
    378   va_start (ap, fmt);
    379   r = vnsprintf_alloc (start_size, fmt, ap);
    380   va_end (ap);
    381   return r;
    382 }
    383 
    384 BOOL reg_search (const char *re, const char *str)
    385 {
    386   regex_t search_re;
    387   int errcode;
    388   char buf[256];
    389 
    390   if ((errcode = regcomp(&search_re, re, REG_ICASE | REG_EXTENDED | REG_NOSUB)))
    391   {
    392     regerror (errcode, &search_re, buf, sizeof(buf));
    393     ne_warn ("Unable to compile regex %s: %s", re, buf);
    394     return FALSE;
    395   }
    396   errcode = regexec (&search_re, str, 0, NULL, 0);
    397   regfree (&search_re);
    398   if (errcode == 0)
    399     return TRUE;
    400   return FALSE;
    401 }
    402 
    403 NEOERR *string_readline (STRING *str, FILE *fp)
    404 {
    405   NEOERR *err;
    406 
    407   /* minimum size for a readline is 256 above current position */
    408   err = string_check_length (str, str->len + 256);
    409   if (err != STATUS_OK) return nerr_pass (err);
    410 
    411   while (fgets(str->buf + str->len, str->max - str->len, fp) != NULL)
    412   {
    413     str->len = strlen(str->buf);
    414     if (str->buf[str->len-1] == '\n') break;
    415     err = string_check_length (str, str->len + 256);
    416     if (err != STATUS_OK) return nerr_pass (err);
    417   }
    418   return STATUS_OK;
    419 }
    420 
    421 NEOERR* neos_escape(UINT8 *buf, int buflen, char esc_char, const char *escape,
    422                     char **esc)
    423 {
    424   int nl = 0;
    425   int l = 0;
    426   int x = 0;
    427   char *s;
    428   int match = 0;
    429 
    430   while (l < buflen)
    431   {
    432     if (buf[l] == esc_char)
    433     {
    434       nl += 2;
    435     }
    436     else
    437     {
    438       x = 0;
    439       while (escape[x])
    440       {
    441 	if (escape[x] == buf[l])
    442 	{
    443 	  nl +=2;
    444 	  break;
    445 	}
    446 	x++;
    447       }
    448     }
    449     nl++;
    450     l++;
    451   }
    452 
    453   s = (char *) malloc (sizeof(char) * (nl + 1));
    454   if (s == NULL)
    455     return nerr_raise (NERR_NOMEM, "Unable to allocate memory to escape %s",
    456 	buf);
    457 
    458   nl = 0; l = 0;
    459   while (l < buflen)
    460   {
    461     match = 0;
    462     if (buf[l] == esc_char)
    463     {
    464       match = 1;
    465     }
    466     else
    467     {
    468       x = 0;
    469       while (escape[x])
    470       {
    471 	if (escape[x] == buf[l])
    472 	{
    473 	  match = 1;
    474 	  break;
    475 	}
    476 	x++;
    477       }
    478     }
    479     if (match)
    480     {
    481       s[nl++] = esc_char;
    482       s[nl++] = "0123456789ABCDEF"[buf[l] / 16];
    483       s[nl++] = "0123456789ABCDEF"[buf[l] % 16];
    484       l++;
    485     }
    486     else
    487     {
    488       s[nl++] = buf[l++];
    489     }
    490   }
    491   s[nl] = '\0';
    492 
    493   *esc = s;
    494   return STATUS_OK;
    495 }
    496 
    497 UINT8 *neos_unescape (UINT8 *s, int buflen, char esc_char)
    498 {
    499   int i = 0, o = 0;
    500 
    501   if (s == NULL) return s;
    502   while (i < buflen)
    503   {
    504     if (s[i] == esc_char && (i+2 < buflen) &&
    505 	isxdigit(s[i+1]) && isxdigit(s[i+2]))
    506     {
    507       UINT8 num;
    508       num = (s[i+1] >= 'A') ? ((s[i+1] & 0xdf) - 'A') + 10 : (s[i+1] - '0');
    509       num *= 16;
    510       num += (s[i+2] >= 'A') ? ((s[i+2] & 0xdf) - 'A') + 10 : (s[i+2] - '0');
    511       s[o++] = num;
    512       i+=3;
    513     }
    514     else {
    515       s[o++] = s[i++];
    516     }
    517   }
    518   if (i && o) s[o] = '\0';
    519   return s;
    520 }
    521 
    522 char *repr_string_alloc (const char *s)
    523 {
    524   int l,x,i;
    525   int nl = 0;
    526   char *rs;
    527 
    528   if (s == NULL)
    529   {
    530     return strdup("NULL");
    531   }
    532 
    533   l = strlen(s);
    534   for (x = 0; x < l; x++)
    535   {
    536     if (isprint(s[x]) && s[x] != '"' && s[x] != '\\')
    537     {
    538       nl++;
    539     }
    540     else
    541     {
    542       if (s[x] == '\n' || s[x] == '\t' || s[x] == '\r' || s[x] == '"' ||
    543 	  s[x] == '\\')
    544       {
    545 	nl += 2;
    546       }
    547       else nl += 4;
    548     }
    549   }
    550 
    551   rs = (char *) malloc ((nl+3) * sizeof(char));
    552   if (rs == NULL)
    553     return NULL;
    554 
    555   i = 0;
    556   rs[i++] = '"';
    557   for (x = 0; x < l; x++)
    558   {
    559     if (isprint(s[x]) && s[x] != '"' && s[x] != '\\')
    560     {
    561       rs[i++] = s[x];
    562     }
    563     else
    564     {
    565       rs[i++] = '\\';
    566       switch (s[x])
    567       {
    568 	case '\n':
    569 	  rs[i++] = 'n';
    570 	  break;
    571 	case '\t':
    572 	  rs[i++] = 't';
    573 	  break;
    574 	case '\r':
    575 	  rs[i++] = 'r';
    576 	  break;
    577 	case '"':
    578 	  rs[i++] = '"';
    579 	  break;
    580 	case '\\':
    581 	  rs[i++] = '\\';
    582 	  break;
    583 	default:
    584 	  sprintf(&(rs[i]), "%03o", (s[x] & 0377));
    585 	  i += 3;
    586 	  break;
    587       }
    588     }
    589   }
    590   rs[i++] = '"';
    591   rs[i] = '\0';
    592   return rs;
    593 }
    594 
    595 // List of all characters that must be escaped
    596 // List based on http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
    597 static char EscapedChars[] = "$&+,/:;=?@ \"<>#%{}|\\^~[]`'";
    598 
    599 // Check if a single character needs to be escaped
    600 static BOOL is_reserved_char(char c)
    601 {
    602   int i = 0;
    603 
    604   if (c < 32 || c > 122) {
    605     return TRUE;
    606   } else {
    607     while (EscapedChars[i]) {
    608       if (c == EscapedChars[i]) {
    609         return TRUE;
    610       }
    611       ++i;
    612     }
    613   }
    614   return FALSE;
    615 }
    616 
    617 NEOERR *neos_js_escape (const char *in, char **esc)
    618 {
    619   int nl = 0;
    620   int l = 0;
    621   unsigned char *buf = (unsigned char *)in;
    622   unsigned char *s;
    623 
    624   while (buf[l])
    625   {
    626     if (buf[l] == '/' || buf[l] == '"' || buf[l] == '\'' ||
    627         buf[l] == '\\' || buf[l] == '>' || buf[l] == '<' ||
    628         buf[l] == '&' || buf[l] == ';' || buf[l] < 32)
    629     {
    630       nl += 3;
    631     }
    632     nl++;
    633     l++;
    634   }
    635 
    636   s = (unsigned char *) malloc (sizeof(unsigned char) * (nl + 1));
    637   if (s == NULL)
    638     return nerr_raise (NERR_NOMEM, "Unable to allocate memory to escape %s",
    639         buf);
    640 
    641   nl = 0; l = 0;
    642   while (buf[l])
    643   {
    644     if (buf[l] == '/' || buf[l] == '"' || buf[l] == '\'' ||
    645         buf[l] == '\\' || buf[l] == '>' || buf[l] == '<' ||
    646         buf[l] == '&' || buf[l] == ';' || buf[l] < 32)
    647     {
    648       s[nl++] = '\\';
    649       s[nl++] = 'x';
    650       s[nl++] = "0123456789ABCDEF"[(buf[l] >> 4) & 0xF];
    651       s[nl++] = "0123456789ABCDEF"[buf[l] & 0xF];
    652       l++;
    653     }
    654     else
    655     {
    656       s[nl++] = buf[l++];
    657     }
    658   }
    659   s[nl] = '\0';
    660 
    661   *esc = (char *)s;
    662   return STATUS_OK;
    663 }
    664 
    665 
    666 NEOERR *neos_url_escape (const char *in, char **esc,
    667                          const char *other)
    668 {
    669   int nl = 0;
    670   int l = 0;
    671   int x = 0;
    672   unsigned char *buf = (unsigned char *)in;
    673   unsigned char *uother = (unsigned char *)other;
    674   unsigned char *s;
    675   int match = 0;
    676 
    677   while (buf[l])
    678   {
    679     if (is_reserved_char(buf[l]))
    680     {
    681       nl += 2;
    682     }
    683     else if (uother)
    684     {
    685       x = 0;
    686       while (uother[x])
    687       {
    688         if (uother[x] == buf[l])
    689         {
    690           nl +=2;
    691           break;
    692         }
    693         x++;
    694       }
    695     }
    696     nl++;
    697     l++;
    698   }
    699 
    700   s = (unsigned char *) malloc (sizeof(unsigned char) * (nl + 1));
    701   if (s == NULL)
    702     return nerr_raise (NERR_NOMEM, "Unable to allocate memory to escape %s",
    703       buf);
    704 
    705   nl = 0; l = 0;
    706   while (buf[l])
    707   {
    708     match = 0;
    709     if (buf[l] == ' ')
    710     {
    711       s[nl++] = '+';
    712       l++;
    713     }
    714     else
    715     {
    716       if (is_reserved_char(buf[l]))
    717       {
    718         match = 1;
    719       }
    720       else if (uother)
    721       {
    722         x = 0;
    723         while (uother[x])
    724         {
    725           if (uother[x] == buf[l])
    726           {
    727             match = 1;
    728             break;
    729           }
    730           x++;
    731         }
    732       }
    733       if (match)
    734       {
    735         s[nl++] = '%';
    736         s[nl++] = "0123456789ABCDEF"[buf[l] / 16];
    737         s[nl++] = "0123456789ABCDEF"[buf[l] % 16];
    738         l++;
    739       }
    740       else
    741       {
    742         s[nl++] = buf[l++];
    743       }
    744     }
    745   }
    746   s[nl] = '\0';
    747 
    748   *esc = (char *)s;
    749   return STATUS_OK;
    750 }
    751 
    752 NEOERR *neos_html_escape (const char *src, int slen,
    753                           char **out)
    754 {
    755   NEOERR *err = STATUS_OK;
    756   STRING out_s;
    757   int x;
    758   char *ptr;
    759 
    760   string_init(&out_s);
    761   err = string_append (&out_s, "");
    762   if (err) return nerr_pass (err);
    763   *out = NULL;
    764 
    765   x = 0;
    766   while (x < slen)
    767   {
    768     ptr = strpbrk(src + x, "&<>\"'\r");
    769     if (ptr == NULL || (ptr-src >= slen))
    770     {
    771       err = string_appendn (&out_s, src + x, slen-x);
    772       x = slen;
    773     }
    774     else
    775     {
    776       err = string_appendn (&out_s, src + x, (ptr - src) - x);
    777       if (err != STATUS_OK) break;
    778       x = ptr - src;
    779       if (src[x] == '&')
    780         err = string_append (&out_s, "&amp;");
    781       else if (src[x] == '<')
    782         err = string_append (&out_s, "&lt;");
    783       else if (src[x] == '>')
    784         err = string_append (&out_s, "&gt;");
    785       else if (src[x] == '"')
    786         err = string_append (&out_s, "&quot;");
    787       else if (src[x] == '\'')
    788         err = string_append (&out_s, "&#39;");
    789       else if (src[x] != '\r')
    790         err = nerr_raise (NERR_ASSERT, "src[x] == '%c'", src[x]);
    791       x++;
    792     }
    793     if (err != STATUS_OK) break;
    794   }
    795   if (err)
    796   {
    797     string_clear (&out_s);
    798     return nerr_pass (err);
    799   }
    800   *out = out_s.buf;
    801   return STATUS_OK;
    802 }
    803 
    804 char *URL_PROTOCOLS[] = {"http://", "https://", "ftp://", "mailto:"};
    805 
    806 NEOERR *neos_url_validate (const char *in, char **esc)
    807 {
    808   NEOERR *err = STATUS_OK;
    809   STRING out_s;
    810   int valid = 0;
    811   size_t i;
    812   size_t inlen;
    813   int num_protocols = sizeof(URL_PROTOCOLS) / sizeof(char*);
    814   void* slashpos;
    815   void* colonpos;
    816 
    817   inlen = strlen(in);
    818 
    819   /*
    820    * <a href="//b:80"> or <a href="a/b:80"> are allowed by browsers
    821    * and ":" is treated as part of the path, while
    822    * <a href="www.google.com:80"> is an invalid url
    823    * and ":" is treated as a scheme separator.
    824    *
    825    * Hence allow for ":" in the path part of a url (after /)
    826    */
    827   slashpos = memchr(in, '/', inlen);
    828   if (slashpos == NULL) {
    829     i = inlen;
    830   }
    831   else {
    832     i = (size_t)((char*)slashpos - in);
    833   }
    834 
    835   colonpos = memchr(in, ':', i);
    836 
    837   if (colonpos == NULL) {
    838     // no scheme in 'in': so this is a relative url
    839     valid = 1;
    840   }
    841   else {
    842     for (i = 0; i < num_protocols; i++)
    843     {
    844       if ((inlen >= strlen(URL_PROTOCOLS[i])) &&
    845           strncmp(in, URL_PROTOCOLS[i], strlen(URL_PROTOCOLS[i])) == 0) {
    846         // 'in' starts with one of the allowed protocols
    847         valid = 1;
    848         break;
    849       }
    850 
    851     }
    852   }
    853 
    854   if (valid)
    855     return neos_html_escape(in, inlen, esc);
    856 
    857   // 'in' contains an unsupported scheme, replace with '#'
    858   string_init(&out_s);
    859   err = string_append (&out_s, "#");
    860   if (err) return nerr_pass (err);
    861 
    862   *esc = out_s.buf;
    863   return STATUS_OK;
    864 
    865 }
    866 
    867 NEOERR *neos_var_escape (NEOS_ESCAPE context,
    868                          const char *in,
    869                          char **esc)
    870 {
    871 
    872   /* Just dup and return if we do nothing. */
    873   if (context == NEOS_ESCAPE_NONE ||
    874       context == NEOS_ESCAPE_FUNCTION)
    875   {
    876     *esc = strdup(in);
    877     return STATUS_OK;
    878   }
    879 
    880   /* Now we escape based on context. This is the order of precedence:
    881    * url > script > style > html
    882    */
    883   if (context & NEOS_ESCAPE_URL)
    884     return nerr_pass(neos_url_escape(in, esc, NULL));
    885   else if (context & NEOS_ESCAPE_SCRIPT)
    886     return nerr_pass(neos_js_escape(in, esc));
    887   else if (context & NEOS_ESCAPE_HTML)
    888     return nerr_pass(neos_html_escape(in, strlen(in), esc));
    889 
    890   return nerr_raise(NERR_ASSERT, "unknown escape context supplied: %d",
    891     context);
    892 }
    893