1 /* wmem_strbuf.c
2  * Wireshark Memory Manager String Buffer
3  * Copyright 2012, Evan Huus <eapache@gmail.com>
4  *
5  * Wireshark - Network traffic analyzer
6  * By Gerald Combs <gerald@wireshark.org>
7  * Copyright 1998 Gerald Combs
8  *
9  * SPDX-License-Identifier: GPL-2.0-or-later
10  */
11 
12 #include "config.h"
13 
14 #include <string.h>
15 #include <stdio.h>
16 #include <errno.h>
17 #include <glib.h>
18 
19 #include "wmem-int.h"
20 #include "wmem_core.h"
21 #include "wmem_strbuf.h"
22 
23 #define DEFAULT_MINIMUM_LEN 16
24 
25 /* Holds a wmem-allocated string-buffer.
26  *  len is the length of the string (not counting the null-terminator) and
27  *      should be the same as strlen(str) unless the string contains embedded
28  *      nulls.
29  *  alloc_len is the length of the raw buffer pointed to by str, regardless of
30  *      what string is actually being stored (i.e. the buffer contents)
31  *  max_len is the maximum permitted alloc_len (NOT the maximum permitted len,
32  *      which must be one shorter than alloc_len to permit null-termination).
33  *      When max_len is 0 (the default), no maximum is enforced.
34  */
35 struct _wmem_strbuf_t {
36     wmem_allocator_t *allocator;
37 
38     gchar *str;
39 
40     gsize len;
41     gsize alloc_len;
42     gsize max_len;
43 };
44 
45 /* _ROOM accounts for the null-terminator, _RAW_ROOM does not.
46  * Some functions need one, some functions need the other. */
47 #define WMEM_STRBUF_ROOM(S) ((S)->alloc_len - (S)->len - 1)
48 #define WMEM_STRBUF_RAW_ROOM(S) ((S)->alloc_len - (S)->len)
49 
50 wmem_strbuf_t *
wmem_strbuf_sized_new(wmem_allocator_t * allocator,gsize alloc_len,gsize max_len)51 wmem_strbuf_sized_new(wmem_allocator_t *allocator,
52                       gsize alloc_len, gsize max_len)
53 {
54     wmem_strbuf_t *strbuf;
55 
56     ASSERT((max_len == 0) || (alloc_len <= max_len));
57 
58     strbuf = wmem_new(allocator, wmem_strbuf_t);
59 
60     strbuf->allocator = allocator;
61     strbuf->len       = 0;
62     strbuf->alloc_len = alloc_len ? alloc_len : DEFAULT_MINIMUM_LEN;
63     strbuf->max_len   = max_len;
64 
65     strbuf->str    = (gchar *)wmem_alloc(strbuf->allocator, strbuf->alloc_len);
66     strbuf->str[0] = '\0';
67 
68     return strbuf;
69 }
70 
71 wmem_strbuf_t *
wmem_strbuf_new(wmem_allocator_t * allocator,const gchar * str)72 wmem_strbuf_new(wmem_allocator_t *allocator, const gchar *str)
73 {
74     wmem_strbuf_t *strbuf;
75     gsize          len, alloc_len;
76 
77     len       = str ? strlen(str) : 0;
78     alloc_len = DEFAULT_MINIMUM_LEN;
79 
80     /* +1 for the null-terminator */
81     while (alloc_len < (len + 1)) {
82         alloc_len *= 2;
83     }
84 
85     strbuf = wmem_strbuf_sized_new(allocator, alloc_len, 0);
86 
87     if (str && len > 0) {
88         (void) g_strlcpy(strbuf->str, str, alloc_len);
89         strbuf->len = len;
90     }
91 
92     return strbuf;
93 }
94 
95 /* grows the allocated size of the wmem_strbuf_t. If max_len is set, then
96  * not guaranteed to grow by the full amount to_add */
97 static inline void
wmem_strbuf_grow(wmem_strbuf_t * strbuf,const gsize to_add)98 wmem_strbuf_grow(wmem_strbuf_t *strbuf, const gsize to_add)
99 {
100     gsize  new_alloc_len, new_len;
101 
102     /* short-circuit for efficiency if we have room already; greatly speeds up
103      * repeated calls to wmem_strbuf_append_c and others which grow a little bit
104      * at a time.
105      */
106     if (WMEM_STRBUF_ROOM(strbuf) >= to_add) {
107         return;
108     }
109 
110     new_alloc_len = strbuf->alloc_len;
111     new_len = strbuf->len + to_add;
112 
113     /* +1 for the null-terminator */
114     while (new_alloc_len < (new_len + 1)) {
115         new_alloc_len *= 2;
116     }
117 
118     /* max length only enforced if not 0 */
119     if (strbuf->max_len && new_alloc_len > strbuf->max_len) {
120         new_alloc_len = strbuf->max_len;
121     }
122 
123     if (new_alloc_len == strbuf->alloc_len) {
124         return;
125     }
126 
127     strbuf->str = (gchar *)wmem_realloc(strbuf->allocator, strbuf->str, new_alloc_len);
128 
129     strbuf->alloc_len = new_alloc_len;
130 }
131 
132 void
wmem_strbuf_append(wmem_strbuf_t * strbuf,const gchar * str)133 wmem_strbuf_append(wmem_strbuf_t *strbuf, const gchar *str)
134 {
135     gsize append_len;
136 
137     if (!str || str[0] == '\0') {
138         return;
139     }
140 
141     append_len = strlen(str);
142 
143     wmem_strbuf_grow(strbuf, append_len);
144 
145     (void) g_strlcpy(&strbuf->str[strbuf->len], str, strbuf->max_len ? WMEM_STRBUF_RAW_ROOM(strbuf) : append_len+1);
146 
147     strbuf->len = MIN(strbuf->len + append_len, strbuf->alloc_len - 1);
148 }
149 
150 void
wmem_strbuf_append_len(wmem_strbuf_t * strbuf,const gchar * str,gsize append_len)151 wmem_strbuf_append_len(wmem_strbuf_t *strbuf, const gchar *str, gsize append_len)
152 {
153 
154     if (!append_len || !str) {
155         return;
156     }
157 
158     wmem_strbuf_grow(strbuf, append_len);
159 
160     if (strbuf->max_len) {
161         append_len = MIN(append_len, WMEM_STRBUF_ROOM(strbuf));
162     }
163 
164     memcpy(&strbuf->str[strbuf->len], str, append_len);
165     strbuf->len += append_len;
166     strbuf->str[strbuf->len] = '\0';
167 }
168 
169 static inline
_strbuf_vsnprintf(wmem_strbuf_t * strbuf,const char * format,va_list ap,gboolean reset)170 int _strbuf_vsnprintf(wmem_strbuf_t *strbuf, const char *format, va_list ap, gboolean reset)
171 {
172     int want_len;
173     char *buffer = &strbuf->str[strbuf->len];
174     size_t buffer_size = WMEM_STRBUF_RAW_ROOM(strbuf);
175 
176     want_len = vsnprintf(buffer, buffer_size, format, ap);
177     if (want_len < 0) {
178         /* Error. */
179         g_warning("%s: vsnprintf: (%d) %s", G_STRFUNC, want_len, g_strerror(errno));
180         return -1;
181     }
182     if ((size_t)want_len < buffer_size) {
183         /* Success. */
184         strbuf->len += want_len;
185         return 0;
186     }
187 
188     /* No space in buffer, output was truncated. */
189     if (reset) {
190         strbuf->str[strbuf->len] = '\0'; /* Reset. */
191     }
192     else {
193         strbuf->len += buffer_size - 1; /* Append. */
194         ASSERT(strbuf->len == strbuf->alloc_len - 1);
195     }
196 
197     return want_len; /* Length (not including terminating null) that would be written
198                         if there was enough space in buffer. */
199 }
200 
201 void
wmem_strbuf_append_vprintf(wmem_strbuf_t * strbuf,const gchar * fmt,va_list ap)202 wmem_strbuf_append_vprintf(wmem_strbuf_t *strbuf, const gchar *fmt, va_list ap)
203 {
204     int want_len;
205     va_list ap2;
206 
207     va_copy(ap2, ap);
208     /* Try to write buffer, check if output fits. */
209     want_len = _strbuf_vsnprintf(strbuf, fmt, ap2, TRUE); /* Remove output if truncated. */
210     va_end(ap2);
211     if (want_len <= 0)
212         return;
213 
214     /* Resize buffer and try again. This could hit the 'max_len' ceiling. */
215     wmem_strbuf_grow(strbuf, want_len);
216     _strbuf_vsnprintf(strbuf, fmt, ap, FALSE); /* Keep output if truncated. */
217 }
218 
219 void
wmem_strbuf_append_printf(wmem_strbuf_t * strbuf,const gchar * format,...)220 wmem_strbuf_append_printf(wmem_strbuf_t *strbuf, const gchar *format, ...)
221 {
222     va_list ap;
223 
224     va_start(ap, format);
225     wmem_strbuf_append_vprintf(strbuf, format, ap);
226     va_end(ap);
227 }
228 
229 void
wmem_strbuf_append_c(wmem_strbuf_t * strbuf,const gchar c)230 wmem_strbuf_append_c(wmem_strbuf_t *strbuf, const gchar c)
231 {
232     wmem_strbuf_grow(strbuf, 1);
233 
234     if (!strbuf->max_len || WMEM_STRBUF_ROOM(strbuf) >= 1) {
235         strbuf->str[strbuf->len] = c;
236         strbuf->len++;
237         strbuf->str[strbuf->len] = '\0';
238     }
239 }
240 
241 void
wmem_strbuf_append_unichar(wmem_strbuf_t * strbuf,const gunichar c)242 wmem_strbuf_append_unichar(wmem_strbuf_t *strbuf, const gunichar c)
243 {
244     gchar buf[6];
245     gsize charlen;
246 
247     charlen = g_unichar_to_utf8(c, buf);
248 
249     wmem_strbuf_grow(strbuf, charlen);
250 
251     if (!strbuf->max_len || WMEM_STRBUF_ROOM(strbuf) >= charlen) {
252         memcpy(&strbuf->str[strbuf->len], buf, charlen);
253         strbuf->len += charlen;
254         strbuf->str[strbuf->len] = '\0';
255     }
256 }
257 
258 void
wmem_strbuf_truncate(wmem_strbuf_t * strbuf,const gsize len)259 wmem_strbuf_truncate(wmem_strbuf_t *strbuf, const gsize len)
260 {
261     if (len >= strbuf->len) {
262         return;
263     }
264 
265     strbuf->str[len] = '\0';
266     strbuf->len = len;
267 }
268 
269 const gchar *
wmem_strbuf_get_str(wmem_strbuf_t * strbuf)270 wmem_strbuf_get_str(wmem_strbuf_t *strbuf)
271 {
272     return strbuf->str;
273 }
274 
275 gsize
wmem_strbuf_get_len(wmem_strbuf_t * strbuf)276 wmem_strbuf_get_len(wmem_strbuf_t *strbuf)
277 {
278     return strbuf->len;
279 }
280 
281 /* Truncates the allocated memory down to the minimal amount, frees the header
282  * structure, and returns a non-const pointer to the raw string. The
283  * wmem_strbuf_t structure cannot be used after this is called.
284  */
285 char *
wmem_strbuf_finalize(wmem_strbuf_t * strbuf)286 wmem_strbuf_finalize(wmem_strbuf_t *strbuf)
287 {
288     char *ret;
289 
290     ret = (char *)wmem_realloc(strbuf->allocator, strbuf->str, strbuf->len+1);
291 
292     wmem_free(strbuf->allocator, strbuf);
293 
294     return ret;
295 }
296 
297 void
wmem_strbuf_destroy(wmem_strbuf_t * strbuf)298 wmem_strbuf_destroy(wmem_strbuf_t *strbuf)
299 {
300     wmem_allocator_t *allocator = strbuf->allocator;
301 
302     wmem_free(allocator, strbuf->str);
303     wmem_free(allocator, strbuf);
304 }
305 
306 /*
307  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
308  *
309  * Local variables:
310  * c-basic-offset: 4
311  * tab-width: 8
312  * indent-tabs-mode: nil
313  * End:
314  *
315  * vi: set shiftwidth=4 tabstop=8 expandtab:
316  * :indentSize=4:tabSize=8:noTabs=true:
317  */
318