When user tries to download .iso or .deb from my website php is opening the file instead of downloading it

When a user tries to download a file on my website, it appears that it’s opening the file instead of downloading it. The code works properly on my local apache server. Here’s the code that should be downloading the file when they click a download button.

<?php

$php_scripts = '../../php/';
require $php_scripts . 'PDO_Connection_Select.php';
require $php_scripts . 'GetUserIpAddr.php';
function mydloader($l_filename=NULL)

{
$ip = GetUserIpAddr();
if (!$pdo = PDOConnect("foxclone_data"))
{	
    exit;
}
if( isset( $l_filename ) ) {  
    header('Content-Type: application/octet-stream');
    header("Content-Disposition: attachment; filename={$l_filename}");
    header('Pragma: no-cache');
    header('Expires: 0');        
    readfile($l_filename);
        


    $ext = pathinfo($l_filename, PATHINFO_EXTENSION);
    $stmt = $pdo->prepare("INSERT INTO download (address, filename,ip_address) VALUES (?, ?, inet_aton('$ip'))");
    $stmt->execute([$ip, $ext]) ; 

    $test = $pdo->query("SELECT lookup.id FROM lookup WHERE inet_aton('$ip') >= lookup.ipstart AND inet_aton('$ip') <= lookup.ipend");
    $ref = $test->fetchColumn();
    $ref = intval($ref);

    $stmt = $pdo->prepare("UPDATE download SET lookup_id = '$ref' WHERE address = '$ip'");
    $stmt->execute() ;         
      }
        
    else {
        echo "isset failed";
        }  
}
mydloader($_GET["f"]);
exit;

This is what the screen looks like:

I’d appreciate some help on this.

Something is probably being output to the browser prior to the header() statements (the 3 followed by the Korean syllable?), so they are not working. There would be php errors if your php error related settings are setup to report and display or log all php errors.

The reason this may work on your localhost system is because php’s output_buffering setting is on in the php.ini, allowing the header() statements to ‘work’, but also pre-pending the errant output to the start of the downloaded file.

You should both set the output_buffering setting to OFF on your localhost system and find and eliminate the errant output.

Also, accepting the actual filename as an input will allow anyone to download any file that’s on the server, such as your database connection file.

You should store the names and file paths of permitted files in a database table, use the id (auto-increment primary index) value in the download links, then query within the code to get the actual file name and file path to use. This will allow only files that are listed in the database table to be downloaded.

Thanks for the reply. I’m pretty new to php. This code was working on the web host until recently, so I’m not sure what might have changed.

The web host probably updated something and change the output_buffering setting in the php.ini. Use a phpinfo(); statement to see what the setting currently is.

Thanks for the guidance. I just talked to someone involved in the website and they’ve previously had problems wth downloaded files not having the same md5sum as the original. This download script is in the same directory as the files to be downloaded, so shouldn’t need path info.

This would occur if errant output is being prepended or appended to the downloaded file. The only things that the download .php code should output are the headers and the file contents. Anything the code outputs before the headers will either be prepend to the file contents (if php’s output_buffering is on) or will prevent the headers from working (if php’s output_buffering is off) and anything the code outputs after the file contents will be appended to the file contents. Any extra characters before/after the file contents will both change the length and the checksum of the downloaded file.

I already have the filenames in a table with an id number so I’m re-working the script to query the database for the filename. I’ll also make sure there aren’t any blank spaces before the header codes. The insert statements will remain where they are, after the header codes.

@phdr - Would moving the entire download directory outside of the public_html structure provide additional security?

OK, I’ve moved the SQL connect code below the the headers and no longer getting the errors or opening of the file, but it’s still not downloading from the webhost. It’s also not working on my local server. (var_dump($file) not working either) Here’s the current code:

<?php

function mydloader($l_filename= "")

