/* source: utils.c */
/* Copyright Gerhard Rieger and contributors (see file CHANGES) */
/* Published under the GNU General Public License V.2, see file COPYING */

/* useful additions to C library */

#include "config.h"

#include "sysincludes.h"

#include "compat.h"	/* socklen_t */
#include "mytypes.h"
#include "sycls.h"
#include "utils.h"


#if !HAVE_PROTOTYPE_LIB_memrchr
/* GNU extension, available since glibc 2.1.91 */
void *memrchr(const void *s, int c, size_t n) {
   const unsigned char *t = ((unsigned char *)s)+n;
   while (--t >= (unsigned char *)s) {
      if (*t == c)  break;
   }
   if (t < (unsigned char *)s)
      return NULL;
   return (void *)t;
}
#endif /* !HAVE_PROTOTYPE_LIB_memrchr */

void *memdup(const void *src, size_t n) {
   void *dest;

   if ((dest = Malloc(n)) == NULL) {
      return NULL;
   }

   memcpy(dest, src, n);
   return dest;
}

/* search the keyword-table for a match of the leading part of name. */
/* returns the pointer to the matching field of the keyword or NULL if no
   keyword was found. */
const struct wordent *keyw(const struct wordent *keywds, const char *name, unsigned int nkeys) {
   unsigned int lower, upper, mid;
   int r;

   lower = 0;
   upper = nkeys;

   while (upper - lower > 1)
   {
      mid = (upper + lower) >> 1;
      if (!(r = strcasecmp(keywds[mid].name, name)))
      {
	 return &keywds[mid];
      }
      if (r < 0)
	 lower = mid;
      else
	 upper = mid;
   }
   if (nkeys > 0 && !(strcasecmp(keywds[lower].name, name)))
   {
      return &keywds[lower];
   }
   return NULL;
}

/* Linux: setenv(), AIX (4.3?): putenv() */
#if !HAVE_SETENV
int setenv(const char *name, const char *value, int overwrite) {
   int result;
   char *env;
   if (!overwrite) {
      if (getenv(name))  return 0;	/* already exists */
   }
   if ((env = Malloc(strlen(name)+strlen(value)+2)) == NULL) {
      return -1;
   }
   sprintf(env, "%s=%s", name, value);
   if ((result = putenv(env)) != 0) {	/* AIX docu says "... nonzero ..." */
      free(env);
      result = -1;
   }
   /* linux "man putenv" says: ...this string becomes part of the environment*/
   return result;
}
#endif /* !HAVE_SETENV */


/* sanitizes an "untrusted" character. output buffer must provide at least 4
   characters space.
   Does not append \0. returns length of output (currently: max 4) */
static size_t sanitize_char(char c, char *o, int style) {
   int hn;	/* high nibble */
   int ln;	/* low nibble */
   int n;	/* written chars */
   if (isprint((unsigned char)c)) {
      *o = c;
      return 1;
   }
   *o++ = '\\';
   n = 2;
   switch (c) {
   case '\0': *o++ = '0';  break;
   case '\a': *o++ = 'a';  break;
   case '\b': *o++ = 'b';  break;
   case '\t': *o++ = 't';  break;
   case '\n': *o++ = 'n';  break;
   case '\v': *o++ = 'v';  break;
   case '\f': *o++ = 'f';  break;
   case '\r': *o++ = 'r';  break;
   case '\'': *o++ = '\''; break;
   case '\"': *o++ = '"';  break;
   case '\\': *o++ = '\\'; break;
   default:
      *o++ = 'x';
      hn = (c>>4)&0x0f;
      ln = c&0x0f;
      *o++ = (hn>=10 ? (('A'-1)+(hn-10)) : ('0'+hn));
      *o++ = (ln>=10 ? (('A'-1)+(ln-10)) : ('0'+ln));
      n = 4;
   }
   return n;
}

/* sanitizes "untrusted" text, replacing special control characters with the C
   string version (eg."\n"), and replacing unprintable chars with hex
   representation ("\xAB").
   text can grow to four times of input, so keep output buffer long enough!
   returns a pointer to the first untouched byte of the output buffer.
   Output is not \0 terminated.
*/
char *sanitize_string(const char *data,	/* input data */
		   size_t bytes,	/* length of input data, >=0 */
		   char *coded,	/* output buffer, must be long enough */
		   int style
		   ) {
   int c;

   while (bytes > 0) {
      c = *(unsigned char *)data++;
      coded += sanitize_char(c, coded, style);
      --bytes;
   }
   return coded;
}

/* copies a substring out of a given buff
   returns scratch, \0 terminated; scratch must provide len+1 bytes
*/
char *xiosubstr(char *scratch, const char *str, size_t from, size_t len) {
   char *scratch0 = scratch;
   str += from;
   while (len--) {
      *scratch++ = *str++;
   }
   *scratch = '\0';
   return scratch0;
}


/* since version 1.7.2.4 socat supports C-99 behaviour of snprintf but still
   can handle the old glibc case with -1 return on truncation.
   Do not rely on exact return value in case of truncation
 */
int xio_snprintf(char *str, size_t size, const char *format, ...) {
   va_list ap;
   int result;

   va_start(ap, format);
   result = vsnprintf(str, size, format, ap);
#if ! HAVE_C99_SNPRINTF
   if (result < 0) {
      result = size+63;	/* indicate truncation with just some guess */
   }
#endif /* !HAVE_C99_SNPRINTF */
   va_end(ap);
   return result;
}
