b4c8901f040e54430c36007ec2fb09d37d276c75
[webgit] / src / webgit.c
1 /*
2     cehtehs git web frontend
3
4   Copyright (C)
5     2007, 2008,         Christian Thaeter <ct@pipapo.org>
6
7   This program is free software: you can redistribute it and/or modify
8   it under the terms of the GNU Affero General Public License as published by
9   the Free Software Foundation, either version 3 of the License, or
10   (at your option) any later version.
11
12   This program is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU Affero General Public License for more details.
16
17   You should have received a copy of the GNU Affero General Public License
18   along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21
22 #include "webgit.h"
23
24 #include "login.h"
25 #include "actions.h"
26 #include "query.h"
27 #include "options.h"
28 #include "rxpd_client.h"
29
30 #include "git/git-compat-util.h"
31
32 #include <cwa.h>
33
34 #include <stdio.h>
35 #include <stdarg.h>
36 #include <string.h>
37 #include <time.h>
38 #include <setjmp.h>
39
40 Html error_log;
41 jmp_buf err_jmp;
42
43 static void
44 webgit_err_vargs (const char *err, va_list params)
45 {
46   html_list_append (error_log, html (html_fmt_vargs (err, params, html_print_escaped), "<br />"));
47   longjmp (err_jmp, 0);
48 }
49
50
51 void
52 webgit_err (const char *err, ...)
53 {
54   va_list args;
55   va_start (args, err);
56   webgit_err_vargs (err, args);
57   va_end (args);
58 }
59
60
61 static void
62 webgit_warn_vargs (const char *err, va_list params)
63 {
64   html_list_append (error_log, html (html_fmt_vargs (err, params, html_print_escaped), "<br />"));
65 }
66
67
68 void
69 webgit_warn (const char *err, ...)
70 {
71   va_list args;
72   va_start (args, err);
73   webgit_warn_vargs (err, args);
74   va_end (args);
75 }
76
77
78 static void
79 webgit_setup (void)
80 {
81   set_die_routine (webgit_err_vargs);
82   set_error_routine (webgit_err_vargs);
83   set_warn_routine (webgit_warn_vargs);
84 }
85
86
87 char*
88 webgit_buffer_provide (size_t size)
89 {
90   static char* buffers[32];
91   static size_t sizes[32];
92   static unsigned idx;
93
94   idx = (idx + 1) & 0x1f;
95
96   if (sizes[idx] < size)
97     {
98       free (buffers[idx]);
99       sizes[idx] = (size+32) & ~0x1f;
100       buffers[idx] = cwa_malloc (sizes[idx]);
101     }
102   return buffers[idx];
103 }
104
105
106 char*
107 webgit_webskinpath (struct webgit_query* query, const char* element)
108 {
109   char* buffer = webgit_buffer_provide (strlen (query->webskindir) + strlen (query->skin) + strlen (element) + 3);
110   sprintf (buffer, "%s/%s/%s", query->webskindir, query->skin, element);
111   return buffer;
112 }
113
114
115 char*
116 webgit_skinpath (struct webgit_query* query, const char* element)
117 {
118   char* buffer = webgit_buffer_provide (strlen (query->skindir) + strlen (query->skin) + strlen (element) + 3);
119   sprintf (buffer, "%s/%s/%s", query->skindir, query->skin, element);
120   return buffer;
121 }
122
123
124 Html
125 webgit_email_link (Html name, Html email)
126 {
127   return html (
128                html_tag("a",
129                         html_attr ("href",
130                                    html ("mailto:", email))
131                         ),
132                name
133                );
134 }
135
136
137 char*
138 webgit_mimetype (const char* name)
139 {
140   char buf[256];
141
142   if (!name)
143     return NULL;
144
145   char* ext = strrchr (name, '.');
146   if (!ext)
147     return NULL;
148   ++ext;
149   if (!*ext)
150     return NULL;
151
152   FILE* mime_types = fopen ("/etc/mime.types", "r");
153   if (!mime_types)
154     return NULL;
155
156   while (fgets (buf, 256, mime_types))
157     {
158       if (buf[0] == '#' || !strtok (buf, " \t\n"))
159         continue;
160
161       char* suffix;
162       while ((suffix = strtok (NULL, " \t\n")))
163         if (!strcmp (suffix, ext))
164           {
165             fclose (mime_types);
166             return cwa_strndup (buf, SIZE_MAX);
167           }
168     }
169   return NULL;
170 }
171
172
173 int
174 main (int argc, char**argv)
175 {
176   Html page;
177   struct webgit_query query;
178
179   setreuid (geteuid (), -1);
180   setregid (getegid (), -1);
181
182   webgit_setup();
183
184   error_log = html_list();
185
186   clock_t timestat = clock();
187
188   webgit_query_init (&query);
189
190   /* set some defaults if not already set */
191   setenv ("REQUEST_METHOD", "GET", 0);
192   setenv ("SCRIPT_NAME", argv[0], 0);
193   setenv ("WEBGIT_CONFIG", WEBGIT_CONFIG, 0);
194
195   /* we can only longjmp out of libgit's error handling */
196   if (setjmp (err_jmp))
197     goto error;
198
199   if (webgit_commandline_dispatch (argc, argv, &query))
200     goto error;
201
202   // read config
203   if (webgit_configfile_dispatch (getenv("WEBGIT_CONFIG"), &query))
204     goto error;
205
206
207   // parse request/query What to show?
208   query.request = cgi_new ();
209
210   cgi_run_query (query.request, webgit_param_dispatch, &query);
211
212   if (query.count < 0)
213     query.count = query.count_def;
214
215   /* dwim, select/complete actions and parameters */
216   const char* default_action = "object";
217
218   if (!query.repo)
219     default_action = "main";
220   else
221     {
222       if (!query.object)
223         {
224           if (!query.head)
225             {
226               default_action = "summary";
227             }
228           else
229             {
230               if (!query.commit)
231                 default_action = "log";
232               else
233                 {
234                   if (!query.path)
235                     {
236                       query.object = cwa_strndup (query.commit, SIZE_MAX);
237                     }
238                 }
239             }
240         }
241     }
242
243   if (!query.action)
244     query.action = cwa_strndup (default_action, SIZE_MAX);
245
246   /* initial rxpd check, just a 'global' access tuple */
247   if (query.rxpd)
248     {
249       RxpdClient rxpd = rxpd_client_new (query.rxpd, "webgit/");  /*TODO config rxpd prefix*/
250       rxpd_client_cmd (rxpd, "CHECK", "access");
251
252       if (query.request->user_agent && strchr (query.request->user_agent, '\n'))
253         webgit_err ("I dont like you");
254
255       rxpd_client_query (rxpd,
256                          "HOST=%s:USER-AGENT=%s:ACTION=%s:REPO=%s:HEAD=%s\n\n",
257                          query.request->user_addr?query.request->user_addr:"",
258                          query.request->user_agent?query.request->user_agent:"",
259                          query.action,
260                          query.repo?query.repo:"",
261                          query.head?query.head:""
262                          );
263
264       if (rxpd_client_pending (rxpd, 1000) < 1)
265         webgit_err ("rxpd check failed");
266
267       if (!!strncmp (rxpd_client_recieve (rxpd), "allow:", 6))
268         webgit_err ("access denied HOST=%s:USER-AGENT=%s:ACTION=%s:REPO=%s:HEAD=%s\n\n",
269                     query.request->user_addr?query.request->user_addr:"",
270                     query.request->user_agent?query.request->user_agent:"",
271                     query.action,
272                     query.repo?query.repo:"",
273                     query.head?query.head:""
274                     );
275
276       rxpd_client_free (rxpd);
277     }
278
279   // query to cache-line
280
281   // check if in cache
282
283
284   // Cookie prep
285
286   time_t expire = query.now + query.cookie_expire;
287   char expire_buf[32];
288   strftime (expire_buf, 32, "%a, %d %b %Y %T %z", gmtime (&expire));
289
290   webgit_login_bakecookie (&query);
291
292   // generate page
293   Html content = webgit_action_dispatch (&query);
294
295   if (query.content_type && !strcmp (query.content_type, "application/xhtml+xml"))
296     {
297       /* generate html page */
298
299       page = html(
300                   html_httpheader(
301                                   html_httpfield (
302                                                   "Content-type",
303                                                   query.content_type,
304                                                   html_attr("charset", "UTF-8")
305                                                   ),
306                                   query.login_cookie ?
307                                   html_httpfield (
308                                                   "Set-Cookie",
309                                                   html_attr ("login", html_str_printfn (query.login_cookie,
310                                                                                         html_print_urlencoded)),
311                                                   html_attr ("path", query.request->script_name),
312                                                   html_attr ("expires", expire_buf)
313                                                   // domain ...
314                                                   ) : html ()
315                                   //html_httpfield("Last-Modified", cgit_page.modified),
316                                   //html_httpfield("Expires", cgit_page.expires)
317                                   ),
318                   html_document(
319                                 "-//W3C//DTD XHTML 1.0 Transitional//EN",
320                                 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd",
321                                 html_content(
322                                              /*head*/
323                                              html(
324                                                   html(html_tag("title"), "webgit"),
325                                                   html_nl(),
326                                                   html_meta("generator", "webgit"),
327                                                   html_link_rel("stylesheet", "text/css",
328                                                                 webgit_webskinpath (&query, "css/webgit.css")
329                                                                 ),
330                                                   html_link_rel("icon", "image/png",
331                                                                 webgit_webskinpath (&query, "icons/favicon.png")
332                                                                 )
333 #ifdef ENABLE_CODEPRESS
334                                                   , html (
335                                                           html_tag ("script",
336                                                                     html_attr ("src", "/codepress/codepress.js"),
337                                                                     html_attr ("type", "text/javascript")
338                                                                     )
339                                                           )
340 #endif
341
342 #ifdef ENABLE_SORTTABLE
343                                                   /*
344                                                     TODO fix this (goibhniu)
345                                                     , html (
346                                                           html_tag ("script",
347                                                                     html_attr ("src", 
348                                                                                webgit_webskinpath (&query, "js/sorttable.js")
349                                                                                ),
350                                                                     html_attr ("type", "text/javascript")
351                                                                     )
352                                                                     ) */
353 #endif
354
355                                                   ),
356                                              /*body*/
357                                              html (
358                                                    html (
359                                                          html_tag("div",
360                                                                   html_attr("id", "container")
361                                                                   ),
362                                                          /*
363                                                            TODO header.inc should be configurable and may not exist
364                                                             The idea is to have a project header and footer available
365                                                             as plain html so that someone can install webgit
366                                                             and quickly add their project logo, menu back to the main
367                                                             project website and any other notices they may wish to add
368                                                          */
369                                                          html (
370                                                                html_tag("div",
371                                                                         html_attr("id", "header")),
372                                                                html_include (webgit_skinpath (&query, "inc/header.inc"))
373                                                                ),
374                                                          content,
375                                                          html(
376                                                               html_tag("div",
377                                                                        html_attr("id", "errors")
378                                                                        ),
379                                                               error_log
380                                                               ),
381                                                          // See TODO header.inc above
382                                                          html (
383                                                                html_tag("div",
384                                                                         html_attr("id", "footer")),
385                                                                html_include (webgit_skinpath (&query, "inc/footer.inc"))
386                                                                )
387
388                                                          )
389                                                    ),
390                                              html_attr ("xmlns", "http://www.w3.org/1999/xhtml"),
391                                              html_attr ("xml:lang", "en"),
392                                              html_attr ("lang", "en")
393                                              )
394                                 )
395                   );
396
397       timestat = clock() - timestat;
398       html_list_append (error_log, html (
399                                          html_tag("div", html_attr ("id", "timestat")),
400                                          html_fmt ("processing took %g secs\n", timestat/(float)CLOCKS_PER_SEC)
401                                          )
402                         );
403
404     }
405   else
406     {
407       /* diffrent content type, raw content */
408       html_free (error_log);
409
410       page = html(
411                   html_httpheader (
412                                    html_httpfield ("Content-type",
413                                                    query.content_type ?
414                                                    query.content_type
415                                                    : "application/octet-stream")
416                                    ),
417                   content
418                   );
419     }
420
421   html_fprint (stdout, page);
422
423   html_free (page);
424
425   webgit_query_destroy (&query);
426
427   return 0;
428
429  error:
430   page = html(
431               html_httpheader(
432                               html_httpfield(
433                                              "Content-type",
434                                              "application/xhtml+xml",
435                                              html_attr("charset", "UTF-8")
436                                              )                              ),
437               html_document(
438                             "-//W3C//DTD XHTML 1.0 Transitional//EN",
439                             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd",
440                             html_content(
441                                          /*head*/
442                                          html(
443                                               html(html_tag("title"), "ERROR: webgit"),
444                                               html_nl(),
445                                               html_meta("generator", "webgit")
446                                               ),
447                                          /*body*/
448                                          html(
449                                               html_tag("div"),
450                                               error_log
451                                               ),
452                                          html_attr("xmlns", "http://www.w3.org/1999/xhtml"),
453                                          html_attr("xml:lang", "en"),
454                                          html_attr("lang", "en")
455                                          )
456                             )
457               );
458
459   html_fprint (stdout, page);
460   html_free (page);
461   return 10;
462 }
463
464
465 /*
466 //      Local Variables:
467 //      mode: C
468 //      c-file-style: "gnu"
469 //      indent-tabs-mode: nil
470 //      End:
471 */