New tool payproc-jrnl.
authorWerner Koch <wk@gnupg.org>
Wed, 28 May 2014 06:44:36 +0000 (08:44 +0200)
committerWerner Koch <wk@gnupg.org>
Wed, 28 May 2014 07:02:29 +0000 (09:02 +0200)
* src/percent.c: New.  From GnuPG master.
* src/util.c (memstr): New.  From my addrutil.c.
(memistr, memicmp): New.  From GnuPG master.
* src/util.h (atoi_1, atoi_2, atoi_4): New.
(xtoi_1, xtoi_2, xtoi_4): New.
* src/payproc-jrnl.c: New.
--

The record selection code has been derived from code I wrote for
addrutil.c.

.gitignore
src/Makefile.am
src/estream.c
src/journal.c
src/payproc-jrnl.c [new file with mode: 0644]
src/percent.c [new file with mode: 0644]
src/util.c
src/util.h

index 17dd524..ab15052 100644 (file)
@@ -33,3 +33,6 @@
 /src/t-connection
 /src/out2
 /payproc-*.tar.bz2
+/src/payproc-jrnl
+/src/libcommon.a
+/src/libcommonpth.a
index cbb9579..7e47336 100644 (file)
 
 EXTRA_DIST = cJSON.readme tls-ca.pem
 
+bin_PROGRAMS = payprocd payproc-jrnl
+noinst_PROGRAMS = $(module_tests) t-http
+noinst_LIBRARIES = libcommon.a libcommonpth.a
 
-bin_PROGRAMS = payprocd
+TESTS = $(module_tests)
 
-utility_sources = \
+common_sources = \
        util.c util.h \
        estream.c estream.h \
        estream-printf.c estream-printf.h \
        libjnlib-config.h \
        logging.c logging.h \
-       http.c http.h \
-       cJSON.c cJSON.h \
        membuf.c membuf.h \
        strlist.c strlist.h \
+       percent.c \
        argparse.c argparse.h
 
+libcommon_a_SOURCES = $(common_sources)
+libcommon_a_CFLAGS = $(AM_CFLAGS) $(LIBGPG_ERROR_CFLAGS) -DWITHOUT_NPTH=1
+libcommonpth_a_SOURCES = $(common_sources)
+libcommonpth_a_CFLAGS = $(AM_CFLAGS) $(LIBGPG_ERROR_CFLAGS)
+
+utility_sources = \
+       http.c http.h \
+       cJSON.c cJSON.h
+
 payprocd_SOURCES = \
        payprocd.c payprocd.h \
        connection.c connection.h \
@@ -42,22 +53,27 @@ payprocd_SOURCES = \
        journal.c journal.h \
        session.c session.h \
        $(utility_sources)
+payprocd_CFLAGS = $(GPG_ERROR_CFLAGS) $(NPTH_CFLAGS) $(LIBGCRYPT_CFLAGS) \
+                  $(LIBGNUTLS_CFLAGS)
+payprocd_LDADD = -lm libcommonpth.a \
+                  $(GPG_ERROR_LIBS) $(NPTH_LIBS) $(LIBGCRYPT_LIBS) \
+                  $(LIBGNUTLS_LIBS)
+
+payproc_jrnl_SOURCES = \
+        payproc-jrnl.c
 
-noinst_PROGRAMS = $(module_tests) t-http
-TESTS = $(module_tests)
 
 module_tests = t-connection
 
-AM_CFLAGS = $(GPG_ERROR_CFLAGS) $(NPTH_CFLAGS) $(LIBGCRYPT_CFLAGS) \
-            $(LIBGNUTLS_CFLAGS)
-LDADD  = -lm $(GPG_ERROR_LIBS) $(NPTH_LIBS) $(LIBGCRYPT_LIBS) \
-        $(LIBGNUTLS_LIBS)
+AM_CFLAGS = $(GPG_ERROR_CFLAGS)
+LDADD  = -lm libcommon.a $(GPG_ERROR_LIBS)
 
 t_common_sources = t-common.h $(utility_sources)
 t_common_cflags  = $(GPG_ERROR_CFLAGS) $(NPTH_CFLAGS) $(LIBGNUTLS_CFLAGS)
-t_common_ldadd   = $(GPG_ERROR_LIBS) $(NPTH_LIBS) $(LIBGNUTLS_LIBS) -lm
+t_common_ldadd   = libcommonpth.a $(GPG_ERROR_LIBS) $(NPTH_LIBS) \
+                   $(LIBGNUTLS_LIBS) -lm
 
-# (http.c and http.h are part of common_sources)
+# (http.c and http.h are part of t_common_sources)
 t_http_SOURCES = t-http.c $(t_common_sources)
 t_http_CFLAGS  = $(t_common_cflags)
 t_http_LDADD   = $(t_common_ldadd)
index 2f6b221..132e9e0 100644 (file)
@@ -372,6 +372,7 @@ init_stream_lock (estream_t ES__RESTRICT stream)
   return rc;
 #else
   (void)stream;
+  return 0;
 #endif
 }
 
@@ -409,6 +410,7 @@ trylock_stream (estream_t ES__RESTRICT stream)
   return rc;
 #else
   (void)stream;
