Dynamically serving files depending on whether they're written to file system - apache

Terrible title, but don't really know how to describe the situation too well.
I would like to dynamically resize/crop images. I'm hoping to config my apache to do the following:
A request for /img/profile.jpg?crop&p=50 is received
Apache checks if the file /img/profile.c.50.jpg exists
If it does exist, it serves that up statically without having to hit my php server
If it doesn't exist, it hits /cropper.php?path=/img/profile.jpg&p=50
This file then writes the image to file, and serves it
Request comes in again (see step 1)
I feel like making use of the FilesMatch directive in the .htaccess file could be of us but I really don't know where to start. Any links would be appreciated, or any ideas.
Thanks all.

\Personally, I'd delegate this task to a script (maybe using X-Sendfile), rather than a rewrite mess.
Here you go, you may have to tweak your document root and make your "profile" part more general:
RewriteEngine on
RewriteCond %{QUERY_STRING} "(?:^|&)crop&p=50"
RewriteCond %{REQUEST_URI} ^(/img/profile)(\.png)$
RewriteCond /your/document/root%1.c.50%2 -f
RewriteRule ^ %1.c.50%2 [L]
# hand over to crop script if the above doesn't match
RewriteCond %{REQUEST_URI} =/img/profile.png
RewriteRule ^ /cropper.php?path=%{REQUEST_URI}&p=50 [L]
Solution always involving a script:
(accessed through /img/profiles/... for clarity)
$1 captures the profile name (in this case assumed to be 'a-z' only
$2 captures the desired width
$3 the file extension
Which really simplifies the rewrite:
RewriteEngine on
RewriteRule ^/img/profiles/([a-z]+)\.([1-9][0-9]+)\.(jpe?g|gif|png)$ /cropper.php?profile=$1.$3&p=$2 [L]
The script would check whether the file exists and either deliver it or generate it first.
Solution taking the image name from the requested URL:
(As you mentionned in your comment)
RewriteEngine on
RewriteCond %{REQUEST_URI} ^(/img/profile)\.([1-9][0-9]+)\.(png|gif|jpe?g)$
RewriteCond /your/document/root%{REQUEST_URI} -f
RewriteRule ^ %{REQUEST_URI} [L]
# hand over to crop script if the above doesn't match
RewriteCond %{REQUEST_URI} ^(/img/profile)\.([1-9][0-9]+)\.(png|gif|jpe?g)$
RewriteRule ^ /cropper.php?path=$1.$3&p=$2 [L]
which may be rewritten to the following, if the images are stored directly within your document root:
RewriteCond %{REQUEST_URI} ^(/img/profile)\.([1-9][0-9]+)\.(png|gif|jpe?g)$
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f
RewriteRule ^ /cropper.php?path=$1.$3&p=$2 [L]

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]

Trouble with a mod_rewrite rule

I have a bit of a complicated rewrite scenario that I could use some expert advice on. I have a directory, "/directory" that contains some files. I would like to rewrite requests for files that don't exist in that directory to another directory which also exists, keeping the original directory in the URL and returning 404 for invalid paths. example
This exists:
/directory/file.php
This does not exist:
/directory/subdirectory/anotherfile.php
...but it does exist here:
/another_directory/subdirectory/anotherfile.php
So I would like
/directory/subdirectory/anotherfile.php to refer to /another_directory/subdirectory/anotherfile.php
... but if the folder/file doesn't exist in /another_directory/ the rule should return a 404.
I've tried various combinations but they all either don't return a 404 or they redirect. This is my latest attempt:
RewriteCond %{DOCUMENT_ROOT}/another_directory/$1 f [OR]
RewriteCond %{DOCUMENT_ROOT}/another_directory/$1 d
RewriteRule (.*) - [S=1]
RewriteRule ^directory/(.*)$ /another_directory/$1 [L, QSA]
If it helps to explain why I have this scenario, /another_directory/ is full of periodically regenerated static files that appear to be in the same directory as the existing files in /directory/ I am moving away from dynamic pages to generated static ones and don't want to mess with my serps and want to keep this large number of generated files isolated from the others.
Here's what I understood from your explanation:
/directory/subdirectory/anotherfile.php
This should be rewritten to
/another_directory/subdirectory/anotherfile.php
whether it exists or not. If I'm right, here's your answer, otherwise tell me more!
RewriteCond %{DOCUMENT_ROOT}/another_directory/$1 -f [OR]
RewriteCond %{DOCUMENT_ROOT}/another_directory/$1 -d
RewriteRule ^directory/(.*) another_directory/$1 [L,S=1]
RewriteCond %{REQUEST_FILENAME} -d [OR]
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule (.*) - [L]
RewriteRule (.*) 404.php?uri=/$1&id=1 [L]

