[exim-cvs] More abstraction of the gstring API

Inizio della pagina
Delete this message
Reply to this message
Autore: Exim Git Commits Mailing List
Data:  
To: exim-cvs
Oggetto: [exim-cvs] More abstraction of the gstring API
Gitweb: https://git.exim.org/exim.git/commitdiff/63deec8a3ba77fcabf405d9c30fdd65a8b909526
Commit:     63deec8a3ba77fcabf405d9c30fdd65a8b909526
Parent:     6d179d5ef22fcdfc4802db3fbb37f88190186367
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Sun Feb 5 16:04:14 2023 +0000
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Sun Feb 5 21:30:14 2023 +0000


    More abstraction of the gstring API
---
 src/src/acl.c           |   2 +-
 src/src/arc.c           |  26 ++++-----
 src/src/deliver.c       |  44 ++++++++-------
 src/src/dkim.c          |   6 +-
 src/src/exim.c          |  12 ++--
 src/src/expand.c        | 143 ++++++++++++++++++++++++++----------------------
 src/src/functions.h     |  53 ++++++++++++++++++
 src/src/imap_utf7.c     |   4 +-
 src/src/log.c           |  20 ++++---
 src/src/lookups/dnsdb.c |  30 ++++------
 src/src/lookups/ldap.c  |  18 +++---
 src/src/mime.c          |   2 +-
 src/src/parse.c         |  12 ++--
 src/src/pdkim/pdkim.c   |   5 +-
 src/src/readconf.c      |   2 +-
 src/src/receive.c       |   5 +-
 src/src/rfc2047.c       |  27 ++++-----
 src/src/sieve.c         |  31 ++++-------
 src/src/smtp_in.c       |  38 +++++++------
 src/src/tls-gnu.c       |   2 +-
 20 files changed, 267 insertions(+), 215 deletions(-)


diff --git a/src/src/acl.c b/src/src/acl.c
index 5ab674776..17d6c68da 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -1092,7 +1092,7 @@ for (header_line * h = acl_added_headers; h; h = h->next)
g = string_append_listele_n(g, '\n', h->text, i);
}

-return g ? g->s : NULL;
+return string_from_gstring(g);
}


diff --git a/src/src/arc.c b/src/src/arc.c
index 30a66320e..ef44672f8 100644
--- a/src/src/arc.c
+++ b/src/src/arc.c
@@ -261,8 +261,7 @@ while ((c = *s))
         if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
           g = string_catn(g, s, 1);
       if (!g) return US"no b= value";
-      al->b.data = string_from_gstring(g);
-      al->b.len = g->ptr;
+      al->b.len = len_string_from_gstring(g, &al->b.data);
       gstring_release_unused(g);
       bend = s;
       break;
@@ -278,8 +277,7 @@ while ((c = *s))
         if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
           g = string_catn(g, s, 1);
       if (!g) return US"no bh= value";
-      al->bh.data = string_from_gstring(g);
-      al->bh.len = g->ptr;
+      al->bh.len = len_string_from_gstring(g, &al->bh.data);
       gstring_release_unused(g);
       break;
     default:
@@ -1308,7 +1306,7 @@ header_line * h = (header_line *)(al+1);


/* Construct the to-be-signed AMS pseudo-header: everything but the sig. */

-ams_off = g->ptr;
+ams_off = gstring_length(g);
 g = string_fmt_append(g, "%s i=%d; a=rsa-sha256; c=relaxed; d=%s; s=%s",
       ARC_HDR_AMS, instance, identity, selector);    /*XXX hardwired a= */
 if (options & ARC_SIGN_OPT_TSTAMP)
@@ -1352,7 +1350,7 @@ for(col = 3; rheaders; rheaders = rheaders->prev)


/* Lose the last colon from the h= list */

-if (g->s[g->ptr - 1] == ':') g->ptr--;
+gstring_trim_trailing(g, ':');

g = string_catn(g, US";\r\n\tb=;", 7);

@@ -1370,7 +1368,7 @@ if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AMS"))
/* Lose the trailing semicolon from the psuedo-header, and append the signature
(folded over lines) and termination to complete it. */

-g->ptr--;
+gstring_trim(g, 1);
g = arc_sign_append_sig(g, &sig);

h->slen = g->ptr - ams_off;
@@ -1548,7 +1546,7 @@ into the copies.
static const uschar *
arc_header_sign_feed(gstring * g)
{
-uschar * s = string_copyn(g->s, g->ptr);
+uschar * s = string_copy_from_gstring(g);
headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr);
return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE);
}
@@ -1772,10 +1770,9 @@ if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS";
DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n");
/* Parse the AMS header */

-h.next = NULL;
-h.slen = g->size;
-h.text = g->s;
 memset(&al, 0, sizeof(arc_line));
+h.next = NULL;
+h.slen = len_string_from_gstring(g, &h.text);
 if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, FALSE)))
   {
   DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr);
@@ -1857,7 +1854,8 @@ for (as = arc_verify_ctx.arcset_chain, inst = 1; as; as = as->next, inst++)
   else
     g = string_catn(g, US":", 1);
   }
-return g ? g->s : US"";
+if (!g) return US"";
+return string_from_gstring(g);
 }



