From the course: PHP Tips, Tricks, and Techniques
Creating a download link
From the course: PHP Tips, Tricks, and Techniques
Creating a download link
- [Instructor] Hi, I'm David Powers, and welcome to this week's edition of PHP Tips, Tricks, and Techniques, designed to help you become a smarter, more productive PHP developer. Creating a download link for images or documents is a common requirement on a website. The basic process is very straightforward. Instead of linking directly to the file, you link to a download script and append the filename to the URL in a query string. The download script sends a series of headers to the browser and then reads and outputs the file. Because the download script is generic, it can be used with any file. And therein lies a serious risk. Query strings are easy to manipulate. So if you're not careful, the download script could be tricked into exposing sensitive files, such as your service password store. To prevent this happening, you need to block any attempt to explore your file system. With that in mind, let's get to the code. The exercise files for this video contain a simple webpage with two links. Both links point to a file called download.php. And then that's followed by a query string, with a name value pair. File equals and then the name of the file that we want to download. The first one is an image and the second one is a Word document. There's also a file called error.php. This contains a simple error message and the download script itself goes in download.php. So far the script consists of two statements, defining the location of the error page and of the downloads folder. To block any attempt to explore the file system, the file path to the downloads folder needs to be an absolute path like this. I've set both values to match my setup, but you need to change them to match your setup. Next, I'm going to create a variable for the filename. So simply, filename and initialize it to null. That's a keyword, so it's not in quotes. And doing this is a fail-safe technique. We want to make sure that nothing is downloaded, unless the next test is satisfied. We can get the filename from the query string, using the superglobal get array. And we need to make sure the value exists. So if isset, then the get array and the other word we're looking for is file. But that on its own is not enough. For example, something like this could result in downloading all usernames and passwords on a Linux server. We need to make sure that the value of file is simply a filename and not a relative path. We can do that by parsing get file to the basename function. So if we have & then basename and we parse that get file, and we compare it with the raw value of get file, what the basename function does is it returns the filename at the end of a path. So if these two are both the same, we know that nobody is trying to explore the file system. We've just got the filename. So if that's the case, inside our curly braces, we assign to filename the value of get file. But if they're not the same, we need to redirect to the error message. So an else block, and inside there the header function, then in double quotes, Location, error. And we also need to immediately exit the script. But assuming the value in get file isn't attempting to explore the file system, we can go ahead with the download. As a precaution, we'll use a conditional statement to make sure that filename isn't still null. So, if filename. Then we can build an absolute path to the requested file by concatenating the name of the path that we defined at the top of the script. So we'll create a variable called abs_path. Then we'll have this file path from up here. Concatenate onto that a PHP constant directory separator. This will insert a back slash or a forward slash, depending on the operating system, and then concatenating onto the end of that, the actual filename. So that locates our file. We need to make sure that it exists and that it's readable. So if file exists, we'll parse it to the absolute path. And is readable, again parsing it back to absolute path. If we've got a file that we can work with, we can then send some headers to the browser to download it. The first header. That will be Content-type, colon application, slash octet-stream. This simply streams that file to the browser in binary form. Then we need the size of the file, so another header. This one will be Content-length. And we can get the size using the PHP filesize function and parsing it, the absolute path to our file. Another header. This one will be Content-disposition. And it will be an attachment. We need a semicolon after that. Then filename equals. Now, we can concatenate onto the end of that the actual filename itself. So this will download the file with its original name. And the last header that we want. That's Content-transfer-encoding and it needs to be binary. So with all those headers being sent to the browser, we can finally output the file using readfile. We simply parse it to the absolute path. But if the file didn't exist or wasn't readable, we need to redirect to the error message. So an else block here. And again, we use the header function and our location is error. And that completes the download script, so let's go our browser and test this. I've got index.php lined up here. Click this first link here, Tunnel View: Yosemite. I immediately get this download dialog box and the filename is already in there. It's Tunnel_view.jpg. So just save that. And let's just make sure that it's been downloaded successfully. I'm going to click that, there it is. There's Tunnel_view.jpg. That's fine. And try it with the Word document. Again, we get the download dialog box, Word.docx. Just save that. And we'll quickly open it. There's the test file, so everything is working fine. This week, we looked at creating a link to download files, such as images or documents. Create a link to a download script and append the filename to the URL in a query string. The download script must block any attempt to explore the file system. The simple way to do this is to parse the value from the query string to the basename function and compare it with the raw value. If they're different, terminate the script. But if they're the same and the file exists, send a series of headers to the browser and output the file. If the file doesn't exist or if there's an attempt to explore the file system, redirect to an error page. This script relies on sending headers to the browser. So you might get the dreaded Headers Already Sent error, preventing the script from working. To avoid this, there must be no whitespace before the opening PHP tag in the download script. Nor must there be any output before calling the header function. The presence of the byte order mark at the beginning of a script can also trigger the Headers Already Sent error. This is an invisible character, but most good editing programs have an option to remove the BOM. That's it for this edition of PHP Tips, Tricks, and Techniques. Thanks for watching.
Practice while you learn with exercise files
Download the files the instructor uses to teach the course. Follow along and learn by watching, listening and practicing.
- Tip_18.zip
- Tip_19.zip
- Tip_01.zip
- Tip_20.zip
- Tip_02.zip
- Tip_25.zip
- Tip_03.zip
- Tip_21.zip
- Tip_22.zip
- Tip_04.zip
- Tip_23.zip
- Tip_24.zip
- Tip_06.zip
- Tip_07.zip
- Tip_34.zip
- Tip_30.zip
- Tip_31.zip
- Tip_14.zip
- Tip_32.zip
- Tip_17.zip
- Tip_33.zip
- Tip_29.zip
- Tip_35.zip
- Tip_36.zip
- Tip_37.zip
- Tip_09.zip
- Tip_10.zip
- Tip_38.zip
- Tip_39.zip
- Tip_40.zip
- Tip_08.zip
- Tip_26.zip
- Tip_11.zip
- Tip_12.zip
- Tip_13.zip
- Tip_15.zip
- Tip_16.zip
- Tip_28.zip
Contents
-
-
-
(Locked)
Round numbers to a specific multiple6m 57s
-
(Locked)
Array dereferencing4m 55s
-
Variable functions6m 8s
-
(Locked)
Build nested unordered lists automatically8m 56s
-
(Locked)
Display a repeating value only once4m 43s
-
(Locked)
Batch convert images to data URIs8m 18s
-
(Locked)
Multiple string replacement and SVGs8m 45s
-
(Locked)
Prevent cross-site script attacks in forms9m 39s
-
(Locked)
Changes to calculations with strings7m 35s
-
(Locked)
Unpacking arrays in PHP 7.17m 46s
-
(Locked)
User authentication with password hashing9m 20s
-
Set a future date7m 40s
-
(Locked)
Block access to expired member10m 55s
-
(Locked)
Extract complete sentences from start of text8m 48s
-
(Locked)
Prevent email header injection attacks7m 17s
-
(Locked)
Variable variables6m 47s
-
(Locked)
Select files for archiving9m 58s
-
(Locked)
Set a time limit on a session7m 27s
-
(Locked)
Custom sort an array with the spaceship operator7m 26s
-
(Locked)
Understanding the splat operator8m 55s
-
(Locked)
Converting new lines to real paragraphs7m 36s
-
(Locked)
Introducing PHP generators9m 33s
-
(Locked)
Dynamically editing a CSV file9m 32s
-
(Locked)
Finding all links in a webpage7m 35s
-
Creating a download link9m 45s
-
(Locked)
Debugging PDO prepared statements5m 59s
-
(Locked)
Time is running out for PHP 54m 43s
-
(Locked)
Extract values with a format string9m 10s
-
(Locked)
Generate harmonious color tones8m 7s
-
(Locked)
Getting all possible permutations of an array5m 39s
-
(Locked)
Merging arrays5m 57s
-
Strip accents from text9m 29s
-
(Locked)
Export associative arrays from a CSV6m 57s
-
Export spreadsheet data to a multi-table database10m 38s
-
(Locked)
Validate email address with accented characters3m 33s
-
(Locked)
Generating random numbers and strings7m 54s
-
(Locked)
Shorthand conditional expressions9m 9s
-
(Locked)
Modifying each element in an array8m 36s
-
Smart quotes and apostrophes7m 52s
-
(Locked)
Shortest distance between two locations6m 55s
-
(Locked)