static int hook_fixup()

in modules/mappers/mod_rewrite.c [5240:5610]


static int hook_fixup(request_rec *r)
{
    rewrite_perdir_conf *dconf;
    char *cp;
    char *cp2;
    const char *ccp;
    apr_size_t l;
    int rulestatus;
    int n;
    char *ofilename, *oargs;
    int is_proxyreq;
    void *skipdata;
    rewriterule_entry *lastsub;

    dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config,
                                                        &rewrite_module);

    /* if there is no per-dir config we return immediately */
    if (dconf == 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;
    }

    /* 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;
    }

    /*
     * Proxy request?
     */
    is_proxyreq = (   r->proxyreq && r->filename
                   && !strncmp(r->filename, "proxy:", 6));

    /*
     *  .htaccess file is called before really entering the directory, i.e.:
     *  URL: http://localhost/foo  and .htaccess is located in foo directory
     *  Ignore such attempts, allowing mod_dir to direct the client to the
     *  canonical URL. This can be controlled with the AllowNoSlash option.
     */
    if (!is_proxyreq && !(dconf->options & OPTION_NOSLASH)) {
        l = strlen(dconf->directory) - 1;
        if (r->filename && strlen(r->filename) == l &&
            (dconf->directory)[l] == '/' &&
            !strncmp(r->filename, dconf->directory, l)) {
            return DECLINED;
        }
    }

    /* END flag was used as a RewriteRule flag on this request */
    apr_pool_userdata_get(&skipdata, really_last_key, r->pool);
    if (skipdata != NULL) {
        rewritelog(r, 8, dconf->directory, "Declining, no further rewriting due to END flag");
        return DECLINED;
    }

    /*
     *  Do the Options check after engine check, so
     *  the user is able to explicitly turn RewriteEngine Off.
     */
    if (!(ap_allow_options(r) & (OPT_SYM_LINKS | OPT_SYM_OWNER))) {
        /* FollowSymLinks is mandatory! */
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00670)
                     "Options FollowSymLinks and SymLinksIfOwnerMatch are both off, "
                     "so the RewriteRule directive is also forbidden "
                     "due to its similar ability to circumvent directory restrictions : "
                     "%s", r->filename);
        return HTTP_FORBIDDEN;
    }

    /*
     *  remember the current filename before rewriting for later check
     *  to prevent deadlooping because of internal redirects
     *  on final URL/filename which can be equal to the initial one.
     *  also, we'll restore original r->filename if we decline this
     *  request
     */
    ofilename = r->filename;
    oargs = r->args;

    if (r->filename == NULL) {
        r->filename = apr_pstrdup(r->pool, r->uri);
        rewritelog(r, 2, dconf->directory, "init rewrite engine with"
                   " requested uri %s", r->filename);
    }

    /*
     *  now apply the rules ...
     */
    rulestatus = apply_rewrite_list(r, dconf->rewriterules, dconf->directory, &lastsub);
    if (rulestatus) {
        unsigned skip_absolute = is_absolute_uri(r->filename, NULL);
        int to_proxyreq = 0;
        int will_escape = 0;

        l = strlen(r->filename);
        to_proxyreq = l > 6 && strncmp(r->filename, "proxy:", 6) == 0;
        will_escape = skip_absolute && (rulestatus != ACTION_NOESCAPE);

        if (r->args
               && !will_escape
               &&  *(ap_scan_vchar_obstext(r->args))) {
            /*
             * We have a raw control character or a ' ' in r->args.
             * Correct encoding was missed.
             */
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10411)
                          "Rewritten query string contains control "
                          "characters or spaces");
            return HTTP_FORBIDDEN;
        }

        if (ACTION_STATUS == rulestatus) {
            int n = r->status;

            r->status = HTTP_OK;
            return n;
        }
        else if (ACTION_STATUS_SET == rulestatus) {
            return r->status;
        }

        if (to_proxyreq) {
            /* it should go on as an internal proxy request */

            /* check if the proxy module is enabled, so
             * we can actually use it!
             */
            if (!proxy_available) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10160)
                              "attempt to make remote request from mod_rewrite "
                              "without proxy enabled: %s", r->filename);
                return HTTP_FORBIDDEN;
            }

            if (rulestatus == ACTION_NOESCAPE) {
                apr_table_setn(r->notes, "proxy-nocanon", "1");
            }

            /* make sure the QUERY_STRING gets incorporated in the case
             * [NE] was specified on the Proxy rule. We are preventing
             * mod_proxy canon handler from incorporating r->args as well
             * as escaping the URL.
             * (r->path_info was already appended by the
             * rewriting engine because of the per-dir context!)
             */
            if ((r->args != NULL) && apr_table_get(r->notes, "proxy-nocanon")) {
                r->filename = apr_pstrcat(r->pool, r->filename,
                                          "?", r->args, NULL);
            }

            /* now make sure the request gets handled by the proxy handler */
            if (PROXYREQ_NONE == r->proxyreq) {
                r->proxyreq = PROXYREQ_REVERSE;
            }
            r->handler  = "proxy-server";

            rewritelog(r, 1, dconf->directory, "go-ahead with proxy request "
                       "%s [OK]", r->filename);
            return OK;
        }
        else if (skip_absolute > 0) {
            /* it was finally rewritten to a remote URL */

            /* because we are in a per-dir context
             * first try to replace the directory with its base-URL
             * if there is a base-URL available
             */
            if (dconf->baseurl != NULL) {
                /* skip 'scheme://' */
                cp = r->filename + skip_absolute;

                if ((cp = ap_strchr(cp, '/')) != NULL && *(++cp)) {
                    rewritelog(r, 2, dconf->directory,
                               "trying to replace prefix %s with %s",
                               dconf->directory, dconf->baseurl);

                    /* I think, that hack needs an explanation:
                     * well, here is it:
                     * mod_rewrite was written for unix systems, were
                     * absolute file-system paths start with a slash.
                     * URL-paths _also_ start with slashes, so they
                     * can be easily compared with system paths.
                     *
                     * the following assumes, that the actual url-path
                     * may be prefixed by the current directory path and
                     * tries to replace the system path with the RewriteBase
                     * URL.
                     * That assumption is true if we use a RewriteRule like
                     *
                     * RewriteRule ^foo bar [R]
                     *
                     * (see apply_rewrite_rule function)
                     * However on systems that don't have a / as system
                     * root this will never match, so we skip the / after the
                     * hostname and compare/substitute only the stuff after it.
                     *
                     * (note that cp was already increased to the right value)
                     */
                    cp2 = subst_prefix_path(r, cp, (*dconf->directory == '/')
                                                   ? dconf->directory + 1
                                                   : dconf->directory,
                                            dconf->baseurl + 1);
                    if (strcmp(cp2, cp) != 0) {
                        *cp = '\0';
                        r->filename = apr_pstrcat(r->pool, r->filename,
                                                  cp2, NULL);
                    }
                }
            }

            /* now prepare the redirect... */
            if (rulestatus != ACTION_NOESCAPE) {
                rewritelog(r, 1, dconf->directory, "escaping %s for redirect",
                           r->filename);
                r->filename = escape_absolute_uri(r->pool, r->filename, skip_absolute);
            }

            /* append the QUERY_STRING part */
            if (r->args) {
                char *escaped_args = NULL;
                int noescape = (rulestatus == ACTION_NOESCAPE ||
                                (oargs && !strcmp(r->args, oargs)));

                r->filename = apr_pstrcat(r->pool, r->filename, "?",
                                          noescape
                                            ? r->args
                                            : (escaped_args = ap_escape_uri(r->pool, r->args)),
                                          NULL);

                rewritelog(r, 1, dconf->directory, "%s %s to query string for redirect %s",
                           noescape ? "copying" : "escaping",
                           r->args ,
                           noescape ? "" : escaped_args);
            }

            /* determine HTTP redirect response code */
            if (ap_is_HTTP_REDIRECT(r->status)) {
                n = r->status;
                r->status = HTTP_OK; /* make Apache kernel happy */
            }
            else {
                n = HTTP_MOVED_TEMPORARILY;
            }

            /* now do the redirection */
            apr_table_setn(r->headers_out, "Location", r->filename);
            rewritelog(r, 1, dconf->directory, "redirect to %s [REDIRECT/%d]",
                       r->filename, n);
            return n;
        }
        else {
            const char *tmpfilename = NULL;
            /* it was finally rewritten to a local path */

            /* if someone used the PASSTHROUGH flag in per-dir
             * context we just ignore it. It is only useful
             * in per-server context
             */
            if (l > 12 && strncmp(r->filename, "passthrough:", 12) == 0) {
                r->filename = apr_pstrdup(r->pool, r->filename+12);
            }

            rewritelog(r, 2, NULL, "local path result: %s", r->filename);

            /* the filename must be either an absolute local path or an
             * absolute local URL.
             */
            if (   *r->filename != '/'
                && !ap_os_is_path_absolute(r->pool, r->filename)) {
                return HTTP_BAD_REQUEST;
            }

            /* Check for deadlooping:
             * At this point we KNOW that at least one rewriting
             * rule was applied, but when the resulting URL is
             * the same as the initial URL, we are not allowed to
             * use the following internal redirection stuff because
             * this would lead to a deadloop.
             */
            if (ofilename != NULL && strcmp(r->filename, ofilename) == 0) {
                rewritelog(r, 1, dconf->directory, "initial URL equal rewritten"
                           " URL: %s [IGNORING REWRITE]", r->filename);
                return OK;
            }

            tmpfilename = r->filename;

            /* if there is a valid base-URL then substitute
             * the per-dir prefix with this base-URL if the
             * current filename still is inside this per-dir
             * context. If not then treat the result as a
             * plain URL
             */
            if (dconf->baseurl != NULL) {
                rewritelog(r, 2, dconf->directory, "trying to replace prefix "
                           "%s with %s", dconf->directory, dconf->baseurl);

                r->filename = subst_prefix_path(r, r->filename,
                                                dconf->directory,
                                                dconf->baseurl);
            }
            else {
                /* if no explicit base-URL exists we assume
                 * that the directory prefix is also a valid URL
                 * for this webserver and only try to remove the
                 * document_root if it is prefix
                 */
                if ((ccp = ap_document_root(r)) != NULL) {
                    /* strip trailing slash */
                    l = strlen(ccp);
                    if (ccp[l-1] == '/') {
                        --l;
                    }
                    if (!strncmp(r->filename, ccp, l) &&
                        r->filename[l] == '/') {
                        rewritelog(r, 2,dconf->directory, "strip document_root"
                                   " prefix: %s -> %s", r->filename,
                                   r->filename+l);

                        r->filename = apr_pstrdup(r->pool, r->filename+l);
                    }
                }
            }

            /* No base URL, or r->filename wasn't still under dconf->directory
             * or, r->filename wasn't still under the document root. 
             * If there's a context document root AND a context prefix, and 
             * the context document root is a prefix of r->filename, replace.
             * This allows a relative substitution on a path found by mod_userdir 
             * or mod_alias without baking in a RewriteBase.
             */
            if (tmpfilename == r->filename && 
                !(dconf->options & OPTION_IGNORE_CONTEXT_INFO)) { 
                if ((ccp = ap_context_document_root(r)) != NULL) { 
                    const char *prefix = ap_context_prefix(r);
                    if (prefix != NULL) { 
                        rewritelog(r, 2, dconf->directory, "trying to replace "
                                    "context docroot %s with context prefix %s",
                                   ccp, prefix);
                        r->filename = subst_prefix_path(r, r->filename,
                                ccp, prefix);
                    }
                }
            }

            apr_table_setn(r->notes, "redirect-keeps-vary", "");

            /* now initiate the internal redirect */
            rewritelog(r, 1, dconf->directory, "internal redirect with %s "
                       "[INTERNAL REDIRECT]", r->filename);
            r->filename = apr_pstrcat(r->pool, "redirect:", r->filename, NULL);
            r->handler = REWRITE_REDIRECT_HANDLER_NAME;
            return OK;
        }
    }
    else {
        rewritelog(r, 1, dconf->directory, "pass through %s, filename %s",
                   r->filename, (ofilename) ? ofilename : "n/a");
        r->filename = ofilename;
        return DECLINED;
    }
}