@@ -1870,7 +1868,7 @@ if (arc_state)
   {
   arc_line * highest_ams;
   int start = 0;        /* Compiler quietening */
-  DEBUG(D_acl) start = g->ptr;
+  DEBUG(D_acl) start = gstring_length(g);


   g = string_append(g, 2, US";\n\tarc=", arc_state);
   if (arc_received_instance > 0)
@@ -1890,7 +1888,7 @@ if (arc_state)
   else if (arc_state_reason)
     g = string_append(g, 3, US" (", arc_state_reason, US")");
   DEBUG(D_acl) debug_printf("ARC:  authres '%.*s'\n",
-          g->ptr - start - 3, g->s + start + 3);
+          gstring_length(g) - start - 3, g->s + start + 3);
   }
 else
   DEBUG(D_acl) debug_printf("ARC:  no authres\n");
diff --git a/src/src/deliver.c b/src/src/deliver.c
index ca31df587..084a048c9 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -1045,7 +1045,7 @@ splitting is done; in those cases use the original field. */
 else
   {
   uschar * cmp;
-  int off = g->ptr;    /* start of the "full address" */
+  int off = gstring_length(g);    /* start of the "full address" */


   if (addr->local_part)
     {
@@ -1338,23 +1338,25 @@ if (LOGGING(deliver_time))
 if (addr->message)
   g = string_append(g, 2, US": ", addr->message);


-(void) string_from_gstring(g);
+ {
+ const uschar * s = string_from_gstring(g);

-/* Log the deferment in the message log, but don't clutter it
-up with retry-time defers after the first delivery attempt. */
+ /* Log the deferment in the message log, but don't clutter it
+ up with retry-time defers after the first delivery attempt. */

-if (f.deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
-  deliver_msglog("%s %s\n", now, g->s);
+  if (f.deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
+    deliver_msglog("%s %s\n", now, s);


-/* Write the main log and reset the store.
-For errors of the type "retry time not reached" (also remotes skipped
-on queue run), logging is controlled by L_retry_defer. Note that this kind
-of error number is negative, and all the retry ones are less than any
-others. */
+ /* Write the main log and reset the store.
+ For errors of the type "retry time not reached" (also remotes skipped
+ on queue run), logging is controlled by L_retry_defer. Note that this kind
+ of error number is negative, and all the retry ones are less than any
+ others. */


-log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags,
-  "== %s", g->s);
+  log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags,
+    "== %s", s);
+ }


store_reset(reset_point);
return;
@@ -1417,17 +1419,19 @@ if (addr->message)
if (LOGGING(deliver_time))
g = string_append(g, 2, US" DT=", string_timediff(&addr->delivery_time));

-(void) string_from_gstring(g);
-
/* Do the logging. For the message log, "routing failed" for those cases,
just to make it clearer. */

-if (driver_kind)
-  deliver_msglog("%s %s failed for %s\n", now, driver_kind, g->s);
-else
-  deliver_msglog("%s %s\n", now, g->s);
+ {
+  const uschar * s = string_from_gstring(g);
+
+  if (driver_kind)
+    deliver_msglog("%s %s failed for %s\n", now, driver_kind, s);
+  else
+    deliver_msglog("%s %s\n", now, s);


-log_write(0, LOG_MAIN, "** %s", g->s);
+ log_write(0, LOG_MAIN, "** %s", s);
+ }

 store_reset(reset_point);
 return;
diff --git a/src/src/dkim.c b/src/src/dkim.c
index 0a8ab6fb3..4c19f752f 100644
--- a/src/src/dkim.c
+++ b/src/src/dkim.c
@@ -83,7 +83,7 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
       return string_from_gstring(g);
       }


-    g->ptr = 0;        /* overwrite previous record */
+    gstring_reset(g);        /* overwrite previous record */
     }


 bad:
