cSvn-UI for SVN Repositories

cGit-UI – is a web interface for Subversion (SVN) Repositories. cSvn CGI script is writen in C and therefore it's fast enough

6 Commits   0 Branches   2 Tags

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <stdint.h>
#include <dirent.h>
#include <sys/stat.h> /* chmod(2)    */
#include <sys/file.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <limits.h>
#include <string.h>   /* strdup(3)   */
#include <libgen.h>   /* basename(3) */
#include <ctype.h>    /* tolower(3)  */
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <pwd.h>
#include <grp.h>
#include <stdarg.h>
#include <unistd.h>

#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>

#include <nls.h>

#include <defs.h>
#include <wrapper.h>
#include <strbuf.h>


struct symbol *strbuf_envtab = (struct symbol *)0;

struct symbol *envtab_install( struct symbol **sym, const char *n, const char *v, envtab_error fatal )
{
  struct symbol *ep = NULL;
  size_t len;

  if( !sym || !n || !v || !fatal )
    return (struct symbol *)NULL;

  ep = (struct symbol *)malloc( sizeof(struct symbol) );
  if( !ep )
  {
    fatal( "cannot allocate memory for symbol '%s'", n );
  }
  bzero( (void *)ep, sizeof(struct symbol) );

  len = strlen( n ) + 1;
  ep->name = (char *)malloc( len );
  if( !ep->name )
  {
    fatal( "cannot allocate memory for symbol name '%s'", n );
  }
  bzero( (void *)ep->name, len );
  strncpy( ep->name, n, len );

  len = strlen( v ) + 1;
  ep->value = (char *)malloc( len );
  if( !ep->value )
  {
    fatal( "cannot allocate memory for symbol value '%s'", v );
  }
  bzero( (void *)ep->value, len );
  strncpy( ep->value, v, len );

  ep->next = *sym;
  *sym = ep;

  return ep;
}

struct symbol *envtab_lookup( struct symbol **sym, const char *n )
{
  struct symbol *ep = NULL;

  if( !sym || !n )
    return (struct symbol *)NULL;

  for( ep = *sym; ep != (struct symbol *)0; ep = ep->next )
      if( strcmp( ep->name, n ) == 0 ) return( ep );

  return (struct symbol *)NULL;
}

void envtab_release( struct symbol **sym )
{
  struct symbol *tab = *sym;

  while( tab )
  {
    struct symbol *ep = tab;
    tab = ep->next;
    if( ep->name )  free( ep->name );
    if( ep->value ) free( ep->value );
    free( ep );
  }
  *sym = (struct symbol *)NULL;
}


#define STRBUF_ERRMSG_SIZE 4096

void strbuf_fatal( const char *fmt, ... )
{
  va_list arg_ptr;
  char  buf[STRBUF_ERRMSG_SIZE];
  char  msg[STRBUF_ERRMSG_SIZE];
  char *format = "%s: %s\n";

  va_start( arg_ptr, fmt );

  vsnprintf( msg, STRBUF_ERRMSG_SIZE, (const void *)fmt, arg_ptr );

  va_end( arg_ptr ); /* Reset variable arguments. */

  snprintf( buf, STRBUF_ERRMSG_SIZE, format, "strbuf", msg );

  (void)write( STDERR_FILENO, buf, strlen( buf ) );

  exit( 1 );
}

char strbuf_slopbuf[1];

#define alloc_nz(x) (((x)+16)*3/2)

static void alloc_grow( struct strbuf *sb, size_t size )
{
  if( size > sb->alloc )
  {
    if( alloc_nz(sb->alloc) < (size) )
      sb->alloc = size;
    else
      sb->alloc = alloc_nz(sb->alloc);

    sb->buf = xrealloc( (void *)sb->buf, sb->alloc );
    memset( (void *)(sb->buf + sb->len), 0, (size_t)(sb->alloc - sb->len) );
  }
}

void strbuf_grow( struct strbuf *sb, size_t extra )
{
  int new_buf = !sb->alloc;

  if( new_buf )
    sb->buf = NULL;

  alloc_grow( sb, sb->len + extra + 1 );

  if( new_buf )
    sb->buf[0] = '\0';
}

