adding forward slash after rewrite if not exist - apache

I have read a ton and tried a ton of solutions, but can't find one specific to my needs.
In my htaccess I have the following:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^profile/([\w-]+)/?$ profile.php?username=$1 [L,QSA]
RewriteRule ^profile/([\w-]+)/([\w-]+)/?$ profile.php?username=$1&type=$2 [L,QSA]
RewriteRule ^profile/([\w-]+)/([\w-]+)/([\w-]+)/?$ profile.php?username=$1&type=$2&o=$3 [L,QSA]
This works wonderfully, except for 1 small problem.
If profile/username does not have a / the links on the page will break unless absolute urls are used. So <a href="./1"> will end up as profile/1 instead of profile/username/1
If I change the first rewrite to the following:
RewriteRule ^profile/([\w-]+)/$ profile.php?username=$1 [L,QSA]
then https://myurl/username will return a 404 which I do not want - I want it to force the / on the end if it does not exist.
I have tried adding:
RewriteRule ^(.*)([^/])$ /$1$2/ [L,R=301]
I have tried
RewriteRule ^(.*)$ https://nmyurl/$1/ [L,R=301]
Just can't figure out how to do this with the rewrite conditions already in place.

To add an optional traling at the end of your profile URLs you can use this
RewriteEngine on
RewriteCond %{REQUEST_URI} ^/profile/
RewriteRule !/$ %{REQUEST_URI}/ [L,R]

If profile/username does not have a / the links on the page will break unless absolute urls are used. So <a href="./1"> will end up as profile/1 instead of profile/username/1
If the issue only applies to URLs of the form /profile/<username> then I would be specific and only append the trailing slash to these specific URLs, otherwise, you are going to get a lot of redirects (which could be detrimental to SEO).
However, you should ensure that internal links are for the canonical URL (ie. with the trailing slash).
For example, the following should go before your existing rewrites:
RewriteRule ^(profile/[\w-]+)$ /$1/ [R=301,L]
Since the canonical URL requires a trailing slash this should be a 301 (permanent) redirect. (But test with a 302 first.)
Alternatively, instead of redirecting in .htaccess (if you are still linking to the slashless URL internally) then you could add a base element (that includes the trailing slash) to the head section to state what relative URLs should be relative to.
For example:
<base href="/profile/<username>/">
Aside:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^profile/([\w-]+)/?$ profile.php?username=$1 [L,QSA]
RewriteRule ^profile/([\w-]+)/([\w-]+)/?$ profile.php?username=$1&type=$2 [L,QSA]
RewriteRule ^profile/([\w-]+)/([\w-]+)/([\w-]+)/?$ profile.php?username=$1&type=$2&o=$3 [L,QSA]
The two RewriteCond directives would seem to be entirely superfluous. RewriteCond directives only apply to the first RewriteRule that follows. But the RewriteRule patterns are unlikely to match real files anyway (unless you have files without extensions or directories with the same name).

So unfortunately I wasn't able to actually achieve what I was hoping to because there are multiple levels of variables that may or may not exist.
In part I used the solution provided by Amit:
RewriteCond %{REQUEST_URI} ^/profile/
RewriteRule !/$ %{REQUEST_URI}/ [L,R]
However this wasn't enough, because as pointed out by MrWhite there are 3 separate potential url's.
https://myurl/profile/username/
https://myurl/profile/username/type/
https://myurl/profile/username/type/o/
In this sitation username should always exist, but type and o may or may not exist.
So what I did was detect the level of the url and then created conditional . and .. using php.
The variable o is always numeric and variable type is never numeric so this worked for me.
if (isset($_GET['o'])) { $o = strip_tags($_GET['o']); }
elseif (isset($_GET['type']) && is_numeric($_GET['type'])) { $o = strip_tags($_GET['type']); }
Then I detect:
// if o is set or if type is numberic, use ..
if (isset($_GET['o']) || (isset($_GET['type']) && is_numeric($_GET['type']))) {
$dots = '..';
// if o is not set and type is not numeric just use .
} else {
$dots = '.';
}
end result of 1:
if url is https://myurl/profile/username/
result is https://myurl/profile/username/1/
if url is https://myurl/profile/username/3/
result is https://myurl/profile/username/1/
if url is https://myurl/profile/username/type/3/
result is https://myurl/profile/username/type/1/
Which was the desired outcome.

Related

htaccess pretty urls not working

