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