This started when I was trying to build a secure system that would store user input. The fundamental question is, if you allow authenticated users to upload image files and store them on a web server, how do you then prevent someone else from viewing those files? In the process of attempting to answer the question, I checked to see how Facebook does it; and I realized that they do not. No, not at all. Anyone with the direct URL can view any of the photos that you or anyone else has uploaded to Facebook, regardless of whatever your privacy settings might happen to be. The person viewing the images does not even have to be logged in to Facebook. They just enter the image URL in any browser, and there it is.
So, OK, the next question is how to build a system that’s more secure than Facebook? (Because unlike our Mr. Zuckerberg, I actually believe in that whole privacy thing, you know, the 4th Amendment and whatnot…)
The thing is, if the image is served the usual way, as a src attribute URL in a standard <img> tag, it’s very difficult to prevent someone from simply accessing the direct URL. That’s the weakness I observed with Facebook. Of course, the system’s file server could (and should) at least check to see if the user has a login cookie set; this can be done in Apache’s access directives, or in an .htaccess file on shared hosting. Facebook does not even take this basic step. That said, it is trivial for anyone who knows what they are doing to create a fake authentication cookie; and if they are accessing an image file directly, then their request will not call the script that would otherwise check the validity of the token.
But what if the image were to be encoded, stored in the database, and served as data embedded in the web page? In that case, the user would only be able to access the image if they requested the page itself. In that case, the script to authenticate the login token would be called before any image or other information was displayed. This would clearly be a more secure way to serve an image file.
But what about the performance penalty?
Enter benchmark testing.
My first test only checks the performance implications of serving a base64 encoded image file. This test does NOT check the performance implications of retrieving the file data from the database. I’ll post that in a follow-up.
The script in this case is so simple, it’s not even worth posting it on GitHub. This is all there is to it:
$file = 'image.png';
$start = (float) $_SERVER['REQUEST_TIME'];
for($i=0; $i<10; $i++){
// based on a function suggested by commenter luke at lukeoliff.com
// from the manual at http://us3.php.net/manual/en/function.base64-encode.php
$imgbinary = fread(fopen($file, "r"), filesize($file));
echo '<img alt="blah" src = "data:image/png;base64,' . base64_encode($imgbinary) . '" />';
}
$finish = microtime(true);
$elapsed = $finish - $start ;
echo 'Completed the request in ' . $elapsed . ' seconds.';
// now start over
$start = microtime(true);
for($i=0; $i<10; $i++){
$file = 'image.png';
echo '<img alt="blah" src="'. $file . '" />';
}
$finish = microtime(true);
$elapsed = $finish - $start ;
echo 'Completed the request in ' . $elapsed . ' seconds.';
I note that several commenters in different forums have pointed out that cURL is significantly faster than fopen(). However, I think they were talking about calling fopen over the network. cURL doesn’t seem to be designed for use with local files.
The upshot is that even with the overhead of both encoding and then parsing the encoded image files, the base64-encode version of this script suffered a performance penalty of a mere 0.006 seconds. That’s fast enough for even the toughest of performance junkies.
This proves the viability of serving base64 encoded files to the user. But what about retrieving the data from the database?
To be continued.