Folder structure:
- assets
- all css / js
- calsses
- all models, db ant etc
- views
- admin
- app
- index.php
- customers.php
.......
my .htaccess
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{HTTP_HOST} ^(www.)?localhost:8080$
RewriteRule ^(.*)$ /views/$1
RewriteRule ^(/)?$ /views/index.php [L]
address : localhost:8080/app/ - working fine, but then I try to add pretty url for example in my customers.php - localhost:8080/app/customers.php?id=5 change to localhost:8080/app/customers/id/5
htaccess added new line:
RewriteRule /id/(.*) customers.php?id=$1
It's not working, it always return 500 Internal Server Error there could be the problem?
plus Need all urls without .php extend
You'd have to include those conditions for every rule. You'd be better off just rewriting everything to, say views/router.php then using PHP to include the different controllers, or serve a 404 when the URL isn't valid.
RewriteRule !^views/router\.php$ views/router.php [NS,L,DPI]
I agree with Walf in that handling routes through a router class is a better idea (especially in the long run!) than using .htaccess redirects.
However, as your question seems to be more about why is this not working than about how you should do it, here is an explanation for what is going on.
I will be using these URLs as examples:
localhost:8080
localhost:8080/app
localhost:8080/app/customers/id/5
Your first rule:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{HTTP_HOST} ^(www.)?localhost:8080$
RewriteRule ^(.*)$ /views/$1
As you intended, this RewriteRule will match any URL which is not a file, not a directory, and made to localhost:8080.
localhost:8080 # not matched because it leads to a directory.
localhost:8080/app -> localhost:8080/views/app
localhost:8080/app/customers/id/5 -> localhost:8080/views/app/customers/id/5
Your next rule:
RewriteRule ^(/)?$ /views/index.php [L]
It is important to realize that RewriteCond statements apply only to the first RewriteRule following them, thus all that is being checked here is the path.
Side note: ^(/)?$, as you are not using $1, can be simplified to ^/?$.
localhost:8080 -> localhost:8080/views/index.php
localhost:8080/views/app # not matched
localhost:8080/views/app/customers/id/5 # not matched
As the L flag is specified, Apache will immediately stop the current iteration and start matching again from the top. The documentation is badly worded. Thus, localhost:8080/views/index.php will be run through the first rule, fail to match, be run through this rule, fail to match, and then as no other rules exist to check (yet) no rewrite will be done.
Now lets look at what happens when you add your broken rule.
RewriteRule /id/(.*) customers.php?id=$1
There are a few problems here. First, as you don't require that the URL start with /id/ the rule will always match a URL that contains /id/, even if you have already rewritten the URL. If you amended this by using ^/id/(.*), then you would still have issues as the string that the rewrite RegEx is tested against has leading slashes removed. Lastly and most importantly, customers.php does not exist in your root directory.
localhost:8080/views/index.php # not matched
localhost:8080/views/app # not matched
localhost:8080/views/app/customers/id/5 -> localhost:8080/customers.php?id=5
This is the last rule in your file currently, so now Apache will start over. customers.php does not exist in your directory, so it will be rewritten to views/customers.php. No other rules matched, but the URL has changed and so Apache will start over again, as /views/customers.php does not exist, it will be rewritten to /views/views/customers.php ... This pattern will repeat until you hit the maximum iteration limit and Apache responds with a 500 error.
You can solve this several ways. Here would be my preferred method, but only if you cannot use a router.
RewriteEngine on
# Rewrite the main page, even though it is a directory
RewriteRule ^/?$ views/index.php [END]
# Don't rewrite any existing files or directories
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .? - [S=999,END]
RewriteRule ^app/?$ views/app/index.php [END]
RewriteRule ^app/id/(.*)$ views/app/customers.php?id=$1 [END]
TL;DR Use a PHP based router. .htaccess rules can be incredibly confusing.
Please refer to the question, How to make Clean URLs
I think this is what you needed.
you can use RewriteRule ^(.*)$ index.php [QSA,L]
Having another crack.
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d [OR]
RewriteCond %{HTTP_HOST} !^(?:www\.)?localhost:8080$ [OR]
RewriteCond $0 =views
RewriteRule [^/]* - [END]
RewriteRule ^(app|admin)/([^/]+) views/$1/$2.php [DPI,END]
RewriteRule ^(app|admin)/?$ views/$1/index.php [DPI,END]
You may have to use L instead of END flags if your Apache is older. Set up an ErrorDocument for 404s, too.
Don't muck around with query strings, just parse $_SERVER['REQUEST_URI'] in PHP, e.g. start by exploding it on /. Then you'll have all the parameters of the original pretty URL. You can do that part in an include so each controller can reuse the same code.
I tried your structure and .htaccess file myself and found an endless loop in the apache logs. I bet you got something like this:
Mon Nov 28 19:57:32.527765 2016] [core:error] [pid 10] [client 172.18.0.1:35048] AH00124: Request exceeded the limit of 10 internal redirects due to probable configuration error. Use 'LimitInternalRecursion' to increase the limit if necessary. Use 'LogLevel debug' to get a backtrace.
I could fix it by adding the last rule like:
RewriteRule id/(.*) /views/app/customers.php?id=$1
The leading / is not needed for the match and the target needs the full path. Note that I got the id double (e.g. 123/123) on the url: http://localhost:8080/id/123.
This is caused by one of the 2 previous rules (removing them fixes it) so you might need to change them.
Here is what you want :
RewriteEngine On
RewriteBase /app/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^\/?$ views/index.php [L]
RewriteRule ^([a-zA-Z0-9]+)\/([a-zA-Z0-9]+)\/([a-zA-Z0-9]+)\/?$ views/$1.php?$2=$3 [L]
RewriteRule ^([a-zA-Z0-9]+)\/?$ views/$1.php [L]

