3b20323fc43608d7afbfd81293ed14f76cc52699
[debian/sudo] / common / lbuf.c
1 /*
2  * Copyright (c) 2007-2011 Todd C. Miller <Todd.Miller@courtesan.com>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
16  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17  */
18
19 #include <config.h>
20
21 #include <sys/types.h>
22 #include <sys/param.h>
23 #include <stdio.h>
24 #ifdef STDC_HEADERS
25 # include <stdlib.h>
26 # include <stddef.h>
27 #else
28 # ifdef HAVE_STDLIB_H
29 #  include <stdlib.h>
30 # endif
31 #endif /* STDC_HEADERS */
32 #ifdef HAVE_STRING_H
33 # if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
34 #  include <memory.h>
35 # endif
36 # include <string.h>
37 #endif /* HAVE_STRING_H */
38 #ifdef HAVE_STRINGS_H
39 # include <strings.h>
40 #endif /* HAVE_STRINGS_H */
41 #ifdef HAVE_UNISTD_H
42 # include <unistd.h>
43 #endif /* HAVE_UNISTD_H */
44 #include <ctype.h>
45
46 #include "missing.h"
47 #include "alloc.h"
48 #include "error.h"
49 #include "lbuf.h"
50 #include "sudo_debug.h"
51
52 void
53 lbuf_init(struct lbuf *lbuf, int (*output)(const char *),
54     int indent, const char *continuation, int cols)
55 {
56     debug_decl(lbuf_init, SUDO_DEBUG_UTIL)
57
58     lbuf->output = output;
59     lbuf->continuation = continuation;
60     lbuf->indent = indent;
61     lbuf->cols = cols;
62     lbuf->len = 0;
63     lbuf->size = 0;
64     lbuf->buf = NULL;
65
66     debug_return;
67 }
68
69 void
70 lbuf_destroy(struct lbuf *lbuf)
71 {
72     debug_decl(lbuf_destroy, SUDO_DEBUG_UTIL)
73
74     efree(lbuf->buf);
75     lbuf->buf = NULL;
76
77     debug_return;
78 }
79
80 /*
81  * Parse the format and append strings, only %s and %% escapes are supported.
82  * Any characters in set are quoted with a backslash.
83  */
84 void
85 lbuf_append_quoted(struct lbuf *lbuf, const char *set, const char *fmt, ...)
86 {
87     va_list ap;
88     int len;
89     char *cp, *s = NULL;
90     debug_decl(lbuf_append_quoted, SUDO_DEBUG_UTIL)
91
92     va_start(ap, fmt);
93     while (*fmt != '\0') {
94         len = 1;
95         if (fmt[0] == '%' && fmt[1] == 's') {
96             s = va_arg(ap, char *);
97             len = strlen(s);
98         }
99         /* Assume worst case that all chars must be escaped. */
100         if (lbuf->len + (len * 2) + 1 >= lbuf->size) {
101             do {
102                 lbuf->size += 256;
103             } while (lbuf->len + len + 1 >= lbuf->size);
104             lbuf->buf = erealloc(lbuf->buf, lbuf->size);
105         }
106         if (*fmt == '%') {
107             if (*(++fmt) == 's') {
108                 while ((cp = strpbrk(s, set)) != NULL) {
109                     len = (int)(cp - s);
110                     memcpy(lbuf->buf + lbuf->len, s, len);
111                     lbuf->len += len;
112                     lbuf->buf[lbuf->len++] = '\\';
113                     lbuf->buf[lbuf->len++] = *cp;
114                     s = cp + 1;
115                 }
116                 if (*s != '\0') {
117                     len = strlen(s);
118                     memcpy(lbuf->buf + lbuf->len, s, len);
119                     lbuf->len += len;
120                 }
121                 fmt++;
122                 continue;
123             }
124         }
125         if (strchr(set, *fmt) != NULL)
126             lbuf->buf[lbuf->len++] = '\\';
127         lbuf->buf[lbuf->len++] = *fmt++;
128     }
129     lbuf->buf[lbuf->len] = '\0';
130     va_end(ap);
131
132     debug_return;
133 }
134
135 /*
136  * Parse the format and append strings, only %s and %% escapes are supported.
137  */
138 void
139 lbuf_append(struct lbuf *lbuf, const char *fmt, ...)
140 {
141     va_list ap;
142     int len;
143     char *s = NULL;
144     debug_decl(lbuf_append, SUDO_DEBUG_UTIL)
145
146     va_start(ap, fmt);
147     while (*fmt != '\0') {
148         len = 1;
149         if (fmt[0] == '%' && fmt[1] == 's') {
150             s = va_arg(ap, char *);
151             len = strlen(s);
152         }
153         if (lbuf->len + len + 1 >= lbuf->size) {
154             do {
155                 lbuf->size += 256;
156             } while (lbuf->len + len + 1 >= lbuf->size);
157             lbuf->buf = erealloc(lbuf->buf, lbuf->size);
158         }
159         if (*fmt == '%') {
160             if (*(++fmt) == 's') {
161                 memcpy(lbuf->buf + lbuf->len, s, len);
162                 lbuf->len += len;
163                 fmt++;
164                 continue;
165             }
166         }
167         lbuf->buf[lbuf->len++] = *fmt++;
168     }
169     lbuf->buf[lbuf->len] = '\0';
170     va_end(ap);
171
172     debug_return;
173 }
174
175 static void
176 lbuf_println(struct lbuf *lbuf, char *line, int len)
177 {
178     char *cp, save;
179     int i, have, contlen;
180     debug_decl(lbuf_println, SUDO_DEBUG_UTIL)
181
182     contlen = lbuf->continuation ? strlen(lbuf->continuation) : 0;
183
184     /*
185      * Print the buffer, splitting the line as needed on a word
186      * boundary.
187      */
188     cp = line;
189     have = lbuf->cols;
190     while (cp != NULL && *cp != '\0') {
191         char *ep = NULL;
192         int need = len - (int)(cp - line);
193
194         if (need > have) {
195             have -= contlen;            /* subtract for continuation char */
196             if ((ep = memrchr(cp, ' ', have)) == NULL)
197                 ep = memchr(cp + have, ' ', need - have);
198             if (ep != NULL)
199                 need = (int)(ep - cp);
200         }
201         if (cp != line) {
202             /* indent continued lines */
203             /* XXX - build up string instead? */
204             for (i = 0; i < lbuf->indent; i++)
205                 lbuf->output(" ");
206         }
207         /* NUL-terminate cp for the output function and restore afterwards */
208         save = cp[need];
209         cp[need] = '\0';
210         lbuf->output(cp);
211         cp[need] = save;
212         cp = ep;
213
214         /*
215          * If there is more to print, reset have, incremement cp past
216          * the whitespace, and print a line continuaton char if needed.
217          */
218         if (cp != NULL) {
219             have = lbuf->cols - lbuf->indent;
220             ep = line + len;
221             while (cp < ep && isblank((unsigned char)*cp)) {
222                 cp++;
223             }
224             if (contlen)
225                 lbuf->output(lbuf->continuation);
226         }
227         lbuf->output("\n");
228     }
229
230     debug_return;
231 }
232
233 /*
234  * Print the buffer with word wrap based on the tty width.
235  * The lbuf is reset on return.
236  */
237 void
238 lbuf_print(struct lbuf *lbuf)
239 {
240     char *cp, *ep;
241     int len;
242     debug_decl(lbuf_print, SUDO_DEBUG_UTIL)
243
244     if (lbuf->buf == NULL || lbuf->len == 0)
245         goto done;
246
247     /* For very small widths just give up... */
248     len = lbuf->continuation ? strlen(lbuf->continuation) : 0;
249     if (lbuf->cols <= lbuf->indent + len + 20) {
250         if (lbuf->len > 0) {
251             lbuf->buf[lbuf->len] = '\0';
252             lbuf->output(lbuf->buf);
253             if (lbuf->buf[lbuf->len - 1] != '\n')
254                 lbuf->output("\n");
255         }
256         goto done;
257     }
258
259     /* Print each line in the buffer */
260     for (cp = lbuf->buf; cp != NULL && *cp != '\0'; ) {
261         if (*cp == '\n') {
262             lbuf->output("\n");
263             cp++;
264         } else {
265             len = lbuf->len - (cp - lbuf->buf);
266             if ((ep = memchr(cp, '\n', len)) != NULL)
267                 len = (int)(ep - cp);
268             if (len)
269                 lbuf_println(lbuf, cp, len);
270             cp = ep ? ep + 1 : NULL;
271         }
272     }
273
274 done:
275     lbuf->len = 0;              /* reset the buffer for re-use. */
276
277     debug_return;
278 }