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