You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by "Ralf S. Engelschall" <rs...@en.muc.de> on 1996/07/07 14:50:48 UTC
Some questions regarding the API for mod_rewrite
[ To all API-specialists:
Please listen and give me some hints,
even this mail is not a short one! Thanks! ]
I'm still working on the next version of mod_rewrite (1.8) which will contain
a lot of new things including PER-DIRECTORY rewriting support (all RewriteXX
directives valid in .htaccess files).
Now I've tested this version also under 1.1.0 and it worked fine. But before
I will officially release it next week, I want to step through the code and
make sure that it is really correctly implemented and does "not only work" ;-).
Some things are left unclear:
Current per-directory support works fine on my test machine, but the way I do
it _is very special and not the obvious way_, even the only possible I think.
The per-dir rewriting engine is run in the "fixup" hook of the API and does
the following at exit:
- When no rewriting rule applies, then we simply return DECLINED
(I'm sure that this is the correct way)
- When a external redirect should be made, then I escape the URL,
set Location: and return with REDIRECT.
(I'm sure that this is the correct way)
- When a local URL rewriting occured, then I do a _partitial_ stat() on
the file (i.e. a stat() on all prefixes). Then
if the whole file (i.e. not only some prefix) exists then I
- set r->finfo to the result of the stat
- do a internal_redirect()
- set r->handler to a own null-handler which does nothing
in the next API hook which will be run after our return
- return OK
(I'm NOT sure if this is the 100% correct way, even if
I'm sure that this is API conform and the only working way)
else if only some prefix exists then I
- set r->finfo.st_mode = 0
- do a internal_redirect()
- set r->handler to a own null-handler which does nothing
in the next API hook which will be run after our return
- return OK
(I'm NOT sure if this is the 100% correct way, even if
I'm sure that this is API conform and the only working way)
else
- set r->finfo.st_mode = 0
- return DECLINED
(I'm sure that this is the correct way)
This approach is very difficult, but _works_ in all situations which occur on
my test machine. The curious partitial stat is really needed, because
when we rewrite some URL to a URL which is only _partitially_ valid then
we have to mark the filename as invalid (finfo.st_mode) but return a OK to
prevent the core module from prefixing it with the document_root.
Returning DECLINED is not the correct action!
And the trick with the null-handler is needed because
we need to do a real internal_redirect() instead of just a OK return,
because at the "fixup" hook a rewrite is sometimes to late, e.g. if the
URL got rewritten to a (valid) directory, then mod_dir will no more
examine the index.xxxx default files at this stage. But a external
redirect is not what the user wanted (this should only occur if a http://
URL occurs or the "redirect" flag is used in a RewriteRule command). So
the only chance to become a correct result is a real internal_redirect().
BUT: When we do a internal_redirect() in a fixup hook and then return OK,
this would lead to a handling of the current (still obsolete because
already rewritten) request, i.e. I GOT TWO FILES SEND OUT FOR THE HTTP
REQUEST!
The only chance is to _silently_ finish the current never more needed
(because it was already replaced by the internal_redirect) request. To
accomplish this I use the null-handler trick which worked correctly!
And now the question:
ARE THESE TWO SPECIAL TRICKS REALLY OK
OR ARE THERE ANY LESS-SPECIAL ALTERNATIVES ?????
Ralf S. Engelschall
rse@en.muc.de
http://www.muc.de/~rse
PS: I will append (although it is not very short) the current mod_rewrite.c
to this message to give you the chance to review the code in more detail:
------
/* ====================================================================
* Copyright (c) 1996 The Apache Group. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the Apache Group
* for use in the Apache HTTP server project (http://www.apache.org/)."
*
* 4. The names "Apache Server" and "Apache Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission.
*
* 5. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the Apache Group
* for use in the Apache HTTP server project (http://www.apache.org/)."
*
* THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Group and was originally based
* on public domain software written at the National Center for
* Supercomputing Applications, University of Illinois, Urbana-Champaign.
* For more information on the Apache Group and the Apache HTTP server
* project, please see <http://www.apache.org/>.
*
*/
/*
** Module mod_rewrite
**
** URL Rewriting Module, Version 1.8 (06-07-1996)
**
** This module uses a regular-expression parser to rewrite requested URLs
** on the fly. It can use external databases (either plain text, or
** DBM) to provide a mapping function or generate real URIs (including
** QUERY_INFO parts) for internal subprocessing or external request
** redirection.
**
** The latest release can be found on
** http://www.engelschall.com/mod_rewrite/
**
** Copyright (c) 1996 The Apache Group
** Copyright (c) 1996 Ralf S. Engelschall
**
** Written for The Apache Group by
** Ralf S. Engelschall
** rse@engelschall.com
** http://www.engelschall.com/~rse
*/
/*
** The complete documentation can be found on
** http://www.engelschall.com/mod_rewrite/mod_rewrite.html
*/
/*
**
** Some defines
**
*/
#define ENVVAR_SCRIPT_URL "SCRIPT_URL"
#define ENVVAR_SCRIPT_URI "SCRIPT_URI"
#define DEFAULT_REWRITELOGFILE "logs/rewrite_log"
#ifndef SUPPORT_DBM_REWRITEMAP
#define SUPPORT_DBM_REWRITEMAP 0
#endif
#define REWRITE_FORCED_MIMETYPE_NOTEVAR "rewrite-forced-mimetype"
#define REWRITEFLAG_NONE 0
#define REWRITEFLAG_FORCEREDIRECT 1
#define REWRITEFLAG_LASTRULE 2
#define REWRITEFLAG_NEWROUND 4
#define REWRITEFLAG_CHAIN 8
#define REWRITEFLAG_IGNOREONSUBREQ 16
#define ENGINE_ENABLED 1
#define ENGINE_DISABLED 0
/*
**
** Includes to energize us
**
*/
/* from the underlying Unix system ... */
#include <string.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
/* from the Apache server ... */
#include "httpd.h"
#include "http_config.h"
#include "http_request.h"
#include "http_core.h"
#include "http_log.h"
/* from us ... */
#include "regexp/regexp.h"
#if SUPPORT_DBM_REWRITEMAP
#include "sdbm/sdbm.h"
#endif
/*
**
** our private data structures we handle with
**
*/
/* the list structures for holding the mapfile information
and the rewrite rules */
typedef struct {
char *name;
char *file;
} rewritemap_entry;
typedef struct {
char *pattern;
regexp *regexp;
char *output;
int flags;
char *forced_mimetype;
} rewriterule_entry;
/* the per-server or per-virtual-server configuration
statically generated once on startup for every server */
typedef struct {
int state; /* the RewriteEngine state */
char *rewritelogfile; /* the RewriteLog filename */
int rewritelogfp; /* the RewriteLog open filepointer */
array_header *rewritemaps; /* the RewriteMap entries */
array_header *rewriterules; /* the RewriteRule entries */
} rewrite_server_conf;
/* the per-directory configuration
individually generated on-the-fly by Apache server for current request */
typedef struct {
int state; /* the RewriteEngine state */
char *rewritelogfile; /* the RewriteLog filename */
int rewritelogfp; /* the RewriteLog open filepointer */
array_header *rewritemaps; /* the RewriteMap entries */
array_header *rewriterules; /* the RewriteRule entries */
char *directory; /* the directory where it applies */
} rewrite_perdir_conf;
/*
**
** function forward declarations
**
*/
static command_rec command_table[];
static handler_rec handler_table[];
static void *config_server_create(pool *p, server_rec *s);
static void *config_server_merge(pool *p, void *basev, void *overridesv);
static void *config_perdir_create(pool *p, char *path);
static void *config_perdir_merge(pool *p, void *basev, void *overridesv);
static char *cmd_rewriteengine(cmd_parms *cmd, rewrite_perdir_conf *dconf, int flag);
static char *cmd_rewritelog(cmd_parms *cmd, rewrite_perdir_conf *dconf, char *a1);
static char *cmd_rewritemap(cmd_parms *cmd, rewrite_perdir_conf *dconf, char *a1, char *a2);
static char *cmd_rewriterule(cmd_parms *cmd, rewrite_perdir_conf *dconf, char *str);
static int cmd_rewriterule_parseargline(char *str, char **a1, char **a2, char **a3);
static int cmd_rewriterule_parseflagfield(rewriterule_entry *new, char *str);
static void cmd_rewriterule_setflag(rewriterule_entry *cfg, char *key, char *val);
static int hook_uri2file(request_rec *r);
static int hook_mimetype(request_rec *r);
static int hook_fixup(request_rec *r);
static int apply_rewrite_list(request_rec *r, array_header *rewriterules, int doesc);
static int apply_rewrite_rule(request_rec *r, rewriterule_entry *p);
static int null_handler(request_rec *r);
static void reduce_uri(request_rec *r);
static void splitout_queryargs(request_rec *r);
static int is_this_our_host(request_rec *r, char *testhost);
static char **make_hostname_list(request_rec *r, char *hostname);
static void expand_map_lookups(request_rec *r, char *uri);
static char *lookup_map(request_rec *r, char *name, char *key);
static char *lookup_map_txtfile(request_rec *r, char *file, char *key);
#if SUPPORT_DBM_REWRITEMAP
static char *lookup_map_dbmfile(request_rec *r, char *file, char *key);
#endif
static void expand_tildepaths(request_rec *r, char *uri);
static int partitial_stat(const char *path, struct stat *sb);
static void init_module(server_rec *s, pool *p);
static void open_rewritelog(server_rec *s, pool *p);
static void rewritelog_child(void *cmd);
static void rewritelog(request_rec *r, char *text, ...);
static char *current_logtime(request_rec *r);
/*
**
** our interface to the Apache server kernel
**
** keep in mind:
**
** o Runtime logic of a request:
** while(request or subrequest) {
** foreach(stage #1...#8) {
** foreach(module) { (**)
** try to run hook
** }
** }
** }
**
** o the order of modules is the inverted order as
** given in the Configuration file, i.e. the last module
** specified is the first one called! The core module
** is allways the last!
**
** o there are two different types of result checking and continue processing:
** for hook #1,#3,#4,#5,#7:
** hook run loop stops on first modules which gives
** back a result != DECLINED, i.e. it usually returns OK
** which says "OK, module has handled this _stage_" and for #1
** this have not to mean "Ok, the filename is now valid".
** for hook #2,#6,#8:
** all hooks are run, independend of result
**
** o at the last stage, the core module allways
** - says "BAD_REQUEST" if r->uri does not begin with "/"
** - prefix URL with document_root or replaced server_root
** with document_root and sets r->filename
** - allways return a "OK" independed if the file really exists
** or not!
**
*/
module rewrite_module = {
/* STANDARD_MODULE_STUFF, */ /* use the following if we want to be
allways up-to-date with the Apache
server API ;-) */
19960526, 0, NULL, /* else use this to force the server to only
work with us if we were programmed with
an up-to-date server API in mind! */
init_module, /* module initializer */
config_perdir_create, /* create per-dir config structures */
config_perdir_merge, /* merge per-dir config structures */
config_server_create, /* create per-server config structures */
config_server_merge, /* merge per-server config structures */
command_table, /* table of config file commands */
handler_table, /* [#7] table of MIME-typed-dispatched request action handlers */
hook_uri2file, /* [#1] URI to filename translation */
NULL, /* [#3] check_user_id: get and validate user id from the HTTP request */
NULL, /* [#4] check_auth: check if the user is ok _here_ */
NULL, /* [#2] check_access: check access by host address, etc. */
hook_mimetype, /* [#5] determine MIME type */
hook_fixup, /* [#6] pre-run fixups */
NULL /* [#8] log a transaction */
};
static command_rec command_table[] = {
{ "RewriteEngine", cmd_rewriteengine, NULL, OR_FILEINFO, FLAG,
"On or Off to enable or disable (default) the whole rewriting engine" },
{ "RewriteLog", cmd_rewritelog, NULL, OR_FILEINFO, TAKE1,
"the filename of the rewriting logfile" },
{ "RewriteMap", cmd_rewritemap, NULL, OR_FILEINFO, TAKE2,
"a mapname and a filename" },
{ "RewriteRule", cmd_rewriterule, NULL, OR_FILEINFO, RAW_ARGS,
"a URL-applied regexp-pattern and a substitution URL" },
{ NULL }
};
static handler_rec handler_table[] = {
{ "null-handler", null_handler },
{ NULL }
};
/*
**
** configuration file stuff
**
*/
static void *config_server_create(pool *p, server_rec *s)
{
rewrite_server_conf *a;
a = (rewrite_server_conf *)pcalloc(p, sizeof(rewrite_server_conf));
a->state = ENGINE_DISABLED;
a->rewritelogfile = DEFAULT_REWRITELOGFILE;
a->rewritelogfp = -1;
a->rewritemaps = make_array(p, 2, sizeof(rewritemap_entry));
a->rewriterules = make_array(p, 2, sizeof(rewriterule_entry));
return (void *)a;
}
static void *config_server_merge(pool *p, void *basev, void *overridesv)
{
rewrite_server_conf *a, *base, *overrides;
a = (rewrite_server_conf *)pcalloc(p, sizeof(rewrite_server_conf));
base = (rewrite_server_conf *)basev;
overrides = (rewrite_server_conf *)overridesv;
a->state = overrides->state;
a->rewritelogfile = overrides->rewritelogfile;
a->rewritelogfp = overrides->rewritelogfp;
a->rewritemaps = append_arrays(p, overrides->rewritemaps, base->rewritemaps);
a->rewriterules = append_arrays(p, overrides->rewriterules, base->rewriterules);
return (void *)a;
}
static void *config_perdir_create(pool *p, char *path)
{
rewrite_perdir_conf *a;
a = (rewrite_perdir_conf *)pcalloc(p, sizeof(rewrite_perdir_conf));
a->state = ENGINE_DISABLED;
a->rewritelogfile = DEFAULT_REWRITELOGFILE;
a->rewritelogfp = -1;
a->rewritemaps = make_array(p, 2, sizeof(rewritemap_entry));
a->rewriterules = make_array(p, 2, sizeof(rewriterule_entry));
a->directory = pstrdup(p, path);
return (void *)a;
}
static void *config_perdir_merge(pool *p, void *basev, void *overridesv)
{
rewrite_perdir_conf *a, *base, *overrides;
a = (rewrite_perdir_conf *)pcalloc(p, sizeof(rewrite_perdir_conf));
base = (rewrite_perdir_conf *)basev;
overrides = (rewrite_perdir_conf *)overridesv;
a->state = overrides->state;
a->rewritelogfile = overrides->rewritelogfile;
a->rewritelogfp = overrides->rewritelogfp;
a->rewritemaps = append_arrays(p, overrides->rewritemaps, base->rewritemaps);
a->rewriterules = append_arrays(p, overrides->rewriterules, base->rewriterules);
a->directory = overrides->directory;
return (void *)a;
}
static char *cmd_rewriteengine(cmd_parms *cmd, rewrite_perdir_conf *dconf, int flag)
{
rewrite_server_conf *sconf;
sconf = (rewrite_server_conf *)get_module_config(cmd->server->module_config, &rewrite_module);
if (cmd->path == NULL) { /* is server command */
if (flag)
sconf->state = ENGINE_ENABLED;
else
sconf->state = ENGINE_DISABLED;
}
else { /* is per-directory command */
if (flag)
dconf->state = ENGINE_ENABLED;
else
dconf->state = ENGINE_DISABLED;
}
return NULL;
}
static char *cmd_rewritelog(cmd_parms *cmd, rewrite_perdir_conf *dconf, char *a1)
{
rewrite_server_conf *sconf;
sconf = (rewrite_server_conf *)get_module_config(cmd->server->module_config, &rewrite_module);
if (cmd->path == NULL) /* is server command */
sconf->rewritelogfile = a1;
else /* is per-directory command */
dconf->rewritelogfile = a1;
return NULL;
}
static char *cmd_rewritemap(cmd_parms *cmd, rewrite_perdir_conf *dconf, char *a1, char *a2)
{
rewrite_server_conf *sconf;
rewritemap_entry *new;
sconf = (rewrite_server_conf *)get_module_config(cmd->server->module_config, &rewrite_module);
if (cmd->path == NULL) /* is server command */
new = push_array(sconf->rewritemaps);
else /* is per-directory command */
new = push_array(dconf->rewritemaps);
new->name = a1;
new->file = a2;
return NULL;
}
static char *cmd_rewriterule(cmd_parms *cmd, rewrite_perdir_conf *dconf, char *str)
{
rewrite_server_conf *sconf;
rewriterule_entry *new;
regexp *regexp;
int i;
char *a1;
char *a2;
char *a3;
sconf = (rewrite_server_conf *)get_module_config(cmd->server->module_config, &rewrite_module);
/* make a new entry in the internal rewrite rule list */
if (cmd->path == NULL) /* is server command */
new = push_array(sconf->rewriterules);
else /* is per-directory command */
new = push_array(dconf->rewriterules);
/* parse the argument line ourself */
if (cmd_rewriterule_parseargline(str, &a1, &a2, &a3)) {
fprintf(stderr, "RewriteRule: bad argument line\n");
exit(1);
}
/* arg1: the pattern
try to compile the regexp to test if is ok */
if ((regexp = regcomp(a1)) == NULL) {
fprintf(stderr, "RewriteRule: cannot compile regular expression '%s'\n", a1);
exit(1);
}
new->pattern = strdup(a1);
new->regexp = regexp;
/* arg2: the output string
replace the $<N> by \<n> which is needed by the currently
used Regular Expression library */
for (i = 0; a2[i] != '\0'; i++) {
if (a2[i] == '$' && a2[i+1] >= '1' && a2[i+1] <= '9')
a2[i] = '\\';
}
new->output = strdup(a2);
/* arg3: optional flags field */
new->flags = REWRITEFLAG_NONE;
new->forced_mimetype = NULL;
if (a3 != NULL) {
if (cmd_rewriterule_parseflagfield(new, a3)) {
fprintf(stderr, "RewriteRule: bad flags field '%s'\n", str);
exit(1);
}
}
return NULL;
}
static int cmd_rewriterule_parseargline(char *str, char **a1, char **a2, char **a3)
{
char *cp;
/* strip whitespaces */
for (cp = str; *cp != '\0' && (*cp == ' ' || *cp == '\t'); cp++)
;
/* determine first argument */
*a1 = cp;
for ( ; *cp != '\0'; cp++) {
if (*cp == '\\' && *(cp+1) == ' ') {
cp++;
continue;
}
if (*cp == ' ' || *cp == '\t')
break;
}
if (*cp == '\0')
return 1;
*cp++ = '\0';
/* strip whitespaces */
for ( ; *cp != '\0' && (*cp == ' ' || *cp == '\t'); cp++)
;
/* determine second argument */
*a2 = cp;
for ( ; *cp != '\0'; cp++) {
if (*cp == '\\' && *(cp+1) == ' ') {
cp++;
continue;
}
if (*cp == ' ' || *cp == '\t')
break;
}
if (*cp == '\0') {
/* there were only two args */
*cp++ = '\0';
*a3 = NULL;
return 0;
}
*cp++ = '\0';
/* strip whitespaces */
for ( ; *cp != '\0' && (*cp == ' ' || *cp == '\t'); cp++)
;
/* again check if there are only two arguments */
if (*cp == '\0') {
*cp++ = '\0';
*a3 = NULL;
return 0;
}
/* determine second argument */
*a3 = cp;
for ( ; *cp != '\0'; cp++) {
if (*cp == '\\' && *(cp+1) == ' ') {
cp++;
continue;
}
if (*cp == ' ' || *cp == '\t')
break;
}
*cp++ = '\0';
return 0;
}
static int cmd_rewriterule_parseflagfield(rewriterule_entry *cfg, char *str)
{
char *cp;
char *cp1;
char *cp2;
char *cp3;
char *key;
char *val;
if (str[0] != '[' || str[strlen(str)-1] != ']')
return 1;
cp = str+1;
str[strlen(str)-1] = ','; /* for simpler parsing */
for ( ; *cp != '\0'; ) {
/* skip whitespaces */
for ( ; (*cp == ' ' || *cp == '\t') && *cp != '\0'; cp++)
;
if (*cp == '\0')
break;
cp1 = cp;
if ((cp2 = strchr(cp, ',')) != NULL) {
cp = cp2+1;
for ( ; (*(cp2-1) == ' ' || *(cp2-1) == '\t'); cp2--)
;
*cp2 = '\0';
if ((cp3 = strchr(cp1, '=')) != NULL) {
*cp3 = '\0';
key = cp1;
val = cp3+1;
}
else {
key = cp1;
val = "yes";
}
cmd_rewriterule_setflag(cfg, key, val);
}
else
break;
}
return 0;
}
static void cmd_rewriterule_setflag(rewriterule_entry *cfg, char *key, char *val)
{
if ( strcasecmp(key, "redirect") == 0
|| strcasecmp(key, "R") == 0 ) {
cfg->flags |= REWRITEFLAG_FORCEREDIRECT;
}
else if ( strcasecmp(key, "last") == 0
|| strcasecmp(key, "L") == 0 ) {
cfg->flags |= REWRITEFLAG_LASTRULE;
}
else if ( strcasecmp(key, "next") == 0
|| strcasecmp(key, "N") == 0 ) {
cfg->flags |= REWRITEFLAG_NEWROUND;
}
else if ( strcasecmp(key, "chain") == 0
|| strcasecmp(key, "C") == 0 ) {
cfg->flags |= REWRITEFLAG_CHAIN;
}
else if ( strcasecmp(key, "type") == 0
|| strcasecmp(key, "T") == 0 ) {
cfg->forced_mimetype = strdup(val);
}
else if ( strcasecmp(key, "nosubreq") == 0
|| strcasecmp(key, "NS") == 0 ) {
cfg->flags |= REWRITEFLAG_IGNOREONSUBREQ;
}
else {
fprintf(stderr, "RewriteRule: unknown flag '%s'\n", key);
exit(1);
}
return;
}
/*
**
** Global RewriteRule runtime engine
**
*/
static int hook_uri2file(request_rec *r)
{
void *sconf;
rewrite_server_conf *conf;
char *var;
char *thisserver, *thisport, *thisurl;
char buf[512];
char *cp, *cp2;
struct stat finfo;
int n;
/*
* retrieve the config structures
*/
sconf = r->server->module_config;
conf = (rewrite_server_conf *)get_module_config(sconf, &rewrite_module);
/*
* only do something under runtime if the engine is really enabled,
* else return immediately!
*/
if (conf->state == ENGINE_DISABLED)
return DECLINED;
/*
* add the SCRIPT_URL variable to the env. this is a bit complicated
* due to the fact that apache uses subrequests and internal redirects
*/
if (r->main == NULL) {
var = pstrcat(r->pool, "REDIRECT_", ENVVAR_SCRIPT_URL, NULL);
var = table_get(r->subprocess_env, var);
if (var == NULL)
table_set(r->subprocess_env, ENVVAR_SCRIPT_URL, pstrdup(r->pool, r->uri));
else
table_set(r->subprocess_env, ENVVAR_SCRIPT_URL, pstrdup(r->pool, var));
}
else {
var = table_get(r->main->subprocess_env, ENVVAR_SCRIPT_URL);
table_set(r->subprocess_env, ENVVAR_SCRIPT_URL, pstrdup(r->pool, var));
}
/*
* create the SCRIPT_URI variable for the env
*/
/* add the canonical URI of this URL */
thisserver = r->server->server_hostname;
if (r->server->port == 80)
thisport = "";
else {
sprintf(buf, ":%d", r->server->port);
thisport = pstrdup(r->pool, buf);
}
thisurl = table_get(r->subprocess_env, ENVVAR_SCRIPT_URL);
/* set the variable */
var = pstrcat(r->pool, "http://", thisserver, thisport, thisurl, NULL);
table_set(r->subprocess_env, ENVVAR_SCRIPT_URI, pstrdup(r->pool, var));
/*
* now apply the rules ...
*/
if (apply_rewrite_list(r, conf->rewriterules, 0)) {
if (strncmp(r->uri, "http://", 7) == 0) {
/* it was finally rewritten to a remote path */
for (cp = r->uri+7; *cp != '/' && *cp != '\0'; cp++)
;
if (*cp != '\0') {
rewritelog(r, "escaping %s for redirect", r->uri);
cp2 = escape_uri(r->pool, cp);
*cp = '\0';
r->uri = pstrcat(r->pool, r->uri, cp2, NULL);
}
table_set(r->headers_out, "Location", r->uri);
rewritelog(r, "redirect to %s [REDIRECT]", r->uri);
return REDIRECT;
}
else {
/* it was finally rewritten to a local path */
r->filename = pstrdup(r->pool, r->uri);
n = partitial_stat(r->filename, &finfo);
if (n == strlen(r->filename)) {
/* it does completely exists, i.e. the whole filename is valid */
/* we do a real stat again to be sure that the r->finfo
gets a correct value too. And we return "OK" to
stop the URI->filename translation phase here. */
stat(r->filename, &r->finfo);
rewritelog(r, "go-ahead with (valid) %s [OK]", r->uri);
return OK;
}
else if (n > 0 && r->main == NULL) {
/* only a partitial part is valid */
/* we don't do a real stat because
the complete filename is not valid.
Instead we mark the filename as invalid
but return "OK" to prevent the core module
from doing any more processing... */
r->finfo.st_mode = 0;
rewritelog(r, "go-ahead with (partitial valid) %s [OK]", r->uri);
return OK;
}
else /* n == 0 */ {
/* it doesn't exist, not even a partitial prefix path does exist */
/* a explicit mark as invalid for the filename
and a return of "DECLINED" is the best we can do.
Notice: the uri/filename is still rewritten!
Sometimes that could be enough... because
of the "DECLINED" the core module will now
prefix it with document_root and says "OK" which
could be really ok... */
r->finfo.st_mode = 0;
rewritelog(r, "go-ahead with (invalid) %s [DECLINED]", r->uri);
return DECLINED;
}
}
}
else {
rewritelog(r, "pass through %s", r->uri);
return DECLINED;
}
}
static int apply_rewrite_list(request_rec *r, array_header *rewriterules, int doesc)
{
rewriterule_entry *entries;
rewriterule_entry *p;
int i;
int changed;
entries = (rewriterule_entry *)rewriterules->elts;
changed = 0;
loop:
for (i = 0; i < rewriterules->nelts; i++) {
p = &entries[i];
if ( p->flags & REWRITEFLAG_IGNOREONSUBREQ
&& r->main != NULL )
continue;
if (apply_rewrite_rule(r, p)) {
changed = 1;
if (p->flags & REWRITEFLAG_LASTRULE)
break;
if (p->flags & REWRITEFLAG_NEWROUND)
goto loop;
}
else {
/* if current rule is chained with next rule(s),
skip all this next rule(s) */
while ( i < rewriterules->nelts
&& p->flags & REWRITEFLAG_CHAIN) {
i++;
p = &entries[i];
}
}
}
return changed;
}
static int apply_rewrite_rule(request_rec *r, rewriterule_entry *p)
{
int changed;
char *uri;
char *output;
int flags;
char newuri[MAX_STRING_LEN];
regexp *regexp;
uri = r->uri;
regexp = p->regexp;
output = p->output;
flags = p->flags;
changed = 0;
if (regexec(regexp, uri)) { /* try to match the pattern */
regsub(regexp, output, newuri); /* substitute in output */
expand_map_lookups(r, newuri); /* expand ${...} */
expand_tildepaths(r, newuri); /* expand ~user prefix */
rewritelog(r, "rewrite %s -> %s", r->uri, newuri);
r->uri = pstrdup(r->pool, newuri);
/* reduce http://<ourhost> */
reduce_uri(r);
/* split out on-the-fly generated QUERY_STRING '....?xxxxx&xxxx...' */
splitout_queryargs(r);
/* if a MIME-type shoudl be later forced for this URL, then remeber this */
if (p->forced_mimetype != NULL) {
table_set(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR, p->forced_mimetype);
rewritelog(r, "remember %s to have MIME-type '%s'", r->uri, p->forced_mimetype);
}
/* if we are forced to do a explicit redirect
finally prefix the new URI with http://<ourname> explicitly */
if (flags & REWRITEFLAG_FORCEREDIRECT) {
if (strncmp(r->uri, "http://", 7) != 0) {
sprintf(newuri, "http://%s%s", r->server->server_hostname, r->uri);
rewritelog(r, "prepare forced redirect %s -> %s", r->uri, newuri);
r->uri = pstrdup(r->pool, newuri);
}
}
changed = 1;
}
return changed;
}
/*
**
** forced MIME type support
**
*/
static int hook_mimetype(request_rec *r)
{
char *t;
/* now check if we have to force a MIME-type */
t = table_get(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR);
if (t == NULL)
return DECLINED;
else {
rewritelog(r, "force filename %s to have MIME-type '%s'", r->filename, t);
r->content_type = t;
return OK;
}
}
/*
**
** Per-Directory RewriteRule runtime engine
**
*/
static int hook_fixup(request_rec *r)
{
rewrite_perdir_conf *dconf;
char *cp;
char *cp2;
struct stat finfo;
int n;
dconf = (rewrite_perdir_conf *)get_module_config(r->per_dir_config, &rewrite_module);
/* if there is no per-dir config we return immediately */
if (dconf == NULL)
return DECLINED;
/* we shouldn't do anything in subrequests */
if (r->main != NULL)
return DECLINED;
/* if there are no real (i.e. no RewriteRule directives!)
per-dir config of us, we return also immediately */
if (dconf->directory == NULL)
return DECLINED;
/*
* only do something under runtime if the engine is really enabled,
* for this directory, else return immediately!
*/
if (dconf->state == ENGINE_DISABLED)
return DECLINED;
/*
* now apply the rules ...
*/
if (apply_rewrite_list(r, dconf->rewriterules, 0)) {
if (strncmp(r->uri, "http://", 7) == 0) {
/* it was finally rewritten to a remote path */
for (cp = r->uri+7; *cp != '/' && *cp != '\0'; cp++)
;
if (*cp != '\0') {
rewritelog(r, "[per-dir %s] escaping %s for redirect", dconf->directory, r->uri);
cp2 = escape_uri(r->pool, cp);
*cp = '\0';
r->uri = pstrcat(r->pool, r->uri, cp2, NULL);
}
table_set(r->headers_out, "Location", r->uri);
rewritelog(r, "[per-dir %s] redirect to %s [REDIRECT]", dconf->directory, r->uri);
return REDIRECT;
}
else {
/* it was finally rewritten to a local path */
r->filename = pstrdup(r->pool, r->uri);
n = partitial_stat(r->filename, &finfo);
if (n == strlen(r->filename)) {
/* it does completely exists, i.e. the whole filename is valid */
/* we do a real stat again to be sure that the r->finfo
gets a correct value too. And we return "OK" to
stop the URI->filename translation phase here. */
stat(r->filename, &r->finfo);
/* now do a _internal_ redirect instead just
returning OK. This is needed because at this
time (hook_fixup) when a rewrite occured we need to
restart the processing from the top (hook_uri2file). If we
would return just OK the redirected file will be served
_AND_ the current request (which could lead to an error
message) ! */
rewritelog(r, "[per-dir %s] internal redirect with (valid) %s [INTERNAL REDIRECT]", dconf->directory, r->uri);
internal_redirect(pstrcat(r->pool, r->uri, r->args ? "?" : NULL, r->args, NULL), r);
/* ok, now make sure that the current request
will not get served, too! We do this by using
an own null handler. */
r->filename = pstrcat(r->pool, "null:", r->uri, NULL);
r->handler = "null-handler";
return OK;
}
else if (n > 0 && r->main == NULL) {
/* only a partitial part is valid */
/* we don't do a real stat because
the complete filename is not valid.
Instead we mark the filename as invalid
but return "OK" to prevent the core module
from doing any more processing... */
r->finfo.st_mode = 0;
/* now do a _internal_ redirect instead just
returning OK. This is needed because at this
time (hook_fixup) when a rewrite occured we need to
restart the processing from the top (hook_uri2file). If we
would return just OK the redirected file will be served
_AND_ the current request (which could lead to an error
message) ! */
rewritelog(r, "[per-dir %s] internal redirect with (partitial valid) %s [INTERNAL REDIRECT]", dconf->directory, r->uri);
internal_redirect(pstrcat(r->pool, r->uri, r->args ? "?" : NULL, r->args, NULL), r);
/* ok, now make sure that the current request
will not get served, too! We do this by using
an own null handler. */
r->filename = pstrcat(r->pool, "null:", r->uri, NULL);
r->handler = "null-handler";
return OK;
}
else /* n == 0 */ {
/* it doesn't exist, not even a partitial prefix path does exist */
/* a explicit mark as invalid for the filename
and a return of "DECLINED" is the best we can do.
Notice: the uri/filename is still rewritten!
Sometimes that could be enough... because
of the "DECLINED" the core module will now
prefix it with document_root and says "OK" which
could be really ok... */
r->finfo.st_mode = 0;
rewritelog(r, "[per-dir %s] go-ahead with (invalid) %s [DECLINED]", dconf->directory, r->uri);
return DECLINED;
}
}
}
else {
rewritelog(r, "[per-dir %s] pass through %s", dconf->directory, r->uri);
return DECLINED;
}
}
/*
**
** real handlers
**
*/
/* the null handler - it does nothing! This is just be able
to silently finish a request which was already served by
a subrequest, i.e. to prevent to output another request */
static int null_handler(request_rec *r)
{
/* just make sure that we are really wanted! */
if (strncmp(r->filename, "null:", 5) != 0)
return DECLINED;
/* ok, we do nothing and return immediately,
which will cause the current request to be finished
without any HTTP header or body output, etc.
Only action which still happen: the logging... */
return OK;
}
/*
**
** DNS hostname check
**
*/
static int is_this_our_host(request_rec *r, char *testhost)
{
char **cppHNLour;
char **cppHNLtest;
int i, j;
if ((cppHNLour = make_hostname_list(r, r->server->server_hostname)) == NULL)
return 0;
if ((cppHNLtest = make_hostname_list(r, testhost)) == NULL)
return 0;
for (i = 0; cppHNLtest[i] != NULL; i++) {
for (j = 0; cppHNLour[j] != NULL; j++) {
if (strcmp(cppHNLtest[i], cppHNLour[j]) == 0) {
return 1;
}
}
}
return 0;
}
static char **make_hostname_list(request_rec *r, char *hostname)
{
char **cppHNL;
struct hostent *hep;
int i;
if ((hep = gethostbyname(hostname)) == NULL)
return NULL;
for (i = 0; hep->h_aliases[i]; i++)
;
cppHNL = (char **)palloc(r->pool, sizeof(char *)*(i+2));
cppHNL[0] = pstrdup(r->pool, hep->h_name);
for (i = 0; hep->h_aliases[i]; i++)
cppHNL[1+i] = pstrdup(r->pool, hep->h_aliases[i]);
cppHNL[1+i] = NULL;
return cppHNL;
}
static void reduce_uri(request_rec *r)
{
char *a;
char *e;
int n;
char hostname[MAX_STRING_LEN];
char olduri[MAX_STRING_LEN];
if (strncmp(r->uri, "http://", 7) == 0) {
/* there was a rewrite to a remote path */
strcpy(olduri, r->uri); /* save for logging */
/* cut the hostname out of the URI */
a = r->uri+7;
e = strchr(a, '/');
if (e == NULL)
e = a+strlen(a);
n = e-a;
memcpy(hostname, a, n);
hostname[n] = '\0';
/* check whether we could reduce it to a local path... */
if (is_this_our_host(r, hostname)) {
/* this is our host, so remove http://ourhostname/ */
if (*e == '\0')
r->uri = pstrdup(r->pool, "/");
else
r->uri = pstrdup(r->pool, e);
rewritelog(r, "reduce %s -> %s", olduri, r->uri);
}
}
return;
}
static void splitout_queryargs(request_rec *r)
{
char *q;
char olduri[MAX_STRING_LEN];
q = strchr(r->uri, '?');
if (q != NULL) {
strcpy(olduri, r->uri);
*q++ = '\0';
r->args = pstrcat(r->pool, q, "&", r->args, NULL);
if (r->args[strlen(r->args)-1] == '&')
r->args[strlen(r->args)-1] = '\0';
rewritelog(r, "split uri=%s -> uri=%s, args=%s", olduri, r->uri, r->args);
}
return;
}
/*
**
** mapfile expansion support
** i.e. expansion of MAP lookup directives
** ${<mapname>:<key>} in RewriteRule rhs
**
*/
static void expand_map_lookups(request_rec *r, char *uri)
{
char newuri[MAX_STRING_LEN];
char *cpI;
char *cpIE;
char *cpO;
char *cpT;
char *cpT2;
char mapname[MAX_STRING_LEN];
char mapkey[MAX_STRING_LEN];
char defaultvalue[MAX_STRING_LEN];
int n;
cpI = uri;
cpIE = cpI+strlen(cpI);
cpO = newuri;
while (cpI < cpIE) {
if (cpI+6 < cpIE && strncmp(cpI, "${", 2) == 0) {
/* missing delimiter -> take it as plain text */
if ( strchr(cpI+2, ':') == NULL
|| strchr(cpI+2, '}') == NULL) {
memcpy(cpO, cpI, 2);
cpO += 2;
cpI += 2;
continue;
}
cpI += 2;
cpT = strchr(cpI, ':');
n = cpT-cpI;
memcpy(mapname, cpI, n);
mapname[n] = '\0';
cpI += n+1;
cpT2 = strchr(cpI, '|');
cpT = strchr(cpI, '}');
if (cpT2 != NULL && cpT2 < cpT) {
n = cpT2-cpI;
memcpy(mapkey, cpI, n);
mapkey[n] = '\0';
cpI += n+1;
n = cpT-cpI;
memcpy(defaultvalue, cpI, n);
defaultvalue[n] = '\0';
cpI += n+1;
}
else {
n = cpT-cpI;
memcpy(mapkey, cpI, n);
mapkey[n] = '\0';
cpI += n+1;
defaultvalue[0] = '\0';
}
cpT = lookup_map(r, mapname, mapkey);
if (cpT != NULL) {
n = strlen(cpT);
memcpy(cpO, cpT, n);
cpO += n;
}
else {
n = strlen(defaultvalue);
memcpy(cpO, defaultvalue, n);
cpO += n;
}
}
else {
cpT = strstr(cpI, "${");
if (cpT == NULL)
cpT = cpI+strlen(cpI);
n = cpT-cpI;
memcpy(cpO, cpI, n);
cpO += n;
cpI += n;
}
}
*cpO = '\0';
strcpy(uri, newuri);
return;
}
static char *lookup_map(request_rec *r, char *name, char *key)
{
void *sconf;
rewrite_server_conf *conf;
array_header *rewritemaps;
rewritemap_entry *entries;
rewritemap_entry *s;
char *value;
int i;
/* get map configuration */
sconf = r->server->module_config;
conf = (rewrite_server_conf *)get_module_config(sconf, &rewrite_module);
rewritemaps = conf->rewritemaps;
entries = (rewritemap_entry *)rewritemaps->elts;
for (i = 0; i < rewritemaps->nelts; i++) {
s = &entries[i];
if (strcmp(s->name, name) == 0) {
if (strlen(s->file) > 4 && strncmp(s->file, "txt:", 4) == 0) {
if ((value = lookup_map_txtfile(r, s->file+4, key)) != NULL)
return value;
}
else if (strlen(s->file) > 4 && strncmp(s->file, "dbm:", 4) == 0) {
#if SUPPORT_DBM_REWRITEMAP
if ((value = lookup_map_dbmfile(r, s->file+4, key)) != NULL)
return value;
#else
return NULL;
#endif
}
else {
if ((value = lookup_map_txtfile(r, s->file, key)) != NULL)
return value;
}
}
}
return NULL;
}
#define PATTERN "^([^ ]+) +([^ ]+).*$"
#define OUTPUT "\\1,\\2"
static regexp *lookup_map_txtfile_regexp = NULL;
static char *lookup_map_txtfile(request_rec *r, char *file, char *key)
{
FILE *fp = NULL;
char line[1024];
char output[1024];
char result[1024];
char *value = NULL;
char *cpT;
char *curkey;
char *curval;
if ((fp = pfopen(r->pool, file, "r")) == NULL)
return NULL;
if (lookup_map_txtfile_regexp == NULL)
lookup_map_txtfile_regexp = regcomp(PATTERN);
strcpy(output, OUTPUT);
while (fgets(line, sizeof(line), fp) != NULL) {
if (line[strlen(line)-1] == '\n')
line[strlen(line)-1] = '\0';
if (regexec(lookup_map_txtfile_regexp, line)) {
regsub(lookup_map_txtfile_regexp, output, result);
cpT = strchr(result, ',');
*cpT = '\0';
curkey = result;
curval = cpT+1;
if (strcmp(curkey, key) == 0) {
value = pstrdup(r->pool, curval);
break;
}
}
}
pfclose(r->pool, fp);
return value;
}
#if SUPPORT_DBM_REWRITEMAP
static char *lookup_map_dbmfile(request_rec *r, char *file, char *key)
{
DBM *dbmfp = NULL;
datum dbmkey;
datum dbmval;
char *value = NULL;
char buf[MAX_STRING_LEN];
dbmkey.dptr = key;
dbmkey.dsize = strlen(key)+1;
if ((dbmfp = dbm_open(file, O_RDONLY, 0666)) != NULL) {
dbmval = dbm_fetch(dbmfp, dbmkey);
if (dbmval.dptr != NULL) {
memcpy(buf, dbmval.dptr, dbmval.dsize);
buf[dbmval.dsize] = '\0';
value = pstrdup(r->pool, buf);
}
dbm_close(dbmfp);
}
return value;
}
#endif
static void expand_tildepaths(request_rec *r, char *uri)
{
char newuri[MAX_STRING_LEN];
char user[MAX_STRING_LEN];
struct passwd *pw;
int i, j;
if (uri != NULL && strlen(uri) > 2 && uri[0] == '/' && uri[1] == '~') {
/* cut out the username */
for (j = 0, i = 2; uri[i] != '\0' &&
( (uri[i] >= '0' && uri[i] <= '9')
|| (uri[i] >= 'a' && uri[i] <= 'z')
|| (uri[i] >= 'A' && uri[i] <= 'Z')); )
user[j++] = uri[i++];
user[j] = '\0';
/* lookup username in systems passwd file */
if ((pw = getpwnam(user)) != NULL) {
/* ok, user was found, so expand the ~user string */
if (uri[i] != '\0') {
/* ~user/anything... has to be expanded */
if (pw->pw_dir[strlen(pw->pw_dir)-1] == '/')
pw->pw_dir[strlen(pw->pw_dir)-1] = '\0';
sprintf(newuri, "%s%s", pw->pw_dir, uri+i);
strcpy(uri, newuri);
}
else {
/* only ~user has to be expanded */
strcpy(uri, pw->pw_dir);
}
}
}
return;
}
/* partitial stat() function which does
a interative stat on the remaining path while
stripping of a dir/file part at the end of the path */
static int partitial_stat(const char *path, struct stat *sb)
{
char curpath[MAX_STRING_LEN];
char *cp;
int n;
strcpy(curpath, path);
while (1) {
n = strlen(curpath);
if (stat(curpath, sb) == 0) {
return n;
}
if ((cp = strrchr(curpath, '/')) == NULL)
break;
*cp = '\0';
}
return 0;
}
/*
**
** RewriteLog support
**
*/
static void init_module(server_rec *s, pool *p)
{
for (; s; s = s->next)
open_rewritelog(s, p);
}
static void open_rewritelog(server_rec *s, pool *p)
{
rewrite_server_conf *conf;
char *fname;
FILE *fp;
static int rewritelog_flags = ( O_WRONLY|O_APPEND|O_CREAT );
static mode_t rewritelog_mode = ( S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH );
conf = get_module_config(s->module_config, &rewrite_module);
if (conf->rewritelogfile == NULL)
return;
if (*(conf->rewritelogfile) == '\0')
return;
if (conf->rewritelogfp > 0)
return; /* virtual log shared w/main server */
fname = server_root_relative(p, conf->rewritelogfile);
if (*conf->rewritelogfile == '|') {
spawn_child(p, rewritelog_child, (void *)(conf->rewritelogfile+1),
kill_after_timeout, &fp, NULL);
if (fp == NULL) {
fprintf (stderr, "Couldn't fork child for RewriteLog process\n");
exit (1);
}
conf->rewritelogfp = fileno(fp);
}
else if (*conf->rewritelogfile != '\0') {
if ((conf->rewritelogfp = popenf(p, fname, rewritelog_flags, rewritelog_mode)) < 0) {
fprintf(stderr, "httpd: could not open rewrite log file %s.\n", fname);
perror("open");
exit(1);
}
}
}
/* Child process code for 'RewriteLog "|..."' */
static void rewritelog_child(void *cmd)
{
cleanup_for_exec();
signal(SIGHUP, SIG_IGN);
execl(SHELL_PATH, SHELL_PATH, "-c", (char *)cmd, NULL);
exit(1);
}
static void rewritelog(request_rec *r, char *text, ...)
{
rewrite_server_conf *conf;
conn_rec *connect;
char *str1;
char str2[HUGE_STRING_LEN];
char str3[HUGE_STRING_LEN];
char type[20];
char redir[20];
va_list ap;
int i;
request_rec *req;
va_start(ap, text);
conf = get_module_config(r->server->module_config, &rewrite_module);
connect = r->connection;
if (conf->rewritelogfp <0)
return;
if (conf->rewritelogfile == NULL)
return;
if (*(conf->rewritelogfile) == '\0')
return;
str1 = pstrcat(r->pool, get_remote_host(connect, r->server->module_config, REMOTE_NAME), " ",
(connect->remote_logname != NULL ? connect->remote_logname : "-"), " ",
(connect->user != NULL ? connect->user : "-"),
NULL);
vsprintf(str2, text, ap);
if (r->main == NULL)
strcpy(type, "initial");
else
strcpy(type, "subreq");
for (i = 0, req = r->prev; req != NULL; req = req->prev)
;
if (i == 0)
strcpy(redir, "");
else
sprintf(redir, "/redir#%d", i);
sprintf(str3, "%s %s [id#%x/%s%s] %s\n", str1, current_logtime(r), (unsigned int)r, type, redir, str2);
write(conf->rewritelogfp, str3, strlen(str3));
va_end(ap);
return;
}
static char *current_logtime(request_rec *r)
{
long timz;
struct tm *t;
char tstr[MAX_STRING_LEN], sign;
t = get_gmtoff(&timz);
sign = (timz < 0 ? '-' : '+');
if(timz < 0)
timz = -timz;
strftime(tstr, MAX_STRING_LEN,"[%d/%b/%Y:%H:%M:%S ",t);
sprintf(tstr + strlen(tstr), "%c%02ld%02ld]", sign, timz/3600, timz%3600);
return pstrdup(r->pool, tstr);
}
/*EOF*/
------