chiark / gitweb /
More memory hygeine.
[disorder] / lib / kvp.c
CommitLineData
460b9539 1/*
2 * This file is part of DisOrder.
5aff007d 3 * Copyright (C) 2004, 2005, 2007, 2008 Richard Kettlewell
460b9539 4 *
e7eb3a27 5 * This program is free software: you can redistribute it and/or modify
460b9539 6 * it under the terms of the GNU General Public License as published by
e7eb3a27 7 * the Free Software Foundation, either version 3 of the License, or
460b9539 8 * (at your option) any later version.
e7eb3a27
RK
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 *
460b9539 15 * You should have received a copy of the GNU General Public License
e7eb3a27 16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
460b9539 17 */
132a5a4a
RK
18/** @file lib/kvp.c
19 * @brief Linked list of key-value pairs
20 *
21 * Also supports URL encoding/decoding (of raw strings and kvp lists).
22 *
23 * For large sets of keys, see @ref lib/hash.c.
24 */
460b9539 25
05b75f8d 26#include "common.h"
460b9539 27
28#include "mem.h"
29#include "kvp.h"
30#include "log.h"
31#include "vector.h"
32#include "hex.h"
33#include "sink.h"
34
031e275e
RK
35/** @brief Decode a URL-encoded string to a ink
36 * @param sink Where to store result
37 * @param ptr Start of string
38 * @param n Length of string
39 * @return 0 on success, non-0 if string could not be decoded or sink write failed
40 */
460b9539 41int urldecode(struct sink *sink, const char *ptr, size_t n) {
42 int c, d1, d2;
43
44 while(n-- > 0) {
45 switch(c = *ptr++) {
46 case '%':
47 if((d1 = unhexdigit(ptr[0])) == -1
48 || (d2 = unhexdigit(ptr[1])) == -1)
49 return -1;
50 c = d1 * 16 + d2;
51 ptr += 2;
52 n -= 2;
53 break;
54 case '+':
55 c = ' ';
56 break;
57 default:
58 break;
59 }
60 if(sink_writec(sink,c) < 0)
61 return -1;
62 }
63 return 0;
64}
65
00682707
RK
66/** @brief URL-decode a string
67 * @param ptr Start of URL-encoded string
68 * @param n Length of @p ptr
69 * @return Decoded string (0-terminated)
70 */
460b9539 71static char *decode(const char *ptr, size_t n) {
72 struct dynstr d;
73 struct sink *s;
74
75 dynstr_init(&d);
76 s = sink_dynstr(&d);
77 if(urldecode(s, ptr, n))
78 return 0;
79 dynstr_terminate(&d);
80 return d.vec;
81}
82
031e275e
RK
83/** @brief Decode a URL-decoded key-value pair list
84 * @param ptr Start of input string
85 * @param n Length of input string
86 * @return @ref kvp of values from input
87 *
88 * The KVP is in the same order as the original input.
89 *
90 * If the original input contains duplicates names, so will the KVP.
91 */
460b9539 92struct kvp *kvp_urldecode(const char *ptr, size_t n) {
93 struct kvp *kvp, **kk = &kvp, *k;
94 const char *q, *r, *top = ptr + n, *next;
95
96 while(ptr < top) {
97 *kk = k = xmalloc(sizeof *k);
98 if(!(q = memchr(ptr, '=', top - ptr)))
99 break;
100 if(!(k->name = decode(ptr, q - ptr))) break;
101 if((r = memchr(ptr, '&', top - ptr)))
102 next = r + 1;
103 else
104 next = r = top;
105 if(r < q)
106 break;
107 if(!(k->value = decode(q + 1, r - (q + 1)))) break;
108 kk = &k->next;
109 ptr = next;
110 }
111 *kk = 0;
112 return kvp;
113}
114
031e275e
RK
115/** @brief URL-encode a string to a sink
116 * @param sink Where to send output
117 * @param s String to encode
118 * @param n Length of string to encode
119 * @return 0 on success or non-0 if sink write failed
120 */
460b9539 121int urlencode(struct sink *sink, const char *s, size_t n) {
122 unsigned char c;
123
124 while(n > 0) {
125 c = *s++;
126 n--;
127 switch(c) {
128 default:
129 if((c >= '0' && c <= '9')
130 || (c >= 'a' && c <= 'z')
131 || (c >= 'A' && c <= 'Z')) {
132 /* RFC2396 2.3 unreserved characters */
133 case '-':
134 case '_':
135 case '.':
136 case '!':
137 case '~':
138 case '*':
139 case '\'':
140 case '(':
141 case ')':
142 /* additional unreserved characters */
143 case '/':
144 if(sink_writec(sink, c) < 0)
145 return -1;
146 } else
147 if(sink_printf(sink, "%%%02x", (unsigned int)c) < 0)
148 return -1;
149 }
150 }
151 return 0;
152}
153
b12be54a
RK
154/** @brief URL-encode @p s
155 * @param s String to encode
156 * @return Encoded string
157 */
36bde473 158char *urlencodestring(const char *s) {
460b9539 159 struct dynstr d;
160
161 dynstr_init(&d);
162 urlencode(sink_dynstr(&d), s, strlen(s));
163 dynstr_terminate(&d);
164 return d.vec;
165}
166
b12be54a
RK
167/** @brief URL-decode @p s
168 * @param s String to decode
169 * @param ns Length of string
36bde473 170 * @return Decoded string or NULL
b12be54a 171 */
36bde473 172char *urldecodestring(const char *s, size_t ns) {
b12be54a
RK
173 struct dynstr d;
174
175 dynstr_init(&d);
36bde473 176 if(urldecode(sink_dynstr(&d), s, ns))
177 return NULL;
b12be54a
RK
178 dynstr_terminate(&d);
179 return d.vec;
180}
181
031e275e
RK
182/** @brief URL-encode a KVP
183 * @param kvp Linked list to encode
184 * @param np Where to store length (or NULL)
185 * @return Newly created string
186 */
460b9539 187char *kvp_urlencode(const struct kvp *kvp, size_t *np) {
188 struct dynstr d;
189 struct sink *sink;
190
191 dynstr_init(&d);
192 sink = sink_dynstr(&d);
193 while(kvp) {
194 urlencode(sink, kvp->name, strlen(kvp->name));
195 dynstr_append(&d, '=');
196 urlencode(sink, kvp->value, strlen(kvp->value));
197 if((kvp = kvp->next))
198 dynstr_append(&d, '&');
199
200 }
201 dynstr_terminate(&d);
202 if(np)
203 *np = d.nvec;
204 return d.vec;
205}
206
031e275e
RK
207/** @brief Set or remove a value in a @ref kvp
208 * @param kvpp Address of KVP head to modify
209 * @param name Key to search for
210 * @param value New value or NULL to delete
211 * @return 1 if any change was made otherwise 0
212 *
213 * If @p value is not NULL then the first matching key is replaced; if
214 * there was no matching key a new one is added at the end.
215 *
216 * If @p value is NULL then the first matching key is removed.
217 *
218 * If anything actually changes the return value is 1. If no actual
219 * change is made then 0 is returned instead.
220 */
460b9539 221int kvp_set(struct kvp **kvpp, const char *name, const char *value) {
222 struct kvp *k, **kk;
223
224 for(kk = kvpp; (k = *kk) && strcmp(name, k->name); kk = &k->next)
225 ;
226 if(k) {
227 if(value) {
228 if(strcmp(k->value, value)) {
229 k->value = xstrdup(value);
230 return 1;
231 } else
232 return 0;
233 } else {
234 *kk = k->next;
235 return 1;
236 }
237 } else {
238 if(value) {
239 *kk = k = xmalloc(sizeof *k);
240 k->name = xstrdup(name);
241 k->value = xstrdup(value);
242 return 1;
243 } else
244 return 0;
245 }
246}
247
031e275e
RK
248/** @brief Look up a value in a @ref kvp
249 * @param kvp Head of KVP linked list
250 * @param name Key to search for
251 * @return Value or NULL
252 *
253 * The returned value is owned by the KVP so must not be modified or
254 * freed.
255 */
460b9539 256const char *kvp_get(const struct kvp *kvp, const char *name) {
257 for(;kvp && strcmp(kvp->name, name); kvp = kvp->next)
258 ;
259 return kvp ? kvp->value : 0;
260}
261
031e275e
RK
262/** @brief Construct a KVP from arguments
263 * @param name First name
264 * @return Newly created KVP
265 *
266 * Arguments must come in name/value pairs and must be followed by a (char *)0.
267 *
268 * The order of the new KVP is not formally defined though the test
269 * programs rely on it nonetheless so update them if you change it.
270 */
b5b7e0bf
RK
271struct kvp *kvp_make(const char *name, ...) {
272 const char *value;
273 struct kvp *kvp = 0, *k;
274 va_list ap;
275
276 va_start(ap, name);
277 while(name) {
278 value = va_arg(ap, const char *);
279 k = xmalloc(sizeof *k);
280 k->name = name;
031e275e 281 k->value = value ? xstrdup(value) : "";
b5b7e0bf
RK
282 k->next = kvp;
283 kvp = k;
284 name = va_arg(ap, const char *);
285 }
286 va_end(ap);
287 return kvp;
288}
289
0d047a12
RK
290void kvp_free(struct kvp *k) {
291 if(k) {
292 kvp_free(k->next);
293 xfree((void *)k->name);
294 xfree((void *)k->value);
295 xfree(k);
296 }
297}
298
460b9539 299/*
300Local Variables:
301c-basic-offset:2
302comment-column:40
303End:
304*/