Want to protect your cyber security and still get fast solutions? Ask a secure question today.Go Premium

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 673
  • Last Modified:

PHP: how to generate protected download link

Hi X-perts,

I need the following functionality:

1) a customer enters username and password, which are verified against db tables. It is a simple thing; everything is clear here

2) if username and password are valid, the script returns a download link. The actual d/l directory cannot be accessed directly, but only via that script after login validation.

How can I do the 2nd part?

Please, suggest a few solutions.

Thanks
0
andy7789
Asked:
andy7789
3 Solutions
 
ludofulopCommented:
put the files outside the web root, than generate link in the following form:
http://server/download.php?hash=23423536

then, your download.php should select filename from database, according to hash, and use headers and readfile() to output the file.
0
 
KalpanCommented:
You can do this as below logic..

1. Maintain the session once the user login is successful...on the index.php

if($user)
 // add this to session
 $_SESSION['user'] = $user;

2. Check if the current session is available in each page with your browser...

if($_SESSION['user']){
   echo $download_link;
}

hope this is helpful.....let me know if this the correct or i could put more logic for this...

Thanks

Kalpan

0
 
andy7789Author Commented:
Thank you, guys. I believe the 1st solution should work better, because I need the header and the d/l to be returned automatically instead of download URL, i.e.

1) my VB.NET application calls the URL www.myurl.com/download.php via HttpWebRequest and passes the username and password

2) download.php generates a response as the actual file headers.

probably, it is a messy explanation. My problem is that it is automatic download via the VB.NET desktop application. it would be easy to make it working as a normal web only access system.

Any comments on this?

Thanks!
0
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
markloganCommented:
As ludofulop said you need the read the contents of your file and return it through a script.

Be careful with large files though, you can get script timeout issues.
<?php

$contentLen=@filesize("yourfile.zip");

header("Content-type: application/zip");
header("Content-type: octet-stream");
header('Content-Disposition: attachment; filename="yourfile.zip"');

if($content_len!=FALSE){
 header("Content-length: $contentLen");
}

readfile("yourfile.zip");

?>

Open in new window

0
 
markloganCommented:
Incorrect variable ^
<?php

$contentLen=@filesize("yourfile.zip");

header("Content-type: application/zip");
header("Content-type: octet-stream");
header('Content-Disposition: attachment; filename="yourfile.zip"');

if($contentLen!=FALSE){
 header("Content-length: $contentLen");
}

readfile("yourfile.zip");

?>

Open in new window

0
 
markloganCommented:
The actually file location is then hidden from the browser headers.
11:48:00.007[18ms][total 18ms] Status: 200[OK]
GET http://127.0.0.1/test.php Load Flags[LOAD_DOCUMENT_URI  LOAD_INITIAL_DOCUMENT_URI  ] Content Size[177] Mime Type[application/x-unknown-content-type]
   Request Headers:
      Host[127.0.0.1]
      User-Agent[Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 (.NET CLR 3.5.30729)]
      Accept[text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8]
      Accept-Language[en-gb,en;q=0.5]
      Accept-Encoding[gzip,deflate]
      Accept-Charset[ISO-8859-1,utf-8;q=0.7,*;q=0.7]
      Keep-Alive[300]
      Connection[keep-alive]
      Cookie[PHPSESSID=60dd006144d08b6b064dbc5405c9557f]
   Response Headers:
      Date[Tue, 12 Jan 2010 11:48:36 GMT]
      Server[Apache/2.2.9 (Unix) mod_ssl/2.2.9 OpenSSL/0.9.8b PHP/5.2.6]
      X-Powered-By[PHP/5.2.6]
      Expires[Thu, 19 Nov 1981 08:52:00 GMT]
      Cache-Control[no-store, no-cache, must-revalidate, post-check=0, pre-check=0]
      Pragma[no-cache]
      Content-Disposition[attachment; filename="yourfile.zip"]
      Content-Length[177]
      Keep-Alive[timeout=5, max=100]
      Connection[Keep-Alive]
      Content-Type[octet-stream]

Open in new window

0
 
andy7789Author Commented:
Thank you - it is getting better :)

>Be careful with large files though, you can get script timeout issues.

What would be a right size to avoid timeout problems? 20MB?

I understand that it is a PHP section, but I have to marry PHP and VB.NET. What happens if I call that file test.php from a VB.NET desktop application - see the code.

The below code works if I use a simple downloadable file test.txt

Dim wr As HttpWebRequest = CType(WebRequestFactory.Create("http://www.testthis.com/test.txt"), HttpWebRequest)

Sorry for asking a wrong zone question here.....
Imports System.IO
Imports System.Net
Imports System.Text
Class WebRetrieve
Public Shared Sub Main()
Dim wr As HttpWebRequest = CType(WebRequestFactory.Create("http://www.testthis.com/test.php"), HttpWebRequest)
Dim ws As HttpWebResponse = CType(wr.GetResponse(), HttpWebResponse)
Dim str As Stream = ws.GetResponseStream()
Dim inBuf(100000) As Byte
Dim bytesToRead As Integer = CInt(inBuf.Length)
Dim bytesRead As Integer = 0
While bytesToRead > 0
Dim n As Integer = str.Read(inBuf, bytesRead, bytesToRead)
If n = 0 Then
Exit While
End If
bytesRead += n
bytesToRead -= n
End While
Dim fstr As New FileStream("weather.jpg", FileMode.OpenOrCreate, FileAccess.Write)
fstr.Write(inBuf, 0, bytesRead)
str.Close()
fstr.Close()
End Sub 'Main
End Class 'WebRetrieve

