Added a htls program for directory listings.
[ashd.git] / src / htls.c
CommitLineData
121d8be9
FT
1/*
2 ashd - A Sane HTTP Daemon
3 Copyright (C) 2008 Fredrik Tolf <fredrik@dolda2000.com>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19#include <stdlib.h>
20#include <unistd.h>
21#include <stdio.h>
22#include <dirent.h>
23#include <sys/stat.h>
24#include <string.h>
25#include <errno.h>
26#include <time.h>
27#include <stdint.h>
28#include <locale.h>
29
30#ifdef HAVE_CONFIG_H
31#include <config.h>
32#endif
33#include <utils.h>
34#include <resp.h>
35#include <log.h>
36#include <resp.h>
37
38struct dentry {
39 char *name;
40 struct stat sb;
41 int w, x;
42};
43
44static int dispmtime = 0;
45static int dispsize = 0;
46
47static void checkcache(struct stat *sb)
48{
49 char *hdr;
50
51 if((hdr = getenv("REQ_IF_MODIFIED_SINCE")) != NULL) {
52 if(parsehttpdate(hdr) < sb->st_mtime)
53 return;
54 printf("HTTP/1.1 304 Not Modified\n");
55 printf("Date: %s\n", fmthttpdate(time(NULL)));
56 printf("Content-Length: 0\n");
57 printf("\n");
58 exit(0);
59 }
60}
61
62static int dcmp(const void *ap, const void *bp)
63{
64 const struct dentry *a = ap, *b = bp;
65
66 if(S_ISDIR(a->sb.st_mode) && !S_ISDIR(b->sb.st_mode))
67 return(-1);
68 if(!S_ISDIR(a->sb.st_mode) && S_ISDIR(b->sb.st_mode))
69 return(1);
70 return(strcoll(a->name, b->name));
71}
72
73static void head(char *name, struct charbuf *dst)
74{
75 char *title;
76
77 bprintf(dst, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
78 bprintf(dst, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n");
79 bprintf(dst, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n");
80 bprintf(dst, "<head>\n");
81 title = htmlquote(name);
82 bprintf(dst, "<title>Index of %s</title>\n", title);
83 bprintf(dst, "</head>\n");
84 bprintf(dst, "<body>\n");
85 bprintf(dst, "<h1>Index of %s</h1>\n", title);
86 free(title);
87}
88
89static void foot(struct charbuf *dst)
90{
91 bprintf(dst, "</body>\n");
92 bprintf(dst, "</html>\n");
93}
94
95static void mkindex(char *name, DIR *dir, struct charbuf *dst)
96{
97 struct {
98 struct dentry *b;
99 size_t s, d;
100 } dirbuf;
101 struct dentry f;
102 struct dirent *dent;
103 char *fn;
104 int i;
105
106 bufinit(dirbuf);
107 while((dent = readdir(dir)) != NULL) {
108 if(*dent->d_name == '.')
109 continue;
110 memset(&f, 0, sizeof(f));
111 f.name = sstrdup(dent->d_name);
112 fn = sprintf3("%s/%s", name, dent->d_name);
113 if(access(fn, R_OK))
114 continue;
115 if(stat(fn, &f.sb))
116 continue;
117 if(!access(fn, W_OK))
118 f.w = 1;
119 if(!access(fn, X_OK))
120 f.x = 1;
121 else if(S_ISDIR(f.sb.st_mode))
122 continue;
123 bufadd(dirbuf, f);
124 }
125 qsort(dirbuf.b, dirbuf.d, sizeof(struct dentry), dcmp);
126 bprintf(dst, "<table class=\"dirindex\">\n");
127 for(i = 0; i < dirbuf.d; i++) {
128 bprintf(dst, "<tr class=\"dentry");
129 if(S_ISDIR(dirbuf.b[i].sb.st_mode))
130 bprintf(dst, " dir");
131 if(dirbuf.b[i].w)
132 bprintf(dst, " writable");
133 if(dirbuf.b[i].x)
134 bprintf(dst, " exec");
135 bprintf(dst, "\">");
136 fn = htmlquote(dirbuf.b[i].name);
137 bprintf(dst, "<td class=\"filename\"><a href=\"%s\">%s</a></td>", fn, fn);
138 free(fn);
139 if(dispsize && !S_ISDIR(dirbuf.b[i].sb.st_mode))
140 bprintf(dst, "<td class=\"filesize\">%ji</td>", (intmax_t)dirbuf.b[i].sb.st_size);
141 if(dispmtime)
142 bprintf(dst, "<td class=\"filemtime\">%s</td>", fmthttpdate(dirbuf.b[i].sb.st_mtime));
143 bprintf(dst, "</tr>\n");
144 free(dirbuf.b[i].name);
145 }
146 bprintf(dst, "</table>\n");
147}
148
149static void usage(void)
150{
151 flog(LOG_ERR, "usage: htls [-hms] METHOD URL REST");
152}
153
154int main(int argc, char **argv)
155{
156 int c;
157 char *dname;
158 DIR *dir;
159 struct charbuf buf;
160 struct stat sb;
161
162 setlocale(LC_ALL, "");
163 while((c = getopt(argc, argv, "hms")) >= 0) {
164 switch(c) {
165 case 'h':
166 usage();
167 exit(0);
168 case 'm':
169 dispmtime = 1;
170 break;
171 case 's':
172 dispsize = 1;
173 break;
174 default:
175 usage();
176 exit(1);
177 }
178 }
179 if(argc - optind < 3) {
180 usage();
181 exit(1);
182 }
183 if((dname = getenv("REQ_X_ASH_FILE")) == NULL) {
184 flog(LOG_ERR, "htls: needs to be called with the X-Ash-File header");
185 exit(1);
186 }
187 if(*argv[optind + 2]) {
188 simpleerror(1, 404, "Not Found", "The requested URL has no corresponding resource.");
189 exit(0);
190 }
191
192 if(stat(dname, &sb) || ((dir = opendir(dname)) == NULL)) {
193 flog(LOG_ERR, "htls: could not open directory `%s': %s", dname, strerror(errno));
194 simpleerror(1, 500, "Server Error", "Could not produce directory index.");
195 exit(1);
196 }
197 checkcache(&sb);
198
199 bufinit(buf);
200 head(argv[optind + 1], &buf);
201 mkindex(dname, dir, &buf);
202 foot(&buf);
203 closedir(dir);
204
205 printf("HTTP/1.1 200 OK\n");
206 printf("Content-Type: text/html; charset=UTF-8\n");
207 printf("Last-Modified: %s\n", fmthttpdate(sb.st_mtime));
208 printf("Content-Length: %zi\n", buf.d);
209 printf("\n");
210 fwrite(buf.b, 1, buf.d, stdout);
211 buffree(buf);
212 return(0);
213}