4

I am wondering, why folder renaming doesn't work. RewriteRule ^pl/(.*) public/$1

Let's say I have a URL like:

example.com/public/index.html

Now I want to rewrite the URL to

example.com/pl/index.html

pl is not a folder rather just a name!

The rest works fine. Why just that one rule doesn't work?

My .htaccess

RewriteEngine on
RewriteRule ^dekoracyjne.html$ decorative.php [NC,L]
RewriteRule ^pl/(.*) public/$1
RewriteRule ^dekoracyjne/(.*) decorative/$1
RewriteRule ^techniczne.html$ architectural.php [NC,L]
RewriteRule ^techniczne/(.*) architectural/$1
RewriteRule ^show.html/([-\w]+)$ show.php?id=${products:$1} [L]
RewriteRule ^p.html$ product_configurator.php [NC,L]
RewriteRule ^show_c\.html/([-*\w]+)$ show_c.php?cat=${catcolors:$1} [L]
RewriteRule ^(product_configurator)\.html/(\d+)&([-\w]+)$ $1.php?prometheus_id=$2&id=${products:$3} [L]
RewriteRule ^(ss_c)\.html/(\d+)&([-\w]+)$ $1.php?prometheus_id=$2&id=${products:$3} [L]
# Rewrite other ".html" requests to ".php"
RewriteRule (.+)\.html$ $1.php [L]
4
  • What you mean by "doesn't work"? Are you expecting the /public/ URLs to redirect or are you expecting that he /pl/ URLs show the same content as the /public/ URLs? Dec 23, 2020 at 11:44
  • Is there no last flag ([L]) on that rule because you are expecting the html files in the /public/ directory to get further rewritten from .html to .php? Dec 23, 2020 at 11:45
  • @Stephen Ostermiller check this cleoni.com/public/ yes pl like public Dec 23, 2020 at 11:54
  • @Stephen Ostermiller All files in /public/ are rewrite from php to html Dec 23, 2020 at 11:57

1 Answer 1

1

This "doesn't work" in your example because of path-info (additional pathname information) on the originally requested URL. Apache mod_rewrite appends this path-info after the rewrite which conflicts with the later rewrite to replace the .html extension with .php.

When you request /pl/index.html, where /pl does not exist as a physical directory then the URL-path that follows, ie. /index.html is path-info. This path-info is re-appended after the first rewrite.

For example, given a request for /pl/index.html:

  1. RewriteRule ^pl/(.*) public/$1 - this rewrites the request to public/index.html.
  2. mod_rewrite then appends the path-info from the original request to become public/index.html/index.html.
  3. RewriteRule (.+)\.html$ $1.php [L] - This directive is now matching against public/index.html/index.html, which results in the request being rewritten to public/index.html/index.php - which is not the intention and most probably results in a 404 (unless public/index.html exists and AcceptPathInfo On is set).

There are three ways you can resolve this, either:

  • Add the L flag to the initial RewriteRule:

    RewriteRule ^pl/(.*) public/$1 [L]
    

    This might seem counterintuitive as you still want the later RewriteRule to change the file extension - but it still does. The L flag does not stop all processing, it only stops the current round of mod_rewrite processing and (in a directory context) causes the rewrite engine to start over. Part of the process of "starting over" is that the newly rewritten request is remapped to the file system (and path-info is recalculated), so the later RewriteRule to replace the .html extension is applied to the rewritten URL as expected (since no other rewrites occur in the meantime).

    The rewrite process loops in this way until the URL passes through unchanged. (Or, the END flag (Apache 2.4) is encountered.)

OR

  • Add the DPI (Discard Path-Info) flag to the initial RewriteRule instead:

    RewriteRule ^pl/(.*) public/$1 [DPI]
    

    This discards the path-info from the current rewrite (and all future rewrites) thus preventing the path-info (ie. /index.html in this case) from being reappended.

OR

  • Change the pattern in the last RewriteRule (that replaces the .html file extension) so that it matches the first .html file extension, and not the last file extension. So as to avoid matching against the appended path-info (if present). For example:

    RewriteRule ^(.+?)\.html $1.php [L]
    

    Note the trailing $ (end-of-string) anchor on the RewriteRule pattern is removed and the captured subpattern is made non-greedy (.+?) so as to match the part of the URL-path before the first .html and not the last .html. Any additional path-info (if any) - is also discarded by the rewrite.

Which method to choose?

Instinctively, I would simply add the L flag. However, adding the DPI flag instead may in fact be more optimal (it prevents the unnecessary additional loop by the rewrite engine). However, if you add the DPI flag I would consider reordering (and grouping) the rules to make it more obvious/readable that it is the intention that the URL could be further rewritten for these requests. (It looks like you would need to apply this same process to two other rules as well?)

The third option to change the pattern in the last RewriteRule feels a bit hacky and I imagine could potentially encounter edge cases that break depending on your URL structure. Although this could still work OK. (Again, reordering of the rules to make the intention more obvious is recommended.)

For example:

RewriteEngine on

RewriteRule ^dekoracyjne.html$ decorative.php [NC,L]
RewriteRule ^techniczne.html$ architectural.php [NC,L]
RewriteRule ^show.html/([-\w]+)$ show.php?id=${products:$1} [L]
RewriteRule ^p.html$ product_configurator.php [NC,L]
RewriteRule ^show_c\.html/([-*\w]+)$ show_c.php?cat=${catcolors:$1} [L]
RewriteRule ^(product_configurator)\.html/(\d+)&([-\w]+)$ $1.php?prometheus_id=$2&id=${products:$3} [L]
RewriteRule ^(ss_c)\.html/(\d+)&([-\w]+)$ $1.php?prometheus_id=$2&id=${products:$3} [L]

RewriteRule ^pl/(.*) public/$1 [DPI]
RewriteRule ^dekoracyjne/(.*) decorative/$1 [DPI]
RewriteRule ^techniczne/(.*) architectural/$1 [DPI]

# Rewrite other ".html" requests to ".php"
RewriteRule (.+)\.html$ $1.php [L]

Aside: You've included the NC flag on two of the rewrites. This naturally makes the initial match case-insensitive - which shouldn't be necessary. If this is required then you potentially have a duplicate content issue, since foo.html is strictly different to FOO.html.

2
  • 1
    I am (as usual) in awe of your knowledge of mod_rewrite's foibles and edge cases. I'm a bit disturbed that [L] doesn't really mean "last." Dec 23, 2020 at 20:51
  • @StephenOstermiller Thanks. Yes, the L flag is a cause of confusion. However, this is only when used in .htaccess (or <Directory> containers). When mod_rewrite is used in a server (or vHost) context then L does literally mean "last" - and I think this is really what Apache considers the primary use case. Use of mod_rewrite in .htaccess does seem somewhat secondary, despite this probably being the most common use case. "The rewrite engine may be used in .htaccess files ... with some additional complexity."
    – MrWhite
    Dec 24, 2020 at 3:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.