]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
liblib: Added API for easily building hash strings based on given format string and...
authorTimo Sirainen <tss@iki.fi>
Tue, 19 Oct 2010 17:10:34 +0000 (18:10 +0100)
committerTimo Sirainen <tss@iki.fi>
Tue, 19 Oct 2010 17:10:34 +0000 (18:10 +0100)
src/lib/Makefile.am
src/lib/hash-format.c [new file with mode: 0644]
src/lib/hash-format.h [new file with mode: 0644]
src/lib/test-hash-format.c [new file with mode: 0644]
src/lib/test-lib.c
src/lib/test-lib.h

index 69700e51223ade95d7c51c3587beee056bc3f667..15967f1890e185448f50ab238ee44a47822f7309 100644 (file)
@@ -36,6 +36,7 @@ liblib_la_SOURCES = \
        file-lock.c \
        file-set-size.c \
        hash.c \
+       hash-format.c \
        hash-method.c \
        hash2.c \
        hex-binary.c \
@@ -146,6 +147,7 @@ headers = \
        file-set-size.h \
        fsync-mode.h \
        hash.h \
+       hash-format.h \
        hash-method.h \
        hash2.h \
        hex-binary.h \
@@ -231,6 +233,7 @@ test_lib_SOURCES = \
        test-bsearch-insert-pos.c \
        test-buffer.c \
        test-crc32.c \
+       test-hash-format.c \
        test-hex-binary.c \
        test-istream-concat.c \
        test-istream-crlf.c \
