/* lowlevelfunc.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2001-2019 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "lowlevelfunc.h"
#include <locale.h>
#include <algorithm>
#include "utf8.hh"

void *_allocsafe(size_t size)
{ /* gibt Speicher zurück oder beendet sofort das Programm */
  void *ptr;

  ptr=(void*)malloc(size);
  if(ptr==NULL) {
    fprintf( stderr, "Worker Error:No Memory!!!!Aborting\n" );
    exit(1);
  }
  return ptr;
}

void waittime(unsigned long msec)
{
#ifdef HAVE_NANOSLEEP
  struct timespec wt;
  wt.tv_nsec = ( msec % 1000 ) * 1000000;
  wt.tv_sec = msec / 1000;
  nanosleep( &wt, NULL );
#else

#ifdef HAVE_USLEEP
  usleep( msec * 1000 );
#else

  /* configure doesn't allow continue without nanosleep and usleep
   * but worker could run without a working waittime (but not very nice)
   * just throw a warning
   */
#warning empty wait-function

#endif /* HAVE_USLEEP */

#endif /* HAVE_NANOSLEEP */
}

char *dupstring(const char *str)
{
  char *tstr;
  tstr=(char*)_allocsafe(strlen(str)+1);
  strcpy(tstr,str);
  return tstr;
}

int MakeLong2NiceStr( loff_t size, std::string &buffer, bool do_nice )
{
  static std::string own_thousands_sep;
  struct lconv *current_locale;
  std::string numstr;
  loff_t tmpval = size;
  int digit, i;

  if ( own_thousands_sep.length() < 1 ) {
    // thousands separator isn't set so try to determine it
    current_locale = localeconv();
    /* check if the locale defines thousands_sep */
    if ( ( current_locale->thousands_sep != NULL ) &&
	 ( *( current_locale->thousands_sep ) != 0 ) ) {
      /* yes, it does so store the string */
      own_thousands_sep = current_locale->thousands_sep;
    } else {
      // default is the dot
      own_thousands_sep = ".";
    }

    // reverse string because we will build the number backwards
    std::reverse( own_thousands_sep.begin(),
                  own_thousands_sep.end() );
  }

  numstr = "";
  i = 0;
  do {
    digit = abs( (int)( tmpval % 10 ) );
    tmpval /= 10;

    if ( ( do_nice == true ) && ( i != 0 ) && ( i % 3 == 0 ) )
      numstr += own_thousands_sep;

    numstr += '0' + digit;
    i++;
  } while ( tmpval != 0 );
  if ( size < 0 ) numstr += '-';

  std::reverse( numstr.begin(), numstr.end() );
  buffer = numstr;

  return numstr.length();
}

char *catstring(const char *str1,const char *str2)
{
  char *tstr;
  tstr=(char*)_allocsafe(strlen(str1)+strlen(str2)+1);
  strcpy(tstr,str1);
  strcpy(tstr+strlen(str1),str2);
  return tstr;
}

char *shrinkstring( const char *str, int maxlen, AWidth &lencalc )
{
  int width, rwidth, ddd_width, real_width, rest_width, lwidth;
  int llen;
  char *newstr;
  const char *hitstr, *trystr;
  std::string str1;
  size_t charlen;
  int pos;

  width = lencalc.getWidth( str );

  if ( width <= maxlen ) return dupstring( str );

  ddd_width = lencalc.getWidth( "..." );

  charlen = UTF8::getLenOfCharacter( str );
  lwidth = lencalc.getWidth( str, charlen );

  if ( ( lwidth + ddd_width ) <= maxlen) {
    rest_width = maxlen - ddd_width;
    
    llen = lencalc.getStrlen4Width( str, rest_width / 2, &real_width );
    str1 = "";
    str1.append( str, llen );
    str1 += "...";
    
    rest_width -= real_width;

    // now find remaining character from right end
    hitstr = NULL;

    pos = strlen( str );
    UTF8::movePosToPrevChar( str, pos );
    trystr = str + pos;
    while ( trystr >= str ) {
      rwidth = lencalc.getWidth( trystr );
      if ( rwidth <= rest_width ) hitstr = trystr;
      else break;
      if ( UTF8::movePosToPrevChar( str, pos ) < 1 ) break;
      trystr = str + pos;
    }
    
    if ( hitstr != NULL ) {
      str1 += hitstr;
    }
    
    newstr = dupstring( str1.c_str() );
  } else {
    llen = lencalc.getStrlen4Width( str, maxlen, &real_width );
    newstr = dupstring( str );
    newstr[llen] = 0;
  }
  return newstr;
}