htaccess rewrite real directory path to a different, non-existing url, hiding the real path

I would like to use a directory ___test like /___test/subdirectory1/ and have some contents, but never show this path in the url. Instead I would like the url /demo/subdirectory1 or say, /demo/subdirectory1/images/image.jpg to point to /___test/subdirectory1/images/image.jpg and the url on the browser to stay like: /demo/subdirectory1/images/image.jpg. Actually replacing ___test with demo
What I have tried with various problems although it looks like it works sometimes is:
RewriteRule ^demo/subdirectory1(.*) ___test/subdirectory1/$1
The above works only on: demo/subdirectory1 or demo/subdirectory1/ (with existing content example an index.html inside /___test/subdirectory1/) but not on demo/subdirectory1/somethingmore... although the content is there in this case as well, unfortunately it shows the real directory path in the url.
Additionally, I am using the following to through 404 to anything starting from /___test in the url:
RewriteCond %{THE_REQUEST} ^GET\ /___test/
RewriteRule ^___test/(.*) - [R=404,L,NC]
It works, but when I add this, then the previous goes to 404 too, unfortunately (again).
But most important for me is to make the first part right, even if I never make the second work.
Any help really appreciated.
Thanks.
Edit 1:
I have also tried adding the following inside an /___test/subdirectory1/.htaccess
Options +FollowSymLinks
RewriteEngine On
RewriteBase /___test/subdirectory1
no success.
Edit 2:
Although it doesn't work well, the best I came up with so far with the help of Jon Lin is the following:
RewriteCond %{REQUEST_URI} ^/demo/subdirectory1(.*)
RewriteCond %{DOCUMENT_ROOT}/___test/subdirectory1/%1 -f [OR]
RewriteCond %{DOCUMENT_ROOT}/___test/subdirectory1/%1 -d
RewriteRule ^ /___test/subdirectory1/%1 [L]
/demo/subdirectory1 <- OK
/demo/subdirectory1/ <- OK
/demo/subdirectory1/subdirectory2 <- Exposes real path
/demo/subdirectory1/subdirectory2/ <- OK
/demo/subdirectory1/subdirectory2/subdirectory3 <- Exposes real path
/demo/subdirectory1/subdirectory2/subdirectory3/ <- OK
As you can see, whatever is deeper level than subdirectory1 has problem. I just cannot understand why. Testing this on a clean .htaccess file existing in the root of the site (no other deeper) on a linux apache.
For the most part you're on the right track, but you may need to add a couple of checks so that the __test directory isn't exposed:
RewriteEngine On
# prevent mod_dir from redirecting to trailing slash
RewriteCond %{REQUEST_URI} ^/demo/subdirectory1/(.*[^/])
RewriteCond %{DOCUMENT_ROOT}/___test/subdirectory1/%1 -d
RewriteRule ^ /demo/subdirectory1/%1/ [L,R=301]
RewriteCond %{REQUEST_URI} ^/demo/subdirectory1$
RewriteRule ^ /demo/subdirectory1/ [L,R=301]
# check if the file actually exists first
RewriteCond %{REQUEST_URI} ^/demo/subdirectory1/(.*)
RewriteCond %{DOCUMENT_ROOT}/___test/subdirectory1/%1 -f [OR]
RewriteCond %{DOCUMENT_ROOT}/___test/subdirectory1/%1 -d
# and rewrite only if the resource exists within ___test
RewriteRule ^ /___test/subdirectory1/%1 [L]
Making sure that you've ended rewriting in the current iteration by using [L].
Your 404 rule should work but you should remove the trailing slash so that mod_dir won't try to redirect and expose the ___test directory:
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /___test
RewriteRule ^ - [R=404,L,NC]

why doesn't this .htacess file prevent users from accessing content I want secured?

