How do I get mod_rewrite to both remove file extensions and redirect certain URLs? - apache

So I'm trying to get mod_rewrite to do a few different things, and I'm not quite there with it. I'd like to:
Remove file extensions from the URLs (in this case, .shtml)
Rewrite certain URLs like so:
/dashboard -> /ui/dashboard/index.shtml
/dashboard/ -> /ui/dashboard/index.shtml
/dashboard/list -> /ui/dashboard/list.shtml
/dashboard/list/ -> /ui/dashboard/list.shtml
/workspace -> /ui/workspace/index.shtml
/workspace/ -> /ui/workspace/index.shtml
/account/manage -> /ui/account/manage.shtml
/account/manage/ -> /ui/account/manage.shtml
Either add or remove a trailing slash ( I don't care which, as long as it's consistent)
What I currently have gets me about 90% of the way there. In my .htaccess file, I've got the following:
DirectoryIndex index.shtml index.html index.htm
<IfModule mod_rewrite.c>
Options +FollowSymlinks
RewriteEngine On
RewriteBase /
# Get rid of the /ui/ in the URLs
RewriteRule ^(account|workspace|dashboard)([a-zA-Z0-9\-_\.\/]+)?$ /ui/$1$2 [NC,L]
# Add the trailing slash
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(\.[a-zA-Z0-9]{1,5}|/|#(.*))$
RewriteRule ^(.*)$ $1/ [R=301,L]
# Remove the shtml extension
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.shtml -f
RewriteRule ^([^\.]+)/$ $1\.shtml
</IfModule>
Now the issues I'm running into are twofold:
First, if I try to access one of the index pages outlined in the directories listed in step 2 above, as long as I do it with a trailing slash, it's fine, but if I omit the trailing slash, the URL rewrites incorrectly (the page still loads, however). For example
/dashboard/ remains /dashboard/ in the address bar.
/dashboard rewrites to /ui/dashboard/ in the address bar.
How can I get these index.shtml pages to keep the address bar consistent?
Second, when I try to access a page other than the directory index in one of the rewritten directories, and I include a trailing slash, it gives me a 404 error. For instance:
/dashboard/list/
throws the 404 error:
The requested URL /ui/dashboard/list.shtml/ was not found on this server.
Any help to get this working properly that you can offer is much appreciated.

So I've figured out an approach that works for what I need. Here's the .htaccess I came up with, commented inline:
# Match URLs that aren't a file
RewriteCond %{REQUEST_FILENAME} !-f
# nor a directory
RewriteCond %{REQUEST_FILENAME} !-d
# if it's the index page of the directory we want, show that and go no further
RewriteRule ^(account|workspace|dashboard)/?$ /ui/$1/index.shtml [L]
# If we've gotten here, we're dealing with something other than the directory index.
# Let's remove the trailing slash internally
# This takes care of my second issue in my original question
RewriteRule ^(.*)/$ $1 [L]
# Do the rewrite for the the non-directory-index files.
RewriteRule ^(account|workspace|dashboard)([a-zA-Z0-9\-_\.\/]+)?$ /ui/$1$2 [L]
Not sure if this is the most efficient way to do this, but it's working for my needs. Thought I'd share it here in case it helps anyone else.

Related

RewriteRule acting strange adding query to some url and not to some others

i'm designing a mvc application in php and I want to reformat and parse the url. My folders are as below:
App
Public
Assets
.htaccess
my htaccess file includes:
Options -Multiviews
RewriteEngine On
RewriteBase /mymvctest/public
RewriteRule ^(.*)/$ index.php?url=$1 [END,QSA]
RewriteRule ^(.*)$ index.php?url=$1 [END,QSA]
Index.php receives a "url" GET variable and prints it for the sake of this example.
if my input url is /mymvctest/public/atestvalue the final url will be the same,/mymvctest/public/atestvalue, and "atestvalue" will be printed in index too.
but when my input url includes a valid directory, the query is shown in the url. for example, if my url is /mymvctest/public/Assets the final url will be /mymvctest/Public/Assets/?url=Assets. The surprising part is that when i add a slash after "Assets", /mymvctest/public/Assets/, the final url will be be the same as my input url: /mymvctest/public/Assets/. I get the "url" variable in both cases though.
why is this happening!?
Look at the 2nd and 3rd lines.
RewriteEngine On
#if not a file
RewriteCond %{REQUEST_FILENAME} !-f
#if not a directory
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?url=$1 [END,QSA]
Input Url :
http://localhost/mymvctest/public/testvalue
http://localhost/mymvctest/public/Assets/
Output Url :
http://localhost/mymvctest/public/index.php?url=testvalue
http://localhost/mymvctest/public/Assets/
It is happening this way because /mymvctest/public/Assets is an actual directory. First mod_rewrite rule executes and makes it /mymvctest/public/index.php?url=Assets internally.
Apache has another module called mod_dir that (due to security reasons) catches all original URLs that point to a directory and have a missing trailing slash. It then redirects them to same path with a trailing slash with R=301 thus making final URL as: /mymvctest/public/Assets/?url=Assets.
In other to prevent this behaviour, use
DirectorySlash off
However this might expose your directory content in browser. To prevent that use this directive as well:
Options -Indexes

My .htaccess rule is not working

I want all my URL with matching pattern as
/released/2013/iron-man
/released/2013/abc-of-death
/released/2012/saw6
to be redirected to
/released/1.php
and I can the name as well.
I am adding this rule to my .htaccess file but its not working
RewriteRule ^released/([0-9]+)/?$ /released/1.php?id=$1 [L]
The trailing question mark matches an optional ending / which is not what you want.
^released/([0-9]+)/iron-man$
or
RewriteRule ^released/([0-9]+)/(.+)$ /released/1.php?id=$1+$2
Problem is that you have $ after second slash but you have movie name after 2nd slash like iron-man etc. Remove $ since you are not matching it.
Make sure that mod_rewrite and .htaccess are enabled through httpd.conf and then put this code in your .htaccess under DOCUMENT_ROOT directory:
Options +FollowSymLinks -MultiViews
# Turn mod_rewrite on
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(released)/([0-9]+)/ /$1/1.php?id=$2 [L,QSA,NC]
A RewriteRule which does not specify a specific external domain is always executed internally. To make it redirect as you ask add [R=301] at the end - or 302, 303 or 307 depending on which kind of redirect you require, but usually 301 is fine.
Besides that, the regular expression you wrote does not allow for extended URLs - remove the trailing $. After that the /? part is moot so you can remove it as well.
The resulting line would read:
RewriteRule ^released/([0-9]+) /released/1.php?id=$1 [L,R=301]

htaccess Silent Redirect to Subdirectory: Subdirectory showing when no trailing '/'

I have dug high and low around Google and StackOverflow to try and figure out my problem, trying countless solutions but nothing has completely worked.
I'm looking to move the web root of the main domain on my server to a sub-directory. What I have currently for a server path to my web root:
/home/user/public_html/MyWebFilesHere
What I'm looking to have:
/home/user/public_html/subdir/MyWebfilesHere
When I browse to mydomain.com, there should be no visible difference though (i.e. "subdir" not visible after redirect).
Unfortunately, I am restricted to doing this purely with a .htaccess file since I'm on shared hosting and don't have access to Apache config files and such. :(
What I currently have in my .htaccess in public_html is:
RewriteEngine on
RewriteCond %{HTTP_HOST} ^(www\.)?mydomain\.com$
RewriteCond %{REQUEST_URI} !^/subdir/
RewriteRule ^(.*)$ /subdir/$1 [L]
This successfully redirects all queries to the sub-directory, however there's a really weird issue. If I go to
mydomain.com/Contact/
it works great, redirecting the query to the path /subdir/Contact/ but leaving the address bar alone. If I go to
mydomain.com/Contact
(Note the lack of a trailing '/') though, what shows in the address bar is
mydomain.com/subdir/Contact/
which isn't what I want since "subdir" is showing.
For a working example on my actual site, try browsing to
colincwilliams.com/Contact/
compared with
colincwilliams.com/Contact
Do you guys have any ideas on how to make this work silently both with and without a trailing slash?
This is probably happening because mod_dir (the module that automatically redirects the browser if a request for a directory is missing a trailing slash to the same thing with a trailing slash. See the DirectorySlash directive in mod_dir
What's happening is:
You request: mydomain.com/Contact
mod_dir doesn't touch this since /Contact isn't a directory
/Contact gets rewritten to /subdir/Contact and internally redirected
mod_dir sees that /subdir/Contact is a directory and missing the trailing slash so it redirects the browser to mydomain.com/subdir/Contact/
So now, your browser's location bar has the /subdir/ in it.
You can add DirectorySlash off in your .htaccess to turn off mod_dir from redirecting. But if you want directories to have trailing slashes, you can add a separate condition for it. Based on what you already have, we can expand it to this:
RewriteEngine on
# Has a trailing slash, don't append one when rewriting
RewriteCond %{HTTP_HOST} ^(www\.)?mydomain\.com$
RewriteCond %{REQUEST_URI} !^/subdir/
RewriteCond %{THE_REQUEST} ./\ HTTP/1\.[01]$ [OR]
# OR if it's a file that ends with one of these extensions
RewriteCond %{REQUEST_URI} \.(php|html?|jpg|gif|css)$
RewriteRule ^(.*)$ /subdir/$1 [L]
# Missing trailing slash, append one
RewriteCond %{HTTP_HOST} ^(www\.)?mydomain\.com$
RewriteCond %{REQUEST_URI} !^/subdir/
RewriteCond %{THE_REQUEST} [^/]\ HTTP/1\.[01]$
# But only if it's not a file that ends with one of these extensions
RewriteCond %{REQUEST_URI} !\.(php|html?|jpg|gif|css)$
RewriteRule ^(.*)$ /subdir/$1/ [L]
Note: I changed !^/mydomain/ to !^/subdir/, figured it was a typo because without it, mod_rewrite would loop internally indefinitely (foo -> /subdir/foo -> /subdir/subdir/foo -> /subdir/subdir/subdir/foo, etc). If I got that wrong, you can change it back.
Edit: See my additions of RewriteCond's matching against \.(php|html?|jpg|gif|css). These are the file extensions that get passed through without getting trailing slashes added. You can add/remove to suit your needs.
Jon Lin's answer was very helpful in determining what was causing the problem in my very similar setup. For completeness I will include the relevant information from his answer:
This is probably happening because mod_dir (the module that automatically redirects the browser if a request for a directory is missing a trailing slash to the same thing with a trailing slash. See the DirectorySlash directive in mod_dir
What's happening is:
You request: mydomain.com/Contact
mod_dir doesn't touch this since /Contact isn't a directory
/Contact gets rewritten to /subdir/Contact and internally redirected
mod_dir sees that /subdir/Contact is a directory and missing the trailing slash so it redirects the browser to mydomain.com/subdir/Contact/
So now, your browser's location bar has the /subdir/ in it.
In my case, I had requests being redirected to /subdir with a few exceptions and didn't want to have to re-enable DirectorySlash for each of those exceptions.
By allowing RewriteEngine to continue after the initial redirect to /subdir, it's possible to mimic what mod_dir would be doing while also taking /subdir into account, before mod_dir gets to see it.
RewriteEngine on
RewriteCond %{REQUEST_URI} !^/exception1|exception2|...
RewriteRule ^(.*)$ /subdir/$1
RewriteCond %{REQUEST_FILENAME} -d
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^subdir/(.*) $1/ [R,L]
Note: You may need to be careful about allowing RewriteEngine to continue if there are further rules. Not matching the second rule will continue on to any further rules which may produce a different result.
This can be avoided by using a third rule to stop RewriteEngine processing if the redirect into /subdir has happened:
RewriteCond %{REQUEST_URI} ^subdir
RewriteRule .* - [L]

Apache mod_rewrite redirect, keep sub-directory name

I'm wondering if this is possible.
I have a single page site in which I'd like to incorporate a trailing slash with a file name that anchors to a section on that site. I'm trying to avoid using hash or hash-bangs.
For example; www.example.com/recent
Right now, I'm removing any trailing slash, but I get a 404 with /recent because it's expecting a file.
RewriteRule ^(.*)/$ /$1 [R=301,L]
Is it possible to redirect to www.example.com, but still maintain the /recent without the server thinking it's a file so I can read it client-side (php/js)? More so that I can keep using the back and forward buttons.
Thanks for any help!
TBH it is not 100% clear for me what you want. As I understand you want URL www.example.com/recent to be rewritten (internal redirect, when URL remains unchanged in browser) to www.example.com/index.php?page=recent (or something like that).
DirectorySlash Off
Options +FollowSymLinks -MultiViews
RewriteEngine On
RewriteBase /
# remove trailing slash if present
RewriteRule ^(.*)/$ /$1 [R=301,L]
# do not do anything for already existing files
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .+ - [L]
# rewrite all non-existing resources to index.php
RewriteRule ^(.+)$ /index.php?page=$1 [L,QSA]
With the above rules (that need to be placed in .htaccess in website root folder) this can be achieved. Request for www.example.com/recent will be rewritten to www.example.com/index.php?page=recent so your single-page server side script knows which URL was requested. The same will be with any other non-existing resource e.g. www.example.com/hello/pink/kitten => www.example.com/index.php?page=hello/pink/kitten.
It may not be necessary to pass originally requested URI as a page parameter as you should be able to access it in PHP via $_SERVER['REQUEST_URI'] anyway.
If I misunderstood you and this is not what you want then you have to clarify your question (update it with more details, make it sound clear).

Using mod_rewrite how to Get the last 3 digits from a url

I need to get the last set of numbers from a random url, the url looks like:
/directory/directory2/a-b-c-d-123
a,b,c,d etc.. can be anything, numbers, letters but will always have dashes in between
We are using kohana for this project so there is some additional rewrite rules in play but this is what I have so far...
# Turn on URL rewriting
RewriteEngine On
# Installation directory
RewriteBase /site/
# Protect hidden files from being viewed
<Files .*>
Order Deny,Allow
Deny From All
</Files>
# Protect application and system files from being viewed
RewriteRule ^(?:application|modules|system)\b - [F,L]
#My Code Attempts Here
RewriteCond %{REQUEST_URI} dealership/Listing/
RewriteRule ([0-9]*)$ index.php/dealership/Listing/$1
# Allow any files or directories that exist to be displayed directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# Rewrite all other URLs to index.php/URL
RewriteRule .* index.php/$0 [PT]
I have tried a few dozen configurations, setups, and researched Google for a few hours with no specific answers.
I have been able to get the 123 all by itself but never with the directory still attached, also when I have tried a few configurations I end up in a endless loop and get an apache error.
The end result would be /directory/directory2/123
Thanks!
Your rule
RewriteRule ([0-9]*)$ index.php/listing/$1
isn't useful, because it doesn't change REQUEST_URI, so the PHP won't see the rewritten index.php/listing/123, it will see the original /listing/foo-123. If you add the [L] flag, it will go into a loop, because the corresponding ReqeuestCond will continue to be true.
Typically you would pass URL bits to the script as parameters, for example
RewriteRule ([0-9]*)$ index.php?listing=$1 [L]
However, in this form it will not work, because ([0-9]*)$ matches the empty string on the end of any path, so it will cause two rewrites:
listing/foo-(123) → index.php?listing=123 # this is what you want ...
index.php() → index.php?listing= # ... but it gets rewritten
index.php() → index.php?listing= # no change so this is final
This happens because all rewrite rules are evaluated from the beginning after every rewrite (regardless of the [L] flag).
Thus, you need a more specific rule
RewriteRule ^listing/[^/]*-([0-9]*)$ index.php?listing=$1 [L]
This works on its own, but it would interact with your final rule, so add a condition on that to prevent it looping
RewriteCond $0 !^/index.php($|/) # $0 is what the RewriteRule matched
RewriteRule .* index.php/$0 [L]