httpd: replace shell-based dir indexer cgi example with C-based one.
[oweals/busybox.git] / networking / httpd_indexcgi.c
1 /*
2  * Copyright (c) 2007 Denys Vlasenko <vda.linux@googlemail.com>
3  *
4  * Licensed under GPLv2, see file LICENSE in this tarball for details.
5  */
6
7 /*
8  * This program is a CGI application. It creates directory index page.
9  * Put it into cgi-bin/index.cgi and chmod 0755.
10  */
11
12 /* Build a-la
13 i486-linux-uclibc-gcc \
14 -static -static-libgcc \
15 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 \
16 -Wall -Wshadow -Wwrite-strings -Wundef -Wstrict-prototypes -Werror \
17 -Wold-style-definition -Wdeclaration-after-statement -Wno-pointer-sign \
18 -Wmissing-prototypes -Wmissing-declarations \
19 -Os -fno-builtin-strlen -finline-limit=0 -fomit-frame-pointer \
20 -ffunction-sections -fdata-sections -fno-guess-branch-probability \
21 -funsigned-char \
22 -falign-functions=1 -falign-jumps=1 -falign-labels=1 -falign-loops=1 \
23 -march=i386 -mpreferred-stack-boundary=2 \
24 -Wl,-Map -Wl,link.map -Wl,--warn-common -Wl,--sort-common -Wl,--gc-sections \
25 httpd_indexcgi.c -o index.cgi
26
27 Size (approximate):
28  text    data     bss     dec     hex filename
29 22642     160    3052   25854    64fe index.cgi
30 */
31
32 /* TODO: get rid of printf's: printf code is more than 50%
33  * of the entire executable when built against static uclibc */
34
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <errno.h>
38 #include <stdint.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <stdio.h>
43 #include <dirent.h>
44 #include <time.h>
45
46 /* Appearance of the table is controlled by style sheet *ONLY*,
47  * formatting code uses <TAG class=CLASS> to apply style
48  * to elements. Edit stylesheet to your liking and recompile. */
49
50 static const char str_header[] =
51 "" /* Additional headers (currently none) */
52 "\r\n" /* Mandatory empty line after headers */
53 "<html><head><title>Index of %s</title>"                "\n"
54 "<style>"                                               "\n"
55 "table {"                                               "\n"
56 "  width: 100%%;"                                       "\n"
57 "  background-color: #fff5ee;"                          "\n"
58 "  border-width: 1px;" /* 1px 1px 1px 1px; */           "\n"
59 "  border-spacing: 2px;"                                "\n"
60 "  border-style: solid;" /* solid solid solid solid; */ "\n"
61 "  border-color: black;" /* black black black black; */ "\n"
62 "  border-collapse: collapse;"                          "\n"
63 "}"                                                     "\n"
64 "th {"                                                  "\n"
65 "  border-width: 1px;" /* 1px 1px 1px 1px; */           "\n"
66 "  padding: 1px;" /* 1px 1px 1px 1px; */                "\n"
67 "  border-style: solid;" /* solid solid solid solid; */ "\n"
68 "  border-color: black;" /* black black black black; */ "\n"
69 "}"                                                     "\n"
70 "td {"                                                  "\n"
71             /* top right bottom left */
72 "  border-width: 0px 1px 0px 1px;"                      "\n"
73 "  padding: 1px;" /* 1px 1px 1px 1px; */                "\n"
74 "  border-style: solid;" /* solid solid solid solid; */ "\n"
75 "  border-color: black;" /* black black black black; */ "\n"
76 "}"                                                     "\n"
77 "tr.hdr { background-color:#eee5de; }"                  "\n"
78 "tr.o { background-color:#ffffff; }"                    "\n"
79 /* tr.e { ... } - for even rows (currently none) */
80 "tr.foot { background-color:#eee5de; }"                 "\n"
81 "th.cnt { text-align:left; }"                           "\n"
82 "th.sz { text-align:right; }"                           "\n"
83 "th.dt { text-align:right; }"                           "\n"
84 "td.sz { text-align:right; }"                           "\n"
85 "td.dt { text-align:right; }"                           "\n"
86 "col.nm { width: 98%%; }"                               "\n"
87 "col.sz { width: 1%%; }"                                "\n"
88 "col.dt { width: 1%%; }"                                "\n"
89 "</style>"                                              "\n"
90 "</head>"                                               "\n"
91 "<body>"                                                "\n"
92 "<h1>Index of %s</h1>"                                  "\n"
93 ""                                                      "\n"
94 "<table>"                                               "\n"
95 "<col class=nm><col class=sz><col class=dt>"            "\n"
96 "<tr class=hdr><th class=cnt>Name<th class=sz>Size<th class=dt>Last modified" "\n"
97 ;
98
99 static const char str_footer[] =
100 "<tr class=foot><th class=cnt>Files: %u, directories: %u<th class=sz>%llu<th class=dt>&nbsp;" "\n"
101 /* "</table></body></html>" - why bother? */
102 ;
103
104 static int bad_url_char(unsigned c)
105 {
106         return (c - '0') > 9 /* not a digit */
107             && ((c|0x20) - 'a') > 26 /* not A-Z or a-z */
108             && !strchr("._-+@", c);
109 }
110
111 static char *url_encode(const char *name)
112 {
113         int i;
114         int size = 0;
115         int len = strlen(name);
116         char *p, *result;
117
118         i = 0;
119         while (name[i]) {
120                 if (bad_url_char((unsigned)name[i]))
121                         size++;
122                 i++;
123         }
124
125         /* No %xx needed! */
126         if (!size)
127                 return (char*)name;
128
129         /* Each %xx requires 2 additional chars */
130         size = size * 2 + len + 1;
131         p = result = malloc(size);
132
133         i = 0;
134         while (name[i]) {
135                 *p = name[i];
136                 if (bad_url_char((unsigned)name[i])) {
137                         *p++ = '%';
138                         *p++ = "0123456789ABCDEF"[(uint8_t)(name[i]) >> 4];
139                         *p = "0123456789ABCDEF"[(uint8_t)(name[i]) & 0xf];
140                 }
141                 p++;
142                 i++;
143         }
144         *p = 0;
145         return result;
146 }
147
148 static char *html_encode(const char *name)
149 {
150         int i;
151         int size = 0;
152         int len = strlen(name);
153         char *p, *result;
154
155         i = 0;
156         while (name[i]) {
157                 if (name[i] == '<'
158                  || name[i] == '>'
159                  || name[i] == '&'
160                 ) {
161                         size++;
162                 }
163                 i++;
164         }
165
166         /* No &lt; etc needed! */
167         if (!size)
168                 return (char*)name;
169
170         /* &amp; requires 4 additional chars */
171         size = size * 4 + len + 1;
172         p = result = malloc(size);
173
174         i = 0;
175         while (name[i]) {
176                 char c;
177                 *p = c = name[i++];
178                 if (c == '<')
179                         strcpy(p, "&lt;");
180                 else if (c == '>')
181                         strcpy(p, "&gt;");
182                 else if (c == '&')
183                         strcpy(++p, "amp;");
184                 else {
185                         p++;
186                         continue;
187                 }
188                 p += 4;
189         }
190         *p = 0;
191         return result;
192 }
193
194 typedef struct dir_list_t {
195         char  *dl_name;
196         mode_t dl_mode;
197         off_t  dl_size;
198         time_t dl_mtime;
199 } dir_list_t;
200
201 static int compare_dl(dir_list_t *a, dir_list_t *b)
202 {
203         if (strcmp(a->dl_name, "..") == 0) {
204                 /* ".." is 'less than' any other dir entry */
205                 return -1;
206         }
207         if (strcmp(b->dl_name, "..") == 0) {
208                 return 1;
209         }
210         if (S_ISDIR(a->dl_mode) != S_ISDIR(b->dl_mode)) {
211                 /* 1 if b is a dir (and thus a is 'after' b, a > b),
212                  * else -1 (a < b)*/
213                 return (S_ISDIR(b->dl_mode) != 0) ? 1 : -1;
214         }
215         return strcmp(a->dl_name, b->dl_name);
216 }
217
218 int main(void)
219 {
220         dir_list_t *dir_list;
221         dir_list_t *cdir;
222         unsigned dir_list_count;
223         unsigned count_dirs;
224         unsigned count_files;
225         unsigned long long size_total;
226         int odd;
227         DIR *dirp;
228         char *QUERY_STRING;
229
230         QUERY_STRING = getenv("QUERY_STRING");
231         if (!QUERY_STRING
232          || QUERY_STRING[0] != '/'
233          || strstr(QUERY_STRING, "/../")
234          || strcmp(strrchr(QUERY_STRING, '/'), "/..") == 0
235         ) {
236                 return 1;
237         }
238
239         if (chdir("..")
240          || (QUERY_STRING[1] && chdir(QUERY_STRING + 1))
241         ) {
242                 return 1;
243         }
244
245         dirp = opendir(".");
246         if (!dirp)
247                 return 1;
248
249         dir_list = NULL;
250         dir_list_count = 0;
251         while (1) {
252                 struct dirent *dp;
253                 struct stat sb;
254
255                 dp = readdir(dirp);
256                 if (!dp)
257                         break;
258                 if (dp->d_name[0] == '.' && !dp->d_name[1])
259                         continue;
260                 if (stat(dp->d_name, &sb) != 0)
261                         continue;
262                 dir_list = realloc(dir_list, (dir_list_count + 1) * sizeof(dir_list[0]));
263                 dir_list[dir_list_count].dl_name = strdup(dp->d_name);
264                 dir_list[dir_list_count].dl_mode = sb.st_mode;
265                 dir_list[dir_list_count].dl_size = sb.st_size;
266                 dir_list[dir_list_count].dl_mtime = sb.st_mtime;
267                 dir_list_count++;
268         }
269
270         qsort(dir_list, dir_list_count, sizeof(dir_list[0]), (void*)compare_dl);
271
272         /* Guard against directories wit &, > etc */
273         QUERY_STRING = html_encode(QUERY_STRING);
274         printf(str_header, QUERY_STRING, QUERY_STRING);
275
276         odd = 0;
277         count_dirs = 0;
278         count_files = 0;
279         size_total = 0;
280
281         cdir = dir_list;
282         while (dir_list_count--) {
283                 char size_str[sizeof(long long) * 3];
284                 const char *slash_if_dir;
285                 struct tm *tm;
286                 char *href;
287                 char *filename;
288                 char datetime_str[sizeof("2000-02-02&nbsp;02:02:02")];
289
290                 slash_if_dir = "/";
291                 if (S_ISDIR(cdir->dl_mode)) {
292                         count_dirs++;
293                         size_str[0] = '\0';
294                 } else if (S_ISREG(cdir->dl_mode)) {
295                         count_files++;
296                         size_total += cdir->dl_size;
297                         slash_if_dir++; /* points to "" now */
298                         sprintf(size_str, "%llu", (unsigned long long)(cdir->dl_size));
299                 } else
300                         goto next;
301                 href = url_encode(cdir->dl_name); /* %20 etc */
302                 filename = html_encode(cdir->dl_name); /* &lt; etc */
303                 tm = gmtime(&cdir->dl_mtime);
304                 sprintf(datetime_str, "%04u-%02u-%02u&nbsp;%02u:%02u:%02u",
305                         (unsigned)(1900 + tm->tm_year),
306                         (unsigned)(tm->tm_mon + 1),
307                         (unsigned)(tm->tm_mday),
308                         (unsigned)(tm->tm_hour),
309                         (unsigned)(tm->tm_min),
310                         (unsigned)(tm->tm_sec)
311                 );
312                 printf("<tr class=%c><td class=nm><a href='%s%s'>%s%s</a><td class=sz>%s<td class=dt>%s\n",
313                         odd ? 'o' : 'e',
314                         href, slash_if_dir,
315                         filename, slash_if_dir,
316                         size_str,
317                         datetime_str
318                 );
319                 if (cdir->dl_name != href)
320                         free(href);
321                 if (cdir->dl_name != filename)
322                         free(filename);
323                 odd = 1 - odd;
324  next:
325                 cdir++;
326         }
327
328         /* count_dirs - 1: we don't want to count ".." */
329         printf(str_footer, count_files, count_dirs - 1, size_total);
330         return 0;
331 }