0

I have built a ZF2 application which includes user profiles and I now want to allow users to upload and display their photo as part of their profile. Something like what you see in LinkedIn.

Uploading the photo seems easy enough (using Zend\InputFilter\FileInput()). I have that working fine.

It seems to me that storing them outside of the web root makes a lot of sense. (For example, I don't need to worry about user's using wget on the directory). But how do I then embed these images as part of a web page?

If they were within the web root I would simply do <img width="140" src="/img/filename.jpg"> but obviously that's not possible if they are in a secure location. What's the solution?

DatsunBing
  • 8,684
  • 17
  • 87
  • 172

2 Answers2

5

You're right. Web developers traditionally obfuscate the paths used to store images to prevent malicious individuals from retrieving them in bulk (as you allude to with your wget comment).

So while storing a user's avatar in /uploads/users/{id}.jpg would be straightforward (and not necessarily inappropriate, depending on your use case), you can use methods to obfuscate the URL. Keep in mind: There are two ways of approaching the problem.

More simply, you want to ensure one cannot determine an asset URL based on "public" information (e.g., the user's primary key). So if a user has a user ID of 37, accessing their avatar won't be as simple as downloading /uploads/users/37.jpg.

A more vigorous approach would be to ensure one cannot relate a URL back to its public information. A URL like /uploads/users/37/this-is-some-gibberish.jpg puts its ownership "on display"; the user responsible for this content must be the user with an ID of 37.

A simple solution

If you'd like to go with simpler approach, generate a fast hash based on set property (e.g., the user's ID) and an application-wide salt. For PHP, take a look at "Fastest hash for non-cryptographic uses?".

$salt = 'abc123'; // Change this, keep it secret, store it as env. variable
$user->id; // 37
$hash = crc32($salt . strval($user->id)); // 1202873758

Now we have a unique hash and can store the file at this endpoint: /uploads/users/37/1202873758.jpg. Anytime we need to reference a user's avatar, we can repeat this logic to generate hash needed to create the filename.

The collision issue

You might be wondering, why can't I store it at /uploads/users/1202873758.jpg? Won't this keep my user's identity safe? (And if you're not wondering, that's OK, I'll explain for other readers.) We could, but the hash generated is not unique; with a sufficiently large number of users, we will overwrite the file with some other user's avatar, rendering our storage solution impotent.

To be more secretive

To be fair, /uploads/users/1202873758.jpg is a more secretive filename. Perhaps even /uploads/1202873758.jpg would be better. To store files with paths like these; we need to ensure uniqueness, which will require not only generating a hash, but also checking for uniqueness, accommodating for inevitable collisions, and storing the (potentially modified) hash—as well as being able to retrieve the hash from storage as needed.

Depending on your application stack, you could implement this an infinite number of ways, some more suitable than others depending on your needs, so I won't dive into it here.

Community
  • 1
  • 1
Jacob Budin
  • 9,753
  • 4
  • 32
  • 35
  • Little note: simply prevent overwriting via `/uploads/users/{userid}/{salted}.jpg` users usually have more than one image, so keeping them inside a respective user-folder is appropriate. In case of "Albums" and the likes, there may be more folders deeper down the road. – Sam Jan 26 '14 at 08:52
  • Hi Jacob, so in essence, I have to store it publicly? There is no other way to embed it in a page? – DatsunBing Jan 26 '14 at 23:05
  • @KimPrince What do you mean by "publicly"? Keep in mind, some services (even Facebook!) keep this data public; e.g., Zuckerberg's Facebook user ID is 4, and his avatar can be found at: `https://graph.facebook.com/4/picture`. To "embed" an image, you could do something like this: `'; ?>` but this is an *absolutely horrible idea*. – Jacob Budin Jan 27 '14 at 01:58
  • Hi, I am trying to do the same thing and I have encountered this base64 approach in other threads around the web. I would like to know why you are saying it's a horrible idea. Not being too experienced I can only think that it's not a good thing because the page will be very heavy to load. I am also thinking that if this is the only reason I might still use this method because I would be loading very small thumbnails with it, and then retrieving the complete images with a dedicated API. Thank you – Francesco D.M. Jul 28 '16 at 15:39
  • @FrancescoD.M. It's a bad idea because: your application needs to load these images into memory and encode them on every request, and your HTTP and application servers needs to then handle these oversized page responses. On the client's end, the page will be much larger and take longer to download and parse. It'll also be impossible for clients to cache the images, so they'll need to be "redownloaded" on every new page request. – Jacob Budin Aug 01 '16 at 13:42
  • @JacobBudin so I should not worry about storing user content in the public folder as long as I have a way of not linking the filename to any publicly available data. Probably just my lack of knowledge, but it doesn't seem very safe. Not only that, but it also seems to me that by serving each image with an http request in an environment with zf2 where the whole app needs to boostrap just to serve an image is a lot of overhead too. Perhaps one could write some rule in the .htaccess file in the public folder to help with this problem. BTW, your answers are very much appreciated, thank you – Francesco D.M. Aug 01 '16 at 17:53
0

If you use Zfcuser, you can use this module:HtProfileImage.

It contains a view helper to display images very easily!

Ujjwal Ojha
  • 1,340
  • 10
  • 17