I must be missing something obvious, since this seems like it shouldn't be a difficult problem. I want all url paths on my site (http://example.com, http://example.com/admin, http://example.com/happy/happy/joy/joy, etc.) to be handled through my index.php. I've seen this done before, but offhand I do not know how to do it.
Just create an .htaccess file in your root directory with this :
# Turn on URL rewriting
RewriteEngine On
# Base directory
RewriteBase /
# Protect hidden files from being viewed
<Files .*>
Order Deny,Allow
Deny From All
</Files>
# 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]
Apache flags explained :
[L] - Alias for "Last", instructs the server that the rewrite rules have come to an end and it's time to perform an internal redirection without changing the browser's uri.
[PT] - Alias for "Pass Through", allows a Mod_Rewrite manipulated uri to be passed to the next type of handler, accordingly to the php.ini module's order of inclusion. Usefull depending on the subject.
Related
Background
I am building a PHP application that uses MVC principles. I am not every experienced in writing .htaccess files, but I have a solution that works very well for my application. All requests are routed to a public/ directory for security and only absolute resources in this directory can be accessed, for example: http://localhost/public/css/main.css.
My issue is properly handling resource files (js, css, or images) that do not actually exist.
Issue & Question
My current .htaccess rules are causing an infinite loop and returning an http 500 status when a resource file (js, css, or images) does not exists.
I know I need to catch this file does not exists problem and return an http 404 status instead, but how do I do that without skipping the last 4 lines of my rules? I'm referring to the last 3 rewrite conditions and 1 rewrite rule. These are crucial to my app.
htaccess code
# Disable index view.
Options -Indexes
# Hide sensitive files.
<Files ~ "\.(env|json|config|md|gitignore|gitattributes|lock|yml|xml)$">
Order allow,deny
Deny from all
</Files>
# Redirect all requests to the public directory.
<IfModule mod_rewrite.c>
RewriteEngine On
# Rewrite base if site is not at the servers root.
RewriteBase /sub/directory/
# If nothing or an index page is requested go to `public/index.php` and stop processing.
RewriteRule ^(\/||.*index\..*)$ public/index.php [L,QSA]
# If the URL already contains the `public/` directory go to URL and stop processing.
RewriteRule ^(.*public\/.*)$ $1 [L,QSA]
# Does it appear that this URL is for a resource (JS, CSS, etc.)?
# ▼ ▼ ▼ ISSUE STARTS HERE ▼ ▼ ▼
RewriteCond %{REQUEST_URI} ^([\w\d\s\-\_\/\%]*)\.(?!php|phtml|htm|html).*$
# Yes, prefix with `public/` and go to the modified URL.
RewriteRule ^(.*)$ public/$1 [L,QSA]
# Rewrite all remaining URLs to our apps MVC structure.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^([\w\d\s\-\_\/\%]*)$ public/index.php?p=$1 [L,QSA]
</IfModule>
Wrong approach in your question. You assume that you have to "catch" the file not found situation and return a 404. But that is not the case. Actually that is the default behavior of the apache http server so you'd have to do nothing for that. Your issue is that you implemented a rewriting loop for those requests, that is what you need to break. Then a 404 will get returned as a standard response.
The actual issue is a classic one: your last rewrite rule rewrites to public/index.php. That target however is matched again by the pattern of the same rewrite rule. So your next rewriting target looks like public/index.php?p=public/index.php&p=some-page. Each time the rule rewrites the rewriting restarts, since the target has been altered. That behavior is documented.
You have two options:
Either you need to use the END flag instead of the L flag in that rule (to finally terminate the rewriting process for that request:
RewriteRule ^([\w\d\s\-\_\/\%]*)$ public/index.php?p=$1 [END,QSA]
Or you need to add an additional condition to break that loop:
RewriteCond %{REQUEST_URI} !public/index\.php$
I would definitely recomment the first approach, assuming that there are no further rewriting steps interfering here.
arkascha's answer helped point me in the right direction; using END instead of L in the correct locations. For those looking to create a similar security focused MVC structure my working .htacces file is below. It provides the following benefits:
Directory listing is disabled and common sensitive files are forbidden from direct viewing. [A]
Any attempt to view an index page is redirected to the public directory. [B]
Rewrite loops to the public directory are caught and prevented from occurring. [C]
Direct access to resource files (JS, CSS, etc.) are allowed in the public directory. [D]
Missing resource files properly return a 404 instead of a 500. Thanks again to #arkascha for pointing out the difference between L and END in htacces files. [E]
# Disable index view. [A]
Options -Indexes
# Hide sensitive files. [A]
<Files ~ "\.(env|json|config|md|gitignore|gitattributes|lock|yml|xml)$">
Order allow,deny
Deny from all
</Files>
# Redirect all requests to the public directory.
<IfModule mod_rewrite.c>
RewriteEngine On
# Rewrite base if site is not at the servers root.
RewriteBase /sub/directory/
# If nothing or an index page is requested go to `public/index.php` and stop processing. [B]
RewriteRule ^(\/||.*index\..*)$ public/index.php [END,QSA]
# If the URL already contains the `public/` directory go to URL and stop processing. [C][E]
RewriteRule ^(.*public\/.*)$ $1 [END,QSA]
# Does it appear that this URL is for a resource (JS, CSS, etc.)?
RewriteCond %{REQUEST_URI} ^([\w\d\s\-\_\/\%]*)\.(?!php|phtml|htm|html).*$
# Yes, prefix with `public/` and go to the modified URL. [D]
RewriteRule ^(.*)$ public/$1 [L,QSA]
# Rewrite all remaining URLs to our apps MVC structure. [E]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^([\w\d\s\-\_\/\%]*)$ public/index.php?p=$1 [END,QSA]
</IfModule>
I feel like this is a rather common request, but I am too confused about .htaccess and couldn't find a solution by Google.
I have a Laravel instance in a subdirectory of the Apache2 htdocs. Now I would like to invisibly redirect all requests from the root domain to this folder (it should be the "root" website). But the tricky thing is, this is not the only folder, there are other folders directly in the htdocs, which should be reached normally. Just the "root" website is not in the root but also in a subfolder. For example:
https://domainA.com should load https://domainA.com/laravel/public (including possible query string or parameters, but invisibly for the user)
https://domainA.com/websiteB should be served as it is
https://domainA.com/websiteC should be served as it is
...
I assume, part of this solution will be to list all the websiteB, websiteC directories in the .htaccess, would it be possible to automate this?
Thanks in advance!
You can put a .htaccess in the folder you want to custom controle but you have to create some filter condition
<IfModule mod_rewrite.c>
RewriteEngine On
## RewriteBase /foo
## conditions to tell what to redirect ie on URI
## RewriteCond %{REQUEST_URI} ^/a-folder/
## not websiteB or websiteC
RewriteCond %{REQUEST_URI} !^/websiteB/
RewriteCond %{REQUEST_URI} !^/websiteC/
## if the file does not exist call index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ my/path/to/a/script.php [L]
</IfModule>
After you have to do something special in script.php for those HTTP calls
You can also rewrite the URI and pass it again to apache but things can be complicated after...
I am trying to update a legacy web application, so I'm trying to set up both the legacy application and the updated version in parallel, and gradually migrate to the new one.
The old application is located directly under public_html, and have put the new Application under public_html/symsale, now I want all requests directed to the new application to be rewritten and a web subdirectory added after symsale, i.e: a url like /symsale/path, must be rewritten to /symsale/web/path. for that I've create a .htaccess file and put it under the symsale directory. The contents follow:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^(.*)(symsale)(.*)$ $1$2/web$3 [L] # i've tried it both with and without the 'L'
</IfModule>
When I try to navigate to /symsale/ I get the following error
You don't have permission to access /symsale/ on this server.
However when I use /symsale/web instead, it works just fine. What's the problem here?
Note: I have seen these other questions: Apache rewrite rule to redirect all request to subdirectory containing another .htaccess and rewrite rules, Apache RewriteRule to subdirectory, .htaccess rewrite to redirect root URL to subdirectory
Note: I'm using the symfony framework (php) so there is another .htaccess file inside the web folder, which rewrites all routes to the front controller. The contents of that .htaccess follow.
DirectoryIndex app.php
<IfModule mod_rewrite.c>
RewriteEngine On
# Determine the RewriteBase automatically and set it as environment variable.
# If you are using Apache aliases to do mass virtual hosting or installed the
# project in a subdirectory, the base path will be prepended to allow proper
# resolution of the app.php file and to redirect to the correct URI. It will
# work in environments without path prefix as well, providing a safe, one-size
# fits all solution. But as you do not need it in this case, you can comment
# the following 2 lines to eliminate the overhead.
RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
RewriteRule ^(.*) - [E=BASE:%1]
# Sets the HTTP_AUTHORIZATION header removed by apache
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Redirect to URI without front controller to prevent duplicate content
# (with and without `/app.php`). Only do this redirect on the initial
# rewrite by Apache and not on subsequent cycles. Otherwise we would get an
# endless redirect loop (request -> rewrite to front controller ->
# redirect -> request -> ...).
# So in case you get a "too many redirects" error or you always get redirected
# to the start page because your Apache does not expose the REDIRECT_STATUS
# environment variable, you have 2 choices:
# - disable this feature by commenting the following 2 lines or
# - use Apache >= 2.3.9 and replace all L flags by END flags and remove the
# following RewriteCond (best solution)
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^app\.php(/(.*)|$) %{ENV:BASE}/$2 [R=301,L]
# If the requested filename exists, simply serve it.
# We only want to let Apache serve files and not directories.
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule .? - [L]
# Rewrite all other queries to the front controller.
RewriteRule .? %{ENV:BASE}/app.php [L]
</IfModule>
<IfModule !mod_rewrite.c>
<IfModule mod_alias.c>
# When mod_rewrite is not available, we instruct a temporary redirect of
# the start page to the front controller explicitly so that the website
# and the generated links can still be used.
RedirectMatch 302 ^/$ /app.php/
# RedirectTemp cannot be used instead
</IfModule>
</IfModule>
Here is one longshot -- mod_rewrite has an unfortunate/quirky behavior where it guesses whether you're substituting a filesystem path or a URL path. It guesses by looking at the first component of the substitution and seeing if it exists as a directory on disk (absolute path)
Do you happen to have a /symsale/ at the root of your filesystem? I believe [PT] will short-circuit this guessing and force the result to be treated as a URI.
I'd like to write a rewrite rule to do the following: if any of the following files exist on the server as a static html file in a specific directory then I'd like .htaccess to serve that static file. Otherwise, I'd like the id (first number after the .com/ and before the first hyphen) to be passed as a query parameter to www.gallery.com/index.php
Redirect should occur for the following URLs if it doesn't exist as a static HTML page.
www.gallery.com/2-swimming.html
www.gallery.com/2
gallery.com/2
Below is my entire .htaccess file
<Files .htaccess>
order allow,deny
deny from all
</Files>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENANE} !-f
RewriteCond %{REQUEST_FILENANE} !-d
RewriteRule ^([0-9]+)-(.*)\.html$ /index.php?id=$1 [L]
</IfModule>
Is there anything wrong with my rewrite condition.(I'm having a hard time). Also what is an efficient way to grab the id incase of a redirect.
Why not just always pass everything to index.php?
PHP is a lot better at manipulating strings than .htaccess
The -f line already takes care of existing static files. (And -d for directories)
RewriteRule ^(.*)$ /index.php?id=$1 [L,QSA]
This rule matches and captures everything.
I am working with a custom MVC PHP framework and the index page (acting as a router) receives a GET variable "do" which contains the path that it will route to. If this variable is not set, it defaults to the Auth controller, method login.
require_once('config.php');
$controllerAction = isset($_GET['do'])?$_GET['do']:"auth/login";
require_once('core/main.php');
Then the index page (source code above) passes this $controllerAction to the main.php file, which autoloads the main controller and then loads the requested controller.
Thus, the URIs in this framework are of the form mysite.com/?do=controller/method/variable and I need it to be in the form mysite.com/controller/method/variable.
Here is the .htaccess file I tried to use, it just didn't work (I have other htaccess files working on the same server so it's not an Apache problem) :(
RewriteEngine On
RewriteRule ^([^/]*)$ /?do=$1 [L]
Someone suggested that I can do this using PHP but I am not sure how to go about that.
Edit:
The error is that I get "This page cannot be displayed", 404 errors, whenever I try to directly access the mysite.com/controller/method links rather than the default mysite.com?do=controller/method
Further Edit
(please note that other virtual hosts work fine on my localhost):
(XAMPP) Apache Virtual Hosting Info:
<VirtualHost *:80>
DocumentRoot "D:\sites\mysite.com\root\wwwroot"
ServerName mysite.com
ServerAlias mysite.com
<Directory "D:\sites\mysite.com\root\wwwroot">
Options Indexes FollowSymLinks Includes ExecCGI
AllowOverride All
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
File structure (Windows):
D:\
--sites
----mysite.com
--------#client_details
--------root
-----------#devfiles
-----------#vars_pwd
-----------wwwroot
--------------config
--------------core
--------------application
------------------controllers
------------------libraries
------------------models
------------------views
----------------------css
----------------------javascript
----------------------images
----------------------icons
First of all, there are some issues with your .htaccess contents. It's always a good idea to not rewrite if a file with the requested name exists. This allows you to have an img/ folder for your images or any other static content like css files, javascript, downloads, etc.. The first RewriteCond tells Apache to only rewrite if no folder with this name exists. The second one does the same with files. Then you probably want the QSA (i.e. Query String Append) option, which will pass all other GET variables to your script.
Under this conditions you can simplify the regex and use this:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?do=$1 [L,QSA]
You might be surprised because this is more or less the same as others posted. I use similar things for many of my projects and I've just tested it, I can guarantee that it works. There must be something wrong with your apache config.
When you have problems with mod_rewrite, the first thing you should try is to enable the module itself. Type these commands as root in your shell:
a2enmod rewrite
/etc/init.d/apache2 restart
The first one activates the module (or complains with Module rewrite already enabled if everything is ok) and the second one restarts your Apache server. The path may of course be different on your server.
Then you have to make sure that your VHost config allows you to use .htaccess files and do rewrites. This means AllowOverride must be set to at least FileInfo (or All). You could also try to put the rewrite rules right into the config file. Your config should look similar to this:
<VirtualHost *:*>
ServerName test.example.com
ServerAlias www.test.example.com
DocumentRoot /home/sites/test/
<Directory "/home/sites/test/">
Allow from all
AllowOverride All
Options +Indexes
</Directory>
</VirtualHost>
Note that you have to restart Apache if you change anything in there.
If that all doesn't help, it's always a good idea to have a look at the error logs. On my system they're located at /var/log/apache2/error.log (debian). They might give you more information on what's going wrong.
Try
RewriteEngine On
RewriteRule ^([^/]*)$ index.php?do=$1 [L]
Try
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?do=$1 [L]
Check your apache logs, access logs specifically. If the folder is present in the web root, then you should be able to access it directly :). You might also want to check if you have duplicate virtualhost entries for the same site by chance.
This one is my customized MVC framework which is based on cake
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?do=$1 [QSA,L]
</IfModule>
May be this should help. The typical URL pattern for this site.com/controller/method
I don't know what your domain setup is like, but here are some suggestions.
If your code resides in the root of your folder, and the index file is called index.php try the following:
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.php?do=$1 [L,QSA]
If your website exists in a subfolder e.g. www.example.com/site/, and the index file is index.php Then try the following (change /site/ to whatever your folder is).
RewriteEngine On
RewriteBase /site/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /site/index.php?do=$1 [L,QSA]
If you still get the 404 error message then do the following:
Make sure your site allows .htaccess files to be processed by checking AllowOverride is set to all. If you don't have access to the necessary config files to check, a simple test is to setup an .htaccess rule to redirect to a dummy file on your system. If it works, then your .htaccess is being executed fine.
Have a look at your MVC framework to see what page it's actually sending the request to. The problem may be that you haven't defined a handler for that particular request, and the default action of your MVC framework is to throw a 404 error.
Edit: Just reading your description, I notice you said that the URL should basically be something like mysite.com/?do=controller/method/variable. If it has be very strict about this format, then you'll also need to put in rules for removing any leading or trailing slashes, e.g. the following re-write rule should do it:
RewriteRule ^\?(.*)\?$ /index.php?do=$1 [L,QSA]
(This makes the leading and trailing slashes optional, but it should remove them from the actual value you pass to do).