Minor fixes
[MonkeyD.git] / plugins / dirlisting / dirlisting.c
blobb779719821c2e843fb1b23b1157b54844f1dcb84
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 /* Monkey HTTP Daemon
4 * ------------------
5 * Copyright (C) 2001-2009, Eduardo Silva P.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
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 Library General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 * Some history about this module
24 * ------------------------------
25 * 2008 - Rewrite module, suport dynamic themes by Eduardo
26 * 2008 - Felipe Astroza (max) provided the mk_dirhtml_human_readable_size_func()
27 * 2007 - Add struct client_request support by Eduardo
28 * 2002 - Original version written by Daniel R. Ome
31 #include <dirent.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/stat.h>
36 #include <sys/socket.h>
37 #include <time.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <unistd.h>
43 #include "monkey.h"
44 #include "http.h"
45 #include "http_status.h"
46 #include "str.h"
47 #include "memory.h"
48 #include "utils.h"
49 #include "config.h"
50 #include "method.h"
51 #include "socket.h"
52 #include "header.h"
53 #include "file.h"
54 #include "iov.h"
56 #include "plugin.h"
57 #include "dirlisting.h"
59 /* Plugin data for register */
60 mk_plugin_data_t _shortname = "dirlisting";
61 mk_plugin_data_t _name = "Directory Listing";
62 mk_plugin_data_t _version = "1.0";
63 mk_plugin_stage_t _stages = MK_PLUGIN_STAGE_40;;
65 struct plugin_api *mk_api;
67 char *_tags_global[] = { "%_html_title_%",
68 "%_theme_path_%",
69 NULL
72 char *_tags_entry[] = { "%_target_title_%",
73 "%_target_url_%",
74 "%_target_name_%",
75 "%_target_time_%",
76 "%_target_size_%",
77 NULL
80 /* DIR_HTML logic:
81 * ---------------
82 * [Monkey Start]
83 * |
84 * *--> mk_dirhtml_conf()
85 * |
86 * *--> mk_dirhtml_read_config()
87 * *--> mk_dirhtml_theme_load()
88 * |
89 * *--> mk_dirhtml_load_file() (FILE_HEADER, FILE_ENTRY, FILE_FOOTER)
90 * *--> mk_dirhtml_template_create()
91 * |
92 * *-->
98 /* Function wrote by Max (Felipe Astroza), thanks! */
99 char *mk_dirhtml_human_readable_size(off_t size)
101 unsigned long u = 1024, i, len;
102 char *buf;
103 static const char *__units[] = { "b", "K", "M", "G",
104 "T", "P", "E", "Z", "Y", NULL
107 for (i = 0; __units[i] != NULL; i++) {
108 if ((size / u) == 0) {
109 break;
111 u *= 1024;
113 if (!i) {
114 mk_api->str_build(&buf, &len, "%u%s", size, __units[0]);
116 else {
117 float fsize = (float) ((double) size / (u / 1024));
118 mk_api->str_build(&buf, &len, "%.1f%s", fsize, __units[i]);
121 return buf;
124 struct mk_f_list *mk_dirhtml_create_element(char *file,
125 unsigned char type,
126 char *full_path,
127 unsigned long *list_len)
129 off_t size;
130 int n;
131 struct tm *st_time;
132 struct mk_f_list *entry;
133 struct file_info *entry_info;
135 entry_info = mk_api->file_get_info(full_path);
137 entry = mk_api->mem_alloc_z(sizeof(struct mk_f_list));
138 entry->name = file;
139 entry->type = type;
140 entry->info = entry_info;
141 entry->next = NULL;
143 st_time = localtime((time_t *) & entry_info->last_modification);
145 entry->ft_modif = mk_api->mem_alloc_z(50);
146 n = strftime(entry->ft_modif, 50, "%d-%b-%G %H:%M", st_time);
148 size = entry->info->size;
150 if (type != DT_DIR) {
151 entry->size = mk_dirhtml_human_readable_size(size);
153 else {
154 entry->size = MK_DIRHTML_SIZE_DIR;
157 *list_len = *list_len + 1;
159 return entry;
162 struct mk_f_list *mk_dirhtml_create_list(DIR * dir, char *path,
163 unsigned long *list_len)
165 unsigned long len;
166 char *full_path = 0;
167 struct dirent *ent;
168 struct mk_f_list *list = 0, *entry = 0, *last = 0;
170 /* Before to send the information, we need to build
171 * the list of entries, this really sucks because the user
172 * always will want to have the information sorted, why we don't
173 * add some spec to the HTTP protocol in order to send the information
174 * in a generic way and let the client choose how to show it
175 * as they does browsing a FTP server ???, we can save bandweight,
176 * let the cool firefox developers create different templates and
177 * we are going to have a more happy end users.
179 * that kind of ideas comes when you are in an airport just waiting :)
182 while ((ent = readdir(dir)) != NULL) {
183 if (ent->d_name[0] == '.')
184 continue;
186 /* Look just for files and dirs */
187 if (ent->d_type != DT_REG && ent->d_type != DT_DIR
188 && ent->d_type != DT_LNK) {
189 continue;
192 if (!ent->d_name) {
193 puts("mk_dirhtml :: buffer error");
197 mk_api->str_build(&full_path, &len, "%s%s", path, ent->d_name);
199 entry = mk_dirhtml_create_element(ent->d_name,
200 ent->d_type, full_path, list_len);
202 mk_api->mem_free(full_path);
204 if (!entry) {
205 continue;
208 if (!list) {
209 list = entry;
211 else {
212 last->next = entry;
214 last = entry;
217 return list;
220 /* Read dirhtml config and themes */
221 int mk_dirhtml_conf(char *confdir)
223 int ret = 0;
224 unsigned long len;
225 char *conf_file;
227 mk_api->str_build(&conf_file, &len, "%s", confdir);
228 ret = mk_dirhtml_read_config(conf_file);
230 /* This function will load the default theme
231 * setted in dirhtml_conf struct
233 ret = mk_dirhtml_theme_load();
234 mk_api->pointer_set(&mk_dirhtml_default_mime, MK_DIRHTML_DEFAULT_MIME);
236 return ret;
240 * Read the main configuration file for dirhtml: dirhtml.conf,
241 * it will alloc the dirhtml_conf struct
243 int mk_dirhtml_read_config(char *path)
245 unsigned long len;
246 char *default_file;
247 struct mk_config *p;
249 mk_api->str_build(&default_file, &len, "%sdirhtml.conf", path);
250 p = conf = mk_api->config_create(default_file);
252 /* alloc dirhtml config struct */
253 dirhtml_conf = mk_api->mem_alloc(sizeof(struct dirhtml_config));
255 while (p) {
256 if (strcasecmp(p->key, "Theme") == 0) {
257 dirhtml_conf->theme = mk_api->str_dup(p->val);
258 mk_api->str_build(&dirhtml_conf->theme_path, &len,
259 "%sthemes/%s/", path, dirhtml_conf->theme);
261 p = p->next;
264 mk_api->mem_free(default_file);
265 return 0;
268 int mk_dirhtml_theme_load()
270 /* Data */
271 char *header, *entry, *footer;
273 /* Load theme files */
274 header = mk_dirhtml_load_file(MK_DIRHTML_FILE_HEADER);
275 entry = mk_dirhtml_load_file(MK_DIRHTML_FILE_ENTRY);
276 footer = mk_dirhtml_load_file(MK_DIRHTML_FILE_FOOTER);
278 if (!header || !entry || !footer) {
279 mk_api->mem_free(header);
280 mk_api->mem_free(entry);
281 mk_api->mem_free(footer);
282 return -1;
285 /* Parse themes */
286 mk_dirhtml_tpl_header = mk_dirhtml_template_create(header);
287 mk_dirhtml_tpl_entry = mk_dirhtml_template_create(entry);
288 mk_dirhtml_tpl_footer = mk_dirhtml_template_create(footer);
290 #ifdef DEBUG_THEME
291 /* Debug data */
292 mk_dirhtml_theme_debug(&mk_dirhtml_tpl_header);
293 mk_dirhtml_theme_debug(&mk_dirhtml_tpl_entry);
294 mk_dirhtml_theme_debug(&mk_dirhtml_tpl_footer);
296 #endif
297 mk_api->mem_free(header);
298 mk_api->mem_free(entry);
299 mk_api->mem_free(footer);
301 return 0;
304 #ifdef DEBUG_THEME
305 int mk_dirhtml_theme_debug(struct dirhtml_template **st_tpl)
307 int i = 0;
308 struct dirhtml_template *aux;
310 aux = *st_tpl;
312 printf("\n** DEBUG_THEME **");
313 fflush(stdout);
315 while (aux) {
316 printf("\n%i) len=%i, tag_id=%i", i, aux->len, aux->tag_id);
317 if (aux->tag_id >= 0) {
318 printf(" (%s) ", aux->tags[aux->tag_id]);
320 fflush(stdout);
321 aux = aux->next;
322 i++;
324 return 0;
326 #endif
328 /* Search which tag exists first in content :
329 * ex: %_html_title_%
331 int mk_dirhtml_theme_match_tag(char *content, char *tpl[])
333 int i, len, match;
335 for (i = 0; tpl[i]; i++) {
336 len = strlen(tpl[i]);
337 match = (int) mk_api->str_search_n(content, tpl[i], len);
338 if (match >= 0) {
339 return i;
343 return -1;
346 /* return the number of valid tags found in text string */
347 int mk_dirhtml_content_count_tags(char *content, char *tpl[])
349 int pos = 0, count = 0;
350 int len, tpl_idx;
351 int loop = 0;
353 len = strlen(content);
354 while (loop < len) {
355 pos =
356 (int) mk_api->str_search_n(content + loop, MK_DIRHTML_TAG_INIT,
357 -1);
358 if (pos >= 0) {
359 tpl_idx = mk_dirhtml_theme_match_tag(content + loop, tpl);
360 if (tpl_idx >= 0) {
361 count++;
363 loop += pos;
365 else {
366 break;
368 loop++;
371 return count;
374 struct dirhtml_template *mk_dirhtml_template_create(char *content)
376 int i = 0, cont_len;
377 int pos, last = 0; /* 0=search init, 1=search end */
378 int n_tags = 0, tpl_idx = 0;
380 char *_buf;
381 int _len;
383 /* Global keys */
384 char **_tpl = 0;
386 /* Template to return */
387 struct dirhtml_template *st_tpl = 0;
389 cont_len = strlen(content);
390 if (cont_len <= 0) {
391 return NULL;
394 /* Parsing content */
395 while (i < cont_len) {
396 pos = (int) mk_api->str_search_n(content + i,
397 MK_DIRHTML_TAG_INIT, -1);
399 if (pos < 0) {
400 break;
403 /* Checking global tag, if it's not found, proceed with
404 * 'entry tags'
406 _tpl = (char **) _tags_global;
407 tpl_idx = mk_dirhtml_theme_match_tag(content + i + pos, _tpl);
409 /* if global template do not match, use the entry tags */
410 if (tpl_idx < 0) {
411 _tpl = (char **) _tags_entry;
412 tpl_idx = mk_dirhtml_theme_match_tag(content + i + pos, _tpl);
415 /* if tag found is known, we add them to our list */
416 if (tpl_idx >= 0) {
418 _buf = mk_api->str_copy_substr(content, i, i + pos);
419 _len = strlen(_buf);
421 /* Dummy if/else to create or pass a created st_tpl */
422 if (!st_tpl) {
423 st_tpl = mk_dirhtml_template_list_add(NULL,
424 _buf, _len, _tpl, -1);
426 else {
427 mk_dirhtml_template_list_add(&st_tpl, _buf, _len, _tpl, -1);
429 i += (pos + strlen(_tpl[tpl_idx]));
431 /* This means that a value need to be replaced */
432 mk_dirhtml_template_list_add(&st_tpl, NULL, -1, _tpl, tpl_idx);
433 n_tags++;
435 else {
436 i++;
440 if (last < cont_len) {
441 _buf = mk_api->str_copy_substr(content, i, cont_len);
442 _len = strlen(_buf);
444 if (n_tags <= 0) {
445 st_tpl = mk_dirhtml_template_list_add(NULL, _buf, _len, _tpl, -1);
447 else {
448 mk_dirhtml_template_list_add(&st_tpl, _buf, _len, _tpl, -1);
452 return st_tpl;
455 struct dirhtml_template
456 *mk_dirhtml_template_list_add(struct dirhtml_template **header,
457 char *buf, int len, char **tpl, int tag_id)
459 struct dirhtml_template *node, *aux;
461 node = mk_api->mem_alloc_z(sizeof(struct dirhtml_template));
462 if (!node) {
463 return NULL;
467 node->buf = buf;
468 node->len = len;
469 node->tag_id = tag_id;
470 node->tags = tpl;
471 node->next = NULL;
474 if(tag_id >= 0){
475 printf("\n -> %s", node->tags[node->tag_id]);
478 if (!header) {
479 return (struct dirhtml_template *) node;
482 aux = *header;
483 while ((*aux).next != NULL) {
484 aux = (*aux).next;
487 (*aux).next = node;
488 return (struct dirhtml_template *) node;
491 int mk_dirhtml_tag_get_id(char *tpl_tags[], char *tag)
493 int i;
494 for (i = 0; tpl_tags[i]; i++) {
495 if (strcmp(tpl_tags[i], tag) == 0) {
496 return i;
500 return -1;
503 int mk_dirhtml_template_len(struct dirhtml_template *tpl)
505 int len = 0;
506 struct dirhtml_template *aux;
508 aux = tpl;
509 while (aux) {
510 len++;
511 aux = aux->next;
514 return len;
517 struct mk_iov *mk_dirhtml_theme_compose(struct dirhtml_template *template,
518 struct dirhtml_value *values)
521 * template = struct { char buf ; int len, int tag }
522 * values = struct {int tag, char *value, struct *next}
525 struct mk_iov *iov;
526 struct dirhtml_template *tpl = template;
527 struct dirhtml_value *val = values;
529 int tpl_len;
531 tpl_len = mk_dirhtml_template_len(template);
533 /* we duplicate the lenght in case we get separators */
534 iov = (struct mk_iov *) mk_api->iov_create(tpl_len * 2, 1);
535 tpl = template;
537 while (tpl) {
538 /* check for dynamic value */
539 if (!tpl->buf && tpl->tag_id >= 0) {
540 val = values;
541 while (val) {
542 if (val->tags == tpl->tags && val->tag_id == tpl->tag_id) {
543 mk_api->iov_add_entry(iov,
544 val->value,
545 val->len,
546 val->sep, MK_IOV_NOT_FREE_BUF);
547 break;
549 else {
550 val = val->next;
555 /* static */
556 else {
557 mk_api->iov_add_entry(iov, tpl->buf,
558 tpl->len, mk_iov_none, MK_IOV_NOT_FREE_BUF);
560 tpl = tpl->next;
562 return (struct mk_iov *) iov;
565 struct dirhtml_value *mk_dirhtml_tag_assign(struct dirhtml_value **values,
566 int tag_id, mk_pointer sep,
567 char *value, char **tags)
569 struct dirhtml_value *check, *aux = 0;
571 aux = mk_api->mem_alloc(sizeof(struct dirhtml_value));
572 if (!aux) {
573 return NULL;
576 aux->tag_id = tag_id;
577 aux->value = value;
578 aux->sep = sep;
579 aux->tags = tags;
581 if (value) {
582 aux->len = strlen(value);
584 else {
585 aux->len = -1;
588 aux->next = NULL;
590 if (!values) {
591 return (struct dirhtml_value *) aux;
594 check = *values;
595 while ((*check).next) {
596 check = (*check).next;
599 (*check).next = aux;
602 return (struct dirhtml_value *) aux;
605 void mk_dirhtml_tag_free_list(struct dirhtml_value **list)
607 struct dirhtml_value *prev, *target;
609 target = *list;
610 while (target) {
611 while ((*target).next) {
612 prev = target;
613 target = (*target).next;
615 mk_api->mem_free(target);
617 if (target == *list) {
618 break;
620 (*prev).next = NULL;
621 target = *list;
623 *list = NULL;
626 char *mk_dirhtml_load_file(char *filename)
628 char *tmp = 0, *data = 0;
629 unsigned long len;
631 mk_api->str_build(&tmp, &len, "%s%s", dirhtml_conf->theme_path, filename);
633 if (!tmp) {
634 return NULL;
637 data = mk_api->file_to_buffer(tmp);
638 mk_api->mem_free(tmp);
640 if (!data) {
641 return NULL;
644 return (char *) data;
647 int mk_dirhtml_entry_cmp(const void *a, const void *b)
649 struct mk_f_list *const *f_a = a;
650 struct mk_f_list *const *f_b = b;
652 return strcmp((*f_a)->name, (*f_b)->name);
655 int mk_dirhtml_send(int fd, struct request *sr, struct mk_iov *data)
657 int n;
658 unsigned long len;
659 char *buf = 0;
661 if (sr->protocol >= HTTP_PROTOCOL_11) {
662 /* Chunk header */
663 mk_api->str_build(&buf, &len, "%x%s", data->total_len - 1, MK_CRLF);
665 /* Add chunked information */
666 mk_api->iov_set_entry(data, buf, len, MK_IOV_FREE_BUF, 0);
668 n = (int) mk_api->iov_send(fd, data, MK_IOV_SEND_TO_SOCKET);
669 return n;
672 int mk_dirhtml_send_chunked_end(int fd)
674 char *_end = "0\r\n\r\n";
675 int len = 5;
677 return write(fd, _end, len);
680 void mk_dirhtml_free_list(struct mk_f_list **toc, unsigned long len)
682 int i;
683 struct mk_f_list *entry;
685 for (i = 0; i < len; i++) {
686 entry = toc[i];
688 if (entry->type != DT_DIR) {
689 mk_api->mem_free(entry->size);
691 mk_api->mem_free(entry->ft_modif);
692 mk_api->mem_free(entry->info);
693 mk_api->mem_free(entry);
696 mk_api->mem_free(toc);
699 int mk_dirhtml_init(struct client_request *cr, struct request *sr)
701 DIR *dir;
702 int i = 0, n;
703 mk_pointer sep;
705 /* file info */
706 unsigned long list_len = 0;
708 struct mk_f_list *file_list, *entry, **toc;
709 struct mk_iov *iov_header, *iov_footer, *iov_entry;
710 struct dirhtml_value *values_global = 0;
711 struct dirhtml_value *values_entry = 0;
713 if (!(dir = opendir(sr->real_path.data))) {
714 return -1;
717 file_list = mk_dirhtml_create_list(dir, sr->real_path.data, &list_len);
719 /* Building headers */
720 sr->headers->status = M_HTTP_OK;
721 sr->headers->cgi = SH_CGI;
722 sr->headers->breakline = MK_HEADER_BREAKLINE;
723 sr->headers->content_type = mk_dirhtml_default_mime;
725 if (sr->protocol >= HTTP_PROTOCOL_11) {
726 sr->headers->transfer_encoding = MK_HEADER_TE_TYPE_CHUNKED;
729 /* Sending headers */
730 n = (int) mk_api->header_send(cr->socket, cr, sr, sr->log);
732 /* Creating response template */
733 /* Set %_html_title_% */
734 values_global = mk_dirhtml_tag_assign(NULL, 0, mk_iov_none,
735 sr->uri_processed,
736 (char **) _tags_global);
738 /* Set %_theme_path_% */
739 mk_dirhtml_tag_assign(&values_global, 1, mk_iov_none,
740 dirhtml_conf->theme_path, (char **) _tags_global);
742 /* HTML Header */
743 iov_header = mk_dirhtml_theme_compose(mk_dirhtml_tpl_header,
744 values_global);
746 /* HTML Footer */
747 iov_footer = mk_dirhtml_theme_compose(mk_dirhtml_tpl_footer,
748 values_global);
750 /* Creating table of contents and sorting */
751 toc = mk_api->mem_alloc(sizeof(struct mk_f_list) * list_len);
752 entry = file_list;
753 i = 0;
754 while (entry) {
755 toc[i] = entry;
756 i++;
757 entry = entry->next;
759 qsort(toc, list_len, sizeof(*toc), mk_dirhtml_entry_cmp);
761 n = mk_dirhtml_send(cr->socket, sr, iov_header);
763 if (n < 0) {
764 closedir(dir);
766 mk_dirhtml_tag_free_list(&values_global);
767 mk_api->iov_free(iov_header);
768 mk_api->iov_free(iov_footer);
769 mk_dirhtml_free_list(toc, list_len);
770 return 0;
773 /* sending TOC */
774 for (i = 0; i < list_len; i++) {
775 /* %_target_title_% */
776 if (toc[i]->type == DT_DIR) {
777 sep = mk_iov_slash;
779 else {
780 sep = mk_iov_none;
783 /* target title */
784 values_entry = mk_dirhtml_tag_assign(NULL, 0, sep,
785 toc[i]->name,
786 (char **) _tags_entry);
788 /* target url */
789 mk_dirhtml_tag_assign(&values_entry, 1, sep,
790 toc[i]->name, (char **) _tags_entry);
792 /* target name */
793 mk_dirhtml_tag_assign(&values_entry, 2, sep,
794 toc[i]->name, (char **) _tags_entry);
796 /* target modification time */
797 mk_dirhtml_tag_assign(&values_entry, 3, mk_iov_none,
798 toc[i]->ft_modif, (char **) _tags_entry);
800 /* target size */
801 mk_dirhtml_tag_assign(&values_entry, 4, mk_iov_none,
802 toc[i]->size, (char **) _tags_entry);
804 iov_entry = mk_dirhtml_theme_compose(mk_dirhtml_tpl_entry,
805 values_entry);
807 /* send entry */
808 n = mk_dirhtml_send(cr->socket, sr, iov_entry);
810 if ((i % 20) == 0 && i > 0) {
811 mk_api->socket_cork_flag(cr->socket, TCP_CORK_OFF);
812 mk_api->socket_cork_flag(cr->socket, TCP_CORK_ON);
815 if (n < 0) {
816 break;
819 /* free entry list */
820 mk_dirhtml_tag_free_list(&values_entry);
821 mk_api->iov_free(iov_entry);
824 n = mk_dirhtml_send(cr->socket, sr, iov_footer);
825 mk_api->socket_cork_flag(cr->socket, TCP_CORK_OFF);
827 if (sr->protocol >= HTTP_PROTOCOL_11 && n >= 0) {
828 mk_dirhtml_send_chunked_end(cr->socket);
831 closedir(dir);
833 mk_dirhtml_tag_free_list(&values_global);
834 mk_api->iov_free(iov_header);
835 mk_api->iov_free(iov_footer);
836 mk_dirhtml_free_list(toc, list_len);
838 return 0;
841 int _mk_plugin_init(void **api, char *confdir)
843 mk_api = *api;
844 mk_dirhtml_conf(confdir);
845 return 0;
848 int _mk_plugin_stage_40(struct plugin *plugin, struct client_request *cr, struct request *sr)
850 /* Validate file/directory */
851 if (!sr->file_info) {
852 return -1;
855 /* This plugin just handle directories */
856 if (sr->file_info->is_directory == MK_FILE_FALSE) {
857 return -1;
860 /* check setup */
861 if (sr->host_conf->getdir == VAR_OFF) {
862 mk_request_error(M_CLIENT_FORBIDDEN, cr, sr, 1, sr->log);
863 return -1;
866 mk_dirhtml_init(cr, sr);
867 return 0;