Line data Source code
1 : /*
2 : * This file originated in the realmd project
3 : *
4 : * Copyright 2013 Red Hat Inc
5 : *
6 : * This program is free software: you can redistribute it and/or modify
7 : * it under the terms of the GNU Lesser General Public License as published
8 : * by the Free Software Foundation; either version 2 of the licence or (at
9 : * your option) any later version.
10 : *
11 : * See the included COPYING file for more information.
12 : *
13 : * Author: Stef Walter <stefw@redhat.com>
14 : */
15 :
16 : /*
17 : * Some snippets of code from gnulib, but have since been refactored
18 : * to within an inch of their life...
19 : *
20 : * vsprintf with automatic memory allocation.
21 : * Copyright (C) 1999, 2002-2003 Free Software Foundation, Inc.
22 : *
23 : * This program is free software; you can redistribute it and/or modify it
24 : * under the terms of the GNU Library General Public License as published
25 : * by the Free Software Foundation; either version 2, or (at your option)
26 : * any later version.
27 : *
28 : * This program is distributed in the hope that it will be useful,
29 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
30 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
31 : * Library General Public License for more details.
32 : */
33 :
34 : #include "config.h"
35 :
36 : #include "safe-format-string.h"
37 :
38 : #include <errno.h>
39 : #include <stdarg.h>
40 : #include <string.h>
41 :
42 : #ifndef MIN
43 : #define MIN(a, b) (((a) < (b)) ? (a) : (b))
44 : #endif
45 :
46 : #ifndef MAX
47 : #define MAX(a, b) (((a) > (b)) ? (a) : (b))
48 : #endif
49 :
50 : static void
51 1986 : safe_padding (int count,
52 : int *total,
53 : void (* copy_fn) (void *, const char *, size_t),
54 : void *data)
55 : {
56 1986 : char eight[] = " ";
57 : int num;
58 :
59 3976 : while (count > 0) {
60 4 : num = MIN (count, 8);
61 4 : copy_fn (data, eight, num);
62 4 : count -= num;
63 4 : *total += num;
64 : }
65 1986 : }
66 :
67 : static void
68 0 : dummy_copy_fn (void *data,
69 : const char *piece,
70 : size_t len)
71 : {
72 :
73 0 : }
74 :
75 : int
76 508 : safe_format_string_cb (void (* copy_fn) (void *, const char *, size_t),
77 : void *data,
78 : const char *format,
79 : const char * const args[],
80 : int num_args)
81 : {
82 508 : int at_arg = 0;
83 : const char *cp;
84 : int precision;
85 : int width;
86 : int len;
87 : const char *value;
88 : int total;
89 : int left;
90 : int i;
91 :
92 508 : if (!copy_fn)
93 0 : copy_fn = dummy_copy_fn;
94 :
95 508 : total = 0;
96 508 : cp = format;
97 :
98 2527 : while (*cp) {
99 :
100 : /* Piece of raw string */
101 1516 : if (*cp != '%') {
102 517 : len = strcspn (cp, "%");
103 517 : copy_fn (data, cp, len);
104 517 : total += len;
105 517 : cp += len;
106 517 : continue;
107 : }
108 :
109 999 : cp++;
110 :
111 : /* An literal percent sign? */
112 999 : if (*cp == '%') {
113 1 : copy_fn (data, "%", 1);
114 1 : total++;
115 1 : cp++;
116 1 : continue;
117 : }
118 :
119 998 : value = NULL;
120 998 : left = 0;
121 998 : precision = -1;
122 998 : width = -1;
123 :
124 : /* Test for positional argument. */
125 998 : if (*cp >= '0' && *cp <= '9') {
126 : /* Look-ahead parsing, otherwise skipped */
127 980 : if (cp[strspn (cp, "0123456789")] == '$') {
128 978 : unsigned int n = 0;
129 1957 : for (i = 0; i < 6 && *cp >= '0' && *cp <= '9'; i++, cp++) {
130 979 : n = 10 * n + (*cp - '0');
131 : }
132 : /* Positional argument 0 is invalid. */
133 978 : if (n == 0) {
134 1 : errno = EINVAL;
135 1 : return -1;
136 : }
137 : /* Positional argument N too high */
138 977 : if (n > num_args) {
139 1 : errno = EINVAL;
140 1 : return -1;
141 : }
142 976 : value = args[n - 1];
143 976 : cp++; /* $ */
144 : }
145 : }
146 :
147 : /* Read the supported flags. */
148 4 : for (; ; cp++) {
149 1000 : if (*cp == '-')
150 2 : left = 1;
151 : /* Supported but ignored */
152 998 : else if (*cp != ' ')
153 996 : break;
154 4 : }
155 :
156 : /* Parse the width. */
157 996 : if (*cp >= '0' && *cp <= '9') {
158 5 : width = 0;
159 15 : for (i = 0; i < 6 && *cp >= '0' && *cp <= '9'; i++, cp++) {
160 10 : width = 10 * width + (*cp - '0');
161 : }
162 : }
163 :
164 : /* Parse the precision. */
165 996 : if (*cp == '.') {
166 3 : precision = 0;
167 6 : for (i = 0, cp++; i < 6 && *cp >= '0' && *cp <= '9'; cp++, i++) {
168 3 : precision = 10 * precision + (*cp - '0');
169 : }
170 : }
171 :
172 : /* Read the conversion character. */
173 996 : switch (*cp++) {
174 : case 's':
175 : /* Non-positional argument */
176 994 : if (value == NULL) {
177 : /* Too many arguments used */
178 18 : if (at_arg == num_args) {
179 1 : errno = EINVAL;
180 1 : return -1;
181 : }
182 17 : value = args[at_arg++];
183 : }
184 993 : break;
185 :
186 : /* No other conversion characters are supported */
187 : default:
188 2 : errno = EINVAL;
189 2 : return -1;
190 : }
191 :
192 : /* How many characters are we printing? */
193 993 : len = strlen (value);
194 993 : if (precision >= 0)
195 3 : len = MIN (precision, len);
196 :
197 : /* Do we need padding? */
198 993 : safe_padding (left ? 0 : width - len, &total, copy_fn, data);
199 :
200 : /* The actual data */;
201 993 : copy_fn (data, value, len);
202 993 : total += len;
203 :
204 : /* Do we need padding? */
205 993 : safe_padding (left ? width - len : 0, &total, copy_fn, data);
206 : }
207 :
208 503 : return total;
209 : }
210 :
211 : static const char **
212 27 : valist_to_args (va_list va,
213 : int *num_args)
214 : {
215 : int alo_args;
216 : const char **args;
217 : const char *arg;
218 : void *mem;
219 :
220 27 : *num_args = alo_args = 0;
221 27 : args = NULL;
222 :
223 : for (;;) {
224 101 : arg = va_arg (va, const char *);
225 101 : if (arg == NULL)
226 27 : break;
227 74 : if (*num_args == alo_args) {
228 26 : alo_args += 8;
229 26 : mem = realloc (args, sizeof (const char *) * alo_args);
230 26 : if (!mem) {
231 0 : free (args);
232 0 : return NULL;
233 : }
234 26 : args = mem;
235 : }
236 74 : args[(*num_args)++] = arg;
237 74 : }
238 :
239 27 : return args;
240 : }
241 :
242 : struct sprintf_ctx {
243 : char *data;
244 : size_t length;
245 : size_t alloc;
246 : };
247 :
248 : static void
249 79 : snprintf_copy_fn (void *data,
250 : const char *piece,
251 : size_t length)
252 : {
253 79 : struct sprintf_ctx *cx = data;
254 :
255 : /* Don't copy if too much data */
256 79 : if (cx->length > cx->alloc)
257 0 : length = 0;
258 79 : else if (cx->length + length > cx->alloc)
259 36 : length = cx->alloc - cx->length;
260 :
261 79 : if (length > 0)
262 44 : memcpy (cx->data + cx->length, piece, length);
263 :
264 : /* Null termination happens later */
265 79 : cx->length += length;
266 79 : }
267 :
268 : int
269 27 : safe_format_string (char *str,
270 : size_t len,
271 : const char *format,
272 : ...)
273 : {
274 : struct sprintf_ctx cx;
275 : int num_args;
276 : va_list va;
277 : const char **args;
278 27 : int error = 0;
279 : int ret;
280 :
281 27 : cx.data = str;
282 27 : cx.length = 0;
283 27 : cx.alloc = len;
284 :
285 27 : va_start (va, format);
286 27 : args = valist_to_args (va, &num_args);
287 27 : va_end (va);
288 :
289 27 : if (args == NULL) {
290 1 : errno = ENOMEM;
291 1 : return -1;
292 : }
293 :
294 26 : if (len)
295 16 : cx.data[0] = '\0';
296 :
297 26 : ret = safe_format_string_cb (snprintf_copy_fn, &cx, format, args, num_args);
298 26 : if (ret < 0) {
299 0 : error = errno;
300 26 : } else if (len > 0) {
301 16 : cx.data[MIN (cx.length, len - 1)] = '\0';
302 : }
303 :
304 26 : free (args);
305 :
306 26 : if (error)
307 0 : errno = error;
308 26 : return ret;
309 : }
|