bottleneck testcase based on rubbos
[bottlenecks.git] / rubbos / app / httpd-2.0.64 / modules / mappers / mod_speling.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "apr.h"
18 #include "apr_file_io.h"
19 #include "apr_strings.h"
20 #include "apr_lib.h"
21
22 #define APR_WANT_STRFUNC
23 #include "apr_want.h"
24
25 #define WANT_BASENAME_MATCH
26
27 #include "httpd.h"
28 #include "http_core.h"
29 #include "http_config.h"
30 #include "http_request.h"
31 #include "http_log.h"
32
33 /* mod_speling.c - by Alexei Kosut <akosut@organic.com> June, 1996
34  *
35  * This module is transparent, and simple. It attempts to correct
36  * misspellings of URLs that users might have entered, namely by checking
37  * capitalizations. If it finds a match, it sends a redirect.
38  *
39  * 08-Aug-1997 <Martin.Kraemer@Mch.SNI.De>
40  * o Upgraded module interface to apache_1.3a2-dev API (more NULL's in
41  *   speling_module).
42  * o Integrated tcsh's "spelling correction" routine which allows one
43  *   misspelling (character insertion/omission/typo/transposition).
44  *   Rewrote it to ignore case as well. This ought to catch the majority
45  *   of misspelled requests.
46  * o Commented out the second pass where files' suffixes are stripped.
47  *   Given the better hit rate of the first pass, this rather ugly
48  *   (request index.html, receive index.db ?!?!) solution can be
49  *   omitted.
50  * o wrote a "kind of" html page for mod_speling
51  *
52  * Activate it with "CheckSpelling On"
53  */
54
55 module AP_MODULE_DECLARE_DATA speling_module;
56
57 typedef struct {
58     int enabled;
59 } spconfig;
60
61 /*
62  * Create a configuration specific to this module for a server or directory
63  * location, and fill it with the default settings.
64  *
65  * The API says that in the absence of a merge function, the record for the
66  * closest ancestor is used exclusively.  That's what we want, so we don't
67  * bother to have such a function.
68  */
69
70 static void *mkconfig(apr_pool_t *p)
71 {
72     spconfig *cfg = apr_pcalloc(p, sizeof(spconfig));
73
74     cfg->enabled = 0;
75     return cfg;
76 }
77
78 /*
79  * Respond to a callback to create configuration record for a server or
80  * vhost environment.
81  */
82 static void *create_mconfig_for_server(apr_pool_t *p, server_rec *s)
83 {
84     return mkconfig(p);
85 }
86
87 /*
88  * Respond to a callback to create a config record for a specific directory.
89  */
90 static void *create_mconfig_for_directory(apr_pool_t *p, char *dir)
91 {
92     return mkconfig(p);
93 }
94
95 /*
96  * Handler for the CheckSpelling directive, which is FLAG.
97  */
98 static const char *set_speling(cmd_parms *cmd, void *mconfig, int arg)
99 {
100     spconfig *cfg = (spconfig *) mconfig;
101
102     cfg->enabled = arg;
103     return NULL;
104 }
105
106 /*
107  * Define the directives specific to this module.  This structure is referenced
108  * later by the 'module' structure.
109  */
110 static const command_rec speling_cmds[] =
111 {
112     AP_INIT_FLAG("CheckSpelling", set_speling, NULL, OR_OPTIONS,
113                  "whether or not to fix miscapitalized/misspelled requests"),
114     { NULL }
115 };
116
117 typedef enum {
118     SP_IDENTICAL = 0,
119     SP_MISCAPITALIZED = 1,
120     SP_TRANSPOSITION = 2,
121     SP_MISSINGCHAR = 3,
122     SP_EXTRACHAR = 4,
123     SP_SIMPLETYPO = 5,
124     SP_VERYDIFFERENT = 6
125 } sp_reason;
126
127 static const char *sp_reason_str[] =
128 {
129     "identical",
130     "miscapitalized",
131     "transposed characters",
132     "character missing",
133     "extra character",
134     "mistyped character",
135     "common basename",
136 };
137
138 typedef struct {
139     const char *name;
140     sp_reason quality;
141 } misspelled_file;
142
143 /*
144  * spdist() is taken from Kernighan & Pike,
145  *  _The_UNIX_Programming_Environment_
146  * and adapted somewhat to correspond better to psychological reality.
147  * (Note the changes to the return values)
148  *
149  * According to Pollock and Zamora, CACM April 1984 (V. 27, No. 4),
150  * page 363, the correct order for this is:
151  * OMISSION = TRANSPOSITION > INSERTION > SUBSTITUTION
152  * thus, it was exactly backwards in the old version. -- PWP
153  *
154  * This routine was taken out of tcsh's spelling correction code
155  * (tcsh-6.07.04) and re-converted to apache data types ("char" type
156  * instead of tcsh's NLS'ed "Char"). Plus it now ignores the case
157  * during comparisons, so is a "approximate strcasecmp()".
158  * NOTE that is still allows only _one_ real "typo",
159  * it does NOT try to correct multiple errors.
160  */
161
162 static sp_reason spdist(const char *s, const char *t)
163 {
164     for (; apr_tolower(*s) == apr_tolower(*t); t++, s++) {
165         if (*t == '\0') {
166             return SP_MISCAPITALIZED;   /* exact match (sans case) */
167         }
168     }
169     if (*s) {
170         if (*t) {
171             if (s[1] && t[1] && apr_tolower(*s) == apr_tolower(t[1])
172                 && apr_tolower(*t) == apr_tolower(s[1])
173                 && strcasecmp(s + 2, t + 2) == 0) {
174                 return SP_TRANSPOSITION;        /* transposition */
175             }
176             if (strcasecmp(s + 1, t + 1) == 0) {
177                 return SP_SIMPLETYPO;   /* 1 char mismatch */
178             }
179         }
180         if (strcasecmp(s + 1, t) == 0) {
181             return SP_EXTRACHAR;        /* extra character */
182         }
183     }
184     if (*t && strcasecmp(s, t + 1) == 0) {
185         return SP_MISSINGCHAR;  /* missing character */
186     }
187     return SP_VERYDIFFERENT;    /* distance too large to fix. */
188 }
189
190 static int sort_by_quality(const void *left, const void *rite)
191 {
192     return (int) (((misspelled_file *) left)->quality)
193         - (int) (((misspelled_file *) rite)->quality);
194 }
195
196 static int check_speling(request_rec *r)
197 {
198     spconfig *cfg;
199     char *good, *bad, *postgood, *url;
200     apr_finfo_t dirent;
201     int filoc, dotloc, urlen, pglen;
202     apr_array_header_t *candidates = NULL;
203     apr_dir_t          *dir;
204
205     cfg = ap_get_module_config(r->per_dir_config, &speling_module);
206     if (!cfg->enabled) {
207         return DECLINED;
208     }
209
210     /* We only want to worry about GETs */
211     if (r->method_number != M_GET) {
212         return DECLINED;
213     }
214
215     /* We've already got a file of some kind or another */
216     if (r->finfo.filetype != 0) {
217         return DECLINED;
218     }
219
220     /* Not a file request */
221     if (r->proxyreq || !r->filename) {
222         return DECLINED;
223     }
224
225     /* This is a sub request - don't mess with it */
226     if (r->main) {
227         return DECLINED;
228     }
229
230     /*
231      * The request should end up looking like this:
232      * r->uri: /correct-url/mispelling/more
233      * r->filename: /correct-file/mispelling r->path_info: /more
234      *
235      * So we do this in steps. First break r->filename into two pieces
236      */
237
238     filoc = ap_rind(r->filename, '/');
239     /*
240      * Don't do anything if the request doesn't contain a slash, or
241      * requests "/" 
242      */
243     if (filoc == -1 || strcmp(r->uri, "/") == 0) {
244         return DECLINED;
245     }
246
247     /* good = /correct-file */
248     good = apr_pstrndup(r->pool, r->filename, filoc);
249     /* bad = mispelling */
250     bad = apr_pstrdup(r->pool, r->filename + filoc + 1);
251     /* postgood = mispelling/more */
252     postgood = apr_pstrcat(r->pool, bad, r->path_info, NULL);
253
254     urlen = strlen(r->uri);
255     pglen = strlen(postgood);
256
257     /* Check to see if the URL pieces add up */
258     if (strcmp(postgood, r->uri + (urlen - pglen))) {
259         return DECLINED;
260     }
261
262     /* url = /correct-url */
263     url = apr_pstrndup(r->pool, r->uri, (urlen - pglen));
264
265     /* Now open the directory and do ourselves a check... */
266     if (apr_dir_open(&dir, good, r->pool) != APR_SUCCESS) {
267         /* Oops, not a directory... */
268         return DECLINED;
269     }
270
271     candidates = apr_array_make(r->pool, 2, sizeof(misspelled_file));
272
273     dotloc = ap_ind(bad, '.');
274     if (dotloc == -1) {
275         dotloc = strlen(bad);
276     }
277
278     while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dir) == APR_SUCCESS) {
279         sp_reason q;
280
281         /*
282          * If we end up with a "fixed" URL which is identical to the
283          * requested one, we must have found a broken symlink or some such.
284          * Do _not_ try to redirect this, it causes a loop!
285          */
286         if (strcmp(bad, dirent.name) == 0) {
287             apr_dir_close(dir);
288             return OK;
289         }
290
291         /*
292          * miscapitalization errors are checked first (like, e.g., lower case
293          * file, upper case request)
294          */
295         else if (strcasecmp(bad, dirent.name) == 0) {
296             misspelled_file *sp_new;
297
298             sp_new = (misspelled_file *) apr_array_push(candidates);
299             sp_new->name = apr_pstrdup(r->pool, dirent.name);
300             sp_new->quality = SP_MISCAPITALIZED;
301         }
302
303         /*
304          * simple typing errors are checked next (like, e.g.,
305          * missing/extra/transposed char)
306          */
307         else if ((q = spdist(bad, dirent.name)) != SP_VERYDIFFERENT) {
308             misspelled_file *sp_new;
309
310             sp_new = (misspelled_file *) apr_array_push(candidates);
311             sp_new->name = apr_pstrdup(r->pool, dirent.name);
312             sp_new->quality = q;
313         }
314
315         /*
316          * The spdist() should have found the majority of the misspelled
317          * requests.  It is of questionable use to continue looking for
318          * files with the same base name, but potentially of totally wrong
319          * type (index.html <-> index.db).
320          * I would propose to not set the WANT_BASENAME_MATCH define.
321          *      08-Aug-1997 <Martin.Kraemer@Mch.SNI.De>
322          *
323          * However, Alexei replied giving some reasons to add it anyway:
324          * > Oh, by the way, I remembered why having the
325          * > extension-stripping-and-matching stuff is a good idea:
326          * >
327          * > If you're using MultiViews, and have a file named foobar.html,
328          * > which you refer to as "foobar", and someone tried to access
329          * > "Foobar", mod_speling won't find it, because it won't find
330          * > anything matching that spelling. With the extension-munging,
331          * > it would locate "foobar.html". Not perfect, but I ran into
332          * > that problem when I first wrote the module.
333          */
334         else {
335 #ifdef WANT_BASENAME_MATCH
336             /*
337              * Okay... we didn't find anything. Now we take out the hard-core
338              * power tools. There are several cases here. Someone might have
339              * entered a wrong extension (.htm instead of .html or vice
340              * versa) or the document could be negotiated. At any rate, now
341              * we just compare stuff before the first dot. If it matches, we
342              * figure we got us a match. This can result in wrong things if
343              * there are files of different content types but the same prefix
344              * (e.g. foo.gif and foo.html) This code will pick the first one
345              * it finds. Better than a Not Found, though.
346              */
347             int entloc = ap_ind(dirent.name, '.');
348             if (entloc == -1) {
349                 entloc = strlen(dirent.name);
350             }
351
352             if ((dotloc == entloc)
353                 && !strncasecmp(bad, dirent.name, dotloc)) {
354                 misspelled_file *sp_new;
355
356                 sp_new = (misspelled_file *) apr_array_push(candidates);
357                 sp_new->name = apr_pstrdup(r->pool, dirent.name);
358                 sp_new->quality = SP_VERYDIFFERENT;
359             }
360 #endif
361         }
362     }
363     apr_dir_close(dir);
364
365     if (candidates->nelts != 0) {
366         /* Wow... we found us a mispelling. Construct a fixed url */
367         char *nuri;
368         const char *ref;
369         misspelled_file *variant = (misspelled_file *) candidates->elts;
370         int i;
371
372         ref = apr_table_get(r->headers_in, "Referer");
373
374         qsort((void *) candidates->elts, candidates->nelts,
375               sizeof(misspelled_file), sort_by_quality);
376
377         /*
378          * Conditions for immediate redirection: 
379          *     a) the first candidate was not found by stripping the suffix 
380          * AND b) there exists only one candidate OR the best match is not
381          *        ambiguous
382          * then return a redirection right away.
383          */
384         if (variant[0].quality != SP_VERYDIFFERENT
385             && (candidates->nelts == 1
386                 || variant[0].quality != variant[1].quality)) {
387
388             nuri = ap_escape_uri(r->pool, apr_pstrcat(r->pool, url,
389                                                      variant[0].name,
390                                                      r->path_info, NULL));
391             if (r->parsed_uri.query)
392                 nuri = apr_pstrcat(r->pool, nuri, "?", r->parsed_uri.query, NULL);
393
394             apr_table_setn(r->headers_out, "Location",
395                           ap_construct_url(r->pool, nuri, r));
396
397             ap_log_rerror(APLOG_MARK, APLOG_INFO, APR_SUCCESS,
398                           r, 
399                           ref ? "Fixed spelling: %s to %s from %s"
400                               : "Fixed spelling: %s to %s",
401                           r->uri, nuri, ref);
402
403             return HTTP_MOVED_PERMANENTLY;
404         }
405         /*
406          * Otherwise, a "[300] Multiple Choices" list with the variants is
407          * returned.
408          */
409         else {
410             apr_pool_t *p;
411             apr_table_t *notes;
412             apr_pool_t *sub_pool;
413             apr_array_header_t *t;
414             apr_array_header_t *v;
415
416
417             if (r->main == NULL) {
418                 p = r->pool;
419                 notes = r->notes;
420             }
421             else {
422                 p = r->main->pool;
423                 notes = r->main->notes;
424             }
425
426             if (apr_pool_create(&sub_pool, p) != APR_SUCCESS)
427                 return DECLINED;
428
429             t = apr_array_make(sub_pool, candidates->nelts * 8 + 8,
430                               sizeof(char *));
431             v = apr_array_make(sub_pool, candidates->nelts * 5,
432                               sizeof(char *));
433
434             /* Generate the response text. */
435
436             *(const char **)apr_array_push(t) =
437                           "The document name you requested (<code>";
438             *(const char **)apr_array_push(t) = ap_escape_html(sub_pool, r->uri);
439             *(const char **)apr_array_push(t) =
440                            "</code>) could not be found on this server.\n"
441                            "However, we found documents with names similar "
442                            "to the one you requested.<p>"
443                            "Available documents:\n<ul>\n";
444
445             for (i = 0; i < candidates->nelts; ++i) {
446                 char *vuri;
447                 const char *reason;
448
449                 reason = sp_reason_str[(int) (variant[i].quality)];
450                 /* The format isn't very neat... */
451                 vuri = apr_pstrcat(sub_pool, url, variant[i].name, r->path_info,
452                                   (r->parsed_uri.query != NULL) ? "?" : "",
453                                   (r->parsed_uri.query != NULL)
454                                       ? r->parsed_uri.query : "",
455                                   NULL);
456                 *(const char **)apr_array_push(v) = "\"";
457                 *(const char **)apr_array_push(v) = ap_escape_uri(sub_pool, vuri);
458                 *(const char **)apr_array_push(v) = "\";\"";
459                 *(const char **)apr_array_push(v) = reason;
460                 *(const char **)apr_array_push(v) = "\"";
461
462                 *(const char **)apr_array_push(t) = "<li><a href=\"";
463                 *(const char **)apr_array_push(t) = ap_escape_uri(sub_pool, vuri);
464                 *(const char **)apr_array_push(t) = "\">";
465                 *(const char **)apr_array_push(t) = ap_escape_html(sub_pool, vuri);
466                 *(const char **)apr_array_push(t) = "</a> (";
467                 *(const char **)apr_array_push(t) = reason;
468                 *(const char **)apr_array_push(t) = ")\n";
469
470                 /*
471                  * when we have printed the "close matches" and there are
472                  * more "distant matches" (matched by stripping the suffix),
473                  * then we insert an additional separator text to suggest
474                  * that the user LOOK CLOSELY whether these are really the
475                  * files she wanted.
476                  */
477                 if (i > 0 && i < candidates->nelts - 1
478                     && variant[i].quality != SP_VERYDIFFERENT
479                     && variant[i + 1].quality == SP_VERYDIFFERENT) {
480                     *(const char **)apr_array_push(t) = 
481                                    "</ul>\nFurthermore, the following related "
482                                    "documents were found:\n<ul>\n";
483                 }
484             }
485             *(const char **)apr_array_push(t) = "</ul>\n";
486
487             /* If we know there was a referring page, add a note: */
488             if (ref != NULL) {
489                 *(const char **)apr_array_push(t) =
490                                "Please consider informing the owner of the "
491                                "<a href=\"";
492                 *(const char **)apr_array_push(t) = ap_escape_uri(sub_pool, ref);
493                 *(const char **)apr_array_push(t) = "\">referring page</a> "
494                                "about the broken link.\n";
495             }
496
497
498             /* Pass our apr_table_t to http_protocol.c (see mod_negotiation): */
499             apr_table_setn(notes, "variant-list", apr_array_pstrcat(p, t, 0));
500
501             apr_table_mergen(r->subprocess_env, "VARIANTS",
502                             apr_array_pstrcat(p, v, ','));
503           
504             apr_pool_destroy(sub_pool);
505
506             ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
507                          ref ? "Spelling fix: %s: %d candidates from %s"
508                              : "Spelling fix: %s: %d candidates",
509                          r->uri, candidates->nelts, ref);
510
511             return HTTP_MULTIPLE_CHOICES;
512         }
513     }
514
515     return OK;
516 }
517
518 static void register_hooks(apr_pool_t *p)
519 {
520     ap_hook_fixups(check_speling,NULL,NULL,APR_HOOK_LAST);
521 }
522
523 module AP_MODULE_DECLARE_DATA speling_module =
524 {
525     STANDARD20_MODULE_STUFF,
526     create_mconfig_for_directory,  /* create per-dir config */
527     NULL,                       /* merge per-dir config */
528     create_mconfig_for_server,  /* server config */
529     NULL,                       /* merge server config */
530     speling_cmds,               /* command apr_table_t */
531     register_hooks              /* register hooks */
532 };