Merge commit 'upstream/1.8.3'
[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
51 void
52 lbuf_init(struct lbuf *lbuf, int (*output)(const char *),
53     int indent, const char *continuation, int cols)
54 {
55     lbuf->output = output;
56     lbuf->continuation = continuation;
57     lbuf->indent = indent;
58     lbuf->cols = cols;
59     lbuf->len = 0;
60     lbuf->size = 0;
61     lbuf->buf = NULL;
62 }
63
64 void
65 lbuf_destroy(struct lbuf *lbuf)
66 {
67     efree(lbuf->buf);
68     lbuf->buf = NULL;
69 }
70
71 /*
72  * Parse the format and append strings, only %s and %% escapes are supported.
73  * Any characters in set are quoted with a backslash.
74  */
75 void
76 lbuf_append_quoted(struct lbuf *lbuf, const char *set, const char *fmt, ...)
77 {
78     va_list ap;
79     int len;
80     char *cp, *s = NULL;
81
82     va_start(ap, fmt);
83     while (*fmt != '\0') {
84         len = 1;
85         if (fmt[0] == '%' && fmt[1] == 's') {
86             s = va_arg(ap, char *);
87             len = strlen(s);
88         }
89         /* Assume worst case that all chars must be escaped. */
90         if (lbuf->len + (len * 2) + 1 >= lbuf->size) {
91             do {
92                 lbuf->size += 256;
93             } while (lbuf->len + len + 1 >= lbuf->size);
94             lbuf->buf = erealloc(lbuf->buf, lbuf->size);
95         }
96         if (*fmt == '%') {
97             if (*(++fmt) == 's') {
98                 while ((cp = strpbrk(s, set)) != NULL) {
99                     len = (int)(cp - s);
100                     memcpy(lbuf->buf + lbuf->len, s, len);
101                     lbuf->len += len;
102                     lbuf->buf[lbuf->len++] = '\\';
103                     lbuf->buf[lbuf->len++] = *cp;
104                     s = cp + 1;
105                 }
106                 if (*s != '\0') {
107                     len = strlen(s);
108                     memcpy(lbuf->buf + lbuf->len, s, len);
109                     lbuf->len += len;
110                 }
111                 fmt++;
112                 continue;
113             }
114         }
115         if (strchr(set, *fmt) != NULL)
116             lbuf->buf[lbuf->len++] = '\\';
117         lbuf->buf[lbuf->len++] = *fmt++;
118     }
119     lbuf->buf[lbuf->len] = '\0';
120     va_end(ap);
121 }
122
123 /*
124  * Parse the format and append strings, only %s and %% escapes are supported.
125  */
126 void
127 lbuf_append(struct lbuf *lbuf, const char *fmt, ...)
128 {
129     va_list ap;
130     int len;
131     char *s = NULL;
132
133     va_start(ap, fmt);
134     while (*fmt != '\0') {
135         len = 1;
136         if (fmt[0] == '%' && fmt[1] == 's') {
137             s = va_arg(ap, char *);
138             len = strlen(s);
139         }
140         if (lbuf->len + len + 1 >= lbuf->size) {
141             do {
142                 lbuf->size += 256;
143             } while (lbuf->len + len + 1 >= lbuf->size);
144             lbuf->buf = erealloc(lbuf->buf, lbuf->size);
145         }
146         if (*fmt == '%') {
147             if (*(++fmt) == 's') {
148                 memcpy(lbuf->buf + lbuf->len, s, len);
149                 lbuf->len += len;
150                 fmt++;
151                 continue;
152             }
153         }
154         lbuf->buf[lbuf->len++] = *fmt++;
155     }
156     lbuf->buf[lbuf->len] = '\0';
157     va_end(ap);
158 }
159
160 static void
161 lbuf_println(struct lbuf *lbuf, char *line, int len)
162 {
163     char *cp, save;
164     int i, have, contlen;
165
166     contlen = lbuf->continuation ? strlen(lbuf->continuation) : 0;
167
168     /*
169      * Print the buffer, splitting the line as needed on a word
170      * boundary.
171      */
172     cp = line;
173     have = lbuf->cols;
174     while (cp != NULL && *cp != '\0') {
175         char *ep = NULL;
176         int need = len - (int)(cp - line);
177
178         if (need > have) {
179             have -= contlen;            /* subtract for continuation char */
180             if ((ep = memrchr(cp, ' ', have)) == NULL)
181                 ep = memchr(cp + have, ' ', need - have);
182             if (ep != NULL)
183                 need = (int)(ep - cp);
184         }
185         if (cp != line) {
186             /* indent continued lines */
187             /* XXX - build up string instead? */
188             for (i = 0; i < lbuf->indent; i++)
189                 lbuf->output(" ");
190         }
191         /* NUL-terminate cp for the output function and restore afterwards */
192         save = cp[need];
193         cp[need] = '\0';
194         lbuf->output(cp);
195         cp[need] = save;
196         cp = ep;
197
198         /*
199          * If there is more to print, reset have, incremement cp past
200          * the whitespace, and print a line continuaton char if needed.
201          */
202         if (cp != NULL) {
203             have = lbuf->cols - lbuf->indent;
204             ep = line + len;
205             while (cp < ep && isblank((unsigned char)*cp)) {
206                 cp++;
207             }
208             if (contlen)
209                 lbuf->output(lbuf->continuation);
210         }
211         lbuf->output("\n");
212     }
213 }
214
215 /*
216  * Print the buffer with word wrap based on the tty width.
217  * The lbuf is reset on return.
218  */
219 void
220 lbuf_print(struct lbuf *lbuf)
221 {
222     char *cp, *ep;
223     int len;
224
225     if (lbuf->buf == NULL || lbuf->len == 0)
226         goto done;
227
228     /* For very small widths just give up... */
229     len = lbuf->continuation ? strlen(lbuf->continuation) : 0;
230     if (lbuf->cols <= lbuf->indent + len + 20) {
231         lbuf->buf[lbuf->len] = '\0';
232         lbuf->output(lbuf->buf);
233         goto done;
234     }
235
236     /* Print each line in the buffer */
237     for (cp = lbuf->buf; cp != NULL && *cp != '\0'; ) {
238         if (*cp == '\n') {
239             lbuf->output("\n");
240             cp++;
241         } else {
242             len = lbuf->len - (cp - lbuf->buf);
243             if ((ep = memchr(cp, '\n', len)) != NULL)
244                 len = (int)(ep - cp);
245             if (len)
246                 lbuf_println(lbuf, cp, len);
247             cp = ep ? ep + 1 : NULL;
248         }
249     }
250
251 done:
252     lbuf->len = 0;              /* reset the buffer for re-use. */
253 }