Add to git.
[pthrlib.git] / src / pthr_cgi.c
1 /* CGI library.
2  * - by Richard W.M. Jones <rich@annexia.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the Free
16  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  *
18  * $Id: pthr_cgi.c,v 1.6 2003/02/02 18:05:30 rich Exp $
19  */
20
21 #include "config.h"
22
23 #include <stdio.h>
24 #include <stdarg.h>
25
26 #ifdef HAVE_STRING_H
27 #include <string.h>
28 #endif
29
30 #ifdef HAVE_CTYPE_H
31 #include <ctype.h>
32 #endif
33
34 #include <pool.h>
35 #include <vector.h>
36 #include <hash.h>
37 #include <pstring.h>
38
39 #include "pthr_http.h"
40 #include "pthr_cgi.h"
41
42 static int post_max = -1;
43
44 static void parse_qs (cgi, const char *qs);
45 static void insert_param (cgi, char *name, char *value);
46
47 struct cgi
48 {
49   pool pool;                    /* Pool for allocations. */
50   shash params;                 /* Parameters (hash of string -> vector). */
51 };
52
53 int
54 cgi_get_post_max (void)
55 {
56   return post_max;
57 }
58
59 int
60 cgi_set_post_max (int new_post_max)
61 {
62   return post_max = new_post_max;
63 }
64
65 cgi
66 new_cgi (struct pool *pool, http_request h, io_handle io)
67 {
68   cgi c = pmalloc (pool, sizeof *c);
69
70   c->pool = pool;
71   c->params = new_shash (pool, vector);
72
73   if (http_request_method (h) != HTTP_METHOD_POST)
74     {
75       const char *qs = http_request_query_string (h);
76
77       parse_qs (c, qs);
78     }
79   else                          /* Method POST. */
80     {
81       const char *content_length_s =
82         http_request_get_header (h, "Content-Length");
83       const char *content_type = http_request_get_header (h, "Content-Type");
84       int content_length = -1;
85       const char std_type[] = "application/x-www-form-urlencoded";
86       struct pool *tmp = new_subpool (pool);
87       char *content;
88
89       if (content_length_s &&
90           sscanf (content_length_s, "%d", &content_length) != 1)
91         return 0;               /* Error in field format. */
92
93       /* Content length too long? */
94       if (post_max >= 0 && content_length >= 0 && content_length > post_max)
95         return 0;               /* Content too long. */
96
97       /* Check content type. If missing, assume it defaults to the
98        * standard application/x-www-form-urlencoded.
99        */
100       if (content_type &&
101           strncasecmp (content_type, std_type, strlen (std_type)) != 0)
102         return 0;               /* Unexpected/unknown content type. */
103
104       /* Read the content, either to the end of input of else to the
105        * expected length. Note that Netscape 4 sends an extra CRLF
106        * after the POST data with is explicitly forbidden (see:
107        * RFC 2616, section 4.1). We ignore these next time around when
108        * we are reading the next request (see code in new_http_request).
109        */
110       if (content_length >= 0)
111         {
112           content = pmalloc (tmp, content_length + 1);
113           if (io_fread (content, 1, content_length, io) < content_length)
114             return 0;
115
116           content[content_length] = '\0';
117         }
118       else
119         {
120           char t[1024];
121           int r, n = 0;
122
123           content = pstrdup (tmp, "");
124
125           while ((r = io_fread (t, 1, 1024, io)) > 0)
126             {
127               n += r;
128               if (post_max >= 0 && n > post_max)
129                 return 0;       /* Content too long. */
130
131               t[r] = '\0';
132               content = pstrcat (tmp, content, t);
133             }
134         }
135
136       parse_qs (c, content);
137
138       delete_pool (tmp);
139     }
140
141   return c;
142 }
143
144 static void
145 parse_qs (cgi c, const char *qs)
146 {
147   vector v;
148   int i;
149   static char *one = "1";
150
151   if (qs && qs[0] != '\0')
152     {
153       /* Parse the query string. */
154       v = pstrcsplit (c->pool, qs, '&');
155
156       for (i = 0; i < vector_size (v); ++i)
157         {
158           char *param, *t;
159
160           vector_get (v, i, param);
161           t = strchr (param, '=');
162
163           /* No '=' char found? Assume it's a parameter of the form name=1. */
164           if (!t) { insert_param (c, param, one); continue; }
165
166           /* Split the string on the '=' character and set name and value. */
167           *t = '\0';
168           insert_param (c, param, t+1);
169         }
170     }
171 }
172
173 static void
174 insert_param (cgi c, char *name, char *value)
175 {
176   vector v;
177   char *s;
178
179   if (!shash_get (c->params, name, v))
180     v = new_vector (c->pool, char *);
181
182   s = cgi_unescape (c->pool, value);
183   vector_push_back (v, s);
184   shash_insert (c->params, name, v);
185 }
186
187 vector
188 cgi_params (cgi c)
189 {
190   return shash_keys (c->params);
191 }
192
193 const char *
194 cgi_param (cgi c, const char *name)
195 {
196   vector v;
197   char *s;
198
199   if (!shash_get (c->params, name, v))
200     return 0;
201
202   vector_get (v, 0, s);
203   return s;
204 }
205
206 const vector
207 cgi_param_list (cgi c, const char *name)
208 {
209   vector v;
210
211   if (!shash_get (c->params, name, v))
212     return 0;
213
214   return v;
215 }
216
217 int
218 cgi_erase (cgi c, const char *name)
219 {
220   return shash_erase (c->params, name);
221 }
222
223 cgi
224 copy_cgi (pool npool, cgi c)
225 {
226   const char *key, *value;
227   int i, j;
228   vector keys, values, v;
229
230   cgi nc = pmalloc (npool, sizeof *nc);
231
232   nc->pool = npool;
233   nc->params = new_shash (npool, vector);
234
235   keys = shash_keys_in_pool (c->params, npool);
236
237   for (i = 0; i < vector_size (keys); ++i)
238     {
239       vector_get (keys, i, key);
240       values = cgi_param_list (c, key);
241
242       for (j = 0; j < vector_size (values); ++j)
243         {
244           vector_get (values, j, value);
245           value = pstrdup (npool, value);
246
247           if (!shash_get (nc->params, key, v))
248             v = new_vector (npool, char *);
249
250           vector_push_back (v, value);
251           shash_insert (nc->params, key, v);
252         }
253     }
254
255   return nc;
256 }
257
258 char *
259 cgi_escape (pool pool, const char *str)
260 {
261   int i, j;
262   int len = strlen (str);
263   int new_len = 0;
264   char *new_str;
265   static const char hexdigits[] = "0123456789abcdef";
266
267   /* Work out how long the escaped string will be. Escaped strings
268    * get bigger.
269    */
270   for (i = 0; i < len; ++i)
271     {
272       if (isalnum ((int) str[i]) ||
273           str[i] == ',' || str[i] == '-' || str[i] == ' ')
274         new_len++;
275       else
276         new_len += 3;
277     }
278
279   new_str = pmalloc (pool, new_len + 1);
280
281   /* Escape the string. */
282   for (i = 0, j = 0; i < len; ++i)
283     {
284       if (isalnum ((int) str[i]) ||
285           str[i] == ',' || str[i] == '-')
286         new_str[j++] = str[i];
287       else if (str[i] == ' ')
288         new_str[j++] = '+';
289       else
290         {
291           new_str[j++] = '%';
292           new_str[j++] = hexdigits [(str[i] >> 4) & 0xf];
293           new_str[j++] = hexdigits [str[i] & 0xf];
294         }
295     }
296
297   new_str[j++] = '\0';
298
299   return new_str;
300 }
301
302 static inline int
303 hex_to_int (char c)
304 {
305   if (c >= '0' && c <= '9') return c - '0';
306   else if (c >= 'a' && c <= 'f') return c - 'a' + 10;
307   else if (c >= 'A' && c <= 'F') return c - 'A' + 10;
308   else return -1;
309 }
310
311 char *
312 cgi_unescape (pool pool, const char *str)
313 {
314   int i, j;
315   int len = strlen (str);
316   char *new_str;
317
318   /* Unescaped strings always get smaller. */
319   new_str = pmalloc (pool, len + 1);
320
321   for (i = 0, j = 0; i < len; ++i)
322     {
323       if (str[i] == '%')
324         {
325           if (i+2 < len)
326             {
327               int a = hex_to_int (str[i+1]);
328               int b = hex_to_int (str[i+2]);
329
330               if (a >= 0 && b >= 0)
331                 {
332                   new_str[j++] = a << 4 | b;
333                   i += 2;
334                 }
335               else
336                 new_str[j++] = str[i];
337             }
338           else
339             new_str[j++] = str[i];
340         }
341       else if (str[i] == '+')
342         new_str[j++] = ' ';
343       else
344         new_str[j++] = str[i];
345     }
346
347   new_str[j++] = '\0';
348
349   return new_str;
350 }