Add to git.
[pthrlib.git] / examples / pthr_eg2_server.c
1 /* Pseudothread server example.
2  * - by Richard W.M. Jones <rich@annexia.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the Free
16  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  *
18  * $Id: pthr_eg2_server.c,v 1.7 2003/02/05 22:13:32 rich Exp $
19  */
20
21 #include "config.h"
22
23 #include <stdio.h>
24
25 #ifdef HAVE_ALLOCA_H
26 #include <alloca.h>
27 #endif
28
29 #ifdef HAVE_STRING_H
30 #include <string.h>
31 #endif
32
33 #ifdef HAVE_SYS_STAT_H
34 #include <sys/stat.h>
35 #endif
36
37 #ifdef HAVE_DIRENT_H
38 #include <dirent.h>
39 #endif
40
41 #ifdef HAVE_FCNTL_H
42 #include <fcntl.h>
43 #endif
44
45 #ifdef HAVE_SYS_SYSLIMITS_H
46 #include <sys/syslimits.h>
47 #endif
48
49 #ifdef HAVE_UNISTD_H
50 #include <unistd.h>
51 #endif
52
53 #include <pool.h>
54 #include <pstring.h>
55
56 #include "src/pthr_pseudothread.h"
57 #include "src/pthr_iolib.h"
58 #include "src/pthr_http.h"
59 #include "src/pthr_cgi.h"
60 #include "pthr_eg2_server.h"
61
62 struct eg2_server_processor
63 {
64   /* Pseudothread handle. */
65   pseudothread pth;
66
67   /* Socket. */
68   int sock;
69
70   /* Pool for memory allocations. */
71   struct pool *pool;
72
73   /* HTTP request. */
74   http_request http_request;
75
76   /* IO handle. */
77   io_handle io;
78 };
79
80 static int file_not_found_error (eg2_server_processor p);
81 static int moved_permanently (eg2_server_processor p, const char *);
82 static int serve_directory (eg2_server_processor p, const char *, const struct stat *);
83 static int serve_file (eg2_server_processor p, const char *, const struct stat *);
84 static void run (void *vp);
85
86 eg2_server_processor
87 new_eg2_server_processor (int sock)
88 {
89   pool pool;
90   eg2_server_processor p;
91
92   pool = new_pool ();
93   p = pmalloc (pool, sizeof *p);
94
95   p->pool = pool;
96   p->sock = sock;
97   p->pth = new_pseudothread (pool, run, p, "eg2_server_processor");
98
99   pth_start (p->pth);
100
101   return p;
102 }
103
104 static void
105 run (void *vp)
106 {
107   eg2_server_processor p = (eg2_server_processor) vp;
108   int close = 0;
109   const char *path;
110   struct stat statbuf;
111
112   p->io = io_fdopen (p->sock);
113
114   /* Sit in a loop reading HTTP requests. */
115   while (!close)
116     {
117       /* ----- HTTP request ----- */
118       p->http_request = new_http_request (p->pool, p->io);
119       if (p->http_request == 0) /* Normal end of file. */
120         break;
121
122       /* Get the path and locate the file. */
123       path = http_request_path (p->http_request);
124       if (stat (path, &statbuf) == -1)
125         {
126           close = file_not_found_error (p);
127           continue;
128         }
129
130       /* File or directory? */
131       if (S_ISDIR (statbuf.st_mode))
132         {
133           close = serve_directory (p, path, &statbuf);
134           continue;
135         }
136       else if (S_ISREG (statbuf.st_mode))
137         {
138           close = serve_file (p, path, &statbuf);
139           continue;
140         }
141       else
142         {
143           close = file_not_found_error (p);
144           continue;
145         }
146     }
147
148   io_fclose (p->io);
149
150   pth_exit ();
151 }
152
153 static int
154 file_not_found_error (eg2_server_processor p)
155 {
156   http_response http_response;
157   int close;
158
159   http_response = new_http_response (p->pool, p->http_request, p->io,
160                                      404, "File or directory not found");
161   http_response_send_headers (http_response,
162                               /* Content type. */
163                               "Content-Type", "text/html",
164                               NO_CACHE_HEADERS,
165                               /* End of headers. */
166                               NULL);
167   close = http_response_end_headers (http_response);
168
169   if (http_request_is_HEAD (p->http_request)) return close;
170
171   io_fprintf (p->io,
172               "<html><head><title>File or directory not found</title></head>" CRLF
173               "<body bgcolor=\"#ffffff\">" CRLF
174               "<h1>404 File or directory not found</h1>" CRLF
175               "The file you requested was not found on this server." CRLF
176               "</body></html>" CRLF);
177
178   return close;
179 }
180
181 int
182 moved_permanently (eg2_server_processor p, const char *location)
183 {
184   http_response http_response;
185   int close;
186
187   http_response = new_http_response (p->pool, p->http_request, p->io,
188                                      301, "Moved permanently");
189   http_response_send_headers (http_response,
190                               /* Content length. */
191                               "Content-Length", "0",
192                               /* Location. */
193                               "Location", location,
194                               /* End of headers. */
195                               NULL);
196   close = http_response_end_headers (http_response);
197
198   if (http_request_is_HEAD (p->http_request)) return close;
199
200   return close;
201 }
202
203 static int
204 serve_directory (eg2_server_processor p, const char *path,
205                  const struct stat *statbuf)
206 {
207   http_response http_response;
208   int close;
209   DIR *dir;
210   struct dirent *d;
211
212 #ifndef NAME_MAX
213   /* Solaris defines NAME_MAX on a per-filesystem basis.
214    * See: http://lists.spine.cx/archives/everybuddy/2002-May/001419.html
215    */
216   long NAME_MAX = pathconf (path, _PC_NAME_MAX);
217 #endif
218
219   /* If the path doesn't end with a "/", then we need to send
220    * a redirect back to the client so it refetches the page
221    * with "/" appended.
222    */
223   if (path[strlen (path)-1] != '/')
224     {
225       char *location = psprintf (p->pool, "%s/", path);
226       return moved_permanently (p, location);
227     }
228
229   dir = opendir (path);
230   if (dir == 0)
231     return file_not_found_error (p);
232
233   http_response = new_http_response (p->pool, p->http_request, p->io,
234                                      200, "OK");
235   http_response_send_headers (http_response,
236                               /* Content type. */
237                               "Content-Type", "text/html",
238                               NO_CACHE_HEADERS,
239                               /* End of headers. */
240                               NULL);
241   close = http_response_end_headers (http_response);
242
243   if (http_request_is_HEAD (p->http_request)) return close;
244
245   io_fprintf (p->io,
246               "<html><head><title>Directory: %s</title></head>" CRLF
247               "<body bgcolor=\"#ffffff\">" CRLF
248               "<h1>Directory: %s</h1>" CRLF
249               "<table>" CRLF
250               "<tr><td></td><td></td>"
251               "<td><a href=\"..\">Parent directory</a></td></tr>" CRLF,
252               path, path);
253
254   while ((d = readdir (dir)) != 0)
255     {
256       if (d->d_name[0] != '.')  /* Ignore hidden files. */
257         {
258           const char *filename;
259           struct stat fstatbuf;
260
261           /* Generate the full pathname to this file. */
262           filename = psprintf (p->pool, "%s/%s", path, d->d_name);
263
264           /* Stat the file to find out what it is. */
265           if (lstat (filename, &fstatbuf) == 0)
266             {
267               const char *type;
268               int size;
269
270               if (S_ISDIR (fstatbuf.st_mode))
271                 type = "dir";
272               else if (S_ISREG (fstatbuf.st_mode))
273                 type = "file";
274               else if (S_ISLNK (fstatbuf.st_mode))
275                 type = "link";
276               else
277                 type = "special";
278
279               size = fstatbuf.st_size;
280
281               /* Print the details. */
282               io_fprintf (p->io,
283                           "<tr><td>[ %s ]</td><td align=right>%d</td>"
284                           "<td><a href=\"%s%s\">%s</a>",
285                           type, size,
286                           d->d_name,
287                           S_ISDIR (fstatbuf.st_mode) ? "/" : "",
288                           d->d_name);
289
290               if (S_ISLNK (fstatbuf.st_mode))
291                 {
292                   char link[NAME_MAX+1];
293                   int r;
294
295                   r = readlink (filename, link, NAME_MAX);
296                   if (r >= 0) link[r] = '\0';
297                   else strcpy (link, "unknown");
298
299                   io_fprintf (p->io, " -&gt; %s", link);
300                 }
301
302               io_fputs ("</td></tr>" CRLF, p->io);
303             }
304         }
305     }
306
307   io_fprintf (p->io,
308               "</table></body></html>" CRLF);
309
310   return close;
311 }
312
313 static int
314 serve_file (eg2_server_processor p, const char *path,
315             const struct stat *statbuf)
316 {
317   http_response http_response;
318   const int n = 4096;
319   char *buffer = alloca (n);
320   int cl, fd, r;
321   char *content_length = pitoa (p->pool, statbuf->st_size);
322
323   fd = open (path, O_RDONLY);
324   if (fd < 0)
325     return file_not_found_error (p);
326
327   http_response = new_http_response (p->pool, p->http_request, p->io,
328                                      200, "OK");
329   http_response_send_headers (http_response,
330                               /* Content type. */
331                               "Content-Type", "text/plain",
332                               "Content-Length", content_length,
333                               /* End of headers. */
334                               NULL);
335   cl = http_response_end_headers (http_response);
336
337   if (http_request_is_HEAD (p->http_request)) return cl;
338
339   while ((r = read (fd, buffer, n)) > 0)
340     {
341       io_fwrite (buffer, r, 1, p->io);
342     }
343
344   if (r < 0)
345     perror ("read");
346
347   close (fd);
348
349   return cl;
350 }