1

I have a site where users have to pay to for downloads of videos they create on the site. Because of this I need to hide the path for the downloads.

I have the php page where the download button is -

<form id="dl_script_form" action="dl-script.php" method="post" target="_blank">
<input name="userID" type="hidden" value="<?php echo $user->ID;?>" />
<input name="videosID" type="hidden" value="<?php echo $vId; ?>" />
<input name="downloadvids" id="downloadvids" type="submit" value="Download"/>
</form>

And on the dl-script.php --

require_once $_SERVER['DOCUMENT_ROOT'] . '/wp-load.php';

global $post;
$userid = $_POST['userID'];
$videosid = $_POST['videosID'];

$nameOld = '/path/to/'.$userid.'/'.$videosid.'/'.$videosid.'.mp4';
$nameNew = "download.mp4";
header('Content-type: video/mp4');
header("Content-disposition: attachment; filename=$nameNew");
header("Content-Length: ".filesize($nameOld));
readfile($nameOld);
exit(); 

When trying to click submit, a download is started (with the filename as download.mp4) but when I try opening the mp4 in windows media player, it tells me -

Windows Media Player cannot play the file. The Player might not support the file type or might not support the codec that was used to compress the file.

I've checked the file path and was able to play the video in my browser or right click download without a problem.

So with php script the file must be corrupted somehow. I tried adding/removing header content-* and file_get_contents, but still no luck.

What am I doing wrong?

EDIT

For testing purposes I modified the code to download from my second server to my original server where I am running the script from.

$userid = $_POST['userID'];
$videosid = $_POST['videosID'];

$path = 'https://path/to/'.$userid.'/'.$videosid.'/'.$videosid.'.mp4';
$save = '/var/path/to/'.$userid.'/'.$videosid.'/'.$videosid.'.mp4';

file_put_contents($save, fopen($path, 'r'));

$nameNew = "download.mp4";

header('Content-type: video/mp4');
header("Content-disposition: attachment; filename=$nameNew");
header("Content-Length: ".filesize($save));
readfile($save);

This however still doesnt download correctly. If I download the file directly though without the script, it works fine.

730wavy
  • 944
  • 1
  • 19
  • 57
  • 3
    FYI, “hiding the path” is pretty useless if you offer an alternative way to download the file. What you want is some sort of permission check in that `dl-script.php` which only lets a user download a file once or such. – deceze Jan 09 '19 at 06:56
  • what do you mean if i offer a alternative way? This is the only way they are supposed to be able to download. My comment about right clicking is just something I did to test the file. Users should only use the one download button mentioned. @deceze – 730wavy Jan 09 '19 at 13:03
  • Yeah, sure, but that doesn’t “hide the file”. It’s just *another way* to get the file. Can you make a specific request to a specific URL and you’ll get a file? → Yes → Not “hidden.” – deceze Jan 09 '19 at 13:18
  • Yeah but it doesnt expose the url to users who arent 'tech savy'. And mostly - if not all, my users wont be. My issue and first step right now is getting the file to download correctly. Do you have another suggestion for the whole process mentioned in the question, and provide a answer? – 730wavy Jan 09 '19 at 15:03
  • I’d try to diff the downloaded file with the original to see what the difference is. Especially the first few bytes are probably different, if anything. – deceze Jan 09 '19 at 15:15
  • the file has 0 bytes – 730wavy Jan 09 '19 at 15:24
  • Well, that don’t help. No errors in the PHP/Apache logs? – deceze Jan 09 '19 at 15:25
  • I found this error `PHP Warning: filesize(): stat failed for https:.....` – 730wavy Jan 09 '19 at 15:30
  • Show a real example of `$nameOld`. – deceze Jan 09 '19 at 15:54
  • `https://example.com/path/to/01/007/video-001.mp4` – 730wavy Jan 09 '19 at 16:14
  • Don’t use a URL, use the local file path. – deceze Jan 09 '19 at 16:34
  • i cant because the files is hosted on my second server – 730wavy Jan 09 '19 at 16:36
  • Well, see https://stackoverflow.com/questions/18073406/get-remote-file-size-using-url-in-php. Or just omit outputting the Content-Length header. – deceze Jan 09 '19 at 16:39
  • when i remove content length the file now downloads all the bytes but is still not working. Is there another header Im missing? – 730wavy Jan 09 '19 at 17:07
  • Again, diff it. – deceze Jan 09 '19 at 17:47
  • for testing => try readfile without headers. I think there must be an error which your headers are hiding. – Aabir Hussain Jan 17 '19 at 11:29
  • @AabirHussain it shows me the coding for the video.. – 730wavy Jan 17 '19 at 15:38
  • @Rich I am trying to reproduce your case on my ubuntu machine but Its all working fine. One difference is the path I think can you check your path mine is => $save = '/var/www/html/application/public/images/SAMPLE.mp4'; – Aabir Hussain Jan 18 '19 at 10:59
  • One moe thing is could be size problem check this out => https://wordpress.stackexchange.com/questions/260381/http-error-when-uploading-mp4-video-file – Aabir Hussain Jan 18 '19 at 11:12

