fix 404 and 403 error handling, improve error message output
[project/uhttpd.git] / client.c
1 /*
2  * uhttpd - Tiny single-threaded httpd
3  *
4  *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
5  *   Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
6  *
7  *  Licensed under the Apache License, Version 2.0 (the "License");
8  *  you may not use this file except in compliance with the License.
9  *  You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  *  Unless required by applicable law or agreed to in writing, software
14  *  distributed under the License is distributed on an "AS IS" BASIS,
15  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  *  See the License for the specific language governing permissions and
17  *  limitations under the License.
18  */
19
20 #include <libubox/blobmsg.h>
21 #include <ctype.h>
22
23 #include "uhttpd.h"
24
25 static LIST_HEAD(clients);
26
27 int n_clients = 0;
28 struct config conf = {};
29
30 static const char *http_versions[] = {
31         [UH_HTTP_VER_0_9] = "HTTP/0.9",
32         [UH_HTTP_VER_1_0] = "HTTP/1.0",
33         [UH_HTTP_VER_1_1] = "HTTP/1.1",
34 };
35
36 void uh_http_header(struct client *cl, int code, const char *summary)
37 {
38         const char *enc = "Transfer-Encoding: chunked\r\n";
39         const char *conn;
40
41         if (!uh_use_chunked(cl))
42                 enc = "";
43
44         if (cl->request.version != UH_HTTP_VER_1_1)
45                 conn = "Connection: close";
46         else
47                 conn = "Connection: keep-alive";
48
49         ustream_printf(cl->us, "%s %03i %s\r\n%s\r\n%s",
50                 http_versions[cl->request.version],
51                 code, summary, conn, enc);
52 }
53
54 static void uh_connection_close(struct client *cl)
55 {
56         cl->state = CLIENT_STATE_DONE;
57         cl->us->eof = true;
58         ustream_state_change(cl->us);
59 }
60
61 static void uh_dispatch_done(struct client *cl)
62 {
63         if (cl->dispatch_free)
64                 cl->dispatch_free(cl);
65         cl->dispatch_free = NULL;
66 }
67
68 void uh_request_done(struct client *cl)
69 {
70         uh_chunk_eof(cl);
71         uh_dispatch_done(cl);
72         cl->us->notify_write = NULL;
73         memset(&cl->data, 0, sizeof(cl->data));
74
75         if (cl->request.version != UH_HTTP_VER_1_1 || !conf.http_keepalive) {
76                 uh_connection_close(cl);
77                 return;
78         }
79
80         cl->state = CLIENT_STATE_INIT;
81         uloop_timeout_set(&cl->timeout, conf.http_keepalive * 1000);
82 }
83
84 void __printf(4, 5)
85 uh_client_error(struct client *cl, int code, const char *summary, const char *fmt, ...)
86 {
87         va_list arg;
88
89         uh_http_header(cl, code, summary);
90         ustream_printf(cl->us, "Content-Type: text/html\r\n\r\n");
91
92         uh_chunk_printf(cl, "<h1>%s</h1>", summary);
93
94         if (fmt) {
95                 va_start(arg, fmt);
96                 uh_chunk_vprintf(cl, fmt, arg);
97                 va_end(arg);
98         }
99
100         uh_request_done(cl);
101 }
102
103 static void uh_header_error(struct client *cl, int code, const char *summary)
104 {
105         uh_client_error(cl, code, summary, NULL);
106         uh_connection_close(cl);
107 }
108
109 static void client_timeout(struct uloop_timeout *timeout)
110 {
111         struct client *cl = container_of(timeout, struct client, timeout);
112
113         cl->state = CLIENT_STATE_CLOSE;
114         uh_connection_close(cl);
115 }
116
117 static int client_parse_request(struct client *cl, char *data)
118 {
119         struct http_request *req = &cl->request;
120         char *type, *path, *version;
121         int i;
122
123         type = strtok(data, " ");
124         path = strtok(NULL, " ");
125         version = strtok(NULL, " ");
126         if (!type || !path || !version)
127                 return CLIENT_STATE_DONE;
128
129         req->url = path;
130         if (!strcmp(type, "GET"))
131                 req->method = UH_HTTP_MSG_GET;
132         else if (!strcmp(type, "POST"))
133                 req->method = UH_HTTP_MSG_POST;
134         else if (!strcmp(type, "HEAD"))
135                 req->method = UH_HTTP_MSG_HEAD;
136         else
137                 return CLIENT_STATE_DONE;
138
139         cl->request.version = -1;
140         i = array_size(http_versions);
141         while (i--) {
142                 if (!strcmp(version, http_versions[i])) {
143                         cl->request.version = i;
144                         break;
145                 }
146         }
147         if (cl->request.version < 0)
148                 return CLIENT_STATE_DONE;
149
150         return CLIENT_STATE_HEADER;
151 }
152
153 static bool client_init_cb(struct client *cl, char *buf, int len)
154 {
155         char *newline;
156
157         newline = strstr(buf, "\r\n");
158         if (!newline)
159                 return false;
160
161         *newline = 0;
162         blob_buf_init(&cl->hdr, 0);
163         blobmsg_add_string(&cl->hdr, "REQUEST", buf);
164         ustream_consume(cl->us, newline + 2 - buf);
165         cl->state = client_parse_request(cl, (char *) blobmsg_data(blob_data(cl->hdr.head)));
166         if (cl->state == CLIENT_STATE_DONE)
167                 uh_header_error(cl, 400, "Bad Request");
168
169         return true;
170 }
171
172 static void client_header_complete(struct client *cl)
173 {
174         uh_handle_file_request(cl);
175 }
176
177 static int client_parse_header(struct client *cl, char *data)
178 {
179         char *name;
180         char *val;
181
182         if (!*data) {
183                 uloop_timeout_cancel(&cl->timeout);
184                 client_header_complete(cl);
185                 return CLIENT_STATE_DATA;
186         }
187
188         val = strchr(data, ':');
189         if (!val)
190                 return CLIENT_STATE_DONE;
191
192         *val = 0;
193         val++;
194
195         while (isspace(*val))
196                 val++;
197
198         for (name = data; *name; name++)
199                 if (isupper(*name))
200                         *name = tolower(*name);
201
202         blobmsg_add_string(&cl->hdr, data, val);
203
204         return CLIENT_STATE_HEADER;
205 }
206
207 static bool client_data_cb(struct client *cl, char *buf, int len)
208 {
209         return false;
210 }
211
212 static bool client_header_cb(struct client *cl, char *buf, int len)
213 {
214         char *newline;
215         int line_len;
216
217         newline = strstr(buf, "\r\n");
218         if (!newline)
219                 return false;
220
221         *newline = 0;
222         cl->state = client_parse_header(cl, buf);
223         line_len = newline + 2 - buf;
224         ustream_consume(cl->us, line_len);
225         if (cl->state == CLIENT_STATE_DATA)
226                 client_data_cb(cl, newline + 2, len - line_len);
227
228         return true;
229 }
230
231 typedef bool (*read_cb_t)(struct client *cl, char *buf, int len);
232 static read_cb_t read_cbs[] = {
233         [CLIENT_STATE_INIT] = client_init_cb,
234         [CLIENT_STATE_HEADER] = client_header_cb,
235         [CLIENT_STATE_DATA] = client_data_cb,
236 };
237
238 static void client_read_cb(struct client *cl)
239 {
240         struct ustream *us = cl->us;
241         char *str;
242         int len;
243
244         do {
245                 str = ustream_get_read_buf(us, &len);
246                 if (!str)
247                         break;
248
249                 if (cl->state >= array_size(read_cbs) || !read_cbs[cl->state])
250                         break;
251
252                 if (!read_cbs[cl->state](cl, str, len)) {
253                         if (len == us->r.buffer_len)
254                                 uh_header_error(cl, 413, "Request Entity Too Large");
255                         break;
256                 }
257         } while(1);
258 }
259
260 static void client_close(struct client *cl)
261 {
262         uh_dispatch_done(cl);
263         uloop_timeout_cancel(&cl->timeout);
264         ustream_free(&cl->sfd.stream);
265         close(cl->sfd.fd.fd);
266         list_del(&cl->list);
267         free(cl);
268
269         uh_unblock_listeners();
270 }
271
272 static void client_ustream_read_cb(struct ustream *s, int bytes)
273 {
274         struct client *cl = container_of(s, struct client, sfd);
275
276         client_read_cb(cl);
277 }
278
279 static void client_ustream_write_cb(struct ustream *s, int bytes)
280 {
281         struct client *cl = container_of(s, struct client, sfd);
282
283         if (cl->dispatch_write_cb)
284                 cl->dispatch_write_cb(cl);
285 }
286
287 static void client_notify_state(struct ustream *s)
288 {
289         struct client *cl = container_of(s, struct client, sfd);
290
291         if (cl->state == CLIENT_STATE_CLOSE ||
292                 (s->eof && !s->w.data_bytes) || s->write_error)
293                 return client_close(cl);
294 }
295
296 void uh_accept_client(int fd)
297 {
298         static struct client *next_client;
299         struct client *cl;
300         unsigned int sl;
301         int sfd;
302         static int client_id = 0;
303
304         if (!next_client)
305                 next_client = calloc(1, sizeof(*next_client));
306
307         cl = next_client;
308
309         sl = sizeof(cl->peeraddr);
310         sfd = accept(fd, (struct sockaddr *) &cl->peeraddr, &sl);
311         if (sfd < 0)
312                 return;
313
314         sl = sizeof(cl->servaddr);
315         getsockname(fd, (struct sockaddr *) &cl->servaddr, &sl);
316         cl->us = &cl->sfd.stream;
317         cl->us->string_data = true;
318         cl->us->notify_read = client_ustream_read_cb;
319         cl->us->notify_write = client_ustream_write_cb;
320         cl->us->notify_state = client_notify_state;
321         ustream_fd_init(&cl->sfd, sfd);
322
323         cl->timeout.cb = client_timeout;
324         uloop_timeout_set(&cl->timeout, conf.network_timeout * 1000);
325
326         list_add_tail(&cl->list, &clients);
327
328         next_client = NULL;
329         n_clients++;
330         cl->id = client_id++;
331 }