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>
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#else
#include <stdint.h>
#endif
#include <stddef.h>   /* offsetof(3) */
#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>

#include <nls.h>

#include <defs.h>

#include <strbuf.h>
#include <date.h>


/* Valid rule actions */
enum rule_action
{
  ACCUM,    /* Accumulate a decimal value */
  MICRO,    /* Accumulate microseconds */
  TZIND,    /* Handle +, -, Z */
  NOOP,     /* Do nothing */
  SKIPFROM, /* If at end-of-value, accept the match.  Otherwise,
               if the next template character matches the current
               value character, continue processing as normal.
               Otherwise, attempt to complete matching starting
               immediately after the first subsequent occurrance of
               ']' in the template. */
  SKIP,     /* Ignore this template character */
  ACCEPT    /* Accept the value */
};

/* How to handle a particular character in a template */
struct rule
{
  char              key;    /* The template char that this rule matches */
  const char       *valid;  /* String of valid chars for this rule */
  enum rule_action  action; /* What action to take when the rule is matched */
  int               offset; /* Where to store the any results of the action,
                               expressed in terms of bytes relative to the
                               base of a match_state object. */
};

struct match_state
{
  struct tm       base;
  struct timeval  tv;
  int             gmtoff;
  int             gmtoff_hours;
  int             gmtoff_minutes;
};


#define DIGITS "0123456789"

/*
  A declarative specification of how each template character
  should be processed, using a rule for each valid symbol.
 */
static const struct rule rules[] =
{
  { 'Y', DIGITS,    ACCUM, offsetof(struct match_state, base.tm_year)   },
  { 'M', DIGITS,    ACCUM, offsetof(struct match_state, base.tm_mon)    },
  { 'D', DIGITS,    ACCUM, offsetof(struct match_state, base.tm_mday)   },
  { 'h', DIGITS,    ACCUM, offsetof(struct match_state, base.tm_hour)   },
  { 'm', DIGITS,    ACCUM, offsetof(struct match_state, base.tm_min)    },
  { 's', DIGITS,    ACCUM, offsetof(struct match_state, base.tm_sec)    },
  { 'u', DIGITS,    MICRO, offsetof(struct match_state, tv.tv_usec)     },
  { 'O', DIGITS,    ACCUM, offsetof(struct match_state, gmtoff_hours)   },
  { 'o', DIGITS,    ACCUM, offsetof(struct match_state, gmtoff_minutes) },
  { '+',   "-+",    TZIND, 0 },
  { 'Z',    "Z",    TZIND, 0 },
  { ':',    ":",     NOOP, 0 },
  { '-',    "-",     NOOP, 0 },
  { 'T',    "T",     NOOP, 0 },
  { ' ',    " ",     NOOP, 0 },
  { '.',   ".,",     NOOP, 0 },
  { '[',   NULL, SKIPFROM, 0 },
  { ']',   NULL,     SKIP, 0 },
  { '\0',  NULL,   ACCEPT, 0 },
};

/* Return the rule associated with TCHAR, or NULL if there is no such rule. */
static const struct rule *find_rule( char tchar )
{
  int i = sizeof(rules)/sizeof(rules[0]);
  while( i-- )
    if( rules[i].key == tchar )
      return &rules[i];
  return NULL;
}

/*
  Attempt to match the date-string in VALUE to the provided TEMPLATE,
  using the rules defined above.  Return TRUE on successful match,
  FALSE otherwise.  On successful match, fill in *TM with the
  matched values and set *LOCALTZ to GMT-offset if the local time zone
  should be used to interpret the match.
 */
