Switch all the HTML-based reporting modes (the internal httpd, the CGI
[sgt/agedu] / html.c
CommitLineData
70322ae3 1/*
2 * html.c: implementation of html.h.
3 */
4
353bc75d 5#include "agedu.h"
70322ae3 6#include "html.h"
995db599 7#include "alloc.h"
70322ae3 8#include "trie.h"
9#include "index.h"
10
70322ae3 11#define MAXCOLOUR 511
12
13struct html {
14 char *buf;
15 size_t buflen, bufsize;
16 const void *t;
17 unsigned long long totalsize, oldest, newest;
18 char *path2;
c47f39de 19 char *oururi;
70322ae3 20 size_t hreflen;
c47f39de 21 const char *uriformat;
3f940260 22 unsigned long long thresholds[MAXCOLOUR];
23 char *titletexts[MAXCOLOUR+1];
70322ae3 24 time_t now;
25};
26
afe761f3 27static void vhtprintf(struct html *ctx, const char *fmt, va_list ap)
70322ae3 28{
29 va_list ap2;
30 int size, size2;
50e82fdc 31 char testbuf[2];
70322ae3 32
33 va_copy(ap2, ap);
50e82fdc 34 /*
35 * Some C libraries (Solaris, I'm looking at you) don't like
36 * an output buffer size of zero in vsnprintf, but will return
37 * sensible values given any non-zero buffer size. Hence, we
38 * use testbuf to gauge the length of the string.
39 */
40 size = vsnprintf(testbuf, 1, fmt, ap2);
70322ae3 41 va_end(ap2);
42
43 if (ctx->buflen + size >= ctx->bufsize) {
44 ctx->bufsize = (ctx->buflen + size) * 3 / 2 + 1024;
45 ctx->buf = sresize(ctx->buf, ctx->bufsize, char);
46 }
47 size2 = vsnprintf(ctx->buf + ctx->buflen, ctx->bufsize - ctx->buflen,
48 fmt, ap);
49 assert(size == size2);
50 ctx->buflen += size;
51}
52
afe761f3 53static void htprintf(struct html *ctx, const char *fmt, ...)
70322ae3 54{
55 va_list ap;
56 va_start(ap, fmt);
57 vhtprintf(ctx, fmt, ap);
58 va_end(ap);
59}
60
61static unsigned long long round_and_format_age(struct html *ctx,
62 unsigned long long age,
63 char *buf, int direction)
64{
65 struct tm tm, tm2;
66 char newbuf[80];
67 unsigned long long ret, newret;
68 int i;
69 int ym;
70 static const int minutes[] = { 5, 10, 15, 30, 45 };
71
72 tm = *localtime(&ctx->now);
73 ym = tm.tm_year * 12 + tm.tm_mon;
74
75 ret = ctx->now;
76 strcpy(buf, "Now");
77
78 for (i = 0; i < lenof(minutes); i++) {
79 newret = ctx->now - minutes[i] * 60;
80 sprintf(newbuf, "%d minutes", minutes[i]);
81 if (newret < age)
82 goto finish;
83 strcpy(buf, newbuf);
84 ret = newret;
85 }
86
87 for (i = 1; i < 24; i++) {
88 newret = ctx->now - i * (60*60);
89 sprintf(newbuf, "%d hour%s", i, i==1 ? "" : "s");
90 if (newret < age)
91 goto finish;
92 strcpy(buf, newbuf);
93 ret = newret;
94 }
95
96 for (i = 1; i < 7; i++) {
97 newret = ctx->now - i * (24*60*60);
98 sprintf(newbuf, "%d day%s", i, i==1 ? "" : "s");
99 if (newret < age)
100 goto finish;
101 strcpy(buf, newbuf);
102 ret = newret;
103 }
104
105 for (i = 1; i < 4; i++) {
106 newret = ctx->now - i * (7*24*60*60);
107 sprintf(newbuf, "%d week%s", i, i==1 ? "" : "s");
108 if (newret < age)
109 goto finish;
110 strcpy(buf, newbuf);
111 ret = newret;
112 }
113
114 for (i = 1; i < 11; i++) {
115 tm2 = tm; /* structure copy */
116 tm2.tm_year = (ym - i) / 12;
117 tm2.tm_mon = (ym - i) % 12;
118 newret = mktime(&tm2);
119 sprintf(newbuf, "%d month%s", i, i==1 ? "" : "s");
120 if (newret < age)
121 goto finish;
122 strcpy(buf, newbuf);
123 ret = newret;
124 }
125
126 for (i = 1;; i++) {
127 tm2 = tm; /* structure copy */
128 tm2.tm_year = (ym - i*12) / 12;
129 tm2.tm_mon = (ym - i*12) % 12;
130 newret = mktime(&tm2);
131 sprintf(newbuf, "%d year%s", i, i==1 ? "" : "s");
132 if (newret < age)
133 goto finish;
134 strcpy(buf, newbuf);
135 ret = newret;
136 }
137
138 finish:
139 if (direction > 0) {
140 /*
141 * Round toward newest, i.e. use the existing (buf,ret).
142 */
143 } else if (direction < 0) {
144 /*
145 * Round toward oldest, i.e. use (newbuf,newret);
146 */
147 strcpy(buf, newbuf);
148 ret = newret;
149 } else {
150 /*
151 * Round to nearest.
152 */
153 if (ret - age > age - newret) {
154 strcpy(buf, newbuf);
155 ret = newret;
156 }
157 }
158 return ret;
159}
160
161static void get_indices(const void *t, char *path,
162 unsigned long *xi1, unsigned long *xi2)
163{
164 size_t pathlen = strlen(path);
256c29a2 165 int c1 = path[pathlen], c2 = (pathlen > 0 ? path[pathlen-1] : 0);
70322ae3 166
167 *xi1 = trie_before(t, path);
256c29a2 168 make_successor(path);
70322ae3 169 *xi2 = trie_before(t, path);
256c29a2 170 path[pathlen] = c1;
171 if (pathlen > 0)
172 path[pathlen-1] = c2;
70322ae3 173}
174
3f940260 175static unsigned long long fetch_size(const void *t,
176 unsigned long xi1, unsigned long xi2,
70322ae3 177 unsigned long long atime)
178{
16139d21 179 if (xi2 - xi1 == 1) {
180 /*
181 * We are querying an individual file, so we should not
182 * depend on the index entries either side of the node,
183 * since they almost certainly don't both exist. Instead,
184 * just look up the file's size and atime in the main trie.
185 */
186 const struct trie_file *f = trie_getfile(t, xi1);
187 if (f->atime < atime)
188 return f->size;
189 else
190 return 0;
191 } else {
192 return index_query(t, xi2, atime) - index_query(t, xi1, atime);
193 }
70322ae3 194}
195
196static void htescape(struct html *ctx, const char *s, int n, int italics)
197{
198 while (n > 0 && *s) {
199 unsigned char c = (unsigned char)*s++;
200
201 if (c == '&')
202 htprintf(ctx, "&amp;");
203 else if (c == '<')
204 htprintf(ctx, "&lt;");
205 else if (c == '>')
206 htprintf(ctx, "&gt;");
207 else if (c >= ' ' && c < '\177')
208 htprintf(ctx, "%c", c);
209 else {
210 if (italics) htprintf(ctx, "<i>");
211 htprintf(ctx, "[%02x]", c);
212 if (italics) htprintf(ctx, "</i>");
213 }
214
215 n--;
216 }
217}
218
219static void begin_colour_bar(struct html *ctx)
220{
221 htprintf(ctx, "<table cellspacing=0 cellpadding=0"
222 " style=\"border:0\">\n<tr>\n");
223}
224
225static void add_to_colour_bar(struct html *ctx, int colour, int pixels)
226{
227 int r, g, b;
70322ae3 228
229 if (colour >= 0 && colour < 256) /* red -> yellow fade */
230 r = 255, g = colour, b = 0;
231 else if (colour >= 256 && colour <= 511) /* yellow -> green fade */
232 r = 511 - colour, g = 255, b = 0;
233 else /* background grey */
234 r = g = b = 240;
235
70322ae3 236 if (pixels > 0) {
237 htprintf(ctx, "<td style=\"width:%dpx; height:1em; "
238 "background-color:#%02x%02x%02x\"",
239 pixels, r, g, b);
240 if (colour >= 0)
3f940260 241 htprintf(ctx, " title=\"%s\"", ctx->titletexts[colour]);
70322ae3 242 htprintf(ctx, "></td>\n");
243 }
244}
245
246static void end_colour_bar(struct html *ctx)
247{
248 htprintf(ctx, "</tr>\n</table>\n");
249}
250
251struct vector {
16139d21 252 int want_href, essential;
70322ae3 253 char *name;
b49db535 254 int literal; /* should the name be formatted in fixed-pitch? */
70322ae3 255 unsigned long index;
256 unsigned long long sizes[MAXCOLOUR+1];
257};
258
259int vec_compare(const void *av, const void *bv)
260{
261 const struct vector *a = *(const struct vector **)av;
262 const struct vector *b = *(const struct vector **)bv;
263
264 if (a->sizes[MAXCOLOUR] > b->sizes[MAXCOLOUR])
265 return -1;
266 else if (a->sizes[MAXCOLOUR] < b->sizes[MAXCOLOUR])
267 return +1;
268 else if (a->want_href < b->want_href)
269 return +1;
270 else if (a->want_href > b->want_href)
271 return -1;
272 else if (a->want_href)
273 return strcmp(a->name, b->name);
274 else if (a->index < b->index)
275 return -1;
276 else if (a->index > b->index)
277 return +1;
16139d21 278 else if (a->essential < b->essential)
279 return +1;
280 else if (a->essential > b->essential)
281 return -1;
70322ae3 282 return 0;
283}
284
285static struct vector *make_vector(struct html *ctx, char *path,
16139d21 286 int want_href, int essential,
287 char *name, int literal)
70322ae3 288{
289 unsigned long xi1, xi2;
290 struct vector *vec = snew(struct vector);
291 int i;
292
293 vec->want_href = want_href;
16139d21 294 vec->essential = essential;
70322ae3 295 vec->name = name ? dupstr(name) : NULL;
b49db535 296 vec->literal = literal;
70322ae3 297
298 get_indices(ctx->t, path, &xi1, &xi2);
299
300 vec->index = xi1;
301
302 for (i = 0; i <= MAXCOLOUR; i++) {
303 unsigned long long atime;
304 if (i == MAXCOLOUR)
305 atime = ULLONG_MAX;
306 else
307 atime = ctx->thresholds[i];
3f940260 308 vec->sizes[i] = fetch_size(ctx->t, xi1, xi2, atime);
70322ae3 309 }
310
311 return vec;
312}
313
314static void print_heading(struct html *ctx, const char *title)
315{
316 htprintf(ctx, "<tr style=\"padding: 0.2em; background-color:#e0e0e0\">\n"
317 "<td colspan=4 align=center>%s</td>\n</tr>\n", title);
318}
319
afe761f3 320static void compute_display_size(unsigned long long size,
321 const char **fmt, double *display_size)
322{
323 static const char *const fmts[] = {
324 "%g b", "%g Kb", "%#.1f Mb", "%#.1f Gb", "%#.1f Tb",
325 "%#.1f Pb", "%#.1f Eb", "%#.1f Zb", "%#.1f Yb"
326 };
327 int shift = 0;
815e510a 328 unsigned long long tmpsize;
329 double denominator;
330
331 tmpsize = size;
332 denominator = 1.0;
333 while (tmpsize >= 1024 && shift < lenof(fmts)-1) {
334 tmpsize >>= 10;
335 denominator *= 1024.0;
afe761f3 336 shift++;
337 }
815e510a 338 *display_size = size / denominator;
afe761f3 339 *fmt = fmts[shift];
340}
341
c47f39de 342struct format_option {
343 const char *prefix, *suffix; /* may include '%%' */
344 int prefixlen, suffixlen; /* does not count '%%' */
345 char fmttype; /* 0 for none, or 'n' or 'p' */
346 int translate_pathsep; /* pathsep rendered as '/'? */
347 int shorten_path; /* omit common prefix? */
348};
349
350/*
351 * Gets the next format option from a format string. Advances '*fmt'
352 * past it, or sets it to NULL if nothing is left.
353 */
354struct format_option get_format_option(const char **fmt)
355{
356 struct format_option ret;
357
358 /*
359 * Scan for prefix of format.
360 */
361 ret.prefix = *fmt;
362 ret.prefixlen = 0;
363 while (1) {
364 if (**fmt == '\0') {
365 /*
366 * No formatting directive, and this is the last option.
367 */
368 ret.suffix = *fmt;
369 ret.suffixlen = 0;
370 ret.fmttype = '\0';
371 *fmt = NULL;
372 return ret;
373 } else if (**fmt == '%') {
374 if ((*fmt)[1] == '%') {
375 (*fmt) += 2; /* just advance one extra */
376 ret.prefixlen++;
377 } else if ((*fmt)[1] == '|') {
378 /*
379 * No formatting directive.
380 */
381 ret.suffix = *fmt;
382 ret.suffixlen = 0;
383 ret.fmttype = '\0';
384 (*fmt) += 2; /* advance to start of next option */
385 return ret;
386 } else {
387 break;
388 }
389 } else {
390 (*fmt)++; /* normal character */
391 ret.prefixlen++;
392 }
393 }
394
395 /*
396 * Interpret formatting directive with flags.
397 */
398 (*fmt)++;
399 ret.translate_pathsep = ret.shorten_path = 1;
400 while (1) {
401 char c = *(*fmt)++;
402 assert(c);
403 if (c == '/') {
404 ret.translate_pathsep = 0;
405 } else if (c == '-') {
406 ret.shorten_path = 0;
407 } else {
408 assert(c == 'n' || c == 'p');
409 ret.fmttype = c;
410 break;
411 }
412 }
413
414 /*
415 * Scan for suffix.
416 */
417 ret.suffix = *fmt;
418 ret.suffixlen = 0;
419 while (1) {
420 if (**fmt == '\0') {
421 /*
422 * This is the last option.
423 */
424 *fmt = NULL;
425 return ret;
426 } else if (**fmt != '%') {
427 (*fmt)++; /* normal character */
428 ret.suffixlen++;
429 } else {
430 if ((*fmt)[1] == '%') {
431 (*fmt) += 2; /* just advance one extra */
432 ret.suffixlen++;
433 } else {
434 assert((*fmt)[1] == '|');
435 (*fmt) += 2; /* advance to start of next option */
436 return ret;
437 }
438 }
439 }
440}
441
442char *format_string(const char *fmt, unsigned long index, const void *t)
00c5e40c 443{
c47f39de 444 int maxlen;
445 char *ret = NULL, *p = NULL;
446 char *path = NULL, *q = NULL;
447 char pathsep = trie_pathsep(t);
448 int maxpathlen = trie_maxpathlen(t);
449
450 while (fmt) {
451 struct format_option opt = get_format_option(&fmt);
452 if (index && !opt.fmttype)
453 continue; /* option is only good for the root, which this isn't */
454
455 maxlen = opt.prefixlen + opt.suffixlen + 1;
456 switch (opt.fmttype) {
457 case 'n':
458 maxlen += 40; /* generous length for an integer */
459 break;
460 case 'p':
461 maxlen += 3*maxpathlen; /* might have to escape everything */
462 break;
463 }
464 ret = snewn(maxlen, char);
465 p = ret;
466 while (opt.prefixlen-- > 0) {
467 if ((*p++ = *opt.prefix++) == '%')
468 opt.prefix++;
469 }
470 switch (opt.fmttype) {
471 case 'n':
472 p += sprintf(p, "%lu", index);
473 break;
474 case 'p':
475 path = snewn(1+trie_maxpathlen(t), char);
476 if (opt.shorten_path) {
477 trie_getpath(t, 0, path);
478 q = path + strlen(path);
479 trie_getpath(t, index, path);
480 if (*q == pathsep)
481 q++;
482 } else {
483 trie_getpath(t, index, path);
484 q = path;
485 }
486 while (*q) {
487 char c = *q++;
488 if (c == pathsep && opt.translate_pathsep)
489 *p++ = '/';
490 else if (!isalnum((unsigned char)c) && !strchr("-.@_", c))
491 p += sprintf(p, "=%02X", (unsigned char)c);
492 else
493 *p++ = c;
494 }
495 sfree(path);
496 break;
497 }
498 while (opt.suffixlen-- > 0) {
499 if ((*p++ = *opt.suffix++) == '%')
500 opt.suffix++;
501 }
502 *p = '\0';
503 assert(p - ret < maxlen);
504 return ret;
505 }
506 assert(!"Getting here implies an incomplete set of formats");
507}
508
509char *html_format_path(const void *t, const struct html_config *cfg,
510 unsigned long index)
511{
512 return format_string(cfg->uriformat, index, t);
513}
514
515int html_parse_path(const void *t, const char *path,
516 const struct html_config *cfg, unsigned long *index)
517{
518 int len = strlen(path);
519 int midlen;
520 const char *p, *q;
521 char *r;
522 char pathsep = trie_pathsep(t);
523 const char *fmt = cfg->uriformat;
524
525 while (fmt) {
526 struct format_option opt = get_format_option(&fmt);
527
528 /*
529 * Check prefix and suffix.
530 */
531 midlen = len - opt.prefixlen - opt.suffixlen;
532 if (midlen < 0)
533 continue; /* prefix and suffix don't even fit */
534
535 p = path;
536 while (opt.prefixlen > 0) {
537 char c = *opt.prefix++;
538 if (c == '%')
539 opt.prefix++;
540 if (*p != c)
541 break;
542 p++;
543 opt.prefixlen--;
544 }
545 if (opt.prefixlen > 0)
546 continue; /* prefix didn't match */
547
548 q = path + len - opt.suffixlen;
549 while (opt.suffixlen > 0) {
550 char c = *opt.suffix++;
551 if (c == '%')
552 opt.suffix++;
553 if (*q != c)
554 break;
555 q++;
556 opt.suffixlen--;
557 }
558 if (opt.suffixlen > 0)
559 continue; /* suffix didn't match */
560
561 /*
562 * Check the data in between. p points at it, and it's midlen
563 * characters long.
564 */
565 if (opt.fmttype == '\0') {
566 if (midlen == 0) {
567 /*
568 * Successful match against a root format.
569 */
570 *index = 0;
571 return 1;
572 }
573 } else if (opt.fmttype == 'n') {
574 *index = 0;
575 while (midlen > 0) {
576 if (*p >= '0' && *p <= '9')
577 *index = *index * 10 + (*p - '0');
578 else
579 break;
580 midlen--;
581 p++;
582 }
583 if (midlen == 0) {
584 /*
585 * Successful match against a numeric format.
586 */
587 return 1;
588 }
589 } else {
590 assert(opt.fmttype == 'p');
591
592 int maxoutlen = trie_maxpathlen(t) + 1;
593 int maxinlen = midlen + 1;
594 char triepath[maxinlen+maxoutlen];
595
596 if (opt.shorten_path) {
597 trie_getpath(t, 0, triepath);
598 r = triepath + strlen(triepath);
599 if (r > triepath && r[-1] != pathsep)
600 *r++ = pathsep;
601 } else {
602 r = triepath;
603 }
604
605 while (midlen > 0) {
606 if (*p == '/' && opt.translate_pathsep) {
607 *r++ = pathsep;
608 p++;
609 midlen--;
610 } else if (*p == '=') {
611 if (midlen < 3 ||
612 !isxdigit((unsigned char)p[1]) ||
613 !isxdigit((unsigned char)p[2]))
614 break; /* faulty escape encoding */
615 char x[3];
616 unsigned cval;
617 x[0] = p[1];
618 x[1] = p[2];
619 x[2] = '\0';
620 sscanf(x, "%x", &cval);
621 *r++ = cval;
622 p += 3;
623 midlen -= 3;
624 } else {
625 *r++ = *p;
626 p++;
627 midlen--;
628 }
629 }
630 if (midlen > 0)
631 continue; /* something went wrong in that loop */
632 assert(r - triepath < maxinlen+maxoutlen);
633 *r = '\0';
634
635 unsigned long gotidx = trie_before(t, triepath);
636 if (gotidx >= trie_count(t))
637 continue; /* index out of range */
638 char retpath[1+maxoutlen];
639 trie_getpath(t, gotidx, retpath);
640 if (strcmp(triepath, retpath))
641 continue; /* exact path not found in trie */
642 if (!index_has_root(t, gotidx))
643 continue; /* path is not a directory */
644
645 /*
646 * Successful path-based match.
647 */
648 *index = gotidx;
649 return 1;
650 }
651 }
652
653 return 0; /* no match from any format option */
654}
655
656char *make_href(const char *source, const char *target)
657{
658 /*
659 * We insist that both source and target URIs start with a /, or
660 * else we won't be reliably able to construct relative hrefs
661 * between them (e.g. because we've got a suffix on the end of
662 * some CGI pathname that this function doesn't know the final
663 * component of).
664 */
665 assert(*source == '/');
666 assert(*target == '/');
667
668 /*
669 * Find the last / in source. Everything up to but not including
670 * that is the directory to which the output href will be
671 * relative. We enforce by assertion that there must be a /
672 * somewhere in source, or else we can't construct a relative href
673 * at all
674 */
675 const char *sourceend = strrchr(source, '/');
676 assert(sourceend != NULL);
677
678 /*
679 * See how far the target URI agrees with the source one, up to
680 * and including that /.
681 */
682 const char *s = source, *t = target;
683 while (s <= sourceend && *s == *t)
684 s++, t++;
685
686 /*
687 * We're only interested in agreement of complete path components,
688 * so back off until we're sitting just after a shared /.
689 */
690 while (s > source && s[-1] != '/')
691 s--, t--;
692 assert(s > source);
693
694 /*
695 * Now we need some number of levels of "../" to get from source
696 * to here, and then we just replicate the rest of 'target'.
697 */
698 int levels = 0;
699 while (s <= sourceend) {
700 if (*s == '/')
701 levels++;
702 s++;
703 }
704 int len = 3*levels + strlen(t);
705 if (len == 0) {
706 /* One last special case: if target has no tail _and_ we
707 * haven't written out any "../". */
708 return dupstr("./");
709 } else {
710 char *ret = snewn(len+1, char);
711 char *p = ret;
712 while (levels-- > 0) {
713 *p++ = '.';
714 *p++ = '.';
715 *p++ = '/';
716 }
717 strcpy(p, t);
718 return ret;
719 }
00c5e40c 720}
721
70322ae3 722#define PIXEL_SIZE 600 /* FIXME: configurability? */
723static void write_report_line(struct html *ctx, struct vector *vec)
724{
742c1a74 725 unsigned long long size, asize, divisor;
afe761f3 726 double display_size;
70322ae3 727 int pix, newpix;
728 int i;
afe761f3 729 const char *unitsfmt;
70322ae3 730
731 /*
010dd2a2 732 * A line with literally zero space usage should not be
733 * printed at all if it's a link to a subdirectory (since it
734 * probably means the whole thing was excluded by some
735 * --exclude-path wildcard). If it's [files] or the top-level
736 * line, though, we must always print _something_, and in that
737 * case we must fiddle about to prevent divisions by zero in
738 * the code below.
742c1a74 739 */
16139d21 740 if (!vec->sizes[MAXCOLOUR] && !vec->essential)
010dd2a2 741 return;
742c1a74 742 divisor = ctx->totalsize;
010dd2a2 743 if (!divisor) {
742c1a74 744 divisor = 1;
010dd2a2 745 }
742c1a74 746
747 /*
70322ae3 748 * Find the total size of this subdirectory.
749 */
750 size = vec->sizes[MAXCOLOUR];
afe761f3 751 compute_display_size(size, &unitsfmt, &display_size);
70322ae3 752 htprintf(ctx, "<tr>\n"
afe761f3 753 "<td style=\"padding: 0.2em; text-align: right\">");
754 htprintf(ctx, unitsfmt, display_size);
755 htprintf(ctx, "</td>\n");
70322ae3 756
757 /*
758 * Generate a colour bar.
759 */
760 htprintf(ctx, "<td style=\"padding: 0.2em\">\n");
761 begin_colour_bar(ctx);
762 pix = 0;
763 for (i = 0; i <= MAXCOLOUR; i++) {
764 asize = vec->sizes[i];
742c1a74 765 newpix = asize * PIXEL_SIZE / divisor;
70322ae3 766 add_to_colour_bar(ctx, i, newpix - pix);
767 pix = newpix;
768 }
769 add_to_colour_bar(ctx, -1, PIXEL_SIZE - pix);
770 end_colour_bar(ctx);
771 htprintf(ctx, "</td>\n");
772
773 /*
774 * Output size as a percentage of totalsize.
775 */
776 htprintf(ctx, "<td style=\"padding: 0.2em; text-align: right\">"
742c1a74 777 "%.2f%%</td>\n", (double)size / divisor * 100.0);
70322ae3 778
779 /*
780 * Output a subdirectory marker.
781 */
782 htprintf(ctx, "<td style=\"padding: 0.2em\">");
783 if (vec->name) {
784 int doing_href = 0;
785
c47f39de 786 if (ctx->uriformat && vec->want_href) {
787 char *targeturi = format_string(ctx->uriformat, vec->index,
788 ctx->t);
789 char *link = make_href(ctx->oururi, targeturi);
790 htprintf(ctx, "<a href=\"%s\">", link);
791 sfree(link);
792 sfree(targeturi);
70322ae3 793 doing_href = 1;
794 }
b49db535 795 if (vec->literal)
796 htprintf(ctx, "<code>");
70322ae3 797 htescape(ctx, vec->name, strlen(vec->name), 1);
b49db535 798 if (vec->literal)
799 htprintf(ctx, "</code>");
70322ae3 800 if (doing_href)
801 htprintf(ctx, "</a>");
802 }
803 htprintf(ctx, "</td>\n</tr>\n");
804}
805
0089cdbb 806int strcmptrailingpathsep(const char *a, const char *b)
807{
808 while (*a == *b && *a)
809 a++, b++;
810
811 if ((*a == pathsep && !a[1] && !*b) ||
812 (*b == pathsep && !b[1] && !*a))
813 return 0;
814
815 return (int)(unsigned char)*a - (int)(unsigned char)*b;
816}
817
f2e52893 818char *html_query(const void *t, unsigned long index,
00c5e40c 819 const struct html_config *cfg, int downlink)
70322ae3 820{
821 struct html actx, *ctx = &actx;
c47f39de 822 char *path, *path2, *p, *q;
70322ae3 823 char agebuf1[80], agebuf2[80];
c47f39de 824 size_t pathlen, subdirpos;
70322ae3 825 unsigned long index2;
826 int i;
827 struct vector **vecs;
828 int nvecs, vecsize;
829 unsigned long xi1, xi2, xj1, xj2;
830
831 if (index >= trie_count(t))
832 return NULL;
833
834 ctx->buf = NULL;
835 ctx->buflen = ctx->bufsize = 0;
836 ctx->t = t;
c47f39de 837 ctx->uriformat = cfg->uriformat;
70322ae3 838 htprintf(ctx, "<html>\n");
839
840 path = snewn(1+trie_maxpathlen(t), char);
841 ctx->path2 = path2 = snewn(1+trie_maxpathlen(t), char);
c47f39de 842 ctx->oururi = format_string(cfg->uriformat, index, t);
70322ae3 843
844 /*
845 * HEAD section.
846 */
847 htprintf(ctx, "<head>\n");
848 trie_getpath(t, index, path);
bf53e756 849 htprintf(ctx, "<title>%s: ", PNAME);
70322ae3 850 htescape(ctx, path, strlen(path), 0);
851 htprintf(ctx, "</title>\n");
852 htprintf(ctx, "</head>\n");
853
854 /*
855 * Begin BODY section.
856 */
857 htprintf(ctx, "<body>\n");
858 htprintf(ctx, "<h3 align=center>Disk space breakdown by"
859 " last-access time</h3>\n");
860
861 /*
862 * Show the pathname we're centred on, with hyperlinks to
863 * parent directories where available.
864 */
865 htprintf(ctx, "<p align=center>\n<code>");
866 q = path;
cfe942fb 867 for (p = strchr(path, pathsep); p && p[1]; p = strchr(p, pathsep)) {
70322ae3 868 int doing_href = 0;
256c29a2 869 char c, *zp;
870
70322ae3 871 /*
872 * See if this path prefix exists in the trie. If so,
873 * generate a hyperlink.
874 */
256c29a2 875 zp = p;
876 if (p == path) /* special case for "/" at start */
877 zp++;
878
879 p++;
880
881 c = *zp;
882 *zp = '\0';
70322ae3 883 index2 = trie_before(t, path);
884 trie_getpath(t, index2, path2);
c47f39de 885 if (!strcmptrailingpathsep(path, path2) && cfg->uriformat) {
886 char *targeturi = format_string(cfg->uriformat, index2, t);
887 char *link = make_href(ctx->oururi, targeturi);
888 htprintf(ctx, "<a href=\"%s\">", link);
889 sfree(link);
890 sfree(targeturi);
70322ae3 891 doing_href = 1;
892 }
256c29a2 893 *zp = c;
894 htescape(ctx, q, zp - q, 1);
70322ae3 895 if (doing_href)
896 htprintf(ctx, "</a>");
256c29a2 897 htescape(ctx, zp, p - zp, 1);
898 q = p;
70322ae3 899 }
900 htescape(ctx, q, strlen(q), 1);
901 htprintf(ctx, "</code>\n");
902
903 /*
904 * Decide on the age limit of our colour coding, establish the
905 * colour thresholds, and write out a key.
906 */
70322ae3 907 ctx->now = time(NULL);
f2e52893 908 if (cfg->autoage) {
909 ctx->oldest = index_order_stat(t, 0.05);
910 ctx->newest = index_order_stat(t, 1.0);
911 ctx->oldest = round_and_format_age(ctx, ctx->oldest, agebuf1, -1);
912 ctx->newest = round_and_format_age(ctx, ctx->newest, agebuf2, +1);
913 } else {
914 ctx->oldest = cfg->oldest;
915 ctx->newest = cfg->newest;
916 ctx->oldest = round_and_format_age(ctx, ctx->oldest, agebuf1, 0);
917 ctx->newest = round_and_format_age(ctx, ctx->newest, agebuf2, 0);
918 }
3f940260 919 for (i = 0; i < MAXCOLOUR; i++) {
70322ae3 920 ctx->thresholds[i] =
3f940260 921 ctx->oldest + (ctx->newest - ctx->oldest) * i / (MAXCOLOUR-1);
922 }
923 for (i = 0; i <= MAXCOLOUR; i++) {
924 char buf[80];
925
926 if (i == 0) {
927 strcpy(buf, "&lt; ");
928 round_and_format_age(ctx, ctx->thresholds[0], buf+5, 0);
929 } else if (i == MAXCOLOUR) {
930 strcpy(buf, "&gt; ");
931 round_and_format_age(ctx, ctx->thresholds[MAXCOLOUR-1], buf+5, 0);
932 } else {
933 unsigned long long midrange =
934 (ctx->thresholds[i-1] + ctx->thresholds[i]) / 2;
935 round_and_format_age(ctx, midrange, buf, 0);
936 }
937
938 ctx->titletexts[i] = dupstr(buf);
70322ae3 939 }
940 htprintf(ctx, "<p align=center>Key to colour coding (mouse over for more detail):\n");
941 htprintf(ctx, "<p align=center style=\"padding: 0; margin-top:0.4em; "
c828a5bc 942 "margin-bottom:1em\">");
70322ae3 943 begin_colour_bar(ctx);
944 htprintf(ctx, "<td style=\"padding-right:1em\">%s</td>\n", agebuf1);
945 for (i = 0; i < MAXCOLOUR; i++)
946 add_to_colour_bar(ctx, i, 1);
947 htprintf(ctx, "<td style=\"padding-left:1em\">%s</td>\n", agebuf2);
948 end_colour_bar(ctx);
949
950 /*
951 * Begin the main table.
952 */
953 htprintf(ctx, "<p align=center>\n<table style=\"margin:0; border:0\">\n");
954
955 /*
956 * Find the total size of our entire subdirectory. We'll use
957 * that as the scale for all the colour bars in this report.
958 */
3f940260 959 get_indices(t, path, &xi1, &xi2);
960 ctx->totalsize = fetch_size(t, xi1, xi2, ULLONG_MAX);
70322ae3 961
962 /*
963 * Generate a report line for the whole subdirectory.
964 */
965 vecsize = 64;
966 vecs = snewn(vecsize, struct vector *);
967 nvecs = 1;
16139d21 968 vecs[0] = make_vector(ctx, path, 0, 1, NULL, 0);
70322ae3 969 print_heading(ctx, "Overall");
970 write_report_line(ctx, vecs[0]);
971
972 /*
973 * Now generate report lines for all its children, and the
974 * files contained in it.
975 */
976 print_heading(ctx, "Subdirectories");
977
978 vecs[0]->name = dupstr("[files]");
979 get_indices(t, path, &xi1, &xi2);
980 xi1++;
981 pathlen = strlen(path);
256c29a2 982 subdirpos = pathlen + 1;
983 if (pathlen > 0 && path[pathlen-1] == pathsep)
984 subdirpos--;
70322ae3 985 while (xi1 < xi2) {
986 trie_getpath(t, xi1, path2);
987 get_indices(t, ctx->path2, &xj1, &xj2);
988 xi1 = xj2;
16139d21 989 if (!cfg->showfiles && xj2 - xj1 <= 1)
70322ae3 990 continue; /* skip individual files */
991 if (nvecs >= vecsize) {
992 vecsize = nvecs * 3 / 2 + 64;
993 vecs = sresize(vecs, vecsize, struct vector *);
994 }
995 assert(strlen(path2) > pathlen);
00c5e40c 996 vecs[nvecs] = make_vector(ctx, path2, downlink && (xj2 - xj1 > 1), 0,
16139d21 997 path2 + subdirpos, 1);
70322ae3 998 for (i = 0; i <= MAXCOLOUR; i++)
999 vecs[0]->sizes[i] -= vecs[nvecs]->sizes[i];
1000 nvecs++;
1001 }
1002
1003 qsort(vecs, nvecs, sizeof(vecs[0]), vec_compare);
1004
1005 for (i = 0; i < nvecs; i++)
1006 write_report_line(ctx, vecs[i]);
1007
1008 /*
1009 * Close the main table.
1010 */
1011 htprintf(ctx, "</table>\n");
1012
1013 /*
1014 * Finish up and tidy up.
1015 */
1016 htprintf(ctx, "</body>\n");
1017 htprintf(ctx, "</html>\n");
c47f39de 1018 sfree(ctx->oururi);
70322ae3 1019 sfree(path2);
1020 sfree(path);
1021 for (i = 0; i < nvecs; i++) {
1022 sfree(vecs[i]->name);
1023 sfree(vecs[i]);
1024 }
1025 sfree(vecs);
1026
1027 return ctx->buf;
1028}
00c5e40c 1029
1030int html_dump(const void *t, unsigned long index, unsigned long endindex,
1031 int maxdepth, const struct html_config *cfg,
1032 const char *pathprefix)
1033{
1034 /*
1035 * Determine the filename for this file.
1036 */
c47f39de 1037 assert(cfg->fileformat != NULL);
1038 char *filename = format_string(cfg->fileformat, index, t);
1039 char *path = dupfmt("%s%s", pathprefix, filename);
1040 sfree(filename);
00c5e40c 1041
1042 /*
1043 * Create the HTML itself. Don't write out downlinks from our
1044 * deepest level.
1045 */
1046 char *html = html_query(t, index, cfg, maxdepth != 0);
1047
1048 /*
1049 * Write it out.
1050 */
c47f39de 1051 FILE *fp = fopen(path, "w");
00c5e40c 1052 if (!fp) {
c47f39de 1053 fprintf(stderr, "%s: %s: open: %s\n", PNAME, path, strerror(errno));
00c5e40c 1054 return 1;
1055 }
1056 if (fputs(html, fp) < 0) {
c47f39de 1057 fprintf(stderr, "%s: %s: write: %s\n", PNAME, path, strerror(errno));
00c5e40c 1058 fclose(fp);
1059 return 1;
1060 }
1061 if (fclose(fp) < 0) {
c47f39de 1062 fprintf(stderr, "%s: %s: fclose: %s\n", PNAME, path, strerror(errno));
00c5e40c 1063 return 1;
1064 }
c47f39de 1065 sfree(path);
00c5e40c 1066
1067 /*
1068 * Recurse.
1069 */
1070 if (maxdepth != 0) {
1071 unsigned long subindex, subendindex;
1072 int newdepth = (maxdepth > 0 ? maxdepth - 1 : maxdepth);
c47f39de 1073 char rpath[1+trie_maxpathlen(t)];
00c5e40c 1074
1075 index++;
1076 while (index < endindex) {
c47f39de 1077 trie_getpath(t, index, rpath);
1078 get_indices(t, rpath, &subindex, &subendindex);
00c5e40c 1079 index = subendindex;
1080 if (subendindex - subindex > 1) {
1081 if (html_dump(t, subindex, subendindex, newdepth,
1082 cfg, pathprefix))
1083 return 1;
1084 }
1085 }
1086 }
1087 return 0;
1088}