]> git.notmuchmail.org Git - notmuch/blob - util/string-util.c
util: make sanitize string available in string util for reuse
[notmuch] / util / string-util.c
1 /* string-util.c -  Extra or enhanced routines for null terminated strings.
2  *
3  * Copyright (c) 2012 Jani Nikula
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see http://www.gnu.org/licenses/ .
17  *
18  * Author: Jani Nikula <jani@nikula.org>
19  */
20
21
22 #include "string-util.h"
23 #include "talloc.h"
24
25 #include <ctype.h>
26 #include <errno.h>
27
28 char *
29 strtok_len (char *s, const char *delim, size_t *len)
30 {
31     /* skip initial delims */
32     s += strspn (s, delim);
33
34     /* length of token */
35     *len = strcspn (s, delim);
36
37     return *len ? s : NULL;
38 }
39
40 char *
41 sanitize_string (const void *ctx, const char *str)
42 {
43     char *out, *loop;
44
45     if (! str)
46         return NULL;
47
48     out = talloc_strdup (ctx, str);
49     if (! out)
50         return NULL;
51
52     for (loop = out; *loop; loop++) {
53         if (*loop == '\t' || *loop == '\n')
54             *loop = ' ';
55         else if ((unsigned char)(*loop) < 32)
56             *loop = '?';
57     }
58
59     return out;
60 }
61
62 static int
63 is_unquoted_terminator (unsigned char c)
64 {
65     return c == 0 || c <= ' ' || c == ')';
66 }
67
68 int
69 make_boolean_term (void *ctx, const char *prefix, const char *term,
70                    char **buf, size_t *len)
71 {
72     const char *in;
73     char *out;
74     size_t needed = 3;
75     int need_quoting = 0;
76
77     /* Do we need quoting?  To be paranoid, we quote anything
78      * containing a quote, even though it only matters at the
79      * beginning, and anything containing non-ASCII text. */
80     for (in = term; *in && !need_quoting; in++)
81         if (is_unquoted_terminator (*in) || *in == '"'
82             || (unsigned char)*in > 127)
83             need_quoting = 1;
84
85     if (need_quoting)
86         for (in = term; *in; in++)
87             needed += (*in == '"') ? 2 : 1;
88     else
89         needed = strlen (term) + 1;
90
91     /* Reserve space for the prefix */
92     if (prefix)
93         needed += strlen (prefix) + 1;
94
95     if ((*buf == NULL) || (needed > *len)) {
96         *len = 2 * needed;
97         *buf = talloc_realloc (ctx, *buf, char, *len);
98     }
99
100     if (! *buf) {
101         errno = ENOMEM;
102         return -1;
103     }
104
105     out = *buf;
106
107     /* Copy in the prefix */
108     if (prefix) {
109         strcpy (out, prefix);
110         out += strlen (prefix);
111         *out++ = ':';
112     }
113
114     if (! need_quoting) {
115         strcpy (out, term);
116         return 0;
117     }
118
119     /* Quote term by enclosing it in double quotes and doubling any
120      * internal double quotes. */
121     *out++ = '"';
122     in = term;
123     while (*in) {
124         if (*in == '"')
125             *out++ = '"';
126         *out++ = *in++;
127     }
128     *out++ = '"';
129     *out = '\0';
130
131     return 0;
132 }
133
134 static const char*
135 skip_space (const char *str)
136 {
137     while (*str && isspace ((unsigned char) *str))
138         ++str;
139     return str;
140 }
141
142 int
143 parse_boolean_term (void *ctx, const char *str,
144                     char **prefix_out, char **term_out)
145 {
146     int err = EINVAL;
147     *prefix_out = *term_out = NULL;
148
149     /* Parse prefix */
150     str = skip_space (str);
151     const char *pos = strchr (str, ':');
152     if (! pos || pos == str)
153         goto FAIL;
154     *prefix_out = talloc_strndup (ctx, str, pos - str);
155     if (! *prefix_out) {
156         err = ENOMEM;
157         goto FAIL;
158     }
159     ++pos;
160
161     /* Implement de-quoting compatible with make_boolean_term. */
162     if (*pos == '"') {
163         char *out = talloc_array (ctx, char, strlen (pos));
164         int closed = 0;
165         if (! out) {
166             err = ENOMEM;
167             goto FAIL;
168         }
169         *term_out = out;
170         /* Skip the opening quote, find the closing quote, and
171          * un-double doubled internal quotes. */
172         for (++pos; *pos; ) {
173             if (*pos == '"') {
174                 ++pos;
175                 if (*pos != '"') {
176                     /* Found the closing quote. */
177                     closed = 1;
178                     pos = skip_space (pos);
179                     break;
180                 }
181             }
182             *out++ = *pos++;
183         }
184         /* Did the term terminate without a closing quote or is there
185          * trailing text after the closing quote? */
186         if (!closed || *pos)
187             goto FAIL;
188         *out = '\0';
189     } else {
190         const char *start = pos;
191         /* Check for text after the boolean term. */
192         while (! is_unquoted_terminator (*pos))
193             ++pos;
194         if (*skip_space (pos)) {
195             err = EINVAL;
196             goto FAIL;
197         }
198         /* No trailing text; dup the string so the caller can free
199          * it. */
200         *term_out = talloc_strndup (ctx, start, pos - start);
201         if (! *term_out) {
202             err = ENOMEM;
203             goto FAIL;
204         }
205     }
206     return 0;
207
208  FAIL:
209     talloc_free (*prefix_out);
210     talloc_free (*term_out);
211     errno = err;
212     return -1;
213 }