]> git.notmuchmail.org Git - notmuch/blob - util/string-util.c
string-util: Disallow empty prefixes in parse_boolean_term
[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 static int
41 is_unquoted_terminator (unsigned char c)
42 {
43     return c == 0 || c <= ' ' || c == ')';
44 }
45
46 int
47 make_boolean_term (void *ctx, const char *prefix, const char *term,
48                    char **buf, size_t *len)
49 {
50     const char *in;
51     char *out;
52     size_t needed = 3;
53     int need_quoting = 0;
54
55     /* Do we need quoting?  To be paranoid, we quote anything
56      * containing a quote, even though it only matters at the
57      * beginning, and anything containing non-ASCII text. */
58     for (in = term; *in && !need_quoting; in++)
59         if (is_unquoted_terminator (*in) || *in == '"'
60             || (unsigned char)*in > 127)
61             need_quoting = 1;
62
63     if (need_quoting)
64         for (in = term; *in; in++)
65             needed += (*in == '"') ? 2 : 1;
66     else
67         needed = strlen (term) + 1;
68
69     /* Reserve space for the prefix */
70     if (prefix)
71         needed += strlen (prefix) + 1;
72
73     if ((*buf == NULL) || (needed > *len)) {
74         *len = 2 * needed;
75         *buf = talloc_realloc (ctx, *buf, char, *len);
76     }
77
78     if (! *buf) {
79         errno = ENOMEM;
80         return -1;
81     }
82
83     out = *buf;
84
85     /* Copy in the prefix */
86     if (prefix) {
87         strcpy (out, prefix);
88         out += strlen (prefix);
89         *out++ = ':';
90     }
91
92     if (! need_quoting) {
93         strcpy (out, term);
94         return 0;
95     }
96
97     /* Quote term by enclosing it in double quotes and doubling any
98      * internal double quotes. */
99     *out++ = '"';
100     in = term;
101     while (*in) {
102         if (*in == '"')
103             *out++ = '"';
104         *out++ = *in++;
105     }
106     *out++ = '"';
107     *out = '\0';
108
109     return 0;
110 }
111
112 static const char*
113 skip_space (const char *str)
114 {
115     while (*str && isspace ((unsigned char) *str))
116         ++str;
117     return str;
118 }
119
120 int
121 parse_boolean_term (void *ctx, const char *str,
122                     char **prefix_out, char **term_out)
123 {
124     int err = EINVAL;
125     *prefix_out = *term_out = NULL;
126
127     /* Parse prefix */
128     str = skip_space (str);
129     const char *pos = strchr (str, ':');
130     if (! pos || pos == str)
131         goto FAIL;
132     *prefix_out = talloc_strndup (ctx, str, pos - str);
133     if (! *prefix_out) {
134         err = ENOMEM;
135         goto FAIL;
136     }
137     ++pos;
138
139     /* Implement de-quoting compatible with make_boolean_term. */
140     if (*pos == '"') {
141         char *out = talloc_array (ctx, char, strlen (pos));
142         int closed = 0;
143         if (! out) {
144             err = ENOMEM;
145             goto FAIL;
146         }
147         *term_out = out;
148         /* Skip the opening quote, find the closing quote, and
149          * un-double doubled internal quotes. */
150         for (++pos; *pos; ) {
151             if (*pos == '"') {
152                 ++pos;
153                 if (*pos != '"') {
154                     /* Found the closing quote. */
155                     closed = 1;
156                     pos = skip_space (pos);
157                     break;
158                 }
159             }
160             *out++ = *pos++;
161         }
162         /* Did the term terminate without a closing quote or is there
163          * trailing text after the closing quote? */
164         if (!closed || *pos)
165             goto FAIL;
166         *out = '\0';
167     } else {
168         const char *start = pos;
169         /* Check for text after the boolean term. */
170         while (! is_unquoted_terminator (*pos))
171             ++pos;
172         if (*skip_space (pos)) {
173             err = EINVAL;
174             goto FAIL;
175         }
176         /* No trailing text; dup the string so the caller can free
177          * it. */
178         *term_out = talloc_strndup (ctx, start, pos - start);
179         if (! *term_out) {
180             err = ENOMEM;
181             goto FAIL;
182         }
183     }
184     return 0;
185
186  FAIL:
187     talloc_free (*prefix_out);
188     talloc_free (*term_out);
189     errno = err;
190     return -1;
191 }