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