>From 1907012f700267f5f0c70b2f82684cb02e3dabf8 Mon Sep 17 00:00:00 2001
From: Stef Walter <stefw@redhat.com>
Date: Tue, 7 Jan 2014 15:51:35 +0100
Subject: [PATCH] string: Add ding-string library with safe-printf code

As suggested by Stephen Gallagher, put the safe-printf.c code
into its own library here in the ding project.
---
 Makefile.am               |  18 +++
 configure.ac              |   4 +-
 contrib/ding-libs.spec.in |  46 +++++-
 string/ding-string.pc.in  |  11 ++
 string/safe-printf.c      | 348 ++++++++++++++++++++++++++++++++++++++++++++++
 string/safe-printf.h      |  45 ++++++
 string/safe-printf_ut.c   | 220 +++++++++++++++++++++++++++++
 version.m4                |   1 +
 8 files changed, 690 insertions(+), 3 deletions(-)
 create mode 100644 string/ding-string.pc.in
 create mode 100644 string/safe-printf.c
 create mode 100644 string/safe-printf.h
 create mode 100644 string/safe-printf_ut.c

diff --git a/Makefile.am b/Makefile.am
index 74aff05..e75ef92 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -299,6 +299,24 @@ clean-local-ini_config:
 	rm -f ./foo.conf #From ini_parse_ut
 
 ##############################################################################
+# ding-string
+##############################################################################
+
+lib_LTLIBRARIES += libding-string.la
+dist_pkgconfig_DATA += string/ding-string.pc
+dist_include_HEADERS += string/safe-printf.h
+
+libding_string_la_SOURCES = \
+    string/safe-printf.c
+libding_string_la_LDFLAGS = \
+    -version-info 0:0:0
+
+check_PROGRAMS += safe-printf_ut
+TESTS += safe-printf_ut
+safe_printf_ut_SOURCES = string/safe-printf_ut.c
+safe_printf_ut_LDADD = libding-string.la
+
+##############################################################################
 # Additional rules
 ##############################################################################
 
diff --git a/configure.ac b/configure.ac
index 021cb34..7f41809 100644
--- a/configure.ac
+++ b/configure.ac
@@ -35,6 +35,7 @@ AC_SUBST([COLLECTION_VERSION], COLLECTION_VERSION_NUMBER)
 AC_SUBST([REF_ARRAY_VERSION], REF_ARRAY_VERSION_NUMBER)
 AC_SUBST([BASICOBJECTS_VERSION], BASICOBJECTS_VERSION_NUMBER)
 AC_SUBST([INI_CONFIG_VERSION], INI_CONFIG_VERSION_NUMBER)
+AC_SUBST([DING_STRING_VERSION_NUMBER], DING_STRING_VERSION_NUMBER)
 
 AM_CONDITIONAL([GIT_CHECKOUT], [git log -1 &>/dev/null])
 
@@ -80,5 +81,6 @@ AC_CONFIG_FILES([Makefile contrib/ding-libs.spec
                  refarray/ref_array.cfg.doxy refarray/ref_array.pc
                  basicobjects/basicobjects.cfg.doxy
                  basicobjects/basicobjects.pc
-                 ini/ini_config.cfg.doxy ini/ini_config.pc])
+                 ini/ini_config.cfg.doxy ini/ini_config.pc
+                 string/ding-string.pc])
 AC_OUTPUT
diff --git a/contrib/ding-libs.spec.in b/contrib/ding-libs.spec.in
index 14ff1f2..8155656 100644
--- a/contrib/ding-libs.spec.in
+++ b/contrib/ding-libs.spec.in
@@ -14,6 +14,7 @@ BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)
 %global ref_array_version @REF_ARRAY_VERSION@
 %global basicobjects_version @BASICOBJECTS_VERSION@
 %global ini_config_version @INI_CONFIG_VERSION@
+%global ding_string_version @DING_STRING_VERSION@
 
 ### Patches ###
 