+  return 0;
 #endif
 }
 
@@ -429,18 +431,18 @@ unlock_stream (estream_t ES__RESTRICT stream)
 }
 
 
+#ifdef HAVE_NPTH
 static int
 init_list_lock (void)
 {
-#ifdef HAVE_NPTH
   int rc;
 
   dbg_lock_0 ("enter init_list_lock\n");
   rc = npth_mutex_init (&estream_list_lock, NULL);
   dbg_lock_1 ("leave init_list_lock: rc=%d\n", rc);
   return rc;
-#endif
 }
+#endif
 
 
 static void
index 078a781..668f08d 100644 (file)
 
    | No | Name     | Description                                    |
    |----+----------+------------------------------------------------|
-   |    | date     | UTC the record was created (yyyymmddThhmmss)   |
-   |    | type     | Record type                                    |
+   |  1 | date     | UTC the record was created (yyyymmddThhmmss)   |
+   |  2 | type     | Record type                                    |
    |    |          | - := sync mark record                          |
    |    |          | $ := system record                             |
    |    |          | C := credit card charge                        |
    |    |          | R := credit card refund                        |
-   |    | account  | Even numbers are test accounts.                |
-   |    | currency | 3 letter ISO code for the currency (lowercase) |
-   |    | amount   | Amount with decimal point                      |
-   |    | desc     | Description for this transaction               |
-   |    | email    | Email address                                  |
-   |    | meta     | Structured field with additional data          |
-   |    | last4    | The last 4 digits of the card                  |
-   |    | paygw    | Payment gateway (0=n/a, 1=stripe.com)          |
-   |    | chargeid | Charge id
-   |    | blntxid  | Balance transaction id                         |
+   |  3 | account  | Even numbers are test accounts.                |
+   |  4 | currency | 3 letter ISO code for the currency (lowercase) |
+   |  5 | amount   | Amount with decimal point                      |
+   |  6 | desc     | Description for this transaction               |
+   |  7 | mail     | Email address                                  |
+   |  8 | meta     | Structured field with additional data          |
+   |  9 | last4    | The last 4 digits of the card                  |
+   | 10 | service  | Payment service (0=n/a, 1=stripe.com)          |
+   | 11 | chargeid | Charge id                                      |
+   | 12 | blntxid  | Balance transaction id                         |
    |----+----------+------------------------------------------------|
 
    Because of the multithreaded operation it may happen that records