static int template_match( struct tm *tm, int *localtz, const char *template, const char *value )
{
  int    multiplier = 100000;
  int    tzind = 0;
  struct match_state  ms;
  char  *base = (char *)&ms;

  memset( &ms, 0, sizeof(ms) );

  for( ;; )
  {
    const struct rule *match = find_rule(*template++);
    char vchar = *value++;
    int *place;

    if( !match || (match->valid && (!vchar || !strchr(match->valid, vchar))) )
      return FALSE;

    /* Compute the address of memory location affected by this
       rule by adding match->offset bytes to the address of ms.
       Because this is a byte-quantity, it is necessary to cast
       &ms to char *. */
    place = (int *)(base + match->offset);
    switch( match->action )
    {
      case ACCUM:
        *place = *place * 10 + vchar - '0';
        continue;
      case MICRO:
        *place += (vchar - '0') * multiplier;
        multiplier /= 10;
        continue;
      case TZIND:
        tzind = vchar;
        continue;
      case SKIP:
        value--;
        continue;
      case NOOP:
        continue;
      case SKIPFROM:
        if( !vchar )
          break;
        match = find_rule(*template);
        if (!strchr(match->valid, vchar))
          template = strchr(template, ']') + 1;
        value--;
        continue;
      case ACCEPT:
        if( vchar )
          return FALSE;
        break;
    }

    break;
  }

  /* Validate gmt offset here, since we can't reliably do it later. */
  if( ms.gmtoff_hours > 23 || ms.gmtoff_minutes > 59 )
    return FALSE;

  /*
    tzind will be '+' or '-' for an explicit time zone,
    'Z' to indicate UTC, or 0 to indicate local time.
   */
  switch( tzind )
  {
    case '+':
      ms.gmtoff =   ms.gmtoff_hours * 3600 + ms.gmtoff_minutes * 60;
      break;
    case '-':
      ms.gmtoff = -(ms.gmtoff_hours * 3600 + ms.gmtoff_minutes * 60);
      break;
  }

  *tm = ms.base;
  *localtz = ms.gmtoff;
  return TRUE;
}

