mod_rewrite RewriteCond based on Last-modified? (.htaccess) - .htaccess

I know that we can easily base a RewriteCond on any http request header. But can we check (some of) the response headers that are going to be sent? In particular, the Last-modified one?
I want to rewrite a url only when the Last-modified date is older than 30 minutes and I'm trying to avoid the overhead of delegating that check to a php file every single time a file from that directory is requested.
Thanks in advance!

No, that’s not possible. But you could use a rewrite map to get that information from a program with less overhead than PHP, maybe a shell script.
Here’s an example bash script:
#!/usr/bin/env bash
while read line; do
max_age=${line%%:*}
filename=${line#*:}
if [[ -f $filename ]]; then
lm=$(stat -f %m "$filename")
if [[ $(date +%s)-$lm -le $max_age ]]; then
echo yes
else
echo no
fi
else
echo no
fi
done
The declaration of the rewrite map needs to be placed in your server or virtual host configuraion file as the program is just started once and then waits for input:
RewriteMap last-modified-within prg:/absolute/file/system/path/to/last-modified-within.sh
And then you can use that rewrite map like this (.htaccess example):
RewriteCond %{last-modified-within:30:%{REQUEST_FILENAME}} =yes
RewriteRule ^foo/bar$ - [L]
RewriteRule ^foo/bar$ script.php [L]

The outbound headers do not exist until much later than mod_rewrite is acting. There also isn't any file-modification-time checking functionality built into mod_rewrite, so the closest you'd get using it is making a RewriteMap of the External Rewriting Program variety to find out whether the file in question has been modified.
If I understand your application correctly, you could also look into having a cron job delete files in that directory that are older than 30 minutes, and then rewriting on a file-nonexistence condition.

Have you considered using mod_proxy, mod_cache, and/or squid? It sounds like you're trying to roll your own caching...

Related

execute script when directory listing by .htaccess

I have a http server (apache HTTPD v2.4) where directory listing is enabled. I configured it to use the 'fancy-index' from https://github.com/Vestride/fancy-index, and works great. However, its searching function didn't give what I was looking for.
I'd like to configure the webserver such that if I goto http://mywebserver.com/someDir/*?list, it will return a list of all the files in someDir in a "file, filesize" format.
Or, if I goto http://mywebserver.com/someDir/essay2020*.txt?list , I'll get a custom list of all essay2020*.txt files. Or some form of a simple regex: say "essay2020[0-2]*.txt".
I can write a php/perl/python script that can read a directory and return the 'file, filesize' list. But how do I configure the .htaccess to call this script? Do I have to muck around with 'RewriteRule'? or something special?
As an added note, I'm not limited to modifying .htaccess only. If I can do all of this by modifying the httpd.conf, I'm open to that suggestion too.
Thanks.
The answer in Comments by CBroe did it. By just modifying the apache2.conf file to add:
RewriteEngine on
RewriteCond %{QUERY_STRING} ^list$
RewriteRule .* /search.php?path=%{REQUEST_URI}
within my <VirtualHost>, I'm now able to forward everything to search.php, where there I can parse and do what needs to be done. All w/o revealing search.php's existence. Thanks.

Case insensitive rewrite rule

So I have a rewrite rule that it's needed because of the old site, and have some images that are linked from another website, the problem is that I can' manually fix the url's because there are a lot of images.
So before the website was hosted on Windows, and there was no problem if you want to link an image like this:
http://www.example.com/Fder69.JPG and the filename was "fder69.JPG" it did work, now I have a rewrite rule like this:
RewriteRule ^([^/.]+.JPG)$ /imgs/$1 [L,NC,R=302] so basicly rewrites the old links to the new structure, but some of the images that don't have the exact filename don't work.
Is there a way to accomplish this? with something like CheckSpelling Off or ? can I make the rewrite cond to accept .JPG and .jpg, any tips?
One option is to rename all the files to be all-lowercase, which generally leads to nicer URLs, and then redirect any requests for mixed-case versions to all lowercase.
This approach has the advantage that each file ends up with only a single URL, rather than the same content appearing under multiple URLs as would be the case if you used mod_speling. This is good for search engine rankings, among other things.
One way to rename all the files would be to generate a bunch of mv commands in a shell script, like this:
find . | perl -ne 'chomp; print "mv \"", $_, "\" \"", lc $_, "\"\n";' > rename-files.sh
Note that I make no warranties that this won't mess up all your files, but I think it's right...
The redirection is done using a "RewriteMap", which is a function which can be applied on the right hand side of a RewriteRule. One of the built-in mappings available is int:tolower, allowing you to do this:
# Alias the mapping function as "lc"
RewriteMap lc int:tolower
# Perform the substitution if the URL contains uppercase letters
RewriteCond %{REQUEST_URI} [A-Z]
# Issue a 301 redirect to the all-lowercase version
RewriteRule /(.*) /${lc:$1} [R=permanent,L]

Htaccess caching system in subfolder not working

Sorry if this is a duplicate: I found many questions about caching system, but my problem seems to tied to the fact that the whole script is working within a subfolder.
All I need to do is implementing a simple caching system for my website, but I can't get this to work.
Here's my .htaccess file (widely commented to be clear - sorry if too many comments are confusing):
RewriteEngine on
# Map for lower-case conversion of some case-insensitive arguments:
RewriteMap lc int:tolower
# The script lives into this subfolder:
RewriteBase /mydir/
# IMAGES
# Checks if cached version exists...
RewriteCond cache/$1-$2-$3-{lc:$4}.$5 -f
# ...if yes, redirects to cached version...
RewriteRule ^(hello|world)\/image\/([a-zA-Z0-9\.\-_]+)\/([a-zA-Z0-9\.\-_]+)\/([a-zA-Z0-9\.\-_\s]+)\.(png|gif|jpeg?|jpg)$ cache/$1-$2-$3-{lc:$4}.$5 [L]
# ...if no, tries to generate content dynamically.
RewriteRule ^(hello|world)\/image\/([a-zA-Z0-9\.\-_]+)\/([a-zA-Z0-9\.\-_]+)\/([a-zA-Z0-9\.\-_\s]+)\.(png|gif|jpeg?|jpg)$ index.php?look=$1&action=image&size=$2&data=$3&name=$4&format=$5 [L,QSA]
# OTHER
# This is always non-cached.
RewriteRule ^(hello|world)\/([a-zA-Z0-9\.\-_\s]+)\/([a-zA-Z0-9\.\-_\s]+)?\/?$ index.php?look=$1&action=$2&name=$3 [QSA]
Now, the issue is that the RewriteCond seems to be always failing, as the served image is always generated by PHP. I also tried prepending a %{DOCUMENT_ROOT}, but is still not working. If I move the whole script to the root directory, it magically starts working.
What am I doing wrong?
Well one thing that you are doing wrong is trying to use a rewrite map in an .htaccess file. in the first place. According to the Apache documentation:
The RewriteMap directive may not be used in <Directory> sections or .htaccess files. You must declare the map in server or virtualhost context. You may use the map, once created, in your RewriteRule and RewriteCond directives in those scopes. You just can't declare it in those scopes.
If your ISP / sysadmin has already defined the lc map then you can use it. If not then you can only do case-sensitive file caching on Linux, because its FS naming is case sensitive. However, since these are internally generated images, just drop the case conversion and stick to lower case.
%{DOCUMENT_ROOT} may not be set correctly at time of mod_rewrite execution on some shared hosting configurations. See my Tips for debugging .htaccess rewrite rules for more hints. Also here is the equivalent lines from my blog's .htaccess FYI. The DR variable does work here, but didn't for my previous ISP, to I had to hard-code the parth
# For HTML cacheable blog URIs (a GET to a specific list, with no query params,
# guest user and the HTML cache file exists) then use it instead of executing PHP
RewriteCond %{HTTP_COOKIE} !blog_user
RewriteCond %{REQUEST_METHOD}%{QUERY_STRING} =GET [NC]
RewriteCond %{DOCUMENT_ROOT}html_cache/$1.html -f
RewriteRule ^(article-\d+|index|sitemap.xml|search-\w+|rss-[0-9a-z]*)$ \
html_cache/$1.html [L,E=END:1]
Note that I bypass the cache if the user is logged on or for posts and if any query parameters are set.
Footnote
Your match patterns are complicated because you are not using the syntax of regexps: use the \w and you don't need to escape . in [ ] or / . Also the jpeg isn't right is it? So why not:
RewriteRule ^(hello|world)/image/([.\w\-]+)/([.\w\-]+)/([\w\-]+\.(png|gif|jpe?g))$ \
cache/$1-$2-$3-$4 [L]
etc.. Or even (given that the file rule will only match for valid files in the cache)
RewriteRule ^(hello|world)/image/(.+?)/(.+?)/(.*?\.(png|gif|jpe?g))$ \
cache/$1-$2-$3-$4 [L]
The non-greedy modifier means that (.+?) is the same as ([^/]+) so doing hacks like ../../../../etc/passwd won't walk the file hierarchy.

htaccess - creating directories and files of the same name

I want to create a bunch of files without an extension showing at the end. The easiest way to do that was to do this:
/usa/index.php
/usa/alaska/index.php
/usa/alabama/index.php
/usa/california/index.php
What I want to do is this
/usa/alaska.php
/usa/alabama.php
/usa/california.php
and have it show up as:
/usa/alaska
/usa/alabama
/usa/california
However, I have one more level I want to add to this, the cities
/usa/alaska/adak.php
/usa/alaska/anchorage.php
/usa/california/los-angles.php
I don't want the ".php" showing up, but then each state exists as both a file and a directory. What I want is an htaccess rule that serves up the file version of the file, not the directory which is the default. I also want to strip the .php off of the end of the files so the final result looks like
/usa
/usa/alaska (alaska.php)
/usa/alaska/adak (adak.php)
I know I can get close to this by creating all the directories and using index.php for each directory, but then I will have thousands of directories each with one file in it and updating is a pain in the butt. I would much rather have one directory with 1000 files in it, than 1000 directories with 1 file in it.
Please, can someone point me in the right direction and know that I am doing this for all 50 states.
Jim
I would also suggest using a single php (e.g. index.php) file and redirecting all urls starting with usa to it, instead of separating them in different directories and files. The you'd need a couple of rewrite rules like the following
RewriteEngine On
RewriteRule ^usa/([^/.]+)$ index.php?state=$1 [L]
RewriteRule ^usa/([^/]+)/([^/.]+)$ index.php?state=$1&city=$2 [L]
So then in your index.php you'd only need to check the $_GET parameters.
Update:
If you don't feel comfortable enough to use a database and pull the needed data from there you could always use the parameters to dynamically include/require the needed files. Something like this
<?php
$source = ''; //or the 'ROOT' directory
if(isset($_GET['state'])) $source .= $_GET['state'].'/';
if(isset($_GET['city'])) $source .= $_GET['city'].'.php';
include($source); // here $source would be something like 'alaska/adak.php'
// and is assumed that the dir 'alaska' is on the same
// level as 'index.php'
?>
But to answer your original question nevertheless you could use the following .htaccess
RewriteEngine On
RewriteRule ^usa/([^/.]+)$ usa/$1.php [L]
RewriteRule ^usa/([^/]+)/([^/.]+)$ usa/$1/$2.php [L]
what about creating just one single file:
/usa/index.php
With
$_SERVER["REQUEST_URI"]
you can read the current URI.
Well, now if a user enters "http://domain.foo/usa/alaska" for example, he will get an 404 error of course.
But to call your index.php instead, you could write this line to the .htaccess:
ErrorDocument 404 /usa/index.php
Now the index.php receives everything what is written to the URI and you can match the result and include files or handle errors.
But maybe there is a better solution with .htaccess only, don't know. :)

Rewriting my dynamic url in htaccess file

I have a problem in rewriting my dynamic url pattern in localhost.
here is my url: http://localhost/realtor/?module=property&action=popular-residential-buy
and i want the url to be http://localhost/realtor/property/popular-residential-buy
I have done so far in my .htaccess file:
Options +FollowSymLinks
RewriteEngine on
RewriteRule type (.*)-(.*)$ /?module=$1&action=$2
but it's not working.
Your existing regular expression gobbles up too much of your URL. You'll end up with something other than what you want in $1 and $2 (probably realtor/property/popular-residential in $1, and just buy in $2, due to the first .* greedily matching as much as it can before back tracking one character at a time to find a match).
Based on the URL you provided, it looks like your regular expression should be something like ^realtor/([^/]*)/([^/]*)/?$. That will give you what you want in $1 and $2, and it should be a quicker regular expression AFAIK.
Past that, make sure that you do have the RewriteEngine properly configured on your particular server.
For Apache, you'll need to enable mod_rewrite. Look in your httpd.conf for LoadModule rewrite_module modules/mod_rewrite.so, and make sure it is uncommented.
IIS is a bigger can of worms -- let me know if that is what you are using, and I will write a more complete answer.

Resources