@@ -822,7 +822,7 @@ authres_dkim(gstring * g)
 {
 int start = 0;        /* compiler quietening */


-DEBUG(D_acl) start = g->ptr;
+DEBUG(D_acl) start = gstring_length(g);

for (pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
{
@@ -884,7 +884,7 @@ for (pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
}

 DEBUG(D_acl)
-  if (g->ptr == start)
+  if (gstring_length(g) == start)
     debug_printf("DKIM: no authres\n");
   else
     debug_printf("DKIM: authres '%.*s'\n", g->ptr - start - 3, g->s + start + 3);
diff --git a/src/src/exim.c b/src/src/exim.c
index c5de167c6..dcc71ea45 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -203,15 +203,16 @@ Returns:   nothing
 */


void
-set_process_info(const char *format, ...)
+set_process_info(const char * format, ...)
{
gstring gs = { .size = PROCESS_INFO_SIZE - 2, .ptr = 0, .s = process_info };
gstring * g;
int len;
+uschar * s;
va_list ap;

g = string_fmt_append(&gs, "%5d ", (int)getpid());
-len = g->ptr;
+len = gstring_length(g);
va_start(ap, format);
if (!string_vformat(g, 0, format, ap))
{
@@ -219,8 +220,7 @@ if (!string_vformat(g, 0, format, ap))
g = string_cat(&gs, US"**** string overflowed buffer ****");
}
g = string_catn(g, US"\n", 1);
-string_from_gstring(g);
-process_info_len = g->ptr;
+process_info_len = len_string_from_gstring(g, &s);
DEBUG(D_process_info) debug_printf("set_process_info: %s", process_info);
va_end(ap);
}
@@ -1501,10 +1501,10 @@ for (int i = 0;; i++)
#endif

   /* g can only be NULL if ss==p */
-  if (ss == p || g->s[g->ptr-1] != '\\') /* not continuation; done */
+  if (ss == p || gstring_last_char(g) != '\\') /* not continuation; done */
     break;


-  --g->ptr;                /* drop the \ */
+  gstring_trim(g, 1);                /* drop the \ */
   }


 if (had_input) return g ? string_from_gstring(g) : US"";
diff --git a/src/src/expand.c b/src/src/expand.c
index a7e6e4fb3..1daf10044 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -1667,12 +1667,13 @@ Returns:        NULL if the header does not exist, else a pointer to a new
 */


static uschar *
-find_header(uschar *name, int *newsize, unsigned flags, const uschar *charset)
+find_header(uschar * name, int * newsize, unsigned flags, const uschar * charset)
{
BOOL found = !name;
int len = name ? Ustrlen(name) : 0;
BOOL comma = FALSE;
gstring * g = NULL;
+uschar * rawhdr;

for (header_line * h = header_list; h; h = h->next)
if (h->type != htype_old && h->text) /* NULL => Received: placeholder */
@@ -1735,8 +1736,9 @@ if (!g) return US"";
/* That's all we do for raw header expansion. */

*newsize = g->size;
+rawhdr = string_from_gstring(g);
if (flags & FH_WANT_RAW)
- return string_from_gstring(g);
+ return rawhdr;

/* Otherwise do RFC 2047 decoding, translating the charset if requested.
The rfc2047_decode2() function can return an error with decoded data if the
@@ -1744,12 +1746,12 @@ charset translation fails. If decoding fails, it returns NULL. */

 else
   {
-  uschar * error, * decoded = rfc2047_decode2(string_from_gstring(g),
+  uschar * error, * decoded = rfc2047_decode2(rawhdr,
     check_rfc2047_length, charset, '?', NULL, newsize, &error);
   if (error)
     DEBUG(D_any) debug_printf("*** error in RFC 2047 decoding: %s\n"
-      "    input was: %s\n", error, g->s);
-  return decoded ? decoded : string_from_gstring(g);
+      "    input was: %s\n", error, rawhdr);
+  return decoded ? decoded : rawhdr;
   }
 }


@@ -1814,7 +1816,7 @@ for (int i = 0; i < recipients_count; i++)
s = recipients_list[i].address;
g = string_append2_listele_n(g, US", ", s, Ustrlen(s));
}
-return g ? g->s : NULL;
+return string_from_gstring(g);
}