/*
   Функцию strbuf_init() надо вызывать в том случае, когда необходимо
   задать внешнюю функцию обработки фатальной ошибки. Функция обработки
   фатальной ошибки, в отличие от стандартной, может выводить как JSON
   ответы, так и файлы 404.html, 50x.html, используя собственный буфер.
 */
void strbuf_init( struct strbuf *sb, strbuf_error fatal, size_t hint )
{
  sb->alloc = sb->len = 0;
  sb->fatal = strbuf_fatal;
  sb->buf   = strbuf_slopbuf;

  if( fatal )
    sb->fatal = fatal;

  if( hint )
    strbuf_grow( sb, hint );
}

void strbuf_release( struct strbuf *sb )
{
  if( sb->alloc )
  {
    free( sb->buf );
    strbuf_init( sb, (strbuf_error)0, 0 );
  }
}

char *strbuf_detach( struct strbuf *sb, size_t *sz )
{
  char *ret;
  strbuf_grow( sb, 0 );
  ret = sb->buf;
  if( sz )
    *sz = sb->len;
  strbuf_init( sb, sb->fatal, 0 );
  return ret;
}

void strbuf_attach( struct strbuf *sb, void *buf, size_t len, size_t alloc )
{
  strbuf_release( sb );
  sb->buf   = buf;
  sb->len   = len;
  sb->alloc = alloc;
  strbuf_grow( sb, 0 );
  sb->buf[sb->len] = '\0';
}

void strbuf_ltrim( struct strbuf *sb )
{
  char *b = sb->buf;
  while( sb->len > 0 && isspace(*b) )
  {
    b++;
    sb->len--;
  }
  memmove( sb->buf, b, sb->len );
  sb->buf[sb->len] = '\0';
}

void strbuf_rtrim( struct strbuf *sb )
{
  while( sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1]) )
    sb->len--;
  sb->buf[sb->len] = '\0';
}

void strbuf_trim( struct strbuf *sb )
{
  strbuf_rtrim( sb );
  strbuf_ltrim( sb );
}


void strbuf_trim_trailing_dir_sep( struct strbuf *sb )
{
  while( sb->len > 0 && is_dir_sep((unsigned char)sb->buf[sb->len - 1]) )
    sb->len--;
  sb->buf[sb->len] = '\0';
}

void strbuf_trim_trailing_newline( struct strbuf *sb )
{
  if( sb->len > 0 && sb->buf[sb->len - 1] == '\n' )
  {
    if( --sb->len > 0 && sb->buf[sb->len - 1] == '\r' )
      --sb->len;
    sb->buf[sb->len] = '\0';
  }
}

int strbuf_cmp( const struct strbuf *a, const struct strbuf *b )
{
  size_t len = a->len < b->len ? a->len: b->len;
  int    cmp = memcmp( a->buf, b->buf, len );
  if( cmp )
    return cmp;
  return a->len < b->len ? -1: a->len != b->len;
}


/* Adding data to the buffer */

void strbuf_add( struct strbuf *sb, const void *data, size_t len )
{
  strbuf_grow( sb, len );
  memcpy( sb->buf + sb->len, data, len );
  strbuf_setlen( sb, sb->len + len );
}

void strbuf_addbuf( struct strbuf *sb, const struct strbuf *sb2 )
{
  strbuf_grow( sb, sb2->len );
  memcpy( sb->buf + sb->len, sb2->buf, sb2->len );
  strbuf_setlen( sb, sb->len + sb2->len );
}

void strbuf_addbuf_percentquote( struct strbuf *dst, const struct strbuf *src )
{
  size_t i, len = src->len;

  for (i = 0; i < len; i++) {
    if( src->buf[i] == '%' )
      strbuf_addch( dst, '%' );
    strbuf_addch( dst, src->buf[i] );
  }
}

