Code

src/utils_cgi.c: Fix paths of included JavaScript and CSS files.
[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  **/
24 #include "config.h"
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>
34 #include "utils_cgi.h"
35 #include "common.h"
37 #include <fcgiapp.h>
38 #include <fcgi_stdio.h>
40 struct parameter_s
41 {
42   char *key;
43   char *value;
44 };
45 typedef struct parameter_s parameter_t;
47 struct param_list_s
48 {
49   parameter_t *parameters;
50   size_t parameters_num;
51 };
53 static param_list_t *pl_global = NULL;
55 static char *uri_unescape_copy (char *dest, const char *src, size_t n) /* {{{ */
56 {
57   const char *src_ptr;
58   char *dest_ptr;
60   if ((dest == NULL) || (src == NULL) || (n < 1))
61     return (NULL);
63   src_ptr = src;
64   dest_ptr = dest;
66   *dest_ptr = 0;
68   while (*src_ptr != 0)
69   {
70     if (n < 2)
71       break;
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;
84       tmpstr[0] = src_ptr[1];
85       tmpstr[1] = src_ptr[2];
86       tmpstr[2] = 0;
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       }
100       src_ptr += 2;
101     }
102     else
103     {
104       *dest_ptr = *src_ptr;
105     }
107     n--;
108     src_ptr++;
109     dest_ptr++;
110     *dest_ptr = 0;
111   } /* while (*src_ptr != 0) */
113   assert (*dest_ptr == 0);
114   return (dest);
115 } /* }}} char *uri_unescape_copy */
117 static char *uri_unescape (const char *string) /* {{{ */
119   char buffer[4096];
121   if (string == NULL)
122     return (NULL);
124   uri_unescape_copy (buffer, string, sizeof (buffer));
125   
126   return (strdup (buffer));
127 } /* }}} char *uri_unescape */
129 static int param_parse_keyval (param_list_t *pl, char *keyval) /* {{{ */
131   char *key_raw;
132   char *value_raw;
133   char *key;
134   char *value;
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++;
143   key = uri_unescape (key_raw);
144   if (key == NULL)
145     return (ENOMEM);
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);
156   free (key);
157   free (value);
159   return (0);
160 } /* }}} int param_parse_keyval */
162 static int parse_query_string (param_list_t *pl, /* {{{ */
163     char *query_string)
165   char *dummy;
166   char *keyval;
168   if ((pl == NULL) || (query_string == NULL))
169     return (EINVAL);
171   dummy = query_string;
172   while ((keyval = strtok (dummy, ";&")) != NULL)
173   {
174     dummy = NULL;
175     param_parse_keyval (pl, keyval);
176   }
178   return (0);
179 } /* }}} int parse_query_string */
181 int param_init (void) /* {{{ */
183   if (pl_global != NULL)
184     return (0);
186   pl_global = param_create (/* query string = */ NULL);
187   if (pl_global == NULL)
188     return (ENOMEM);
190   return (0);
191 } /* }}} int param_init */
193 void param_finish (void) /* {{{ */
195   param_destroy (pl_global);
196   pl_global = NULL;
197 } /* }}} void param_finish */
199 const char *param (const char *key) /* {{{ */
201   param_init ();
203   return (param_get (pl_global, key));
204 } /* }}} const char *param */
206 param_list_t *param_create (const char *query_string) /* {{{ */
208   char *tmp;
209   param_list_t *pl;
211   if (query_string == NULL)
212     query_string = getenv ("QUERY_STRING");
214   if (query_string == NULL)
215     return (NULL);
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));
229   parse_query_string (pl, tmp);
231   free (tmp);
232   return (pl);
233 } /* }}} param_list_t *param_create */
235   param_list_t *param_clone (__attribute__((unused)) param_list_t *pl) /* {{{ */
237   /* FIXME: To be implemented. */
238   assert (23 == 42);
239   return (NULL);
240 } /* }}} param_list_t *param_clone */
242 void param_destroy (param_list_t *pl) /* {{{ */
244   size_t i;
246   if (pl == NULL)
247     return;
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 */
258 const char *param_get (param_list_t *pl, const char *name) /* {{{ */
260   size_t i;
262   if ((pl == NULL) || (name == NULL))
263     return (NULL);
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   }
274   return (NULL);
275 } /* }}} char *param_get */
277 static int param_add (param_list_t *pl, /* {{{ */
278     const char *key, const char *value)
280   parameter_t *tmp;
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;
289   memset (tmp, 0, sizeof (*tmp));
290   tmp->key = strdup (key);
291   if (tmp->key == NULL)
292     return (ENOMEM);
294   tmp->value = strdup (value);
295   if (tmp->value == NULL)
296   {
297     free (tmp->key);
298     return (ENOMEM);
299   }
301   pl->parameters_num++;
303   return (0);
304 } /* }}} int param_add */
306 static int param_delete (param_list_t *pl, /* {{{ */
307     const char *name)
309   size_t i;
311   if ((pl == NULL) || (name == NULL))
312     return (EINVAL);
314   for (i = 0; i < pl->parameters_num; i++)
315     if (strcasecmp (pl->parameters[i].key, name) == 0)
316       break;
318   if (i >= pl->parameters_num)
319     return (ENOENT);
321   if (i < (pl->parameters_num - 1))
322   {
323     parameter_t p;
325     p = pl->parameters[i];
326     pl->parameters[i] = pl->parameters[pl->parameters_num - 1];
327     pl->parameters[pl->parameters_num - 1] = p;
328   }
330   pl->parameters_num--;
331   free (pl->parameters[pl->parameters_num].key);
332   free (pl->parameters[pl->parameters_num].value);
334   return (0);
335 } /* }}} int param_delete */
337 int param_set (param_list_t *pl, const char *name, /* {{{ */
338     const char *value)
340   parameter_t *p;
341   char *value_copy;
342   size_t i;
344   if ((pl == NULL) || (name == NULL))
345     return (EINVAL);
347   if (value == NULL)
348     return (param_delete (pl, name));
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   }
360   if (p == NULL)
361     return (param_add (pl, name, value));
363   value_copy = strdup (value);
364   if (value_copy == NULL)
365     return (ENOMEM);
367   free (p->value);
368   p->value = value_copy;
370   return (0);
371 } /* }}} int param_set */
373 char *param_as_string (param_list_t *pl) /* {{{ */
375   char buffer[4096];
376   char key[2048];
377   char value[2048];
378   size_t i;
380   if (pl == NULL)
381     return (NULL);
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));
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   }
396   return (strdup (buffer));
397 } /* }}} char *param_as_string */
399 int param_print_hidden (param_list_t *pl) /* {{{ */
401   char key[2048];
402   char value[2048];
403   size_t i;
405   if (pl == NULL)
406     return (EINVAL);
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));
413     printf ("  <input type=\"hidden\" name=\"%s\" value=\"%s\" />\n",
414         key, value);
415   }
417   return (0);
418 } /* }}} int param_print_hidden */
420 char *uri_escape_copy (char *dest, const char *src, size_t n) /* {{{ */
422   size_t in;
423   size_t out;
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         /* RFC 3986, gen-delims */
437         || (src[in] == ':') || (src[in] == '/') || (src[in] == '?')
438         || (src[in] == '#') || (src[in] == '[') || (src[in] == ']')
439         || (src[in] == '@')
440         /* RFC 3986, sub-delims */
441         || (src[in] == '!') || (src[in] == '$') || (src[in] == '&')
442         || (src[in] == '(') || (src[in] == ')') || (src[in] == '*')
443         || (src[in] == '+') || (src[in] == ',') || (src[in] == ';')
444         || (src[in] == '=') || (src[in] == '\'')
445         /* 8-bit data */
446         || (((unsigned char) src[in]) >= 128))
447     {
448       char esc[4];
450       if ((n - out) < 4)
451         break;
452       
453       snprintf (esc, sizeof (esc), "%%%02x", (unsigned int) src[in]);
454       dest[out] = esc[0];
455       dest[out+1] = esc[1];
456       dest[out+2] = esc[2];
458       out += 3;
459       in++;
460     }
461     else
462     {
463       dest[out] = src[in];
464       out++;
465       in++;
466     }
467   } /* while (42) */
469   return (dest);
470 } /* }}} char *uri_escape_copy */
472 char *uri_escape_buffer (char *buffer, size_t buffer_size) /* {{{ */
474   char temp[buffer_size];
476   uri_escape_copy (temp, buffer, buffer_size);
477   memcpy (buffer, temp, buffer_size);
479   return (&buffer[0]);
480 } /* }}} char *uri_escape_buffer */
482 char *uri_escape (const char *string) /* {{{ */
484   char buffer[4096];
486   if (string == NULL)
487     return (NULL);
489   uri_escape_copy (buffer, string, sizeof (buffer));
491   return (strdup (buffer));
492 } /* }}} char *uri_escape */
494 #define COPY_ENTITY(e) do {    \
495   size_t len = strlen (e);     \
496   if (dest_size < (len + 1))   \
497     break;                     \
498   strcpy (dest_ptr, (e));      \
499   dest_ptr += len;             \
500   dest_size -= len;            \
501 } while (0)
503 char *json_escape_copy (char *dest, const char *src, size_t n) /* {{{ */
505   char *dest_ptr;
506   size_t dest_size;
507   size_t pos;
509   dest[0] = 0;
510   dest_ptr = dest;
511   dest_size = n;
512   for (pos = 0; src[pos] != 0; pos++)
513   {
514     if (src[pos] == '"')
515       COPY_ENTITY ("\\\"");
516     else if (src[pos] == '\\')
517       COPY_ENTITY ("\\\\");
518     else if (((uint8_t) src[pos]) < 32)
519     {
520       if (src[pos] == '\n')
521         COPY_ENTITY ("\\n");
522       else if (src[pos] == '\r')
523         COPY_ENTITY ("\\r");
524       else if (src[pos] == '\t')
525         COPY_ENTITY ("\\t");
526       else if (src[pos] == '\b')
527         COPY_ENTITY ("\\b");
528       else if (src[pos] == '\f')
529         COPY_ENTITY ("\\f");
530       else
531       {
532         char buffer[8];
533         sprintf (buffer, "\\u%04"PRIx8, (uint8_t) src[pos]);
534         buffer[sizeof (buffer) - 1] = 0;
535         COPY_ENTITY (buffer);
536       }
537     }
538     else
539     {
540       *dest_ptr = src[pos];
541       dest_ptr++;
542       dest_size--;
543       *dest_ptr = 0;
544     }
546     if (dest_size <= 1)
547       break;
548   }
550   return (dest);
551 } /* }}} char *json_escape_copy */
553 #undef COPY_ENTITY
555 char *json_escape_buffer (char *buffer, size_t buffer_size)
557   char temp[buffer_size];
559   json_escape_copy (temp, buffer, buffer_size);
560   memcpy (buffer, temp, buffer_size);
562   return (buffer);
563 } /* }}} char *json_escape_buffer */
565 char *json_escape (const char *string) /* {{{ */
567   char buffer[4096];
569   if (string == NULL)
570     return (NULL);
572   json_escape_copy (buffer, string, sizeof (buffer));
574   return (strdup (buffer));
575 } /* }}} char *json_escape */
577 const char *script_name (void) /* {{{ */
579   char *ret;
581   ret = getenv ("SCRIPT_NAME");
582   if (ret == NULL)
583     ret = "collection4.fcgi";
585   return (ret);
586 } /* }}} char *script_name */
588 int time_to_rfc1123 (time_t t, char *buffer, size_t buffer_size) /* {{{ */
590   struct tm tm_tmp;
591   size_t status;
593   /* RFC 1123 *requires* the time to be GMT and the "GMT" timezone string.
594    * Apache will ignore the timezone if "localtime_r" and "%z" is used,
595    * resulting in weird behavior. */
596   if (gmtime_r (&t, &tm_tmp) == NULL)
597     return (errno);
599   status = strftime (buffer, buffer_size, "%a, %d %b %Y %T GMT", &tm_tmp);
600   if (status == 0)
601     return (errno);
603   return (0);
604 } /* }}} int time_to_rfc1123 */
606 #define COPY_ENTITY(e) do {    \
607   size_t len = strlen (e);     \
608   if (dest_size < (len + 1))   \
609     break;                     \
610   strcpy (dest_ptr, (e));      \
611   dest_ptr += len;             \
612   dest_size -= len;            \
613 } while (0)
615 char *html_escape_copy (char *dest, const char *src, size_t n) /* {{{ */
617   char *dest_ptr;
618   size_t dest_size;
619   size_t pos;
621   dest[0] = 0;
622   dest_ptr = dest;
623   dest_size = n;
624   for (pos = 0; src[pos] != 0; pos++)
625   {
626     if (src[pos] == '"')
627       COPY_ENTITY ("&quot;");
628     else if (src[pos] == '<')
629       COPY_ENTITY ("&lt;");
630     else if (src[pos] == '>')
631       COPY_ENTITY ("&gt;");
632     else if (src[pos] == '&')
633       COPY_ENTITY ("&amp;");
634     else
635     {
636       *dest_ptr = src[pos];
637       dest_ptr++;
638       dest_size--;
639       *dest_ptr = 0;
640     }
642     if (dest_size <= 1)
643       break;
644   }
646   return (dest);
647 } /* }}} char *html_escape_copy */
649 #undef COPY_ENTITY
651 char *html_escape_buffer (char *buffer, size_t buffer_size) /* {{{ */
653   char tmp[buffer_size];
655   html_escape_copy (tmp, buffer, sizeof (tmp));
656   memcpy (buffer, tmp, buffer_size);
658   return (buffer);
659 } /* }}} char *html_escape_buffer */
661 char *html_escape (const char *string) /* {{{ */
663   char buffer[4096];
665   if (string == NULL)
666     return (NULL);
668   html_escape_copy (buffer, string, sizeof (buffer));
670   return (strdup (buffer));
671 } /* }}} char *html_escape */
673 int html_print_page (const char *title, /* {{{ */
674     const page_callbacks_t *cb, void *user_data)
676   char *title_html;
678   printf ("Content-Type: text/html\n"
679       "X-Generator: "PACKAGE_STRING"\n"
680       "\n\n");
682   if (title == NULL)
683     title = "C&#x2084;: collection4 graph interface";
685   title_html = html_escape (title);
687   printf ("<html>\n"
688       "  <head>\n"
689       "    <title>%s</title>\n"
690       "    <link rel=\"stylesheet\" type=\"text/css\" href=\"../share/"PACKAGE"/style.css\" />\n"
691       "    <script type=\"text/javascript\" src=\"../share/"PACKAGE"/jquery-1.4.2.min.js\">\n"
692       "    </script>\n"
693       "    <script type=\"text/javascript\" src=\"../share/"PACKAGE"/raphael-min.js\">\n"
694       "    </script>\n"
695       "    <script type=\"text/javascript\" src=\"../share/"PACKAGE"/g.raphael-min.js\">\n"
696       "    </script>\n"
697       "    <script type=\"text/javascript\" src=\"../share/"PACKAGE"/g.line-min.js\">\n"
698       "    </script>\n"
699       "    <script type=\"text/javascript\" src=\"../share/"PACKAGE"/collection.js\">\n"
700       "    </script>\n"
701       "  </head>\n",
702       title_html);
704   printf ("  <body>\n"
705       "    <table id=\"layout-table\">\n"
706       "      <tr id=\"layout-top\">\n"
707       "        <td id=\"layout-top-left\">");
708   if (cb->top_left != NULL)
709     (*cb->top_left) (user_data);
710   else
711     html_print_logo (NULL);
712   printf ("</td>\n"
713       "        <td id=\"layout-top-center\">");
714   if (cb->top_center != NULL)
715     (*cb->top_center) (user_data);
716   else
717     printf ("<h1>%s</h1>", title_html);
718   printf ("</td>\n"
719       "        <td id=\"layout-top-right\">");
720   if (cb->top_right != NULL)
721     (*cb->top_right) (user_data);
722   printf ("</td>\n"
723       "      </tr>\n"
724       "      <tr id=\"layout-middle\">\n"
725       "        <td id=\"layout-middle-left\">");
726   if (cb->middle_left != NULL)
727     (*cb->middle_left) (user_data);
728   printf ("</td>\n"
729       "        <td id=\"layout-middle-center\">");
730   if (cb->middle_center != NULL)
731     (*cb->middle_center) (user_data);
732   printf ("</td>\n"
733       "        <td id=\"layout-middle-right\">");
734   if (cb->middle_right != NULL)
735     (*cb->middle_right) (user_data);
736   printf ("</td>\n"
737       "      </tr>\n"
738       "      <tr id=\"layout-bottom\">\n"
739       "        <td id=\"layout-bottom-left\">");
740   if (cb->bottom_left != NULL)
741     (*cb->bottom_left) (user_data);
742   printf ("</td>\n"
743       "        <td id=\"layout-bottom-center\">");
744   if (cb->bottom_center != NULL)
745     (*cb->bottom_center) (user_data);
746   printf ("</td>\n"
747       "        <td id=\"layout-bottom-right\">");
748   if (cb->bottom_right != NULL)
749     (*cb->bottom_right) (user_data);
750   printf ("</td>\n"
751       "      </tr>\n"
752       "    </table>\n"
753       "    <div class=\"footer\"><a href=\"http://octo.it/c4/\">"PACKAGE_STRING"</a></div>\n"
754       "  </body>\n"
755       "</html>\n");
757   free (title_html);
758   return (0);
759 } /* }}} int html_print_page */
761 int html_print_logo (__attribute__((unused)) void *user_data) /* {{{ */
763   printf ("<a href=\"%s?action=list_graphs\" id=\"logo-canvas\">\n"
764       "  <h1>C<sub>4</sub></h1>\n"
765       "  <div id=\"logo-subscript\">collection&nbsp;4</div>\n"
766       "</a>\n", script_name ());
768   return (0);
769 } /* }}} int html_print_search_box */
771 int html_print_search_box (__attribute__((unused)) void *user_data) /* {{{ */
773   char *term_html;
775   term_html = html_escape (param ("q"));
777   printf ("<form action=\"%s\" method=\"get\" id=\"search-form\">\n"
778       "  <input type=\"hidden\" name=\"action\" value=\"search\" />\n"
779       "  <input type=\"text\" name=\"q\" value=\"%s\" id=\"search-input\" />\n"
780       "  <input type=\"submit\" name=\"button\" value=\"Search\" />\n"
781       "</form>\n",
782       script_name (),
783       (term_html != NULL) ? term_html : "");
785   free (term_html);
787   return (0);
788 } /* }}} int html_print_search_box */
790 /* vim: set sw=2 sts=2 et fdm=marker : */