/*
 * 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 <unistd.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <regex.h>
#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++;
  }
}

char *neos_strndup(const char *s, int len)
{
  int x;
  char *dupl;
  if (s == NULL) return NULL;
  dupl = (char *) malloc(len+1);
  if (dupl == NULL) return NULL;
  for (x = 0; x < len && s[x]; x++)
  {
    dupl[x] = s[x];
  }
  dupl[x] = '\0';
  dupl[len] = '\0';
  return dupl;
}

void neo_string_init (STRING *str)
{
  str->buf = NULL;
  str->len = 0;
  str->max = 0;
  str->fixed = 0;
}

void neo_string_clear (STRING *str)
{
  if (str->buf != NULL)
    free(str->buf);
  neo_string_init(str);
}

static NEOERR* neo_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)
  {
    void *new_ptr;
    int new_max = str->max;

    /* TODO(blong): better would be to fill to size and drop the rest */
    if (str->fixed)
      return nerr_raise(NERR_ASSERT, "Length exceeds fixed size %d", str->max);

    do
    {
      new_max *= 2;
    } while (str->len + l >= new_max);
    new_ptr = realloc (str->buf, sizeof(char) * new_max);
    if (new_ptr == NULL) {
      return nerr_raise (NERR_NOMEM, "Unable to allocate STRING buf of size %d",
	  new_max);
    }
    str->buf = (char *) new_ptr;
    str->max = new_max;
    /* ne_warn("Growing string %x to %d (%5.2fK)", str, str->max, (str->max / 1024.0)); */
  }
  return STATUS_OK;
}

NEOERR *neo_string_set (STRING *str, const char *buf)
{
  str->len = 0;
  return nerr_pass (neo_string_append (str, buf));
}

NEOERR *neo_string_append (STRING *str, const char *buf)
{
  NEOERR *err;
  int l;

  l = strlen(buf);
  err = neo_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 *neo_string_appendn (STRING *str, const char *buf, int l)
{
  NEOERR *err;

  err = neo_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 *neo_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 neo_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 = neo_string_append(str, a_buf);
    free(a_buf);
    return nerr_pass(err);
  }

  err = neo_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 *neo_string_appendf (STRING *str, const char *fmt, ...)
{
  NEOERR *err;
  va_list ap;

  va_start (ap, fmt);
  err = neo_string_appendvf (str, fmt, ap);
  va_end (ap);
  return nerr_pass(err);
}

NEOERR *neo_string_append_char (STRING *str, char c)
{
  NEOERR *err;
  err = neo_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 neo_string_array_init (STRING_ARRAY *arr)
{
  arr->entries = NULL;
  arr->count = 0;
  arr->max = 0;
}

NEOERR *neo_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 neo_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)
  {
    void *new_ptr;
    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;
    new_ptr = realloc (*buf, size * sizeof(char));
    if (new_ptr == NULL) {
      free(*buf);
      *buf = NULL;
      return 0;
    }
    *buf = (char *) new_ptr;
  }
}

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 *neo_string_readline (STRING *str, FILE *fp)
{
  NEOERR *err;

  /* minimum size for a readline is 256 above current position */
  err = neo_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 = neo_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;
}

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;
}

static int is_json_escaped_char(unsigned char c) {
  // RFC 4627 says only reverse-solidus, quotation mark and control characters
  // must be escaped in JSON, but we want other characters escaped for safety.
  return (c == '/' || c == '"' || c == '\'' || c == '\\' ||
          c == '>' || c == '<' || c == '&' || c == ';' || c < 32);
}

NEOERR *neos_json_escape (const char *in, char **esc)
{
  int new_length = 0;
  int length = 0;
  unsigned char *buf = (unsigned char *)in;
  unsigned char *s;

  while (buf[length])
  {
    if (is_json_escaped_char(buf[length]))
    {
      new_length += 5;
    }
    new_length++;
    length++;
  }

  s = (unsigned char *) malloc (sizeof(unsigned char) * (new_length + 1));
  if (s == NULL) {
    return nerr_raise (NERR_NOMEM, "Unable to allocate memory to escape %s",
        buf);
  }

  new_length = 0; length = 0;
  while (buf[length])
  {
    if (is_json_escaped_char(buf[length]))
    {
      s[new_length++] = '\\';
      s[new_length++] = 'u';
      s[new_length++] = '0';
      s[new_length++] = '0';
      s[new_length++] = "0123456789ABCDEF"[(buf[length] >> 4) & 0xF];
      s[new_length++] = "0123456789ABCDEF"[buf[length] & 0xF];
      length++;
    }
    else
    {
      s[new_length++] = buf[length++];
    }
  }
  s[new_length] = '\0';

  *esc = (char *)s;
  return STATUS_OK;
}

/* List of all characters that must be escaped
 * List based on http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
 */
static char QueryReservedChars[] = "$&+,/:;=?@ \"<>#%{}|\\^~[]`'";
// List of characters to escape in URLs inside CSS.
static char CssReservedChars[] = "\n\r\"'()*<>\\";
#define IN_LIST(l, c) (strchr(l, c) != NULL)

/*
 * Apply URL escaping to 'in' and return result in 'esc'.
 * The parameters 'reserved' and 'other' indicate which characters to escape.
 * If 'escape_non_printable' is non zero, all characters < 0x20 and > 0x7E
 * will also be escaped.
 * If map_space_to_plus is true, then ' ' will be mapped to '+'. Else it will be
 * mapped to "%20".
 */
