phpwordpress.htaccesspluginshttp-status-code-404

404 Error When Trying to Serve Restricted PDFs After Login


I'm banging my head against a wall with an issue involving a WordPress plugin I've been working on. It's supposed to restrict access to PDF files in a specific area of the site, only letting logged-in users see them. The PDFs are located in wp-content/custom-files/pdfs/ and their filenames all start with 's'.

The Problem:

So, the plugin seems to be doing its job. When a logged-in user tries to grab one of these restricted PDFs, I'm seeing in the WordPress debug log that the plugin fires, sees they're logged in, finds the PDF file just fine, and even says it's sending the file using readfile() (or I tried fpassthru() too, same result). It even logs the correct file size and says all the bytes were sent!

But instead of the PDF popping up in the browser, the user gets a big fat 404 Not Found error. What's really weird is, if I access a PDF in the same folder that doesn't start with 's' (so the plugin doesn't kick in), it works perfectly! This makes me think the problem is happening after my plugin does its thing, but before the browser gets the file.

Code Snippets:

Here's the code for my plugin (with the folder path sanitized):

<?php /**


Plugin Name: ...


*/

defined('ABSPATH') || exit;
add_action( 'template_redirect', 'restrict_s_pdfs' );

function restrict_s_pdfs() {
    error_log('restrict_s_pdfs function called');

    // Check if the user is trying to access a restricted PDF
    if ( strpos( $_SERVER['REQUEST_URI'], '/wp-content/custom-files/pdfs/s' ) !== false &&
         strtolower( pathinfo( $_SERVER['REQUEST_URI'], PATHINFO_EXTENSION ) ) === 'pdf' ) {

        error_log('Potential restricted PDF access detected: ' . $_SERVER['REQUEST_URI']);

        if ( is_user_logged_in() ) {
            error_log('User is logged in.');

            // Define the full path to the PDF file (remove the leading slash from REQUEST_URI)
            $filepath = ABSPATH . ltrim(urldecode( $_SERVER['REQUEST_URI'] ), '/');
            error_log('Filepath constructed: ' . $filepath);

            // Security check: Ensure the file exists and is within the allowed directory
            $file_exists = file_exists( $filepath );
            error_log('file_exists: ' . ($file_exists ? 'true' : 'false'));

            $is_within_allowed_dir = strpos( $filepath, ABSPATH . 'wp-content/custom-files/pdfs/' ) === 0;
            error_log('is_within_allowed_dir: ' . ($is_within_allowed_dir ? 'true' : 'false'));

            if ( $file_exists && $is_within_allowed_dir ) {
                error_log('File exists and is within allowed directory. Serving PDF inline.');
                ob_end_clean(); // Try clearing output buffer
                // Set the appropriate headers for a PDF
                header('Content-Type: application/pdf');
                error_log('Content-Type header sent.');
                header('Content-Disposition: inline; filename="' . basename( $_SERVER['REQUEST_URI'] ) . '"');
                error_log('Content-Disposition header sent.');
                $filesize = filesize($filepath);
                header('Content-Length: ' . $filesize);
                error_log('Content-Length header sent: ' . $filesize);

                error_log('About to readfile for inline display: ' . $filepath);
                $bytes_sent = readfile($filepath);
                error_log('readfile completed for inline display. Bytes sent: ' . $bytes_sent);

                if ($bytes_sent !== $filesize) {
                    error_log('ERROR: Incomplete file transfer for inline display!');
                } else {
                    error_log('File transfer for inline display appears complete.');
                }
                exit;
            } else {
                error_log('File not found or not within allowed directory. Returning 403.');
                wp_die( 'File not found or access denied.', 'Access Denied', array( 'response' => 403 ) );
            }
        } else {
            error_log('User is not logged in. Redirecting to login with redirect URL.');
            // Get the current URL to redirect back to after login
            $redirect_url = $_SERVER['REQUEST_URI'];
            $login_url = wp_login_url( $redirect_url );

            // Redirect to the login page with the redirect URL
            wp_redirect( $login_url, 302 );
            exit;
        }
    } else {
        error_log('Not a restricted PDF request: ' . $_SERVER['REQUEST_URI']);
    }
}

And here my htaccess file:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteRule ^en/wp-login.php /wp-login.php [QSA,L]
RewriteRule ^de/wp-login.php /wp-login.php [QSA,L]

# **Add your PDF restriction rules here, before the last RewriteRule**
RewriteCond %{REQUEST_URI} ^/wp-content/custom-files/pdfs/s.*\.pdf$ [NC]
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule . /index.php [L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

I'm at a complete loss here. It feels like PHP is doing its job, but something else in the server stack is causing these requests to fail with a 404. If you have any ideas what could be causing this error, i would appreciate your help. Thanks in advance!

What I've already tried:


Solution

  • when the execution arrive to the hook template_redirect, wordpress has already send the 404 status code.

    then when you send the PDF file, you need to set the status code again with status_header(200);.