diff --git a/src/payproc-jrnl.c b/src/payproc-jrnl.c
new file mode 100644 (file)
index 0000000..02aaa07
--- /dev/null
@@ -0,0 +1,845 @@
+/* payproc-jrnl.c - Payproc journal tool
+ * Copyright (C) 2014 g10 Code GmbH
+ *
+ * This file is part of Payproc.
+ *
+ * Payproc 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Payproc 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 <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <gpg-error.h>
+
+#include "util.h"
+#include "logging.h"
+#include "argparse.h"
+#include "estream.h"
+
+
+/* Constants to identify the options. */
+enum opt_values
+  {
+    aNull = 0,
+    oVerbose   = 'v',
+    oIgnoreCase = 'i',
+    oField      = 'F',
+    oSelect     = 'S',
+
+    aCount      = 500,
+    aPrint,
+
+    oHTML,
+    oSeparator,
+
+    oLast
+  };
+
+
+/* The list of commands and options. */
+static ARGPARSE_OPTS opts[] = {
+  ARGPARSE_group (300, "@Commands:\n "),
+
+  ARGPARSE_c (aCount, "count", "count selected records"),
+  ARGPARSE_c (aPrint, "print", "print fields from selected records"),
+
+  ARGPARSE_group (301, "@\nOptions:\n "),
+
+  ARGPARSE_s_n (oVerbose,"verbose",  "verbose diagnostics"),
+  ARGPARSE_s_n (oHTML,   "html",     "print for use with HTML"),
+  ARGPARSE_s_n (oIgnoreCase, "ignore-case", "ignore case in record matching"),
+  ARGPARSE_s_s (oSeparator, "separator", "|CHAR|use CHAR as output separator"),
+  ARGPARSE_s_s (oField,  "field",    "|NAME|output field NAME"),
+  ARGPARSE_s_s (oSelect, "select",   "|EXPR|output records matching EXPR"),
+
+  ARGPARSE_end ()
+};
+
+/* List of the journal field names.  */
+static char *jrnl_field_names[] =
+  {
+    "_lnr", /* virtual field.  */
+    "date", "type", "account", "currency", "amount",
+    "desc", "mail", "meta", "last4", "service",
+    "chargeid", "blnxtxid"
+  };
+
+
+/* Select operators.  */
+typedef enum
+  {
+    SELECT_SAME,
+    SELECT_NOTSAME,
+    SELECT_SUB,
+    SELECT_NOTSUB,
+    SELECT_EMPTY,
+    SELECT_NOTEMPTY,
+    SELECT_EQ, /* Numerically equal.  */
+    SELECT_NE, /* Numerically not equal.  */
+    SELECT_LE,
+    SELECT_GE,
+    SELECT_LT,
+    SELECT_GT
+  } select_op_t;
+
+
+/* Defintion for a select expression.  */
+typedef struct selectexpr_s
+{
+  struct selectexpr_s *next;
+  int meta;
+  unsigned int fnr;
+  select_op_t op;
+  const char *value;  /* Points into NAME.  */
+  long numvalue;
+  char name[1];
+} *selectexpr_t;
+
+
+/* Definition for field names.  */
+typedef struct outfield_s
+{
+  struct outfield_s *next;
+  int meta;
+  unsigned int fnr;
+  char name[1];
+} *outfield_t;
+
+
+/* Command line options.  */
+static struct
+{
+  int verbose;
+  int html;
+  int separator;
+  int ignorecase;
+  outfield_t outfields;
+  selectexpr_t selectexpr;
+} opt;
+
+
+/* The general action - one of the opt_values.  */
+static int command;
+
+/* Total number of selected records so far.  */
+static unsigned int recordcount;
+
+\f
+/* Local prototypes.  */
+static const char *get_fieldname (int fnr);
+static int parse_fieldname (char *name, int *r_meta, unsigned int *r_fnr);
+static selectexpr_t parse_selectexpr (const char *expr);
+static void one_file (const char *fname);
+
+
+\f
+static const char *
+my_strusage( int level )
+{
+  const char *p;
+
+  switch (level)
+    {
+    case 11: p = "payproc-jrnl"; break;
+    case 13: p = PACKAGE_VERSION; break;
+    case 19: p = "Please report bugs to bugs@g10code.com.\n"; break;
+    case 1:
+    case 40: p = "Usage: payproc-jrnl [options] FILES (-h for help)"; break;
+    case 41: p = ("Syntax: payproc-jrnl [options]\n"
+                  "Payproc journal tool\n"); break;
+    default: p = NULL; break;
+    }
+  return p;
+}
+
+
+int
+main (int argc, char **argv)
+{
+  ARGPARSE_ARGS pargs;
+  outfield_t of, of2;
+  selectexpr_t se, se2;
+
+  opt.separator = ':';
+
+  /* Set program name etc.  */
+  set_strusage (my_strusage);
+  log_set_prefix ("payproc-jrnl", JNLIB_LOG_WITH_PREFIX);
+
+  /* Make sure that our subsystems are ready.  */
+  es_init ();
+
+  /* Parse the command line. */
+  pargs.argc  = &argc;
+  pargs.argv  = &argv;
+  pargs.flags = ARGPARSE_FLAG_KEEP;
+  while (optfile_parse (NULL, NULL, NULL, &pargs, opts))
+    {
+      switch (pargs.r_opt)
+        {
+        case aCount:
+        case aPrint:
+          command = pargs.r_opt;
+          break;
+
+        case oVerbose:  opt.verbose++; break;
+        case oHTML: opt.html = 1; break;
+        case oIgnoreCase: opt.ignorecase = 1; break;
+        case oSeparator:
+          if (strlen (pargs.r.ret_str) > 1)
+            log_error ("--separator takes only a single character\n");
+          else
+            opt.separator = *pargs.r.ret_str;
+          break;
+        case oField:
+         of = xmalloc (sizeof *of + strlen (pargs.r.ret_str));
+         strcpy (of->name, pargs.r.ret_str);
+         of->next = NULL;
+          of->meta = 0;
+          of->fnr = 0;
+          if (parse_fieldname (of->name, &of->meta, &of->fnr))
+            ;
+         else if (!(of2 = opt.outfields))
+           opt.outfields = of;
+         else
+           {
+             for (; of2->next; of2 = of2->next)
+               ;
+             of2->next = of;
+           }
+         break;
+
+       case oSelect:
+          se = parse_selectexpr (pargs.r.ret_str);
+          if (!se)
+            ;
+         else if (!(se2 = opt.selectexpr))
+           opt.selectexpr = se;
+         else
+           {
+             for (; se2->next; se2 = se2->next)
+               ;
+             se2->next = se;
+           }
+         break;
+
+
+        default: pargs.err = ARGPARSE_PRINT_ERROR; break;
+       }
+    }
+
+  if (log_get_errorcount (0))
+    exit (2);
+  if (!command)
+    {
+      log_info ("no command given - assuming '--count'\n");
+      command = aCount;
+    }
+
+  /* Debug output.  */
+  if (opt.outfields && opt.verbose > 1)
+    {
+      log_info ("--- Begin output fields ---\n");
+      for (of = opt.outfields; of; of = of->next)
+        {
+          if (of->meta)
+            log_info ("meta '%s'\n", of->name);
+          else
+            log_info (" %3d '%s'\n", of->fnr, get_fieldname (of->fnr));
+        }
+      log_info ("--- End output fields ---\n");
+    }
+  if (opt.selectexpr && opt.verbose > 1)
+    {
+      log_info ("--- Begin selectors ---\n");
+      for (se=opt.selectexpr; se; se = se->next)
+        log_info ("*(%s) %s '%s'\n",
+                  se->name,
+                  se->op == SELECT_SAME?    "= ":
+                  se->op == SELECT_NOTSAME? "<>":
+                  se->op == SELECT_SUB?     "=~":
+                  se->op == SELECT_NOTSUB?  "!~":
+                  se->op == SELECT_EMPTY?   "-z":
+                  se->op == SELECT_NOTEMPTY?"-n":
+                  se->op == SELECT_EQ?      "==":
+                  se->op == SELECT_NE?      "!=":
+                  se->op == SELECT_LT?      "< ":
+                  se->op == SELECT_LE?      "<=":
+                  se->op == SELECT_GT?      "> ":
+                  se->op == SELECT_GE?      ">=":"[oops]",
+                  se->value);
+      log_info ("--- End selectors ---\n");
+    }
+
+  /* Process all files.  */
+  for (; argc; argc--, argv++)
+    {
+      one_file (*argv);
+    }
+
+  /* Print totals.  */
+  if (command == aCount)
+    es_printf ("%u\n", recordcount);
+
+  return !!log_get_errorcount (0);
+}
+
+
+/* Return the name of the field with FNR.  */
+static const char *
+get_fieldname (int fnr)
+{
+  if (fnr < 0 || fnr >= DIM(jrnl_field_names))
+    return "?";
+  return jrnl_field_names[fnr];
+}
+
+
+/* Parse a field name.  Returns 0 on success.  */
+static int
+parse_fieldname (char *name, int *r_meta, unsigned int *r_fnr)
+{
+  const char *s;
+  char *p;
+
+  *r_meta = 0;
+  *r_fnr = 0;
+
+  s = name;
+  if (*s == '[')
+    {
+      *r_meta = 1;
+      for (p=name, ++s; *s && *s != ']';)
+        *p++ = *s++;
+      *p = 0;
+      if (*s != ']' || s[1] || !*name)
+        {
+          log_error ("field '%s': invalid meta field name syntax\n", name);
+          return -1;
+        }
+    }
+  else
+    {
+      if (digitp (s))
+        {
+          *r_fnr = atoi (s);
+          if (*r_fnr >= DIM(jrnl_field_names))
+            {
+              log_error ("field '%s': field number out of range\n", name);
+              return -1;
+            }
+          *name = 0;
+        }
+      else
+        {
+          for (*r_fnr = 0; *r_fnr < DIM(jrnl_field_names); ++*r_fnr)
+            if (!strcmp (s, jrnl_field_names[*r_fnr]))
+              break;
+          if (*r_fnr >= DIM(jrnl_field_names))
+            {
+              log_error ("field '%s': unknown name\n", s);
+              return -1;
+            }
+        }
+    }
+
+  return 0;
+}
+
+
+/* Parse a select expression.  Supported expressions are:
+
+   [<ws>]NAME[<ws>]<op>[<ws>]VALUE[<ws>]
+
+   NAME and VALUE may not be the empty string. <ws> indicates white
+   space.  [] indicates optional parts.  If VALUE starts with one of
+   the characters used in any <op> a space after the <op> is required.
+   Valid <op> are:
+
+      =~  Substring must match
+      !~  Substring must not match
+      =   The full string must match
+      <>  The full string must not match
+      ==  The numerical value must match
+      !=  The numerical value must not match
+      <=  The numerical value of the field must be LE than the value.
+      <   The numerical value of the field must be LT than the value.
+      >=  The numerical value of the field must be GT than the value.
+      >=  The numerical value of the field must be GE than the value.
+      -n  True if value is not empty.
+      -z  True if value is empty.
+
+      Numerical values are computed as long int.  */
+static selectexpr_t
+parse_selectexpr (const char *expr)
+{
+  selectexpr_t se;
+  const char *s0, *s;
+
+  while (*expr == ' ' || *expr == '\t')
+    expr++;
+
+  se = xmalloc (sizeof *se + strlen (expr));
+  strcpy (se->name, expr);
+  se->next = NULL;
+
+  s = strpbrk (expr, "=<>!~-");
+  if (!s || s == expr )
+    {
+      log_error ("no field name given for select\n");
+      return NULL;
+    }
+  s0 = s;
+
+  if (!strncmp (s, "=~", 2))
+    {
+      se->op = SELECT_SUB;
+      s += 2;
+    }
+  else if (!strncmp (s, "!~", 2))
+    {
+      se->op = SELECT_NOTSUB;
+      s += 2;
+    }
+  else if (!strncmp (s, "<>", 2))
+    {
+      se->op = SELECT_NOTSAME;
+      s += 2;
+    }
+  else if (!strncmp (s, "==", 2))
+    {
+      se->op = SELECT_EQ;
+      s += 2;
+    }
+  else if (!strncmp (s, "!=", 2))
+    {
+      se->op = SELECT_NE;
+      s += 2;
+    }
+  else if (!strncmp (s, "<=", 2))
+    {
+      se->op = SELECT_LE;
+      s += 2;
+    }
+  else if (!strncmp (s, ">=", 2))
+    {
+      se->op = SELECT_GE;
+      s += 2;
+    }
+  else if (!strncmp (s, "<", 1))
+    {
+      se->op = SELECT_LT;
+      s += 1;
+    }
+  else if (!strncmp (s, ">", 1))
+    {
+      se->op = SELECT_GT;
+      s += 1;
+    }
+  else if (!strncmp (s, "=", 1))
+    {
+      se->op = SELECT_SAME;
+      s += 1;
+    }
+  else if (!strncmp (s, "-z", 2))
+    {
+      se->op = SELECT_EMPTY;
+      s += 2;
+    }
+  else if (!strncmp (s, "-n", 2))
+    {
+      se->op = SELECT_NOTEMPTY;
+      s += 2;
+    }
+  else
+    {
+      log_error ("invalid select operator\n");
+      return NULL;
+    }
+
+  /* We require that a space is used if the value starts with any of
+     the operator characters.  */
+  if (se->op == SELECT_EMPTY || se->op == SELECT_NOTEMPTY)
+    ;
+  else if (strchr ("=<>!~", *s))
+    {
+      log_error ("invalid select operator\n");
+      return NULL;
+    }
+
+  while (*s == ' ' || *s == '\t')
+    s++;
+
+  if (se->op == SELECT_EMPTY || se->op == SELECT_NOTEMPTY)
+    {
+      if (*s)
+        {
+          log_error ("value given for -n or -z\n");
+          return NULL;
+        }
+    }
+  else
+    {
+      if (!*s)
+        {
+          log_error ("no value given for select\n");
+          return NULL;
+        }
+    }
+
+  se->name[s0 - expr] = 0;
+  trim_spaces (se->name);
+  if (!se->name[0])
+    {
+      log_error ("no field name given for select\n");
+      return NULL;
+    }
+
+  trim_spaces (se->name + (s - expr));
+  se->value = se->name + (s - expr);
+  if (!se->value[0] && !(se->op == SELECT_EMPTY || se->op == SELECT_NOTEMPTY))
+    {
+      log_error ("no value given for select\n");
+      return NULL;
+    }
+
+  if (parse_fieldname (se->name, &se->meta, &se->fnr))
+    return NULL;
+
+  se->numvalue = strtol (se->value, NULL, 10);
+
+  return se;
+}
+
+
+/* Return true if the record RECORD has been selected.  Note that
+   selection on meta fields is not yet functional.  */
+static int
+select_record_p (char **field, int nfields, unsigned int lnr)
+{
+  char linenostr[20];
+  selectexpr_t se;
+  const char *value;
+  size_t selen, valuelen;
+  long numvalue;
+  int result = 1;
+
+  *linenostr = 0;
+
+  for (se=opt.selectexpr; se; se = se->next)
+    {
+      if (se->meta)
+        {
+          log_info ("meta fields in selects are not yet supported\n");
+          continue;
+        }
+      else if (!se->fnr)
+        {
+          if (!*linenostr)
+            snprintf (linenostr, sizeof linenostr, "%u", lnr);
+          value = linenostr;
+        }
+      else if (se->fnr-1 < nfields)
+        value = field[se->fnr-1];
+      else
+        {
+          log_debug ("oops: fieldno out of range at %d\n", __LINE__);
+          continue;
+        }
+
+      if (!*value)
+        {
+          /* Field is empty.  */
+          switch (se->op)
+            {
+            case SELECT_NOTSAME:
+            case SELECT_NOTSUB:
+            case SELECT_NE:
+            case SELECT_EMPTY:
+              result = 1;
+              break;
+            default:
+              result = 0;
+              break;
+            }
+        }
+      else /* Field has a value.  */
+        {
+          valuelen = strlen (value);
+          numvalue = strtol (value, NULL, 10);
+          selen = strlen (se->value);
+
+          switch (se->op)
+            {
+            case SELECT_SAME:
+              if (opt.ignorecase)
+                result = (valuelen==selen && !memicmp (value,se->value,selen));
+              else
+                result = (valuelen==selen && !memcmp (value,se->value,selen));
+              break;
+            case SELECT_NOTSAME:
+              if (opt.ignorecase)
+                result = !(valuelen==selen && !memicmp (value,se->value,selen));
+              else
+                result = !(valuelen==selen && !memcmp (value,se->value,selen));
+              break;
+            case SELECT_SUB:
+              if (opt.ignorecase)
+                result = !!memistr (value, valuelen, se->value);
+              else
+                result = !!memstr (value, valuelen, se->value);
+              break;
+            case SELECT_NOTSUB:
+              if (opt.ignorecase)
+                result = !memistr (value, valuelen, se->value);
+              else
+                result = !memstr (value, valuelen, se->value);
+              break;
+            case SELECT_EMPTY:
+              result = !valuelen;
+              break;
+            case SELECT_NOTEMPTY:
+              result = !!valuelen;
+              break;
+            case SELECT_EQ:
+              result = (numvalue == se->numvalue);
+              break;
+            case SELECT_NE:
+              result = (numvalue != se->numvalue);
+              break;
+            case SELECT_GT:
+              result = (numvalue > se->numvalue);
+              break;
+            case SELECT_GE:
+              result = (numvalue >= se->numvalue);
+              break;
+            case SELECT_LT:
+              result = (numvalue < se->numvalue);
+              break;
+            case SELECT_LE:
+              result = (numvalue <= se->numvalue);
+              break;
+            }
+        }
+      if (!result)
+        break;
+    }
+
+  return result;
+}
+
+
+/* Print a string.  */
+static void
+print_string (const char *string)
+{
+  if (opt.html)
+    {
+      const char *s;
+      char *raw;
+
+      raw = percent_unescape (string, ' ');
+      if (!raw)
+        log_fatal ("percent_unescape failed: %s\n",
+                   gpg_strerror (gpg_error_from_syserror ()));
+
+      for (s = raw; *s; s++)
+        {
+          if (*s == opt.separator)
+            es_printf ("&#%d;", opt.separator);
+          else if (*s == '<')
+            es_fputs ("&lt;", es_stdout);
+          else if (*s == '>')
+            es_fputs ("&gt;", es_stdout);
+          else if (*s == '&')
+            es_fputs ("&amp;", es_stdout);
+          else if (*s == '\n')
+            es_fputs ("<br/>", es_stdout);
+          else if (*s == '\r')
+            ;
+          else
+            es_putc (*s, es_stdout);
+        }
+
+      xfree (raw);
+    }
+  else
+    es_fputs (string, es_stdout);
+}
+
+
+/* Print a meta subfield with NAME.  BUFFER holds the name/value pair
+   of that subfield.  Return true if NAME matches.  */
+static int
+print_meta_sub (char *buffer, const char *name)
+{
+  char *p;
+
+  /* In theory name could be escaped but we neglect that because this
+     does not make any sense.  */
+  p = strchr (buffer, '=');
+  if (!p)
+    return 0; /* No name/value.  */
+  *p = 0;
+  if (strcmp (buffer, name))
+    {
+      *p = '=';
+      return 0; /* Does not match.  */
+    }
+  *p++ = '=';
+
+  /* We can keep the percent escaping.  */
+  print_string (p);
+  return 1;   /* Found.  */
+}
+
+
+/* Print the meta subfield NAME from BUFFER which holds the entire
+   meta field. */
+static void
+print_meta (char *buffer, const char *name)
+{
+  char *p;
+
+  do
+    {
+      p = strchr (buffer, '&');
+      if (p)
+        *p = 0;
+      if (print_meta_sub (buffer, name))
+        {
+          if (p)
+            *p = '&';
+          return;
+        }
+      if (p)
+        *p++ = '&';
+      buffer = p;
+    }
+  while (buffer);
+}
+
+
+/* Process one journal line.  LINE has no trailing LF.  The function
+   may change LINE.  */
+static int
+one_line (const char *fname, unsigned int lnr, char *line)
+{
+  char *field[12];
+  int nfields = 0;
+
+  /* Parse into fields.  */
+  while (line && nfields < DIM(field))
+    {
+      field[nfields++] = line;
+      line = strchr (line, ':');
+      if (line)
+       *(line++) = '\0';
+    }
+  if (nfields < DIM(field))
+    {
+      log_error ("%s:%u: not enough fields - not a Payproc journal?\n",
+                 fname, lnr);
+      return -1;
+    }
+
+  if (opt.selectexpr && !select_record_p (field, nfields, lnr))
+    return 0; /* Not selected.  */
+
+
+  recordcount++;
+
+  /* Process.  */
+  if (command == aCount)
+    ;
+  else if (command == aPrint)
+    {
+      outfield_t of;
+      int i;
+
+      if (opt.outfields)
+        {
+          for (of = opt.outfields; of; of = of->next)
+            {
+              if (of->meta)
+                {
+                  if (nfields > 7)
+                    print_meta (field[7], of->name);
+                }
+              else if (!of->fnr)
+                es_printf ("%u", lnr);
+              else if (of->fnr-1 < nfields)
+                print_string (field[of->fnr-1]);
+
+              if (of->next)
+                es_putc (opt.separator, es_stdout);
+            }
+        }
+      else
+        {
+          for (i=0; i < nfields;)
+            {
+              print_string (field[i]);
+              if (++i < nfields)
+                es_putc (opt.separator, es_stdout);
+            }
+        }
+      es_putc ('\n', es_stdout);
+    }
+
+  return 0;
+}
+
+
+static void
+one_file (const char *fname)
+{
+  gpg_error_t err;
+  estream_t fp;
+  char *buffer = NULL;
+  size_t buflen = 0;
+  ssize_t nread;
+  unsigned int lnr = 0;
+
+  fp = es_fopen (fname, "r");
+  if (!fp)
+    {
+      err = gpg_error_from_syserror ();
+      log_error ("error opening '%s': %s\n", fname, gpg_strerror (err));
+      return;
+    }
+  if (opt.verbose)
+    log_info ("processing '%s'\n", fname);
+
+  while ((nread = es_read_line (fp, &buffer, &buflen, NULL)) > 0)
+    {
+      lnr++;
+      if (buffer[nread-1] == '\n')
+        buffer[--nread] = 0;
+      if (nread && buffer[nread-1] == '\r')
+        buffer[--nread] = 0;
+      if (nread && one_line (fname, lnr, buffer))
+        goto leave;
+    }
+  if (nread < 0)
+    {
+      err = gpg_error_from_syserror ();
+      log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
+    }
+
+ leave:
+  es_free (buffer);
+  es_fclose (fp);
+}
diff --git a/src/percent.c b/src/percent.c
new file mode 100644 (file)
index 0000000..9a1813f
--- /dev/null
@@ -0,0 +1,239 @@
+/* percent.c - Percent escaping
+ * Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of either
+ *
+ *   - the GNU Lesser General Public License as published by the Free
+ *     Software Foundation; either version 3 of the License, or (at
+ *     your option) any later version.
+ *
+ * or
+ *
+ *   - 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.
+ *
+ * or both in parallel, as here.
+ *
+ * This file 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 <config.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "util.h"
+
+
+/* Create a newly alloced string from STRING with all spaces and
+   control characters converted to plus signs or %xx sequences.  The
+   function returns the new string or NULL in case of a malloc
+   failure.
+
+   Note that we also escape the quote character to work around a bug
+   in the mingw32 runtime which does not correcty handle command line
+   quoting.  We correctly double the quote mark when calling a program
+   (i.e. gpg-protect-tool), but the pre-main code does not notice the
+   double quote as an escaped quote.  We do this also on POSIX systems
+   for consistency.  */
+char *
+percent_plus_escape (const char *string)
+{
+  char *buffer, *p;
+  const char *s;
+  size_t length;
+
+  for (length=1, s=string; *s; s++)
+    {
+      if (*s == '+' || *s == '\"' || *s == '%'
+          || *(const unsigned char *)s < 0x20)
+        length += 3;
+      else
+        length++;
+    }
+
+  buffer = p = xtrymalloc (length);
+  if (!buffer)
+    return NULL;
+
+  for (s=string; *s; s++)
+    {
+      if (*s == '+' || *s == '\"' || *s == '%'
+          || *(const unsigned char *)s < 0x20)
+        {
+          snprintf (p, 4, "%%%02X", *(unsigned char *)s);
+          p += 3;
+        }
+      else if (*s == ' ')
+        *p++ = '+';
+      else
+        *p++ = *s;
+    }
+  *p = 0;
+
+  return buffer;
+
+}
+
+
+/* Do the percent and plus/space unescaping from STRING to BUFFER and
+   return the length of the valid buffer.  Plus unescaping is only
+   done if WITHPLUS is true.  An escaped Nul character will be
+   replaced by NULREPL.  */
+static size_t
+do_unescape (unsigned char *buffer, const unsigned char *string,
+             int withplus, int nulrepl)
+{
+  unsigned char *p = buffer;
+
+  while (*string)
+    {
+      if (*string == '%' && string[1] && string[2])
+        {
+          string++;
+          *p = xtoi_2 (string);
+          if (!*p)
+            *p = nulrepl;
+          string++;
+        }
+      else if (*string == '+' && withplus)
+        *p = ' ';
+      else
+        *p = *string;
+      p++;
+      string++;
+    }
+
+  return (p - buffer);
+}
+
+
+/* Count space required after unescaping STRING.  Note that this will
+   never be larger than strlen (STRING).  */
+static size_t
+count_unescape (const unsigned char *string)
+{
+  size_t n = 0;
+
+  while (*string)
+    {
+      if (*string == '%' && string[1] && string[2])
+        {
+          string++;
+          string++;
+        }
+      string++;
+      n++;
+    }
+
+  return n;
+}
+
+
+/* Helper.  */
+static char *
+do_plus_or_plain_unescape (const char *string, int withplus, int nulrepl)
+{
+  size_t nbytes, n;
+  char *newstring;
+
+  nbytes = count_unescape (string);
+  newstring = xtrymalloc (nbytes+1);
+  if (newstring)
+    {
+      n = do_unescape (newstring, string, withplus, nulrepl);
+      assert (n == nbytes);
+      newstring[n] = 0;
+    }
+  return newstring;
+}
+
+
+/* Create a new allocated string from STRING with all "%xx" sequences
+   decoded and all plus signs replaced by a space.  Embedded Nul
+   characters are replaced by the value of NULREPL.  The function
+   returns the new string or NULL in case of a malloc failure.  */
+char *
+percent_plus_unescape (const char *string, int nulrepl)
+{
+  return do_plus_or_plain_unescape (string, 1, nulrepl);
+}
+
+
+/* Create a new allocated string from STRING with all "%xx" sequences
+   decoded.  Embedded Nul characters are replaced by the value of
+   NULREPL.  The function returns the new string or NULL in case of a
+   malloc failure.  */
+char *
+percent_unescape (const char *string, int nulrepl)
+{
+  return do_plus_or_plain_unescape (string, 0, nulrepl);
+}
+
+
+static size_t
+do_unescape_inplace (char *string, int withplus, int nulrepl)
+{
+  unsigned char *p, *p0;
+
+  p = p0 = string;
+  while (*string)
+    {
+      if (*string == '%' && string[1] && string[2])
+        {
+          string++;
+          *p = xtoi_2 (string);
+          if (!*p)
+            *p = nulrepl;
+          string++;
+        }
+      else if (*string == '+' && withplus)
+        *p = ' ';
+      else
+        *p = *string;
+      p++;
+      string++;
+    }
+
+  return (p - p0);
+}
+
+
+/* Perform percent and plus unescaping in STRING and return the new
+   valid length of the string.  Embedded Nul characters are replaced
+   by the value of NULREPL.  A terminating Nul character is not
+   inserted; the caller might want to call this function this way:
+
+      foo[percent_plus_unescape_inplace (foo, 0)] = 0;
+ */
+size_t
+percent_plus_unescape_inplace (char *string, int nulrepl)
+{
+  return do_unescape_inplace (string, 1, nulrepl);
+}
+
+
+/* Perform percent unescaping in STRING and return the new valid
+   length of the string.  Embedded Nul characters are replaced by the
+   value of NULREPL.  A terminating Nul character is not inserted; the
+   caller might want to call this function this way:
+
+      foo[percent_unescape_inplace (foo, 0)] = 0;
+ */
+size_t
+percent_unescape_inplace (char *string, int nulrepl)
+{
+  return do_unescape_inplace (string, 0, nulrepl);
+}
index 3059024..d58ca34 100644 (file)
@@ -162,6 +162,71 @@ has_leading_keyword (const char *string, const char *keyword)
 }
 
 
