swps

Static Web Page Server
git clone https://noxz.tech/git/swps.git
Log | Files | LICENSE

swps.c
1/* swps - Static Web Page Server
2 * -----------------------------
3 * This programs purpose is to serve a single static file as a web server.
4 * Besides serving a file in the root, swps also handles 301, 403 and 404.
5 *
6 *
7 * MIT License
8 *
9 * © 2019 Chris Noxz <chris@noxz.tech>
10 *
11 * Permission is hereby granted, free of charge, to any person obtaining a
12 * copy of this software and associated documentation files (the "Software"),
13 * to deal in the Software without restriction, including without limitation
14 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 * and/or sell copies of the Software, and to permit persons to whom the
16 * Software is furnished to do so, subject to the following conditions:
17 *
18 * The above copyright notice and this permission notice shall be included in
19 * all copies or substantial portions of the Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 * SOFTWARE.
28 */
29
30#include <stdarg.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <unistd.h>
34#include <string.h>
35#include <time.h>
36#include <fcntl.h>
37#include <signal.h>
38#include <sys/socket.h>
39#include <arpa/inet.h>
40
41#define BUFSIZE     8096
42#define DATESIZE    64
43#define SUCCESS     200
44#define REDIRECT    301
45#define FORBIDDEN   403
46#define NOTFOUND    404
47#define BROKEN_PIPE 1001
48
49/* functions */
50static void die(int, const char *, ...);
51static int handle(int, unsigned char *);
52static int respond(int, int, unsigned char *, const char *);
53static void serve(int);
54static void stopserver(int);
55static int toint(const char *);
56
57static int serverfd;
58static const char *server = "swps 0.1";
59static const char *usage ="usage: swps PORT FILE\n"
60    "Serve html FILE as a web page on specified PORT\n\n"
61    "Options\n"
62    "  -h   display this help and exit\n\n"
63    "Full documentation and source code <https://noxz.tech/software/swps>\n";
64static const char *path = NULL;
65static const char *response_200 =
66    "HTTP/1.1 200 OK\n"
67    "Date: %s\n"
68    "Server: %s\n"
69    "Content-Length: %ld\n"
70    "Content-Type: text/html\n"
71    "Connection: closed\n\n";
72static const char *response_301 =
73    "HTTP/1.1 301 Moved Permanently\n"
74    "Location: /\n";
75static const char *response_403 =
76    "HTTP/1.1 403 Forbidden\n"
77    "Date: %s\n"
78    "Server: %s\n"
79    "Content-Length: 153\n"
80    "Content-Type: text/html\n"
81    "Connection: closed\n\n"
82
83    "<html><head>\n"
84    "<title>403 Forbidden</title>\n"
85    "</head><body>\n"
86    "<h1>Forbidden</h1>\n"
87    "The requested URL, or operation is not allowed on this server.\n"
88    "</body></html>\n";
89static const char *response_404 =
90    "HTTP/1.1 404 Not Found\n"
91    "Date: %s\n"
92    "Server: %s\n"
93    "Content-Length: 140\n"
94    "Content-Type: text/html\n"
95    "Connection: closed\n\n"
96
97    "<html><head>\n"
98    "<title>404 Not Found</title>\n"
99    "</head><body>\n"
100    "<h1>Not Found</h1>\n"
101    "The requested URL / was not found on this server.\n"
102    "</body></html>\n";
103
104int
105toint(const char *s)
106{
107    const char *p = s;
108    int n = -1;
109
110    while (*p && (n >= 0 || (n = 0) == 0)) {
111        if (*p >= 48 && *p <= 57)
112            n = (n * 10) + ((*p) - 48);
113        else
114            return -1;
115        p++;
116    }
117
118    return n;
119}
120
121void
122die(int n, const char *msg, ...)
123{
124    va_list arguments;
125
126    va_start(arguments, msg);
127    if (msg)
128        vfprintf(stderr, msg, arguments);
129    va_end(arguments);
130
131    if (n >= 0)
132        exit(n);
133}
134
135int
136respond(int type, int fd, unsigned char *ip, const char *pt)
137{
138    static char buffer[BUFSIZE + 1];
139    static char date[DATESIZE + 1];
140    time_t t = time(NULL);
141    struct tm *tm = gmtime(&t);
142    int filefd;
143    long l;
144
145    strftime(date, DATESIZE, "%a, %d %b %Y %H:%M:%S GMT", tm);
146
147    switch (type) {
148    case SUCCESS:
149        if ((filefd = open(path, O_RDONLY)) == -1)
150            return respond(NOTFOUND, fd, ip, pt);
151        l = (long)lseek(filefd, (off_t)0, SEEK_END);
152        lseek(filefd, (off_t)0, SEEK_SET);
153        sprintf(buffer, response_200, date, server, l);
154        if (write(fd, buffer, strlen(buffer)) == -1)
155            return BROKEN_PIPE;
156        while ((l = read(filefd, buffer, BUFSIZE)) > 0)
157            write(fd, buffer, l);
158        close(filefd);
159        break;
160    case REDIRECT:
161        sprintf(buffer, response_301, date, server);
162        if (write(fd, buffer, strlen(buffer)) == -1)
163            return BROKEN_PIPE;
164        break;
165    case FORBIDDEN:
166        sprintf(buffer, response_403, date, server);
167        if (write(fd, buffer, strlen(buffer)) == -1)
168            return BROKEN_PIPE;
169        break;
170    case NOTFOUND:
171        sprintf(buffer, response_404, date, server);
172        if (write(fd, buffer, strlen(buffer)) == -1)
173            return BROKEN_PIPE;
174        break;
175    }
176
177    die(-1, "[%s] %d %d.%d.%d.%d %s\n",
178        date, type, ip[0], ip[1], ip[2], ip[3], pt);
179    return type;
180}
181
182int
183handle(int clientfd, unsigned char *ip)
184{
185    static char buffer[BUFSIZE + 1], *pt;
186    long r;
187
188    if ((r = read(clientfd, buffer, BUFSIZE)) <= 0)
189        return respond(FORBIDDEN, clientfd, ip, NULL);
190
191    /* verify operation to be GET */
192    if (strncmp(buffer,"GET ", 4))
193        return respond(FORBIDDEN, clientfd, ip, NULL);
194
195    /* get requested uri */
196    pt = buffer + 4;
197    while (*pt) {
198        if (*pt == ' ') {
199            *pt = 0;
200            break;
201        }
202        pt++;
203    }
204    pt = buffer + 4;
205
206    /* redirect everything but '/' */
207    if (strncmp(pt, "/", strlen(pt)))
208        return respond(REDIRECT, clientfd, ip, pt);
209
210    /* serve the path */
211    return respond(SUCCESS, clientfd, ip, pt);
212}
213
214void
215stopserver(int signum)
216{
217    if (signum == SIGINT || signum == SIGTERM) {
218        close(serverfd);
219        die(0, "\nShutting down...\n");
220    }
221}
222
223void
224serve(int serverfd)
225{
226    static struct sockaddr_in c_addr;
227    unsigned char *ip;
228    unsigned int l;
229    int clientfd;
230
231    /* define the only way out... */
232    if (signal(SIGINT, &stopserver) == SIG_ERR)
233        die(1, "Error: system call 'signal'\n");
234    if (signal(SIGTERM, &stopserver) == SIG_ERR)
235        die(1, "Error: system call 'signal'\n");
236    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
237        die(1, "Error: system call 'signal'\n");
238
239    /* ...and server forever */
240    for (;;) {
241        l = sizeof(c_addr);
242        if ((clientfd = accept(serverfd, (struct sockaddr *)&c_addr, &l)) < 0)
243            die(1, "Error: system call 'accept'\n");
244        ip = (unsigned char *)(&c_addr.sin_addr.s_addr);
245        switch (handle(clientfd, ip)) {
246            case BROKEN_PIPE:
247                die(-1, "Error: handling request 'broken pipe'\n");
248                break;
249        }
250        close(clientfd);
251    }
252}
253
254int
255main(int argc, char **argv)
256{
257    static struct sockaddr_in s_addr;
258    int port, filefd;
259    int reuse = 1;
260
261    /* validate arguments */
262    if (argc != 3 || !strcmp(argv[1], "-h"))
263        die(0, usage);
264    if ((port = toint(argv[1])) < 1 || port > 65535)
265        die(1, "Invalid port number: %s\n", argv[1]);
266    if ((filefd = open(argv[2], O_RDONLY)) == -1)
267        die(1, "Cannot open file: %s\n", argv[2]);
268    close(filefd);
269
270    /* define server socket address */
271    s_addr.sin_family       = AF_INET;
272    s_addr.sin_addr.s_addr  = htonl(INADDR_ANY);
273    s_addr.sin_port         = htons(port);
274
275    /* setup the network socket */
276    if ((serverfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
277        die(1, "Error: system call 'socket'\n");
278
279    /* reuse address and port */
280    if (setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR,
281        (const char*)&reuse, sizeof(reuse)) < 0)
282        die(1, "Error: system call 'setsockopt(SO_REUSEADDR)'\n");
283    #ifdef SO_REUSEPORT
284    if (setsockopt(serverfd, SOL_SOCKET, SO_REUSEPORT,
285        (const char*)&reuse, sizeof(reuse)) < 0)
286        die(1, "Error: system call 'setsockopt(SO_REUSEPORT)'\n");
287    #endif
288
289    /* bind and listen */
290    if (bind(serverfd, (struct sockaddr *)&s_addr, sizeof(s_addr)) < 0)
291        die(1, "Error: system call 'bind'\n");
292    if (listen(serverfd, 64) < 0)
293        die(1, "Error: system call 'listen'\n");
294
295    path = argv[2];
296
297    die(-1, "Serving '%s' on 0.0.0.0:%d...\n\n", path, port);
298    serve(serverfd);
299
300    return 0;
301}