double diffgtod( struct timeval *tv1, struct timeval *tv0 )
{
  long s, us;

  if ( ( tv1 == NULL ) || ( tv0 == NULL ) ) return 0.0;
  
  s = tv1->tv_sec - tv0->tv_sec;
  us = tv1->tv_usec - tv0->tv_usec;
  
  return ((double)s) * 1000000.0 + (double)us;
}

long ldiffgtod( struct timeval *tv1, struct timeval *tv0 )
{
  long s, us;

  if ( ( tv1 == NULL ) || ( tv0 == NULL ) ) return 0;
  
  s = tv1->tv_sec - tv0->tv_sec;
  us = tv1->tv_usec - tv0->tv_usec;
  
  return s * 1000000 + us;
}

long ldiffgtod_m( const struct timeval *tv1, const struct timeval *tv0 )
{
  long s, us;

  if ( ( tv1 == NULL ) || ( tv0 == NULL ) ) return 0;
  
  s = tv1->tv_sec - tv0->tv_sec;
  us = tv1->tv_usec - tv0->tv_usec;
  
  return s * 1000 + us / 1000;
}

/* this function calculate the quote mode at the end of str
   0 = no quotes
   1 = single quote
   2 = double quote
   -1 = error
*/
int AGUIX_getQuoteMode( const char *str )
{
  int quotemode = 0; /* 0 means no quote
			1 means in single quotes
			2 means in double quotes */
  int i, len;

  if ( str == NULL ) return -1;

  len = strlen( str );

  for ( i = 0; i < len; i++ ) {
    switch ( quotemode ) {
    case 1:
      // even backslash doesn't matter in single-quotes so just check for closing quote
      if ( str[i] == '\'' ) {
	// you cannot do single quote inside double quote so we return to "no quote"
	quotemode = 0;
      }
      break;
    case 2:
      // this is more complicated
      // the backslash doesn't protect everything just special characters
      // anyway if the following char isn't special we don't care on it
      // and if it's a special we also don't care because it's backslashed
      // so it's safe to skip next char in any case
      if ( str[i] == '\\' ) {
	i++;
      } else if ( str[i] == '"' ) {
	// remember that we skiped this char if it was backslashed
	// so we really have to leave quote mode and for the same reason
	// as above it's not possibly to do double quote in single quote
	// we can go to "no quotes" mode
	quotemode = 0;
      }
      break;
    default:
      if ( str[i] == '\\' ) {
	// not in quotes => ignore next
	i++;
      } else if ( str[i] == '"' ) {
	// double quote begin
	quotemode = 2;
      } else if ( str[i] == '\'' ) {
	// single quote begin
	quotemode = 1;
      }
      break;
    }
  }
  return quotemode;
}

