I'm trying to figure out a folder structure for storing user content such as images, which will work for a massive amount of users. I was going to go with something like... 000/000/001 (user id 1) ....999 max subfolder per folder.
But I would like to hide the total number of users, easily seen by starting at 1. Should I start at some random number like 349203480? I'm baffled as to how to work out the folders based on ID this way. Is there some better/easier way?
Use GUID's:
http://php.net/manual/en/function.uniqid.php
As per the PHP manual:
<?php
/* A uniqid, like: 4b3403665fea6 */
printf("uniqid(): %s\r\n", uniqid());
/* We can also prefix the uniqid, this the same as
* doing:
*
* $uniqid = $prefix . uniqid();
* $uniqid = uniqid($prefix);
*/
printf("uniqid('php_'): %s\r\n", uniqid('php_'));
/* We can also activate the more_entropy parameter, which is
* required on some systems, like Cygwin. This makes uniqid()
* produce a value like: 4b340550242239.64159797
*/
printf("uniqid('', true): %s\r\n", uniqid('', true));
?>
Create unique folders, create unique sub-folders. No one, including yourself, will ever know how many you have ... unless you are doing a count of folders / sub-folders on the FileSystem or are maintaining a reference of actual users to GUID's
Well, the simplest and easiest approach would be what you suggested. Pre-start counting users with a random number.
Another way would be to append a random number to your user id.
$userid = get_user_id();
$foldername = strval($userid) . strval(rand(1, 999));
//$foldername = 11, 231, ...
But of course, the above method has the overhead of checking if the folder already exists.
A third way would be use a simple encryption (such as rot13) on the username/id and append folder count from there on.
$username = get_user_name(); //Use the user id if you wish.
$next_folder_count = $last_folder_count + 1;
$foldername = str_rot13($username) . strval($next_folder_count);
//$foldername = hxcvf1, hxcvf2, ...
Again, you'll have to store the count of folders, or obtain it on the fly from the directory. The advantage of the last method would we that just by knowing the foldername, you can find out which user it belongs to.
Related
I'm writing a component for Joomla 2.5 that generates a graph using the PHP GD Library depending on the users input.
What name should I give the image file to avoid conflict when multiple users are using the component.
In my opinion you have two way.
The first is save the image with prefix like this
scotttiger-img12321323.jpg
where scotttiger is the username and 12321323 is the user input.
Another way is to use different folder for every user
scotttiger/1231321.jpg
But if You don't have the user logged in and doesn't know the username, do a simple check with isfile (http://it1.php.net/is_file) function and set incremental name. eg:
$wrote = false;
$prog = 0;
$filename="img-{$user_input}-{$prog}.jpg";
while(isfile($filename)) {
$prog++;
$filename="img-{$user_input}-{$prog}.jpg";
}
write_file($filename);
So you have the first time img-input-1.jpg the second once img-input-2.jpg and so on.
With this solution You doesn't have duplicates or conflict.
You can improve performance with this solution:
$wrote = false;
$prog = md5(time());
$filename="img-{$user_input}-{$prog}.jpg";
while(isfile($filename)) {
$prog = md5(time());
$filename="img-{$user_input}-{$prog}.jpg";
}
write_file($filename);
you can generate random name with help of e.g. date and php hash function:
http://www.php.net/manual/en/function.hash.php
So the name will be unique.
Or, depends on used feature in your component, you can somehow add the user name to the image name. It depends of the function of the component and on my factors (if there will be only one image per user, or per some other feature, etc.)
Jan
I have small problem.
I've coded a full website in php using CodeIgniter framework. One of my modules is search module, it contains text input with keyword and three select lists with filtering criterias.
That's ok, when I'm searching something - result's listing pagination is done via URL like that:
mysite.com/$keyword/$criteria1/$criteria2/$criteria3/$offset
works like a charm.
But when I'm entering into one of my images (it's image gallery) I want to have an option to go into NEXT and PREVIOUS image from my search results - the ones which I entered this image from.
I'm solving this case now in this way - I have session table called 'search_conditions' and I'm storing values of keyword and my three criterias there, but that's quite not comfortable, because why if someone opens second window and search something else there?
Then all of his searches in another windows or tabs are getting the same criteria - because with every new search, user overwrite the session value.
My next and previous functions:
public function next($count)
{
$search = $this->session->userdata('search_conditions'); //getting session table and overwriting it
$catid = isset($search['catid'])?$search['catid']:'0';
$brandid = isset($search['brandid'])?$search['brandid']:'0';
$prodid = isset($search['prodid'])?$search['prodid']:'0';
$keyword = isset($search['keyword'])?$search['keyword']:'';
$res = $this->search_model->main_search($keyword, $catid, $brandid, $prodid, $count, 1);
}
public function previous($count)
{
$search = $this->session->userdata('search_conditions');
$catid = isset($search['catid'])?$search['catid']:'0';
$brandid = isset($search['brandid'])?$search['brandid']:'0';
$prodid = isset($search['prodid'])?$search['prodid']:'0';
$keyword = isset($search['keyword'])?$search['keyword']:'';
$res = $this->search_model->main_search($keyword, $catid, $brandid, $prodid, $count-2, 1);
}
Can you recommend me some other, more comfortable solution, because this seems not to be good...
: )
Thank you!
Add an index to the $search_conditions variable:
$search_conditions[1]['catid']
$search_conditions[1]['brandid']
...
then refer to it with a controller's or config variable. This way you can allow one session to store multiple search conditions.
But I would recommend you drop storing the search condition in session. Instead, just pass it with the URI. Session data, in the case you describe, work as an intermediary; you don't need it. Use the Pagination Class and pass the search page number, not the direction (next or previous) to the URI.
Do not worry that the URI may look ugly - it only depends on what user searches for, and it's still friendly to share. Your only concern is if the GET string does not extend the limited length.
Pull the segments from the URI in your next() and previous() functions. Use the codeigniter URL helper. That should allow you to pass the different search criterion as variables to the next page, this would also remove your need to use the session.
I am trying to develop an application on android. The application is basically send the user's location information with the users it specified for a finite amount of time. It will be like Glympse.
I am doing this for a school project so don't have good hosts for the web. I am trying to use free ones. And i can not use their database systems to my own wishes. and inserting and fetching from the database continously will be a burden.
One other option coming to my mind, is to open up php page when the user wants to share hislocation. This page will continously communicate with the gps information. But as this pages should be specific to some people, they must have unique, unpredictable url. like the ones when have password recovery mails. Also i wonder how to make this pages exists for some time and disappears.
The page links will be sent to the shared users lately.
So what about storing any of the URI data is a session array and when a specific key is requested by the user use the received key and replace it by your actual URI stored in the $_SESSION array?
when generating a uri you could have a method store it for you and return a replacing URI
like so (note that you should have session already started that should not be part of this objects scope):
class URIStore{
private $URIstoreID;
private $SUPERSECRETSALT;
private $storeName;
const STORE_EXCEPTION_EXPIRED = 0;
const STORE_EXCEPTION_CSRF = 1;
public function __construct($storename){
//retreive existing store ID so we can built on previous
$this->URIstoreID = isset($_SESSION[$storename]['URIStoreID']) ? $_SESSION[$storename]['URIStoreID'] : 0;
$this->SUPERSECRETSALT = 'jsf098u32rojwe092';//salt could be made random here
$this->storename = $storename;
}
/**
* stored the $uri data in a session for later retrieval through the returned key
* #param mixed $uri - can be anything that you want, like a key value pair array
* #return string
*/
public function store($uri){
$r = md5($this->URIStoreID.$this->SUPERSECRETSALT);
$_SESSION[$this->storename][$r] = $uri;
$this->URIStoreID++;
$_SESSION[$this->storename]['URIStoreID'] = $this->URIStoreID;
return $r;
}
/**
* returns a previously stored URI item
* #param string $key - the key you want to get the URI data for
* #return mixed
* #Throws Exception - when Store or Key doesnt exist
*/
public function retrieve($key){
if(!isset($_SESSION[$this->storename]){
//the initial session has expired
throw new Exception("URIStore [$this->storename] does not exists",self::STORE_EXCEPTION_EXPIRED);
}
elseif(!isset($_SESSION[$this->storename][$key]){
//the requested key does not exist
//could be CSRF attempt?
throw new Exception("URIStore [$this->storename] does not contain this key",self::STORE_EXCEPTION_CSRF);
}
return $_SESSION[$this->storename][$key];
}
}
the use the above you could do the following when building a URL
$store = new URIStore('URIStore');
$URL = SERVER_URL . 'yourscriptthathandlestherequest.php?key=' . $store->store(array('a'=>1,'b'=>23));
and when you retrieve a request you first get the actual data instead of get data
like
$store = new URIStore('URIStore');
try{
$data = $store->retrieve(#$_GET['key']);
}
catch(Exception $e){
switch($e->getCode()){
case URIStore::STORE_EXCEPTION_EXPIRED:
//handle accordingly
break;
case URIStore::STORE_EXCEPTION_CSRF:
//handle accordingly
break;
}
}
note that in your case GET is fine, for CRUD actions I would highly suggest using POST
the Session will expire when no requests are made for a while (depends on server settings)
if you want different expirations for different items (still limited to the session expiration time as a maximum though) you would add another layer to the array that stores the uri and an additional timestamp that you have the URIStore::retrieve method compair against. and finally you could use multiple stores for different things if you wanted. so that you only use the one you expect on a certain page.
You could give the users a link like: script.php?key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx where the x's are an MD5. That MD5 could be the username and a "salt", like sort of a password. In other words, if $_GET['key'] matches md5($username . $super_secret_site_password) then you'll know that person is the real deal.
You need to have some kind of storage anyway, so I'm not goint into that.
The simplest solution is probably to have a unique number (Random unique ID or sequence number), suffix it with something like "read" and "write" and then secure it by building a hash over it and a secret key:
$secret = 'changeme';
$user_id = uniqid();
$public_url = 'page.php?user_id='.$user_id.
'&access=r&check='.sha1($user_id.'r'.$secret);
$owner_url = 'page.php?user_id='.$user_id.
'&access=w&check='.sha1($user_id.'w'.$secret);
Then you can check for them like this:
if ($_GET['check'] == sha1($_GET['user_id'].'r'.$secret))
{
// show location
}
if ($_GET['check'] == sha1($_GET['user_id'].'w'.$secret))
{
// do location update
}
If you don't want the user_id and access type to be visible in the URL you can use phpseclib to encrypt it instead of just securing it by sha1().
Is anything wrong with naming images uploaded by users (for example avatars) like that:
/user_id-username.jpg
Example:
/1-feont.jpg
All images has unique name (becouse user_id is primary key), but what about security? Has that any bad influence? If has, what conventions should I do instead?
Make sure that the username is appropriately sanitized before using it as part of the filename. User id should be system generated so that should not cause any problems.
The name that you give the images is purely conventional. I do not think it is a security issue for revealing the usernames of your users. (If it is, then you better check your CMS right away!) However, if your website is not completely secure a hacker can make use of SQL injection to access your user data.
But the idea is really far-fetched. You can go ahead with using usernames. :-)
IMO, just name the images as user-user_id.jpg (Here, "user" being a normal string followed by the integer - user_id)
Generally it is fine but some users may not like their name to be displayed and the user ID being a primary key could have vulnerability if your site isn't completely secure or if a PHP vulnerability is found in the future that is currently unknown.
For this sort of thing I tend to use the following code
$createName = date('YmdHis');
$fileType = '.jpg';
$imgName = $createName.$fileType;
This should give a string like 20110702155513.jpg - this is the full date and time the image was named and is unique.
If you really want to be safe then you can write a call back function that if there was a failure due to the file name not being unique (generally because there were 2 requests in the same second - unlikely but possible), then you can use a fall back adding the user ID in the middle of the string or use the fall back as your primary naming method, so for example
if($imgName == 'inuse'){
$createName1 = date('Ym');
$createName2 = date('dHis');
$fileType = '.jpg';
$imgName = $createName1.$userId.$createName2.$fileType;
}
This allows the user ID to be hidden but entirely unique.
*Edit - * another option is to use the existing format and create an MD5 hash, the code would be something like
$user_id = 'user_id';
$username = 'username';
$fileType = '.jpg';
$fileName = md5($user_id).'-'.md5($username).$fileType;
I hope this helps
Using the date as Ryan suggests fails when you have users that upload at the same time
Using the username fails when users may change their username. "Fail" is a bit hard here, but you have to move the files around which is something that's not needed when using another solution
Using the id solves all concurrency problems if the ID is an autogenerated auto-increment ID from your database. Opposed to Kerrek SB, I don't see a problem that this makes a connection between a unique identifier and an image on your file system. I mean it's unique and you can use it.
Using the ID also makes it easy for your users to find/link their image if you constantly use the ID publicly. If you on the other hand have only the user names in URLs - like /profile/$username, then I'd use the username in the image file name, too - to be consistent.
About security: If you sanitize the username, all is fine. Just make sure that it's unique, so people can't overwrite each other's names (which means that you need to use the same sanitation rules for usernames in database as you use for usernames in image file names).
Having the username as image name also makes it "easier" to find an image if you manually need to find one, but that probably happens not often enough to be of worth for you.
To summarize, I'd go with the ID.
there is no huge risk to naming files like the way you use in your first example but to make it more secure why don't you use something better like
function Naming($username,$imagename)
{
$uniq = time() ;
$name = $uniq.'-'.$username.'-'.$imagename ;
return $name ;
}
i think its better to avoid using users ID's
Unique user_id is enough, you don't need "username".
Problem isn't here, but in count of files (in one folder).
Split them by 1000 in folder (or by 100): take first, second and third symbols of ID string and put in separate dirs:
ID = 10524.jpg
filename = 1/0/5/10524.jpg
If it's hard to write algorithm, you can try this function:
function getStorePath($filename, $base_dir)
{
//file should have extension
$ext_pos = strrpos($filename, '.');
if ($ext_pos===false) return false;
//extension will be sanitized (filtered actually)
$ext = preg_replace("|\W+|", "", substr($filename, $ext_pos+1));
if (empty($ext)) return false;
if (in_array($ext, array('php', 'shtml', 'cgi', 'inc', 'module', 'sh', 'sql', 'class'))) return false;
//filename will be filtered
$filename = preg_replace("|\W+|", "", substr($filename, 0, $ext_pos));
if (empty($filename)) $filename = mt_rand(100000, 999999).round(microtime(true)*1000000);
//let's create path to the file.
//we will take first 3 symbols of filename as names of folders
$d = realpath($base_dir).'/';
//first symbol
$d .= $filename[0].'/';
if (!file_exists($d))
{
$md = mkdir($d, 0755);
if ($md===false && !file_exists($d)) return false;
}
//second symbol
if (isset($filename[1]))
{
$d .= $filename[1].'/';
if (!file_exists($d))
{
$md = mkdir($d, 0755);
if ($md===false && !file_exists($d)) return false;
}
}
//and third symbol
if (isset($filename[2]))
{
$d .= $filename[2].'/';
if (!file_exists($d))
{
$md = mkdir($d, 0755);
if ($md===false && !file_exists($d)) return false;
}
}
if (!file_exists($d.$filename.'.'.$ext)) return $d.$filename.'.'.$ext;
else return false;
}
I typically name my images a random string, then store it in the database attached to the uploaders id. You can also store other data about the image this way.
$filename = uniqid('', true) . '.jpg';
Image Table
id | user_id | filename | etc
Just my own two cents.
If you are gonna add more avatars for users to choose from, you're better off using a function like uniqid() instead of the user of. Else, you can do that all you like.
I use an MD5 hash of the userid and time() though. Just a matter of preference.
I'm toying with the idea of creating automatic electronic certificates. It's pretty easy to create custom certificates using the fpdf PHP class. The way I have it set up is that given a URL
http://www.example.com/makepdf.php?name=myname&class=classname
you get a PDF certificate with the student name and the class they took taken from the $_GET variable. Of course, this means that anyone could manipulate the URL to very easily create a custom certificate. (They could do this in Photoshop anyway, but the idea is to make manipulating the certificate not totally trivial.) Once a class is over, I want to send a mail merge to everyone with a unique URL for their certificate.
How would you approach this problem? Should I just create a set of random numbers and associate these with the student/workshop pairs in a database? Are there standard ways of approaching this problem?
Couple solutions stand out:
Store the names & classes in a database, and reference them with a numeric ID instead of passing the data in the request
Keep the information in the request, but add a secure hash that will prevent tampering with the data
The hash mechanism would be something like this:
When generating the link for the certificate, you have $name and $class. You'll create a third GET variable that is a hash of $name, $class, and a secret string that only your program knows. Something like this:
$salt = "this is my secret";
$hash = md5($name . $class . $salt);
$url = "http://www.mysite.com/certificate.php?name=" . urlencode($name) . "&class=" . urlencode($class) . "&hash=" . $hash;
Now when a user hits your certificate generation page, you must verify the hash:
$salt = "this is my secret";
$expected = md5($_GET['name'] . $_GET['class'] . $salt);
if ($expected != $_GET['hash']) {
die("You are not authorized");
} else {
// User is OK; generate the certificate
}
Yes, if you want to limit your inputs to a fixed pool, then creating a database full of random keys is the way I would go.
If you want a quicker and dirtier way to do it, just generate the keys into a text file, use a script to pull the file apart to send them to the recipients, and have your PHP certificate generator read from a copy of the file on the server.
Assuming you are generating these URLs yourself on the server, you could join all your parameter values together into a string:
hash_string = "myname:classname";
Then append a final parameter that's a hash of that string along with some secret seed:
query_string .= "&h=" . md5("my_secret_key:" . hash_string)
Then, when you get the query back, just check to make sure that the hash matches:
hash_string = params['name'] . ':' . params['class'];
if (params['h'] == md5("my_secret_key:" . hash_string)) ...
I don't really know PHP syntax, but you get the idea.
Your best bet would be to have a list of students/classes (some kind of database) and only allow generation of allowed certificates. That way you don't need to obfuscate the name of the student or class, because only valid certificates can be generated.
If that's too much to ask - you could generate a MD5 hash based on the combination and some salt, then add that hash to the URL. That way the salt would need to be know to forge a URL.
http://www.example.com/makepdf.php?name=Tim&class=PHP&hash=c2c455ce438112b44499561131321126
Then the generation script just does this:
$hash = md5($_GET['name'] . $_GET['class'] . $salt);
if($hash != $_GET['hash']){
//invalid request
}
Of course you'll need to generate the URL's with the same salt.
Should I just create a set of random numbers and associate these with the student/workshop pairs in a database?
Yes.