static NEOERR *url_escape_helper (const char *in, char **esc, char *reserved,
                                  const char *other, int escape_non_printable,
                                  int map_space_to_plus)
{
  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 (IN_LIST(reserved, buf[l]) || 
        (escape_non_printable && (buf[l] < 32 || buf[l] > 126)))
    {
      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 (map_space_to_plus && buf[l] == ' ' && IN_LIST(reserved, buf[l]))
    {
      s[nl++] = '+';
      l++;
    }
    else
    {
      if (IN_LIST(reserved, buf[l]) ||
          (escape_non_printable && (buf[l] < 32 || buf[l] > 126)))
      {
        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_url_escape (const char *in, char **esc,
                         const char *other)
{
  return url_escape_helper(in, esc, QueryReservedChars, other, 1, 1);
}

NEOERR *neos_url_escape_rfc2396(const char *in, char **esc,
                                const char *other)
{
  return url_escape_helper(in, esc, QueryReservedChars, other, 1, 0);
}

NEOERR *neos_html_escape (const char *src, int slen,
                          char **out)
{
  NEOERR *err = STATUS_OK;
  STRING out_s;
  int x;
  char *ptr;

  neo_string_init(&out_s);
  err = neo_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 = neo_string_appendn (&out_s, src + x, slen-x);
      x = slen;
    }
    else
    {
      err = neo_string_appendn (&out_s, src + x, (ptr - src) - x);
      if (err != STATUS_OK) break;
      x = ptr - src;
      if (src[x] == '&')
        err = neo_string_append (&out_s, "&amp;");
      else if (src[x] == '<')
        err = neo_string_append (&out_s, "&lt;");
      else if (src[x] == '>')
        err = neo_string_append (&out_s, "&gt;");
      else if (src[x] == '"')
        err = neo_string_append (&out_s, "&quot;");
      else if (src[x] == '\'')
        err = neo_string_append (&out_s, "&#39;");
      else if (src[x] != '\r')
        err = nerr_raise (NERR_ASSERT, "src[x] == '%c'", src[x]);
      x++;
    }
    if (err != STATUS_OK) break;
  }
  if (err)
  {
    neo_string_clear (&out_s);
    return nerr_pass (err);
  }
  *out = out_s.buf;
  return STATUS_OK;
}

static NEOERR *css_url_escape(const char *in, char **esc)
{
  return url_escape_helper(in, esc, CssReservedChars, NULL, 0, 1);
}

char *URL_PROTOCOLS[] = {"http://", "https://", "ftp://", "mailto:"};

int neos_has_secure_protocol(const char *in)
{
  int end;
  int i;
  void* colonpos;
  const char* scheme;
  int valid = 0;
  int num_protocols = sizeof(URL_PROTOCOLS) / sizeof(char*);

  /*
   * <a href="//b:80"> or <a href="a/b:80"> are allowed by browsers
   * and ":" is treated as part of the path, while
   * <a href="www.google.com:80"> is an invalid url
   * and ":" is treated as a scheme separator.
   *
   * Hence allow for ":" in the path part of a url (after /) or query (after ?)
   */
  end = 0;
  while(in[end])
  {
    if (in[end] == '/' || in[end] == '?')
      break;
    end++;
  }

  colonpos = memchr(in, ':', end);

  if (colonpos == NULL)
  {
    /* no scheme in 'in': so this is a relative url */
    valid = 1;
  }
  else
  {
    scheme = in;
    while (isspace(*scheme))
      scheme++;

    for (i = 0; i < num_protocols; i++)
    {
      if ((strlen(scheme) >= strlen(URL_PROTOCOLS[i])) &&
          strncasecmp(scheme, URL_PROTOCOLS[i], strlen(URL_PROTOCOLS[i]))
          == 0) {
        /* 'in' starts with one of the allowed protocols */
        valid = 1;
        break;
      }
    }
  }

  return valid;
}

/*
 * Helper function to validate a URL for protecting against XSS.
 * Ensures that the URL is a relative URL or an absolute url with a safe scheme
 * (currently http, https, ftp or mailto). This is to avoid
 * dangerous schemes like javascript. It then escapes the URL in the requested
 * escape_mode.
 */
static NEOERR *url_validate(const char *in, char **esc, NEOS_ESCAPE escape_mode)
{
  NEOERR *err = STATUS_OK;
  STRING out_s;

  if (neos_has_secure_protocol(in))
  {
    if (escape_mode == NEOS_ESCAPE_HTML)
    {
      return neos_html_escape(in, strlen(in), esc);
    }
    else if (escape_mode == NEOS_ESCAPE_CSS_URL)
    {
      return css_url_escape(in, esc);
    }
    else
    {
      return nerr_raise(NERR_ASSERT, "Invalid escape mode: %d\n", escape_mode);
    }
  }

  /* 'in' contains an unsupported scheme, replace with '#' */
  neo_string_init(&out_s);
  err = neo_string_append (&out_s, "#");
  if (err) return nerr_pass (err);

  *esc = out_s.buf;
  return STATUS_OK;
}

NEOERR *neos_url_validate (const char *in, char **esc)
{
  return url_validate(in, esc, NEOS_ESCAPE_HTML);
}

NEOERR *neos_css_url_validate (const char *in, char **esc)
{
  return url_validate(in, esc, NEOS_ESCAPE_CSS_URL);
}

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);
}
