2

Wondering if anyone can help with this problem. I have been able to get file protection working in WordPress with the method below:


.htaccess

RewriteCond %{REQUEST_FILENAME} -s
RewriteRule ^wp-content/uploads/(.*)$ checkloggedin.php?file=$1 [QSA,L]

checkloggedin.php has code to make sure the user is logged in before providing the file otherwise the login page is displayed.

This works exactly as I want but if the file contains an ampersand (&) the file does not load or display. The error I get is below:

PHP Warning: mime_content_type() failed to open stream: No such file or directory

I found a few other examples that mentioned changing RewriteRule [QSA,L] to RewriteRule [BNC] (see http://httpd.apache.org/docs/current/rewrite/flags.html#flag_b).

I have tried many different combinations with the B flag but this causes a bigger problem as the file will load but then none of the code in checkloggedin.php that checks if a user is logged in works.

Does anyone know of a solution to this problem if the file contains an ampersand?

4
  • By "file contains ampersand" do you mean the filename or the actual file contents? Does this help you to get unknown query params?
    – kero
    Jan 29, 2021 at 13:47
  • 1
    Before going any further. I am presuming you meant the filename requested has an ampersand in the name, am I right? This could be a case of mismatching encoding, perhaps in the checkloggedin.php. Can you give example of a requested filename with ampersand and its correspondent name on your file system? Jan 29, 2021 at 14:10
  • Thanks for your help, sorry if I was not clear. Yes, if the filename requested contains an ampersand there is a problem. URL/testfile.pdf (works if the filename does not contain %26 or &) URL/test%26file.pdf (does not work if the filename contains %26 or &) URL/test&file.pdf (does not work if the filename contains %26 or &)
    – crmau
    Jan 29, 2021 at 15:20
  • With checkloggedin.php?file=$1 [QSA,L] in the htaccess when accessing a file that has an ampersand in the filename then PHP Warning: mime_content_type() failed to open stream: No such file or directory is displayed. If I change to checkloggedin.php?file=$1 [BNC] in the htaccess there is now no problem accessing a file that has an ampersand in the filename but then checkloggedin.php does not get hit so it does not check if the user is logged in before accessing the file.
    – crmau
    Jan 29, 2021 at 15:22

1 Answer 1

2

As you've stated, the B flag is required in this case. But this should be [B,L], not [BNC] as you've quoted a couple of times?

Not sure where you got [BNC] from, but that's wholly invalid and would break an Apache server (500 Internal Server Error response). If you don't see an error, using this invalid flag, then it's possible you are on a LiteSpeed server, which quietly ignores the error and the directive does not run, allowing unrestricted access to the file (which seems to be what you are seeing here).

The B flag is required in order to re-encode the & (as %26) as captured from the requested decoded-URL before passing this to the query string, so that you pass the entire "filename" and not just the part before the &.

For example, if you request /wp-content/uploads/abc%26def.pdf and do not use the B flag then the resulting request will be:

checkloggedin.php?file=abc&def

That's now effectively two URL parameters: file=abc and def=

With the B flag, the captured backreference is re-encoded and becomes:

checkloggedin.php?file=abc%26def

PHP/WordPress then decodes this to abc&def - the complete filename.

So, the complete rule should be:

RewriteCond %{REQUEST_FILENAME} -s
RewriteRule ^wp-content/uploads/(.*) checkloggedin.php?file=$1 [B,L]

The QSA flag is not required, unless you are passing additional query string parameters in your initial file request? The trailing $ on the RewriteRule pattern is not required either, since regex is greedy by default.

If this still does not work then the problem is with the checkloggedin.php script. But from what you say, this script is not even being called when using BNC - which is why I think you are on a LiteSpeed server (not Apache).


UPDATE:

An alternative method is to pass the filename as path-info instead of as a URL parameter in the query string. You are then not prone to URL-encoding issues associated with & (this is not a special character in the URL-path).

So, instead of the above rule, you would use the following instead in .htaccess:

RewriteCond %{REQUEST_FILENAME} -s
RewriteRule ^wp-content/uploads/(.*) checkloggedin.php/$1 [L]

The B flag is not required.

The URL-path matched by the RewriteRule pattern is already %-decoded (so it doesn't matter whether the original request includes & or %26). The same "file" value is passed as path-info (additional pathname information) and is therefore available via the $_SERVER['PATH_INFO'] superglobal, instead of $_GET['file'].

So, in your script you would need to do something like the following instead to populate your $filename variable:

$filename = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : null;
5
  • Thanks so much for your help. Yes, I am on a Litespeed Server. Changed to RewriteRule ^wp-content/uploads/(.*) checkloggedin.php?file=$1 [B,L] as recommended but still had the same problem. Starting debugging checkloggedin.php as recommended and I think the problem is in here.
    – crmau
    Jan 30, 2021 at 2:51
  • I am using $_GET['file'] in checkloggedin.php e.g. $filename = $_GET['file'] This is returning PATH/test if the file contains ampersand (instead of PATH/test&file1.pdf) As soon as it hits the ampersand in the filename there is a problem as the full file path is not correct and then then the file cannot be found. So, does anyone know how to solve this problem with $_GET['file'] and ampersands?
    – crmau
    Jan 30, 2021 at 2:52
  • The $_GET superglobal is a fundamental part of PHP. If this is incorrect as you suggest then it implies the query string is incorrectly encoded in the request, as if the B flag is not working as intended. OR, something is pre-processing the $_GET array? Try a simple test script, without any other PHP includes - is the $_GET['file'] value still incorrect? Incidentally, I have tested this on a LiteSpeed server and the B flag does appear to work as intended for me. There are, however, other workarounds for this. Are you passing %26 or & in the initial request?
    – MrWhite
    Jan 30, 2021 at 20:13
  • I've updated my answer with an alternative method for passing the filename - using "path-info" instead of the query string. This is probably easier than messing with the URL-encoding in .htaccess.
    – MrWhite
    Jan 30, 2021 at 20:38
  • 1
    Wow, thanks so much for your help, is working great!
    – crmau
    Jan 31, 2021 at 2:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.