void strbuf_addchars( struct strbuf *sb, int c, size_t n )
{
  strbuf_grow( sb, n );
  memset( sb->buf + sb->len, c, n );
  strbuf_setlen( sb, sb->len + n );
}

void strbuf_vaddf( struct strbuf *sb, const char *fmt, va_list ap )
{
  int len;
  va_list cp;

  if( !strbuf_avail( sb ) )
    strbuf_grow( sb, 64 );
  va_copy( cp, ap );
  len = vsnprintf( sb->buf + sb->len, sb->alloc - sb->len, fmt, cp );
  va_end( cp );
  if( len < 0 )
    sb->fatal( "your vsnprintf is broken (returned %d)", len );
  if( len > strbuf_avail( sb ) )
  {
    strbuf_grow( sb, len );
    len = vsnprintf( sb->buf + sb->len, sb->alloc - sb->len, fmt, ap );
    if( len > strbuf_avail( sb ) )
      sb->fatal( "your vsnprintf is broken (insatiable)" );
  }
  strbuf_setlen( sb, sb->len + len );
}

void strbuf_addf( struct strbuf *sb, const char *fmt, ... )
{
  va_list ap;
  va_start( ap, fmt );
  strbuf_vaddf( sb, fmt, ap );
  va_end( ap );
}


size_t strbuf_fread( struct strbuf *sb, FILE *fp )
{
  size_t ret, nb = 64, read = 0;
  size_t oldalloc = sb->alloc;

  if( !sb || !fp ) return read;

  do
  {
    strbuf_grow( sb, nb );
    ret = fread( sb->buf + sb->len, 1, nb, fp );
    if( ret > 0 )
    {
      strbuf_setlen( sb, sb->len + ret );
      read += ret;
    }
    else if( oldalloc == 0 )
    {
      strbuf_release( sb );
      return ret;
    }

  } while( ret == nb );

  return read;
}


#define NAMELEN_MAX  128

size_t strbuf_env_fread( struct strbuf *sb, FILE *fp )
{
  size_t  read = 0;
  char   *ln, line[STRBUF_MAXLINE], retline[STRBUF_MAXLINE];

  if( !sb || !fp ) return read;

  bzero( (void *)line, STRBUF_MAXLINE );
  bzero( (void *)retline, STRBUF_MAXLINE );

  while( (ln = fgets( line, STRBUF_MAXLINE, fp )) )
  {
    char *start = NULL, *stop = NULL;
    char *sp = ln;

    if( (start = strstr( sp, "${" )) && (stop = strstr( sp, "}" )) && ((stop - start) > 1) )
    {
      struct symbol *sym = NULL;
      char  *lp = retline;

      do
      {
        /* may be multiple variables on a single line: */

        *start = '\0'; *stop++ = '\0';
        if( (sym = envtab_lookup( &strbuf_envtab, start+2 )) && sym->value && sym->value[0] )
        {
          strncpy( lp, (const char *)sp, (size_t)(start - sp + 1) );
          lp += (start - sp);
          strcpy( lp, (const char *)sym->value );
          lp += strlen( sym->value );
          strcpy( lp, (const char *)stop );
          sp = stop;
        }
        else
        {
          strncpy( lp, (const char *)sp, (size_t)(start - sp + 1) );
          lp += (start - sp);
          strcpy( lp, (const char *)stop );
          sp = stop;
        }

      } while( (start = strstr( sp, "${" )) && (stop = strstr( sp, "}" )) && ((stop - start) > 1) );

      strbuf_addstr( sb, retline );
      read += strlen( retline );;
    }
    else
    {
      strbuf_addstr( sb, line );
      read += strlen( line );;
    }

  } /* End of while( ln ) */

  return read;
}

ssize_t strbuf_read( struct strbuf *sb, int fd, size_t hint )
{
  size_t oldlen   = sb->len;
  size_t oldalloc = sb->alloc;

  strbuf_grow(sb, hint ? hint : 8192);
  for( ;; )
  {
    ssize_t want = sb->alloc - sb->len - 1;
    ssize_t got  = read_in_full( fd, sb->buf + sb->len, want );

    if( got < 0 )
    {
      if( oldalloc == 0 )
        strbuf_release( sb );
      else
        strbuf_setlen( sb, oldlen );
      return -1;
    }
    sb->len += got;
    if( got < want )
      break;
    strbuf_grow( sb, 8192 );
  }

  sb->buf[sb->len] = '\0';
  return sb->len - oldlen;
}


