New tool ppsepaqr
authorWerner Koch <wk@gnupg.org>
Tue, 30 May 2017 16:08:08 +0000 (18:08 +0200)
committerWerner Koch <wk@gnupg.org>
Tue, 30 May 2017 16:08:08 +0000 (18:08 +0200)
* configure.ac: Cehck for libqrencode.
* src/ppsepaqr.c: New.
* src/Makefile.am (bin_PROGRAMS): Add ppsepaqr

Signed-off-by: Werner Koch <wk@gnupg.org>
configure.ac
src/Makefile.am
src/ppsepaqr.c [new file with mode: 0644]

index 51cc9a8..12b2844 100644 (file)
@@ -221,6 +221,26 @@ fi
 
 
 #
+# QRENCODE
+#
+PKG_CHECK_MODULES([QRENCODE],[libqrencode >= 3.0],
+                             [have_qrencode=yes],
+                             [have_qrencode=no])
+if test "$have_qrencode" = "yes"; then
+  AC_SUBST([QRENCODE_CFLAGS])
+  AC_SUBST([QRENCODE_LIBS])
+  :
+else
+  tmp=$(echo "$QRENCODE_PKG_ERRORS" | tr '\n' '\v' | sed 's/\v/\n*** /g')
+  AC_MSG_WARN([[
+***
+*** Libqrencode not found
+***
+*** $tmp]])
+fi
+
+
+#
 # GPGME
 #
 AM_PATH_GPGME("$NEED_GPGME_API:$NEED_GPGME_VERSION",
@@ -461,6 +481,13 @@ if test "$have_sqlite3" = "no"; then
 *** You need SQLite3 to build this program.
 ***]])
 fi
