Recursively look in subdirectories for matching filename. - apache

if a file is not found, i would like to recursively check in subdirectories until a file with the requested name is found and deliver that.
example:
the request is:
http://domain.com/file.txt
look recursively in subdirectories until file.txt is found, then deliver:
http://domain.com/foo/bar/file.txt
the only thing i have managed so far is the trigger when a file is not found:
RewriteCond %{REQUEST_FILENAME} !-f

Take this into a loader script. First, have this rewrite in your application (vhost configuration or .htaccess file)
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.+)$ /loader.php?q=$1 [L,QSA]
Then have a loader.php script in the root directory of your website, that would search for a file recursively and either redirect to the proper URL or load the file, get the mime tipe, set the proper headers and respond with the content of that file. Something like this:
<?php
// Do some processing here to extract the proper file name
// from $_REQUEST['q'] into a variable called $filename
// Assume we have the $filename
$it = new RecursiveDirectoryIterator(__DIR__); // Start search where loader.php is located
foreach (new RecursiveIteratorIterator($it) as $file) {
if (pathinfo($file, PATHINFO_BASENAME) == $filename) {
$returnFile = $file;
break;
}
}
// Check if the file was found
// Do a proper redirect to the 404 page here if you need more
if (empty($returnFile)) {
header ("HTTP/1.0 404 Not Found");
die();
}
// You need to do some fancy magic here to set the proper content type
// We will return plain text for now
header("Content-Type:text/plain");
// Handle large files
set_time_limit(0);
$file = #fopen($file_path,"rb");
while(!feof($file))
{
print(#fread($file, 1024*8));
ob_flush();
flush();
}
/* EndOfScript */
And that's about it, I guess... I haven't tested this AT ALL, so there might be a lot wrong with it! Then again, you can get the main picture of how you can handle this issue!

Related

Dynamic datetime value for a header as condition for mod_rewrite apache

I have a directory './output/' with images which are only accessible if a header ('testheader') with the value 'tst' has been send by the browser. It works by these lines in .htaccess using mod_rewrite. The .htaccess file is located in './output/':
RewriteEngine On
RewriteCond "%{HTTP:testheader}" !tst
RewriteRule ^ - [F]
To test it I'm running this code on my webserver:
<!DOCTYPE html>
<head>
<script>
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob'; //so you can access the response like a normal URL
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
var img = document.createElement('img');
img.src = URL.createObjectURL(xhr.response);
document.body.appendChild(img);
}
};
xhr.open('GET', 'https://www.example.com/output/down.png', true);
xhr.setRequestHeader('testheader','tst');
xhr.send();
</script>
</head>
<body></body>
</html>
This verifies and works great, when changing values of the 'testheader'!
Now I want to go next-level and create a more dynamic solution, I want to feed the 'testheader' with a dynamic date time value, YYYYMMDD, for example '20220817'. If the header that is send is smaller than this integer, it should be forbidden.
Reading through the manuals
https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html
https://harrybailey.com/2015/08/htaccess-redirects-based-on-date-and-time/
I came up with this:
RewriteEngine On
RewriteCond %{HTTP:testheader} <%{TIME_YEAR}%{TIME_MON}%{TIME_DAY}
RewriteRule ^ - [F]
Now with the above code I set the header like this:
xhr.setRequestHeader('testheader','20220817'); //should be changed to current date
But whatever date I have set, now the images in './output/' are always blocked.
Maybe my syntax in .htaccess is wrong? I'm not sure if I'm allowed to use '%{TIME_YEAR}%{TIME_MON}%{TIME_DAY}' on the right side of the equation in RewriteCond.
Hope one of you out there has a brilliant solution!
SOLUTION:
It was just there in the manual under section 5.
https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html
Using the 'expr' with -strmatch, it does as requested.
With this .htaccess the issue is addressed:
RewriteEngine On
RewriteCond expr "! %{HTTP:testheader} -strmatch '%{TIME_YEAR}%{TIME_MON}%{TIME_DAY}'"
RewriteRule ^ - [F]

Redirect, Hide Folder Name and enabling URL access with multiple subdirectory [duplicate]