{


    $file = $l_filename;
    var_dump($file)
    if (file_exists($file)) {
        header('Content-Description: File Transfer');
        header('Content-Type: application/octet-stream');
        header('Content-Disposition: attachment; filename="'.basename($file).'"');
        header('Expires: 0');
        header('Cache-Control: must-revalidate');
        header('Pragma: public');
        header('Content-Length: ' . filesize($file));  /*Read the size of the file*/
        readfile($file);
        exit;
        }
    }
    /*Clear system output buffer*/
    flush();
    
 /*Terminate from the script*/
 die();

}


mydloader($_GET["f"]);
exit;

That’s because you have introduced a fatal php parse/syntax error on that line, and php’s error related settings are not setup so that all php errors will be reporting and displayed.

On your localhost development system, find the php.ini that php is using (the output from a phpinfo(); statement contains a ‘Loaded Configuration File’ line listing the php.ini that is being used) and set the following -

  1. error_reporting = E_ALL
  2. display_errors = on
  3. output_buffering = off

Stop and start your web server to get any changes made to the php.ini to take effect and then check using a phpinfo(); statement that these settings actually got changed to the desired values. The error_reporting value for E_ALL will be the integer 32767.

This will get you to the point where php will report and display all the errors it detects, saving you a ton of time. If you have any php error related settings in your code, remove them so that they don’t override these php.ini settings. You want the php error settings in the php.ini only so that you can change them at a single point and so that when you move your code from the localhost development system to a live/public server you don’t need to find and edit these settings in your code, you only need to make sure that the (local) php.ini has the desired settings in it.

You can now get your code to work on your localhost development system and it should work on the live/public server, since you have turned off the output_buffering setting on your development system, and it will no longer matter what this setting is set to on the live/public server.

If you are getting php error messages, you would need to post them to get any help with what is causing them.

All my php.ini values are already set as you described. If it’s woking locally, what could be the problem on the webhost’s server?

The most likely cause has already been given -

You need to get the php error related settings set up on the live/public server so that php will report and either display or log all php detected errors so that you can find what is actually causing the problem.

@phdr - The php.ini settings on the webhost are now identical to the php.ini on my machine. It is located in my public_html directory. Still have the same problem.

@phdr - Thanks for your assistance.

Fixed by moving db connection block of code to after the headers.

That only moved the errant output to a different point, which eliminated the php header errors, but now the errant output is appended to the end of the downloaded file, corrupting it, changing its size, and checksum. This may not matter for some of the files, but it may prevent others from working properly.

You still need to find and fix what is actually causing the problem. In the two other help forums where you did post the error messages, you are getting output on line 1 of the db connection file. You either have characters in the file before the <?php tag, or as was already stated in one of the help forums, the file has been saved with BOM (Byte Order Mark) characters.

@phdr - No more problems like I had before. I have a new one. When I run the following code on my local apache server, the md5sums of the downloaded files are correct. When I run the code on my web host, I get different md5sums. I’ve scrubbed the code to eliminate blank spaces before the readfile statement. I’m not getting any errors with error_reporting(E_ALL), display_errors=On, and output_buffering = off in my local php.ini as well as on my web host.

Here’s the code I’m currently using:

<?php
function mydloader($l_filename=NULL){    
    if( isset( $l_filename ) ) {
        $filename = preg_replace("/\s+/u", " ", $l_filename);//Eliminate any hidden characters
        $ext = pathinfo($filename, PATHINFO_EXTENSION);{
        if  ($ext = '.deb')
            header('Content-Type: application/x-debian-package');
        elseif ($ext = '.iso')
            header('Content-Type: application/x-cd-image');
        elseif ($ext = '.gz')
            header('Content-Type: application/zip');
        else
            header('Content-Type: octet-stream');}
            header("Content-Disposition: attachment; filename={$filename}");
            header('Pragma: no-cache');
            header('Expires: 0');        
            readfile($filename);
      }
        
    else {
        echo "isset failed";
        }  
}      
mydloader($_GET["f"]);

I’ve removed all other code from this script in order to eliminate any other issues. I really appreciate you assistance.

I fixed the problem. The if → elseif statements had = instead of ==.

Sponsor our Newsletter | Privacy Policy | Terms of Service