ad7d701e873f0822e88a1afaae34360874687e12
[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 ( /*TODO, only if action == object && object == blob*/
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                                                     , html (
344                                                           html_tag ("script",
345                                                                     html_attr ("src",
346                                                                                webgit_webskinpath (&query, "js/sorttable.js")
347                                                                                ),
348                                                                     html_attr ("type", "text/javascript")
349                                                                     )
350                                                             )
351 #endif
352
353                                                   ),
354                                              /*body*/
355                                              html (
356                                                    html (
357                                                          html_tag("div",
358                                                                   html_attr("id", "container")
359                                                                   ),
360                                                          /*
361                                                            TODO header.inc should be configurable and may not exist
362                                                             The idea is to have a project header and footer available
363                                                             as plain html so that someone can install webgit
364                                                             and quickly add their project logo, menu back to the main
365                                                             project website and any other notices they may wish to add
366                                                          */
367                                                          html (
368                                                                html_tag("div",
369                                                                         html_attr("id", "header")),
370                                                                html_include (webgit_skinpath (&query, "inc/header.inc"))
371                                                                ),
372                                                          content,
373                                                          html(
374                                                               html_tag("div",
375                                                                        html_attr("id", "errors")
376                                                                        ),
377                                                               error_log
378                                                               ),
379                                                          // See TODO header.inc above
380                                                          html (
381                                                                html_tag("div",
382                                                                         html_attr("id", "footer")),
383                                                                html_include (webgit_skinpath (&query, "inc/footer.inc"))
384                                                                )
385
386                                                          )
387                                                    ),
388                                              html_attr ("xmlns", "http://www.w3.org/1999/xhtml"),
389                                              html_attr ("xml:lang", "en"),
390                                              html_attr ("lang", "en")
391                                              )
392                                 )
393                   );
394
395       timestat = clock() - timestat;
396       html_list_append (error_log, html (
397                                          html_tag("div", html_attr ("id", "timestat")),
398                                          html_fmt ("processing took %g secs\n", timestat/(float)CLOCKS_PER_SEC)
399                                          )
400                         );
401
402     }
403   else
404     {
405       /* diffrent content type, raw content */
406       html_free (error_log);
407
408       page = html(
409                   html_httpheader (
410                                    html_httpfield ("Content-type",
411                                                    query.content_type ?
412                                                    query.content_type
413                                                    : "application/octet-stream")
414                                    ),
415                   content
416                   );
417     }
418
419   html_fprint (stdout, page);
420
421   html_free (page);
422
423   webgit_query_destroy (&query);
424
425   return 0;
426
427  error:
428   page = html(
429               html_httpheader(
430                               html_httpfield(
431                                              "Content-type",
432                                              "application/xhtml+xml",
433                                              html_attr("charset", "UTF-8")
434                                              )                              ),
435               html_document(
436                             "-//W3C//DTD XHTML 1.0 Transitional//EN",
437                             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd",
438                             html_content(
439                                          /*head*/
440                                          html(
441                                               html(html_tag("title"), "ERROR: webgit"),
442                                               html_nl(),
443                                               html_meta("generator", "webgit")
444                                               ),
445                                          /*body*/
446                                          html(
447                                               html_tag("div"),
448                                               error_log
449                                               ),
450                                          html_attr("xmlns", "http://www.w3.org/1999/xhtml"),
451                                          html_attr("xml:lang", "en"),
452                                          html_attr("lang", "en")
453                                          )
454                             )
455               );
456
457   html_fprint (stdout, page);
458   html_free (page);
459   return 10;
460 }
461
462
463 /*
464 //      Local Variables:
465 //      mode: C
466 //      c-file-style: "gnu"
467 //      indent-tabs-mode: nil
468 //      End:
469 */