1 //
2 // aegis - project change supervisor
3 // Copyright (C) 2003-2008, 2011, 2012 Peter Miller
4 // Copyright (C) 2007, 2008 Walter Franzini
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program. If not, see
18 // <http://www.gnu.org/licenses/>.
19 //
20
21 #include <common/ac/assert.h>
22 #include <common/ac/ctype.h>
23 #include <common/ac/errno.h>
24 #include <common/ac/stdarg.h>
25 #include <common/ac/stdio.h>
26 #include <common/ac/stdlib.h>
27 #include <common/ac/string.h>
28 #include <common/ac/time.h>
29
30 #include <common/now.h>
31 #include <common/nstring.h>
32 #include <common/progname.h>
33 #include <common/str.h>
34 #include <common/str_list.h>
35 #include <common/uuidentifier.h>
36 #include <common/uuidentifier/translate.h>
37 #include <common/version_stmp.h>
38 #include <libaegis/change.h>
39 #include <libaegis/emit/project.h>
40 #include <libaegis/http.h>
41 #include <libaegis/os.h>
42 #include <libaegis/project.h>
43
44
45 bool http_fatal_noerror;
46
47
48 void
http_fatal(http_error_t oops,const char * fmt,...)49 http_fatal(http_error_t oops, const char *fmt, ...)
50 {
51 va_list ap;
52
53 va_start(ap, fmt);
54 if (oops != http_error_ok && !http_fatal_noerror)
55 printf("Status: %d Error\n", oops);
56 printf("Content-Type: text/html\n\n");
57 printf("<html><head><title>Error</title></head><body><h1>Error</h1>\n");
58 printf("The request failed because:\n<em>");
59 vprintf(fmt, ap);
60 va_end(ap);
61 printf("</em>\n</body></html>\n");
62 exit(0);
63 }
64
65
66 const char *
http_getenv(const char * name)67 http_getenv(const char *name)
68 {
69 char *result;
70
71 result = getenv(name);
72 if (!result)
73 {
74 http_fatal
75 (
76 http_error_internal_server,
77 "Environment variable $%s not set.",
78 name
79 );
80 }
81 return result;
82 }
83
84
85 static const char *
http_getenv(const char * name,const char * dflt)86 http_getenv(const char *name, const char *dflt)
87 {
88 char *result = getenv(name);
89 if (!result)
90 return dflt;
91 return result;
92 }
93
94
95 void
html_escape_charstar(const char * s)96 html_escape_charstar(const char *s)
97 {
98 html_escape_string(nstring(s));
99 }
100
101
102 void
html_escape_string(string_ty * s)103 html_escape_string(string_ty *s)
104 {
105 html_escape_charstar(s->str_text);
106 }
107
108
109 void
html_escape_string(const nstring & s)110 html_escape_string(const nstring &s)
111 {
112 fputs(s.url_quote().c_str(), stdout);
113 }
114
115
116 void
html_encode_charstar(const char * s)117 html_encode_charstar(const char *s)
118 {
119 html_encode_string(nstring(s));
120 }
121
122
123 void
html_encode_string(string_ty * s)124 html_encode_string(string_ty *s)
125 {
126 html_encode_charstar(s->str_text);
127 }
128
129
130 void
html_encode_string(const nstring & s)131 html_encode_string(const nstring &s)
132 {
133 fputs(s.html_quote(true).c_str(), stdout);
134 }
135
136
137 nstring
http_sanitize_content_type(const nstring & content_type)138 http_sanitize_content_type(const nstring &content_type)
139 {
140 const char *s = content_type.c_str();
141 const char *semicolon = s;
142 while (*semicolon && *semicolon != ';' && *semicolon != ' ')
143 ++semicolon;
144 nstring left(s, semicolon - s);
145 if (left == "text/html")
146 return content_type;
147 while (*semicolon == ';' || *semicolon == ' ')
148 ++semicolon;
149 nstring right;
150 if (*semicolon)
151 {
152 const char *end = s + content_type.size();
153 right = "; " + nstring(semicolon, end - semicolon);
154 }
155 if
156 (
157 left.starts_with("text/")
158 ||
159 left == "application/x-awk"
160 ||
161 left == "application/x-gawk"
162 ||
163 left == "application/x-nawk"
164 ||
165 left == "application/x-perl"
166 ||
167 (left.starts_with("application/x-") && left.ends_with("script"))
168 )
169 {
170 if (right.empty())
171 right = "; charset=us-ascii";
172 return "text/plain" + right;
173 }
174 return content_type;
175 }
176
177
178 void
http_content_type_header(string_ty * filename)179 http_content_type_header(string_ty *filename)
180 {
181 os_become_orig();
182 nstring content_type = os_magic_file(filename);
183 os_become_undo();
184 content_type = http_sanitize_content_type(content_type);
185 assert(!content_type.empty());
186 printf("Content-Type: %s\n", content_type.c_str());
187 }
188
189
190 static void
emit_project_attribute(project * pp,change::pointer cp,const char * cname)191 emit_project_attribute(project *pp, change::pointer cp, const char *cname)
192 {
193 if (!pp && cp)
194 pp = cp->pp;
195 if (pp && (!cp || cp->bogus))
196 cp = pp->change_get();
197 if (!cp)
198 return;
199 pconf_ty *pconf_data = change_pconf_get(cp, 0);
200 if (!pconf_data->project_specific)
201 return;
202 string_ty *name = str_from_c(cname);
203 for (size_t j = 0; j < pconf_data->project_specific->length; ++j)
204 {
205 attributes_ty *ap = pconf_data->project_specific->list[j];
206 if
207 (
208 ap->name
209 &&
210 ap->value
211 &&
212 0 == strcasecmp(name->str_text, ap->name->str_text)
213 )
214 {
215 printf("%s\n", ap->value->str_text);
216 }
217 }
218 str_free(name);
219 }
220
221
222 void
html_header_ps(project * pp,change::pointer cp)223 html_header_ps(project *pp, change::pointer cp)
224 {
225 emit_project_attribute(pp, cp, "html:body-begin");
226 }
227
228
229 void
html_footer(project * pp,change::pointer cp)230 html_footer(project *pp, change::pointer cp)
231 {
232 time_t tmp = now();
233 printf("<hr>\n");
234 printf("This page was generated by <em>%s</em>\n", progname_get());
235 printf("version %s\n", version_stamp());
236 char buffer[BUFSIZ];
237 // gcc 3.3 doesn't like %c
238 if (strftime(buffer, BUFSIZ, "%a %d %b %Y %H:%M:%S %Z", localtime(&tmp)))
239 printf("on %s.\n", buffer);
240 else
241 printf("on %.24s.\n", ctime(&tmp));
242 emit_project_attribute(pp, cp, "html:body-end");
243 printf("</body></html>\n");
244 }
245
246
247 static void
emit_project_stylesheet(project * pp)248 emit_project_stylesheet(project *pp)
249 {
250 //
251 // Netscape 4.x has numerous CSS bugs, two of which need mentioning.
252 // 1. If a style sheet is not present Netscape says 404 Not found, when
253 // it should silently ignore it. 2. Style sheets who's media is not
254 // "screen" will be ignored. Fortunately we can use (2) to get around (1).
255 //
256 if (pp && !pp->is_a_trunk())
257 emit_project_stylesheet(pp->parent_get());
258 else
259 {
260 printf("<style type=\"text/css\">\n"
261 "tr.even-group { background-color: #CCCCCC; }\n"
262 "body { background-color: white; }\n"
263 "</style>\n");
264 printf("<link rel=\"stylesheet\" type=\"text/css\" "
265 "href=\"/aedefault.css\" media=\"all\">\n");
266 }
267 if (pp)
268 {
269 printf("<link rel=\"stylesheet\" type=\"text/css\" href=\"/");
270 html_escape_string(project_name_get(pp));
271 printf(".css\" media=\"all\">\n");
272 }
273 }
274
275
276 void
html_header(project * pp,change::pointer cp)277 html_header(project *pp, change::pointer cp)
278 {
279 if (cp && !pp)
280 pp = cp->pp;
281
282 printf("Content-Type: text/html\n\n");
283 printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\""
284 "\"http://www.w3.org/TR/REC-html40/loose.dtd\">\n");
285 printf("<html><head>\n");
286 printf("<meta name=\"ROBOTS\" content=\"NOINDEX, NOFOLLOW\">\n");
287 printf("<meta name=\"GENERATOR\" content=\"aegis-%s\">\n", version_stamp());
288 printf("<meta http-equiv=\"Content-Type\" "
289 "content=\"text/html; charset=ISO-8859-1\">\n");
290 emit_project_stylesheet(pp);
291 emit_project_attribute(pp, cp, "html:meta");
292 }
293
294
295 void
emit_change(change::pointer cp)296 emit_change(change::pointer cp)
297 {
298 long n;
299
300 emit_project(cp->pp);
301 if (cp->bogus)
302 return;
303 printf(",<br>\nChange <a href=\"%s/", http_script_name());
304 html_escape_string(project_name_get(cp->pp));
305 n = magic_zero_decode(cp->number);
306 printf(".C%ld/?menu\">%ld</a>", n, n);
307 }
308
309
310 void
emit_change_but1(change::pointer cp)311 emit_change_but1(change::pointer cp)
312 {
313 if (cp->bogus)
314 emit_project_but1(cp->pp);
315 else
316 {
317 emit_project(cp->pp);
318 printf(",<br>\nChange %ld", magic_zero_decode(cp->number));
319 }
320 }
321
322
323 const char *
http_script_name(void)324 http_script_name(void)
325 {
326 //
327 // We cache the results, so this fairly expensive operation is only
328 // done once.
329 //
330 static nstring result;
331 if (result.empty())
332 {
333 //
334 // Get the script name. It must exist.
335 //
336 nstring script_name = http_getenv("SCRIPT_NAME", "/cgi-bin/aeget");
337
338 //
339 // Get the server name. Default it if it doesn't exist.
340 //
341 nstring server_name = http_getenv("SERVER_NAME", "localhost");
342
343 //
344 // Get the server port. Default it if it doesn't exist.
345 // Make sure that it is a number and is in range.
346 //
347 nstring server_port = http_getenv("SERVER_PORT", "80");
348 int port = atoi(server_port.c_str());
349 if (port <= 0 || port >= 0x10000)
350 {
351 http_fatal
352 (
353 http_error_internal_server,
354 "SERVER_PORT of \"%s\" not known",
355 server_port.c_str()
356 );
357 }
358
359 //
360 // Get the server protocol. Default it if it does not exist.
361 // We only grok HTTP, so barf if it's anything alse.
362 //
363 nstring server_protocol = http_getenv("SERVER_PROTOCOL", "HTTP/1.1");
364 if (!server_protocol.upcase().starts_with("HTTP"))
365 {
366 http_fatal
367 (
368 http_error_internal_server,
369 "SERVER_PROTOCOL of \"%s\" not known",
370 server_protocol.c_str()
371 );
372 }
373
374 //
375 // Get the request scheme. Default to http://.
376 //
377 const char *script_uri = getenv("SCRIPT_URI");
378 if (!script_uri)
379 script_uri = "";
380 nstring request_scheme = "http";
381 const char *colon = strchr(script_uri, ':');
382 if (colon)
383 request_scheme = nstring(script_uri, colon - script_uri);
384
385 //
386 // Assemble the script name from the various components to
387 // include the protocol, host and port sections as well as the
388 // script itself.
389 //
390 result =
391 (
392 request_scheme + "://"
393 +
394 server_name
395 +
396 (port == 80 ? "" : nstring::format(":%d", port))
397 +
398 script_name
399 );
400 }
401 return result.c_str();
402 }
403
404
405 void
emit_project_href(project * pp)406 emit_project_href(project *pp)
407 {
408 printf("<a href=\"%s/", http_script_name());
409 html_escape_string(project_name_get(pp));
410 printf("/\">");
411 }
412
413
414 void
emit_project_href(project * pp,const char * modifier,...)415 emit_project_href(project *pp, const char *modifier, ...)
416 {
417 assert(modifier);
418 va_list ap;
419 va_start(ap, modifier);
420 char buffer[1000];
421 vsnprintf(buffer, sizeof(buffer), modifier, ap);
422 va_end(ap);
423
424 printf("<a href=\"%s/", http_script_name());
425 html_escape_string(project_name_get(pp));
426 printf("/");
427 if (buffer[0])
428 printf("?%s", buffer);
429 printf("\">");
430 }
431
432
433 void
emit_change_href_n(project * pp,long n,const char * modifier)434 emit_change_href_n(project *pp, long n, const char *modifier)
435 {
436 printf("<a href=\"%s/", http_script_name());
437 html_escape_string(project_name_get(pp));
438 printf(".C%ld/", n);
439 if (modifier && *modifier)
440 printf("?%s", modifier);
441 printf("\">");
442 }
443
444
445 void
emit_change_href(change::pointer cp,const char * modifier)446 emit_change_href(change::pointer cp, const char *modifier)
447 {
448 if (cp->bogus)
449 emit_project_href(cp->pp, "%s", modifier);
450 else
451 emit_change_href_n(cp->pp, magic_zero_decode(cp->number), modifier);
452 }
453
454
455 void
emit_change_uuid_href(change::pointer cp,const nstring & uuid,const nstring & prefix,const nstring & suffix)456 emit_change_uuid_href(change::pointer cp, const nstring &uuid,
457 const nstring &prefix, const nstring &suffix)
458 {
459 if (cp->bogus)
460 emit_project_href(cp->pp, "%s", "menu");
461 else
462 {
463 assert(universal_unique_identifier_valid(cp->uuid_get()));
464 printf
465 (
466 "<a href=\"%s%s%s\">",
467 prefix.c_str(),
468 uuid_translate(uuid.c_str()).c_str(),
469 suffix.c_str()
470 );
471 }
472 }
473
474 void
emit_file_href(change::pointer cp,const nstring & filename,const char * modifier)475 emit_file_href(change::pointer cp, const nstring &filename,
476 const char *modifier)
477 {
478 printf("<a href=\"%s/", http_script_name());
479 html_escape_string(project_name_get(cp->pp));
480 if (!cp->bogus)
481 printf(".C%ld", magic_zero_decode(cp->number));
482 printf("/");
483 html_escape_string(filename);
484 if (modifier && *modifier)
485 printf("?%s", modifier);
486 printf("\">");
487 }
488
489
490 void
emit_file_href(change::pointer cp,string_ty * filename,const char * modifier)491 emit_file_href(change::pointer cp, string_ty *filename, const char *modifier)
492 {
493 emit_file_href(cp, nstring(filename), modifier);
494 }
495
496
497 void
emit_rect_image(int width,int height,const char * label,int hspace)498 emit_rect_image(int width, int height, const char *label, int hspace)
499 {
500 if (width < 3)
501 width = 3;
502 if (height < 3)
503 height = 3;
504 printf
505 (
506 "<img src=\"%s/?rect+width=%d+height=%d",
507 http_script_name(),
508 width,
509 height
510 );
511 if (label && *label)
512 {
513 printf("+label=");
514 html_escape_charstar(label);
515 }
516 printf("\" width=%d height=%d", width, height);
517 if (hspace >= 0)
518 {
519 #ifndef USE_STYLES
520 printf(" hspace=%d", hspace);
521 #else
522 printf(" style=\"left:%d;right:%d;\"", hspace, hspace);
523 #endif
524 }
525 printf(">");
526 }
527
528
529 void
emit_rect_image_rgb(int width,int height,const char * color,int hspace)530 emit_rect_image_rgb(int width, int height, const char *color, int hspace)
531 {
532 if (width < 3)
533 width = 3;
534 if (height < 3)
535 height = 3;
536 printf
537 (
538 "<img\nsrc=\"%s/?rect+width=%d+height=%d",
539 http_script_name(),
540 width,
541 height
542 );
543 if (color && *color)
544 printf("+color=%s", color);
545 printf("\" width=%d height=%d", width, height);
546 if (hspace >= 0)
547 {
548 #ifndef USE_STYLES
549 printf(" hspace=%d", hspace);
550 #else
551 printf(" style=\"left:%d;right:%d;\"", hspace, hspace);
552 #endif
553 }
554 printf(">");
555 }
556
557
558 bool
modifier_test(string_list_ty * modifiers,const char * name)559 modifier_test(string_list_ty *modifiers, const char *name)
560 {
561 for (size_t j = 0; j < modifiers->nstrings; ++j)
562 {
563 if (0 == strcasecmp(modifiers->string[j]->str_text, name))
564 {
565 return true;
566 }
567 }
568 return false;
569 }
570
571
572 bool
modifier_test_and_clear(string_list_ty * modifiers,const char * name)573 modifier_test_and_clear(string_list_ty *modifiers, const char *name)
574 {
575 for (size_t j = 0; j < modifiers->nstrings; ++j)
576 {
577 if (0 == strcasecmp(modifiers->string[j]->str_text, name))
578 {
579 modifiers->remove(modifiers->string[j]);
580 return true;
581 }
582 }
583 return false;
584 }
585
586
587 void
emit_rss_icon_with_link(project * pp,const nstring & rss_filename)588 emit_rss_icon_with_link(project *pp, const nstring &rss_filename)
589 {
590 printf
591 (
592 "<a href=\"%s/%s/?rss+%s\"><img src=\"%s/icon/rss.gif\" border=0 "
593 "alt=\"RSS\"></a>\n",
594 http_script_name(),
595 project_name_get(pp).c_str(),
596 rss_filename.c_str(),
597 http_script_name()
598 );
599 }
600
601
602 void
emit_rss_meta_data(project * pp,const nstring & rss_filename)603 emit_rss_meta_data(project *pp, const nstring &rss_filename)
604 {
605 printf
606 (
607 "<link rel=\"alternate\" type=\"application/rss+xml\" "
608 "href=\"%s/%s/?rss+%s\"/>\n",
609 http_script_name(),
610 pp->name_get()->str_text,
611 rss_filename.c_str()
612 );
613 }
614
615
616 // vim: set ts=8 sw=4 et :
617