src/utils_cgi.c: Implement "uri_escape_buffer".
[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         || (src[in] == '/')
440         || (((unsigned char) src[in]) >= 128))
441     {
442       char esc[4];
443
444       if ((n - out) < 4)
445         break;
446       
447       snprintf (esc, sizeof (esc), "%%%02x", (unsigned int) src[in]);
448       dest[out] = esc[0];
449       dest[out+1] = esc[1];
450       dest[out+2] = esc[2];
451
452       out += 3;
453       in++;
454     }
455     else
456     {
457       dest[out] = src[in];
458       out++;
459       in++;
460     }
461   } /* while (42) */
462
463   return (dest);
464 } /* }}} char *uri_escape_copy */
465
466 char *uri_escape_buffer (char *buffer, size_t buffer_size) /* {{{ */
467 {
468   char temp[buffer_size];
469
470   uri_escape_copy (temp, buffer, buffer_size);
471   memcpy (buffer, temp, buffer_size);
472
473   return (&buffer[0]);
474 } /* }}} char *uri_escape_buffer */
475
476 char *uri_escape (const char *string) /* {{{ */
477 {
478   char buffer[4096];
479
480   if (string == NULL)
481     return (NULL);
482
483   uri_escape_copy (buffer, string, sizeof (buffer));
484
485   return (strdup (buffer));
486 } /* }}} char *uri_escape */
487
488 #define COPY_ENTITY(e) do {    \
489   size_t len = strlen (e);     \
490   if (dest_size < (len + 1))   \
491     break;                     \
492   strcpy (dest_ptr, (e));      \
493   dest_ptr += len;             \
494   dest_size -= len;            \
495 } while (0)
496
497 char *json_escape_copy (char *dest, const char *src, size_t n) /* {{{ */
498 {
499   char *dest_ptr;
500   size_t dest_size;
501   size_t pos;
502
503   dest[0] = 0;
504   dest_ptr = dest;
505   dest_size = n;
506   for (pos = 0; src[pos] != 0; pos++)
507   {
508     if (src[pos] == '"')
509       COPY_ENTITY ("\\\"");
510     else if (src[pos] == '\\')
511       COPY_ENTITY ("\\\\");
512     else if (((uint8_t) src[pos]) < 32)
513     {
514       if (src[pos] == '\n')
515         COPY_ENTITY ("\\n");
516       else if (src[pos] == '\r')
517         COPY_ENTITY ("\\r");
518       else if (src[pos] == '\t')
519         COPY_ENTITY ("\\t");
520       else if (src[pos] == '\b')
521         COPY_ENTITY ("\\b");
522       else if (src[pos] == '\f')
523         COPY_ENTITY ("\\f");
524       else
525       {
526         char buffer[8];
527         sprintf (buffer, "\\u%04"PRIx8, (uint8_t) src[pos]);
528         buffer[sizeof (buffer) - 1] = 0;
529         COPY_ENTITY (buffer);
530       }
531     }
532     else
533     {
534       *dest_ptr = src[pos];
535       dest_ptr++;
536       dest_size--;
537       *dest_ptr = 0;
538     }
539
540     if (dest_size <= 1)
541       break;
542   }
543
544   return (dest);
545 } /* }}} char *json_escape_copy */
546
547 #undef COPY_ENTITY
548
549 char *json_escape_buffer (char *buffer, size_t buffer_size)
550 {
551   char temp[buffer_size];
552
553   json_escape_copy (temp, buffer, buffer_size);
554   memcpy (buffer, temp, buffer_size);
555
556   return (buffer);
557 } /* }}} char *json_escape_buffer */
558
559 char *json_escape (const char *string) /* {{{ */
560 {
561   char buffer[4096];
562
563   if (string == NULL)
564     return (NULL);
565
566   json_escape_copy (buffer, string, sizeof (buffer));
567
568   return (strdup (buffer));
569 } /* }}} char *json_escape */
570
571 const char *script_name (void) /* {{{ */
572 {
573   char *ret;
574
575   ret = getenv ("SCRIPT_NAME");
576   if (ret == NULL)
577     ret = "collection4.fcgi";
578
579   return (ret);
580 } /* }}} char *script_name */
581
582 int time_to_rfc1123 (time_t t, char *buffer, size_t buffer_size) /* {{{ */
583 {
584   struct tm tm_tmp;
585   size_t status;
586
587   /* RFC 1123 *requires* the time to be GMT and the "GMT" timezone string.
588    * Apache will ignore the timezone if "localtime_r" and "%z" is used,
589    * resulting in weird behavior. */
590   if (gmtime_r (&t, &tm_tmp) == NULL)
591     return (errno);
592
593   status = strftime (buffer, buffer_size, "%a, %d %b %Y %T GMT", &tm_tmp);
594   if (status == 0)
595     return (errno);
596
597   return (0);
598 } /* }}} int time_to_rfc1123 */
599
600 #define COPY_ENTITY(e) do {    \
601   size_t len = strlen (e);     \
602   if (dest_size < (len + 1))   \
603     break;                     \
604   strcpy (dest_ptr, (e));      \
605   dest_ptr += len;             \
606   dest_size -= len;            \
607 } while (0)
608
609 char *html_escape_copy (char *dest, const char *src, size_t n) /* {{{ */
610 {
611   char *dest_ptr;
612   size_t dest_size;
613   size_t pos;
614
615   dest[0] = 0;
616   dest_ptr = dest;
617   dest_size = n;
618   for (pos = 0; src[pos] != 0; pos++)
619   {
620     if (src[pos] == '"')
621       COPY_ENTITY ("&quot;");
622     else if (src[pos] == '<')
623       COPY_ENTITY ("&lt;");
624     else if (src[pos] == '>')
625       COPY_ENTITY ("&gt;");
626     else if (src[pos] == '&')
627       COPY_ENTITY ("&amp;");
628     else
629     {
630       *dest_ptr = src[pos];
631       dest_ptr++;
632       dest_size--;
633       *dest_ptr = 0;
634     }
635
636     if (dest_size <= 1)
637       break;
638   }
639
640   return (dest);
641 } /* }}} char *html_escape_copy */
642
643 #undef COPY_ENTITY
644
645 char *html_escape_buffer (char *buffer, size_t buffer_size) /* {{{ */
646 {
647   char tmp[buffer_size];
648
649   html_escape_copy (tmp, buffer, sizeof (tmp));
650   memcpy (buffer, tmp, buffer_size);
651
652   return (buffer);
653 } /* }}} char *html_escape_buffer */
654
655 char *html_escape (const char *string) /* {{{ */
656 {
657   char buffer[4096];
658
659   if (string == NULL)
660     return (NULL);
661
662   html_escape_copy (buffer, string, sizeof (buffer));
663
664   return (strdup (buffer));
665 } /* }}} char *html_escape */
666
667 int html_print_page (const char *title, /* {{{ */
668     const page_callbacks_t *cb, void *user_data)
669 {
670   char *title_html;
671
672   printf ("Content-Type: text/html\n"
673       "X-Generator: "PACKAGE_STRING"\n"
674       "\n\n");
675
676   if (title == NULL)
677     title = "C&#x2084;: collection4 graph interface";
678
679   title_html = html_escape (title);
680
681   printf ("<html>\n"
682       "  <head>\n"
683       "    <title>%s</title>\n"
684       "    <link rel=\"stylesheet\" type=\"text/css\" href=\"../share/style.css\" />\n"
685       "    <script type=\"text/javascript\" src=\"../share/jquery-1.4.2.min.js\">\n"
686       "    </script>\n"
687       "    <script type=\"text/javascript\" src=\"../share/collection.js\">\n"
688       "    </script>\n"
689       "  </head>\n",
690       title_html);
691
692   printf ("  <body>\n"
693       "    <table id=\"layout-table\">\n"
694       "      <tr id=\"layout-top\">\n"
695       "        <td id=\"layout-top-left\">");
696   if (cb->top_left != NULL)
697     (*cb->top_left) (user_data);
698   else
699     html_print_logo (NULL);
700   printf ("</td>\n"
701       "        <td id=\"layout-top-center\">");
702   if (cb->top_center != NULL)
703     (*cb->top_center) (user_data);
704   else
705     printf ("<h1>%s</h1>", title_html);
706   printf ("</td>\n"
707       "        <td id=\"layout-top-right\">");
708   if (cb->top_right != NULL)
709     (*cb->top_right) (user_data);
710   printf ("</td>\n"
711       "      </tr>\n"
712       "      <tr id=\"layout-middle\">\n"
713       "        <td id=\"layout-middle-left\">");
714   if (cb->middle_left != NULL)
715     (*cb->middle_left) (user_data);
716   printf ("</td>\n"
717       "        <td id=\"layout-middle-center\">");
718   if (cb->middle_center != NULL)
719     (*cb->middle_center) (user_data);
720   printf ("</td>\n"
721       "        <td id=\"layout-middle-right\">");
722   if (cb->middle_right != NULL)
723     (*cb->middle_right) (user_data);
724   printf ("</td>\n"
725       "      </tr>\n"
726       "      <tr id=\"layout-bottom\">\n"
727       "        <td id=\"layout-bottom-left\">");
728   if (cb->bottom_left != NULL)
729     (*cb->bottom_left) (user_data);
730   printf ("</td>\n"
731       "        <td id=\"layout-bottom-center\">");
732   if (cb->bottom_center != NULL)
733     (*cb->bottom_center) (user_data);
734   printf ("</td>\n"
735       "        <td id=\"layout-bottom-right\">");
736   if (cb->bottom_right != NULL)
737     (*cb->bottom_right) (user_data);
738   printf ("</td>\n"
739       "      </tr>\n"
740       "    </table>\n"
741       "    <div class=\"footer\">"PACKAGE_STRING"</div>\n"
742       "  </body>\n"
743       "</html>\n");
744
745   free (title_html);
746   return (0);
747 } /* }}} int html_print_page */
748
749 int html_print_logo (__attribute__((unused)) void *user_data) /* {{{ */
750 {
751   printf ("<a href=\"%s?action=list_graphs\" id=\"logo-canvas\">\n"
752       "  <h1>C<sub>4</sub></h1>\n"
753       "  <div id=\"logo-subscript\">collection&nbsp;4</div>\n"
754       "</a>\n");
755
756   return (0);
757 } /* }}} int html_print_search_box */
758
759 int html_print_search_box (__attribute__((unused)) void *user_data) /* {{{ */
760 {
761   char *term_html;
762
763   term_html = html_escape (param ("q"));
764
765   printf ("<form action=\"%s\" method=\"get\" id=\"search-form\">\n"
766       "  <input type=\"hidden\" name=\"action\" value=\"search\" />\n"
767       "  <input type=\"text\" name=\"q\" value=\"%s\" id=\"search-input\" />\n"
768       "  <input type=\"submit\" name=\"button\" value=\"Search\" />\n"
769       "</form>\n",
770       script_name (),
771       (term_html != NULL) ? term_html : "");
772
773   free (term_html);
774
775   return (0);
776 } /* }}} int html_print_search_box */
777
778 /* vim: set sw=2 sts=2 et fdm=marker : */