+/* Find string SUB in (BUFFER,BUFLEN).  */
+const char *
+memstr (const void *buffer, size_t buflen, const char *sub)
+{
+  const char *buf = buffer;
+  const char *t = buf;
+  const char *s = sub;
+  size_t n = buflen;
+
+  for (; n; t++, n--)
+    {
+      if (*t == *s)
+        {
+          for (buf = t++, buflen = n--, s++; n && *t == *s; t++, s++, n--)
+            ;
+          if (!*s)
+            return buf;
+          t = buf;
+          s = sub ;
+          n = buflen;
+       }
+    }
+  return NULL;
+}
+
+
+/* Find string SUB in (BUFFER,BUFLEN).
+ * Comparison is case-insensitive.  */
+const char *
+memistr (const void *buffer, size_t buflen, const char *sub)
+{
+  const unsigned char *buf = buffer;
+  const unsigned char *t = (const unsigned char *)buffer;
+  const unsigned char *s = (const unsigned char *)sub;
+  size_t n = buflen;
+
+  for ( ; n ; t++, n-- )
+    {
+      if ( toupper (*t) == toupper (*s) )
+        {
+          for ( buf=t++, buflen = n--, s++;
+                n && toupper (*t) == toupper (*s); t++, s++, n-- )
+            ;
+          if (!*s)
+            return (const char*)buf;
+          t = buf;
+          s = (const unsigned char *)sub ;
+          n = buflen;
+       }
+    }
+  return NULL;
+}
+
+
+int
+memicmp (const char *a, const char *b, size_t n)
+{
+  for ( ; n; n--, a++, b++ )
+    if (*a != *b && (toupper (*(const unsigned char*)a)
+                     != toupper(*(const unsigned char*)b)))
+      return *(const unsigned char *)a - *(const unsigned char*)b;
+  return 0;
+}
+
+
 /*
  * Remove leading and trailing white space from STR.  Return STR.
  */
