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