@@ -4804,7 +4806,7 @@ while (*s)
   skipping, but "break" otherwise so we get debug output for the item
   expansion. */
   {
-  int start = gstring_length(yield);
+  int expansion_start = gstring_length(yield);
   switch(item_type)
     {
     /* Call an ACL from an expansion.  We feed data in via $acl_arg1 - $acl_arg9.
@@ -4868,7 +4870,7 @@ while (*s)


       yield = string_append(yield, 3,
             US"Authentication-Results: ", sub_arg[0], US"; none");
-      yield->ptr -= 6;
+      yield->ptr -= 6;            /* ignore tha ": none" for now */


       yield = authres_local(yield, sub_arg[0]);
       yield = authres_iprev(yield);
@@ -5785,7 +5787,7 @@ while (*s)


       if (o2m >= 0) for (; oldptr < yield->ptr; oldptr++)
         {
-        uschar *m = Ustrrchr(sub[1], yield->s[oldptr]);
+        uschar * m = Ustrrchr(sub[1], yield->s[oldptr]);
         if (m)
           {
           int o = m - sub[1];
@@ -7107,7 +7109,7 @@ while (*s)
     it was for good reason */


     if (quoted) yield = string_catn(yield, US"\"", 1);
-    yield = string_catn(yield, g->s, g->ptr);
+    yield = gstring_append(yield, g);
     if (quoted) yield = string_catn(yield, US"\"", 1);


     /* @$original_domain */
@@ -7126,10 +7128,11 @@ while (*s)
     }    /* EITEM_* switch */
     /*NOTREACHED*/


-  DEBUG(D_expand)
-    if (yield && (start > 0 || *s))    /* only if not the sole expansion of the line */
+  DEBUG(D_expand)        /* only if not the sole expansion of the line */
+    if (yield && (expansion_start > 0 || *s))
       debug_expansion_interim(US"item-res",
-                  yield->s + start, yield->ptr - start, !!(flags & ESI_SKIPPING));
+      yield->s + expansion_start, yield->ptr - expansion_start,
+      !!(flags & ESI_SKIPPING));
   continue;


 NOT_ITEM: ;
@@ -7219,11 +7222,11 @@ NOT_ITEM: ;
       {
       case EOP_BASE32:
     {
-    uschar *t;
+    uschar * t;
     unsigned long int n = Ustrtoul(sub, &t, 10);
     gstring * g = NULL;


-    if (*t != 0)
+    if (*t)
       {
       expand_string_message = string_sprintf("argument for base32 "
         "operator is \"%s\", which is not a decimal number", sub);
@@ -7832,16 +7835,19 @@ NOT_ITEM: ;


     case EOP_UTF8CLEAN:
       {
-      int seq_len = 0, index = 0;
-      int bytes_left = 0;
+      int seq_len = 0, index = 0, bytes_left = 0, complete;
       long codepoint = -1;
-      int complete;
       uschar seq_buff[4];            /* accumulate utf-8 here */


       /* Manually track tainting, as we deal in individual chars below */


-      if (!yield->s || !yield->ptr)
+      if (!yield)
+        yield = string_get_tainted(Ustrlen(sub), sub);
+      else if (!yield->s || !yield->ptr)
+        {
         yield->s = store_get(yield->size = Ustrlen(sub), sub);
+        gstring_reset(yield);
+        }
       else if (is_incompatible(yield->s, sub))
         gstring_rebuffer(yield, sub);


@@ -7967,7 +7973,7 @@ NOT_ITEM: ;
         goto EXPAND_FAILED;
         }
       yield = string_cat(yield, s);
-      DEBUG(D_expand) debug_printf_indent("yield: '%s'\n", yield->s);
+      DEBUG(D_expand) debug_printf_indent("yield: '%s'\n", string_from_gstring(yield));
       break;
       }


@@ -8276,7 +8282,8 @@ NOT_ITEM: ;

        DEBUG(D_expand)
     {
-    const uschar * s = yield->s + expansion_start;
+    const uschar * res = string_from_gstring(yield);
+    const uschar * s = res + expansion_start;
     int i = gstring_length(yield) - expansion_start;
     BOOL tainted = is_tainted(s);


@@ -8286,7 +8293,7 @@ NOT_ITEM: ;
       if (tainted)
         {
         debug_printf_indent("%s     \\__", flags & ESI_SKIPPING ? "|     " : "      ");
-        debug_print_taint(yield->s);
+        debug_print_taint(res);
         }
       }
     else
@@ -8299,7 +8306,7 @@ NOT_ITEM: ;
         debug_printf_indent("%s",
           flags & ESI_SKIPPING
           ? UTF8_VERT "             " : "           " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ);
-        debug_print_taint(yield->s);
+        debug_print_taint(res);
         }
       }
     }
@@ -8374,58 +8381,62 @@ if (flags & ESI_BRACE_ENDS && !*s)
 added to the string. If so, set up an empty string. Add a terminating zero. If
 left != NULL, return a pointer to the terminator. */


-if (!yield)
- yield = string_get(1);
-(void) string_from_gstring(yield);
-if (left) *left = s;
+ {
+ uschar * res;

-/* Any stacking store that was used above the final string is no longer needed.
-In many cases the final string will be the first one that was got and so there
-will be optimal store usage. */
+  if (!yield)
+    yield = string_get(1);
+  res = string_from_gstring(yield);
+  if (left) *left = s;


-if (resetok) gstring_release_unused(yield);
-else if (resetok_p) *resetok_p = FALSE;
+ /* Any stacking store that was used above the final string is no longer needed.
+ In many cases the final string will be the first one that was got and so there
+ will be optimal store usage. */

-DEBUG(D_expand)
-  {
-  BOOL tainted = is_tainted(yield->s);
-  DEBUG(D_noutf8)
+  if (resetok) gstring_release_unused(yield);
+  else if (resetok_p) *resetok_p = FALSE;
+
+  DEBUG(D_expand)
     {
-    debug_printf_indent("|--expanding: %.*s\n", (int)(s - string), string);
-    debug_printf_indent("%sresult: %s\n",
-      flags & ESI_SKIPPING ? "|-----" : "\\_____", yield->s);
-    if (tainted)
+    BOOL tainted = is_tainted(res);
+    DEBUG(D_noutf8)
       {
-      debug_printf_indent("%s     \\__", flags & ESI_SKIPPING ? "|     " : "      ");
-      debug_print_taint(yield->s);
+      debug_printf_indent("|--expanding: %.*s\n", (int)(s - string), string);
+      debug_printf_indent("%sresult: %s\n",
+    flags & ESI_SKIPPING ? "|-----" : "\\_____", res);
+      if (tainted)
+    {
+    debug_printf_indent("%s     \\__", flags & ESI_SKIPPING ? "|     " : "      ");
+    debug_print_taint(res);
+    }
+      if (flags & ESI_SKIPPING)
+    debug_printf_indent("\\___skipping: result is not used\n");
       }
-    if (flags & ESI_SKIPPING)
-      debug_printf_indent("\\___skipping: result is not used\n");
-    }
-  else
-    {
-    debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
-      "expanding: %.*s\n",
-      (int)(s - string), string);
-    debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-      "result: %s\n",
-      flags & ESI_SKIPPING ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
-      yield->s);
-    if (tainted)
+    else
       {
-      debug_printf_indent("%s",
-    flags & ESI_SKIPPING
-    ? UTF8_VERT "             " : "           " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ);
-      debug_print_taint(yield->s);
+      debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
+    "expanding: %.*s\n",
+    (int)(s - string), string);
+      debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+    "result: %s\n",
+    flags & ESI_SKIPPING ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
+    res);
+      if (tainted)
+    {
+    debug_printf_indent("%s",
+      flags & ESI_SKIPPING
+      ? UTF8_VERT "             " : "           " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ);
+    debug_print_taint(res);
+    }
+      if (flags & ESI_SKIPPING)
+    debug_printf_indent(UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+      "skipping: result is not used\n");
       }
-    if (flags & ESI_SKIPPING)
-      debug_printf_indent(UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-    "skipping: result is not used\n");
     }
-  }
-if (textonly_p) *textonly_p = textonly;
-expand_level--;
-return yield->s;
+  if (textonly_p) *textonly_p = textonly;
+  expand_level--;
+  return res;
+ }


