source: mainline/uspace/app/websrv/websrv.c@ f2f85283

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since f2f85283 was 6ff23ff, checked in by Jiri Svoboda <jiri@…>, 7 years ago

More comment fixing (ccheck).

  • Property mode set to 100644
File size: 9.7 KB
RevLine 
[f4a2d624]1/*
[26b6789]2 * Copyright (c) 2012 Jiri Svoboda
[f4a2d624]3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup websrv
30 * @{
31 */
32/**
[4a4cc150]33 * @file Skeletal web server.
[f4a2d624]34 */
35
[4a4cc150]36#include <errno.h>
[d4d74dc]37#include <assert.h>
[8d2dd7f2]38#include <stdbool.h>
[f4a2d624]39#include <stdio.h>
[8d2dd7f2]40#include <stdint.h>
41#include <stddef.h>
[9c3bba0]42#include <stdlib.h>
[26b6789]43#include <task.h>
[f4a2d624]44
[b19e892]45#include <vfs/vfs.h>
46
[fab2746]47#include <inet/addr.h>
48#include <inet/endpoint.h>
49#include <inet/tcp.h>
[f4a2d624]50
[0a549cc]51#include <arg_parse.h>
52#include <macros.h>
[f4a2d624]53#include <str.h>
[0a549cc]54#include <str_error.h>
[f4a2d624]55
[0a549cc]56#define NAME "websrv"
[f4a2d624]57
[0a549cc]58#define DEFAULT_PORT 8080
59
60#define WEB_ROOT "/data/web"
[4a4cc150]61
[f4a2d624]62/** Buffer for receiving the request. */
[0a549cc]63#define BUFFER_SIZE 1024
64
[fab2746]65static void websrv_new_conn(tcp_listener_t *, tcp_conn_t *);
66
67static tcp_listen_cb_t listen_cb = {
68 .new_conn = websrv_new_conn
69};
70
71static tcp_cb_t conn_cb = {
72 .connected = NULL
73};
74
[0a549cc]75static uint16_t port = DEFAULT_PORT;
76
[d076f16]77typedef struct {
78 tcp_conn_t *conn;
[4a4cc150]79
[d076f16]80 char rbuf[BUFFER_SIZE];
81 size_t rbuf_out;
82 size_t rbuf_in;
[4a4cc150]83
[d076f16]84 char lbuf[BUFFER_SIZE + 1];
85 size_t lbuf_used;
86} recv_t;
[f4a2d624]87
[26b6789]88static bool verbose = false;
89
[0a549cc]90/** Responses to send to client. */
91
92static const char *msg_ok =
[f4a2d624]93 "HTTP/1.0 200 OK\r\n"
[4a4cc150]94 "\r\n";
95
[0a549cc]96static const char *msg_bad_request =
97 "HTTP/1.0 400 Bad Request\r\n"
98 "\r\n"
99 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
100 "<html><head>\r\n"
101 "<title>400 Bad Request</title>\r\n"
102 "</head>\r\n"
103 "<body>\r\n"
104 "<h1>Bad Request</h1>\r\n"
105 "<p>The requested URL has bad syntax.</p>\r\n"
106 "</body>\r\n"
107 "</html>\r\n";
108
109static const char *msg_not_found =
110 "HTTP/1.0 404 Not Found\r\n"
111 "\r\n"
112 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
113 "<html><head>\r\n"
114 "<title>404 Not Found</title>\r\n"
115 "</head>\r\n"
116 "<body>\r\n"
117 "<h1>Not Found</h1>\r\n"
118 "<p>The requested URL was not found on this server.</p>\r\n"
119 "</body>\r\n"
120 "</html>\r\n";
121
122static const char *msg_not_implemented =
123 "HTTP/1.0 501 Not Implemented\r\n"
124 "\r\n"
125 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
126 "<html><head>\r\n"
127 "<title>501 Not Implemented</title>\r\n"
128 "</head>\r\n"
129 "<body>\r\n"
130 "<h1>Not Implemented</h1>\r\n"
131 "<p>The requested method is not implemented on this server.</p>\r\n"
132 "</body>\r\n"
133 "</html>\r\n";
134
[d076f16]135
[b7fd2a0]136static errno_t recv_create(tcp_conn_t *conn, recv_t **rrecv)
[d076f16]137{
138 recv_t *recv;
[a35b458]139
[d076f16]140 recv = calloc(1, sizeof(recv_t));
141 if (recv == NULL)
142 return ENOMEM;
[a35b458]143
[d076f16]144 recv->conn = conn;
145 recv->rbuf_out = 0;
146 recv->rbuf_in = 0;
147 recv->lbuf_used = 0;
[a35b458]148
[d076f16]149 *rrecv = recv;
150 return EOK;
151}
152
153static void recv_destroy(recv_t *recv)
154{
155 free(recv);
156}
157
[4a4cc150]158/** Receive one character (with buffering) */
[b7fd2a0]159static errno_t recv_char(recv_t *recv, char *c)
[4a4cc150]160{
[fab2746]161 size_t nrecv;
[b7fd2a0]162 errno_t rc;
[a35b458]163
[d076f16]164 if (recv->rbuf_out == recv->rbuf_in) {
165 recv->rbuf_out = 0;
166 recv->rbuf_in = 0;
[a35b458]167
[d076f16]168 rc = tcp_conn_recv_wait(recv->conn, recv->rbuf, BUFFER_SIZE, &nrecv);
[fab2746]169 if (rc != EOK) {
[c1694b6b]170 fprintf(stderr, "tcp_conn_recv() failed: %s\n", str_error(rc));
[4a4cc150]171 return rc;
172 }
[a35b458]173
[d076f16]174 recv->rbuf_in = nrecv;
[4a4cc150]175 }
[a35b458]176
[d076f16]177 *c = recv->rbuf[recv->rbuf_out++];
[4a4cc150]178 return EOK;
179}
180
[0a549cc]181/** Receive one line with length limit */
[b7fd2a0]182static errno_t recv_line(recv_t *recv, char **rbuf)
[4a4cc150]183{
[d076f16]184 char *bp = recv->lbuf;
[0a549cc]185 char c = '\0';
[a35b458]186
[d076f16]187 while (bp < recv->lbuf + BUFFER_SIZE) {
[0a549cc]188 char prev = c;
[b7fd2a0]189 errno_t rc = recv_char(recv, &c);
[a35b458]190
[4a4cc150]191 if (rc != EOK)
192 return rc;
[a35b458]193
[4a4cc150]194 *bp++ = c;
[0a549cc]195 if ((prev == '\r') && (c == '\n'))
[4a4cc150]196 break;
197 }
[a35b458]198
[d076f16]199 recv->lbuf_used = bp - recv->lbuf;
[4a4cc150]200 *bp = '\0';
[a35b458]201
[d076f16]202 if (bp == recv->lbuf + BUFFER_SIZE)
[4a4cc150]203 return ELIMIT;
[a35b458]204
[d076f16]205 *rbuf = recv->lbuf;
[4a4cc150]206 return EOK;
207}
208
209static bool uri_is_valid(char *uri)
210{
211 if (uri[0] != '/')
212 return false;
[a35b458]213
[4a4cc150]214 if (uri[1] == '.')
215 return false;
[a35b458]216
[0a549cc]217 char *cp = uri + 1;
[a35b458]218
[4a4cc150]219 while (*cp != '\0') {
[0a549cc]220 char c = *cp++;
[4a4cc150]221 if (c == '/')
222 return false;
223 }
[a35b458]224
[4a4cc150]225 return true;
226}
227
[b7fd2a0]228static errno_t send_response(tcp_conn_t *conn, const char *msg)
[4a4cc150]229{
[0a549cc]230 size_t response_size = str_size(msg);
[a35b458]231
[26b6789]232 if (verbose)
[3bacee1]233 fprintf(stderr, "Sending response\n");
[a35b458]234
[b7fd2a0]235 errno_t rc = tcp_conn_send(conn, (void *) msg, response_size);
[fab2746]236 if (rc != EOK) {
237 fprintf(stderr, "tcp_conn_send() failed\n");
[4a4cc150]238 return rc;
239 }
[a35b458]240
[4a4cc150]241 return EOK;
242}
243
[b7fd2a0]244static errno_t uri_get(const char *uri, tcp_conn_t *conn)
[4a4cc150]245{
[d076f16]246 char *fbuf = NULL;
247 char *fname = NULL;
[b7fd2a0]248 errno_t rc;
[8e3498b]249 size_t nr;
[d076f16]250 int fd = -1;
[a35b458]251
[d076f16]252 fbuf = calloc(BUFFER_SIZE, 1);
253 if (fbuf == NULL) {
254 rc = ENOMEM;
255 goto out;
256 }
[a35b458]257
[4a4cc150]258 if (str_cmp(uri, "/") == 0)
[6bb169b5]259 uri = "/index.html";
[a35b458]260
[d5c1051]261 if (asprintf(&fname, "%s%s", WEB_ROOT, uri) < 0) {
[d076f16]262 rc = ENOMEM;
263 goto out;
264 }
[a35b458]265
[f77c1c9]266 rc = vfs_lookup_open(fname, WALK_REGULAR, MODE_READ, &fd);
267 if (rc != EOK) {
[fab2746]268 rc = send_response(conn, msg_not_found);
[d076f16]269 goto out;
[4a4cc150]270 }
[a35b458]271
[9c3bba0]272 free(fname);
[d076f16]273 fname = NULL;
[a35b458]274
[fab2746]275 rc = send_response(conn, msg_ok);
[4a4cc150]276 if (rc != EOK)
[d076f16]277 goto out;
[a35b458]278
[58898d1d]279 aoff64_t pos = 0;
[4a4cc150]280 while (true) {
[8e3498b]281 rc = vfs_read(fd, &pos, fbuf, BUFFER_SIZE, &nr);
282 if (rc != EOK)
283 goto out;
[a35b458]284
[4a4cc150]285 if (nr == 0)
286 break;
[a35b458]287
[fab2746]288 rc = tcp_conn_send(conn, fbuf, nr);
289 if (rc != EOK) {
290 fprintf(stderr, "tcp_conn_send() failed\n");
[d076f16]291 goto out;
[4a4cc150]292 }
293 }
[a35b458]294
[d076f16]295 rc = EOK;
296out:
297 if (fd >= 0)
298 vfs_put(fd);
299 free(fname);
300 free(fbuf);
301 return rc;
[4a4cc150]302}
303
[b7fd2a0]304static errno_t req_process(tcp_conn_t *conn, recv_t *recv)
[4a4cc150]305{
[d076f16]306 char *reqline = NULL;
307
[b7fd2a0]308 errno_t rc = recv_line(recv, &reqline);
[4a4cc150]309 if (rc != EOK) {
[0a549cc]310 fprintf(stderr, "recv_line() failed\n");
[4a4cc150]311 return rc;
312 }
[a35b458]313
[26b6789]314 if (verbose)
[d076f16]315 fprintf(stderr, "Request: %s", reqline);
[a35b458]316
[d076f16]317 if (str_lcmp(reqline, "GET ", 4) != 0) {
[fab2746]318 rc = send_response(conn, msg_not_implemented);
[0a549cc]319 return rc;
[4a4cc150]320 }
[a35b458]321
[d076f16]322 char *uri = reqline + 4;
[0a549cc]323 char *end_uri = str_chr(uri, ' ');
[4a4cc150]324 if (end_uri == NULL) {
[d076f16]325 end_uri = reqline + str_size(reqline) - 2;
[4a4cc150]326 assert(*end_uri == '\r');
327 }
[a35b458]328
[4a4cc150]329 *end_uri = '\0';
[26b6789]330 if (verbose)
331 fprintf(stderr, "Requested URI: %s\n", uri);
[a35b458]332
[4a4cc150]333 if (!uri_is_valid(uri)) {
[fab2746]334 rc = send_response(conn, msg_bad_request);
[0a549cc]335 return rc;
[4a4cc150]336 }
[a35b458]337
[fab2746]338 return uri_get(uri, conn);
[4a4cc150]339}
[f4a2d624]340
[0a549cc]341static void usage(void)
[f4a2d624]342{
[d076f16]343 printf("Simple web server\n"
[0a549cc]344 "\n"
345 "Usage: " NAME " [options]\n"
346 "\n"
347 "Where options are:\n"
348 "-p port_number | --port=port_number\n"
349 "\tListening port (default " STRING(DEFAULT_PORT) ").\n"
350 "\n"
351 "-h | --help\n"
[26b6789]352 "\tShow this application help.\n"
353 "-v | --verbose\n"
354 "\tVerbose mode\n");
[0a549cc]355}
[f4a2d624]356
[b7fd2a0]357static errno_t parse_option(int argc, char *argv[], int *index)
[0a549cc]358{
359 int value;
[b7fd2a0]360 errno_t rc;
[a35b458]361
[0a549cc]362 switch (argv[*index][1]) {
363 case 'h':
364 usage();
365 exit(0);
366 break;
367 case 'p':
368 rc = arg_parse_int(argc, argv, index, &value, 0);
369 if (rc != EOK)
370 return rc;
[a35b458]371
[0a549cc]372 port = (uint16_t) value;
373 break;
[26b6789]374 case 'v':
375 verbose = true;
376 break;
[0a549cc]377 case '-':
[6ff23ff]378 /* Long options with double dash */
[0a549cc]379 if (str_lcmp(argv[*index] + 2, "help", 5) == 0) {
380 usage();
381 exit(0);
382 } else if (str_lcmp(argv[*index] + 2, "port=", 5) == 0) {
383 rc = arg_parse_int(argc, argv, index, &value, 7);
384 if (rc != EOK)
385 return rc;
[a35b458]386
[0a549cc]387 port = (uint16_t) value;
[3bacee1]388 } else if (str_cmp(argv[*index] + 2, "verbose") == 0) {
[26b6789]389 verbose = true;
[0a549cc]390 } else {
391 usage();
392 return EINVAL;
393 }
394 break;
395 default:
396 usage();
397 return EINVAL;
398 }
[a35b458]399
[0a549cc]400 return EOK;
401}
[f4a2d624]402
[fab2746]403static void websrv_new_conn(tcp_listener_t *lst, tcp_conn_t *conn)
404{
[b7fd2a0]405 errno_t rc;
[d076f16]406 recv_t *recv = NULL;
[a35b458]407
[fab2746]408 if (verbose)
409 fprintf(stderr, "New connection, waiting for request\n");
[a35b458]410
[d076f16]411 rc = recv_create(conn, &recv);
412 if (rc != EOK) {
413 fprintf(stderr, "Out of memory.\n");
414 goto error;
415 }
[a35b458]416
[d076f16]417 rc = req_process(conn, recv);
[fab2746]418 if (rc != EOK) {
419 fprintf(stderr, "Error processing request (%s)\n",
420 str_error(rc));
[d076f16]421 goto error;
[b99f6e2]422 }
[a35b458]423
[b99f6e2]424 rc = tcp_conn_send_fin(conn);
425 if (rc != EOK) {
426 fprintf(stderr, "Error sending FIN.\n");
[d076f16]427 goto error;
[fab2746]428 }
[d076f16]429
430 recv_destroy(recv);
431 return;
432error:
433 rc = tcp_conn_reset(conn);
434 if (rc != EOK)
435 fprintf(stderr, "Error resetting connection.\n");
[a35b458]436
[d076f16]437 recv_destroy(recv);
[fab2746]438}
439
[0a549cc]440int main(int argc, char *argv[])
441{
[fab2746]442 inet_ep_t ep;
443 tcp_listener_t *lst;
444 tcp_t *tcp;
[b7fd2a0]445 errno_t rc;
[a35b458]446
[0a549cc]447 /* Parse command line arguments */
448 for (int i = 1; i < argc; i++) {
449 if (argv[i][0] == '-') {
[fab2746]450 rc = parse_option(argc, argv, &i);
[0a549cc]451 if (rc != EOK)
452 return rc;
453 } else {
454 usage();
455 return EINVAL;
456 }
457 }
[a35b458]458
[26b6789]459 printf("%s: HelenOS web server\n", NAME);
460
461 if (verbose)
[fab2746]462 fprintf(stderr, "Creating listener\n");
[a35b458]463
[fab2746]464 inet_ep_init(&ep);
465 ep.port = port;
466
467 rc = tcp_create(&tcp);
[f4a2d624]468 if (rc != EOK) {
[fab2746]469 fprintf(stderr, "Error initializing TCP.\n");
470 return 1;
[f4a2d624]471 }
[fab2746]472
473 rc = tcp_listener_create(tcp, &ep, &listen_cb, NULL, &conn_cb, NULL,
474 &lst);
[f4a2d624]475 if (rc != EOK) {
[fab2746]476 fprintf(stderr, "Error creating listener.\n");
477 return 2;
[f4a2d624]478 }
[a35b458]479
[26b6789]480 fprintf(stderr, "%s: Listening for connections at port %" PRIu16 "\n",
481 NAME, port);
482
483 task_retval(0);
[fab2746]484 async_manager();
[a35b458]485
[0a549cc]486 /* Not reached */
[f4a2d624]487 return 0;
488}
489
490/** @}
491 */
Note: See TracBrowser for help on using the repository browser.