master xplshn/aruu / cmd / net / httpd.c
  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}