So, after searching for a solution all over this community, my question is as follow:
Im working within the Wordpress enviroment, Apache server. I have a folder within uploads named /restricted/. Everything in here (any file extension) can only be accessed if:
A cookie named 'custom_cookie' is set
And this cookie value must be a partial match of the URL request
If these conditions fail, an image is served. Inside this /restricted/ folder I got a .htaccess file. Everything must (prefered) be done in that htaccess file, not on root htaccess file.
The cookie is set by functions.php, no problem with that
part. And comments about security is not the question here
This is an url example (localhost): http://localhost/komfortkonsult/wp-content/uploads/restricted/some-file.jpg?r=870603c9d23f2b7ea7882e89923582d7
The first condition A cookie named custom_cookie is set, everything is working with this:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /komfortkonsult/
RewriteCond %{REQUEST_URI} ^.*uploads/restricted/.*
RewriteCond %{HTTP_COOKIE} !custom_cookie
RewriteRule . /komfortkonsult/restricted.png [R,L]
</IfModule>
However, the next part Im totally out in the blue, But I tried and failed with the following approaches:
RewriteCond %{HTTP_COOKIE} custom_cookie=(.*)$
RewriteCond %1::%{REQUEST_URI} ^(.*?)::/\1/?
RewriteRule . /komfortkonsult/restricted.png [R,L]
Likewise:
RewriteCond %{QUERY_STRING} ^r=(.*)$
RewriteRule ^/ - [E=COOKIE_MATCH:%1]
RewriteCond %{HTTP_COOKIE} !custom_cookie="%{ENV:COOKIE_MATCH}"
RewriteRule . /komfortkonsult/restricted.png [R,L]
Likewise:
RewriteCond %{HTTP_COOKIE} custom_cookie=([^;]+) [NC]
RewriteCond %{REQUEST_URI} !%1 [NC]
RewriteRule . /komfortkonsult/restricted.png [R,L]
And so on. I really want to keep this inside the .htaccess, instead using validation through a .php file call. But if that is the only solution to my architechture, please provide a full working example (not foo=bar, your redirects goes here...)
Any other approaches of my objectives are welcome.
Thanks so much for helping me out with this.
/ Intervik
Update (after accepted answer and working) example of usage
The objectives are one layer of protection in a Wordpress single install. All media, images or other files, uploaded and attached to pages, are hidden (replaced by an image) if A) the user is not logged-in or B) The user is logged in but not with the capability of 'edit_post'.
But the restriction is only for files uploaded into a unique folder called /restricted/. The folder is resident in the Wordpress original /uploads/ root. This restricted material is not allowed to be direct-linked or accessable by search engines etc etc. No browser-cache is allowed and restriction must work immediately after log-out. And more... but I think you get it.
The namespace 'custom_cookie' is just a providing example. And the examples showing the Wordpress install is within a subfolder on localhost. LIKE h**p://example.com/workspace/. Remove 'workspace/' if in root.
The cookie architecture, functions.php
function intervik_theme_set_custom_cookie(){
if(is_user_logged_in()){
global $current_user;
if(current_user_can('edit_posts')){
if(!isset($_COOKIE['custom_cookie'])){
$cookie_value = $current_user->ID . '|' . $current_user->user_login . '|' . $current_user->roles;
$salt = wp_salt('auth');
$cookie_hash = hash_hmac('md5', $cookie_value, $salt);
setcookie('custom_cookie', $cookie_hash, time()+36, '/');
$_COOKIE['custom_cookie'] = $cookie_hash;
} else {
$cookie_value = $current_user->ID . '|' . $current_user->user_login . '|' . $current_user->roles;
$salt = wp_salt('auth');
$cookie_hash = hash_hmac('md5', $cookie_value, $salt);
if($cookie_hash != $_COOKIE['custom_cookie']){
setcookie('custom_cookie', '', 1, '/');
unset($_COOKIE['custom_cookie']);
}
}
} else {
if(isset($_COOKIE['custom_cookie'])){
setcookie('custom_cookie', '', 1, '/');
unset($_COOKIE['custom_cookie']);
}
}
} else {
if(isset($_COOKIE['custom_cookie'])){
setcookie('custom_cookie', '', 1, '/');
unset($_COOKIE['custom_cookie']);
}
}
}
add_action('init', 'intervik_theme_set_custom_cookie');
As you can see, Each cookie is unique for each valid user, for each +36 seconds period (enough for a page-load - but use +120 for 2 minutes). This "token" is applied to every request send to the the server:
The link to attachment url filter:
function intervik_restricted_wp_get_attachment_url($url, $post_id){
if(strpos($url, '/restricted/') !== FALSE){
if(isset($_COOKIE['custom_cookie'])){
$url = add_query_arg('r', $_COOKIE['custom_cookie'], $url);
}
}
return $url;
}
add_filter('wp_get_attachment_url', 'intervik_restricted_wp_get_attachment_url', 10, 2);
We are not allowing any other query strings. Remark, more filter must be added for sizes, like wp_get_attachment_image_src etc etc. But direct links to media, this is enough.
Replace the if(current_user_can('edit_posts') with another
if(is_user_logged_in() ... changes everything to just login/out
users. Then skip the filters in the admin backend with if(!is_admin()
&& strpos($url, '/restricted/')!== FALSE) ...
And finally the .htaccess file, in the root of the uploads/restricted/ folder:
# BEGIN Intervik
Options +FollowSymLinks
Options All -Indexes
<IfModule !mod_rewrite.c>
Deny from all
</IfModule>
<IfModule mod_headers.c>
Header set Cache-Control "no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires 0
</IfModule>
RewriteEngine On
RewriteCond %{HTTP_COOKIE}::%{QUERY_STRING} !\bcustom_cookie=([0-9a-f]{32})\b.*::r=\1(&|$)
RewriteRule . /workspace/restricted.png? [R,L]
# END Intervik
I also placed the nice PNG IMAGE "Restriced Access timeout" in the Wordpress install root. This is also served as thumbnail in Library admin area for non valid administrators. The upload filter or backend is another area.
We are not protecting Englands financial plans here, but we wanna keep
away some paperwork for an organistion and some picures from Google and from
your wife.
Please comment
Its actually working and you are welcome to comment the flaws or security risks. However, there is also another layer validation with PHP above this layer in our install, but we need speed for not so important stuff.
You've got some of the correct bits in your different attempts, but you need to bring them together in the correct order.
Try the following instead:
RewriteEngine On
# custom_cookie value is 32 char hex and must match the value of the "r" URL parameter
RewriteCond %{HTTP_COOKIE}::%{QUERY_STRING} !\bcustom_cookie=([0-9a-f]{32})\b.*::r=\1(&|$)
RewriteRule ^ /komfortkonsult/restricted.png [QSD,R,L]
The QSD flag (Apache 2.4+) is required to remove the query string from the redirected URL. Alternatively, if you are still using Apache 2.2 then you can append a ? to the susbstitution instead.
Note that the RewriteBase is not required here. The <IfModule> should also be removed. The <IfModule mod_rewrite.c> wrapper is only required if this is intended to work without mod_rewrite being available. It is not. If mod_rewrite is not available then your conditions will simply fail silently and access will be unrestricted. In this case, it is preferable to fail with an error and access is forbidden (for everyone).
Assumptions:
The cookie value is a 32 character hex value (as in your example).
The r URL parameter is always the first URL parameter (as in your example).
You mentioned "any file extension", however, redirecting to an image only really "works" if an image is being requested in the first place. If you have files other than images it may be preferable to simply return a 403 Forbidden. (Strictly speaking, sending a 403 is the correct response rather than a 302, followed by 200 OK.) To send a 403 instead, just change the RewriteRule directive to read:
RewriteRule ^ - [F]
How this works...
An important point, that is missed from all but one of your examples, is the r URL parameter is part of the query string, not the URL-path. The REQUEST_URI server variable contains the URL-path only, which notably excludes the query string. To match the query string you need to compare against the QUERY_STRING server variable.
%{HTTP_COOKIE}::%{QUERY_STRING} - The cookie HTTP request header is joined with the query string using a separater (::) that is guaranteed to not appear in either value. This forms the TestString.
!\bcustom_cookie=([0-9a-f]{32})\b.*::r=\1(&|$) - This is the CondPattern that matches the TestString. \b is a word boundary, so we match only this specific cookie. The value of this cookie is captured using ([0-9a-f]{32}). We then skip over any remaining characters in the cookie header until we get to our separater (::). After this we are matching against the query string (value of the QUERY_STRING server variable in the TestString). The "magic" is the \1 backreference to the first captured group, ie. the cookie value.
The ! prefix on the CondPattern negates the entire pattern. So, the condition is successful when this pattern does not match, ie. when the values of the cookie and URL parameter are different (or not present at all).
Why your attempts were not working...
RewriteCond %{HTTP_COOKIE} custom_cookie=(.*)$
RewriteCond %1::%{REQUEST_URI} ^(.*?)::/\1/?
This assumes your cookie is the last cookie in the Cookie header. This is difficult to guarantee.
You are trying to match the cookie value with the entire URL-path (REQUEST_URI), so this will never match. It assumes your URL is of the form: http://localhost/870603c9d23f2b7ea7882e89923582d7.
RewriteCond %{QUERY_STRING} ^r=(.*)$
RewriteRule ^/ - [E=COOKIE_MATCH:%1]
RewriteCond %{HTTP_COOKIE} !custom_cookie="%{ENV:COOKIE_MATCH}"
Good, you are checking the query string for the URL parameter value. However...
The first RewriteRule never matches because the URL-path never starts with a slash in per-directory (.htaccess) context. Consequently, the COOKIE_MATCH environment variable is never set.
The CondPattern is a regex, not a plain string, so %{ENV:COOKIE_MATCH} is not evaluated - it is seen as a literal string. You've also enclosed this in double quotes, which aren't part of the cookie value either.
RewriteCond %{HTTP_COOKIE} custom_cookie=([^;]+) [NC]
RewriteCond %{REQUEST_URI} !%1 [NC]
Again, you are comparing against the URL-path, not the query string. However, as mentioned above, the %1 backreference is not evaluated in the CondPattern, so this is seen as a literal string anyway.
It is why the %{VARIABLE} (and %1 etc) expressions are not evaluated in the CondPattern that we need to use the seemingly complex expression that uses a regex backreference of the form:
%{VAR1}##%{VAR2} ^(.+)##\1$
I want to add if condition in htaccess file for a controller action. The url is like
www.example.com/user/profile/5?cid=7&pos=1
I want to add condition in htaccess file if url is action "profile". I tried following:
<If "%{REQUEST_URI} == 'profile'">
// do something
</If>
but it doesn't work. Please advice.
It's because your REQUEST_URI is equal to /user/profile/5?cid=7&pos=1 and not just profile, so your condition is not met.
If you want to catch if your REQUEST_URI contains profile, you can do this with a simple regex :
<If "%{REQUEST_URI} =~ /profile/">
// do something
</If>
I am currently using Bootstrap to add an accordion function to a jobs page on a new website. Everything works fine, however when I click to view a specific accordion to display the listing, the URL only works like this: website/jobs#developer and not like this: website/jobs/#developer which would look a lot more professional on job boards and maintain what we have on our current site.
I don't want to have to go back to every link distributed and remove the slash. I am attaching what I have done in my htaccess but I am not sure its is affected by it. Please help!
<IfModule mod_rewrite.c>
RewriteEngine On
ErrorDocument 404 /views/errors/404.php
RewriteRule ^(jobs|Jobs|JOBS)$ views/jobs.php [L]
</IfModule>
The rewrite rule will not match a trailing slash
^(jobs|Jobs|JOBS)$
This rewrite rule will not match a url with a trailing slash, it means:
^ # start of match
jobs|Jobs|JOBS # One of these strings
$ # End of match
To have it match urls with a trailing slash, that needs to be in the rewrite rule:
RewriteRule ^(jobs|Jobs|JOBS)/?$
^ A slash
^ 0 or 1 times - i.e. optional
Professional urls?
website/jobs/#developer which would look a lot more professional on job boards
Unrelated to the question, but the presence or absence of a trailing slash doesn't have any bearing on how professional a url looks.
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.
So let's say:
- .htaccess
* assets
|> images
|> logo.png
|> css
|> style.css
|> home.css
How can i set the expires header for the whole assets folder and its contents?
I know that i can set it by type like:
ExpiresByType text/javascript "modification plus 2 hours 45 minutes"
But what about for a whole directory?
At the .conf level, use a <directory> directive:
<Directory /path/to/your/assets/folder>
ExpiresDefault ...
</Directory>
If you have only .htaccess control, then put a .htaccess into the assets folder with the same ExpiresDefault directive