The anticipated usage of this is to serialize a string map into the data area of a xapian document --- test/T710-string-map.sh | 312 ++++++++++++++++++++++++++++++++++++++++ util/string-map.c | 42 ++++++ util/string-map.h | 7 + 3 files changed, 361 insertions(+) diff --git a/test/T710-string-map.sh b/test/T710-string-map.sh index b2f65381..9c5c1d8e 100755 --- a/test/T710-string-map.sh +++ b/test/T710-string-map.sh @@ -114,4 +114,316 @@ testval1 EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "serialize empty" +cat c_head - c_tail <<'EOF' | test_C $ +{ + notmuch_string_map_t *map = _notmuch_string_map_create(ctx); + dump_map (map); + printf ("%s",_notmuch_string_map_serialize (ctx, map)); +} +EOF +cat<<EOF > EXPECTED +== stdout == +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "serialize" +cat c_head - c_tail <<'EOF' | test_C $ +{ + notmuch_string_map_t *map = _notmuch_string_map_create(ctx); + _notmuch_string_map_append (map, "testkey1", "testval1"); + _notmuch_string_map_append (map, "testkey2", "testval2"); + _notmuch_string_map_append (map, "testkey1", "testval3"); + dump_map (map); + printf ("%s",_notmuch_string_map_serialize (ctx, map)); +} +EOF +cat<<EOF > EXPECTED +== stdout == +key[0]=testkey1 +val[0]=testval1 +key[1]=testkey1 +val[1]=testval3 +key[2]=testkey2 +val[2]=testval2 +testkey1 +testval1 +testkey1 +testval3 +testkey2 +testval2 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "serialize, key with embedded newline" +cat c_head - c_tail <<'EOF' | test_C $ +{ + notmuch_string_map_t *map = _notmuch_string_map_create(ctx); + _notmuch_string_map_append (map, "testkey1", "testval1"); + _notmuch_string_map_append (map, "testkey2", "testval2"); + _notmuch_string_map_append (map, "testkey1", "testval3"); + _notmuch_string_map_append (map, "testkey2\nreallynot", "testval4"); + dump_map (map); + printf ("%s",_notmuch_string_map_serialize (ctx, map)); +} +EOF +cat<<EOF > EXPECTED +== stdout == +key[0]=testkey1 +val[0]=testval1 +key[1]=testkey1 +val[1]=testval3 +key[2]=testkey2 +val[2]=testval2 +key[3]=testkey2 +reallynot +val[3]=testval4 +testkey1 +testval1 +testkey1 +testval3 +testkey2 +testval2 +testkey2\nreallynot +testval4 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "serialize, key with embedded backslash" +cat c_head - c_tail <<'EOF' | test_C $ +{ + notmuch_string_map_t *map = _notmuch_string_map_create(ctx); + _notmuch_string_map_append (map, "testkey1", "testval1"); + _notmuch_string_map_append (map, "testkey2", "testval2"); + _notmuch_string_map_append (map, "testkey1", "testval3"); + _notmuch_string_map_append (map, "testkey2\\not", "testval4"); + dump_map (map); + printf ("%s",_notmuch_string_map_serialize (ctx, map)); +} +EOF +cat<<'EOF' > EXPECTED +== stdout == +key[0]=testkey1 +val[0]=testval1 +key[1]=testkey1 +val[1]=testval3 +key[2]=testkey2 +val[2]=testval2 +key[3]=testkey2\not +val[3]=testval4 +testkey1 +testval1 +testkey1 +testval3 +testkey2 +testval2 +testkey2\\not +testval4 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "serialize, value with embedded newline" +cat c_head - c_tail <<'EOF' | test_C $ +{ + notmuch_string_map_t *map = _notmuch_string_map_create(ctx); + _notmuch_string_map_append (map, "testkey1", "testval1"); + _notmuch_string_map_append (map, "testkey2", "testval2"); + _notmuch_string_map_append (map, "testkey1", "testval3"); + _notmuch_string_map_append (map, "testkey2", "testval4\nvalue continues"); + dump_map (map); + printf ("%s",_notmuch_string_map_serialize (ctx, map)); +} +EOF +cat<<EOF > EXPECTED +== stdout == +key[0]=testkey1 +val[0]=testval1 +key[1]=testkey1 +val[1]=testval3 +key[2]=testkey2 +val[2]=testval2 +key[3]=testkey2 +val[3]=testval4 +value continues +testkey1 +testval1 +testkey1 +testval3 +testkey2 +testval2 +testkey2 +testval4\nvalue continues +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "serialize, key and value with embedded newline" +cat c_head - c_tail <<'EOF' | test_C $ +{ + notmuch_string_map_t *map = _notmuch_string_map_create(ctx); + _notmuch_string_map_append (map, "testkey1", "testval1"); + _notmuch_string_map_append (map, "testkey2", "testval2"); + _notmuch_string_map_append (map, "testkey1\nkey continues", "testval3\nvalue continues"); + dump_map (map); + printf ("%s",_notmuch_string_map_serialize (ctx, map)); +} +EOF +cat<<EOF > EXPECTED +== stdout == +key[0]=testkey1 +val[0]=testval1 +key[1]=testkey1 +key continues +val[1]=testval3 +value continues +key[2]=testkey2 +val[2]=testval2 +testkey1 +testval1 +testkey1\nkey continues +testval3\nvalue continues +testkey2 +testval2 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "serialize, key and value with embedded literal \n" +cat c_head - c_tail <<'EOF' | test_C $ +{ + notmuch_string_map_t *map = _notmuch_string_map_create(ctx); + _notmuch_string_map_append (map, "testkey1", "testval1"); + _notmuch_string_map_append (map, "testkey2", "testval2"); + _notmuch_string_map_append (map, "testkey1\\nkey continues", "testval3\\nvalue continues"); + dump_map (map); + printf ("%s",_notmuch_string_map_serialize (ctx, map)); +} +EOF +cat<<'EOF' > EXPECTED +== stdout == +key[0]=testkey1 +val[0]=testval1 +key[1]=testkey1\nkey continues +val[1]=testval3\nvalue continues +key[2]=testkey2 +val[2]=testval2 +testkey1 +testval1 +testkey1\\nkey continues +testval3\\nvalue continues +testkey2 +testval2 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "deserialize empty string" +cat c_head - c_tail <<'EOF' | test_C $ +{ + notmuch_string_map_t *map = _notmuch_string_map_deserialize (ctx, ""); + dump_map (map); +} +EOF +cat<<EOF > EXPECTED +== stdout == +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "deserialize" +cat c_head - c_tail <<'EOF' | test_C $ +{ + notmuch_string_map_t *map = _notmuch_string_map_deserialize (ctx, + "testkey1\n" + "testval1\n" + "testkey1\n" + "testval3\n" + "testkey2\n" + "testval2\n"); + dump_map (map); +} +EOF +cat<<EOF > EXPECTED +== stdout == +key[0]=testkey1 +val[0]=testval1 +key[1]=testkey1 +val[1]=testval3 +key[2]=testkey2 +val[2]=testval2 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "deserialize, key and value with embedded newline" +cat c_head - c_tail <<'EOF' | test_C $ +{ + notmuch_string_map_t *map = _notmuch_string_map_deserialize (ctx, + "testkey1\n" + "testval1\n" + "testkey1\\nkey continues\n" + "testval3\\nvalue continues\n" + "testkey2\n" + "testval2\n"); + dump_map (map); +} +EOF +cat<<EOF > EXPECTED +== stdout == +key[0]=testkey1 +val[0]=testval1 +key[1]=testkey1 +key continues +val[1]=testval3 +value continues +key[2]=testkey2 +val[2]=testval2 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "deserialize, keys and values with embedded backslashes" +cat c_head - c_tail <<'EOF' | test_C $ +{ + const char * str = + "testkey1\n" + "testval1\\b\n" + "testkey1\n" + "testval3\\\n" + "testkey2\\\\not\n" + "testval2\n" + "testkey2\\toto\n" + "testval4\n"; + + notmuch_string_map_t *map = _notmuch_string_map_deserialize (ctx, str); + fputs(str, stdout); + dump_map (map); +} +EOF +cat<<'EOF' > EXPECTED +== stdout == +testkey1 +testval1\b +testkey1 +testval3\ +testkey2\\not +testval2 +testkey2\toto +testval4 +key[0]=testkey1 +val[0]=testval1\b +key[1]=testkey1 +val[1]=testval3\ +key[2]=testkey2\not +val[2]=testval2 +key[3]=testkey2\toto +val[3]=testval4 +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done diff --git a/util/string-map.c b/util/string-map.c index ad818207..ab0c42ab 100644 --- a/util/string-map.c +++ b/util/string-map.c @@ -226,3 +226,45 @@ _notmuch_string_map_iterator_destroy (notmuch_string_map_iterator_t *iterator) { talloc_free (iterator); } + +static const char * +_append_escaped (void *ctx, const char *dest, const char *str) { + /* At worst we escape everything */ + char *buf = talloc_zero_size (ctx, 2 * strlen(str) + 1); + char *ret; + int j = 0; + for (const char *cur = str; *cur; cur++){ + if (*cur == '\n') { + buf[j++] = '\\'; + buf[j++] = 'n'; + } else if (*cur == '\\' && *(cur+1)== 'n') { + buf[j++] = '\\'; + buf[j++] = '\\'; + } else { + buf[j++] = *cur; + } + } + /* this NUL is already there, but better safe than sorry */ + buf[j] = '\0'; + ret = talloc_asprintf (ctx, "%s%s", dest, buf); + talloc_free (buf); + return ret; +} + +const char * +_notmuch_string_map_serialize (void* ctx, notmuch_string_map_t *map) +{ + const char *ret; + + _notmuch_string_map_sort (map); + + ret=talloc_strdup(ctx, ""); + for (size_t i=0; i < map->length; i++) { + ret = _append_escaped (ctx, ret, map->pairs[i].key); + ret = talloc_asprintf(ctx, "%s\n", ret); + ret = _append_escaped (ctx, ret, map->pairs[i].value); + ret = talloc_asprintf(ctx, "%s\n", ret); + } + + return ret; +} diff --git a/util/string-map.h b/util/string-map.h index 42d16da4..9baf3530 100644 --- a/util/string-map.h +++ b/util/string-map.h @@ -33,4 +33,11 @@ _notmuch_string_map_iterator_value (notmuch_string_map_iterator_t *iterator); void _notmuch_string_map_iterator_destroy (notmuch_string_map_iterator_t *iterator); + +/* + * encode map as newline delimited string "key1\nval1\key2\nval2\n..." + * newlines will be escaped as "\n", and "\n" will be escaped as "\\n". + */ +const char * +_notmuch_string_map_serialize (void *ctx, notmuch_string_map_t *map); #endif -- 2.17.1 _______________________________________________ notmuch mailing list notmuch@notmuchmail.org https://notmuchmail.org/mailman/listinfo/notmuch