I have a URL that looks like:
url.com/picture.php?id=51
How would I go about converting that URL to:
picture.php/Some-text-goes-here/51
I think WordPress does the same.
How do I go about making friendly URLs in PHP?
You can essentially do this 2 ways:
The .htaccess route with mod_rewrite
Add a file called .htaccess in your root folder, and add something like this:
RewriteEngine on
RewriteRule ^/?Some-text-goes-here/([0-9]+)$ /picture.php?id=$1
This will tell Apache to enable mod_rewrite for this folder, and if it gets asked a URL matching the regular expression it rewrites it internally to what you want, without the end user seeing it. Easy, but inflexible, so if you need more power:
The PHP route
Put the following in your .htaccess instead: (note the leading slash)
FallbackResource /index.php
This will tell it to run your index.php for all files it cannot normally find in your site. In there you can then for example:
$path = ltrim($_SERVER['REQUEST_URI'], '/'); // Trim leading slash(es)
$elements = explode('/', $path); // Split path on slashes
if(empty($elements[0])) { // No path elements means home
ShowHomepage();
} else switch(array_shift($elements)) // Pop off first item and switch
{
case 'Some-text-goes-here':
ShowPicture($elements); // passes rest of parameters to internal function
break;
case 'more':
...
default:
header('HTTP/1.1 404 Not Found');
Show404Error();
}
This is how big sites and CMS-systems do it, because it allows far more flexibility in parsing URLs, config and database dependent URLs etc. For sporadic usage the hardcoded rewrite rules in .htaccess will do fine though.
If you only want to change the route for picture.php then adding rewrite rule in .htaccess will serve your needs, but, if you want the URL rewriting as in Wordpress then PHP is the way. Here is simple example to begin with.
Folder structure
There are two files that are needed in the root folder, .htaccess and index.php, and it would be good to place the rest of the .php files in separate folder, like inc/.
root/
inc/
.htaccess
index.php
.htaccess
RewriteEngine On
RewriteRule ^inc/.*$ index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
This file has four directives:
RewriteEngine - enable the rewriting engine
RewriteRule - deny access to all files in inc/ folder, redirect any call to that folder to index.php
RewriteCond - allow direct access to all other files ( like images, css or scripts )
RewriteRule - redirect anything else to index.php
index.php
Because everything is now redirected to index.php, there will be determined if the url is correct, all parameters are present, and if the type of parameters are correct.
To test the url we need to have a set of rules, and the best tool for that is a regular expression. By using regular expressions we will kill two flies with one blow. Url, to pass this test must have all the required parameters that are tested on allowed characters. Here are some examples of rules.
$rules = array(
'picture' => "/picture/(?'text'[^/]+)/(?'id'\d+)", // '/picture/some-text/51'
'album' => "/album/(?'album'[\w\-]+)", // '/album/album-slug'
'category' => "/category/(?'category'[\w\-]+)", // '/category/category-slug'
'page' => "/page/(?'page'about|contact)", // '/page/about', '/page/contact'
'post' => "/(?'post'[\w\-]+)", // '/post-slug'
'home' => "/" // '/'
);
Next is to prepare the request uri.
$uri = rtrim( dirname($_SERVER["SCRIPT_NAME"]), '/' );
$uri = '/' . trim( str_replace( $uri, '', $_SERVER['REQUEST_URI'] ), '/' );
$uri = urldecode( $uri );
Now that we have the request uri, the final step is to test uri on regular expression rules.
foreach ( $rules as $action => $rule ) {
if ( preg_match( '~^'.$rule.'$~i', $uri, $params ) ) {
/* now you know the action and parameters so you can
* include appropriate template file ( or proceed in some other way )
*/
}
}
Successful match will, since we use named subpatterns in regex, fill the $params array almost the same as PHP fills the $_GET array. However, when using a dynamic url, $_GET array is populated without any checks of the parameters.
/picture/some+text/51
Array
(
[0] => /picture/some text/51
[text] => some text
[1] => some text
[id] => 51
[2] => 51
)
picture.php?text=some+text&id=51
Array
(
[text] => some text
[id] => 51
)
These few lines of code and a basic knowing of regular expressions is enough to start building a solid routing system.
Complete source
define( 'INCLUDE_DIR', dirname( __FILE__ ) . '/inc/' );
$rules = array(
'picture' => "/picture/(?'text'[^/]+)/(?'id'\d+)", // '/picture/some-text/51'
'album' => "/album/(?'album'[\w\-]+)", // '/album/album-slug'
'category' => "/category/(?'category'[\w\-]+)", // '/category/category-slug'
'page' => "/page/(?'page'about|contact)", // '/page/about', '/page/contact'
'post' => "/(?'post'[\w\-]+)", // '/post-slug'
'home' => "/" // '/'
);
$uri = rtrim( dirname($_SERVER["SCRIPT_NAME"]), '/' );
$uri = '/' . trim( str_replace( $uri, '', $_SERVER['REQUEST_URI'] ), '/' );
$uri = urldecode( $uri );
foreach ( $rules as $action => $rule ) {
if ( preg_match( '~^'.$rule.'$~i', $uri, $params ) ) {
/* now you know the action and parameters so you can
* include appropriate template file ( or proceed in some other way )
*/
include( INCLUDE_DIR . $action . '.php' );
// exit to avoid the 404 message
exit();
}
}
// nothing is found so handle the 404 error
include( INCLUDE_DIR . '404.php' );
this is an .htaccess file that forward almost all to index.php
# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteCond %{REQUEST_URI} !-l
RewriteCond %{REQUEST_FILENAME} !\.(ico|css|png|jpg|gif|js)$ [NC]
# otherwise forward it to index.php
RewriteRule . index.php
then is up to you parse $_SERVER["REQUEST_URI"] and route to picture.php or whatever
PHP is not what you are looking for, check out mod_rewrite
Although already answered, and author's intent is to create a front controller type app but I am posting literal rule for problem asked. if someone having the problem for same.
RewriteEngine On
RewriteRule ^([^/]+)/([^/]+)/([\d]+)$ $1?id=$3 [L]
Above should work for url picture.php/Some-text-goes-here/51. without using a index.php as a redirect app.

