rev |
line source |
nuclear@12
|
1 /* tinyweb - tiny web server library and daemon
|
nuclear@12
|
2 * Author: John Tsiombikas <nuclear@member.fsf.org>
|
nuclear@12
|
3 *
|
nuclear@12
|
4 * This program is placed in the public domain. Feel free to use it any
|
nuclear@12
|
5 * way you like. Mentions and retaining this attribution header will be
|
nuclear@12
|
6 * appreciated, but not required.
|
nuclear@12
|
7 */
|
nuclear@7
|
8 #include <stdio.h>
|
nuclear@7
|
9 #include <stdlib.h>
|
nuclear@7
|
10 #include <string.h>
|
nuclear@7
|
11 #include <ctype.h>
|
nuclear@7
|
12 #include <errno.h>
|
nuclear@7
|
13 #include <unistd.h>
|
nuclear@7
|
14 #include <fcntl.h>
|
nuclear@7
|
15 #include <sys/stat.h>
|
nuclear@7
|
16 #include <sys/select.h>
|
nuclear@7
|
17 #include <sys/mman.h>
|
nuclear@7
|
18 #include <sys/types.h>
|
nuclear@7
|
19 #include <sys/socket.h>
|
nuclear@7
|
20 #include <arpa/inet.h>
|
nuclear@7
|
21 #include "tinyweb.h"
|
nuclear@7
|
22 #include "http.h"
|
nuclear@7
|
23 #include "mime.h"
|
nuclear@7
|
24 #include "logger.h"
|
nuclear@7
|
25
|
nuclear@7
|
26 /* HTTP version */
|
nuclear@7
|
27 #define HTTP_VER_MAJOR 1
|
nuclear@7
|
28 #define HTTP_VER_MINOR 1
|
nuclear@7
|
29 #define HTTP_VER_STR "1.1"
|
nuclear@7
|
30
|
nuclear@7
|
31 /* maximum request length: 64mb */
|
nuclear@7
|
32 #define MAX_REQ_LENGTH (65536 * 1024)
|
nuclear@7
|
33
|
nuclear@7
|
34 struct client {
|
nuclear@7
|
35 int s;
|
nuclear@7
|
36 char *rcvbuf;
|
nuclear@7
|
37 int bufsz;
|
nuclear@7
|
38 struct client *next;
|
nuclear@7
|
39 };
|
nuclear@7
|
40
|
nuclear@7
|
41 static int accept_conn(int lis);
|
nuclear@7
|
42 static void close_conn(struct client *c);
|
nuclear@7
|
43 static int handle_client(struct client *c);
|
nuclear@7
|
44 static int do_get(struct client *c, const char *uri, int with_body);
|
nuclear@7
|
45 static void respond_error(struct client *c, int errcode);
|
nuclear@7
|
46
|
nuclear@7
|
47 static int lis = -1;
|
nuclear@7
|
48 static int maxfd;
|
nuclear@7
|
49 static int port = 8080;
|
nuclear@7
|
50 static struct client *clist;
|
nuclear@7
|
51 static int num_clients;
|
nuclear@7
|
52
|
nuclear@7
|
53 static const char *indexfiles[] = {
|
nuclear@7
|
54 "index.cgi",
|
nuclear@7
|
55 "index.html",
|
nuclear@7
|
56 "index.htm",
|
nuclear@7
|
57 0
|
nuclear@7
|
58 };
|
nuclear@7
|
59
|
nuclear@7
|
60 void tw_set_port(int p)
|
nuclear@7
|
61 {
|
nuclear@7
|
62 port = p;
|
nuclear@7
|
63 }
|
nuclear@7
|
64
|
nuclear@7
|
65 int tw_set_root(const char *path)
|
nuclear@7
|
66 {
|
nuclear@7
|
67 return chdir(path);
|
nuclear@7
|
68 }
|
nuclear@7
|
69
|
nuclear@7
|
70 int tw_set_logfile(const char *fname)
|
nuclear@7
|
71 {
|
nuclear@7
|
72 return set_log_file(fname);
|
nuclear@7
|
73 }
|
nuclear@7
|
74
|
nuclear@7
|
75 int tw_start(void)
|
nuclear@7
|
76 {
|
nuclear@7
|
77 int s;
|
nuclear@7
|
78 struct sockaddr_in sa;
|
nuclear@7
|
79
|
nuclear@7
|
80 logmsg("starting server ...\n");
|
nuclear@7
|
81
|
nuclear@7
|
82 if(lis != -1) {
|
nuclear@7
|
83 logmsg("can't start tinyweb server: already running!\n");
|
nuclear@7
|
84 return -1;
|
nuclear@7
|
85 }
|
nuclear@7
|
86
|
nuclear@7
|
87 if((s = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
|
nuclear@7
|
88 logmsg("failed to create listening socket: %s\n", strerror(errno));
|
nuclear@7
|
89 return -1;
|
nuclear@7
|
90 }
|
nuclear@7
|
91 fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK);
|
nuclear@7
|
92
|
nuclear@7
|
93 memset(&sa, 0, sizeof sa);
|
nuclear@7
|
94 sa.sin_family = AF_INET;
|
nuclear@7
|
95 sa.sin_addr.s_addr = INADDR_ANY;
|
nuclear@7
|
96 sa.sin_port = htons(port);
|
nuclear@7
|
97
|
nuclear@7
|
98 if(bind(s, (struct sockaddr*)&sa, sizeof sa) == -1) {
|
nuclear@7
|
99 logmsg("failed to bind socket to port %d: %s\n", port, strerror(errno));
|
nuclear@7
|
100 return -1;
|
nuclear@7
|
101 }
|
nuclear@7
|
102 listen(s, 16);
|
nuclear@7
|
103
|
nuclear@7
|
104 lis = s;
|
nuclear@7
|
105 return s;
|
nuclear@7
|
106 }
|
nuclear@7
|
107
|
nuclear@7
|
108 int tw_stop(void)
|
nuclear@7
|
109 {
|
nuclear@7
|
110 if(lis == -1) {
|
nuclear@7
|
111 return -1;
|
nuclear@7
|
112 }
|
nuclear@7
|
113
|
nuclear@7
|
114 logmsg("stopping server...\n");
|
nuclear@7
|
115
|
nuclear@7
|
116 close(lis);
|
nuclear@7
|
117 lis = -1;
|
nuclear@7
|
118
|
nuclear@7
|
119 while(clist) {
|
nuclear@7
|
120 struct client *c = clist;
|
nuclear@7
|
121 clist = clist->next;
|
nuclear@7
|
122 close_conn(c);
|
nuclear@7
|
123 free(c);
|
nuclear@7
|
124 }
|
nuclear@7
|
125 clist = 0;
|
nuclear@7
|
126
|
nuclear@7
|
127 return 0;
|
nuclear@7
|
128 }
|
nuclear@7
|
129
|
nuclear@7
|
130 int tw_get_sockets(int *socks)
|
nuclear@7
|
131 {
|
nuclear@7
|
132 struct client *c, dummy;
|
nuclear@7
|
133
|
nuclear@7
|
134 /* first cleanup the clients marked for removal */
|
nuclear@7
|
135 dummy.next = clist;
|
nuclear@7
|
136 c = &dummy;
|
nuclear@7
|
137
|
nuclear@7
|
138 while(c->next) {
|
nuclear@7
|
139 struct client *n = c->next;
|
nuclear@7
|
140
|
nuclear@7
|
141 if(n->s == -1) {
|
nuclear@7
|
142 /* marked for removal */
|
nuclear@7
|
143 c->next = n->next;
|
nuclear@7
|
144 free(n);
|
nuclear@7
|
145 --num_clients;
|
nuclear@7
|
146 } else {
|
nuclear@7
|
147 c = c->next;
|
nuclear@7
|
148 }
|
nuclear@7
|
149 }
|
nuclear@7
|
150 clist = dummy.next;
|
nuclear@7
|
151
|
nuclear@8
|
152
|
nuclear@8
|
153 if(!socks) {
|
nuclear@8
|
154 /* just return the count */
|
nuclear@8
|
155 return num_clients + 1; /* +1 for the listening socket */
|
nuclear@8
|
156 }
|
nuclear@8
|
157
|
nuclear@7
|
158 /* go through the client list and populate the array */
|
nuclear@7
|
159 maxfd = lis;
|
nuclear@7
|
160 *socks++ = lis;
|
nuclear@7
|
161
|
nuclear@7
|
162 c = clist;
|
nuclear@7
|
163 while(c) {
|
nuclear@7
|
164 *socks++ = c->s;
|
nuclear@7
|
165 if(c->s > maxfd) {
|
nuclear@7
|
166 maxfd = c->s;
|
nuclear@7
|
167 }
|
nuclear@7
|
168 c = c->next;
|
nuclear@7
|
169 }
|
nuclear@7
|
170 return num_clients + 1; /* +1 for the listening socket */
|
nuclear@7
|
171 }
|
nuclear@7
|
172
|
nuclear@7
|
173 int tw_get_maxfd(void)
|
nuclear@7
|
174 {
|
nuclear@7
|
175 return maxfd;
|
nuclear@7
|
176 }
|
nuclear@7
|
177
|
nuclear@7
|
178 int tw_handle_socket(int s)
|
nuclear@7
|
179 {
|
nuclear@7
|
180 struct client *c;
|
nuclear@7
|
181
|
nuclear@7
|
182 if(s == lis) {
|
nuclear@7
|
183 return accept_conn(s);
|
nuclear@7
|
184 }
|
nuclear@7
|
185
|
nuclear@7
|
186 /* find which client corresponds to this socket */
|
nuclear@7
|
187 c = clist;
|
nuclear@7
|
188 while(c) {
|
nuclear@7
|
189 if(c->s == s) {
|
nuclear@7
|
190 return handle_client(c);
|
nuclear@7
|
191 }
|
nuclear@7
|
192 c = c->next;
|
nuclear@7
|
193 }
|
nuclear@7
|
194
|
nuclear@7
|
195 logmsg("socket %d doesn't correspond to any client\n");
|
nuclear@7
|
196 return -1;
|
nuclear@7
|
197 }
|
nuclear@7
|
198
|
nuclear@7
|
199 static int accept_conn(int lis)
|
nuclear@7
|
200 {
|
nuclear@7
|
201 int s;
|
nuclear@7
|
202 struct client *c;
|
nuclear@7
|
203 struct sockaddr_in addr;
|
nuclear@7
|
204 socklen_t addr_sz = sizeof addr;
|
nuclear@7
|
205
|
nuclear@7
|
206 if((s = accept(lis, (struct sockaddr*)&addr, &addr_sz)) == -1) {
|
nuclear@7
|
207 logmsg("failed to accept incoming connection: %s\n", strerror(errno));
|
nuclear@7
|
208 return -1;
|
nuclear@7
|
209 }
|
nuclear@7
|
210 fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK);
|
nuclear@7
|
211
|
nuclear@7
|
212 if(!(c = malloc(sizeof *c))) {
|
nuclear@7
|
213 logmsg("failed to allocate memory while accepting connection: %s\n", strerror(errno));
|
nuclear@7
|
214 return -1;
|
nuclear@7
|
215 }
|
nuclear@7
|
216 c->s = s;
|
nuclear@7
|
217 c->rcvbuf = 0;
|
nuclear@7
|
218 c->bufsz = 0;
|
nuclear@7
|
219 c->next = clist;
|
nuclear@7
|
220 clist = c;
|
nuclear@8
|
221 ++num_clients;
|
nuclear@7
|
222 return 0;
|
nuclear@7
|
223 }
|
nuclear@7
|
224
|
nuclear@7
|
225 static void close_conn(struct client *c)
|
nuclear@7
|
226 {
|
nuclear@7
|
227 close(c->s);
|
nuclear@7
|
228 c->s = -1; /* mark it for removal */
|
nuclear@7
|
229 free(c->rcvbuf);
|
nuclear@7
|
230 c->rcvbuf = 0;
|
nuclear@7
|
231 }
|
nuclear@7
|
232
|
nuclear@7
|
233 static int handle_client(struct client *c)
|
nuclear@7
|
234 {
|
nuclear@7
|
235 struct http_req_header hdr;
|
nuclear@7
|
236 static char buf[2048];
|
nuclear@7
|
237 int rdsz, status;
|
nuclear@7
|
238
|
nuclear@7
|
239 while((rdsz = recv(c->s, buf, sizeof buf, 0)) > 0) {
|
nuclear@7
|
240 char *newbuf;
|
nuclear@7
|
241 int newsz = c->bufsz + rdsz;
|
nuclear@7
|
242 if(newsz > MAX_REQ_LENGTH) {
|
nuclear@7
|
243 respond_error(c, 413);
|
nuclear@7
|
244 return -1;
|
nuclear@7
|
245 }
|
nuclear@7
|
246
|
nuclear@7
|
247 if(!(newbuf = realloc(c->rcvbuf, newsz + 1))) {
|
nuclear@7
|
248 logmsg("failed to allocate %d byte buffer\n", newsz);
|
nuclear@7
|
249 respond_error(c, 503);
|
nuclear@7
|
250 return -1;
|
nuclear@7
|
251 }
|
nuclear@7
|
252
|
nuclear@7
|
253 memcpy(newbuf + c->bufsz, buf, rdsz);
|
nuclear@7
|
254 newbuf[newsz] = 0;
|
nuclear@7
|
255
|
nuclear@7
|
256 c->rcvbuf = newbuf;
|
nuclear@7
|
257 c->bufsz = newsz;
|
nuclear@7
|
258 }
|
nuclear@7
|
259
|
nuclear@7
|
260 if((status = http_parse_request(&hdr, c->rcvbuf, c->bufsz)) != HTTP_HDR_OK) {
|
nuclear@7
|
261 http_log_request(&hdr);
|
nuclear@7
|
262 switch(status) {
|
nuclear@7
|
263 case HTTP_HDR_INVALID:
|
nuclear@7
|
264 respond_error(c, 400);
|
nuclear@7
|
265 return -1;
|
nuclear@7
|
266
|
nuclear@7
|
267 case HTTP_HDR_NOMEM:
|
nuclear@7
|
268 respond_error(c, 503);
|
nuclear@7
|
269 return -1;
|
nuclear@7
|
270
|
nuclear@7
|
271 case HTTP_HDR_PARTIAL:
|
nuclear@7
|
272 return 0; /* partial header, continue reading */
|
nuclear@7
|
273 }
|
nuclear@7
|
274 }
|
nuclear@7
|
275 http_log_request(&hdr);
|
nuclear@7
|
276
|
nuclear@7
|
277 /* we only support GET and HEAD at this point, so freak out on anything else */
|
nuclear@7
|
278 switch(hdr.method) {
|
nuclear@7
|
279 case HTTP_GET:
|
nuclear@7
|
280 if(do_get(c, hdr.uri, 1) == -1) {
|
nuclear@7
|
281 return -1;
|
nuclear@7
|
282 }
|
nuclear@7
|
283 break;
|
nuclear@7
|
284
|
nuclear@7
|
285 case HTTP_HEAD:
|
nuclear@7
|
286 if(do_get(c, hdr.uri, 0) == -1) {
|
nuclear@7
|
287 return -1;
|
nuclear@7
|
288 }
|
nuclear@7
|
289 break;
|
nuclear@7
|
290
|
nuclear@7
|
291 default:
|
nuclear@7
|
292 respond_error(c, 501);
|
nuclear@7
|
293 return -1;
|
nuclear@7
|
294 }
|
nuclear@7
|
295
|
nuclear@7
|
296 close_conn(c);
|
nuclear@7
|
297 return 0;
|
nuclear@7
|
298 }
|
nuclear@7
|
299
|
nuclear@7
|
300 static int do_get(struct client *c, const char *uri, int with_body)
|
nuclear@7
|
301 {
|
nuclear@7
|
302 const char *ptr;
|
nuclear@7
|
303 struct http_resp_header resp;
|
nuclear@7
|
304
|
nuclear@7
|
305 if((ptr = strstr(uri, "://"))) {
|
nuclear@7
|
306 uri = ptr + 3;
|
nuclear@7
|
307 }
|
nuclear@7
|
308
|
nuclear@7
|
309 /* skip the host part and the first slash if it exists */
|
nuclear@7
|
310 if((ptr = strchr(uri, '/'))) {
|
nuclear@7
|
311 uri = ptr + 1;
|
nuclear@7
|
312 }
|
nuclear@7
|
313
|
nuclear@7
|
314 if(*uri) {
|
nuclear@7
|
315 struct stat st;
|
nuclear@7
|
316 char *path = 0;
|
nuclear@7
|
317 char *rsphdr;
|
nuclear@7
|
318 const char *type;
|
nuclear@7
|
319 int fd, rspsize;
|
nuclear@7
|
320
|
nuclear@7
|
321 if(stat(uri, &st) == -1) {
|
nuclear@7
|
322 respond_error(c, 404);
|
nuclear@7
|
323 return -1;
|
nuclear@7
|
324 }
|
nuclear@7
|
325
|
nuclear@7
|
326 if(S_ISDIR(st.st_mode)) {
|
nuclear@7
|
327 int i;
|
nuclear@7
|
328 path = alloca(strlen(uri) + 64);
|
nuclear@7
|
329
|
nuclear@7
|
330 for(i=0; indexfiles[i]; i++) {
|
nuclear@7
|
331 sprintf(path, "%s/%s", uri, indexfiles[i]);
|
nuclear@7
|
332 if(stat(path, &st) == 0 && !S_ISDIR(st.st_mode)) {
|
nuclear@7
|
333 break;
|
nuclear@7
|
334 }
|
nuclear@7
|
335 }
|
nuclear@7
|
336
|
nuclear@7
|
337 if(indexfiles[i] == 0) {
|
nuclear@7
|
338 respond_error(c, 404);
|
nuclear@7
|
339 return -1;
|
nuclear@7
|
340 }
|
nuclear@7
|
341 } else {
|
nuclear@7
|
342 path = (char*)uri;
|
nuclear@7
|
343 }
|
nuclear@7
|
344
|
nuclear@7
|
345 if((fd = open(path, O_RDONLY)) == -1) {
|
nuclear@7
|
346 respond_error(c, 403);
|
nuclear@7
|
347 return -1;
|
nuclear@7
|
348 }
|
nuclear@7
|
349
|
nuclear@7
|
350 /* construct response header */
|
nuclear@7
|
351 http_init_resp(&resp);
|
nuclear@7
|
352 http_add_resp_field(&resp, "Content-Length: %d", st.st_size);
|
nuclear@7
|
353 if((type = mime_type(path))) {
|
nuclear@7
|
354 http_add_resp_field(&resp, "Content-Type: %s", type);
|
nuclear@7
|
355 }
|
nuclear@7
|
356
|
nuclear@7
|
357 rspsize = http_serialize_resp(&resp, 0);
|
nuclear@7
|
358 rsphdr = alloca(rspsize);
|
nuclear@7
|
359 http_serialize_resp(&resp, rsphdr);
|
nuclear@7
|
360
|
nuclear@7
|
361 if(with_body) {
|
nuclear@7
|
362 int cont_left = st.st_size;
|
nuclear@7
|
363 char *cont = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
nuclear@7
|
364 if(cont == (void*)-1) {
|
nuclear@7
|
365 respond_error(c, 503);
|
nuclear@7
|
366 close(fd);
|
nuclear@7
|
367 return -1;
|
nuclear@7
|
368 }
|
nuclear@7
|
369 ptr = cont;
|
nuclear@7
|
370
|
nuclear@7
|
371 send(c->s, rsphdr, rspsize, 0);
|
nuclear@7
|
372 while(cont_left > 0) {
|
nuclear@7
|
373 int sz = cont_left < 4096 ? cont_left : 4096;
|
nuclear@7
|
374 send(c->s, ptr, sz, 0);
|
nuclear@7
|
375 ptr += sz;
|
nuclear@8
|
376 cont_left -= sz;
|
nuclear@7
|
377 }
|
nuclear@7
|
378
|
nuclear@7
|
379 munmap(cont, st.st_size);
|
nuclear@7
|
380 } else {
|
nuclear@7
|
381 send(c->s, rsphdr, rspsize, 0);
|
nuclear@7
|
382 }
|
nuclear@7
|
383
|
nuclear@7
|
384 close(fd);
|
nuclear@7
|
385 }
|
nuclear@7
|
386 return 0;
|
nuclear@7
|
387 }
|
nuclear@7
|
388
|
nuclear@7
|
389 static void respond_error(struct client *c, int errcode)
|
nuclear@7
|
390 {
|
nuclear@7
|
391 char buf[512];
|
nuclear@7
|
392
|
nuclear@7
|
393 sprintf(buf, "HTTP/" HTTP_VER_STR " %d %s\r\n\r\n", errcode, http_strmsg(errcode));
|
nuclear@7
|
394
|
nuclear@7
|
395 send(c->s, buf, strlen(buf), 0);
|
nuclear@7
|
396 close_conn(c);
|
nuclear@7
|
397 }
|
nuclear@7
|
398
|