+if test "$have_qrencode" = "no"; then
+   die=yes
+   AC_MSG_NOTICE([[
+***
+*** You need Libqrencode to build this program.
+***]])
+fi
 if test "$have_gpgme" = "no"; then
    die=yes
    AC_MSG_NOTICE([[
index c02e2a7..07a8ba5 100644 (file)
@@ -18,7 +18,8 @@
 
 EXTRA_DIST = cJSON.readme tls-ca.pem
 
-bin_PROGRAMS = payprocd payproc-jrnl payproc-stat payproc-post ppipnhd
+bin_PROGRAMS = payprocd payproc-jrnl payproc-stat payproc-post ppipnhd \
+              ppsepaqr
 noinst_PROGRAMS = $(module_tests) t-http
 noinst_LIBRARIES = libcommon.a libcommonpth.a
 dist_pkglibexec_SCRIPTS = geteuroxref
@@ -87,6 +88,10 @@ ppipnhd_SOURCES = ppipnhd.c
 ppipnhd_CFLAGS =
 ppipnhd_LDADD =
 
+ppsepaqr_SOURCES = ppsepaqr.c
+ppsepaqr_CFLAGS = $(QRENCODE_CFLAGS) $(GPG_ERROR_CFLAGS)
+ppsepaqr_LDADD = $(QRENCODE_LIBS) -lm libcommon.a $(GPG_ERROR_LIBS)
+
 module_tests = t-util t-preorder t-encrypt
 
 AM_CFLAGS = $(GPG_ERROR_CFLAGS)
diff --git a/src/ppsepaqr.c b/src/ppsepaqr.c
new file mode 100644 (file)
index 0000000..91e0b1c
--- /dev/null
@@ -0,0 +1,293 @@
+/* ppsepaqr - Create a SEPA QR code
+ * Copyright (C) 2017 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 <assert.h>
+#include <ctype.h>
+#include <qrencode.h>
+
+#include "util.h"
+#include "logging.h"
+#include "argparse.h"
+
+/* Constants to identify the options. */
+enum opt_values
+  {
+    aNull = 0,
+    oVerbose   = 'v',
+
+    oDebug = 500,
+
+    oLast
+  };
+
+
+/* The list of commands and options. */
+static ARGPARSE_OPTS opts[] = {
+  ARGPARSE_group (301, "@\nOptions:\n "),
+
+  ARGPARSE_s_n (oVerbose,"verbose",  "verbose diagnostics"),
+
+  ARGPARSE_end ()
+};
+
+
+/* Gobal options.  */
+static struct
+{
+  int verbose;
+} opt;
+
+
+
+\f
+/* Local prototypes.  */
+static char *format_data (const char *iban, const char *name,
+                          const char *amount, const char *text);
+static void encode (const char *text, estream_t fp);
+
+
+
+\f
+static const char *
+my_strusage( int level )
+{
+  const char *p;
+
+  switch (level)
+    {
+    case 11: p = "ppsepaqr"; break;
+    case 13: p = PACKAGE_VERSION; break;
+    case 19: p = "Please report bugs to https://bugs.gnupg.org.\n"; break;
+    case 1:
+    case 40: p =
+        "Usage: ppsepaqr [options] IBAN NAME AMOUNT TEXT  (-h for help)";
+      break;
+    case 41: p =
+        "Syntax: ppsepaqr [options] IBAN NAME AMOUNT TEXT\n"
+        "Print an SVG with the QR code for a SEPA transaction\n";
+      break;
+    default: p = NULL; break;
+    }
+  return p;
+}
+
+
+int
+main (int argc, char **argv)
+{
+  ARGPARSE_ARGS pargs;
+  char *data;
+
+  /* Set program name etc.  */
+  set_strusage (my_strusage);
+  log_set_prefix ("ppsepaqr", JNLIB_LOG_WITH_PREFIX);
+
+  /* Make sure that our subsystems are ready.  */
+  gpgrt_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 oVerbose:  opt.verbose++; break;
+
+        default: pargs.err = ARGPARSE_PRINT_ERROR; break;
+       }
+    }
+
+  if (log_get_errorcount (0))
+    exit (2);
+
+  if (argc != 4)
+    usage (1);
+
+  data = format_data (argv[0], argv[1], argv[2], argv[3]);
+  if (data)
+    {
+      encode (data, es_stdout);
+      if (es_ferror (es_stdout) || es_fflush (es_stdout))
+        log_error ("error writing to stdout: %s\n",
+                   gpg_strerror (gpg_error_from_syserror ()));
+
+      xfree (data);
+    }
+
+  return !!log_get_errorcount (0);
+}
+
+
+/* Create a string with the data accroding to "EPC069-12v2.1 Quick
+ * Response Code - Guidelines to Enable the Data Capture for the
+ * Initiation of a SCT".  Returns a malloced string or NULL on
+ * error. In the error case a diagnositc has been printed.  */
+static char *
+format_data (const char *iban, const char *name, const char *amount,
+             const char *text)
+{
+  char *string;
+  int anyerr = 0;
+  unsigned int cents;
+  char *xamount;
+  char *xtext;
+  char *p;
+
+  /* Check various things.  */
+  if (strlen (iban) < 8 /* arbitrary value - what is the right one?*/
+      || strlen (iban) > 34
+      || strchr (iban, '\n') || strchr (iban, '\r'))
+    {
+      log_error ("invalid IBAN given");
+      anyerr++;
+    }
+
+  if (!*name || strlen (name) > 70
+      || strchr (name, '\n') || strchr (name, '\r'))
+    {
+      log_error ("invalid or too long NAME given\n");
+      anyerr++;
+    }
+
+  cents = convert_amount (amount, 2);
+  if (!cents)
+    {
+      log_error ("invalid AMOUNT given\n");
+      anyerr++;
+    }
+  xamount = reconvert_amount (cents, 2);
+  if (!xamount)
+    {
+      log_error ("error formatting amount: %s\n",
+                 gpg_strerror (gpg_error_from_syserror ()));
+      return NULL;
+    }
+
+  xtext = xstrdup (text);
+  for (p=xtext; *p; p++)
+    if (*p == '\n' || *p == '\r')
+      *p = ' ';
+  trim_spaces (xtext);
+  if (!*xtext)
+    {
+      log_error ("empty TEXT is not allowed\n");
+      anyerr++;
+    }
+  if (strlen (xtext) > 140)
+    {
+      /* FIXME: This may invalidate UTF-8 chars.  */
+      xtext[140] = 0;
+      log_info ("TEXT truncated to 140 octets\n");
+    }
+
+  if (anyerr)
+    string = NULL;
+  else
+    {
+      string = strconcat ("BCD\n"
+                          "002\n"
+                          "1\n"
+                          "SCT\n"
+                          "\n",  /* BIC is not anymore used.  */
+                          name, "\n",
+                          iban, "\n",
+                          "EUR", xamount, "\n",
+                          "\n"         /* Purpose */
+                          "\n",        /* Remittance info (structured) */
+                          xtext, "\n", /* Remittance info (unstructured) */
+                          "",           /* Information.  */
+                          NULL );
+      if (!string)
+        log_error ("strconcat failed: %s\n",
+                   gpg_strerror (gpg_error_from_syserror ()));
+    }
+
+  es_free (xamount);
+  xfree (xtext);
+  return string;
+}
+
+
+/* Encode to QR and render as SVG to FP  */
+static void
+encode (const char *text, estream_t fp)
+{
+  QRcode *code;
+  unsigned int symwidth;
+  float realwidth;
+  const unsigned char *row;
+  unsigned int x, y;
+
+  code = QRcode_encodeString (text, 0, QR_ECLEVEL_M, QR_MODE_8, 1);
+  if (!code)
+    {
+      log_error ("QR encoding failed: %s\n",
+                 gpg_strerror (gpg_error_from_syserror ()));
+      return;
+    }
+
+#define SVG_MARGIN 4
+#define SVG_PIXELS 3
+#define SVG_DPI 72
+
+  symwidth = code->width + SVG_MARGIN * 2;
+  realwidth = (symwidth * SVG_PIXELS) / (SVG_DPI * (100.0/2.54) / 100.0);
+
+  es_fprintf (fp,
+              "<svg width=\"%0.2fcm\" height=\"%0.2fcm\""
+              " viewBox=\"0 0 %d %d\"\n"
+              "     preserveAspectRatio=\"none\" version=\"1.1\"\n"
+              "     xmlns=\"http://www.w3.org/2000/svg\">\n"
+              "  <g id=\"QRcode\">\n"
+              "    <rect x=\"0\" y=\"0\" width=\"%d\" height=\"%d\""
+              " fill=\"#ffffff\"/>\n"
+              "    <g id=\"Pattern\">\n",
+              realwidth, realwidth,
+              symwidth,  symwidth,
+              symwidth,  symwidth );
+
+  for (y = 0; y < code->width; y++)
+    {
+      row = code->data + (y * code->width);
+      for (x = 0; x < code->width; x++)
+        {
+          if ((row[x] & 1))
+            es_fprintf (fp,
+                        "      <rect x=\"%d\" y=\"%d\""
+                        " width=\"1\" height=\"1\""
+                        " fill=\"#000000\"/>\n",
+                        SVG_MARGIN + x, SVG_MARGIN + y);
+       }
+    }
+
+  es_fputs ("    </g>\n"
+            "  </g>\n"
+            "</svg>\n",
+            fp);
+
+  QRcode_free (code);
+}