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