/*++++
  strfncs.c provides some string and array helpers
  markus@mhoenicka.de 2-8-00
  ++++++*/

/*
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, see <http://www.gnu.org/licenses/>

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <limits.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <errno.h>

#include "strfncs.h"
#include "outformats.h"
#include "rtfhelper.h" /* for RTF treatment in sgml_entitize */

/* Max integer that can be stored in a size_t variable */
#ifndef SIZE_T_MAX
#define SIZE_T_MAX UINT_MAX
#endif /* !SIZE_T_MAX */

/* forward declarations of local functions */
static int is_ip(char *address);


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  is_ip(): checks whether string contains a valid IP address
           This function does not verify that the given IP address
	   exists or is accessible, but rather checks whether the
	   string is sane.

  static int is_ip returns 0 if error, 1 if ok

  char *address ptr to the string to check

  returns 0 if invalid, 1 if valid

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int is_ip(char *address) {
  char *token[4];
  char ip_address[16];
  int i, n_value;

  /* refuse if string is too long */
  if (strlen(address) > 15) {
    return 0;
  }

  /* make a local copy as strtok modifies its argument */
  strncpy(ip_address, address, 16);
  ip_address[15] = '\0';

  /* now look for four tokens separated by "." */
  token[0] = strtok(ip_address, ".");
  if (token[0] == NULL) {
    return 0;
  }

  token[1] = strtok(NULL, ".");
  if (token[1] == NULL) {
    return 0;
  }

  token[2] = strtok(NULL, ".");
  if (token[2] == NULL) {
    return 0;
  }

  token[3] = strtok(NULL, "\r\n");
  if (token[3] == NULL) {
    return 0;
  }

  /* see whether the tokens are in the allowed numerical range */
  for (i = 0; i < 4; i++) {
    n_value = atoi(token[i]);
    if (n_value < 0 || n_value > 255) {
      return 0;
    }
  }
  return 1;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  check_ip(): validate an IP address/hostname
              The fn first tests whether the input string looks like
	      a dotted quad. If not, the string is assumed to be a
	      hostname. It tries to resolve this host. If it has one
	      IP address, the input string will be replaced with a
	      string with a dotted quad representation of the IP
	      address. If the host has more than one IP address, no
	      substitution is performed.

  int check_ip returns 0 if the string looks like a dotted quad or
              if the string can be resolved as a hostname for exactly
	      one IP address. Returns -1 if the string is 'localhost'
	      which has a special meaning for some database servers.
	      Returns > 0 if there's a problem:
	      1: cannot be resolved as a hostname
	      2: does not appear to be an IP host
	      3: does not appear to have a valid IP address
	      4: has more than one network interface

  char* server_ip ptr to a string with the IP-address or the putative
              hostname. The size of the buffer must be at least 16
              chars regardless of the length of the input string as
	      it will receive a dotted quad representation
	      under certain circumstances.

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int check_ip (char* server_ip) {
  char new_server_ip[16];
  char** addrs;
  int num_interfaces = 0;
  struct hostent *hostinfo;

  if (server_ip == NULL) {
    return 1;
  }

  if (is_ip(server_ip)) {
    return 0; /* looks like a dotted quad */
  }

  if (!strcmp(server_ip, "localhost")) {
    return -1; /* special hostname string for some database servers */
  }

  /* try to use the string as a hostname */
  hostinfo = gethostbyname(server_ip);

  if (hostinfo == NULL) {
    return 1;
  }

  if (hostinfo->h_addrtype != AF_INET) {
    return 2;
  }

  addrs = hostinfo->h_addr_list;
  while (*addrs && num_interfaces < 3) {
    sprintf(new_server_ip, "%s", inet_ntoa(*(struct in_addr*)*addrs));
    num_interfaces++;
    addrs++;
  }

  if (!num_interfaces) {
    return 3;
  }
  else if (num_interfaces == 1) {
    strcpy(server_ip, new_server_ip);
    return 0;
  }
  else {
    return 4;
  }
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  is_port(): checks whether string contains a valid port
             This fn simply ensures that the port is outside the
             range that the system uses, i.e. > 1024

  int is_port

  char *address ptr to the string to check

  returns 0 if invalid, 1 if valid

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int is_port(char *port) {
  if (port == NULL) {
    return 0;
  }

  if (atoi(port) < 1024) {
    return 0;
  }
  else {
    return 1;
  }
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  is_number(): tests whether a string is a number

  int is_number returns 1 if the string is a representation of a positive
                integer; returns 0 if the string does not exist or is
		empty or contains other characters than digits

  char* string address of string to test

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int is_number(char *string) {
  char *curr;

  if (!string || !*string) {
    return 0;
  }

  curr = string;

  while (*curr) {
    if (!isdigit((int)(*curr))) {
      return 0;
    }
    curr++;
  }
  return 1;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  is_real_number(): tests whether a string is a number including
                    decimal fractions

  int is_real_number returns 1 if the string is a representation of a positive
                number; returns 0 if the string does not exist or is
		empty or contains other characters than digits or a decimal
		point or if there is more than one decimal point

  char* string address of string to test

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int is_real_number(const char *string) {
  char *curr;
  int decpoint = 0;

  if (!string || !*string) {
    return 0;
  }

  curr = (char*)string;

  while (*curr) {
    if (*curr == '.') {
      decpoint++;
    }

    if ((!isdigit((int)(*curr)) && *curr != '.') || decpoint > 1) {
      return 0;
    }
    curr++;
  }
  return 1;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  stripwhite(): strips whitespace from the start and end of STRING

  char* stripwhite

  char* string address of string to convert

  int mode 0 = strips start and end, 1 = start only, 2 = end only

  int type 0 = only space and tab 1 = space, tab, cr, lf

  Returns ptr to the modified string

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char *stripwhite (char *string, int mode, int type) {
  /* this function was modified from a readline sample program. The
     original style is unnecessarily cryptic, but it works. */
  register char *s, *t;

  if (string == NULL) {
    return NULL;
  }

  s = string;

  if (type) {
    if (mode != 2) {
      for (; full_whitespace (*s); s++)
	;
      
      if (*s == 0)
	return (s);
    }

    if (mode != 1) {
      t = s + strlen (s) - 1;
      while (t > s && full_whitespace (*t))
	t--;
      *++t = '\0';
    }
  }
  else {
    if (mode != 2) {
      for (; whitespace (*s); s++)
	;
      
      if (*s == 0)
	return (s);
    }

    if (mode != 1) {
      t = s + strlen (s) - 1;
      while (t > s && whitespace (*t))
	t--;
      *++t = '\0';
    }
  }
  return s;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  strup(): converts a string in place to uppercase
  
  char* strup() returns a pointer to the modified string

  char* string pointer to the string to be converted

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* strup(char* string) {
  char* chr;

  if (string == NULL) {
    return NULL;
  }

  chr = string; /* don't modify string, we need it as a return value */

  /* loop until we find \0 */
  while (*chr) {
    *chr = (char)toupper((int)*chr); /* now convert */
    chr++;
  }
  return string;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  strdn(): converts a string in place to lowercase
  
  char* strup() returns a pointer to the modified string

  char* string pointer to the string to be converted

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* strdn(char* string) {
  char* chr;

  if (string == NULL) {
    return NULL;
  }

  chr = string; /* don't modify string, we need it as a return value */

  /* loop until we find \0 */
  while (*chr) {
    *chr = (char)tolower((int)*chr); /* now convert */
    chr++;
  }
  return string;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  stricap(): converts a string in place to lowercase, but capitalize
             the first letter of each word
  
  char* stricap() returns a pointer to the modified string

  char* string pointer to the string to be converted

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* stricap(char* string) {
  char* period;

  strdn(string);
  /* now uppercase the first character of each word unless it is something like "the", "a" etc. */
    
  /* ToDo: handle the "the", "a" etc. cases */
  period = string;
  if (islower((int)(*period))) {
    *period = (char)toupper((int)(*period));
  }
  period++;
  while (*period != '\0') {
    if (ispunct((int)(*period)) || isspace((int)(*period))) {
      if (islower((int)(*(period+1)))) {
	*(period+1) = (char)toupper((int)(*(period+1)));
      }
    }
    period++;
  }

  return string;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  compare_ptr(): compares two pointers to strings

  int compare_ptr returns -1 if the first argument is smaller than the
                  second; 0 if both are equal; 1 if the first is larger

  void* ptr_one the first char* to compare

  void* ptr_two the second char* to compare

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int compare_ptr(const void* ptr_one, const void* ptr_two) {
  char** firstptr;
  char** secondptr;

  firstptr = (char**)ptr_one;
  secondptr = (char**)ptr_two;

  /* handle cases where at least one of the ptrs is a NULL ptr */
  if (*firstptr == NULL) {
    if (*secondptr == NULL) {
      return 0;
    }
    else {
      return 1;
    }
  }
  else if (*secondptr == NULL) {
    return -1;
  }

  /* all ptrs are non-NULL now */
  if (*firstptr < *secondptr) {
    return -1;
  }
  else if (*firstptr == *secondptr) {
    return 0;
  }
  else {
    return 1;
  }
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  mstrcat(): a modified version of strcat which operates on destination
             strings obtained with malloc(); the length of the buffer
             is dynamically increased if necessary.

  char* mstrcat returns a pointer to the destination string or NULL
        if the realloc failed. As the buffer holding the destination
        string may have been reallocated, it is mandatory to use
        *ONLY* this returned pointer after the function call and
        *NEVER* the old pointer to destination

  char* destination the buffer obtained with malloc() to which the 
        source string will be appended. The calling function is
        responsible to free this buffer after use

  char* source the string that will be appended to destination

  size_t* ptr_dest_len points to a variable that contains the current size
       of the buffer that destination points to. Will be modified if
       a realloc() is necessary to increase the buffer size

  size_t offset a positive value n will concatenate the string at the 
      position omega-n of the destination string. Set offset to zero
      to get the standard strcat behaviour.

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* mstrcat(char* destination, char* source, size_t* ptr_dest_len, size_t offset) {
  char *new_dest;
  size_t len;
  size_t source_len;
  size_t destination_len;

  /* take the short way out if destination doesn't exist */
  if (destination == NULL || source == NULL) {
    return NULL;
  }

  source_len = strlen(source);
  destination_len = strlen(destination);

  /* don't allow offsets longer than the destination length */
  offset = (offset > destination_len) ? destination_len : offset;

  /* we need the sum of both string lengths plus one for the \0 minus
     the offset if there is one */
  len = destination_len + source_len + 1 - offset;

  /* reallocate buffer if it is too small */
  if (len > *ptr_dest_len) {
    len = (len - *ptr_dest_len < realloc_chunk) ? *ptr_dest_len + realloc_chunk : len;
    if ((new_dest = (char*)realloc(destination, len)) == NULL) {
      return NULL;
    }
    else {
      destination = new_dest;
    }
    /* adjust the length variable */
    *ptr_dest_len = len;
  }
  
  if (!offset) { /* this is the standard strcat behaviour */
    strcat(destination, source);
  }
  else { /* this will append the string at position omega minus offset */
    strcpy(&destination[strlen(destination)-offset], source);
  }

  /* return the new pointer to the buffer */
  return destination;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  mstrcpy(): a modified version of strcpy which operates on destination
             strings obtained with malloc(); the length of the buffer
             is dynamically increased if necessary.

  char* mstrcpy returns a pointer to the destination string or NULL
        if the realloc failed. As the buffer holding the destination
        string may have been reallocated, it is mandatory to use
        *ONLY* this returned pointer after the function call and
        *NEVER* the old pointer to destination

  char* destination the buffer obtained with malloc() to which the 
        source string will be copied

  char* source the string that will be copied to destination

  size_t* ptr_dest_len points to a variable that contains the current size
       of the buffer that destination points to. Will be modified if
       a realloc() is necessary to increase the buffer size

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* mstrcpy(char* destination, char* source, size_t* ptr_dest_len) {
  char *new_dest;
  size_t len;

  /* take the short way out if destination doesn't exist */
  if (destination == NULL) {
    return NULL;
  }

  /* we need the length of the string plus one for the \0 */
  len = strlen(source)+1;

  /* reallocate the buffer if it is too small */
  if (*ptr_dest_len < len) {
    len = (len - *ptr_dest_len < realloc_chunk) ? *ptr_dest_len + realloc_chunk : len;
    if ((new_dest = (char*)realloc(destination, len)) == NULL) {
      return NULL;
    }
    else {
      destination = new_dest;
    }
    /* adjust the length variable */
    *ptr_dest_len = len;
  }

  /* now copy the string*/
  strcpy(destination, source);

  /* return the new pointer to the buffer */
  return destination;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  mstrncpy(): a modified version of strncpy which operates on destination
             strings obtained with malloc(); the length of the buffer
             is dynamically increased if necessary.

  char* mstrncpy returns a pointer to the destination string or NULL
        if the realloc failed. As the buffer holding the destination
        string may have been reallocated, it is mandatory to use
        *ONLY* this returned pointer after the function call and
        *NEVER* the old pointer to destination

  char* destination the buffer obtained with malloc() to which the 
        source string will be copied

  char* source the string that will be copied to destination

  size_t n the number of characters to copy

  size_t* ptr_dest_len points to a variable that contains the current size
       of the buffer that destination points to. Will be modified if
       a realloc() is necessary to increase the buffer size

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* mstrncpy(char* destination, char* source, size_t n, size_t* ptr_dest_len) {
  char *new_dest;
  size_t len;

  /* take the short way out if destination doesn't exist */
  if (destination == NULL || source == NULL) {
    return NULL;
  }

  /* we need the length of the string plus one for the \0 */
  len = n+1;

  /* reallocate the buffer if it is too small */
  if (*ptr_dest_len < len) {
    len = (len - *ptr_dest_len < realloc_chunk) ? *ptr_dest_len + realloc_chunk : len;
    if ((new_dest = (char*)realloc(destination, len)) == NULL) {
      return NULL;
    }
    else {
      destination = new_dest;
    }
    /* adjust the length variable */
    *ptr_dest_len = len;
  }

  /* now copy the string*/
  strncpy(destination, source, n);

  /* return the new pointer to the buffer */
  return destination;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  mstrdup() creates a copy of a string in a malloc()'ed buffer

  char* mstrdup returns a pointer to the copy of the string. Returns
                NULL in the case of a failure (out of memory)

  char* buffer  pointer to the string to be copied

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* mstrdup(char *buffer) {
  char *new_buffer;

  if ((new_buffer = malloc(strlen(buffer)+1)) == NULL) {
    return NULL;
  }
  else {
    strcpy(new_buffer, buffer);
    return new_buffer;
  }
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  free_all() frees memory, taking the adresses out of a
             NULL-terminated array

  void free_all

  void*** ptr_mem pointer to an array of pointers which point to
             pointer variables which hold valid addresses of memory 
             allocated with malloc/calloc/realloc or NULL. This
             somewhat complicated arrangement allows to reallocate
             the memory without changing the values in the array.
             If memory is freed before free_all() is called, the
             value of the pointer to the freed memory must be set
             to NULL, which will not harm free().

 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void free_all(void*** ptr_mem) {
  int i = 0;

  while (ptr_mem[i] != NULL) {
/*      printf("free %d: %d\n", i, *(ptr_mem[i])); */
    free(*(ptr_mem[i++]));
  }
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  count_the_flowers() counts the number of occurrences of letter in
                      buffer. letter may be a multibyte character

  static size_t count_the_flowers returns the number of occurrences of letter
                      in buffer. It will return zero if letter is not
                      in buffer.

  const char* buffer pointer to the string to scan

  const char* letter ptr to the letter to locate

  size_t len the number of bytes that represent letter

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
size_t count_the_flowers(const char *buffer, const char *letter, size_t len) {
  size_t numletters = 0;
  size_t i;
  size_t buflen;

  buflen = strlen(buffer);

  if (buflen >= len) {
    buflen = buflen-len+1;

    for (i = 0; i < buflen; i++) {
      if (!memcmp(buffer+i, letter, len)) {
	numletters++;
      }
    }
    return numletters;
  }
  else {
    /* string too short to hold the flower */
    return 0;
  }
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  replace_char_string() replaces the letter that buffer points to
                  with an insert of arbitrary size. The calling function
                  is responsible to allocate enough space for buffer
                  so the insertion can be safely executed. The letter may
                  be a multibyte character.

  void replace_char_string has no return value

  char* buffer pointer to the null-terminated string that will be extended

  char* insert pointer to the string that will be inserted

  size_t len length of the letter to replace in bytes

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void replace_char_string(char *buffer, char *insert, size_t len) {
  size_t insert_len;

  insert_len = strlen(insert);

  memmove(buffer+insert_len, buffer+len, strlen(buffer+len)+1);
  memcpy(buffer, insert, insert_len);
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  remove_substring() removes the substring that buffer points to from
                  a string. If the requested substring length is longer
		  than the remainder of the string, the string will
		  be terminated where buffer points to

  void remove_substring has no return value

  char* buffer pointer to the substring to be removed

  size_t sublen number of characters to remove

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void remove_substring(char *buffer, size_t sublen) {
  if (strlen(buffer) >= sublen) {
    /* move one extra char to include the trailing \0 */
    memmove(buffer, buffer+sublen, strlen(buffer+sublen)+1);
    
  }
  else {
    *buffer = '\0';
  }
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  canonicalize_path(): converts a path to a canonical absolute path,
                       resolving all symbolic links and eliminating
		       path components like "/./" or "/../" or "~"

  char* canonicalize_path returns the canonical path in a malloc()'ed
                       buffer. The calling function is responsible for
		       freeing this buffer after use. Returns NULL if
		       an error occurs.

  char* the_path ptr to a string with the path to convert

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char *canonicalize_path(char *the_path) {
  /* this fn was stolen and modified from cpbk */
  char *temp;
  char *myhome;
  char *homepath;
  char *resolved_path;
  int numtilde;

  if ((temp = malloc(PATH_MAX)) == NULL) {
    return NULL;
  }

  if ((homepath = malloc(PATH_MAX)) == NULL) {
    free(temp);
    return NULL;
  }

  /* refuse if path is too long */
  if (strlen(the_path) > PATH_MAX) {
    free(temp);
    free(homepath);
    return NULL;
  }

  /* check whether the string abbreviates the home directory. If so,
     expand with the value of HOME. This way we can call this function
     with paths that did not go through shell expansion */
  if ((numtilde = count_the_flowers(the_path, "~", 1)) > 1) {
    free(homepath);
    free(temp);
    return NULL;
  }
  else if (numtilde) {
    myhome = getenv("HOME");
    strcpy(homepath, the_path);
    replace_char_string(strchr(homepath, (int)'~'), myhome, 1);
  }
  else {
    strcpy(homepath, the_path);
  }

  resolved_path = realpath(homepath, temp);

  if (resolved_path == NULL) { /* some or all parts of the path are invalid */
    if (errno == ENOENT) {
      /* file does not exist yet which may be ok if a file is to be created */
      strcpy(temp, homepath);
    }
    else {
      free(homepath);
      free(temp);
      return NULL;
    }
  }

  free(homepath);
  
  return temp;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  strip_quote(): strips leading and trailing quotes

  char* strip_quote returns a ptr to the string without quotes or NULL
        if the_string is NULL

  char* the_string ptr to a string to strip

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char *strip_quote(char *the_string) {
  char* ptr_c;
  char* stripped_string;
  
  if (!the_string) {
    return NULL;
  }

  ptr_c = the_string;

  while (*ptr_c != '\0' && (*ptr_c == '\''
			    || *ptr_c == '\"'
			    || *ptr_c == '`')) {
    ptr_c++;
  }

  stripped_string = ptr_c;

  ptr_c = &stripped_string[strlen(stripped_string)-1];

  while (ptr_c >= the_string && (*ptr_c == '\''
				 || *ptr_c == '\"'
				 || *ptr_c == '`')) {
    *ptr_c = '\0';
    ptr_c--;
  }


  return stripped_string;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  truncate_quoted_string(): truncate a quoted string to a defined length
                            the input string must be quoted with either
			    single or double quotes, with no leading or
			    trailing whitespace..

  char* strip_quote returns a ptr to the string without quotes or NULL
        if the_string is NULL

  char* the_string ptr to a string to strip

  size_t len the maximum length of the string, sans the trailing '\0'

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char *truncate_quoted_string(char *the_string, size_t len) {
  char quote_char;

  if (!the_string
      || strlen(the_string) < len
      || len < 3) {
    /* nothing to do */
    return the_string;
  }

  quote_char = *the_string;

  if (quote_char != '\'' && quote_char != '\"') {
    /* string does not appear to be quoted properly */
    return NULL;
  }

  /* make sure we don't accidentally escape the trailing quote */
  if (the_string[len-2] == '\\'
      || the_string[len-2] == quote_char) {
    the_string[len-1] = ' ';
  }

  the_string[len-1] = quote_char;
  the_string[len] = '\0';

  return the_string;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  escape_chars(): backslash-escapes characters in a string

  size_t escape_chars returns the length of the escaped string

  char *dest pointer to a buffer that will receive the escaped string
             must hold at least twice the size of orig

  char *orig pointer to the buffer with the string to be escaped

  size_t orig_size length of original string

  const char *toescape array of characters that must be escaped

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
size_t escape_chars(char *dest, const char *orig, size_t orig_size, const char *toescape) {
  char *curdest = dest;
  const char *curorig = orig;
  const char *curescaped;
  size_t len = 0;
  
  while (curorig && curorig < orig+orig_size) {
    curescaped = toescape;
    while (curescaped && *curescaped) {
      if (*curorig == *curescaped) {
	*curdest = '\\';
	curdest++;
	len++;
	break;
      }
      curescaped++;
    }
    /* Copy char to destination */
    *curdest = *curorig;
		
    curorig++;
    curdest++;
    len++;
  }

  /* append a NULL byte. This is required if orig was a
     zero-terminated string. It does not hurt if orig was a
     binary string as the calling function is not supposed to
     read past len bytes */
  *curdest = '\0';
  return len;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  unescape_chars(): unescapes a backslash-escaped string

  size_t unescape_chars returns the length of the unescaped string

  char *dest pointer to a buffer that will receive the unescaped string
             must hold at least the size of orig

  char *orig pointer to the buffer with the string to be unescaped

  size_t orig_size length of original string

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
size_t unescape_chars(char *dest, const char *orig, size_t orig_size) {
  char *curdest = dest;
  const char *curorig = orig;
  size_t len = 0;
  
  while (curorig && curorig < orig+orig_size) {
    if (*curorig == '\\') {
      curorig++;
    }
    /* Copy char to destination */
    *curdest = *curorig;
		
    curorig++;
    curdest++;
    len++;
  }

  /* append a NULL byte. This is required if orig was a
     zero-terminated string. It does not hurt if orig was a
     binary string as the calling function is not supposed to
     read past len bytes */
  *curdest = '\0';
  return len;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  escape_latex_chars(): backslash-escapes characters in a string
             this function is a wrapper for escape_chars() that
	     escapes all LaTeX command characters

  size_t escape_chars returns the length of the escaped string

  char *dest pointer to a buffer that will receive the escaped string
             must hold at least twice the size of orig

  char *orig pointer to the buffer with the string to be escaped

  size_t orig_size length of original string

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
size_t escape_latex_chars(char *dest, const char *orig, size_t orig_size) {
  return escape_chars(dest, orig, orig_size, "#$%&~_^\\{}");
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  escape_latex_chars_copy(): backslash-escapes characters in a string
             this function is a wrapper for escape_latex_chars()
	     it returns an allocated escaped string

  char* escape_chars returns the escaped string

  char *orig pointer to the buffer with the string to be escaped

  size_t orig_size length of original string

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* escape_latex_chars_copy(const char *orig, size_t orig_size) {
  char* dest;

  if ((dest = malloc(2*orig_size)) == NULL) {
    return NULL;
  }
  escape_latex_chars(dest, orig, orig_size);
  return dest;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  increment_suffix(): calling this function repeatedly generates a
                      sequence of suffices. The sequence
                      starts with a,b...z, then aa,ba..za...zz until
		      the length of the string reaches max_depth where
		      the fn will return an error.
                      this function is recursive

  int increment_suffix returns 0 if ok or 1 if max_depth is 
                  exceeded

  char* suffix ptr to buffer to receive suffix. Must hold at least
               max_depth characters plus a terminating \0. In most
	       cases you want the buffer to be an empty string when
	       you first call this function to start with "a" or "A"

  int max_depth maximum depth of recursive nesting, determines
               maximum length of resulting suffix

  int upper if 1, the suffix will use uppercase characters, if 0 it'll
               be lowercase

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int increment_suffix(char* suffix, int max_depth, int upper) {
  char start[2] = "a";
  char stopchar = 'z';

  if (upper) {
    *start = 'A';
    stopchar = 'Z';
  }

  if (!*suffix) { /* still empty */
    strcpy(suffix, start);
  }
  else {
    (*suffix)++;
    if (*suffix > stopchar) {
      if (max_depth > 1) {
	*suffix = *start;
	/* recursion increments the next character to the right */
	if (increment_suffix(suffix+1, max_depth-1, upper)) {
	  return 1;
	}
      }
      else {
	return 1;
      }
    }
  }
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  parse_versioninfo(): parses a version info string (e.g. 3.23.11) and
                       extracts the major, minor, and minuscule numbers
		       the results are filled into a structure as numbers

  int parse_versioninfo returns 0 if ok or 1 if error

  const char* version ptr to buffer containing the version info string.
                       the string may have up to two periods to separate
		       the major, minor, and minuscule numbers.

  struct VERSIONINFO* ver ptr to structure that receives the numeric
                       version info

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int parse_versioninfo(const char* version, struct VERSIONINFO* ver) {
  char* start;
  char* dot;
  char* my_version;

  /* initialize structure */
  ver->major = 0;
  ver->minor = 0;
  ver->minuscule = 0;

  if (!version || !*version) {
    /* don't treat this as error. The version structure will contain
     all zeros */
    return 0;
  }

  /* get a copy of version to mangle */
  if ((my_version = strdup(version)) == NULL) {
    return 1;
  }

  start = my_version;
  if ((dot = strchr(start, (int)'.')) == NULL) {
    ver->major = atoi(start);
    free(my_version);
    return 0;
  }

  *dot = '\0';
  ver->major = atoi(start);

  if (!(dot+1)) {
    free(my_version);
    return 0;
  }

  start = dot+1;

  if ((dot = strchr(start, (int)'.')) == NULL) {
    ver->minor = atoi(start);
    free(my_version);
    return 0;
  }

  *dot = '\0';
  ver->minor = atoi(start);

  if (!(dot+1)) {
    free(my_version);
    return 0;
  }

  start = dot+1;

  ver->minuscule = atoi(start);

  free(my_version);
  return 0;
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  min_token_length(): calculates the size of the smallest token in the
                      string. The string is tokenized using strtok()
		      according to the passed separators

  size_t min_token_length returns the size of the smallest token, or 0 if
                      an error occurred

  const char* string ptr to the buffer to check

  const char* sep ptr to the string containing the separator characters

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
size_t min_token_length(const char* string, const char* sep) {
  char* my_string;
  char* start;
  size_t min_length = SIZE_T_MAX;
  size_t curr_length;

  if (!string
      || !sep
      || (my_string = strdup(string)) == NULL) {
    return 0;
  }

  start = my_string;

  for (start = strtok(my_string, sep); start; start = strtok(NULL, sep)) {
    if ((curr_length = strlen(start)) < min_length) {
      min_length = curr_length;
    }
  }

  free(my_string);

  return min_length;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  min_token_length(): calculates the size of the smallest token in the
                      string. The string is tokenized using strtok()
		      according to the passed separators

  size_t min_token_length returns the size of the smallest token, or 0 if
                      an error occurred

  const char* string ptr to the buffer to check

  const char* sep ptr to the string containing the separator characters

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int replace_regexp_chars(char* string, char* regexp_chars, char replacement) {
  char* curr;
  char* regexp_char;
  int num_replaced = 0;

  curr = string;

  while (*curr) {
    regexp_char = regexp_chars;

    while (*regexp_char) {
      if (*curr == *regexp_char) {
	*curr = replacement;
	num_replaced++;
      }
      regexp_char++;
    }
    curr++;
  }

  return num_replaced;
}
