| 461 | | char **ptr; |
| 462 | | size_t used; |
| 463 | | size_t size; |
| 464 | | } string_buffer; |
| | 461 | size_t namelen; |
| | 462 | time_t mtime; |
| | 463 | off_t size; |
| | 464 | } dirls_entry_t; |
| | 465 | |
| | 466 | typedef struct { |
| | 467 | dirls_entry_t **ent; |
| | 468 | int used; |
| | 469 | int size; |
| | 470 | } dirls_list_t; |
| | 471 | |
| | 472 | #define DIRLIST_ENT_NAME(ent) (char*) ent + sizeof(dirls_entry_t) |
| | 473 | #define DIRLIST_BLOB_SIZE 16 |
| | 474 | |
| | 475 | /* simple combsort algorithm */ |
| | 476 | static void http_dirls_sort(dirls_entry_t **ent, int num) { |
| | 477 | int gap = num; |
| | 478 | int i, j; |
| | 479 | int swapped; |
| | 480 | dirls_entry_t *tmp; |
| | 481 | |
| | 482 | do { |
| | 483 | gap = (gap * 10) / 13; |
| | 484 | if (gap == 9 || gap == 10) |
| | 485 | gap = 11; |
| | 486 | if (gap < 1) |
| | 487 | gap = 1; |
| | 488 | swapped = 0; |
| | 489 | |
| | 490 | for (i = 0; i < num - gap; i++) { |
| | 491 | j = i + gap; |
| | 492 | if (strcmp(DIRLIST_ENT_NAME(ent[i]), DIRLIST_ENT_NAME(ent[j])) > 0) { |
| | 493 | tmp = ent[i]; |
| | 494 | ent[i] = ent[j]; |
| | 495 | ent[j] = tmp; |
| | 496 | swapped = 1; |
| | 497 | } |
| | 498 | } |
| | 499 | |
| | 500 | } while (gap > 1 || swapped); |
| | 501 | } |
| | 502 | |
| | 503 | /* buffer must be able to hold "999.9K" |
| | 504 | * conversion is simple but not perfect |
| | 505 | */ |
| | 506 | static int http_list_directory_sizefmt(char *buf, off_t size) { |
| | 507 | const char unit[] = "KMGTPE"; /* Kilo, Mega, Tera, Peta, Exa */ |
| | 508 | const char *u = unit - 1; /* u will always increment at least once */ |
| | 509 | int remain; |
| | 510 | char *out = buf; |
| | 511 | |
| | 512 | if (size < 100) |
| | 513 | size += 99; |
| | 514 | if (size < 100) |
| | 515 | size = 0; |
| | 516 | |
| | 517 | while (1) { |
| | 518 | remain = (int) size & 1023; |
| | 519 | size >>= 10; |
| | 520 | u++; |
| | 521 | if ((size & (~0 ^ 1023)) == 0) |
| | 522 | break; |
| | 523 | } |
| | 524 | |
| | 525 | remain /= 100; |
| | 526 | if (remain > 9) |
| | 527 | remain = 9; |
| | 528 | if (size > 999) { |
| | 529 | size = 0; |
| | 530 | remain = 9; |
| | 531 | u++; |
| | 532 | } |
| | 533 | |
| | 534 | out += ltostr(out, size); |
| | 535 | out[0] = '.'; |
| | 536 | out[1] = remain + '0'; |
| | 537 | out[2] = *u; |
| | 538 | out[3] = '\0'; |
| | 539 | |
| | 540 | return (out + 3 - buf); |
| | 541 | } |
| | 542 | |
| | 543 | static void http_list_directory_header(buffer *out, connection *con) { |
| | 544 | BUFFER_APPEND_STRING_CONST(out, |
| | 545 | "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n" |
| | 546 | "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n" |
| | 547 | "<head>\n" |
| | 548 | "<title>Index of " |
| | 549 | ); |
| | 550 | buffer_append_string_html_encoded(out, con->uri.path->ptr); |
| | 551 | BUFFER_APPEND_STRING_CONST(out, "</title>\n"); |
| | 552 | |
| | 553 | if (con->conf.dirlist_css->used > 1) { |
| | 554 | BUFFER_APPEND_STRING_CONST(out, "<link rel=\"stylesheet\" type=\"text/css\" href=\""); |
| | 555 | buffer_append_string_buffer(out, con->conf.dirlist_css); |
| | 556 | BUFFER_APPEND_STRING_CONST(out, "\" />\n"); |
| | 557 | } else { |
| | 558 | BUFFER_APPEND_STRING_CONST(out, |
| | 559 | "<style type=\"text/css\">\n" |
| | 560 | "a, a:active {text-decoration: none; color: blue;}\n" |
| | 561 | "a:visited {color: #48468F;}\n" |
| | 562 | "a:hover, a:focus {text-decoration: underline; color: red;}\n" |
| | 563 | "body {background-color: #F5F5F5;}\n" |
| | 564 | "h2 {margin-bottom: 12px;}\n" |
| | 565 | "table {margin-left: 12px;}\n" |
| | 566 | "th, td {" |
| | 567 | " font-family: \"Courier New\", Courier, monospace;" |
| | 568 | " font-size: 10pt;" |
| | 569 | " text-align: left;" |
| | 570 | "}\n" |
| | 571 | "th {" |
| | 572 | " font-weight: bold;" |
| | 573 | " padding-right: 14px;" |
| | 574 | " padding-bottom: 3px;" |
| | 575 | "}\n" |
| | 576 | ); |
| | 577 | BUFFER_APPEND_STRING_CONST(out, |
| | 578 | "td {padding-right: 14px;}\n" |
| | 579 | "td.s, th.s {text-align: right;}\n" |
| | 580 | "div.list {" |
| | 581 | " background-color: white;" |
| | 582 | " border-top: 1px solid #646464;" |
| | 583 | " border-bottom: 1px solid #646464;" |
| | 584 | " padding-top: 10px;" |
| | 585 | " padding-bottom: 14px;" |
| | 586 | "}\n" |
| | 587 | "div.foot {" |
| | 588 | " font-family: \"Courier New\", Courier, monospace;" |
| | 589 | " font-size: 10pt;" |
| | 590 | " color: #787878;" |
| | 591 | " padding-top: 4px;" |
| | 592 | "}\n" |
| | 593 | "</style>\n" |
| | 594 | ); |
| | 595 | } |
| | 596 | |
| | 597 | BUFFER_APPEND_STRING_CONST(out, "</head>\n<body>\n<h2>Index of "); |
| | 598 | buffer_append_string_html_encoded(out, con->uri.path->ptr); |
| | 599 | BUFFER_APPEND_STRING_CONST(out, |
| | 600 | "</h2>\n" |
| | 601 | "<div class=\"list\">\n" |
| | 602 | "<table cellpadding=\"0\" cellspacing=\"0\">\n" |
| | 603 | "<thead>" |
| | 604 | "<tr>" |
| | 605 | "<th class=\"n\">Name</th>" |
| | 606 | "<th class=\"m\">Last Modified</th>" |
| | 607 | "<th class=\"s\">Size</th>" |
| | 608 | "<th class=\"t\">Type</th>" |
| | 609 | "</tr>" |
| | 610 | "</thead>\n" |
| | 611 | "<tbody>\n" |
| | 612 | "<tr>" |
| | 613 | "<td class=\"n\"><a href=\"../\">Parent Directory</a>/</td>" |
| | 614 | "<td class=\"m\"> </td>" |
| | 615 | "<td class=\"s\">- </td>" |
| | 616 | "<td class=\"t\">Directory</td>" |
| | 617 | "</tr>\n" |
| | 618 | ); |
| | 619 | } |
| | 620 | |
| | 621 | static void http_list_directory_footer(buffer *out, connection *con) { |
| | 622 | BUFFER_APPEND_STRING_CONST(out, |
| | 623 | "</tbody>\n" |
| | 624 | "</table>\n" |
| | 625 | "</div>\n" |
| | 626 | "<div class=\"foot\">" |
| | 627 | ); |
| | 628 | |
| | 629 | if (buffer_is_empty(con->conf.server_tag)) { |
| | 630 | BUFFER_APPEND_STRING_CONST(out, PACKAGE_NAME "/" PACKAGE_VERSION); |
| | 631 | } else { |
| | 632 | buffer_append_string_buffer(out, con->conf.server_tag); |
| | 633 | } |
| | 634 | |
| | 635 | BUFFER_APPEND_STRING_CONST(out, |
| | 636 | "</div>\n" |
| | 637 | "</body>\n" |
| | 638 | "</html>\n" |
| | 639 | ); |
| | 640 | } |
| 478 | | |
| 479 | | b = chunkqueue_get_append_buffer(con->write_queue); |
| 480 | | buffer_copy_string(b, |
| 481 | | "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n" |
| 482 | | "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" |
| 483 | | " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" |
| 484 | | "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" |
| 485 | | " <head>\n" |
| 486 | | " <title>Directory Listing for "); |
| 487 | | buffer_append_string_html_encoded(b, con->uri.path->ptr); |
| 488 | | |
| 489 | | BUFFER_APPEND_STRING_CONST(b, |
| 490 | | "</title>\n" |
| 491 | | " <style type=\"text/css\">\n"); |
| 492 | | |
| 493 | | BUFFER_APPEND_STRING_CONST(b, |
| 494 | | "th.dirlisting { background-color: black; color: white; }\n" |
| 495 | | "table.dirlisting { border: black solid thin; }\n" |
| 496 | | "td.dirlisting { background-color: #f0f0f0; }\n" |
| 497 | | "td.dirlistingnumber { background-color: #f0f0f0; text-align: right }\n" |
| 498 | | ); |
| 499 | | |
| 500 | | BUFFER_APPEND_STRING_CONST(b, |
| 501 | | " </style>\n" |
| 502 | | " </head>\n" |
| 503 | | " <body>\n" |
| 504 | | " <h2>Directory Listing for "); |
| 505 | | buffer_append_string_html_encoded(b, con->uri.path->ptr); |
| 506 | | BUFFER_APPEND_STRING_CONST(b,"</h2>\n <table class='dirlisting'>\n"); |
| 507 | | BUFFER_APPEND_STRING_CONST(b," <tr class='dirlisting'><th class='dirlisting'>date</th><th class='dirlisting'>size</th><th class='dirlisting'>type</th><th class='dirlisting'>name</th></tr>\n"); |
| 508 | | |
| 509 | | |
| 510 | | /* allocate memory */ |
| 511 | | sb = calloc(1, sizeof(*sb)); |
| 512 | | assert(sb); |
| 513 | | |
| 514 | | sb->ptr = NULL; |
| 515 | | sb->used = 0; |
| 516 | | sb->size = 0; |
| 517 | | |
| 518 | | while(NULL != (dent = readdir(d))) { |
| 519 | | size_t j; |
| 520 | | if (sb->size == 0) { |
| 521 | | sb->size = 4; |
| 522 | | sb->ptr = malloc(sizeof(*sb->ptr) * sb->size); |
| 523 | | assert(sb->ptr); |
| 524 | | } else if (sb->used == sb->size) { |
| 525 | | sb->size += 4; |
| 526 | | sb->ptr = realloc(sb->ptr, sizeof(*sb->ptr) * sb->size); |
| 527 | | assert(sb->ptr); |
| 528 | | } |
| 529 | | |
| 530 | | for (i = 0; i < sb->used; i++) { |
| 531 | | if (strcmp(dent->d_name, sb->ptr[i]) < 0) { |
| 532 | | break; |
| 533 | | } |
| 534 | | } |
| 535 | | |
| 536 | | /* <left><right> */ |
| 537 | | /* <left>[i]<right> */ |
| 538 | | |
| 539 | | for (j = sb->used - 1; sb->used && j >= i && (j+1) > 0; j--) { |
| 540 | | sb->ptr[j + 1] = sb->ptr[j]; |
| 541 | | } |
| 542 | | sb->ptr[i] = strdup(dent->d_name); |
| 543 | | |
| 544 | | sb->used++; |
| 545 | | |
| 546 | | } |
| 547 | | |
| 548 | | closedir(d); |
| 549 | | |
| 550 | | date_buf = buffer_init(); |
| 551 | | buffer_prepare_copy(date_buf, 22); |
| 552 | | for (i = 0; i < sb->used; i++) { |
| 553 | | struct stat st; |
| 554 | | struct tm tm; |
| 555 | | size_t s_len = strlen(sb->ptr[i]); |
| 556 | | |
| 557 | | |
| 558 | | buffer_copy_string(srv->tmp_buf, dir->ptr); |
| 559 | | buffer_append_string(srv->tmp_buf, sb->ptr[i]); |
| 560 | | |
| 561 | | if (0 != stat(srv->tmp_buf->ptr, &st)) { |
| 562 | | free(sb->ptr[i]); |
| | 679 | |
| | 680 | dirs.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE); |
| | 681 | assert(dirs.ent); |
| | 682 | dirs.size = DIRLIST_BLOB_SIZE; |
| | 683 | dirs.used = 0; |
| | 684 | files.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE); |
| | 685 | assert(files.ent); |
| | 686 | files.size = DIRLIST_BLOB_SIZE; |
| | 687 | files.used = 0; |
| | 688 | |
| | 689 | while ((dent = readdir(dp)) != NULL) { |
| | 690 | if (dent->d_name[0] == '.') { |
| | 691 | if (hide_dotfiles) |
| | 692 | continue; |
| | 693 | if (dent->d_name[1] == '\0') |
| | 694 | continue; |
| | 695 | if (dent->d_name[1] == '.' && dent->d_name[2] == '\0') |
| | 696 | continue; |
| | 697 | } |
| | 698 | |
| | 699 | /* NOTE: the manual says, d_name is never more than NAME_MAX |
| | 700 | * so this should actually not be a buffer-overflow-risk |
| | 701 | */ |
| | 702 | i = strlen(dent->d_name); |
| | 703 | if (i > NAME_MAX) |
| 564 | | } |
| 565 | | |
| 566 | | BUFFER_APPEND_STRING_CONST(b, |
| 567 | | " <tr><td class='dirlisting'>"); |
| 568 | | |
| | 705 | memcpy(path_file, dent->d_name, i + 1); |
| | 706 | if (stat(path, &st) != 0) |
| | 707 | continue; |
| | 708 | |
| | 709 | list = &files; |
| | 710 | if (S_ISDIR(st.st_mode)) |
| | 711 | list = &dirs; |
| | 712 | |
| | 713 | if (list->used == list->size) { |
| | 714 | list->size += DIRLIST_BLOB_SIZE; |
| | 715 | list->ent = (dirls_entry_t**) realloc(list->ent, sizeof(dirls_entry_t*) * list->size); |
| | 716 | assert(list->ent); |
| | 717 | } |
| | 718 | |
| | 719 | tmp = (dirls_entry_t*) malloc(sizeof(dirls_entry_t) + 1 + i); |
| | 720 | tmp->mtime = st.st_mtime; |
| | 721 | tmp->size = st.st_size; |
| | 722 | tmp->namelen = i; |
| | 723 | memcpy(DIRLIST_ENT_NAME(tmp), dent->d_name, i + 1); |
| | 724 | |
| | 725 | list->ent[list->used++] = tmp; |
| | 726 | } |
| | 727 | closedir(dp); |
| | 728 | |
| | 729 | if (dirs.used) http_dirls_sort(dirs.ent, dirs.used); |
| | 730 | |
| | 731 | if (files.used) http_dirls_sort(files.ent, files.used); |
| | 732 | |
| | 733 | out = chunkqueue_get_append_buffer(con->write_queue); |
| | 734 | BUFFER_COPY_STRING_CONST(out, "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"); |
| | 735 | http_list_directory_header(out, con); |
| | 736 | |
| | 737 | /* directories */ |
| | 738 | for (i = 0; i < dirs.used; i++) { |
| | 739 | tmp = dirs.ent[i]; |
| | 740 | |
| 577 | | date_buf->used++; |
| 578 | | |
| 579 | | buffer_append_string_buffer(b, date_buf); |
| 580 | | BUFFER_APPEND_STRING_CONST(b, |
| 581 | | "</td><td class='dirlistingnumber'>"); |
| 582 | | |
| 583 | | buffer_append_off_t(b, st.st_size); |
| 584 | | |
| 585 | | /* mime type */ |
| 586 | | BUFFER_APPEND_STRING_CONST(b, |
| 587 | | "</td><td class='dirlisting'>"); |
| 588 | | |
| 589 | | if (S_ISDIR(st.st_mode)) { |
| 590 | | buffer_append_string_rfill(b, "directory", 28); |
| 591 | | } else { |
| 592 | | size_t k; |
| 593 | | unsigned short have_content_type = 0; |
| | 747 | |
| | 748 | BUFFER_APPEND_STRING_CONST(out, "<tr><td class=\"n\"><a href=\""); |
| | 749 | buffer_append_string_url_encoded(out, DIRLIST_ENT_NAME(tmp)); |
| | 750 | BUFFER_APPEND_STRING_CONST(out, "/\">"); |
| | 751 | buffer_append_string_html_encoded(out, DIRLIST_ENT_NAME(tmp)); |
| | 752 | BUFFER_APPEND_STRING_CONST(out, "</a>/</td><td class=\"m\">"); |
| | 753 | buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1); |
| | 754 | BUFFER_APPEND_STRING_CONST(out, "</td><td class=\"s\">- </td><td class=\"t\">Directory</td></tr>\n"); |
| | 755 | |
| | 756 | free(tmp); |
| | 757 | } |
| | 758 | |
| | 759 | /* files */ |
| | 760 | for (i = 0; i < files.used; i++) { |
| | 761 | tmp = files.ent[i]; |
| 605 | | |
| 606 | | if(!have_content_type) { |
| 607 | | for (k = 0; k < con->conf.mimetypes->used; k++) { |
| 608 | | data_string *ds = (data_string *)con->conf.mimetypes->data[k]; |
| 609 | | size_t ct_len; |
| 610 | | |
| 611 | | if (ds->key->used == 0) continue; |
| 612 | | |
| 613 | | ct_len = ds->key->used - 1; |
| 614 | | |
| 615 | | if (s_len < ct_len) continue; |
| 616 | | |
| 617 | | if (0 == strncmp(sb->ptr[i] + s_len - ct_len, ds->key->ptr, ct_len)) { |
| 618 | | buffer_append_string_rfill(b, ds->value->ptr, 28); |
| 619 | | break; |
| 620 | | } |
| | 777 | content_type = "application/octet-stream"; |
| | 778 | for (k = 0; k < con->conf.mimetypes->used; k++) { |
| | 779 | data_string *ds = (data_string *)con->conf.mimetypes->data[k]; |
| | 780 | size_t ct_len; |
| | 781 | |
| | 782 | if (ds->key->used == 0) |
| | 783 | continue; |
| | 784 | |
| | 785 | ct_len = ds->key->used - 1; |
| | 786 | if (tmp->namelen < ct_len) |
| | 787 | continue; |
| | 788 | |
| | 789 | if (0 == strncmp(DIRLIST_ENT_NAME(tmp) + tmp->namelen - ct_len, ds->key->ptr, ct_len)) { |
| | 790 | content_type = ds->value->ptr; |
| | 791 | break; |
| 622 | | |
| 623 | | if (k == con->conf.mimetypes->used) { |
| 624 | | buffer_append_string_rfill(b, "application/octet-stream", 28); |
| 625 | | } |
| 626 | | } |
| 627 | | } |
| 628 | | |
| 629 | | /* URL */ |
| 630 | | BUFFER_APPEND_STRING_CONST(b,"</td><td class='dirlisting'><a href=\""); |
| 631 | | /* URL encode */ |
| 632 | | buffer_append_string_url_encoded(b, sb->ptr[i]); |
| 633 | | if (S_ISDIR(st.st_mode)) { |
| 634 | | BUFFER_APPEND_STRING_CONST(b,"/"); |
| 635 | | } |
| 636 | | BUFFER_APPEND_STRING_CONST(b,"\">"); |
| 637 | | /* HTML encode */ |
| 638 | | buffer_append_string_html_encoded(b, sb->ptr[i]); |
| 639 | | if (S_ISDIR(st.st_mode)) { |
| 640 | | BUFFER_APPEND_STRING_CONST(b,"/"); |
| 641 | | } |
| 642 | | BUFFER_APPEND_STRING_CONST(b,"</a></td></tr>\n"); |
| 643 | | |
| 644 | | |
| 645 | | free(sb->ptr[i]); |
| 646 | | } |
| 647 | | |
| 648 | | buffer_free(date_buf); |
| 649 | | free(sb->ptr); |
| 650 | | free(sb); |
| 651 | | |
| | 793 | } |
| | 794 | } |
| | 795 | |
| | 796 | #ifdef HAVE_LOCALTIME_R |
| | 797 | localtime_r(&(tmp->mtime), &tm); |
| | 798 | strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm); |
| | 7 |