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