OK I figured it out a way to do it.
What I did was use mod_rewrite
, which is an Apache module (shipped by default with all installations) for rewriting requested URLs on the fly. Here's the code:
# Custom error pages
ErrorDocument 404 /hidden/404/index.html
ErrorDocument 403 /hidden/403/index.html
# Pretend that /hidden doesn't exist
# (unless this is an internal redirect,
# such as rendering a 404 or 403)
RewriteCond %{ENV:REDIRECT_STATUS} =""
RewriteRule ^/hidden/.* - [L,R=404]
So let's walk through these lines. The ErrorDocument
directives haven't changed - they just say what page should be served when a given error occurs.
The real meat is in the RewriteCond
and RewriteRule
directives. What the RewriteRule
directive says is this: if a url is requested whose path matches the regex ^/hidden/.*
(essentially, any path that is to anything in /hidden/ or its subtree), leave the url unchanged (that's what the -
means), but don't interpret any more RewriteRule
directives (that's what the L
means), and return a 404 error code (that's what the R=404
means).
This, combined with the ErrorDocument 404 /hidden/404/index.html
directive, might seem to do the trick. The problem, however, is that when Apache goes to serve /hidden/404/index.html
, it runs through these rules again. That means that the RewriteRule
directive is executed, which in turn says to render the 404, and the whole thing just infinite-loops.
Thus, we use the RewriteCond
directive to make sure this doesn't happen. RewriteCond
directives are conditionals - the RewriteRule
directive below will only be executed if the condition is satisfied. The condition in this case is "the environment variable REDIRECT_STATUS
is equal to the empty string." Now, REDIRECT_STATUS
is an environment variable that is set by ErrorDocument
, and holds the status code that is being redirected (so, if the ErrorDocument 404 ...
directive had been triggered, it would be set to the value "404"). However, if no ErrorDocument
directive had been executed, then REDIRECT_STATUS
would not have been set, and so asking for its value would simply return the empty string. Thus, RewriteCond %{ENV:REDIRECT_STATUS} =""
essentially says, "don't bother evaluating the RewriteRule
directive below unless we're not in the middle of serving an error page."
note: if you have looked at the RewriteRule
documentation, you may have noticed that the R
argument is supposed to perform an explicit redirect. The reason it works fine in this case is that there's an undocumented feature of the R
argument which is that it only redirects on 300-class codes. Thus, R=404
does not do an explicit redirect, only an internal one.