3

Why can't I use mod_rewrite rules similar to this:

RewriteEngine On
RewriteCond %{ENV:did_rewrite} !=true
RewriteCond %{REQUEST_URI} ^(.*)/
RewriteRule (.*) %1/foo.php?original=$1 [E=did_rewrite:true]

To prevent recursion?

When I turn up the RewriteLogLevel, I see:

[.../initial] (3) [perdir /.../test/] strip per-dir prefix: /.../test/stuff -> stuff
[.../initial] (3) [perdir /.../test/] applying pattern '(.*)' to uri 'stuff'
[.../initial] (4) [perdir /.../test/] RewriteCond: input='' pattern='!=true' => matched
[.../initial] (4) [perdir /.../test/] RewriteCond: input='/test/stuff' pattern='(.*)/' => matched
[.../initial] (2) [perdir /.../test/] rewrite 'stuff' -> '/test/foo.php?original=stuff'
[.../initial] (5) setting env variable 'did_rewrite' to 'true'
[.../initial] (3) split uri=/test/foo.php?original=stuff -> uri=/test/foo.php, args=original=stuff
[.../initial] (1) [perdir /.../test/] internal redirect with /test/foo.php [INTERNAL REDIRECT]
[.../initial/redir#1] (3) [perdir /.../test/] strip per-dir prefix: /.../test/foo.php -> foo.php
[.../initial/redir#1] (3) [perdir /.../test/] applying pattern '(.*)' to uri 'foo.php'
[.../initial/redir#1] (4) [perdir /.../test/] RewriteCond: input='' pattern='!=true' => matched
[.../initial/redir#1] (4) [perdir /.../test/] RewriteCond: input='/test/foo.php' pattern='(.*)/' => matched
[.../initial/redir#1] (2) [perdir /.../test/] rewrite 'foo.php' -> '/test/foo.php?original=foo.php'
...

It seems like, on the "recursive" call, the environment variable somehow becomes un-set… But I can't figure out why that would happen.

To be clear: I know there are a bunch of ways to prevent this kind of recursion. I'd like to understand why this particular way doesn't work.

David Wolever
  • 148,955
  • 89
  • 346
  • 502
  • I don't have an answer, but their `LA-U` stuff makes it look like their "state" is pretty loosely controlled during processing. FWIW I always to try make sure any rule that might accidentally recurse can be last in processing so I can use `[L]` and _know_ it won't trigger anything unintentionally. – Ry4an Brase Feb 22 '12 at 23:39
  • The mod_rewrite [L] flag behaves differently in the global rules file and in individual .htaccess files. In a .htaccess file, [L] loops back to the beginning to make sure there are no further changes - exactly what you do NOT want if you have a recursion issue. If you really want your recipe in .htaccess to stop without any chance of recursion, use the [END] flag instead. – Chuck Kollars Apr 29 '21 at 11:02

1 Answers1

8

I think this blog entry may have your answer. In summary, when Apache performs an internal redirect, it renames all of the environment variables:

static apr_table_t *rename_original_env(apr_pool_t *p, apr_table_t *t)
{
    const apr_array_header_t *env_arr = apr_table_elts(t);
    const apr_table_entry_t *elts = (const apr_table_entry_t *) env_arr->elts;
    apr_table_t *new = apr_table_make(p, env_arr->nalloc);
    int i;

    for (i = 0; i < env_arr->nelts; ++i) {
        if (!elts[i].key)
            continue;
        apr_table_setn(new, apr_pstrcat(p, "REDIRECT_", elts[i].key, NULL),
                  elts[i].val);
    }

    return new;
}

Unfortunately, this doesn't seem to be documented anywhere.

matthewwithanm
  • 3,733
  • 2
  • 21
  • 27
  • 1
    So you can use the variable, but you have to look for it with a REDIRECT_ prefix. You can understand why they would do this. It simulates dynamic scoping with scope resolution. In the recursed context you can ask for REDIRECT_FOO to see whether a FOO variable exists in the parent environment. And presumably REDIRECT_REDIRECT_FOO ... This can be used as a nice hack to distinguish whether or not you have arrived at some rule after an internal redirect or directly. I will be sure to try this. Darn useful piece of knowledge; thanks. – Kaz Mar 12 '12 at 04:33
  • I tried using this answer but can't make it work. I need more details about using an .htaccess file to redirect all Apache accesses to a PHP file, which in turn needs to call Apache to serve a file. For example, to visit file 'abc', the PHP file will change the filename to 'abc.php' and redirect to it so the PHP file can map it further. But without a recursion test, the .htaccess/PHP loop never ends and the file is never served. There's no room here for my details. – David Spector Jul 06 '21 at 16:29