3 Answers3

0

The scripts looks fine for what it does.

Based on the comments it seems that you are trying to get the file from a remote location, the function filesize would not work, you have to find another way of getting the filesize or simply removing it

a way to get the filesize is via the headers

the following code should get the filesize from $nameOld remote location

$headers= get_headers($nameOld, 1);
$filesize = $headers['Content-Length'];

get_headers() documentation

readfile() supports remote file locations, but the allow_url_fopen php configuration should be On


In order to make it work you will have to find the assosiate error log, if the error reporting is to error_reporting=E_ALL then the file you download contains the error, so if you open it in a text editor you will be able to see the error, otherwise search in your error.log, there is high changes that the allow_url_fopen was not configured properly, but finding the error would help you solve the issue.


Edit:

I have tested your code and it is posible to download a sample mp4 from a remote location

$nameOld = 'https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_2mb.mp4';
$nameNew = "download.mp4";

$headers= get_headers($nameOld, 1);
$filesize = $headers['Content-Length'];

header('Content-type: video/mp4');
header("Content-disposition: attachment; filename=$nameNew");
header("Content-Length: ".$filesize);

readfile($nameOld);

if that doesn't work for you then the issue is in your server settings, probably the allow_url_fopen that I mentioned before.

Community
  • 1
  • 1
knetsi
  • 1,601
  • 1
  • 16
  • 18
  • @Rich updated my answer with a test example that I ran to verify that the code works. The file that was downloaded was in playable condition. – knetsi Jan 17 '19 at 19:21
  • I checked both servers and allow_url_fopen is set to 'On' for both. I've also copied and paste your code and the download was still unable to be opened and played. – 730wavy Jan 18 '19 at 15:39
  • @Rich can you make your error reporting more verbose, and then you can basically see what is the error that makes it fail, otherwise it would be just guessing. if you change the error reporting, then you can remove the header Content-Type or open the downloaded file with a text editor to view its contents (or simply check the error.log) – knetsi Jan 18 '19 at 15:59
  • what do you mean by making more verbose? How so? My current error log doesnt show any related errors. And opening the file shows the text/code of the file. – 730wavy Jan 18 '19 at 17:24
0

Could it be some issue with server-side https handling? Have you tried plain http request to second server?

Have you ever considered using secure urls? This could help reduce network and cpu load on your servers. Instead of hiding real URL, you could give user temporary link, which works only for his IP (and expires after some time). This is what most tube sites do. Check this: http://nginx.org/en/docs/http/ngx_http_secure_link_module.html

Noobus
  • 96
  • 2
0

I wasnt able to get it working with any of the suggestions here, but after continuing to dig deeper, I was able to get it working.

The only issue though is that I have to save the file from my second server to my first server, for the download to work. I then delete it, but I'm pretty sure this is not efficient performance wise. However, nothing else I tried work.

The key thing(s) that got it to work is ob_start(), while (ob_get_level()) { ob_end_clean(); }

full code -

ob_start();

$userid = $_POST['userID'];
$videosid = $_POST['videosID'];

$nameOld = 'https://path/to/get/'.$userid.'/'.$videosid.'/'.$videosid.'.mp4';
$save = '/path/to/save/it/'.$userid.'/'.$videosid.'/'.$videosid.'.mp4';

$nameNew = "download.mp4";

file_put_contents($save, fopen($nameOld, 'r'));

set_time_limit(0);

header('Connection: Keep-Alive');
header('Content-Description: File Transfer');
header('Content-Type: application/force-download');
header("Content-Disposition: attachment; filename=$nameNew");
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($save));

      while (ob_get_level()) {
        ob_end_clean();
      }

readfile($save);
exit;

// delete file when done
unlink($save);

If anyone can suggest a more efficient code example, please go ahead.

730wavy
  • 944
  • 1
  • 19
  • 57
  • It seems that this method is also saving the file in a temporary memory. Are you sure this method is better in I/O performance? – Ali Sheikhpour Dec 03 '20 at 09:31