static int valid_days_by_month[] =
{
  31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

/*
  Returns -1 on error,
  time_t as the number of seconds since Epoch, 1970-01-01 00:00:00 +0000 (UTC)
  on success.
 */
time_t parse_date( struct tm *tm, const char *text )
{
  time_t     n, ret = (time_t)-1;
  struct tm  pt, *now;
  int        localtz;

  n = time( NULL ); /* current UTC time */
  now = gmtime( &n );


  if( /* ISO-8601 extended, date only: */
      template_match( &pt, &localtz, "YYYY-M[M]-D[D]", text ) ||
      /* ISO-8601 extended, UTC: */
      template_match( &pt, &localtz, "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u][Z]", text ) ||
      /* ISO-8601 extended, with offset: */
      template_match( &pt, &localtz, "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u]+OO[:oo]", text ) ||
      /* ISO-8601 basic, date only */
      template_match( &pt, &localtz, "YYYYMMDD", text ) ||
      /* ISO-8601 basic, UTC: */
      template_match( &pt, &localtz, "YYYYMMDDThhmm[ss[.u[u[u[u[u[u][Z]", text ) ||
      /* ISO-8601 basic, with offset: */
      template_match( &pt, &localtz, "YYYYMMDDThhmm[ss[.u[u[u[u[u[u]+OO[oo]", text ) ||
      /* "svn log" format: */
      template_match( &pt, &localtz, "YYYY-M[M]-D[D] h[h]:mm[:ss[.u[u[u[u[u[u][ +OO[oo]", text ) ||
      /* GNU date's iso-8601: */
      template_match( &pt, &localtz, "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u]+OO[oo]", text ) )
  {
    pt.tm_year -= 1900;
    pt.tm_mon -= 1;
  }
  else if( template_match( &pt, &localtz, "h[h]:mm[:ss[.u[u[u[u[u[u]", text) ) /* Just a time */
  {
    pt.tm_year = now->tm_year;
    pt.tm_mon  = now->tm_mon;
    pt.tm_mday = now->tm_mday;
  }

  /* Range validation, allowing for leap seconds */
  if( pt.tm_mon  <  0 ||
      pt.tm_mon  > 11 ||
      pt.tm_mday > valid_days_by_month[pt.tm_mon] ||
      pt.tm_mday <  1 ||
      pt.tm_hour > 23 ||
      pt.tm_min  > 59 ||
      pt.tm_sec  > 60   )
    return ret;

  /*
    february/leap-year day checking. tm_year is bias-1900, so
    centuries that equal 100 (mod 400) are multiples of 400.
   */
  if( pt.tm_mon  ==  1 &&
      pt.tm_mday == 29 &&
     (pt.tm_year % 4 != 0 || (pt.tm_year % 100 == 0 && pt.tm_year % 400 != 100)) )
    return ret;

  if( localtz )
  {
    struct tm *gmt = NULL;
    time_t     time;

    time = mktime( &pt ); /* brocken-down tm asumed as localtime */
    if( time == -1 )
      return ret;

    time -= (time_t)localtz;

    gmt = localtime( &time );
    if( !gmt )
      return ret;

    memcpy( (void *)&pt, (const void *)gmt, sizeof(struct tm) );
  }

  memcpy( (void *)tm, (const void *)&pt, sizeof(struct tm) );

  return mktime( &pt );
}


void show_date_relative( struct strbuf *sb, time_t t )
{
  time_t now, diff;

  if( !sb || !t ) return;

  now = time( NULL );
  if( now < t )
  {
    strbuf_addstr( sb, _("in the future") );
    return;
  }
  diff = now - t;
  if( diff < 90 )
  {
    strbuf_addf( sb, Q_("%"PRIdMAX" second ago", "%"PRIdMAX" seconds ago", diff), diff );
    return;
  }
  /* Turn it into minutes */
  diff = (diff + 30) / 60;
  if( diff < 90 )
  {
    strbuf_addf( sb, Q_("%"PRIdMAX" minute ago", "%"PRIdMAX" minutes ago", diff), diff );
    return;
  }
  /* Turn it into hours */
  diff = (diff + 30) / 60;
  if( diff < 36 )
  {
    strbuf_addf( sb, Q_("%"PRIdMAX" hour ago", "%"PRIdMAX" hours ago", diff), diff );
    return;
  }
  /* We deal with number of days from here on */
  diff = (diff + 12) / 24;
  if( diff < 14 )
  {
    strbuf_addf( sb, Q_("%"PRIdMAX" day ago", "%"PRIdMAX" days ago", diff), diff );
    return;
  }
  /* Say weeks for the past 10 weeks or so */
  if( diff < 70 )
  {
    strbuf_addf( sb, Q_("%"PRIdMAX" week ago", "%"PRIdMAX" weeks ago", (diff+3)/7), (diff+3)/7 );
    return;
  }
  /* Say months for the past 12 months or so */
  if( diff < 365 )
  {
    strbuf_addf( sb, Q_("%"PRIdMAX" month ago", "%"PRIdMAX" months ago", (diff+15)/30), (diff+15)/30 );
    return;
  }
  /* Give years and months for 5 years or so */
  if( diff < 1825 )
  {
    time_t totalmonths = (diff * 12 * 2 + 365) / (365 * 2);
    time_t years = totalmonths / 12;
    time_t months = totalmonths % 12;
    if( months )
    {
      struct strbuf buf = STRBUF_INIT;
      strbuf_addf( &buf, Q_("%"PRIdMAX" year", "%"PRIdMAX" years", years), years );
      strbuf_addf( sb,
         /* TRANSLATORS: "%s" is "<n> months" */
         Q_("%s, %"PRIdMAX" month ago", "%s, %"PRIdMAX" months ago", months), buf.buf, months );
      strbuf_release( &buf );
    }
    else
      strbuf_addf( sb, Q_("%"PRIdMAX" year ago", "%"PRIdMAX" years ago", years), years );
    return;
  }
  /* Otherwise, just years. Centuries is probably overkill. */
  strbuf_addf( sb, Q_("%"PRIdMAX" year ago", "%"PRIdMAX" years ago", (diff+183)/365), (diff+183)/365 );
}

struct date_mode *date_mode_from_type( enum date_mode_type type )
{
  static struct date_mode mode;
  mode.type = type;
  mode.local = 0;
  return &mode;
}

static const char *month_names[] = {
  "January", "February", "March", "April", "May", "June",
  "July", "August", "September", "October", "November", "December"
};

static const char *weekday_names[] = {
  "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"
};

static time_t gm_time_t( time_t time, int tz )
{
  int minutes;

  minutes = tz < 0 ? -tz : tz;
  minutes = (minutes / 100)*60 + (minutes % 100);
  minutes = tz < 0 ? -minutes : minutes;

  time += minutes * 60;

  return time;
}

static struct tm *time_to_tm( time_t time, int tz, struct tm *tm )
{
  time_t t = gm_time_t( time, tz );
  return gmtime_r( &t, tm );
}

static struct tm *time_to_tm_local( time_t time, struct tm *tm )
{
  time_t t = time;
  return localtime_r( &t, tm );
}

/**********************************************************
  Fill in the localtime 'struct tm' for the supplied time,
  and return the local tz.
 */
static int local_time_tzoffset( time_t t, struct tm *tm )
{
  time_t t_local;
  int offset, eastwest;

  localtime_r( &t, tm );
  t_local = mktime( tm );
  if( t_local == -1 )
    return 0; /* error; just use +0000 */
  if( t_local < t )
  {
    eastwest = -1;
    offset = t - t_local;
  }
  else
  {
    eastwest = 1;
    offset = t_local - t;
  }
  offset /= 60; /* in minutes */
  offset = (offset % 60) + ((offset / 60) * 100);
  return offset * eastwest;
}

static int local_tzoffset( time_t time )
{
  struct tm tm;
  return local_time_tzoffset( time, &tm );
}


static void show_date_normal( struct strbuf *sb,
                              time_t time, struct tm *tm, int tz,
                              struct tm *human_tm, int human_tz, int local )
{
  struct
  {
    unsigned int year:1,
                 date:1,
                 wday:1,
                 time:1,
                 seconds:1,
                 tz:1;
  } hide = { 0 };

  hide.tz = local || tz == human_tz;
  hide.year = tm->tm_year == human_tm->tm_year;
  if( hide.year )
  {
    if( tm->tm_mon == human_tm->tm_mon )
    {
      if( tm->tm_mday > human_tm->tm_mday )
      {
        /* Future date: think timezones */
      }
      else if( tm->tm_mday == human_tm->tm_mday )
      {
        hide.date = hide.wday = 1;
      }
      else if( tm->tm_mday + 5 > human_tm->tm_mday )
      {
        /* Leave just weekday if it was a few days ago */
        hide.date = 1;
      }
    }
  }

  /* Show "today" times as just relative times */
  if( hide.wday )
  {
    show_date_relative( sb, time );
    return;
  }

  /******************************************************
    Always hide seconds for human-readable.
    Hide timezone if showing date.
    Hide weekday and time if showing year.

    The logic here is two-fold:
     (a) only show details when recent enough to matter
     (b) keep the maximum length "similar", and in check
   ******************************************************/
  if( human_tm->tm_year )
  {
    hide.seconds = 1;
    hide.tz |= !hide.date;
    hide.wday = hide.time = !hide.year;
  }

  if( !hide.wday )
    strbuf_addf( sb, "%.3s ", weekday_names[tm->tm_wday] );
  if( !hide.date )
    strbuf_addf( sb, "%.3s %d ", month_names[tm->tm_mon], tm->tm_mday );

  /* Do we want AM/PM depending on locale? */
  if( !hide.time )
  {
    strbuf_addf( sb, "%02d:%02d", tm->tm_hour, tm->tm_min );
    if( !hide.seconds )
      strbuf_addf( sb, ":%02d", tm->tm_sec );
  }
  else
    strbuf_rtrim( sb );

  if( !hide.year )
    strbuf_addf( sb, " %d", tm->tm_year + 1900 );

  if( !hide.tz )
    strbuf_addf( sb, " %+05d", tz );
}

void show_date( struct strbuf *sb, time_t t, int tz, const struct date_mode *mode )
{
  struct tm *tm;
  struct tm  tmbuf = { 0 };
  struct tm  human_tm = { 0 };
  int        human_tz = -1;

  if( mode->type == DATE_UNIX )
  {
    strbuf_addf( sb, "%"PRIdMAX, t );
  }

  if( mode->type == DATE_HUMAN )
  {
    time_t now = time( NULL );

    /* Fill in the data for "current time" in human_tz and human_tm */
    human_tz = local_time_tzoffset( now, &human_tm );
  }

  if( mode->local )
    tz = local_tzoffset( t );

  if( mode->type == DATE_RAW )
  {
    strbuf_addf( sb, "%"PRIdMAX" %+05d", t, tz );
  }

  if( mode->type == DATE_RELATIVE )
  {
    show_date_relative( sb, t );
  }

  if( mode->local )
    tm = time_to_tm_local( t, &tmbuf );
  else
    tm = time_to_tm( t, tz, &tmbuf );
  if (!tm) {
    tm = time_to_tm( 0, 0, &tmbuf );
    tz = 0;
  }

  if( mode->type == DATE_SHORT )
    strbuf_addf( sb, "%04d-%02d-%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday );
  else if( mode->type == DATE_ISO8601 )
    strbuf_addf( sb, "%04d-%02d-%02d %02d:%02d:%02d %+05d",
                      tm->tm_year + 1900,
                      tm->tm_mon + 1,
                      tm->tm_mday,
                      tm->tm_hour, tm->tm_min, tm->tm_sec,
                      tz );
  else if( mode->type == DATE_ISO8601_STRICT )
  {
    char sign = (tz >= 0) ? '+' : '-';
    tz = abs( tz );
    strbuf_addf( sb, "%04d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
                      tm->tm_year + 1900,
                      tm->tm_mon + 1,
                      tm->tm_mday,
                      tm->tm_hour, tm->tm_min, tm->tm_sec,
                      sign, tz / 100, tz % 100 );
  }
  else if( mode->type == DATE_RFC2822 )
    strbuf_addf( sb, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
                      weekday_names[tm->tm_wday], tm->tm_mday,
                      month_names[tm->tm_mon], tm->tm_year + 1900,
                      tm->tm_hour, tm->tm_min, tm->tm_sec, tz );
  else
    show_date_normal( sb, t, tm, tz, &human_tm, human_tz, mode->local );
}