/* this function will cat 2 strings:
   str1 have to be a string which STARTS unquoted (can begin with quotes
   but it's interpreted as beginning quotes not ending for a another unknown
   string!
   str2 is an untrusted string and will be single quoted in any case
   depending on quote-status at the end of str1, str2 is correctly
   quoted in single-quotes so it can be used in shell-script to avoid problems
   for filenames like test`rm -rf /`
   
   str1 is not changed but between str1 and str2 and at the end
   I add the correct quotes
   str2 can be changed (single quotes become '\'' (to avoid any known and unknown shell actions) )

   return string will always end in same quotes like str1
*/
char *AGUIX_catTrustedAndUnTrusted( const char *str1, const char *str2 )
{
  int quotemode = 0; /* 0 means no quote
			1 means in single quotes
			2 means in double quotes */
  int i, o, lenstr2, sqcount;
  char *newstr2, *tstr1;

  if ( ( str1 == NULL ) || ( str2 == NULL ) ) return NULL;

  // first fix backslashed str1 because it a last backslash
  // will protect the quoting for str2
  tstr1 = AGUIX_fixBackslashed( str1 );
  if ( tstr1 == NULL ) return NULL;

  quotemode = AGUIX_getQuoteMode( tstr1 );
  if ( quotemode < 0 ) return NULL;

  // now we know in which quote mode str1 is at the end
  // se we can do the following:
  // in single quotes we have just to replace singlequotes with '\''
  // in double quotes we have to close double close and add single quoted filename
  // in no quotes we just have to add single quoted filename

  // since filename will always be inside single quotes first replace all single quotes
  // in filename with '\''
  lenstr2 = strlen( str2 );
  sqcount = 0;
  for ( i = 0; i < lenstr2; i++ ) {
    if ( str2[i] == '\'' ) sqcount++;
  }
  // for every single quote we need 3 additional chars
  newstr2 = (char*)_allocsafe( lenstr2 + sqcount * 3 +
                               1 /* null byte */ +
                               3 /* additional dummy bytes for optimized strlen */ );
  for ( i = 0, o = 0; i < lenstr2; i++ ) {
    if ( str2[i] == '\'' ) {
      newstr2[o++] = '\'';
      newstr2[o++] = '\\';
      newstr2[o++] = '\'';
      newstr2[o++] = '\'';
    } else {
      newstr2[o++] = str2[i];
    }
  }
  newstr2[o] = '\0';

  /* just to keep optimized strlen and co quiet */
  newstr2[o+1] = '\0';
  newstr2[o+2] = '\0';
  newstr2[o+3] = '\0';

  // newfilename is now okay
  // space needed for newstr1:
  // str1len
  // perhaps a closing double quote and a beginning single quote
  // strlen( newfilename )
  // perhaps a closing single quote and beginning double quote
  std::string newstr;
  switch ( quotemode ) {
  case 1:
    // single quote so no need for quote changing
    // remember newfilename is correctly quoted to live inside
    // single quote
    newstr = tstr1;
    newstr += newstr2;
    break;
  case 2:
    // double quotes so close double quotes, begin single quote
    // and after newfilename close single quote and start double quote
    newstr = tstr1;
    newstr += "\"'";
    newstr += newstr2;
    newstr += "'\"";
    break;
  default:
    // no quote so just start single quote and after newfilename single quote
    newstr = tstr1;
    newstr += "'";
    newstr += newstr2;
    newstr += "'";
    break;
  }
  _freesafe( newstr2 );
  _freesafe( tstr1 );
  return dupstring( newstr.c_str() );
}

/* this function replace all single quotes with '\'' so it can be
   used in single quotes */
char *AGUIX_prepareForSingleQuote( const char *str1 )
{
  int i, o, lenstr1, sqcount;
  char *newstr1;

  if ( str1 == NULL ) return NULL;

  // single quotes in filename with '\''
  lenstr1 = strlen( str1 );
  sqcount = 0;
  for ( i = 0; i < lenstr1; i++ ) {
    if ( str1[i] == '\'' ) sqcount++;
  }
  // for every single quote we need 3 additional chars
  newstr1 = (char*)_allocsafe( lenstr1 + sqcount * 3 + 1 );
  for ( i = 0, o = 0; i < lenstr1; i++ ) {
    if ( str1[i] == '\'' ) {
      newstr1[o++] = '\'';
      newstr1[o++] = '\\';
      newstr1[o++] = '\'';
      newstr1[o++] = '\'';
    } else {
      newstr1[o++] = str1[i];
    }
  }
  newstr1[o] = '\0';
  return newstr1;
}