I am trying to accomplish several things with this .htacess file, but cannot seem to
get it to serve the rewrites I need, while preventing unathorized access to files I want
hidden. My goal is to allow any file located in /sections/section_name/webroot/ to be accessed through /section_name/. So, /admin/images/kittens/cat.jpg would serve up /sections/admin/webroot/images/kittens/cat.jpg if it existed. I want to be able to have multiple sections. If a section is not specified, but the file exists in /sections/default/webroot, then I'd like for that to be served. Any other request should
go to /dispatcher.php. I thought I had this working, until I requested a configuration
file in /config and was able to see it. Then I realized I could basically view any file
if I knew that path.
How can I fix this security issue while still keeping the rewrites working?
Here is my .htacess file:
Options +FollowSymlinks -MultiViews -Indexes
RewriteEngine On
# If a file is requested in the admin webroot, and it exists, allow it to pass through
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^admin(/.*)$ sections/admin/webroot/$1 [L,QSA]
# if the requested url begins with /customers and it is located in /sections/customers/webroot
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^customers(/.*)$ sections/customers/webroot/$1 [L,QSA]
# if the requested url begins with /resellers and it is located in /sections/resellers/webroot
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^resellers(/.*)$ sections/resellers/webroot/$1 [L,QSA]
# if the requested file does not begin with /admin, /customers, or /resellers, and is in /sections/default/webroot, then serve it
RewriteCond %{DOCUMENT_ROOT}/sections/default/webroot/$1 -f
RewriteRule ^(.*)$ sections/default/webroot/$1 [QSA,L,NC]
# Send everything else to the dispatcher
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^(.*)$ dispatcher.php [QSA,L]
I know this isn't quite a solution, but have you tried cutting it all the way down and then adding the rules back one by one, testing each one along the way? For starters, if you can get files in /config that you're not supposed to, I'd try cutting everything except the final rule (which is supposed to send requests in /config to dispatcher) and see if that works. If it does, keep adding things back slowly until you see which specific rule is allowing /config files to be seen. Then you know what to fix.
Adding this to the end worked. Anything that wasn't a valid file requested in one of the
webroot folders, and that wasn't a call to dispatcher.php, was redirected to dispatcher.
RewriteCond %{REQUEST_URI} !=/dispatcher.php
RewriteCond %{REQUEST_URI} !^/sections/[a-zA-Z0-9_-]+/webroot/
RewriteCond %{SCRIPT_FILENAME} -f
RewriteRule ^(.*)$ dispatcher.php [QSA,L]

RewriteRule checking file in rewriten file path exists

How can you use ModRewrite to check if a cache file exists, and if it does, rewrite to the cache file and otherwise rewrite to a dynamic file.
For example I have the following folder structure:
pages.php
cache/
pages/
1.html
2.html
textToo.html
etc.
How would you setup the RewriteRules for this so request can be send like this:
example.com/pages/1
And if the cache file exists rewrite tot the cache file, and if the cache file does not exists, rewrite to pages.php?p=1
It should be something like this: (note that this does not work, otherwise I would not have asked this)
RewriteRule ^pages/([^/\.]+) cache/pages/$1.html [NC,QSA]
RewriteCond %{REQUEST_FILENAME} -f [NC,OR]
RewriteCond %{REQUEST_FILENAME} -d [NC]
RewriteRule cache/pages/([^/\.]+).html pages.php?p=$1 [NC,QSA,L]
I can off coarse do this using PHP but I thought it had to be possible using mod_rewrite.
RewriteRule ^pages/([^/\.]+) cache/pages/$1.html [NC,QSA]
# At this point, we would have already re-written pages/4 to cache/pages/4.html
RewriteCond %{REQUEST_FILENAME} !-f
# If the above RewriteCond succeeded, we don't have a cache, so rewrite to
# the pages.php URI, otherwise we fall off the end and go with the
# cache/pages/4.html
RewriteRule ^cache/pages/([^/\.]+).html pages.php?p=$1 [NC,QSA,L]
Turning off MultiViews is crucial (if you have them enabled) as well.
Options -MultiViews
Otherwise the initial request (/pages/...) will get automatically converted to /pages.php before mod_rewrite kicks in. You can also just rename pages.php to something else (and update the last rewrite rule as well) to avoid the MultiViews conflict.
Edit: I initially included RewriteCond ... !-d but it is extraneous.
Another approach would be to first look if there is a chached representation available:
RewriteCond %{DOCUMENT_ROOT}/cache/$0 -f
RewriteRule ^pages/[^/\.]+$ cache/$0.html [L,QSA]
RewriteRule ^pages/([^/\.]+)$ pages.php?p=$1 [L,QSA]
To generalize the question: insert this above the rule that should not be matched if the file exists.
RewriteCond %{REQUEST_FILENAME} !-f
Sean Bright's answer provides a nice worked example for the caching question, but this line works more broadly. In my case, I have a link shortener where people can choose custom URLs and I didn't want it to be able to override existing files such as favicon.ico. Adding this line before the rewriterule fixed that issue.