chiark / gitweb /
Add arg missed in change 958.
[disorder] / lib / kvp.c
1 /*
2  * This file is part of DisOrder.
3  * Copyright (C) 2004, 2005, 2007, 2008 Richard Kettlewell
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 /** @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  */
25
26 #include "common.h"
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
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  */
41 int 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
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  */
71 static 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
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  */
92 struct 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
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  */
121 int 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
154 /** @brief URL-encode @p s
155  * @param s String to encode
156  * @return Encoded string
157  */
158 char *urlencodestring(const char *s) {
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
167 /** @brief URL-decode @p s
168  * @param s String to decode
169  * @param ns Length of string
170  * @return Decoded string or NULL
171  */
172 char *urldecodestring(const char *s, size_t ns) {
173   struct dynstr d;
174
175   dynstr_init(&d);
176   if(urldecode(sink_dynstr(&d), s, ns))
177     return NULL;
178   dynstr_terminate(&d);
179   return d.vec;
180 }
181
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  */
187 char *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
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  */
221 int 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
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  */
256 const 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
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  */
271 struct 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;
281     k->value = value ? xstrdup(value) : "";
282     k->next = kvp;
283     kvp = k;
284     name = va_arg(ap, const char *);
285   }
286   va_end(ap);
287   return kvp;
288 }
289
290 /*
291 Local Variables:
292 c-basic-offset:2
293 comment-column:40
294 End:
295 */