index d77ee59..9ef942f 100644 (file)
    \v, but works for the purposes used here. */
 #define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t')
 
+/* The atoi macros assume that the buffer has only valid digits. */
+#define atoi_1(p)   (*(p) - '0' )
+#define atoi_2(p)   ((atoi_1(p) * 10) + atoi_1((p)+1))
+#define atoi_4(p)   ((atoi_2(p) * 100) + atoi_2((p)+2))
+#define xtoi_1(p)   (*(p) <= '9'? (*(p)- '0'): \
+                     *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
+#define xtoi_2(p)   ((xtoi_1(p) * 16) + xtoi_1((p)+1))
+#define xtoi_4(p)   ((xtoi_2(p) * 256) + xtoi_2((p)+2))
+
 
 /* The default error source of the application.  This is different
    from GPG_ERR_SOURCE_DEFAULT in that it does not depend on the
@@ -103,6 +112,9 @@ char *strconcat (const char *s1, ...) JNLIB_GCC_A_SENTINEL(0);
 
 
 char *has_leading_keyword (const char *string, const char *keyword);
+const char *memstr (const void *buffer, size_t buflen, const char *sub);
+const char *memistr (const void *buffer, size_t buflen, const char *sub);
+int memicmp (const char *a, const char *b, size_t n);
 char *trim_spaces (char *str);
 
 
@@ -131,6 +143,12 @@ int         keyvalue_get_int (keyvalue_t list, const char *key);
 int zb32_index (int c);
 char *zb32_encode (const void *data, unsigned int databits);
 
+/*-- percent.c --*/
+char *percent_plus_escape (const char *string);
+char *percent_plus_unescape (const char *string, int nulrepl);
+char *percent_unescape (const char *string, int nulrepl);
+size_t percent_plus_unescape_inplace (char *string, int nulrepl);
+size_t percent_unescape_inplace (char *string, int nulrepl);
 
 
 #endif /*UTIL_H*/