Open in new window

0
 
markloganCommented:
There is a PHP function

http://php.net/manual/en/function.set-time-limit.php 

that you can set the timeout period for the single script.

Also in your php.ini file there is a variable (max_execution_time) you can change that sets the timeout period for all scripts.



To integrate the PHP into your VB you will need to do something like

Dim wr As HttpWebRequest = CType(WebRequestFactory.Create("http://www.testthis.com/download.php?file=testfile.zip"), HttpWebRequest)


Note: Make sure you don't have spaces in your file names. This can mess up the header in PHP and the file isn't returned.
<?php

if(isset($_GET['file']){

 $contentLen=@filesize($_GET['file']);

 header("Content-type: application/zip");
 header("Content-type: octet-stream");
 header('Content-Disposition: attachment; filename="".$_GET['file'].""');

 if($contentLen!=FALSE){
  header("Content-length: $contentLen");
 }

 readfile($_GET['file']);

}

?>

Open in new window

0
 
markloganCommented:
The PHP script would return the file piece by piece and your VB puts the file back together.


Quick question: If you are hiding the download location in the VB code why does it need to be hidden by the PHP script? Or are you going to use the PHP script externally as well, say in a browser?
0
 
andy7789Author Commented:
The file is to be downloaded via VB code as a desktop application. However, the location has to be hidden to avoid reverse engineering and downloading the file directly via web browser without correct username/password
0
 
markloganCommented:
So they insert the username and password to access the VB program.

If the user is to watch network traffic and requests on their PC they might notice the VB application requesting

http://www.testthis.com/download.php?file=testfile.zip

If they then try this in the browser they will download the file. Is it feasible for you to check the username and password again in the PHP code?
0
 
andy7789Author Commented:
yes - this is how it should be done., i.e. the PHP code will check the username and password. The VB application should only pass the access details to the server (PHP)
0
 
Ray PaseurCommented:
2) if username and password are valid, the script returns a download link. The actual d/l directory cannot be accessed directly, but only via that script after login validation.

How can I do the 2nd part?

Here is how I would handle it - all PHP.  

1. The login validation starts the session.  Either in that script or in the download script you would choose what file is to be downloaded.  The name of that file can be set in the $_SESSION array.

2. The download script looks in the $_SESSION array to get the file name.  If it is missing, obviously the client has not logged in so the file is not made available for download.  You can do your own set of validation rules here - it should be pretty obvious.

3. You take the file name out of the session and feed it to your version of this script.  AFAIK the function in here will force a download of any kind of file on any modern browser.

I like @ludofulop's suggestion to keep the files in a directory that is outside of the WWW root - less chance of an accidental data loss, and without the login credentials, your unauthorized visitors will not be able to get to the files.

Best regards, ~Ray
<?php // RAY_force_download.php
error_reporting(E_ALL);


// A FILE TO DOWNLOAD - THIS LINK COULD COME IN THE URL VIA $_GET OR COULD BE FOUND IN THE SESSION ARRAY AFTER LOGIN
$url = "http://a0.twimg.com/a/1252097501/images/twitter_logo_header.png";

// USE CASE
force_download($url);


// FUNCTION TO FORCE A DOWNLOAD
function force_download($filename)
{
   $basename = basename($filename);
   $filedata = file_get_contents($filename);

   if ($filedata)
   {
   // THESE HEADERS ARE USED ON ALL BROWSERS
      header("Content-Type: application-x/force-download");
      header("Content-Disposition: attachment; filename=\"$basename\"");
      header("Content-length: ".(string)(strlen($filedata)));
      header("Expires: ".gmdate("D, d M Y H:i:s", mktime(date("H")+2, date("i"), date("s"), date("m"), date("d"), date("Y")))." GMT");
      header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");

   // THIS HEADER MUST BE OMITTED FOR IE 6+
      if (FALSE === strpos($_SERVER["HTTP_USER_AGENT"], 'MSIE '))
      {
         header("Cache-Control: no-cache, must-revalidate");
      }

   // THIS IS THE LAST HEADER
      header("Pragma: no-cache");

   // FLUSH THE HEADERS TO THE BROWSER
      flush();

   // CAPTURE THE FILE IN THE OUTPUT BUFFERS - WILL BE FLUSHED AT SCRIPT END
      ob_start();
      echo $filedata;
   }
}

Open in new window

0
 
andy7789Author Commented:
Thank you guys, very helpful!
0
 
andy7789Author Commented:
I am playing with Ray's code and it works perfectly on small-to-medium files up to 12-13MB. Please, have a look at

http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/PHP/Q_25049980.html

what could be the problem here?
0

Featured Post

Keep up with what's happening at Experts Exchange!

Sign up to receive Decoded, a new monthly digest with product updates, feature release info, continuing education opportunities, and more.

Tackle projects and never again get stuck behind a technical roadblock.
Join Now