diff --git a/src/lib/hash-format.c b/src/lib/hash-format.c
new file mode 100644 (file)
index 0000000..2b4c8a0
--- /dev/null
@@ -0,0 +1,228 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "base64.h"
+#include "hex-binary.h"
+#include "str.h"
+#include "hash-method.h"
+#include "hash-format.h"
+
+enum hash_encoding {
+       HASH_ENCODING_HEX,
+       HASH_ENCODING_HEX_SHORT,
+       HASH_ENCODING_BASE64
+};
+
+struct hash_format_list {
+       struct hash_format_list *next;
+
+       const struct hash_method *method;
+       void *context;
+       unsigned int bits;
+       enum hash_encoding encoding;
+};
+
+struct hash_format {
+       pool_t pool;
+       const char *str;
+
+       struct hash_format_list *list, **pos;
+};
+
+static int
+hash_format_parse(const char *str, unsigned int *idxp,
+                 const struct hash_method **method_r,
+                 unsigned int *bits_r, const char **error_r)
+{
+       const char *name, *end, *bitsp;
+       unsigned int bits, i = *idxp;
+
+       /* we should have "hash_name}" or "hash_name:bits}" */
+       end = strchr(str+i, '}');
+       if (end == NULL) {
+               *error_r = "Missing '}'";
+               return -1;
+       }
+       *idxp = end - str;
+       name = t_strdup_until(str+i, end);
+
+       bitsp = strchr(name, ':');
+       if (bitsp != NULL)
+               name = t_strdup_until(name, bitsp++);
+
+       *method_r = hash_method_lookup(name);
+       if (*method_r == NULL) {
+               *error_r = t_strconcat("Unknown hash method: ", name, NULL);
+               return -1;
+       }
+
+       bits = (*method_r)->digest_size * 8;
+       if (bitsp != NULL) {
+               if (str_to_uint(bitsp, &bits) < 0 ||
+                   bits == 0 || bits > (*method_r)->digest_size*8) {
+                       *error_r = t_strconcat("Invalid :bits number: ",
+                                              bitsp, NULL);
+                       return -1;
+               }
+               if ((bits % 8) != 0) {
+                       *error_r = t_strconcat(
+                               "Currently :bits must be divisible by 8: ",
+                               bitsp, NULL);
+                       return -1;
+               }
+       }
+       *bits_r = bits;
+       return 0;
+}
+
+static int
+hash_format_string_analyze(struct hash_format *format, const char *str,
+                          const char **error_r)
+{
+       struct hash_format_list *list;
+       unsigned int i;
+
+       for (i = 0; str[i] != '\0'; i++) {
+               if (str[i] != '%')
+                       continue;
+               i++;
+
+               list = p_new(format->pool, struct hash_format_list, 1);
+               list->encoding = HASH_ENCODING_HEX;
+               *format->pos = list;
+               format->pos = &list->next;
+
+               if (str[i] == 'B') {
+                       list->encoding = HASH_ENCODING_BASE64;
+                       i++;
+               } else if (str[i] == 'X') {
+                       list->encoding = HASH_ENCODING_HEX_SHORT;
+                       i++;
+               }
+               if (str[i++] != '{') {
+                       *error_r = "No '{' after '%'";
+                       return -1;
+               }
+               if (hash_format_parse(str, &i, &list->method,
+                                     &list->bits, error_r) < 0)
+                       return -1;
+               list->context = p_malloc(format->pool,
+                                        list->method->context_size);
+               list->method->init(list->context);
+       }
+       return 0;
+}
+
+int hash_format_init(const char *format_string, struct hash_format **format_r,
+                    const char **error_r)
+{
+       struct hash_format *format;
+       pool_t pool;
+       int ret;
+
+       pool = pool_alloconly_create("hash format", 1024);
+       format = p_new(pool, struct hash_format, 1);
+       format->pool = pool;
+       format->str = p_strdup(pool, format_string);
+       format->pos = &format->list;
+       T_BEGIN {
+               ret = hash_format_string_analyze(format, format_string,
+                                                error_r);
+               if (ret < 0)
+                       *error_r = p_strdup(format->pool, *error_r);
+       } T_END;
+       if (ret < 0) {
+               *error_r = t_strdup(*error_r);
+               return -1;
+       }
+       *format_r = format;
+       return 0;
+}
+
+void hash_format_loop(struct hash_format *format,
+                     const void *data, size_t size)
+{
+       struct hash_format_list *list;
+
+       for (list = format->list; list != NULL; list = list->next)
+               list->method->loop(list->context, data, size);
+}
+
+static void
+hash_format_digest(string_t *dest, const struct hash_format_list *list,
+                  const unsigned char *digest)
+{
+       unsigned int i, orig_len, size = list->bits / 8;
+       
+       i_assert(list->bits % 8 == 0);
+
+       switch (list->encoding) {
+       case HASH_ENCODING_HEX:
+               binary_to_hex_append(dest, digest, size);
+               break;
+       case HASH_ENCODING_HEX_SHORT:
+               orig_len = str_len(dest);
+               binary_to_hex_append(dest, digest, size);
+               /* drop leading zeros, except if it's the only one */
+               for (i = orig_len; i < str_len(dest); i++) {
+                       if (str_data(dest)[i] != '0')
+                               break;
+               }
+               if (i == str_len(dest)) i--;
+               str_delete(dest, orig_len, i-orig_len);
+               break;
+       case HASH_ENCODING_BASE64:
+               orig_len = str_len(dest);
+               base64_encode(digest, size, dest);
+               /* drop trailing '=' chars */
+               while (str_len(dest) > orig_len &&
+                      str_data(dest)[str_len(dest)-1] == '=')
+                       str_truncate(dest, str_len(dest)-1);
+               break;
+       }
+}
+
+void hash_format_deinit(struct hash_format **_format, string_t *dest)
+{
+       struct hash_format *format = *_format;
+       struct hash_format_list *list;
+       const char *p;
+       unsigned char *digest;
+       unsigned int i, max_digest_size = 0;
+
+       *_format = NULL;
+
+       for (list = format->list; list != NULL; list = list->next) {
+               if (max_digest_size < list->method->digest_size)
+                       max_digest_size = list->method->digest_size;
+       }
+       digest = p_malloc(format->pool, max_digest_size);
+
+       list = format->list;
+       for (i = 0; format->str[i] != '\0'; i++) {
+               if (format->str[i] != '%') {
+                       str_append_c(dest, format->str[i]);
+                       continue;
+               }
+
+               /* we already verified that the string is ok */
+               i_assert(list != NULL);
+               list->method->result(list->context, digest);
+               hash_format_digest(dest, list, digest);
+               list = list->next;
+
+               p = strchr(format->str+i, '}');
+               i_assert(p != NULL);
+               i = p - format->str;
+       }
+
+       pool_unref(&format->pool);
+}
+
+void hash_format_deinit_free(struct hash_format **_format)
+{
+       struct hash_format *format = *_format;
+
+       *_format = NULL;
+       pool_unref(&format->pool);
+}
diff --git a/src/lib/hash-format.h b/src/lib/hash-format.h
new file mode 100644 (file)
index 0000000..a0a0d88
--- /dev/null
@@ -0,0 +1,19 @@
+#ifndef HASH_FORMAT_H
+#define HASH_FORMAT_H
+
+struct hash_format;
+
+/* Initialize formatting hash. Format can contain text with %{sha1} style
+   variables. Each hash hash can be also truncated by specifying the number
+   of bits to truncate to, such as %{sha1:80}. */
+int hash_format_init(const char *format_string, struct hash_format **format_r,
+                    const char **error_r);
+/* Add more data to hash. */
+void hash_format_loop(struct hash_format *format,
+                     const void *data, size_t size);
+/* Write the hash into given string and free used memory. */
+void hash_format_deinit(struct hash_format **format, string_t *dest);
+/* Free used memory without writing to string. */
+void hash_format_deinit_free(struct hash_format **format);
+
+#endif
diff --git a/src/lib/test-hash-format.c b/src/lib/test-hash-format.c
new file mode 100644 (file)
index 0000000..1169e49
--- /dev/null
@@ -0,0 +1,54 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "str.h"
+#include "hash-format.h"
+
+struct hash_format_test {
+       const char *input;
+       const char *output;
+};
+
+void test_hash_format(void)
+{
+       static const char *fail_input[] = {
+               "%",
+               "%A{sha1}",
+               "%{sha1",
+               "%{sha1:8",
+               "%{sha1:8a}",
+               "%{sha1:0}",
+               "%{sha1:168}",
+               NULL
+       };
+       static struct hash_format_test tests[] = {
+               { "%{sha1}", "8843d7f92416211de9ebb963ff4ce28125932878" },
+               { "*%{sha1}*", "*8843d7f92416211de9ebb963ff4ce28125932878*" },
+               { "*%{sha1:8}*", "*88*" },
+               { "%{sha1:152}", "8843d7f92416211de9ebb963ff4ce281259328" },
+               { "%X{size}", "6" },
+               { "%{sha256:80}", "c3ab8ff13720e8ad9047" },
+               { "%{sha512:80}", "0a50261ebd1a390fed2b" },
+               { "%{md4}", "547aefd231dcbaac398625718336f143" },
+               { "%{md5}", "3858f62230ac3c915f300c664312c63f" },
+               { "%{sha256:80}-%X{size}", "c3ab8ff13720e8ad9047-6" }
+       };
+       struct hash_format *format;
+       string_t *str = t_str_new(128);
+       const char *error;
+       unsigned int i;
+
+       test_begin("hash_format");
+       for (i = 0; fail_input[i] != NULL; i++)
+               test_assert(hash_format_init(fail_input[i], &format, &error) < 0);
+
+       for (i = 0; i < N_ELEMENTS(tests); i++) {
+               test_assert(hash_format_init(tests[i].input, &format, &error) == 0);
+               hash_format_loop(format, "foo", 3);
+               hash_format_loop(format, "bar", 3);
+               str_truncate(str, 0);
+               hash_format_deinit(&format, str);
+               test_assert(strcmp(str_c(str), tests[i].output) == 0);
+       }
+       test_end();
+}
index 91b113b658360e50dc8cd22899298d40fc6e741b..c76ba712e1fb5235819572f41ad5ce54ff87cf69 100644 (file)
@@ -11,6 +11,7 @@ int main(void)
                test_bsearch_insert_pos,
                test_buffer,
                test_crc32,
+               test_hash_format,
                test_hex_binary,
                test_istream_concat,
                test_istream_crlf,
index 43fdc282f184d41d73e76dafef568ab0f5363ec8..068c57c15e7eef2da0b6ee002e63352230942f02 100644 (file)
@@ -10,6 +10,7 @@ void test_base64(void);
 void test_bsearch_insert_pos(void);
 void test_buffer(void);
 void test_crc32(void);
+void test_hash_format(void);
 void test_hex_binary(void);
 void test_istream_concat(void);
 void test_istream_crlf(void);