src/utils_cgi.[ch]: Basically a rewrite of the parameter handling functions.
[collection4.git] / src / utils_cgi.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <ctype.h>
5 #include <errno.h>
6 #include <time.h>
7 #include <assert.h>
8
9 #include "utils_cgi.h"
10 #include "common.h"
11
12 #include <fcgiapp.h>
13 #include <fcgi_stdio.h>
14
15 struct parameter_s
16 {
17   char *key;
18   char *value;
19 };
20 typedef struct parameter_s parameter_t;
21
22 struct param_list_s
23 {
24   parameter_t *parameters;
25   size_t parameters_num;
26 };
27
28 static param_list_t *pl_global = NULL;
29
30 static char *uri_unescape_copy (char *dest, const char *src, size_t n) /* {{{ */
31 {
32   const char *src_ptr;
33   char *dest_ptr;
34
35   if ((dest == NULL) || (src == NULL) || (n < 1))
36     return (NULL);
37
38   src_ptr = src;
39   dest_ptr = dest;
40
41   *dest_ptr = 0;
42
43   while (*src_ptr != 0)
44   {
45     if (n < 2)
46       break;
47
48     if (*src_ptr == '+')
49     {
50       *dest_ptr = ' ';
51     }
52     else if ((src_ptr[0] == '%')
53         && isxdigit ((int) src_ptr[1]) && isxdigit ((int) src_ptr[2]))
54     {
55       char tmpstr[3];
56       char *endptr;
57       long value;
58
59       tmpstr[0] = src_ptr[1];
60       tmpstr[1] = src_ptr[2];
61       tmpstr[2] = 0;
62
63       errno = 0;
64       endptr = NULL;
65       value = strtol (tmpstr, &endptr, /* base = */ 16);
66       if ((endptr == tmpstr) || (errno != 0))
67       {
68         *dest_ptr = '?';
69       }
70       else
71       {
72         *dest_ptr = (char) value;
73       }
74
75       src_ptr += 2;
76     }
77     else
78     {
79       *dest_ptr = *src_ptr;
80     }
81
82     src_ptr++;
83     dest_ptr++;
84     *dest_ptr = 0;
85   } /* while (*src_ptr != 0) */
86
87   assert (*dest_ptr == 0);
88   return (dest);
89 } /* }}} char *uri_unescape */
90
91 static char *uri_unescape (const char *string) /* {{{ */
92 {
93   char buffer[4096];
94
95   if (string == NULL)
96     return (NULL);
97
98   uri_unescape_copy (buffer, string, sizeof (buffer));
99   
100   return (strdup (buffer));
101 } /* }}} char *uri_unescape */
102
103 static int param_parse_keyval (param_list_t *pl, char *keyval) /* {{{ */
104 {
105   char *key_raw;
106   char *value_raw;
107   char *key;
108   char *value;
109
110   key_raw = keyval;
111   value_raw = strchr (key_raw, '=');
112   if (value_raw == NULL)
113     return (EINVAL);
114   *value_raw = 0;
115   value_raw++;
116
117   key = uri_unescape (key_raw);
118   if (key == NULL)
119     return (ENOMEM);
120
121   value = uri_unescape (value_raw);
122   if (value == NULL)
123   {
124     free (key);
125     return (ENOMEM);
126   }
127   
128   param_set (pl, key, value);
129
130   free (key);
131   free (value);
132
133   return (0);
134 } /* }}} int param_parse_keyval */
135
136 static int parse_query_string (param_list_t *pl, /* {{{ */
137     char *query_string)
138 {
139   char *dummy;
140   char *keyval;
141
142   if ((pl == NULL) || (query_string == NULL))
143     return (EINVAL);
144
145   dummy = query_string;
146   while ((keyval = strtok (dummy, ";&")) != NULL)
147   {
148     dummy = NULL;
149     param_parse_keyval (pl, keyval);
150   }
151
152   return (0);
153 } /* }}} int parse_query_string */
154
155 int param_init (void) /* {{{ */
156 {
157   if (pl_global != NULL)
158     return (0);
159
160   pl_global = param_create (/* query string = */ NULL);
161   if (pl_global == NULL)
162     return (ENOMEM);
163
164   return (0);
165 } /* }}} int param_init */
166
167 void param_finish (void) /* {{{ */
168 {
169   param_destroy (pl_global);
170   pl_global = NULL;
171 } /* }}} void param_finish */
172
173 const char *param (const char *key) /* {{{ */
174 {
175   param_init ();
176
177   return (param_get (pl_global, key));
178 } /* }}} const char *param */
179
180 param_list_t *param_create (const char *query_string) /* {{{ */
181 {
182   char *tmp;
183   param_list_t *pl;
184
185   if (query_string == NULL)
186     query_string = getenv ("QUERY_STRING");
187
188   if (query_string == NULL)
189     return (NULL);
190
191   tmp = strdup (query_string);
192   if (tmp == NULL)
193     return (NULL);
194   
195   pl = malloc (sizeof (*pl));
196   if (pl == NULL)
197   {
198     free (tmp);
199     return (NULL);
200   }
201   memset (pl, 0, sizeof (*pl));
202
203   parse_query_string (pl, tmp);
204
205   free (tmp);
206   return (pl);
207 } /* }}} param_list_t *param_create */
208
209   param_list_t *param_clone (__attribute__((unused)) param_list_t *pl) /* {{{ */
210 {
211   /* FIXME: To be implemented. */
212   assert (23 == 42);
213   return (NULL);
214 } /* }}} param_list_t *param_clone */
215
216 void param_destroy (param_list_t *pl) /* {{{ */
217 {
218   size_t i;
219
220   if (pl == NULL)
221     return;
222
223   for (i = 0; i < pl->parameters_num; i++)
224   {
225     free (pl->parameters[i].key);
226     free (pl->parameters[i].value);
227   }
228   free (pl->parameters);
229   free (pl);
230 } /* }}} void param_destroy */
231
232 const char *param_get (param_list_t *pl, const char *name) /* {{{ */
233 {
234   size_t i;
235
236   if ((pl == NULL) || (name == NULL))
237     return (NULL);
238
239   for (i = 0; i < pl->parameters_num; i++)
240   {
241     if ((name == NULL) && (pl->parameters[i].key == NULL))
242       return (pl->parameters[i].value);
243     else if ((name != NULL) && (pl->parameters[i].key != NULL)
244         && (strcmp (name, pl->parameters[i].key) == 0))
245       return (pl->parameters[i].value);
246   }
247
248   return (NULL);
249 } /* }}} char *param_get */
250
251 static int param_add (param_list_t *pl, /* {{{ */
252     const char *key, const char *value)
253 {
254   parameter_t *tmp;
255
256   tmp = realloc (pl->parameters,
257       sizeof (*pl->parameters) * (pl->parameters_num + 1));
258   if (tmp == NULL)
259     return (ENOMEM);
260   pl->parameters = tmp;
261   tmp = pl->parameters + pl->parameters_num;
262
263   memset (tmp, 0, sizeof (*tmp));
264   tmp->key = strdup (key);
265   if (tmp->key == NULL)
266     return (ENOMEM);
267
268   tmp->value = strdup (value);
269   if (tmp->value == NULL)
270   {
271     free (tmp->key);
272     return (ENOMEM);
273   }
274
275   pl->parameters_num++;
276
277   return (0);
278 } /* }}} int param_add */
279
280 static int param_delete (param_list_t *pl, /* {{{ */
281     const char *name)
282 {
283   size_t i;
284
285   if ((pl == NULL) || (name == NULL))
286     return (EINVAL);
287
288   for (i = 0; i < pl->parameters_num; i++)
289     if (strcasecmp (pl->parameters[i].key, name) == 0)
290       break;
291
292   if (i >= pl->parameters_num)
293     return (ENOENT);
294
295   if (i < (pl->parameters_num - 1))
296   {
297     parameter_t p;
298
299     p = pl->parameters[i];
300     pl->parameters[i] = pl->parameters[pl->parameters_num - 1];
301     pl->parameters[pl->parameters_num - 1] = p;
302   }
303
304   pl->parameters_num--;
305   free (pl->parameters[pl->parameters_num].key);
306   free (pl->parameters[pl->parameters_num].value);
307
308   return (0);
309 } /* }}} int param_delete */
310
311 int param_set (param_list_t *pl, const char *name, /* {{{ */
312     const char *value)
313 {
314   parameter_t *p;
315   char *value_copy;
316   size_t i;
317
318   if ((pl == NULL) || (name == NULL))
319     return (EINVAL);
320
321   if (value == NULL)
322     return (param_delete (pl, name));
323
324   p = NULL;
325   for (i = 0; i < pl->parameters_num; i++)
326   {
327     if (strcasecmp (pl->parameters[i].key, name) == 0)
328     {
329       p = pl->parameters + i;
330       break;
331     }
332   }
333
334   if (p == NULL)
335     return (param_add (pl, name, value));
336
337   value_copy = strdup (value);
338   if (value_copy == NULL)
339     return (ENOMEM);
340
341   free (p->value);
342   p->value = value_copy;
343
344   return (0);
345 } /* }}} int param_set */
346
347 const char *param_as_string (param_list_t *pl) /* {{{ */
348 {
349   char buffer[4096];
350   char key[2048];
351   char value[2048];
352   size_t i;
353
354   if (pl == NULL)
355     return (NULL);
356
357   buffer[0] = 0;
358   for (i = 0; i < pl->parameters_num; i++)
359   {
360     uri_escape_copy (key, pl->parameters[i].key, sizeof (key));
361     uri_escape_copy (value, pl->parameters[i].value, sizeof (value));
362
363     if (i != 0)
364       strlcat (buffer, ";", sizeof (buffer));
365     strlcat (buffer, key, sizeof (buffer));
366     strlcat (buffer, "=", sizeof (buffer));
367     strlcat (buffer, value, sizeof (buffer));
368   }
369
370   return (strdup (buffer));
371 } /* }}} char *param_as_string */
372
373 int param_print_hidden (param_list_t *pl) /* {{{ */
374 {
375   char key[2048];
376   char value[2048];
377   size_t i;
378
379   if (pl == NULL)
380     return (EINVAL);
381
382   for (i = 0; i < pl->parameters_num; i++)
383   {
384     html_escape_copy (key, pl->parameters[i].key, sizeof (key));
385     html_escape_copy (value, pl->parameters[i].value, sizeof (value));
386
387     printf ("  <input type=\"hidden\" name=\"%s\" value=\"%s\" />\n",
388         key, value);
389   }
390
391   return (0);
392 } /* }}} int param_print_hidden */
393
394 char *uri_escape_copy (char *dest, const char *src, size_t n) /* {{{ */
395 {
396   size_t in;
397   size_t out;
398
399   in = 0;
400   out = 0;
401   while (42)
402   {
403     if (src[in] == 0)
404     {
405       dest[out] = 0;
406       return (dest);
407     }
408     else if ((((unsigned char) src[in]) < 32)
409         || (src[in] == '&')
410         || (src[in] == ';')
411         || (src[in] == '?')
412         || (src[in] == '/')
413         || (((unsigned char) src[in]) >= 128))
414     {
415       char esc[4];
416
417       if ((n - out) < 4)
418         break;
419       
420       snprintf (esc, sizeof (esc), "%%%02x", (unsigned int) src[in]);
421       dest[out] = esc[0];
422       dest[out+1] = esc[1];
423       dest[out+2] = esc[2];
424
425       out += 3;
426       in++;
427     }
428     else
429     {
430       dest[out] = src[in];
431       out++;
432       in++;
433     }
434   } /* while (42) */
435
436   return (dest);
437 } /* }}} char *uri_escape_copy */
438
439 char *uri_escape (const char *string) /* {{{ */
440 {
441   char buffer[4096];
442
443   if (string == NULL)
444     return (NULL);
445
446   uri_escape_copy (buffer, string, sizeof (buffer));
447
448   return (strdup (buffer));
449 } /* }}} char *uri_escape */
450
451 const char *script_name (void) /* {{{ */
452 {
453   char *ret;
454
455   ret = getenv ("SCRIPT_NAME");
456   if (ret == NULL)
457     ret = "collection4.fcgi";
458
459   return (ret);
460 } /* }}} char *script_name */
461
462 int time_to_rfc1123 (time_t t, char *buffer, size_t buffer_size) /* {{{ */
463 {
464   struct tm tm_tmp;
465   size_t status;
466
467   /* RFC 1123 *requires* the time to be GMT and the "GMT" timezone string.
468    * Apache will ignore the timezone if "localtime_r" and "%z" is used,
469    * resulting in weird behavior. */
470   if (gmtime_r (&t, &tm_tmp) == NULL)
471     return (errno);
472
473   status = strftime (buffer, buffer_size, "%a, %d %b %Y %T GMT", &tm_tmp);
474   if (status == 0)
475     return (errno);
476
477   return (0);
478 } /* }}} int time_to_rfc1123 */
479
480 #define COPY_ENTITY(e) do {    \
481   size_t len = strlen (e);     \
482   if (dest_size < (len + 1)) \
483     break;                     \
484   strcpy (dest_ptr, (e));    \
485   dest_ptr += len;           \
486   dest_size -= len;          \
487 } while (0)
488
489 char *html_escape_copy (char *dest, const char *src, size_t n) /* {{{ */
490 {
491   char *dest_ptr;
492   size_t dest_size;
493   size_t pos;
494
495   dest[0] = 0;
496   dest_ptr = dest;
497   dest_size = n;
498   for (pos = 0; src[pos] != 0; pos++)
499   {
500     if (src[pos] == '"')
501       COPY_ENTITY ("&quot;");
502     else if (src[pos] == '<')
503       COPY_ENTITY ("&lt;");
504     else if (src[pos] == '>')
505       COPY_ENTITY ("&gt;");
506     else if (src[pos] == '&')
507       COPY_ENTITY ("&amp;");
508     else
509     {
510       *dest_ptr = src[pos];
511       dest_ptr++;
512       dest_size--;
513       *dest_ptr = 0;
514     }
515
516     if (dest_size <= 1)
517       break;
518   }
519
520   return (dest);
521 } /* }}} char *html_escape_copy */
522
523 #undef COPY_ENTITY
524
525 char *html_escape_buffer (char *buffer, size_t buffer_size) /* {{{ */
526 {
527   char tmp[buffer_size];
528
529   html_escape_copy (tmp, buffer, sizeof (tmp));
530   memcpy (buffer, tmp, buffer_size);
531
532   return (buffer);
533 } /* }}} char *html_escape_buffer */
534
535 char *html_escape (const char *string) /* {{{ */
536 {
537   char buffer[4096];
538
539   if (string == NULL)
540     return (NULL);
541
542   html_escape_copy (buffer, string, sizeof (buffer));
543
544   return (strdup (buffer));
545 } /* }}} char *html_escape */
546
547 int html_print_page (const char *title, /* {{{ */
548     const page_callbacks_t *cb, void *user_data)
549 {
550   char *title_html;
551
552   printf ("Content-Type: text/html\n\n");
553
554   if (title == NULL)
555     title = "c4: collection4 graph interface";
556
557   title_html = html_escape (title);
558
559   printf ("<html>\n"
560       "  <head>\n"
561       "    <title>%s</title>\n"
562       "    <link rel=\"stylesheet\" type=\"text/css\" href=\"../share/style.css\" />\n"
563       "    <script type=\"text/javascript\" src=\"../share/jquery-1.4.2.min.js\">\n"
564       "    </script>\n"
565       "    <script type=\"text/javascript\" src=\"../share/collection.js\">\n"
566       "    </script>\n"
567       "  </head>\n",
568       title_html);
569
570   printf ("  <body>\n"
571       "    <table id=\"layout-table\">\n"
572       "      <tr id=\"layout-top\">\n"
573       "        <td id=\"layout-top-left\">");
574   if (cb->top_left != NULL)
575     (*cb->top_left) (user_data);
576   printf ("</td>\n"
577       "        <td id=\"layout-top-center\">");
578   if (cb->top_center != NULL)
579     (*cb->top_center) (user_data);
580   else
581     printf ("<h1>%s</h1>", title_html);
582   printf ("</td>\n"
583       "        <td id=\"layout-top-right\">");
584   if (cb->top_right != NULL)
585     (*cb->top_right) (user_data);
586   printf ("</td>\n"
587       "      </tr>\n"
588       "      <tr id=\"layout-middle\">\n"
589       "        <td id=\"layout-middle-left\">");
590   if (cb->middle_left != NULL)
591     (*cb->middle_left) (user_data);
592   printf ("</td>\n"
593       "        <td id=\"layout-middle-center\">");
594   if (cb->middle_center != NULL)
595     (*cb->middle_center) (user_data);
596   printf ("</td>\n"
597       "        <td id=\"layout-middle-right\">");
598   if (cb->middle_right != NULL)
599     (*cb->middle_right) (user_data);
600   printf ("</td>\n"
601       "      </tr>\n"
602       "      <tr id=\"layout-bottom\">\n"
603       "        <td id=\"layout-bottom-left\">");
604   if (cb->bottom_left != NULL)
605     (*cb->bottom_left) (user_data);
606   printf ("</td>\n"
607       "        <td id=\"layout-bottom-center\">");
608   if (cb->bottom_center != NULL)
609     (*cb->bottom_center) (user_data);
610   printf ("</td>\n"
611       "        <td id=\"layout-bottom-right\">");
612   if (cb->bottom_right != NULL)
613     (*cb->bottom_right) (user_data);
614   printf ("</td>\n"
615       "      </tr>\n"
616       "    </table>\n"
617       "  </body>\n"
618       "</html>\n");
619
620   free (title_html);
621   return (0);
622 } /* }}} int html_print_page */
623
624 int html_print_search_box (__attribute__((unused)) void *user_data) /* {{{ */
625 {
626   char *term_html;
627
628   term_html = html_escape (param ("q"));
629
630   printf ("<form action=\"%s\" method=\"get\" id=\"search-form\">\n"
631       "  <input type=\"hidden\" name=\"action\" value=\"list_graphs\" />\n"
632       "  <input type=\"text\" name=\"q\" value=\"%s\" id=\"search-input\" />\n"
633       "  <input type=\"submit\" name=\"button\" value=\"Search\" />\n"
634       "</form>\n",
635       script_name (),
636       (term_html != NULL) ? term_html : "");
637
638   free (term_html);
639
640   return (0);
641 } /* }}} int html_print_search_box */
642
643 /* vim: set sw=2 sts=2 et fdm=marker : */