1/* See LICENSE file for copyright and license details. */
2
3
4#include "util.h"
5#include "arg.h"
6
7#include <arpa/inet.h>
8#include <ctype.h>
9#include <dirent.h>
10#include <errno.h>
11#include <fcntl.h>
12#include <netdb.h>
13#include <netinet/in.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <sys/socket.h>
18#include <sys/stat.h>
19#include <sys/types.h>
20#include <time.h>
21#include <unistd.h>
22
23static void
24usage(void)
25{
26 eprintf("usage: %s [-e string] [-d string] [-v] [dir]\n", argv0);
27}
28
29static char *
30url_decode(char *s)
31{
32 char *r, *w;
33 unsigned int val;
34
35 for (r = w = s; *r; ) {
36 if (*r == '%' && isxdigit((unsigned char)r[1]) && isxdigit((unsigned char)r[2])) {
37 sscanf(r + 1, "%2x", &val);
38 *w++ = val;
39 r += 3;
40 } else if (*r == '+') {
41 *w++ = ' ';
42 r++;
43 } else {
44 *w++ = *r++;
45 }
46 }
47 *w = '\0';
48 return s;
49}
50
51static char *
52url_encode(const char *s)
53{
54 char *buf = emalloc(strlen(s) * 3 + 1);
55 char *w = buf;
56 const char *r = s;
57
58 while (*r) {
59 if (isalnum((unsigned char)*r) || strchr("-._~", *r)) {
60 *w++ = *r++;
61 } else {
62 w += sprintf(w, "%%%02X", (unsigned char)*r);
63 r++;
64 }
65 }
66 *w = '\0';
67 return buf;
68}
69
70static void
71send_error(int status, const char *msg)
72{
73 printf("HTTP/1.1 %d %s\r\n"
74 "Content-Type: text/html; charset=UTF-8\r\n"
75 "Connection: close\r\n\r\n", status, msg);
76 printf("<html><head><title>%d %s</title></head>"
77 "<body><h3>%d %s</h3></body></html>\n", status, msg, status, msg);
78}
79
80static int
81is_under(const char *file, const char *dir)
82{
83 char *rfile = realpath(file, NULL);
84 char *rdir = realpath(dir, NULL);
85 int rc = 0;
86 size_t len;
87
88 if (rfile && rdir) {
89 len = strlen(rdir);
90 if (strncmp(rfile, rdir, len) == 0) {
91 if (rfile[len] == '\0' || rfile[len] == '/' || (len > 0 && rdir[len - 1] == '/'))
92 rc = 1;
93 }
94 }
95 free(rfile);
96 free(rdir);
97 return rc;
98}
99
100static const char *
101get_mime_type(const char *file)
102{
103 const char *ext = strrchr(file, '.');
104 if (!ext)
105 return "application/octet-stream";
106 ext++;
107
108 if (strcasecmp(ext, "html") == 0 || strcasecmp(ext, "htm") == 0)
109 return "text/html; charset=UTF-8";
110 if (strcasecmp(ext, "css") == 0)
111 return "text/css";
112 if (strcasecmp(ext, "js") == 0)
113 return "application/javascript";
114 if (strcasecmp(ext, "png") == 0)
115 return "image/png";
116 if (strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "jpeg") == 0)
117 return "image/jpeg";
118 if (strcasecmp(ext, "gif") == 0)
119 return "image/gif";
120 if (strcasecmp(ext, "txt") == 0)
121 return "text/plain; charset=UTF-8";
122 if (strcasecmp(ext, "pdf") == 0)
123 return "application/pdf";
124 if (strcasecmp(ext, "zip") == 0)
125 return "application/zip";
126
127 return "application/octet-stream";
128}
129
130static void
131handle_connection(void)
132{
133 struct stat st, st_idx;
134 struct dirent *de;
135 DIR *dir;
136 char line[4096];
137 char method[32], path[2048], proto[32];
138 char index_path[sizeof(path) + 32];
139 char file_buf[8192];
140 char date_buf[64];
141 const char *mime_type;
142 char *query, *p, *enc;
143 ssize_t n_read;
144 size_t len;
145 int fd;
146
147 if (!fgets(line, sizeof(line), stdin))
148 return;
149
150 if (sscanf(line, "%31s %2047s %31s", method, path, proto) != 3) {
151 send_error(400, "Bad Request");
152 return;
153 }
154
155 while (fgets(line, sizeof(line), stdin)) {
156 if (line[0] == '\r' || line[0] == '\n')
157 break;
158 }
159
160 if (strcasecmp(method, "GET") != 0) {
161 send_error(501, "Not Implemented");
162 return;
163 }
164
165 url_decode(path);
166
167 query = strchr(path, '?');
168 if (query) {
169 *query = '\0';
170 setenv("QUERY_STRING", query + 1, 1);
171 } else {
172 unsetenv("QUERY_STRING");
173 }
174
175 p = path;
176 while (*p == '/')
177 p++;
178 if (*p == '\0')
179 p = ".";
180
181 if (stat(p, &st) < 0) {
182 send_error(404, "Not Found");
183 return;
184 }
185
186 if (!is_under(p, ".")) {
187 send_error(403, "Forbidden");
188 return;
189 }
190
191 if (S_ISDIR(st.st_mode)) {
192 len = strlen(path);
193 if (len > 0 && path[len - 1] != '/') {
194 printf("HTTP/1.1 302 Found\r\n"
195 "Location: %s/\r\n"
196 "Connection: close\r\n\r\n", path);
197 return;
198 }
199
200 snprintf(index_path, sizeof(index_path), "%s/index.html", p);
201 if (stat(index_path, &st_idx) == 0 && S_ISREG(st_idx.st_mode)) {
202 p = index_path;
203 st = st_idx;
204 goto serve_file;
205 }
206
207 dir = opendir(p);
208 if (!dir) {
209 send_error(403, "Forbidden");
210 return;
211 }
212
213 printf("HTTP/1.1 200 OK\r\n"
214 "Content-Type: text/html; charset=UTF-8\r\n"
215 "Connection: close\r\n\r\n");
216 printf("<html><head><title>Index of %s</title></head><body>\n", path);
217 printf("<h3>Index of %s</h3><hr><pre>\n", path);
218
219 while ((de = readdir(dir))) {
220 if (strcmp(de->d_name, ".") == 0)
221 continue;
222 enc = url_encode(de->d_name);
223 printf("<a href=\"%s\">%s</a>\n", enc, de->d_name);
224 free(enc);
225 }
226 printf("</pre><hr></body></html>\n");
227 closedir(dir);
228 return;
229 }
230
231serve_file:
232 fd = open(p, O_RDONLY);
233 if (fd < 0) {
234 send_error(403, "Forbidden");
235 return;
236 }
237
238 mime_type = get_mime_type(p);
239 strftime(date_buf, sizeof(date_buf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&st.st_mtime));
240
241 printf("HTTP/1.1 200 OK\r\n"
242 "Content-Type: %s\r\n"
243 "Content-Length: %lld\r\n"
244 "Last-Modified: %s\r\n"
245 "Connection: close\r\n\r\n",
246 mime_type, (long long)st.st_size, date_buf);
247
248 while ((n_read = read(fd, file_buf, sizeof(file_buf))) > 0) {
249 writeall(1, file_buf, n_read);
250 }
251 close(fd);
252}
253
254// ?man httpd: simple http daemon
255// ?man arguments: dir
256// ?man serve static files over http
257int
258main(int argc, char *argv[])
259{
260 char *eflag = NULL;
261 char *dflag = NULL;
262 char *enc;
263
264 ARGBEGIN {
265 // ?man -e:str: specify expression or pattern
266 case 'e':
267 eflag = EARGF(usage());
268 break;
269 // ?man -d:str: specify directory
270 case 'd':
271 dflag = EARGF(usage());
272 break;
273 // ?man -v: verbose mode; show progress
274 case 'v':
275 break;
276 default:
277 usage();
278 } ARGEND
279
280 if (eflag) {
281 enc = url_encode(eflag);
282 printf("%s\n", enc);
283 free(enc);
284 return 0;
285 }
286
287 if (dflag) {
288 printf("%s\n", url_decode(dflag));
289 return 0;
290 }
291
292 if (argc > 0) {
293 if (chdir(argv[0]) < 0)
294 eprintf("chdir %s:\n", argv[0]);
295 }
296
297 handle_connection();
298 return 0;
299}