@@ -25,6 +26,7 @@ Requires: libdhash = %{dhash_version}-%{release}
 Requires: libcollection = %{collection_version}-%{release}
 Requires: libref_array = %{ref_array_version}-%{release}
 Requires: libini_config = %{ini_config_version}-%{release}
+Requires: ding-string = %{ding_string_version}-%{release}
 
 ### Build Dependencies ###
 
@@ -36,7 +38,7 @@ BuildRequires: doxygen
 
 %description
 A meta-package that pulls in libcollection, libdhash, libini_config,
-librefarray and libpath_utils.
+librefarray ding-string and libpath_utils.
 
 %package devel
 Summary: Development packages for ding-libs
@@ -50,10 +52,11 @@ Requires: libdhash-devel = %{dhash_version}-%{release}
 Requires: libcollection-devel = %{collection_version}-%{release}
 Requires: libref_array-devel = %{ref_array_version}-%{release}
 Requires: libini_config-devel = %{ini_config_version}-%{release}
+Requires: ding-string-devel = %{ding_string_version}-%{release}
 
 %description devel
 A meta-package that pulls in development libraries for libcollection,
-libdhash, libini_config, librefarray and libpath_utils.
+libdhash, libini_config, librefarray ding-string and libpath_utils.
 
 ##############################################################################
 # Path Utils
@@ -315,6 +318,45 @@ structure
 
 
 ##############################################################################
+# ding-string
+##############################################################################
+
+%package -n ding-string
+Summary: String utility functions
+Group: Development/Libraries
+License: LGPLv2+
+Version: %{ding_string_version}
+
+%description -n ding-string
+String utility functions
+
+%package -n ding-string-devel
+Summary: Development files for ding-string
+Group: Development/Libraries
+License: LGPLv2+
+Version: %{ding_string_version}
+Requires: ding-string = %{ding_string_version}-%{release}
+
+%description -n ding-string-devel
+String utility functions
+
+%post -n ding-string -p /sbin/ldconfig
+%postun -n ding-string -p /sbin/ldconfig
+
+%files -n ding-string
+%defattr(-,root,root,-)
+%doc COPYING.LESSER.V2
+%{_libdir}/libding-string.so.0
+%{_libdir}/libding-string.so.0.0.0
+
+%files -n libding-string-devel
+%defattr(-,root,root,-)
+%{_includedir}/safe-printf.h
+%{_libdir}/libding-string.so
+%{_libdir}/pkgconfig/ding-string.pc
+
+
+##############################################################################
 # Build steps
 ##############################################################################
 