/* This is the failure exit: easiest to program with a goto. We still need
to update the pointer to the terminator, for cases of nested calls with "fail".
diff --git a/src/src/functions.h b/src/src/functions.h
index 1817144ea..961db2dc0 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -963,12 +963,58 @@ g->s[g->ptr] = '\0';
return g->s;
}

+static inline int
+len_string_from_gstring(gstring * g, uschar ** sp)
+{
+if (g)
+ {
+ *sp = g->s;
+ g->s[g->ptr] = '\0';
+ return g->ptr;
+ }
+else
+ {
+ *sp = NULL;
+ return 0;
+ }
+}
+
+static inline uschar *
+string_copy_from_gstring(gstring * g)
+{
+return g ? string_copyn(g->s, g->ptr) : NULL;
+}
+
static inline unsigned
gstring_length(const gstring * g)
{
return g ? (unsigned)g->ptr : 0;
}

+static inline uschar
+gstring_last_char(gstring * g)
+{
+return g->s[g->ptr-1];
+}
+
+static inline void
+gstring_trim(gstring * g, unsigned amount)
+{
+g->ptr -= amount;
+}
+
+static inline void
+gstring_trim_trailing(gstring * g, uschar c)
+{
+if (gstring_last_char(g) == c) gstring_trim(g, 1);
+}
+
+static inline void
+gstring_reset(gstring * g)
+{
+g->ptr = 0;
+}
+

 #define gstring_release_unused(g) \
     gstring_release_unused_trc(g, __FUNCTION__, __LINE__)
@@ -1014,6 +1060,13 @@ memcpy(s, g->s, g->ptr);
 g->s = s;
 }


+/* Append one gstring to another */
+static inline gstring *
+gstring_append(gstring * dest, gstring * item)
+{
+return string_catn(dest, item->s, item->ptr);
+}
+

# ifndef COMPILE_UTILITY
/******************************************************************************/
diff --git a/src/src/imap_utf7.c b/src/src/imap_utf7.c
index 1c09db621..6c9b5c179 100644
--- a/src/src/imap_utf7.c
+++ b/src/src/imap_utf7.c
@@ -200,9 +200,7 @@ iconv_close(icd);
#endif

yield = string_catn(yield, outbuf, outptr - outbuf);
-
-if (yield->s[yield->ptr-1] == '.')
- yield->ptr--;
+gstring_trim_trailing(yield, '.');

return string_from_gstring(yield);
}
diff --git a/src/src/log.c b/src/src/log.c
index d11b933f9..08ece6158 100644
--- a/src/src/log.c
+++ b/src/src/log.c
@@ -684,11 +684,11 @@ Returns:
length actually written, persisting an errno from write()
*/
ssize_t
-write_to_fd_buf(int fd, const uschar *buf, size_t length)
+write_to_fd_buf(int fd, const uschar * buf, size_t length)
{
ssize_t wrote;
size_t total_written = 0;
-const uschar *p = buf;
+const uschar * p = buf;
size_t left = length;

while (1)
@@ -711,6 +711,12 @@ while (1)
return total_written;
}

+static inline ssize_t
+write_gstring_to_fd_buf(int fd, const gstring * g)
+{
+return write_to_fd_buf(fd, g->s, g->ptr);
+}
+


