Add to git.
[rws.git] / process_rq.c
1 /* Request processing thread.
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: process_rq.c,v 1.25 2003/03/01 12:11:33 rich Exp $
19  */
20
21 #include "config.h"
22
23 #include <stdio.h>
24
25 #ifdef HAVE_SYS_SOCKET_H
26 #include <sys/socket.h>
27 #endif
28
29 #ifdef HAVE_STRING_H
30 #include <string.h>
31 #endif
32
33 #ifdef HAVE_UNISTD_H
34 #include <unistd.h>
35 #endif
36
37 #ifdef HAVE_SYS_STAT_H
38 #include <sys/stat.h>
39 #endif
40
41 #ifdef HAVE_FCNTL_H
42 #include <fcntl.h>
43 #endif
44
45 #include <pool.h>
46 #include <vector.h>
47 #include <hash.h>
48 #include <pstring.h>
49
50 #include <pthr_pseudothread.h>
51 #include <pthr_iolib.h>
52 #include <pthr_http.h>
53 #include <pthr_cgi.h>
54
55 #include "cfg.h"
56 #include "file.h"
57 #include "dir.h"
58 #include "errors.h"
59 #include "rewrite.h"
60 #include "process_rq.h"
61
62 /* Maximum number of requests to service in one thread. This just acts
63  * as a check on the size of the thread pool, preventing it from growing
64  * out of control.
65  */
66 #define MAX_REQUESTS_IN_THREAD 30
67
68 #define PR_DEBUG 0              /* Set this to enable debugging. */
69
70 static void run (void *vp);
71
72 process_rq
73 new_process_rq (int sock)
74 {
75   pool pool;
76   process_rq p;
77
78   pool = new_pool ();
79   p = pmalloc (pool, sizeof *p);
80
81   memset (p, 0, sizeof *p);
82
83   /* Set the FD_CLOEXEC flag so that when we fork off CGI scripts, they
84    * won't inherit the socket.
85    */
86   if (fcntl (sock, F_SETFD, FD_CLOEXEC) < 0) { perror ("fcntl"); exit (1); }
87
88   p->sock = sock;
89   p->pth = new_pseudothread (pool, run, p, "process_rq");
90
91   pth_start (p->pth);
92
93   return p;
94 }
95
96 #define THREAD_NAME "rws process request thread"
97
98 static void
99 run (void *vp)
100 {
101   process_rq p = (process_rq) vp;
102   int close = 0;
103   int request_timeout;
104   vector path_comps, v;
105   int i, is_dir, nr_requests = 1;
106   const char *location;
107
108   p->pool = pth_get_pool (p->pth);
109   p->io = io_fdopen (p->sock);
110
111   request_timeout = cfg_get_int (0, 0, "request timeout", 60);
112
113   /* Sit in a loop reading HTTP requests. */
114   while (!close && nr_requests <= MAX_REQUESTS_IN_THREAD)
115     {
116       /* Generic name for this thread. */
117       pth_set_name (THREAD_NAME " (idle)");
118
119       /* Count the number of requests serviced in this thread. */
120       nr_requests++;
121
122       /* Timeout requests. */
123       pth_timeout (request_timeout);
124
125       /* Read the request. */
126       p->http_request = new_http_request (p->pool, p->io);
127       if (p->http_request == 0) /* Normal end of file. */
128         break;
129
130       /* Reset timeout. */
131       pth_timeout (0);
132
133       /* Choose the correct configuration file based on the Host: header. */
134       p->host_header = http_request_get_header (p->http_request, "Host");
135       if (p->host_header)
136         {
137           p->host_header = pstrlwr (pstrdup (p->pool, p->host_header));
138
139           if ((p->host = cfg_get_host (p->host_header)) != 0)
140             goto found_host;
141           fprintf (stderr, "unknown virtual host: %s, trying default\n",
142                    p->host_header);
143         }
144
145       p->host_header = "default";
146       if ((p->host = cfg_get_host (p->host_header)) == 0)
147         {
148           close = bad_request_error (p, "no \"default\" virtual host!");
149           continue;
150         }
151     found_host:
152
153       /* Get the originally requested path. */
154       p->requested_path = http_request_path (p->http_request);
155       if (!p->requested_path || p->requested_path[0] != '/')
156         {
157           close = bad_request_error (p, "bad pathname");
158           continue;
159         }
160
161       /* Path may contain % sequences. Unescape them. */
162       p->requested_path = cgi_unescape (p->pool, p->requested_path);
163
164       /* If the path ends in a /, then it's a request for a directory.
165        * Record this fact now, because pstrcsplit will forget about the
166        * trailing slash otherwise.
167        */
168       is_dir = p->requested_path[strlen (p->requested_path)-1] == '/';
169
170       /* Split up the path into individual components. */
171       path_comps = pstrcsplit (p->pool, p->requested_path, '/');
172
173       /* Remove "", "." and ".." components. */
174       for (i = 0; i < vector_size (path_comps); ++i)
175         {
176           char *comp;
177
178           vector_get (path_comps, i, comp);
179
180           if (strcmp (comp, "") == 0 || strcmp (comp, ".") == 0)
181             {
182               vector_erase (path_comps, i);
183               i--;
184             }
185           else if (strcmp (comp, "..") == 0)
186             {
187               if (i > 0)
188                 {
189                   vector_erase_range (path_comps, i-1, i+1);
190                   i -= 2;
191                 }
192               else
193                 {
194                   vector_erase (path_comps, i);
195                   i--;
196                 }
197             }
198         }
199
200       /* Construct the canonical path. Add a trailing slash if the
201        * original request was for a directory.
202        */
203       p->canonical_path = psprintf (p->pool, "/%s",
204                                     pjoin (p->pool, path_comps, "/"));
205       if (strlen (p->canonical_path) > 1 && is_dir)
206         p->canonical_path = psprintf (p->pool, "%s/", p->canonical_path);
207
208 #if PR_DEBUG
209       fprintf (stderr, "canonical path is %s\n", p->canonical_path);
210 #endif
211
212       /* Update the name of the thread with the full request URL. */
213       pth_set_name (psprintf (p->pool, THREAD_NAME " http://%s%s",
214                               p->host_header,
215                               p->canonical_path));
216
217       /* Apply internal and external rewrite rules. */
218       i = apply_rewrites (p, p->canonical_path, &location);
219       if (i == 1)               /* External rewrite. */
220         {
221 #if PR_DEBUG
222           fprintf (stderr, "external rewrite rule to %s\n", location);
223 #endif
224           close = moved_permanently (p, location);
225           continue;
226         }
227       else if (i == 2)          /* Internal rewrite. */
228         {
229 #if PR_DEBUG
230           fprintf (stderr, "internal rewrite rule to %s\n", location);
231 #endif
232
233           /* Update the http_request object with the new path. This also
234            * changes the query string held in this object so that the cgi
235            * library works correctly.
236            */
237           http_request_set_url (p->http_request, location);
238
239           /* Get the path, minus query string. */
240           p->rewritten_path = http_request_path (p->http_request);
241
242           /* Resplit the path. */
243           path_comps = pstrcsplit (p->pool, p->rewritten_path, '/');
244         }
245
246       /* Look for longest matching alias. */
247       for (i = vector_size (path_comps); i >= 0; --i)
248         {
249           if (i > 0)
250             {
251               v = new_subvector (p->pool, path_comps, 0, i);
252               p->aliasname =
253                 psprintf (p->pool, "/%s/", pjoin (p->pool, v, "/"));
254             }
255           else
256             p->aliasname = "/";
257
258 #if PR_DEBUG
259           fprintf (stderr, "try to find alias matching %s\n", p->aliasname);
260 #endif
261
262           if ((p->alias = cfg_get_alias (p->host, p->aliasname)) != 0)
263             goto found_alias;
264         }
265
266 #if PR_DEBUG
267       fprintf (stderr, "no matching alias found\n");
268 #endif
269
270       /* No alias. */
271       close = file_not_found_error (p);
272       continue;
273
274     found_alias:
275       /* Build up the remainder of the path and the file. */
276       v = new_subvector (p->pool, path_comps, i, vector_size (path_comps));
277       p->remainder = pjoin (p->pool, v, "/");
278
279       /* Find the root path for this alias. */
280       p->root = cfg_get_string (p->host, p->alias, "path", 0);
281       if (p->root == 0)
282         {
283           close = file_not_found_error (p);
284           continue;
285         }
286
287       /* Construct the file path. */
288       p->file_path = psprintf (p->pool, "%s/%s", p->root, p->remainder);
289
290 #if PR_DEBUG
291       fprintf (stderr,
292                "rp = %s, cp = %s, rew = %s, "
293                "an = %s, rem = %s, root = %s, fp = %s, qs = %s\n",
294                p->requested_path, p->canonical_path, p->rewritten_path,
295                p->aliasname, p->remainder, p->root,
296                p->file_path,
297                http_request_query_string (p->http_request) ? : "(null)");
298 #endif
299
300       /* Find the file to serve and stat it. */
301       if (stat (p->file_path, &p->statbuf) == -1)
302         {
303           close = file_not_found_error (p);
304           continue;
305         }
306
307       /* If it's a directory, but the last component of the name isn't
308        * a "/" character, then we need to add a "/" character and send
309        * a browser redirect back.
310        */
311       if (S_ISDIR (p->statbuf.st_mode) &&
312           p->canonical_path[strlen(p->canonical_path)-1] != '/')
313         {
314           location = psprintf (p->pool, "%s/", p->requested_path);
315           close = moved_permanently (p, location);
316           continue;
317         }
318
319       /* What type of file are we serving? */
320       if (S_ISREG (p->statbuf.st_mode))
321         {
322           close = file_serve (p);
323           continue;
324         }
325       else if (S_ISDIR (p->statbuf.st_mode))
326         {
327           close = dir_serve (p);
328           continue;
329         }
330
331       /* Bad request. */
332       close = bad_request_error (p, "not a regular file or directory");
333     }
334
335   io_fclose (p->io);
336
337   pth_exit ();
338 }