fc2058e03483da596c72946f1fe29f6ecfa60b44
[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 https://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 const char *
41 strtok_len_c (const char *s, const char *delim, size_t *len)
42 {
43     /* strtok_len is already const-safe, but we can't express both
44      * versions in the C type system. */
45     return strtok_len ((char*)s, delim, len);
46 }
47
48 char *
49 sanitize_string (const void *ctx, const char *str)
50 {
51     char *out, *loop;
52
53     if (! str)
54         return NULL;
55
56     out = talloc_strdup (ctx, str);
57     if (! out)
58         return NULL;
59
60     for (loop = out; *loop; loop++) {
61         if (*loop == '\t' || *loop == '\n')
62             *loop = ' ';
63         else if ((unsigned char)(*loop) < 32)
64             *loop = '?';
65     }
66
67     return out;
68 }
69
70 static int
71 is_unquoted_terminator (unsigned char c)
72 {
73     return c == 0 || c <= ' ' || c == ')';
74 }
75
76 int
77 make_boolean_term (void *ctx, const char *prefix, const char *term,
78                    char **buf, size_t *len)
79 {
80     const char *in;
81     char *out;
82     size_t needed = 3;
83     int need_quoting = 0;
84
85     /* Do we need quoting?  To be paranoid, we quote anything
86      * containing a quote or '(', even though these only matter at the
87      * beginning, and anything containing non-ASCII text. */
88     if (! term[0])
89         need_quoting = 1;
90     for (in = term; *in && !need_quoting; in++)
91         if (is_unquoted_terminator (*in) || *in == '"' || *in == '('
92             || (unsigned char)*in > 127)
93             need_quoting = 1;
94
95     if (need_quoting)
96         for (in = term; *in; in++)
97             needed += (*in == '"') ? 2 : 1;
98     else
99         needed = strlen (term) + 1;
100
101     /* Reserve space for the prefix */
102     if (prefix)
103         needed += strlen (prefix) + 1;
104
105     if ((*buf == NULL) || (needed > *len)) {
106         *len = 2 * needed;
107         *buf = talloc_realloc (ctx, *buf, char, *len);
108     }
109
110     if (! *buf) {
111         errno = ENOMEM;
112         return -1;
113     }
114
115     out = *buf;
116
117     /* Copy in the prefix */
118     if (prefix) {
119         strcpy (out, prefix);
120         out += strlen (prefix);
121         *out++ = ':';
122     }
123
124     if (! need_quoting) {
125         strcpy (out, term);
126         return 0;
127     }
128
129     /* Quote term by enclosing it in double quotes and doubling any
130      * internal double quotes. */
131     *out++ = '"';
132     in = term;
133     while (*in) {
134         if (*in == '"')
135             *out++ = '"';
136         *out++ = *in++;
137     }
138     *out++ = '"';
139     *out = '\0';
140
141     return 0;
142 }
143
144 const char*
145 skip_space (const char *str)
146 {
147     while (*str && isspace ((unsigned char) *str))
148         ++str;
149     return str;
150 }
151
152 int
153 parse_boolean_term (void *ctx, const char *str,
154                     char **prefix_out, char **term_out)
155 {
156     int err = EINVAL;
157     *prefix_out = *term_out = NULL;
158
159     /* Parse prefix */
160     str = skip_space (str);
161     const char *pos = strchr (str, ':');
162     if (! pos || pos == str)
163         goto FAIL;
164     *prefix_out = talloc_strndup (ctx, str, pos - str);
165     if (! *prefix_out) {
166         err = ENOMEM;
167         goto FAIL;
168     }
169     ++pos;
170
171     /* Implement de-quoting compatible with make_boolean_term. */
172     if (*pos == '"') {
173         char *out = talloc_array (ctx, char, strlen (pos));
174         int closed = 0;
175         if (! out) {
176             err = ENOMEM;
177             goto FAIL;
178         }
179         *term_out = out;
180         /* Skip the opening quote, find the closing quote, and
181          * un-double doubled internal quotes. */
182         for (++pos; *pos; ) {
183             if (*pos == '"') {
184                 ++pos;
185                 if (*pos != '"') {
186                     /* Found the closing quote. */
187                     closed = 1;
188                     pos = skip_space (pos);
189                     break;
190                 }
191             }
192             *out++ = *pos++;
193         }
194         /* Did the term terminate without a closing quote or is there
195          * trailing text after the closing quote? */
196         if (!closed || *pos)
197             goto FAIL;
198         *out = '\0';
199     } else {
200         const char *start = pos;
201         /* Check for text after the boolean term. */
202         while (! is_unquoted_terminator (*pos))
203             ++pos;
204         if (*skip_space (pos)) {
205             err = EINVAL;
206             goto FAIL;
207         }
208         /* No trailing text; dup the string so the caller can free
209          * it. */
210         *term_out = talloc_strndup (ctx, start, pos - start);
211         if (! *term_out) {
212             err = ENOMEM;
213             goto FAIL;
214         }
215     }
216     return 0;
217
218  FAIL:
219     talloc_free (*prefix_out);
220     talloc_free (*term_out);
221     errno = err;
222     return -1;
223 }
224
225 int
226 strcmp_null (const char *s1, const char *s2)
227 {
228     if (s1 && s2)
229         return strcmp (s1, s2);
230     else if (! s1 && ! s2)
231         return 0;
232     else if (s1)
233         return 1;       /* s1 (non-NULL) is greater than s2 (NULL) */
234     else
235         return -1;      /* s1 (NULL) is less than s2 (non-NULL) */
236 }
237
238 int
239 strcase_equal (const void *a, const void *b)
240 {
241     return strcasecmp (a, b) == 0;
242 }
243
244 unsigned int
245 strcase_hash (const void *ptr)
246 {
247     const char *s = ptr;
248
249     /* This is the djb2 hash. */
250     unsigned int hash = 5381;
251     while (s && *s) {
252         hash = ((hash << 5) + hash) + tolower (*s);
253         s++;
254     }
255
256     return hash;
257 }
258
259 void
260 strip_trailing (char *str, char ch)
261 {
262     int i;
263
264     for (i = strlen (str) - 1; i >= 0; i--) {
265         if (str[i] == ch)
266             str[i] = '\0';
267         else
268             break;
269     }
270 }