"list graphs" action: Add link to display dynamic graphs, too.
[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     n--;
108     src_ptr++;
109     dest_ptr++;
110     *dest_ptr = 0;
111   } /* while (*src_ptr != 0) */
112
113   assert (*dest_ptr == 0);
114   return (dest);
115 } /* }}} char *uri_unescape_copy */
116
117 static char *uri_unescape (const char *string) /* {{{ */
118 {
119   char buffer[4096];
120
121   if (string == NULL)
122     return (NULL);
123
124   uri_unescape_copy (buffer, string, sizeof (buffer));
125   
126   return (strdup (buffer));
127 } /* }}} char *uri_unescape */
128
129 static int param_parse_keyval (param_list_t *pl, char *keyval) /* {{{ */
130 {
131   char *key_raw;
132   char *value_raw;
133   char *key;
134   char *value;
135
136   key_raw = keyval;
137   value_raw = strchr (key_raw, '=');
138   if (value_raw == NULL)
139     return (EINVAL);
140   *value_raw = 0;
141   value_raw++;
142
143   key = uri_unescape (key_raw);
144   if (key == NULL)
145     return (ENOMEM);
146
147   value = uri_unescape (value_raw);
148   if (value == NULL)
149   {
150     free (key);
151     return (ENOMEM);
152   }
153   
154   param_set (pl, key, value);
155
156   free (key);
157   free (value);
158
159   return (0);
160 } /* }}} int param_parse_keyval */
161
162 static int parse_query_string (param_list_t *pl, /* {{{ */
163     char *query_string)
164 {
165   char *dummy;
166   char *keyval;
167
168   if ((pl == NULL) || (query_string == NULL))
169     return (EINVAL);
170
171   dummy = query_string;
172   while ((keyval = strtok (dummy, ";&")) != NULL)
173   {
174     dummy = NULL;
175     param_parse_keyval (pl, keyval);
176   }
177
178   return (0);
179 } /* }}} int parse_query_string */
180
181 int param_init (void) /* {{{ */
182 {
183   if (pl_global != NULL)
184     return (0);
185
186   pl_global = param_create (/* query string = */ NULL);
187   if (pl_global == NULL)
188     return (ENOMEM);
189
190   return (0);
191 } /* }}} int param_init */
192
193 void param_finish (void) /* {{{ */
194 {
195   param_destroy (pl_global);
196   pl_global = NULL;
197 } /* }}} void param_finish */
198
199 const char *param (const char *key) /* {{{ */
200 {
201   param_init ();
202
203   return (param_get (pl_global, key));
204 } /* }}} const char *param */
205
206 param_list_t *param_create (const char *query_string) /* {{{ */
207 {
208   char *tmp;
209   param_list_t *pl;
210
211   if (query_string == NULL)
212     query_string = getenv ("QUERY_STRING");
213
214   if (query_string == NULL)
215     return (NULL);
216
217   tmp = strdup (query_string);
218   if (tmp == NULL)
219     return (NULL);
220   
221   pl = malloc (sizeof (*pl));
222   if (pl == NULL)
223   {
224     free (tmp);
225     return (NULL);
226   }
227   memset (pl, 0, sizeof (*pl));
228
229   parse_query_string (pl, tmp);
230
231   free (tmp);
232   return (pl);
233 } /* }}} param_list_t *param_create */
234
235   param_list_t *param_clone (__attribute__((unused)) param_list_t *pl) /* {{{ */
236 {
237   /* FIXME: To be implemented. */
238   assert (23 == 42);
239   return (NULL);
240 } /* }}} param_list_t *param_clone */
241
242 void param_destroy (param_list_t *pl) /* {{{ */
243 {
244   size_t i;
245
246   if (pl == NULL)
247     return;
248
249   for (i = 0; i < pl->parameters_num; i++)
250   {
251     free (pl->parameters[i].key);
252     free (pl->parameters[i].value);
253   }
254   free (pl->parameters);
255   free (pl);
256 } /* }}} void param_destroy */
257
258 const char *param_get (param_list_t *pl, const char *name) /* {{{ */
259 {
260   size_t i;
261
262   if ((pl == NULL) || (name == NULL))
263     return (NULL);
264
265   for (i = 0; i < pl->parameters_num; i++)
266   {
267     if ((name == NULL) && (pl->parameters[i].key == NULL))
268       return (pl->parameters[i].value);
269     else if ((name != NULL) && (pl->parameters[i].key != NULL)
270         && (strcmp (name, pl->parameters[i].key) == 0))
271       return (pl->parameters[i].value);
272   }
273
274   return (NULL);
275 } /* }}} char *param_get */
276
277 static int param_add (param_list_t *pl, /* {{{ */
278     const char *key, const char *value)
279 {
280   parameter_t *tmp;
281
282   tmp = realloc (pl->parameters,
283       sizeof (*pl->parameters) * (pl->parameters_num + 1));
284   if (tmp == NULL)
285     return (ENOMEM);
286   pl->parameters = tmp;
287   tmp = pl->parameters + pl->parameters_num;
288
289   memset (tmp, 0, sizeof (*tmp));
290   tmp->key = strdup (key);
291   if (tmp->key == NULL)
292     return (ENOMEM);
293
294   tmp->value = strdup (value);
295   if (tmp->value == NULL)
296   {
297     free (tmp->key);
298     return (ENOMEM);
299   }
300
301   pl->parameters_num++;
302
303   return (0);
304 } /* }}} int param_add */
305
306 static int param_delete (param_list_t *pl, /* {{{ */
307     const char *name)
308 {
309   size_t i;
310
311   if ((pl == NULL) || (name == NULL))
312     return (EINVAL);
313
314   for (i = 0; i < pl->parameters_num; i++)
315     if (strcasecmp (pl->parameters[i].key, name) == 0)
316       break;
317
318   if (i >= pl->parameters_num)
319     return (ENOENT);
320
321   if (i < (pl->parameters_num - 1))
322   {
323     parameter_t p;
324
325     p = pl->parameters[i];
326     pl->parameters[i] = pl->parameters[pl->parameters_num - 1];
327     pl->parameters[pl->parameters_num - 1] = p;
328   }
329
330   pl->parameters_num--;
331   free (pl->parameters[pl->parameters_num].key);
332   free (pl->parameters[pl->parameters_num].value);
333
334   return (0);
335 } /* }}} int param_delete */
336
337 int param_set (param_list_t *pl, const char *name, /* {{{ */
338     const char *value)
339 {
340   parameter_t *p;
341   char *value_copy;
342   size_t i;
343
344   if ((pl == NULL) || (name == NULL))
345     return (EINVAL);
346
347   if (value == NULL)
348     return (param_delete (pl, name));
349
350   p = NULL;
351   for (i = 0; i < pl->parameters_num; i++)
352   {
353     if (strcasecmp (pl->parameters[i].key, name) == 0)
354     {
355       p = pl->parameters + i;
356       break;
357     }
358   }
359
360   if (p == NULL)
361     return (param_add (pl, name, value));
362
363   value_copy = strdup (value);
364   if (value_copy == NULL)
365     return (ENOMEM);
366
367   free (p->value);
368   p->value = value_copy;
369
370   return (0);
371 } /* }}} int param_set */
372
373 char *param_as_string (param_list_t *pl) /* {{{ */
374 {
375   char buffer[4096];
376   char key[2048];
377   char value[2048];
378   size_t i;
379
380   if (pl == NULL)
381     return (NULL);
382
383   buffer[0] = 0;
384   for (i = 0; i < pl->parameters_num; i++)
385   {
386     uri_escape_copy (key, pl->parameters[i].key, sizeof (key));
387     uri_escape_copy (value, pl->parameters[i].value, sizeof (value));
388
389     if (i != 0)
390       strlcat (buffer, ";", sizeof (buffer));
391     strlcat (buffer, key, sizeof (buffer));
392     strlcat (buffer, "=", sizeof (buffer));
393     strlcat (buffer, value, sizeof (buffer));
394   }
395
396   return (strdup (buffer));
397 } /* }}} char *param_as_string */
398
399 int param_print_hidden (param_list_t *pl) /* {{{ */
400 {
401   char key[2048];
402   char value[2048];
403   size_t i;
404
405   if (pl == NULL)
406     return (EINVAL);
407
408   for (i = 0; i < pl->parameters_num; i++)
409   {
410     html_escape_copy (key, pl->parameters[i].key, sizeof (key));
411     html_escape_copy (value, pl->parameters[i].value, sizeof (value));
412
413     printf ("  <input type=\"hidden\" name=\"%s\" value=\"%s\" />\n",
414         key, value);
415   }
416
417   return (0);
418 } /* }}} int param_print_hidden */
419
420 char *uri_escape_copy (char *dest, const char *src, size_t n) /* {{{ */
421 {
422   size_t in;
423   size_t out;
424
425   in = 0;
426   out = 0;
427   while (42)
428   {
429     if (src[in] == 0)
430     {
431       dest[out] = 0;
432       return (dest);
433     }
434     else if ((((unsigned char) src[in]) < 32)
435         || (src[in] == '&')
436         || (src[in] == ';')
437         || (src[in] == '?')
438         || (src[in] == '/')
439         || (((unsigned char) src[in]) >= 128))
440     {
441       char esc[4];
442
443       if ((n - out) < 4)
444         break;
445       
446       snprintf (esc, sizeof (esc), "%%%02x", (unsigned int) src[in]);
447       dest[out] = esc[0];
448       dest[out+1] = esc[1];
449       dest[out+2] = esc[2];
450
451       out += 3;
452       in++;
453     }
454     else
455     {
456       dest[out] = src[in];
457       out++;
458       in++;
459     }
460   } /* while (42) */
461
462   return (dest);
463 } /* }}} char *uri_escape_copy */
464
465 char *uri_escape (const char *string) /* {{{ */
466 {
467   char buffer[4096];
468
469   if (string == NULL)
470     return (NULL);
471
472   uri_escape_copy (buffer, string, sizeof (buffer));
473
474   return (strdup (buffer));
475 } /* }}} char *uri_escape */
476
477 #define COPY_ENTITY(e) do {    \
478   size_t len = strlen (e);     \
479   if (dest_size < (len + 1))   \
480     break;                     \
481   strcpy (dest_ptr, (e));      \
482   dest_ptr += len;             \
483   dest_size -= len;            \
484 } while (0)
485
486 char *json_escape_copy (char *dest, const char *src, size_t n) /* {{{ */
487 {
488   char *dest_ptr;
489   size_t dest_size;
490   size_t pos;
491
492   dest[0] = 0;
493   dest_ptr = dest;
494   dest_size = n;
495   for (pos = 0; src[pos] != 0; pos++)
496   {
497     if (src[pos] == '"')
498       COPY_ENTITY ("\\\"");
499     else if (src[pos] == '\\')
500       COPY_ENTITY ("\\\\");
501     else if (((uint8_t) src[pos]) < 32)
502     {
503       if (src[pos] == '\n')
504         COPY_ENTITY ("\\n");
505       else if (src[pos] == '\r')
506         COPY_ENTITY ("\\r");
507       else if (src[pos] == '\t')
508         COPY_ENTITY ("\\t");
509       else if (src[pos] == '\b')
510         COPY_ENTITY ("\\b");
511       else if (src[pos] == '\f')
512         COPY_ENTITY ("\\f");
513       else
514       {
515         char buffer[8];
516         sprintf (buffer, "\\u%04"PRIx8, (uint8_t) src[pos]);
517         buffer[sizeof (buffer) - 1] = 0;
518         COPY_ENTITY (buffer);
519       }
520     }
521     else
522     {
523       *dest_ptr = src[pos];
524       dest_ptr++;
525       dest_size--;
526       *dest_ptr = 0;
527     }
528
529     if (dest_size <= 1)
530       break;
531   }
532
533   return (dest);
534 } /* }}} char *json_escape_copy */
535
536 #undef COPY_ENTITY
537
538 char *json_escape_buffer (char *buffer, size_t buffer_size)
539 {
540   char temp[buffer_size];
541
542   json_escape_copy (temp, buffer, buffer_size);
543   memcpy (buffer, temp, buffer_size);
544
545   return (buffer);
546 } /* }}} char *json_escape_buffer */
547
548 char *json_escape (const char *string) /* {{{ */
549 {
550   char buffer[4096];
551
552   if (string == NULL)
553     return (NULL);
554
555   json_escape_copy (buffer, string, sizeof (buffer));
556
557   return (strdup (buffer));
558 } /* }}} char *json_escape */
559
560 const char *script_name (void) /* {{{ */
561 {
562   char *ret;
563
564   ret = getenv ("SCRIPT_NAME");
565   if (ret == NULL)
566     ret = "collection4.fcgi";
567
568   return (ret);
569 } /* }}} char *script_name */
570
571 int time_to_rfc1123 (time_t t, char *buffer, size_t buffer_size) /* {{{ */
572 {
573   struct tm tm_tmp;
574   size_t status;
575
576   /* RFC 1123 *requires* the time to be GMT and the "GMT" timezone string.
577    * Apache will ignore the timezone if "localtime_r" and "%z" is used,
578    * resulting in weird behavior. */
579   if (gmtime_r (&t, &tm_tmp) == NULL)
580     return (errno);
581
582   status = strftime (buffer, buffer_size, "%a, %d %b %Y %T GMT", &tm_tmp);
583   if (status == 0)
584     return (errno);
585
586   return (0);
587 } /* }}} int time_to_rfc1123 */
588
589 #define COPY_ENTITY(e) do {    \
590   size_t len = strlen (e);     \
591   if (dest_size < (len + 1))   \
592     break;                     \
593   strcpy (dest_ptr, (e));      \
594   dest_ptr += len;             \
595   dest_size -= len;            \
596 } while (0)
597
598 char *html_escape_copy (char *dest, const char *src, size_t n) /* {{{ */
599 {
600   char *dest_ptr;
601   size_t dest_size;
602   size_t pos;
603
604   dest[0] = 0;
605   dest_ptr = dest;
606   dest_size = n;
607   for (pos = 0; src[pos] != 0; pos++)
608   {
609     if (src[pos] == '"')
610       COPY_ENTITY ("&quot;");
611     else if (src[pos] == '<')
612       COPY_ENTITY ("&lt;");
613     else if (src[pos] == '>')
614       COPY_ENTITY ("&gt;");
615     else if (src[pos] == '&')
616       COPY_ENTITY ("&amp;");
617     else
618     {
619       *dest_ptr = src[pos];
620       dest_ptr++;
621       dest_size--;
622       *dest_ptr = 0;
623     }
624
625     if (dest_size <= 1)
626       break;
627   }
628
629   return (dest);
630 } /* }}} char *html_escape_copy */
631
632 #undef COPY_ENTITY
633
634 char *html_escape_buffer (char *buffer, size_t buffer_size) /* {{{ */
635 {
636   char tmp[buffer_size];
637
638   html_escape_copy (tmp, buffer, sizeof (tmp));
639   memcpy (buffer, tmp, buffer_size);
640
641   return (buffer);
642 } /* }}} char *html_escape_buffer */
643
644 char *html_escape (const char *string) /* {{{ */
645 {
646   char buffer[4096];
647
648   if (string == NULL)
649     return (NULL);
650
651   html_escape_copy (buffer, string, sizeof (buffer));
652
653   return (strdup (buffer));
654 } /* }}} char *html_escape */
655
656 int html_print_page (const char *title, /* {{{ */
657     const page_callbacks_t *cb, void *user_data)
658 {
659   char *title_html;
660
661   printf ("Content-Type: text/html\n"
662       "X-Generator: "PACKAGE_STRING"\n"
663       "\n\n");
664
665   if (title == NULL)
666     title = "C&#x2084;: collection4 graph interface";
667
668   title_html = html_escape (title);
669
670   printf ("<html>\n"
671       "  <head>\n"
672       "    <title>%s</title>\n"
673       "    <link rel=\"stylesheet\" type=\"text/css\" href=\"../share/style.css\" />\n"
674       "    <script type=\"text/javascript\" src=\"../share/jquery-1.4.2.min.js\">\n"
675       "    </script>\n"
676       "    <script type=\"text/javascript\" src=\"../share/collection.js\">\n"
677       "    </script>\n"
678       "  </head>\n",
679       title_html);
680
681   printf ("  <body>\n"
682       "    <table id=\"layout-table\">\n"
683       "      <tr id=\"layout-top\">\n"
684       "        <td id=\"layout-top-left\">");
685   if (cb->top_left != NULL)
686     (*cb->top_left) (user_data);
687   else
688     html_print_logo (NULL);
689   printf ("</td>\n"
690       "        <td id=\"layout-top-center\">");
691   if (cb->top_center != NULL)
692     (*cb->top_center) (user_data);
693   else
694     printf ("<h1>%s</h1>", title_html);
695   printf ("</td>\n"
696       "        <td id=\"layout-top-right\">");
697   if (cb->top_right != NULL)
698     (*cb->top_right) (user_data);
699   printf ("</td>\n"
700       "      </tr>\n"
701       "      <tr id=\"layout-middle\">\n"
702       "        <td id=\"layout-middle-left\">");
703   if (cb->middle_left != NULL)
704     (*cb->middle_left) (user_data);
705   printf ("</td>\n"
706       "        <td id=\"layout-middle-center\">");
707   if (cb->middle_center != NULL)
708     (*cb->middle_center) (user_data);
709   printf ("</td>\n"
710       "        <td id=\"layout-middle-right\">");
711   if (cb->middle_right != NULL)
712     (*cb->middle_right) (user_data);
713   printf ("</td>\n"
714       "      </tr>\n"
715       "      <tr id=\"layout-bottom\">\n"
716       "        <td id=\"layout-bottom-left\">");
717   if (cb->bottom_left != NULL)
718     (*cb->bottom_left) (user_data);
719   printf ("</td>\n"
720       "        <td id=\"layout-bottom-center\">");
721   if (cb->bottom_center != NULL)
722     (*cb->bottom_center) (user_data);
723   printf ("</td>\n"
724       "        <td id=\"layout-bottom-right\">");
725   if (cb->bottom_right != NULL)
726     (*cb->bottom_right) (user_data);
727   printf ("</td>\n"
728       "      </tr>\n"
729       "    </table>\n"
730       "    <div class=\"footer\">"PACKAGE_STRING"</div>\n"
731       "  </body>\n"
732       "</html>\n");
733
734   free (title_html);
735   return (0);
736 } /* }}} int html_print_page */
737
738 int html_print_logo (__attribute__((unused)) void *user_data) /* {{{ */
739 {
740   printf ("<a href=\"%s?action=list_graphs\" id=\"logo-canvas\">\n"
741       "  <h1>C<sub>4</sub></h1>\n"
742       "  <div id=\"logo-subscript\">collection&nbsp;4</div>\n"
743       "</a>\n");
744
745   return (0);
746 } /* }}} int html_print_search_box */
747
748 int html_print_search_box (__attribute__((unused)) void *user_data) /* {{{ */
749 {
750   char *term_html;
751
752   term_html = html_escape (param ("q"));
753
754   printf ("<form action=\"%s\" method=\"get\" id=\"search-form\">\n"
755       "  <input type=\"hidden\" name=\"action\" value=\"search\" />\n"
756       "  <input type=\"text\" name=\"q\" value=\"%s\" id=\"search-input\" />\n"
757       "  <input type=\"submit\" name=\"button\" value=\"Search\" />\n"
758       "</form>\n",
759       script_name (),
760       (term_html != NULL) ? term_html : "");
761
762   free (term_html);
763
764   return (0);
765 } /* }}} int html_print_search_box */
766
767 /* vim: set sw=2 sts=2 et fdm=marker : */