diff --git a/string/ding-string.pc.in b/string/ding-string.pc.in
new file mode 100644
index 0000000..5eed498
--- /dev/null
+++ b/string/ding-string.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: ding-string
+Description: Library contains string utilities
+Version: @BASICOBJECTS_VERSION@
+Libs: -L${libdir} -lding-string
+Cflags: -I${includedir}
+URL: http://fedorahosted.org/sssd/
diff --git a/string/safe-printf.c b/string/safe-printf.c
new file mode 100644
index 0000000..4f6ff4c
--- /dev/null
+++ b/string/safe-printf.c
@@ -0,0 +1,348 @@
+/* realmd -- Realm configuration service
+ *
+ * Copyright 2013 Red Hat Inc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: Stef Walter <stefw@redhat.com>
+ */
+
+#include "config.h"
+
+#include "safe-printf.h"
+
+#include <stdarg.h>
+#include <string.h>
+
+#ifndef MIN
+#define MIN(a, b)  (((a) < (b)) ? (a) : (b))
+#endif
+
+#ifndef MAX
+#define MAX(a, b)  (((a) > (b)) ? (a) : (b))
+#endif
+
+static void
+safe_padding (int count,
+              int *total,
+              void (* callback) (void *, const char *, size_t),
+              void *data)
+{
+    char eight[] = "        ";
+    int num;
+
+    while (count > 0) {
+        num = MIN (count, 8);
+        callback (data, eight, num);
+        count -= num;
+        *total += num;
+    }
+}
+
+static void
+dummy_callback (void *data,
+                const char *piece,
+                size_t len)
+{
+
+}
+
+int
+safe_printf_cb (void (* callback) (void *, const char *, size_t),
+                void *data,
+                const char *format,
+                const char * const args[],
+                int num_args)
+{
+    int at_arg = 0;
+    const char *cp;
+    int precision;
+    int width;
+    int len;
+    const char *value;
+    int total;
+    int left;
+
+    if (!callback)
+        callback = dummy_callback;
+
+    total = 0;
+    cp = format;
+
+    while (*cp) {
+
+        /* Piece of raw string */
+        if (*cp != '%') {
+            len = strcspn (cp, "%");
+            callback (data, cp, len);
+            total += len;
+            cp += len;
+            continue;
+        }
+
+        cp++;
+
+        /* An literal percent sign? */
+        if (*cp == '%') {
+            callback (data, "%", 1);
+            total++;
+            cp++;
+            continue;
+        }
+
+        value = NULL;
+        left = 0;
+        precision = -1;
+        width = -1;
+
+        /* Test for positional argument.  */
+        if (*cp >= '0' && *cp <= '9') {
+            /* Look-ahead parsing, otherwise skipped */
+            if (cp[strspn (cp, "0123456789")] == '$') {
+                unsigned int n = 0;
+                for (; *cp >= '0' && *cp <= '9'; cp++)
+                    n = 10 * n + (*cp - '0');
+                /* Positional argument 0 is invalid. */
+                if (n == 0)
+                    return -1;
+                /* Positional argument N too high */
+                if (n > num_args)
+                    return -1;
+                value = args[n - 1];
+                cp++; /* $ */
+            }
+        }
+
+        /* Read the supported flags. */
+        for (; ; cp++) {
+            if (*cp == '-')
+                left = 1;
+            /* Supported but ignored */
+            else if (*cp != ' ')
+                break;
+        }
+
+        /* Parse the width. */
+        if (*cp >= '0' && *cp <= '9') {
+            width = 0;
+            for (; *cp >= '0' && *cp <= '9'; cp++)
+                width = 10 * width + (*cp - '0');
+        }
+
+        /* Parse the precision. */
+        if (*cp == '.') {
+            precision = 0;
+            for (cp++; *cp >= '0' && *cp <= '9'; cp++)
+                precision = 10 * precision + (*cp - '0');
+        }
+
+        /* Read the conversion character.  */
+        switch (*cp++) {
+        case 's':
+            /* Non-positional argument */
+            if (value == NULL) {
+                /* Too many arguments used */
+                if (at_arg == num_args)
+                    return -1;
+                value = args[at_arg++];
+            }
+            break;
+
+        /* No other conversion characters are supported */
+        default:
+            return -1;
+        }
+
+        /* How many characters are we printing? */
+        len = strlen (value);
+        if (precision >= 0)
+            len = MIN (precision, len);
+
+        /* Do we need padding? */
+        safe_padding (left ? 0 : width - len, &total, callback, data);
+
+        /* The actual data */;
+        callback (data, value, len);
+        total += len;
+
+        /* Do we need padding? */
+        safe_padding (left ? width - len : 0, &total, callback, data);
+    }
+
+    return total;
+}
+
+struct sprintf_ctx {
+    char *data;
+    size_t length;
+    size_t alloc;
+};
+
+static void
+asprintf_callback (void *data,
+                   const char *piece,
+                   size_t length)
+{
+    struct sprintf_ctx *cx = data;
+    void *mem;
+
+    if (!cx->data)
+        return;
+
+    /* Reallocate if necessary */
+    if (cx->length + length + 1 > cx->alloc) {
+        cx->alloc += MAX (length + 1, 1024);
+        mem = realloc (cx->data, cx->alloc);
+        if (mem == NULL) {
+            free (cx->data);
+            cx->data = NULL;
+            return;
+        }
+        cx->data = mem;
+    }
+
+    memcpy (cx->data + cx->length, piece, length);
+    cx->length += length;
+    cx->data[cx->length] = 0;
+}
+
+static const char **
+valist_to_args (va_list va,
+                int *num_args)
+{
+    int alo_args;
+    const char **args;
+    const char *arg;
+    void *mem;
+
+    *num_args = alo_args = 0;
+    args = NULL;
+
+    for (;;) {
+        arg = va_arg (va, const char *);
+        if (arg == NULL)
+            break;
+        if (*num_args == alo_args) {
+            alo_args += 8;
+            mem = realloc (args, sizeof (const char *) * alo_args);
+            if (!mem) {
+                free (args);
+                return NULL;
+            }
+            args = mem;
+        }
+        args[(*num_args)++] = arg;
+    }
+
+    return args;
+}
+
+int
+safe_asprintf (char **strp,
+               const char *format,
+               ...)
+{
+    struct sprintf_ctx cx;
+    const char **args;
+    int num_args;
+    va_list va;
+    int ret;
+    int i;
+
+    va_start (va, format);
+    args = valist_to_args (va, &num_args);
+    va_end (va);
+
+    if (args == NULL)
+        return -1;
+
+    /* Preallocate a pretty good guess */
+    cx.alloc = strlen (format) + 1;
+    for (i = 0; i < num_args; i++)
+        cx.alloc += strlen (args[i]);
+
+    cx.data = malloc (cx.alloc);
+    if (!cx.data) {
+        free (args);
+        return -1;
+    }
+
+    cx.data[0] = '\0';
+    cx.length = 0;
+
+    ret = safe_printf_cb (asprintf_callback, &cx, format, args, num_args);
+    if (cx.data == NULL)
+        ret = -1;
+    if (ret < 0)
+        free (cx.data);
+    else
+        *strp = cx.data;
+
+    free (args);
+    return ret;
+}
+
+static void
+snprintf_callback (void *data,
+                   const char *piece,
+                   size_t length)
+{
+    struct sprintf_ctx *cx = data;
+
+    /* Don't copy if too much data */
+    if (cx->length > cx->alloc)
+        length = 0;
+    else if (cx->length + length > cx->alloc)
+        length = cx->alloc - cx->length;
+    else
+        length = length;
+
+    if (length > 0)
+        memcpy (cx->data + cx->length, piece, length);
+
+    /* Null termination happens later */
+    cx->length += length;
+}
+
+int
+safe_snprintf (char *str,
+               size_t len,
+               const char *format,
+               ...)
+{
+    struct sprintf_ctx cx;
+    int num_args;
+    va_list va;
+    const char **args;
+    int ret;
+
+    cx.data = str;
+    cx.length = 0;
+    cx.alloc = len;
+
+    va_start (va, format);
+    args = valist_to_args (va, &num_args);
+    va_end (va);
+
+    if (args == NULL)
+        return -1;
+
+    if (len)
+        cx.data[0] = '\0';
+
+    ret = safe_printf_cb (snprintf_callback, &cx, format, args, num_args);
+    if (ret < 0)
+        return ret;
+
+    /* Null terminate appropriately */
+    if (len > 0)
+        cx.data[MIN(cx.length, len - 1)] = '\0';
+
+    free (args);
+    return ret;
+}
diff --git a/string/safe-printf.h b/string/safe-printf.h
new file mode 100644
index 0000000..8881b91
--- /dev/null
+++ b/string/safe-printf.h
@@ -0,0 +1,45 @@
+/* realmd -- Realm configuration service
+ *
+ * Copyright 2013 Red Hat Inc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: Stef Walter <stefw@redhat.com>
+ */
+
+#include "config.h"
+
+#ifndef __SAFE_PRINTF_H__
+#define __SAFE_PRINTF_H__
+
+#include <stdlib.h>
+
+#ifndef GNUC_NULL_TERMINATED
+#if __GNUC__ >= 4
+#define GNUC_NULL_TERMINATED __attribute__((__sentinel__))
+#else
+#define GNUC_NULL_TERMINATED
+#endif
+#endif
+
+int        safe_asprintf     (char **strp,
+                              const char *format,
+                              ...) GNUC_NULL_TERMINATED;
+
+int        safe_snprintf     (char *str,
+                              size_t len,
+                              const char *format,
+                              ...) GNUC_NULL_TERMINATED;
+
+int        safe_printf_cb    (void (* callback) (void *data, const char *piece, size_t len),
+                              void *data,
+                              const char *format,
+                              const char * const args[],
+                              int num_args);
+
+#endif /* __SAFE_PRINTF_H__ */
diff --git a/string/safe-printf_ut.c b/string/safe-printf_ut.c
new file mode 100644
index 0000000..38be9fd
--- /dev/null
+++ b/string/safe-printf_ut.c
@@ -0,0 +1,220 @@
+/*
+ * This file originated in realmd
+ *
+ * Copyright 2012 Red Hat Inc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: Stef Walter <stefw@redhat.com>
+ */
+
+#include "config.h"
+
+#include "safe-printf.h"
+
+#include <assert.h>
+#include <string.h>
+
+typedef struct {
+    const char *format;
+    const char *args[8];
+    const char *result;
+} Fixture;
+
+static const Fixture fixtures[] = {
+    {
+      /* Just a bog standard string */
+      "%s", { "blah", NULL, },
+      "blah"
+    },
+    {
+      /* Empty to print */
+      "%s", { "", NULL, },
+      ""
+    },
+    {
+      /* Nothing to print */
+      "", { "blah", NULL, },
+      ""
+    },
+    {
+      /* Width right aligned */
+      "%8s", { "blah", NULL, },
+      "    blah"
+    },
+    {
+      /* Width left aligned */
+      "whoop %-8s doo", { "dee", NULL, },
+      "whoop dee      doo"
+    },
+    {
+      /* Width space aligned (ignored) */
+      "whoop % 8s doo", { "dee", NULL, },
+      "whoop      dee doo"
+    },
+    {
+      /* Width left space aligned (ignored) */
+      "whoop % -8s doo", { "dee", NULL, },
+      "whoop dee      doo"
+    },
+    {
+      /* Precision 1 digit */
+      "whoop %.3s doo", { "deedle-dee", NULL, },
+      "whoop dee doo"
+    },
+    {
+      /* Precision, N digits */
+      "whoop %.10s doo", { "deedle-dee-deedle-do-deedle-dum", NULL, },
+      "whoop deedle-dee doo"
+    },
+    {
+      /* Precision, zero digits */
+      "whoop %.s doo", { "deedle", NULL, },
+      "whoop  doo"
+    },
+    {
+      /* Multiple simple arguments */
+      "space %s %s", { "man", "dances", NULL, },
+      "space man dances"
+    },
+    {
+      /* Literal percent */
+      "100%% of space folk dance", { NULL, },
+      "100% of space folk dance"
+    },
+    {
+      /* Multiple simple arguments */
+      "space %2$s %1$s", { "dances", "man", NULL, },
+      "space man dances"
+    },
+    {
+      /* Skipping an argument (not supported by standard printf) */
+      "space %2$s dances", { "dances", "man", NULL, },
+      "space man dances"
+    },
+
+    /* Failures start here */
+
+    {
+      /* Unsupported conversion */
+      "%x", { "blah", NULL, },
+      NULL
+    },
+    {
+      /* Bad positional argument */
+      "space %55$s dances", { "dances", "man", NULL, },
+      NULL
+    },
+    {
+      /* Zero positional argument */
+      "space %0$s dances", { "dances", "man", NULL, },
+      NULL
+    },
+    {
+      /* Too many args used */
+      "%s %s dances", { "space", NULL, },
+      NULL
+    },
+};
+
+
+static void
+callback (void *data, const char *piece, size_t len)
+{
+    assert (strlen (data) + len < 1024);
+    strncat (data, piece, len);
+}
+
+static void
+test_safe_printf(const Fixture *fixture)
+{
+    char buffer[1024] = { 0, };
+    char *out;
+    int num_args;
+    int ret;
+
+    for (num_args = 0; fixture->args[num_args] != NULL; )
+        num_args++;
+
+    ret = safe_printf_cb(callback, buffer, fixture->format, (const char * const*)fixture->args, num_args);
+    if (fixture->result) {
+        assert(ret >= 0);
+        assert(strcmp (buffer, fixture->result) == 0);
+        assert(ret == strlen(buffer));
+    } else {
+        assert(ret < 0);
+    }
+}
+
+static void
+test_safe_snprintf(void)
+{
+    char buffer[8];
+    int ret;
+
+    ret = safe_snprintf(buffer, 8, "%s", "space", "man", NULL);
+    assert(ret == 5);
+    assert(strcmp(buffer, "space") == 0);
+
+    ret = safe_snprintf(buffer, 8, "", "space", "man", NULL);
+    assert(ret == 0);
+    assert(strcmp(buffer, "") == 0);
+
+    ret = safe_snprintf(buffer, 8, "the %s %s dances away", "space", "man", NULL);
+    assert(ret == 25);
+    assert(strcmp(buffer, "the spa") == 0);
+
+    ret = safe_snprintf(NULL, 0, "the %s %s dances away", "space", "man", NULL);
+    assert(ret == 25);
+
+    ret = safe_snprintf(buffer, 8, "%5$s", NULL);
+    assert(ret < 0);
+}
+
+static void
+test_safe_asprintf(void)
+{
+    char *buffer;
+    int ret;
+
+    ret = safe_asprintf(&buffer, "%s", "space", "man", NULL);
+    assert(ret == 5);
+    assert(strcmp(buffer, "space") == 0);
+    free (buffer);
+
+    ret = safe_asprintf(&buffer, "", "space", "man", NULL);
+    assert(ret == 0);
+    assert(strcmp(buffer, "") == 0);
+    free (buffer);
+
+    ret = safe_asprintf(&buffer, "the %s %s dances away", "space", "man", NULL);
+    assert(ret == 25);
+    assert(strcmp(buffer, "the space man dances away") == 0);
+    free (buffer);
+
+    ret = safe_asprintf(&buffer, "%1$s %1$s %1$s %1$s %1$s %1$s", "space man", NULL);
+    assert(ret == 59);
+    assert(strcmp(buffer, "space man space man space man space man space man space man") == 0);
+    free (buffer);
+
+    ret = safe_asprintf(&buffer, "%5$s", NULL);
+    assert(ret < 0);
+}
+
+int
+main(void)
+{
+    int i;
+
+    for (i = 0; i < (sizeof (fixtures) / sizeof (fixtures[0])); i++)
+        test_safe_printf (fixtures + i);
+    test_safe_snprintf ();
+    test_safe_asprintf ();
+
+    return 0;
+}
diff --git a/version.m4 b/version.m4
index 76b8a0d..6625f16 100644
--- a/version.m4
+++ b/version.m4
@@ -16,3 +16,4 @@ m4_define([COLLECTION_VERSION_NUMBER], [0.6.2])
 m4_define([REF_ARRAY_VERSION_NUMBER], [0.1.3])
 m4_define([BASICOBJECTS_VERSION_NUMBER], [0.1.0])
 m4_define([INI_CONFIG_VERSION_NUMBER], [1.0.0.1])
+m4_define([DING_STRING_VERSION_NUMBER], [0.1])
-- 
1.8.4.2