Subdirectories, slashes and single files

first if someone founds a better title...go for it!
The question is, i'm trying to setup mod rewrite so that all these are translated to
http:// mysite/phpnuget/nuget/metadata.php
preserving the query string parameters
http:// mysite/phpnuget/nuget/$metadata
http:// mysite/phpnuget/nuget/$metadata/wetheaverdirstructure
http:// mysite/phpnuget/nuget/$metadata/
http:// mysite/phpnuget/nuget/$metadata?wetheaver=parameter
http:// mysite/phpnuget/nuget/$metadata/wetheaverdirstructure?wetheaver=parameter
http:// mysite/phpnuget/nuget/$metadata/?wetheaver=parameter
I doged for hours through the internet with various tests but not one of the tenth i tryed worked...
(the space before mysite is only to allow posting on Stackoverflow... think it does not exists!)
Can someone give me some hint?
Thank you,
Enrico
(Changed the title)
You may try this:
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !metadata\.php [NC]
RewriteCond %{REQUEST_URI} ^/phpnuget/nuget/\$metadata [NC]
RewriteRule .* phpnuget/nuget/metadata.php [L]
It will map silently any URL like this one:
http://mysite.com/phpnuget/nuget/$metadata/anything
To:
http://mysite.com/phpnuget/nuget/metadata.php removing the last anything segment.
If the incoming URL holds a query, it will be appended to metadata.php automatically in query format. i.e. ?this=is&a=query
Just in case, there is NO need for the QSA flag.
To make the substitution URL permanent and visible, replace the last [L] with [L,R=301]

How to retrieve, if mod_rewrite should rewrite?