Slim framework and GET/PUT/POST methods

For example, i use this code for testing routes:
$app->get('/api', function () {
echo 'get!';
});
$app->post('/api', function () {
echo 'post!';
});
$app->put('/api', function () {
echo 'put!';
});
For api testing i use RestClient plugin for Chrome.
When i try do GET request, response is 'get!'. Its good.
But:
When i try do POST request, response also is 'get!'. Why? Its must be 'post!'.
When i try do PUT request, (in Response Headers: Allow: GET,HEAD,POST,OPTIONS,TRACE ) Slim response have 405 error (Method Not Allowed) with message:
"The requested method PUT is not allowed for the URL /api."
What am I doing wrong?
Be sure that your .htaccess is the following (from slimphp/Slim#2.x):
RewriteEngine On
# Some hosts may require you to use the `RewriteBase` directive.
# If you need to use the `RewriteBase` directive, it should be the
# absolute physical path to the directory that contains this htaccess file.
#
# RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]

htaccess rewrite rule with : in the url on windows

I'm trying to do a rewrite rule that'll allow only P00:0:R It's for a pagination system for a website.
I tried it using php, and it works fine. But how do I get something like this into a rewrite rule?
$x = 'P10:10:R';
if(pageNos($x)) {
echo 'Passed';
} else {
echo 'Failed';
}
//
function pageNos($page) {
if(preg_match('/^[P]{1}[0-9]{1,10}[:]{1}[0-9]{1,10}[:]{1}[L|R]{1}$/',$page)) {
return true;
} else {
return false;
}
}
All I get with rule is
RewriteEngine On
RewriteRule ^/?([P]{1}[0-9]{1,10}[:]{1}[0-9]{1,10}[:]{1}[L|R]{1}+)/?$ /test/index.php [NC,L]
Forbidden
You don't have permission to access /P10:2:R on this server.
You're getting forbidden error because : is not allowed in URLs by Apache on Windows. On Windows the colon is forbidden as it is used as the drive letter separator.
However do note that colon (:) is allowed as a valid character under Linux and other non-windows platforms.

Can I redirect to the newest file in directory using .htaccess?

I want to create .htaccess rule for situation like below:
I have a link to file: http://something.com/images/some/image_001.png
If this file doesn't exists I want to redirect to the newest file in /images/some directory
Is something like this possible using .htaccess? I know that I can check if file exists with RewriteCond, but don't know if it is possible to redirect to the newest file.
Rewriting to a CGI script is your only option from a .htaccess, technically you could use a programatic RewriteMap with a RewriteRule in a httpd.conf file.
The script can serve the file directly, so with an internal rewrite the logic can be entirely server side e.g.
.htaccess Rule
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-s
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^images/(.*)$ /getLatest.php [L]
Where getLatest.php is something like:
<?php
$dir = "/srv/www/images";
$pattern = '/\.(jpg|jpeg|png|gif)$/';
$newstamp = 0;
$newname = "";
if ($handle = opendir($dir)) {
while (false !== ($fname = readdir($handle))) {
// Eliminate current directory, parent directory
if (preg_match('/^\.{1,2}$/',$fname)) continue;
// Eliminate all but the permitted file types
if (! preg_match($pattern,$fname)) continue;
$timedat = filemtime("$dir/$fname");
if ($timedat > $newstamp) {
$newstamp = $timedat;
$newname = $fname;
}
}
}
closedir ($handle);
$filepath="$dir/$newname";
$etag = md5_file($filepath);
header("Content-type: image/jpeg");
header('Content-Length: ' . filesize($filepath));
header("Accept-Ranges: bytes");
header("Last-Modified: ".gmdate("D, d M Y H:i:s", $newstamp)." GMT");
header("Etag: $etag");
readfile($filepath);
?>
Note: Code partially borrowed from the answers in: PHP: Get the Latest File Addition in a Directory