/* this function will remove all quotes from the string
   rules:
   single quotes: allowed, no special character
   double quotes: allowed, backslash escapes next character
   single and double quotes can be mixed
*/
char *AGUIX_unquoteString( const char *str1 )
{
  char *newstr;
  int quotemode = 0; /* 0 means no quote
			1 means in single quotes
			2 means in double quotes */
  int i, o, len;

  if ( str1 == NULL ) return NULL;
  newstr = (char*)_allocsafe( strlen( str1 ) + 1 );

  len = strlen( str1 );

  for ( i = 0, o = 0; i < len; i++ ) {
    switch ( quotemode ) {
    case 1:
      // even backslash doesn't matter in single-quotes so just check for closing quote
      if ( str1[i] == '\'' ) {
	// you cannot do single quote inside double quote so we return to "no quote"
	quotemode = 0;
      } else {
	newstr[o++] = str1[i];
      }
      break;
    case 2:
      // this is more complicated
      // the backslash doesn't protect everything just special characters
      // anyway if the following char isn't special we don't care on it
      // and if it's a special we also don't care because it's backslashed
      // so it's safe to skip next char in any case
      if ( str1[i] == '\\' ) {
	// only backslash $, `, ", <backslash
	// (just like shell (at least bash))
	switch( str1[i + 1] ) {
	case '$':
	case '`':
	case '"':
	case '\\':
	  i++;
	default:
	  newstr[o++] = str1[i];
	  break;
	}
      } else if ( str1[i] == '"' ) {
	// remember that we skiped this char if it was backslashed
	// so we really have to leave quote mode and for the same reason
	// as above it's not possibly to do double quote in single quote
	// we can go to "no quotes" mode
	quotemode = 0;
      } else {
	newstr[o++] = str1[i];
      }
      break;
    default:
      if ( str1[i] == '\\' ) {
	// backslash in nonquoted env escapes everything
	i++;
	newstr[o++] = str1[i];
      } else if ( str1[i] == '"' ) {
	// double quote begin
	quotemode = 2;
      } else if ( str1[i] == '\'' ) {
	// single quote begin
	quotemode = 1;
      } else {
	newstr[o++] = str1[i];
      }
      break;
    }
  }
  newstr[o] = '\0';

  return newstr;
}

/*
 * this function will cat 2 strings
 * the first one can have single and double quotes
 * the second will be protected according to the quote-mode at the end
 * of str1
 *
 * what's done here is not enough for direct shell output because special
 * shell stuff like $... is not protected
 * use catTrustedAndUnTrusted instead
 *
 * this function is enough for secureCommand... from execlass
 *
 * following rules apply:
 * 1.str1 not quoted at the end: whitespaces, single,double quotes and backslash
 *   are protected with backslash
 * 2.str1 ends with single quote: single quotes are replaced with '\''
 * 3.str1 ends with double quote: double quotes and backslash are protected with backslash
 */