I'm trying to do a mod_rewrite. The given URLS should look like this:
RewriteRule ^([a-zA-Z0-9\-)/([a-zA-Z0-9\-)$ index.php?anum=$1&aname=$2
The problem with that is, that it also rewrites things like javascript files matching the pattern, because they are in some subdirectories.
So how do I achieve, that mod-rewrite only accepts URLs like "foo/bar" and no URL like "fizz/buzz/jq.js"?
if you want
test/foo/dir/test44.j4s
to
dir test44.j4s
RewriteRule ^foo/(.*)/(.*) index.php?dir=$1&file=$2 [L]
You may ensure to not match existing files or directories by using the following rewrite conditions:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([a-zA-Z0-9\-)/([a-zA-Z0-9\-)$ index.php?anum=$1&aname=$2
See http://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritecond for the full power of RewriteCond.
I often use
RewriteCond $1 !^favicon.ico|^css|^js
to exclude favicon, and the css and js directories from beeing rewritten.
The following should exclude Javascript and CSS files:
RewriteCond $1 !\.css$|\.js$
Just do this thing: if the request doesn't end with a known extension, then apply the rewrite rule:
RewriteCond %{REQUEST_URI} (.*)(\.(css|js|pdf|jpg|jpeg|gif|png|ico))
RewriteRule ^([a-zA-Z0-9\-)/([a-zA-Z0-9\-)$ index.php?anum=$1&aname=$2
Or do it the opposite side: if the request starts with a known extension, then stops, otherwise keep on applying the rest:
RewriteRule (.*)(\.(css|js|htc|pdf|jpg|jpeg|gif|png|ico)){1}$ $1$2 [QSA,L]
RewriteRule ^([a-zA-Z0-9\-)/([a-zA-Z0-9\-)$ index.php?anum=$1&aname=$2
Both solutions should work.
Tell me which one did ;)
Olivier

Apache Rewrite Issue, Custom URI

I have a solution but it is one that I know is not the greatest and would better be solved with a full rewrite. Basically I have 3 rewrites that will go to the correct areas I want and do what they need to do. However in order to switch between where I need to go I had to write a URI class to strip through the url set the page and vars manually. It all works out great but the urls are a pain in the ass specially if not formatted exactly.
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^/bsiadmin/$ /bsiadmin/index.php [L,QSA]
RewriteRule ^/bsiadmin/(.+)/$ /bsiadmin/index.php?page=$1 [L,QSA]
RewriteRule ^/(.+)/$ /index.php?page=$1 [QSA]
So the first rule will make sure to direct everything to the directory and not the root index.php, the second rule does the same if there is a "page" specified. The last rule will take anything else and make sure it uses the root index.php and goes from there.
Example of urls:
http://mysite.test/icecream/id=2/
My custom uri class would strip this clean and set id as a $_REQUEST var.
I guess what I really want to know is how can I just rewrite a simple url such as:
http://mysite.test?page=icecream&id=2
AS
http://mysite.test/icecream/id/2/
Without any limitation on how many vars can be passed and the directory that does exist "bsiadmin" to display without me having to use a uri class to direct it.
Thanks for the help.
You can use mod_rewrite to do so:
RewriteRule ^/([^/]+)/([^/]+)/([^/]+)/(.*) /$1/$4?$2=$3 [N,QSA]
RewriteRule ^/([^/]+)/$ /bsiadmin/index.php?page=$1 [L,QSA]
But I think the best would be to use PHP for that job. Because with mod_rewrite you can only rewrite a fixed amount of URL arguments at a time (here one with every rewrite). With PHP you can parse any arbitrary number of arguments like this:
$_SERVER['REQUEST_URI_PATH'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$segments = explode('/', $_SERVER['REQUEST_URI_PATH']);
if (count($segments) > 2) {
for ($i=4, $n=count($segments); $i<$n; $i+=2) {
$_GET[rawurledecode($segments[$i-1])] = rawurldecode($segments[$i]);
}
$_GET['page'] = rawurldecode($segments[1]);
}
Then all you need for mod_rewrite is this single rule to rewrite the requests to your index.php:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule !^/bsiadmin/index\.php$ /bsiadmin/index.php [L]

Why is Apache mod_rewrite not behaving as expected

I want to redirect URLs from an old site that used raw URL requests to my new site which I have implemented in CodeIgniter. I simply want to redirect them to my index page. I also would like to get rid of "index.php" in my URLs so that my URLs can be as simple as example.com/this/that. So, this is the .htaccess file I have created:
RewriteEngine on
Options FollowSymLinks
RewriteBase /
RewriteCond $1 ^assets
RewriteRule ^(.*)$ example/production/$1
RewriteCond %{QUERY_STRING} .+
RewriteRule ^(.*)$ index.php? [R=301]
RewriteCond $1 !^(index\.php|example|robots\.txt)
RewriteRule ^(.*)$ index.php/$1
It should also be noted that my index.php is actually a symlink to example/production/index.php.
Now, the first rule works as expected - all my styles and images show up just fine, it's the second two rules I'm having trouble with. The second rule is basically to destroy the query string and redirect to my index page (externally). So, I found this in the Apache manual:
Note: Query String
The Pattern will not be matched against the query string. Instead, you must use a RewriteCond with the %{QUERY_STRING} variable. You can, however, create URLs in the substitution string, containing a query string part. Simply use a question mark inside the substitution string, to indicate that the following text should be re-injected into the query string. When you want to erase an existing query string, end the substitution string with just a question mark. To combine a new query string with an old one, use the [QSA] flag.
However, when I try to access one of the old pages, instead of redirecting to my index page, I get a 404 page not found error. I have figured out a workaround by making it an internal redirect, but I would really like it to be external.
The next problem, and the one that has been baffling me the most is with the third rule. I would expect this to do something like the following. If I type in:
http://example.com/this/thing
I would expect it to re-route to
http://example.com/index.php/this/thing
Unfortunately, this does not work. Instead, no matter what I type in, it always routes to my index page as if nothing else was in the URL (it just goes to http://example.com/).
Furthermore, and even more confusing to me, if I replace that rule with the following:
RewriteCond $1 !^(index\.php|example|robots\.txt)
RewriteRule ^(.*)$ index.php/this/thing
If I type in a URL such as http://example.com/other/thing, then it will go to http://example.com/index.php/this/thing as expected, BUT if I type in http://example.com/this/thing it goes to http://example.com/ (my index page). I can't make heads or tails out of it. Any help would be greatly appreciated.
This should solve your index.php problem and it will simply detect if a robots.txt is available:
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
hmmm - this doesn't seem to work either. The problem is my URLs aren't really asking for a filename or directory anyway. For example: example.com/index.php/this/thing should call the 'thing' method of the 'this' controller. – Steven Oxley
The condition is: If request is NOT a file and NOT a directory, so that was right, what you should have done is combine the appending of the request string:
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]