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