char *AGUIX_catQuotedAndUnQuoted( const char *str1, const char *str2 )
{
  int quotemode = 0; /* 0 means no quote
			1 means in single quotes
			2 means in double quotes */
  int i, o, lenstr1, lenstr2, newlen;
  char *newstr, *tstr1;

  if ( ( str1 == NULL ) || ( str2 == NULL ) ) return NULL;

  // first fix backslashed str1
  tstr1 = AGUIX_fixBackslashed( str1 );
  if ( tstr1 == NULL ) return NULL;

  lenstr1 = strlen( tstr1 );

  quotemode = AGUIX_getQuoteMode( tstr1 );
  if ( quotemode < 0 ) return NULL;

  /* to know the length of the new string
   * count all whitespaces, single and double quotes in it
   * and add the needed space to protect them
   */
  lenstr2 = strlen( str2 );
  newlen = 0;
  for ( i = 0; i < lenstr2; i++, newlen++ ) {
    if ( str2[i] == '\'' ) {
      newlen += 3; /* in quotemode 1 I have to protect it
		    * with '\'' so I need 3 additional chars */
    } else if ( str2[i] == '\"' ) {
      newlen++;
    } else if ( str2[i] == '\\' ) {
      newlen++;
    } else if ( str2[i] == '$' ) {
      newlen++;
    } else if ( str2[i] == '&' ) {
      newlen++;
    } else if ( str2[i] == '|' ) {
      newlen++;
    } else if ( str2[i] == ';' ) {
      newlen++;
    } else if ( str2[i] == '>' ) {
      newlen++;
    } else if ( str2[i] == '<' ) {
      newlen++;
    } else if ( isspace( str2[i] ) ) {
      newlen++;
    }
  }

  newstr = (char*)_allocsafe( lenstr1 + newlen + 1 );
  strcpy( newstr, tstr1 );
  o = lenstr1;

  for ( i = 0; i < lenstr2; i++ ) {
    switch ( quotemode ) {
    case 1:
      if ( str2[i] == '\'' ) {
	newstr[o++] = '\'';
	newstr[o++] = '\\';
	newstr[o++] = '\'';
	newstr[o++] = '\'';
      } else {
	newstr[o++] = str2[i];
      }
      break;
    case 2:
      if ( str2[i] == '"' ) {
	newstr[o++] = '\\';
	newstr[o++] = '"';
      } else if ( str2[i] == '\\' ) {
	newstr[o++] = '\\';
	newstr[o++] = '\\';
      } else if ( str2[i] == '$' ) {
	newstr[o++] = '\\';
	newstr[o++] = '$';
      } else {
	newstr[o++] = str2[i];
      }
      break;
    default:
      if ( str2[i] == '"' ) {
	newstr[o++] = '\\';
	newstr[o++] = '"';
      } else if ( str2[i] == '\'' ) {
	newstr[o++] = '\\';
	newstr[o++] = '\'';
      } else if ( str2[i] == '\\' ) {
	newstr[o++] = '\\';
	newstr[o++] = '\\';
      } else if ( str2[i] == '$' ) {
	newstr[o++] = '\\';
	newstr[o++] = '$';
      } else if ( str2[i] == '&' ) {
	newstr[o++] = '\\';
	newstr[o++] = '&';
      } else if ( str2[i] == '|' ) {
	newstr[o++] = '\\';
	newstr[o++] = '|';
      } else if ( str2[i] == ';' ) {
	newstr[o++] = '\\';
	newstr[o++] = ';';
      } else if ( str2[i] == '<' ) {
	newstr[o++] = '\\';
	newstr[o++] = '<';
      } else if ( str2[i] == '>' ) {
	newstr[o++] = '\\';
	newstr[o++] = '>';
      } else if ( isspace( str2[i] ) ) {
	newstr[o++] = '\\';
	newstr[o++] = str2[i];
      } else {
	newstr[o++] = str2[i];
      }
      break;
    }
  }
  newstr[o] = '\0';

  _freesafe( tstr1 );

  return newstr;
}

/*
 * this function will remove a backslash at the end
 * if this backslash
 * 1.exists
 * 2.is not inside single quotes (backslash doesn't matter there)
 * 3.the backslash is not backslashed (\\)
 */
char *AGUIX_fixBackslashed( const char *str1 )
{
  char *newstr;
  int lenstr1, bscount, i;
  int quotemode;

  if ( str1 == NULL ) return NULL;

  lenstr1 = strlen( str1 );
  if ( lenstr1 < 1 ) return dupstring( str1 );

  bscount = 0;
  for ( i = lenstr1 - 1; i >= 0; i-- ) {
    if ( str1[ i ] == '\\' ) {
      bscount++;
    } else {
      break;
    }
  }

  // even backslash count is okay because two
  // protects a backslash

  quotemode = AGUIX_getQuoteMode( str1 );
  if ( quotemode < 0 ) return NULL;

  newstr = dupstring( str1 );
  if ( quotemode != 1 ) {
    // backslash is no special character in single quotes
    if ( ( bscount % 2 ) == 1 ) {
      // clear last backslash
      newstr[ lenstr1 - 1 ] = '\0';
    }
  }
  return newstr;
}

std::string AGUIX_catTrustedAndUnTrusted2( const std::string &str1,
                                           const std::string &str2 )
{
    char *tstr = AGUIX_catTrustedAndUnTrusted( str1.c_str(), str2.c_str() );

    if ( ! tstr ) return "";
    std::string res = tstr;
    _freesafe( tstr );

    return res;
}

std::string AGUIX_catQuotedAndUnQuoted2( const std::string &str1,
                                         const std::string &str2 )
{
    char *tstr = AGUIX_catQuotedAndUnQuoted( str1.c_str(), str2.c_str() );

    if ( ! tstr ) return "";
    std::string res = tstr;
    _freesafe( tstr );

    return res;
}

std::string AGUIX_prepareForSingleQuote2( const std::string str )
{
    char *tstr = AGUIX_prepareForSingleQuote( str.c_str() );

    if ( ! tstr ) return "";
    std::string res = tstr;
    _freesafe( tstr );

    return res;
}