size_t strbuf_fwrite( struct strbuf *sb, FILE *fp )
{
  return sb->len ? fwrite( sb->buf, 1, sb->len, fp ) : 0;
}

ssize_t strbuf_write( struct strbuf *sb, int fd )
{
  return sb->len ? write( fd, (const void *)sb->buf, sb->len ) : 0;
}


/* XML quoted: */

void strbuf_addstr_xml_quoted( struct strbuf *sb, const char *s )
{
  while( *s )
  {
    size_t len = strcspn( s, "\"<>&" );
    strbuf_add( sb, s, len );
    s += len;

    switch( *s )
    {
      case '"':
        strbuf_addstr( sb, "&quot;" );
        break;
      case '<':
        strbuf_addstr( sb, "&lt;" );
        break;
      case '>':
        strbuf_addstr( sb, "&gt;" );
        break;
      case '&':
        strbuf_addstr( sb, "&amp;" );
        break;
      case 0:
        return;
    }
    s++;
  }
}

static int is_html_quoted( const char *str )
{
  int         rc = 0, error = 0;
  PCRE2_SIZE  offset = 0;
  const char  pattern[] = "^(&[#A-Za-z0-9]*;)";

  pcre2_match_data *match;

  pcre2_code *regexp = pcre2_compile( (PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, 0, &error, &offset, NULL );
  if( regexp == NULL )
  {
    return 0; /* PCRE compilation failed */
  }

  match = pcre2_match_data_create_from_pattern( regexp, NULL );

  rc = pcre2_match( regexp, (PCRE2_SPTR)str, (PCRE2_SIZE)strlen(str), 0, 0, match, NULL ); /* sizeof(match)/sizeof(match[0]) */
  if( rc < 0 )
  {
    /* not match */
    pcre2_match_data_free( match );
    pcre2_code_free( regexp );
    return 0;
  }
  else
  {
    /* match */
    pcre2_match_data_free( match );
    pcre2_code_free( regexp );
    return 1;
  }
}

void strbuf_addstr_html_quoted( struct strbuf *sb, const char *s )
{
  while( *s )
  {
    size_t len = strcspn( s, "\"<>&" );
    strbuf_add( sb, s, len );
    s += len;

    switch( *s )
    {
      case '"':
        strbuf_addstr( sb, "&quot;" );
        break;
      case '<':
        strbuf_addstr( sb, "&lt;" );
        break;
      case '>':
        strbuf_addstr( sb, "&gt;" );
        break;
      case '&':
        if( !is_html_quoted( s ) )
          strbuf_addstr( sb, "&amp;" );
        else
          strbuf_addch( sb, *s );
        break;
      case 0:
        return;
    }
    s++;
  }
}


/* urlencode: */

int is_rfc3986_reserved_or_unreserved( char ch )
{
  if( is_rfc3986_unreserved(ch) )
    return 1;
  switch( ch )
  {
    case '!': case '*': case '\'': case '(': case ')': case ';':
    case ':': case '@': case '&': case '=': case '+': case '$':
    case ',': case '/': case '?': case '#': case '[': case ']':
      return 1;
  }
  return 0;
}

int is_rfc3986_unreserved( char ch )
{
  return isalnum(ch) || ch == '-' || ch == '_' || ch == '.' || ch == '~';
}

static void
strbuf_add_urlencode( struct strbuf *sb, const char *s, size_t len,
                      char_predicate allow_unencoded_fn )
{
  strbuf_grow( sb, len );
  while( len-- )
  {
    char ch = *s++;
    if( allow_unencoded_fn(ch) )
      strbuf_addch( sb, ch );
    else
      strbuf_addf( sb, "%%%02x", (unsigned char)ch );
  }
}

void strbuf_addstr_urlencode( struct strbuf *sb, const char *s, char_predicate allow_unencoded_fn )
{
  strbuf_add_urlencode( sb, s, strlen(s), allow_unencoded_fn );
}


/* humanise: */

static void strbuf_humanise( struct strbuf *sb, off_t bytes, int humanise_rate )
{
  if( bytes > 1 << 30 )
  {
    strbuf_addf( sb,
      humanise_rate == 0 ?
        /* TRANSLATORS: IEC 80000-13:2008 gibibyte */
        _("%u.%2.2u GiB") :
        /* TRANSLATORS: IEC 80000-13:2008 gibibyte/second */
        _("%u.%2.2u GiB/s"),
        (unsigned)(bytes >> 30),
        (unsigned)(bytes & ((1 << 30) - 1)) / 10737419 );
  }
  else if( bytes > 1 << 20 )
  {
    unsigned x = bytes + 5243;  /* for rounding */
    strbuf_addf( sb,
      humanise_rate == 0 ?
        /* TRANSLATORS: IEC 80000-13:2008 mebibyte */
        _("%u.%2.2u MiB") :
        /* TRANSLATORS: IEC 80000-13:2008 mebibyte/second */
        _("%u.%2.2u MiB/s"),
        x >> 20, ((x & ((1 << 20) - 1)) * 100) >> 20 );
  }
  else if( bytes > 1 << 10 )
  {
    unsigned x = bytes + 5;  /* for rounding */
    strbuf_addf( sb,
      humanise_rate == 0 ?
        /* TRANSLATORS: IEC 80000-13:2008 kibibyte */
        _("%u.%2.2u KiB") :
        /* TRANSLATORS: IEC 80000-13:2008 kibibyte/second */
        _("%u.%2.2u KiB/s"),
        x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10 );
  }
  else
  {
    strbuf_addf( sb,
      humanise_rate == 0 ?
        /* TRANSLATORS: IEC 80000-13:2008 byte */
        Q_("%u byte", "%u bytes", (unsigned)bytes) :
        /* TRANSLATORS: IEC 80000-13:2008 byte/second */
        Q_("%u byte/s", "%u bytes/s", (unsigned)bytes),
      (unsigned)bytes );
  }
}

void strbuf_humanise_bytes( struct strbuf *sb, off_t bytes )
{
  strbuf_humanise( sb, bytes, 0 );
}

void strbuf_humanise_rate( struct strbuf *sb, off_t bytes )
{
  strbuf_humanise( sb, bytes, 1 );
}


int is_directory( const char *path )
{
  struct stat st;
  return ( !stat( path, &st ) && S_ISDIR(st.st_mode) );
}

void strbuf_selfdir( struct strbuf *sb )
{
  char    path[PATH_MAX];
  ssize_t len;

  bzero( (void *)path, PATH_MAX );

  len = readlink( "/proc/self/exe", &path[0], (size_t)PATH_MAX );
  if( len > 0 && len < PATH_MAX )
  {
    strbuf_addstr( sb, (const char *)dirname( (char *)&path[0] ) );
  }
  else
    sb->fatal( "cannot get selfdir" );
}

void strbuf_relpath( struct strbuf *sb, const char *path )
{
  struct strbuf self = STRBUF_INIT;
  char   p[PATH_MAX];

  bzero( (void *)p, PATH_MAX );

  if( realpath( path, (char *)&p[0] ) == NULL )
  {
    sb->fatal( "cannot get relative path of '%s'", path );
  }

  strbuf_init( &self, sb->fatal, 0 );
  strbuf_selfdir( &self );

  strbuf_addstr( sb, (const char *)&p[self.len] );

  strbuf_release( &self );
}

void strbuf_abspath( struct strbuf *sb, const char *path )
{
  char p[PATH_MAX];

  if( realpath( path, (char *)&p[0] ) == NULL )
  {
    sb->fatal( "cannot get absolute path of '%s'", path );
  }

  strbuf_addstr( sb, (char *)&p[0] );
}