/* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 2016-2021 Free Software Foundation, Inc. This library 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 3 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ /* String expander */ #ifdef HAVE_CONFIG_H # include #endif #include enum segm_type { segm_copy, /* reference to a fragment of the source string */ segm_repl /* replacement */ }; struct string_segment { enum segm_type type; /* Segment type */ size_t beg; /* Beginning of the source string fragmen */ size_t end; /* End of it */ char *repl; /* Replacement, if type == segm_repl */ }; struct stringbuf { char const *string; /* Source string */ size_t length; /* Length of the source */ size_t pos; /* Offset of the current character */ mu_list_t seglist; /* List of segments */ jmp_buf errbuf; /* Return location on failure */ char *expansion; /* Expanded string */ char *endptr; /* Used when assembling expansion */ mu_i_sv_interp_t interp; void *data; }; static struct string_segment * segment_alloc (struct stringbuf *buf, size_t beg, enum segm_type type) { int rc; struct string_segment *segm; segm = malloc (sizeof *segm); if (!segm) longjmp (buf->errbuf, ENOMEM); segm->type = type; segm->beg = beg; segm->end = buf->pos - 1; rc = mu_list_append (buf->seglist, segm); if (rc) longjmp (buf->errbuf, rc); return segm; } static void segm_free (void *data) { struct string_segment *segm = data; if (segm->type == segm_repl) free (segm->repl); free (segm); } static void string_next_fragment (struct stringbuf *buf) { size_t beg; struct string_segment *segm; char *exp; beg = buf->pos; while (buf->pos < buf->length) { if (buf->string[buf->pos] == '$' && buf->pos + 1 < buf->length && buf->string[buf->pos + 1] == '{') break; buf->pos++; } segm = segment_alloc (buf, beg, segm_copy); if (buf->pos == buf->length) return; beg = buf->pos; buf->pos += 2; /* Look for closing brace */ while (buf->pos < buf->length) { if (buf->string[buf->pos] == '$' && buf->pos + 1 < buf->length && buf->string[buf->pos + 1] == '{') { /* Found nested reference. Update verbatim segment and restart */ segm->end = buf->pos - 1; beg = buf->pos; buf->pos++; } else if (buf->string[buf->pos] == '}') break; buf->pos++; } if (buf->pos == buf->length) { /* No references found */ segm->end = buf->pos - 1; return; } if (buf->interp (buf->string + beg + 2, buf->pos - beg - 2, &exp, buf->data) == 0) { segm = segment_alloc (buf, beg, segm_repl); segm->repl = exp; } else segm->end = buf->pos; buf->pos++; } struct segm_stat { size_t end; size_t len; }; static int update_len (void *item, void *data) { struct string_segment *segm = item; struct segm_stat *st = data; switch (segm->type) { case segm_copy: if (segm->beg == st->end) st->end = segm->end; st->len += segm->end - segm->beg + 1; break; case segm_repl: if (segm->repl) st->len += strlen (segm->repl); break; } return 0; } static int append_segm (void *item, void *data) { struct string_segment *segm = item; struct stringbuf *buf = data; size_t len; switch (segm->type) { case segm_copy: len = segm->end - segm->beg + 1; memcpy (buf->endptr, buf->string + segm->beg, len); break; case segm_repl: if (segm->repl) { len = strlen (segm->repl); memcpy (buf->endptr, segm->repl, len); } else len = 0; } buf->endptr += len; return 0; } static void string_split (struct stringbuf *buf) { while (buf->pos < buf->length) string_next_fragment (buf); } static int string_assemble (struct stringbuf *buf) { int rc; struct segm_stat st; st.len = 0; st.end = 0; rc = mu_list_foreach (buf->seglist, update_len, &st); if (rc) longjmp (buf->errbuf, rc); if (st.end == buf->length - 1) return MU_ERR_CANCELED; buf->expansion = malloc (st.len + 1); if (!buf->expansion) longjmp (buf->errbuf, ENOMEM); buf->endptr = buf->expansion; rc = mu_list_foreach (buf->seglist, append_segm, buf); if (rc) { free (buf->expansion); buf->expansion = NULL; longjmp (buf->errbuf, rc); } *buf->endptr = 0; return 0; } int mu_i_sv_string_expand (char const *input, mu_i_sv_interp_t interp, void *data, char **ret) { struct stringbuf sb; int rc; sb.string = input; sb.length = strlen (input); sb.pos = 0; rc = mu_list_create (&sb.seglist); if (rc) return rc; mu_list_set_destroy_item (sb.seglist, segm_free); sb.expansion = NULL; sb.endptr = NULL; sb.interp = interp; sb.data = data; rc = setjmp (sb.errbuf); if (rc == 0) { string_split (&sb); rc = string_assemble (&sb); if (rc == 0) *ret = sb.expansion; } mu_list_destroy (&sb.seglist); return rc; }