Add to git.
[rws.git] / rewrite.c
1 /* Rewrite rules.
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: rewrite.c,v 1.7 2002/10/20 13:09:10 rich Exp $
19  */
20
21 #include "config.h"
22
23 #include <stdio.h>
24
25 #ifdef HAVE_STRING_H
26 #include <string.h>
27 #endif
28
29 #include <pcre.h>
30
31 #include <pool.h>
32 #include <vector.h>
33 #include <hash.h>
34 #include <pstring.h>
35 #include <pre.h>
36
37 #include "cfg.h"
38 #include "process_rq.h"
39 #include "re.h"
40 #include "rewrite.h"
41
42 #define RW_DEBUG 0              /* Set this to enable debugging. */
43
44 static pool rw_pool = 0;
45
46 /* Cache of host -> struct rw *. The null host is stored with key = "". */
47 static shash rw_cache;
48
49 /* Pre-parsed rules. */
50 struct rw
51 {
52   vector rules;                 /* Vector of struct rw_rule. */
53 };
54
55 /* Each rule. */
56 struct rw_rule
57 {
58   const char *pattern_text;
59   const pcre *pattern;
60   const char *sub;
61   int flags;
62 #define RW_RULE_EXTERNAL 0x0001
63 #define RW_RULE_LAST     0x0002
64 #define RW_RULE_QSA      0x0004
65 };
66
67 static struct rw *parse_rules (const char *cfg);
68 static const char *append_qs (process_rq p, const char *path);
69
70 void
71 rewrite_reset_rules ()
72 {
73   if (rw_pool) delete_pool (rw_pool);
74   rw_pool = new_subpool (global_pool);
75
76   rw_cache = new_shash (rw_pool, struct rw *);
77 }
78
79 int
80 apply_rewrites (const process_rq p, const char *path, const char **location)
81 {
82   struct rw *rw = 0;
83   const char *host = p->host_header ? p->host_header : "";
84   int i, matches = 0, qsa = 0;
85
86 #if RW_DEBUG
87   fprintf (stderr, "apply_rewrites: original path = %s\n",
88            p->canonical_path);
89 #endif
90
91   /* Get the configuration entry. (Note the alias is not known
92    * yet, so rewrite rules inside alias sections have no effect).
93    */
94   if (!shash_get (rw_cache, host, rw))
95     {
96       const char *cfg;
97
98       cfg = cfg_get_string (p->host, 0, "rewrite", 0);
99       if (cfg) rw = parse_rules (cfg);
100       shash_insert (rw_cache, host, rw);
101     }
102
103   if (!rw)                      /* No matching rule. */
104     {
105 #if RW_DEBUG
106       fprintf (stderr, "apply_rewrites: no matching rule\n");
107 #endif
108       return 0;
109     }
110
111   /* Look for a matching rule. */
112   for (i = 0; i < vector_size (rw->rules); ++i)
113     {
114       struct rw_rule rule;
115       const char *old_path = path;
116
117       vector_get (rw->rules, i, rule);
118
119 #if RW_DEBUG
120       fprintf (stderr, "apply_rewrites: try matching against %s\n",
121                rule.pattern_text);
122 #endif
123
124       path = presubst (p->pool, old_path, rule.pattern, rule.sub, 0);
125       if (path != old_path) /* It matched. */
126         {
127           matches = 1;
128           if (rule.flags & RW_RULE_QSA) qsa = 1;
129
130 #if RW_DEBUG
131           fprintf (stderr, "apply_rewrites: it matches %s\n",
132                    rule.pattern_text);
133 #endif
134
135           /* External link? If so, send a redirect. External rules are
136            * always 'last'.
137            */
138           if (rule.flags & RW_RULE_EXTERNAL)
139             {
140 #if RW_DEBUG
141               fprintf (stderr,
142                        "apply_rewrites: external: send redirect to %s\n",
143                        path);
144 #endif
145               *location = qsa ? append_qs (p, path) : path;
146               return 1;
147             }
148
149           /* Last rule? */
150           if (rule.flags & RW_RULE_LAST)
151             {
152 #if RW_DEBUG
153               fprintf (stderr,
154                        "apply_rewrites: last rule: finished with %s\n",
155                        path);
156 #endif
157               *location = qsa ? append_qs (p, path) : path;
158               return 2;
159             }
160
161           /* Jump back to the beginning of the list. */
162           i = -1;
163         }
164     }
165
166 #if RW_DEBUG
167   fprintf (stderr,
168            "apply_rewrites: finished with %s\n",
169            path);
170 #endif
171
172   if (matches)
173     {
174       *location = qsa ? append_qs (p, path) : path;
175       return 2;
176     }
177
178   return 0;
179 }
180
181 /* Append query string, if there is one. */
182 static const char *
183 append_qs (process_rq p, const char *path)
184 {
185   pool pool = p->pool;
186   const char *qs = http_request_query_string (p->http_request);
187
188   if (qs && strlen (qs) > 0)
189     {
190       const char *t = strchr (path, '?');
191
192       if (t)                    /* Path already has a query string? */
193         {
194           if (t[1] != '\0')
195             return psprintf (pool, "%s&%s", path, qs);
196           else
197             return psprintf (pool, "%s%s", path, qs);
198         }
199       else                      /* Path doesn't have a query string. */
200         return psprintf (pool, "%s?%s", path, qs);
201     }
202   return path;                  /* No query string. */
203 }
204
205 static void parse_error (const char *line, const char *msg);
206
207 static struct rw *
208 parse_rules (const char *cfg)
209 {
210   pool tmp = new_subpool (rw_pool);
211   vector lines;
212   const char *line;
213   struct rw *rw;
214   struct rw_rule rule;
215   int i;
216
217   /* Split up the configuration string into lines. */
218   lines = pstrcsplit (tmp, cfg, '\n');
219
220   /* Remove any empty lines (these have probably already been removed,
221    * but we can safely do this again anyway).
222    */
223   for (i = 0; i < vector_size (lines); ++i)
224     {
225       vector_get (lines, i, line);
226
227       if (strcmp (line, "") == 0)
228         {
229           vector_erase (lines, i);
230           i--;
231         }
232     }
233
234   if (vector_size (lines) == 0) { delete_pool (tmp); return 0; }
235
236   /* Allocate space for the return structure. */
237   rw = pmalloc (rw_pool, sizeof *rw);
238   rw->rules = new_vector (rw_pool, struct rw_rule);
239
240   /* Each line is a separate rule in the current syntax, so examine
241    * each line and turn it into a rule.
242    */
243   for (i = 0; i < vector_size (lines); ++i)
244     {
245       vector v;
246
247       vector_get (lines, i, line);
248
249       v = pstrresplit (tmp, line, re_ws);
250
251       if (vector_size (v) < 2 || vector_size (v) > 3)
252         parse_error (line, "unrecognised format");
253       vector_get (v, 0, rule.pattern_text);
254       rule.pattern_text = pstrdup (rw_pool, rule.pattern_text);
255       rule.pattern = precomp (rw_pool, rule.pattern_text, 0);
256       vector_get (v, 1, rule.sub);
257       rule.sub = pstrdup (rw_pool, rule.sub);
258
259       /* Parse the flags. */
260       rule.flags = 0;
261       if (vector_size (v) == 3)
262         {
263           const char *flags;
264           int j;
265
266           vector_get (v, 2, flags);
267           v = pstrresplit (tmp, flags, re_comma);
268
269           for (j = 0; j < vector_size (v); ++j)
270             {
271               const char *flag;
272
273               vector_get (v, j, flag);
274
275               if (strcasecmp (flag, "external") == 0)
276                 rule.flags |= RW_RULE_EXTERNAL;
277               else if (strcasecmp (flag, "last") == 0)
278                 rule.flags |= RW_RULE_LAST;
279               else if (strcasecmp (flag, "qsa") == 0)
280                 rule.flags |= RW_RULE_QSA;
281               else
282                 parse_error (line, "unknown flag");
283             }
284         }
285
286 #if RW_DEBUG
287       fprintf (stderr,
288                "parse rule: pattern=%s sub=%s flags=0x%04x\n",
289                rule.pattern_text, rule.sub, rule.flags);
290 #endif
291
292       vector_push_back (rw->rules, rule);
293     }
294
295   delete_pool (tmp);
296   return rw;
297 }
298
299 static void
300 parse_error (const char *line, const char *msg)
301 {
302   fprintf (stderr,
303            "rewrite rule: %s\n"
304            "at line: %s\n",
305            msg, line);
306   exit (1);
307 }