]> git.notmuchmail.org Git - notmuch/blob - util/string-util.c
debian: build depend on dh-python
[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 or '(', even though these only matter at the
79      * beginning, and anything containing non-ASCII text. */
80     if (! term[0])
81         need_quoting = 1;
82     for (in = term; *in && !need_quoting; in++)
83         if (is_unquoted_terminator (*in) || *in == '"' || *in == '('
84             || (unsigned char)*in > 127)
85             need_quoting = 1;
86
87     if (need_quoting)
88         for (in = term; *in; in++)
89             needed += (*in == '"') ? 2 : 1;
90     else
91         needed = strlen (term) + 1;
92
93     /* Reserve space for the prefix */
94     if (prefix)
95         needed += strlen (prefix) + 1;
96
97     if ((*buf == NULL) || (needed > *len)) {
98         *len = 2 * needed;
99         *buf = talloc_realloc (ctx, *buf, char, *len);
100     }
101
102     if (! *buf) {
103         errno = ENOMEM;
104         return -1;
105     }
106
107     out = *buf;
108
109     /* Copy in the prefix */
110     if (prefix) {
111         strcpy (out, prefix);
112         out += strlen (prefix);
113         *out++ = ':';
114     }
115
116     if (! need_quoting) {
117         strcpy (out, term);
118         return 0;
119     }
120
121     /* Quote term by enclosing it in double quotes and doubling any
122      * internal double quotes. */
123     *out++ = '"';
124     in = term;
125     while (*in) {
126         if (*in == '"')
127             *out++ = '"';
128         *out++ = *in++;
129     }
130     *out++ = '"';
131     *out = '\0';
132
133     return 0;
134 }
135
136 static const char*
137 skip_space (const char *str)
138 {
139     while (*str && isspace ((unsigned char) *str))
140         ++str;
141     return str;
142 }
143
144 int
145 parse_boolean_term (void *ctx, const char *str,
146                     char **prefix_out, char **term_out)
147 {
148     int err = EINVAL;
149     *prefix_out = *term_out = NULL;
150
151     /* Parse prefix */
152     str = skip_space (str);
153     const char *pos = strchr (str, ':');
154     if (! pos || pos == str)
155         goto FAIL;
156     *prefix_out = talloc_strndup (ctx, str, pos - str);
157     if (! *prefix_out) {
158         err = ENOMEM;
159         goto FAIL;
160     }
161     ++pos;
162
163     /* Implement de-quoting compatible with make_boolean_term. */
164     if (*pos == '"') {
165         char *out = talloc_array (ctx, char, strlen (pos));
166         int closed = 0;
167         if (! out) {
168             err = ENOMEM;
169             goto FAIL;
170         }
171         *term_out = out;
172         /* Skip the opening quote, find the closing quote, and
173          * un-double doubled internal quotes. */
174         for (++pos; *pos; ) {
175             if (*pos == '"') {
176                 ++pos;
177                 if (*pos != '"') {
178                     /* Found the closing quote. */
179                     closed = 1;
180                     pos = skip_space (pos);
181                     break;
182                 }
183             }
184             *out++ = *pos++;
185         }
186         /* Did the term terminate without a closing quote or is there
187          * trailing text after the closing quote? */
188         if (!closed || *pos)
189             goto FAIL;
190         *out = '\0';
191     } else {
192         const char *start = pos;
193         /* Check for text after the boolean term. */
194         while (! is_unquoted_terminator (*pos))
195             ++pos;
196         if (*skip_space (pos)) {
197             err = EINVAL;
198             goto FAIL;
199         }
200         /* No trailing text; dup the string so the caller can free
201          * it. */
202         *term_out = talloc_strndup (ctx, start, pos - start);
203         if (! *term_out) {
204             err = ENOMEM;
205             goto FAIL;
206         }
207     }
208     return 0;
209
210  FAIL:
211     talloc_free (*prefix_out);
212     talloc_free (*term_out);
213     errno = err;
214     return -1;
215 }