static void
@@ -1113,7 +1119,7 @@ if ( flags & LOG_MAIN

     /* Failing to write to the log is disastrous */


-    written_len = write_to_fd_buf(mainlogfd, g->s, g->ptr);
+    written_len = write_gstring_to_fd_buf(mainlogfd, g);
     if (written_len != g->ptr)
       {
       log_write_failed(US"main log", g->ptr, written_len);
@@ -1172,8 +1178,8 @@ if (flags & LOG_REJECT)
     g = g2;
       else        /* Buffer is full; truncate */
         {
-        g->ptr -= 100;        /* For message and separator */
-        if (g->s[g->ptr-1] == '\n') g->ptr--;
+    gstring_trim(g, 100);        /* For message and separator */
+    gstring_trim_trailing(g, '\n');
         g = string_cat(g, US"\n*** truncated ***\n");
         break;
         }
@@ -1228,7 +1234,7 @@ if (flags & LOG_REJECT)
       if (fstat(rejectlogfd, &statbuf) >= 0) rejectlog_inode = statbuf.st_ino;
       }


-    written_len = write_to_fd_buf(rejectlogfd, g->s, g->ptr);
+    written_len = write_gstring_to_fd_buf(rejectlogfd, g);
     if (written_len != g->ptr)
       {
       log_write_failed(US"reject log", g->ptr, written_len);
@@ -1263,7 +1269,7 @@ if (flags & LOG_PANIC)
     if (panic_save_buffer)
       (void) write(paniclogfd, panic_save_buffer, Ustrlen(panic_save_buffer));


-    written_len = write_to_fd_buf(paniclogfd, g->s, g->ptr);
+    written_len = write_gstring_to_fd_buf(paniclogfd, g);
     if (written_len != g->ptr)
       {
       int save_errno = errno;
diff --git a/src/src/lookups/dnsdb.c b/src/src/lookups/dnsdb.c
index 5482cd9d1..1563eda56 100644
--- a/src/src/lookups/dnsdb.c
+++ b/src/src/lookups/dnsdb.c
@@ -136,15 +136,12 @@ dnsdb_find(void * handle, const uschar * filename, const uschar * keystring,
 {
 int rc;
 int sep = 0;
-int defer_mode = PASS;
-int dnssec_mode = PASS;
-int save_retrans = dns_retrans;
-int save_retry =   dns_retry;
+int defer_mode = PASS, dnssec_mode = PASS;
+int save_retrans = dns_retrans, save_retry =   dns_retry;
 int type;
 int failrc = FAIL;
-const uschar *outsep = CUS"\n";
-const uschar *outsep2 = NULL;
-uschar *equals, *domain, *found;
+const uschar * outsep = CUS"\n", * outsep2 = NULL;
+uschar * equals, * domain, * found;


 dns_answer * dnsa = store_get_dns_answer();
 dns_scan dnss;
@@ -385,10 +382,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
       if (type == T_A || type == T_AAAA || type == T_ADDRESSES)
         {
         for (dns_address * da = dns_address_from_rr(dnsa, rr); da; da = da->next)
-          {
-          if (yield->ptr) yield = string_catn(yield, outsep, 1);
-          yield = string_cat(yield, da->address);
-          }
+      yield = string_append_listele(yield, *outsep, da->address);
         continue;
         }


@@ -399,21 +393,17 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))

       if (type == T_TXT || type == T_SPF)
         {
-        if (outsep2 == NULL)    /* output only the first item of data */
+        if (!outsep2)            /* output only the first item of data */
           yield = string_catn(yield, US (rr->data+1), (rr->data)[0]);
         else
-          {
-          /* output all items */
-          int data_offset = 0;
-          while (data_offset < rr->size)
+          for (unsigned data_offset = 0; data_offset < rr->size; )
             {
-            uschar chunk_len = (rr->data)[data_offset++];
-            if (outsep2[0] != '\0' && data_offset != 1)
+            uschar chunk_len = (rr->data)[data_offset];
+            if (*outsep2  && data_offset != 0)
               yield = string_catn(yield, outsep2, 1);
-            yield = string_catn(yield, US ((rr->data)+data_offset), chunk_len);
+            yield = string_catn(yield, US ((rr->data) + ++data_offset), chunk_len);
             data_offset += chunk_len;
             }
-          }
         }
       else if (type == T_TLSA)
         {
diff --git a/src/src/lookups/ldap.c b/src/src/lookups/ldap.c
index b2ad3bbbc..82d6954ff 100644
--- a/src/src/lookups/ldap.c
+++ b/src/src/lookups/ldap.c
@@ -326,17 +326,19 @@ if (!lcp)
     g = string_catn(NULL, ldap_url, init_ptr - ldap_url);
     g = string_fmt_append(g, "//%s:%d/", shost, port);
     }
-  string_from_gstring(g);


   /* Call ldap_initialize() and check the result */
+   {
+    const uschar * s = string_from_gstring(g);


-  DEBUG(D_lookup) debug_printf_indent("ldap_initialize with URL %s\n", g->s);
-  if ((rc = ldap_initialize(&ld, CS g->s)) != LDAP_SUCCESS)
-    {
-    *errmsg = string_sprintf("ldap_initialize: (error %d) URL \"%s\"\n",
-      rc, g->s);
-    goto RETURN_ERROR;
-    }
+    DEBUG(D_lookup) debug_printf_indent("ldap_initialize with URL %s\n", s);
+    if ((rc = ldap_initialize(&ld, CS s)) != LDAP_SUCCESS)
+      {
+      *errmsg = string_sprintf("ldap_initialize: (error %d) URL \"%s\"\n",
+    rc, s);
+      goto RETURN_ERROR;
+      }
+   }
   store_reset(reset_point);   /* Might as well save memory when we can */



diff --git a/src/src/mime.c b/src/src/mime.c
index 7c3a33d62..d4c26540a 100644
--- a/src/src/mime.c
+++ b/src/src/mime.c
@@ -755,7 +755,7 @@ while(1)
     int result = 0;


     /* must find first free sequential filename */
-    for (gstring * g = string_get(64); result != -1; g->ptr = 0)
+    for (gstring * g = string_get(64); result != -1; gstring_reset(g))
       {
       struct stat mystat;
       g = string_fmt_append(g,
diff --git a/src/src/parse.c b/src/src/parse.c
index 53d660869..ead8751ae 100644
--- a/src/src/parse.c
+++ b/src/src/parse.c
@@ -875,26 +875,26 @@ Returns:       pointer to the original string, if no quoting needed, or
 */


const uschar *
-parse_quote_2047(const uschar *string, int len, const uschar *charset,
+parse_quote_2047(const uschar * string, int len, const uschar * charset,
BOOL fold)
{
const uschar * s = string;
-int hlen, l;
+int hlen, line_off;
BOOL coded = FALSE;
BOOL first_byte = FALSE;
gstring * g =
- string_fmt_append(NULL, "=?%s?Q?", charset ? charset : US"iso-8859-1");
+ string_fmt_append(NULL, "=?%s?Q?%n", charset ? charset : US"iso-8859-1", &hlen);

-hlen = l = g->ptr;
+line_off = hlen;

for (s = string; len > 0; s++, len--)
{
int ch = *s;

-  if (g->ptr - l > 67 && !first_byte)
+  if (g->ptr - line_off > 67 && !first_byte)
     {
     g = fold ? string_catn(g, US"?=\n ", 4) : string_catn(g, US"?= ", 3);
-    l = g->ptr;
+    line_off = g->ptr;
     g = string_catn(g, g->s, hlen);
     }


diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c
index eb26b3864..c8f180a58 100644
--- a/src/src/pdkim/pdkim.c
+++ b/src/src/pdkim/pdkim.c
@@ -957,9 +957,8 @@ return;
 static int
 pdkim_header_complete(pdkim_ctx * ctx)
 {
-if ( (ctx->cur_header->ptr > 1) &&
-     (ctx->cur_header->s[ctx->cur_header->ptr-1] == '\r') )
-  --ctx->cur_header->ptr;
+if (ctx->cur_header->ptr > 1)
+  gstring_trim_trailing(ctx->cur_header, '\r');
 (void) string_from_gstring(ctx->cur_header);


 #ifdef EXPERIMENTAL_ARC
diff --git a/src/src/readconf.c b/src/src/readconf.c
index 48b648bb2..6dba11ca1 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -3219,7 +3219,7 @@ if (config_file)
     g = string_cat(NULL, buf);


     /* If the dir does not end with a "/", append one */
-    if (g->s[g->ptr-1] != '/')
+    if (gstring_last_char(g) != '/')
       g = string_catn(g, US"/", 1);


     /* If the config file contains a "/", extract the directory part */
diff --git a/src/src/receive.c b/src/src/receive.c
index 9bf834aaf..77665d89f 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -4214,7 +4214,8 @@ if (message_logs && !blackholed_by)
       }
     else
       {
-      uschar *now = tod_stamp(tod_log);
+      uschar * now = tod_stamp(tod_log);
+      /* Drop the initial "<= " */
       fprintf(message_log, "%s Received from %s\n", now, g->s+3);
       if (f.deliver_freeze) fprintf(message_log, "%s frozen by %s\n", now,
         frozen_by);
@@ -4266,7 +4267,7 @@ if (  smtp_input && sender_host_address && !f.sender_host_notsocket


       /* Re-use the log line workspace */


-      g->ptr = 0;
+      gstring_reset(g);
       g = string_cat(g, US"SMTP connection lost after final dot");
       g = add_host_info_for_log(g);
       log_write(0, LOG_MAIN, "%s", string_from_gstring(g));
diff --git a/src/src/rfc2047.c b/src/src/rfc2047.c
index c40518a5d..d5e33b9b1 100644
--- a/src/src/rfc2047.c
+++ b/src/src/rfc2047.c
@@ -192,9 +192,9 @@ rfc2047_decode2(uschar *string, BOOL lencheck, const uschar *target,
 {
 int size = Ustrlen(string);
 size_t dlen;
-uschar *dptr;
-gstring *yield;
-uschar *mimeword, *q1, *q2, *endword;
+uschar * dptr;
+gstring * yield;
+uschar * mimeword, * q1, * q2, * endword;


*error = NULL;
mimeword = decode_mimeword(string, lencheck, &q1, &q2, &endword, &dlen, &dptr);
@@ -210,17 +210,14 @@ building the result as we go. The result may be longer than the input if it is
translated into a multibyte code such as UTF-8. That's why we use the dynamic
string building code. */

-yield = store_get(sizeof(gstring) + ++size, string);
-yield->size = size;
-yield->ptr = 0;
-yield->s = US(yield + 1);
+yield = string_get_tainted(++size, string);

while (mimeword)
{

- #if HAVE_ICONV
+#if HAVE_ICONV
iconv_t icd = (iconv_t)(-1);
- #endif
+#endif

   if (mimeword != string)
     yield = string_catn(yield, string, mimeword - string);
@@ -233,7 +230,7 @@ while (mimeword)
   of long strings - the RFC puts limits on the length, but it's best to be
   robust. */


-  #if HAVE_ICONV
+#if HAVE_ICONV
   *q1 = 0;
   if (target && strcmpic(target, mimeword+2) != 0)
     if ((icd = iconv_open(CS target, CS(mimeword+2))) == (iconv_t)-1)
@@ -241,14 +238,14 @@ while (mimeword)
         target, mimeword+2, strerror(errno),
         (errno == EINVAL)? " (maybe unsupported conversion)" : "");
   *q1 = '?';
-  #endif
+#endif


   while (dlen > 0)
     {
     uschar *tptr = NULL;   /* Stops compiler warning */
     int tlen = -1;


-    #if HAVE_ICONV
+#if HAVE_ICONV
     uschar tbuffer[256];
     uschar *outptr = tbuffer;
     size_t outleft = sizeof(tbuffer);
@@ -281,7 +278,7 @@ while (mimeword)
         }
       }


-    #endif
+#endif


     /* No charset translation is happening or there was a translation error;
     just set up the original as the string to be added, and mark it all used.
@@ -305,9 +302,9 @@ while (mimeword)
     yield = string_catn(yield, tptr, tlen);
     }


- #if HAVE_ICONV
+#if HAVE_ICONV
if (icd != (iconv_t)(-1)) iconv_close(icd);
- #endif
+#endif

   /* Update string past the MIME word; skip any white space if the next thing
   is another MIME word. */
diff --git a/src/src/sieve.c b/src/src/sieve.c
index 0b347e48d..4793d5756 100644
--- a/src/src/sieve.c
+++ b/src/src/sieve.c
@@ -439,8 +439,7 @@ if (*uri && *uri!='?')
       {
       gstring * g = string_catn(NULL, start, uri-start);


-      to.character = string_from_gstring(g);
-      to.length = g->ptr;
+      to.length = len_string_from_gstring(g, &to.character);
       if (uri_decode(&to)==-1)
         {
         filter->errmsg=US"Invalid URI encoding";
@@ -472,8 +471,7 @@ if (*uri=='?')
       {
       gstring * g = string_catn(NULL, start, uri-start);


-      hname.character = string_from_gstring(g);
-      hname.length = g->ptr;
+      hname.length = len_string_from_gstring(g, &hname.character);
       if (uri_decode(&hname)==-1)
         {
         filter->errmsg=US"Invalid URI encoding";
@@ -494,8 +492,7 @@ if (*uri=='?')
       {
       gstring * g = string_catn(NULL, start, uri-start);


-      hname.character = string_from_gstring(g);
-      hname.length = g->ptr;
+      hname.length = len_string_from_gstring(g, &hname.character);
       if (uri_decode(&hvalue)==-1)
         {
         filter->errmsg=US"Invalid URI encoding";
@@ -541,8 +538,7 @@ if (*uri=='?')
         g = string_catn(g, hvalue.character, hvalue.length);
         g = string_catn(g, CUS "\n", 1);


-    header->character = string_from_gstring(g);
-    header->length = g->ptr;
+    hname.length = len_string_from_gstring(g, &hname.character);
         }
       }
     if (*uri=='&') ++uri;
@@ -1482,10 +1478,7 @@ if (*filter->pc=='"') /* quoted string */
       ++filter->pc;


       if (g)
-    {
-    data->character = string_from_gstring(g);
-    data->length = g->ptr;
-    }
+    data->length = len_string_from_gstring(g, &data->character);
       else
     data->character = US"\0";
       /* that way, there will be at least one character allocated */
@@ -1569,10 +1562,7 @@ else if (Ustrncmp(filter->pc,CUS "text:",5)==0) /* multiline string */
 #endif
         {
     if (g)
-      {
-      data->character = string_from_gstring(g);
-      data->length = g->ptr;
-      }
+      data->length = len_string_from_gstring(g, &data->character);
     else
       data->character = US"\0";
     /* that way, there will be at least one character allocated */
@@ -3303,7 +3293,7 @@ while (*filter->pc)


           if (subject.length==-1)
             {
-            uschar *subject_def;
+            uschar * subject_def;


             subject_def = expand_string(US"${if def:header_subject {true}{false}}");
             if (subject_def && Ustrcmp(subject_def,"true")==0)
@@ -3312,13 +3302,12 @@ while (*filter->pc)


               expand_header(&subject,&str_subject);
               g = string_catn(g, subject.character, subject.length);
-          subject.character = string_from_gstring(g);
-              subject.length = g->ptr;
+          subject.length = len_string_from_gstring(g, &subject.character);
               }
             else
               {
-              subject.character=US"Automated reply";
-              subject.length=Ustrlen(subject.character);
+              subject.character = US"Automated reply";
+              subject.length = Ustrlen(subject.character);
               }
             }


diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 04b20d27c..a13af867a 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -4508,7 +4508,7 @@ while (done <= 0)

       if (fl.esmtp)
     {
-    g->s[3] = '-';
+    g->s[3] = '-';    /* overwrite the space after the SMTP response code */


     /* I'm not entirely happy with this, as an MTA is supposed to check
     that it has enough room to accept a message of maximum size before
@@ -4639,9 +4639,9 @@ while (done <= 0)
           first = FALSE;
           fl.auth_advertised = TRUE;
           }
-        saveptr = g->ptr;
+        saveptr = gstring_length(g);
         g = string_catn(g, US" ", 1);
-        g = string_cat (g, au->public_name);
+        g = string_cat(g, au->public_name);
         while (++saveptr < g->ptr) g->s[saveptr] = toupper(g->s[saveptr]);
         au->advertised = TRUE;
         }
@@ -4704,25 +4704,29 @@ while (done <= 0)
       /* Terminate the string (for debug), write it, and note that HELO/EHLO
       has been seen. */


+       {
+    uschar * ehlo_resp;
+    int len = len_string_from_gstring(g, &ehlo_resp);
 #ifndef DISABLE_TLS
-      if (tls_in.active.sock >= 0)
-    (void)tls_write(NULL, g->s, g->ptr,
+    if (tls_in.active.sock >= 0)
+      (void) tls_write(NULL, ehlo_resp, len,
 # ifndef DISABLE_PIPE_CONNECT
-            fl.pipe_connect_acceptable && pipeline_connect_sends());
+              fl.pipe_connect_acceptable && pipeline_connect_sends());
 # else
-            FALSE);
+              FALSE);
 # endif
-      else
+    else
 #endif
-    (void) fwrite(g->s, 1, g->ptr, smtp_out);
-
-      DEBUG(D_receive) for (const uschar * t, * s = string_from_gstring(g);
-                s && (t = Ustrchr(s, '\r'));
-                s = t + 2)                /* \r\n */
-      debug_printf("%s %.*s\n",
-            s == g->s ? "SMTP>>" : "      ",
-            (int)(t - s), s);
-      fl.helo_seen = TRUE;
+      (void) fwrite(ehlo_resp, 1, len, smtp_out);
+
+    DEBUG(D_receive) for (const uschar * t, * s = ehlo_resp;
+                  s && (t = Ustrchr(s, '\r'));
+                  s = t + 2)                /* \r\n */
+        debug_printf("%s %.*s\n",
+              s == g->s ? "SMTP>>" : "      ",
+              (int)(t - s), s);
+    fl.helo_seen = TRUE;
+       }


       /* Reset the protocol and the state, abandoning any previous message. */
       received_protocol =
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index b47fabf1d..f3f70d2e0 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -2302,7 +2302,7 @@ old_pool = store_pool;


     for (s++; (c = *s) && c != ')'; s++) g = string_catn(g, s, 1);


-    tlsp->ver = string_copyn(g->s, g->ptr);
+    tlsp->ver = string_copy_from_gstring(g);
     for (uschar * p = US tlsp->ver; *p; p++)
       if (*p == '-') { *p = '\0'; break; }    /* TLS1.0-PKIX -> TLS1.0 */