1 /**
2  * @file
3  * General purpose object for storing and parsing strings
4  *
5  * @authors
6  * Copyright (C) 2017 Ian Zimmerman <itz@primate.net>
7  * Copyright (C) 2017-2019 Richard Russon <rich@flatcap.org>
8  *
9  * @copyright
10  * This program is free software: you can redistribute it and/or modify it under
11  * the terms of the GNU General Public License as published by the Free Software
12  * Foundation, either version 2 of the License, or (at your option) any later
13  * version.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License along with
21  * this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 /**
25  * @page mutt_buffer Helper object for storing and parsing strings
26  *
27  * The Buffer object make parsing and manipulating strings easier.
28  */
29 
30 #include "config.h"
31 #include <stdarg.h>
32 #include <stdbool.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include "buffer.h"
36 #include "memory.h"
37 #include "string2.h"
38 
39 /**
40  * mutt_buffer_init - Initialise a new Buffer
41  * @param buf Buffer to initialise
42  * @retval ptr Initialised Buffer
43  *
44  * This must not be called on a Buffer that already contains data.
45  */
mutt_buffer_init(struct Buffer * buf)46 struct Buffer *mutt_buffer_init(struct Buffer *buf)
47 {
48   if (!buf)
49     return NULL;
50   memset(buf, 0, sizeof(struct Buffer));
51   return buf;
52 }
53 
54 /**
55  * mutt_buffer_make - Make a new buffer on the stack
56  * @param size Initial size
57  * @retval buf Initialized buffer
58  *
59  * The buffer must be released using mutt_buffer_dealloc
60  */
mutt_buffer_make(size_t size)61 struct Buffer mutt_buffer_make(size_t size)
62 {
63   struct Buffer buf = { 0 };
64   if (size != 0)
65   {
66     buf.dptr = buf.data = mutt_mem_calloc(1, size);
67     buf.dsize = size;
68   }
69   return buf;
70 }
71 
72 /**
73  * mutt_buffer_reset - Reset an existing Buffer
74  * @param buf Buffer to reset
75  *
76  * This can be called on a Buffer to reset the pointers,
77  * effectively emptying it.
78  */
mutt_buffer_reset(struct Buffer * buf)79 void mutt_buffer_reset(struct Buffer *buf)
80 {
81   if (!buf || !buf->data || (buf->dsize == 0))
82     return;
83   memset(buf->data, 0, buf->dsize);
84   mutt_buffer_seek(buf, 0);
85 }
86 
87 /**
88  * mutt_buffer_addstr_n - Add a string to a Buffer, expanding it if necessary
89  * @param buf Buffer to add to
90  * @param s   String to add
91  * @param len Length of the string
92  * @retval num Bytes written to Buffer
93  * @retval 0   Error
94  *
95  * Dynamically grow a Buffer to accommodate s, in increments of 128 bytes.
96  * Always one byte bigger than necessary for the null terminator, and the
97  * buffer is always NUL-terminated
98  */
mutt_buffer_addstr_n(struct Buffer * buf,const char * s,size_t len)99 size_t mutt_buffer_addstr_n(struct Buffer *buf, const char *s, size_t len)
100 {
101   if (!buf || !s)
102     return 0;
103 
104   if (!buf->data || !buf->dptr || ((buf->dptr + len + 1) > (buf->data + buf->dsize)))
105     mutt_buffer_alloc(buf, buf->dsize + MAX(128, len + 1));
106 
107   memcpy(buf->dptr, s, len);
108   buf->dptr += len;
109   *(buf->dptr) = '\0';
110   return len;
111 }
112 
113 /**
114  * buffer_printf - Format a string into a Buffer
115  * @param buf Buffer
116  * @param fmt printf-style format string
117  * @param ap  Arguments to be formatted
118  * @retval num Characters written
119  * @retval 0   Error
120  */
buffer_printf(struct Buffer * buf,const char * fmt,va_list ap)121 static int buffer_printf(struct Buffer *buf, const char *fmt, va_list ap)
122 {
123   if (!buf || !fmt)
124     return 0; /* LCOV_EXCL_LINE */
125 
126   if (!buf->data || !buf->dptr || (buf->dsize < 128))
127     mutt_buffer_alloc(buf, 128);
128 
129   int doff = buf->dptr - buf->data;
130   int blen = buf->dsize - doff;
131 
132   va_list ap_retry;
133   va_copy(ap_retry, ap);
134 
135   int len = vsnprintf(buf->dptr, blen, fmt, ap);
136   if (len >= blen)
137   {
138     blen = ++len - blen;
139     if (blen < 128)
140       blen = 128;
141     mutt_buffer_alloc(buf, buf->dsize + blen);
142     len = vsnprintf(buf->dptr, len, fmt, ap_retry);
143   }
144   if (len > 0)
145     buf->dptr += len;
146 
147   va_end(ap_retry);
148 
149   return len;
150 }
151 
152 /**
153  * mutt_buffer_printf - Format a string overwriting a Buffer
154  * @param buf Buffer
155  * @param fmt printf-style format string
156  * @param ... Arguments to be formatted
157  * @retval num Characters written
158  * @retval -1  Error
159  */
mutt_buffer_printf(struct Buffer * buf,const char * fmt,...)160 int mutt_buffer_printf(struct Buffer *buf, const char *fmt, ...)
161 {
162   if (!buf || !fmt)
163     return -1;
164 
165   va_list ap;
166 
167   va_start(ap, fmt);
168   mutt_buffer_reset(buf);
169   int len = buffer_printf(buf, fmt, ap);
170   va_end(ap);
171 
172   return len;
173 }
174 
175 /**
176  * mutt_buffer_fix_dptr - Move the dptr to end of the Buffer
177  * @param buf Buffer to alter
178  *
179  * Ensure buffer->dptr points to the end of the buffer.
180  */
mutt_buffer_fix_dptr(struct Buffer * buf)181 void mutt_buffer_fix_dptr(struct Buffer *buf)
182 {
183   if (!buf)
184     return;
185 
186   mutt_buffer_seek(buf, 0);
187 
188   if (buf->data && (buf->dsize > 0))
189   {
190     buf->data[buf->dsize - 1] = '\0';
191     buf->dptr = strchr(buf->data, '\0');
192   }
193 }
194 
195 /**
196  * mutt_buffer_add_printf - Format a string appending a Buffer
197  * @param buf Buffer
198  * @param fmt printf-style format string
199  * @param ... Arguments to be formatted
200  * @retval num Characters written
201  * @retval -1  Error
202  */
mutt_buffer_add_printf(struct Buffer * buf,const char * fmt,...)203 int mutt_buffer_add_printf(struct Buffer *buf, const char *fmt, ...)
204 {
205   if (!buf || !fmt)
206     return -1;
207 
208   va_list ap;
209 
210   va_start(ap, fmt);
211   int len = buffer_printf(buf, fmt, ap);
212   va_end(ap);
213 
214   return len;
215 }
216 
217 /**
218  * mutt_buffer_addstr - Add a string to a Buffer
219  * @param buf Buffer to add to
220  * @param s   String to add
221  * @retval num Bytes written to Buffer
222  *
223  * If necessary, the Buffer will be expanded.
224  */
mutt_buffer_addstr(struct Buffer * buf,const char * s)225 size_t mutt_buffer_addstr(struct Buffer *buf, const char *s)
226 {
227   if (!buf || !s)
228     return 0;
229   return mutt_buffer_addstr_n(buf, s, mutt_str_len(s));
230 }
231 
232 /**
233  * mutt_buffer_addch - Add a single character to a Buffer
234  * @param buf Buffer to add to
235  * @param c   Character to add
236  * @retval num Bytes written to Buffer
237  *
238  * If necessary, the Buffer will be expanded.
239  */
mutt_buffer_addch(struct Buffer * buf,char c)240 size_t mutt_buffer_addch(struct Buffer *buf, char c)
241 {
242   if (!buf)
243     return 0;
244   return mutt_buffer_addstr_n(buf, &c, 1);
245 }
246 
247 /**
248  * mutt_buffer_is_empty - Is the Buffer empty?
249  * @param buf Buffer to inspect
250  * @retval true Buffer is empty
251  */
mutt_buffer_is_empty(const struct Buffer * buf)252 bool mutt_buffer_is_empty(const struct Buffer *buf)
253 {
254   if (!buf || !buf->data)
255     return true;
256 
257   return (buf->data[0] == '\0');
258 }
259 
260 /**
261  * mutt_buffer_alloc - Make sure a buffer can store at least new_size bytes
262  * @param buf      Buffer to change
263  * @param new_size New size
264  */
mutt_buffer_alloc(struct Buffer * buf,size_t new_size)265 void mutt_buffer_alloc(struct Buffer *buf, size_t new_size)
266 {
267   if (!buf)
268   {
269     return;
270   }
271 
272   if (!buf->dptr)
273   {
274     mutt_buffer_seek(buf, 0);
275   }
276 
277   if ((new_size > buf->dsize) || !buf->data)
278   {
279     size_t offset = (buf->dptr && buf->data) ? buf->dptr - buf->data : 0;
280 
281     buf->dsize = new_size;
282     mutt_mem_realloc(&buf->data, buf->dsize);
283     mutt_buffer_seek(buf, offset);
284     /* This ensures an initially NULL buf->data is now properly terminated. */
285     if (buf->dptr)
286       *buf->dptr = '\0';
287   }
288 }
289 
290 /**
291  * mutt_buffer_dealloc - Release the memory allocated by a buffer
292  * @param buf Buffer to change
293  */
mutt_buffer_dealloc(struct Buffer * buf)294 void mutt_buffer_dealloc(struct Buffer *buf)
295 {
296   if (!buf || !buf->data)
297     return;
298 
299   buf->dptr = NULL;
300   buf->dsize = 0;
301   FREE(&buf->data);
302 }
303 
304 /**
305  * mutt_buffer_strcpy - Copy a string into a Buffer
306  * @param buf Buffer to overwrite
307  * @param s   String to copy
308  * @retval num Bytes written to Buffer
309  *
310  * Overwrites any existing content.
311  */
mutt_buffer_strcpy(struct Buffer * buf,const char * s)312 size_t mutt_buffer_strcpy(struct Buffer *buf, const char *s)
313 {
314   mutt_buffer_reset(buf);
315   return mutt_buffer_addstr(buf, s);
316 }
317 
318 /**
319  * mutt_buffer_strcpy_n - Copy a string into a Buffer
320  * @param buf Buffer to overwrite
321  * @param s   String to copy
322  * @param len Length of string to copy
323  * @retval num Bytes written to Buffer
324  *
325  * Overwrites any existing content.
326  */
mutt_buffer_strcpy_n(struct Buffer * buf,const char * s,size_t len)327 size_t mutt_buffer_strcpy_n(struct Buffer *buf, const char *s, size_t len)
328 {
329   mutt_buffer_reset(buf);
330   return mutt_buffer_addstr_n(buf, s, len);
331 }
332 
333 /**
334  * mutt_buffer_substrcpy - Copy a partial string into a Buffer
335  * @param buf Buffer to overwrite
336  * @param beg Start of string to copy
337  * @param end End of string to copy
338  * @retval num Bytes written to Buffer
339  *
340  * Overwrites any existing content.
341  */
mutt_buffer_substrcpy(struct Buffer * buf,const char * beg,const char * end)342 size_t mutt_buffer_substrcpy(struct Buffer *buf, const char *beg, const char *end)
343 {
344   mutt_buffer_reset(buf);
345   if (end <= beg)
346     return 0;
347 
348   return mutt_buffer_strcpy_n(buf, beg, end - beg);
349 }
350 
351 /**
352  * mutt_buffer_len - Calculate the length of a Buffer
353  * @param buf Buffer
354  * @retval num Size of buffer
355  */
mutt_buffer_len(const struct Buffer * buf)356 size_t mutt_buffer_len(const struct Buffer *buf)
357 {
358   if (!buf || !buf->data || !buf->dptr)
359     return 0;
360 
361   return buf->dptr - buf->data;
362 }
363 
364 /**
365  * mutt_buffer_concat_path - Join a directory name and a filename
366  * @param buf   Buffer to add to
367  * @param dir   Directory name
368  * @param fname File name
369  * @retval num Bytes written to Buffer
370  *
371  * If both dir and fname are supplied, they are separated with '/'.
372  * If either is missing, then the other will be copied exactly.
373  */
mutt_buffer_concat_path(struct Buffer * buf,const char * dir,const char * fname)374 size_t mutt_buffer_concat_path(struct Buffer *buf, const char *dir, const char *fname)
375 {
376   if (!buf)
377     return 0;
378 
379   if (!dir)
380     dir = "";
381   if (!fname)
382     fname = "";
383 
384   const bool d_set = (dir[0] != '\0');
385   const bool f_set = (fname[0] != '\0');
386   if (!d_set && !f_set)
387     return 0;
388 
389   const int d_len = strlen(dir);
390   const bool slash = d_set && (dir[d_len - 1] == '/');
391 
392   const char *fmt = "%s/%s";
393   if (!f_set || !d_set || slash)
394     fmt = "%s%s";
395 
396   return mutt_buffer_printf(buf, fmt, dir, fname);
397 }
398 
399 /**
400  * mutt_buffer_concatn_path - Join a directory name and a filename
401  * @param buf      Buffer for the result
402  * @param dir      Directory name
403  * @param dirlen   Directory name
404  * @param fname    File name
405  * @param fnamelen File name
406  * @retval num Size of buffer
407  *
408  * If both dir and fname are supplied, they are separated with '/'.
409  * If either is missing, then the other will be copied exactly.
410  */
mutt_buffer_concatn_path(struct Buffer * buf,const char * dir,size_t dirlen,const char * fname,size_t fnamelen)411 size_t mutt_buffer_concatn_path(struct Buffer *buf, const char *dir,
412                                 size_t dirlen, const char *fname, size_t fnamelen)
413 {
414   size_t len = 0;
415   mutt_buffer_reset(buf);
416   if (dirlen != 0)
417     len += mutt_buffer_addstr_n(buf, dir, dirlen);
418   if ((dirlen != 0) && (fnamelen != 0))
419     len += mutt_buffer_addch(buf, '/');
420   if (fnamelen != 0)
421     len += mutt_buffer_addstr_n(buf, fname, fnamelen);
422   return len;
423 }
424 
425 /**
426  * mutt_buffer_strdup - Copy a Buffer's string
427  * @param buf Buffer to copy
428  * @retval ptr Copy of string
429  *
430  * @note Caller must free the returned string
431  */
mutt_buffer_strdup(const struct Buffer * buf)432 char *mutt_buffer_strdup(const struct Buffer *buf)
433 {
434   if (!buf)
435     return NULL;
436 
437   return mutt_str_dup(buf->data);
438 }
439 
440 /**
441  * mutt_buffer_copy - Copy a Buffer's contents to another Buffer
442  * @param dst Buffer for result
443  * @param src Buffer to copy
444  */
mutt_buffer_copy(struct Buffer * dst,const struct Buffer * src)445 size_t mutt_buffer_copy(struct Buffer *dst, const struct Buffer *src)
446 {
447   if (!dst)
448     return 0;
449 
450   mutt_buffer_reset(dst);
451   if (!src || !src->data)
452     return 0;
453 
454   return mutt_buffer_addstr_n(dst, src->data, mutt_buffer_len(src));
455 }
456 
457 /**
458  * mutt_buffer_seek - Set current read/write position to offset from beginning
459  * @param buf    Buffer to use
460  * @param offset Distance from the beginning
461  *
462  * This is used for cases where the buffer is read from
463  * A value is placed in the buffer, and then b->dptr is set back to the
464  * beginning as a read marker instead of write marker.
465  */
mutt_buffer_seek(struct Buffer * buf,size_t offset)466 void mutt_buffer_seek(struct Buffer *buf, size_t offset)
467 {
468   if (buf)
469     buf->dptr = buf->data + offset;
470 }
471