69

I need to convert some files to PDF and then attach them to an email. I'm using Pear Mail for the email side of it and that's fine (mostly--still working out some issues) but as part of this I need to create temporary files. Now I could use the tempnam() function but it sounds like it creates a file on the filesystem, which isn't what I want.

I just want a name in the temporary file system (using sys_get_temp_dir()) that won't clash with someone else running the same script of the same user invoking the script more than once.

Suggestions?

cletus
  • 616,129
  • 168
  • 910
  • 942

10 Answers10

168

I've used uniqid() in the past to generate a unique filename, but not actually create the file.

$filename = uniqid(rand(), true) . '.pdf';

The first parameter can be anything you want, but I used rand() here to make it even a bit more random. Using a set prefix, you could further avoid collisions with other temp files in the system.

$filename = uniqid('MyApp', true) . '.pdf';

From there, you just create the file. If all else fails, put it in a while loop and keep generating it until you get one that works.

while (true) {
 $filename = uniqid('MyApp', true) . '.pdf';
 if (!file_exists(sys_get_temp_dir() . $filename)) break;
}
Lusid
  • 4,518
  • 1
  • 24
  • 24
  • 6
    I like the use of a random number for the first parameter on uniquid, however i use mt_rand() instead because of it's speed. this'll be helpful for places like where the while loop is used and there are many files. – Joshua K May 29 '11 at 16:55
  • 1
    I don't know if adding a rand() in there will necessarily make it any *more* random - odds are that both uniqid and rand() are being seeded by the system time. – Sam Dufel Mar 18 '13 at 23:16
  • 19
    This while loop should really be written as a do-while loop. – moteutsch Apr 17 '13 at 17:29
  • 8
    bit old, but that while loop should *never* be praised without a 2nd failover break implemented. ESPECIALLY in code that may well be part of a PHP CLI (or other "hidden" portions of UI), I have never seen such praise for a really bad piece of code - not impressed – user26676 Sep 30 '13 at 15:08
  • 3
    You should delimit the temp_dir and the filename: `!file_exists(sys_get_temp_dir() . '/' . $filename)` – Bouke Versteegh Jul 14 '15 at 06:02
  • 1
    The `uniqid(rand(), true)` command actually generated a period `.` in the name, which I found strange. I have stuck to `uniqid(rand(), false)` since, although the last entropy parameter is only meant to generate a random name of smaller length. – dr_rk Sep 22 '15 at 15:30
  • I think it's very unlikely but if you exhaust all possible filename IDs, wouldn't this be stuck in an infinite loop? – Nubcake Sep 10 '17 at 15:42
  • I'd be probably more "clean code" to do a do_while loop instead of a while_wend loop: `do { $filename = xxxx; } while( file_exists( yyy ) );` - It's easily read like "do seek for a new filename while it is already in use". – Xavi Montero Jun 01 '19 at 19:16
39

Seriously, use tempnam(). Yes, this creates the file, but this is a very intentional security measure designed to prevent another process on your system from "stealing" your filename and causing your process to overwrite files you don't want.

I.e., consider this sequence:

  • You generate a random name.
  • You check the file system to make sure it doesn't exist. If it does, repeat the previous step.
  • Another, evil, process creates a file with the same name as a hard link to a file Mr Evil wants you to accidentally overwrite.
  • You open the file, thinking you're creating the file rather than opening an existing one in write mode and you start writing to it.
  • You just overwrote something important.

PHP's tempnam() actually calls the system's mkstemp() under the hood (that's for Linux... substitute the "best practice" function for other OSes), which goes through a process like this:

  • Pick a filename
  • Create the file with restrictive permissions, inside a directory that prevents others from removing files it doesn't own (that's what the sticky-bit does on /var/tmp and /tmp)
  • Confirms that the file created still has the restrictive permissions.
  • If any of the above fails, try again with a different name.
  • Returns the filename created.

Now, you can do all of those things yourself, but what for, when "the proper function" does everything that's required to create secure temporary files, and that almost always involves creating an empty file for you.

Exceptions:

  • You're creating a temporary file in a directory that only your process can create/delete files in.
  • Create a randomly generated temporary directory, which only your process can create/delete files in.
Marcin Orlowski
  • 72,056
  • 11
  • 123
  • 141
Chris Cogdon
  • 7,481
  • 5
  • 38
  • 30
  • Not usable in all cases, such as when you use custom stream wrappers. IMHO this is a dealbreaker, as your code shouldn't care how the stream is handled, just that it does. https://www.php.net/manual/en/function.tempnam.php#97086 – XedinUnknown Aug 29 '23 at 15:06
3

Another alternative based on @Lusid answer with a failover of max execution time:

// Max exectution time of 10 seconds.
$maxExecTime = time() + 10; 
$isUnique = false;

while (time() !== $maxExecTime) {
    // Unique file name
    $uniqueFileName = uniqid(mt_rand(), true) . '.pdf';
    if (!file_exists(sys_get_temp_dir() . $uniqueFileName)){
        $isUnique = true;
        break;
    }
}

if($isUnique){
    // Save your file with your unique name
}else{
    // Time limit was exceeded without finding a unique name
}

Note:

I prefer to use mt_rand instead of rand because the first function use Mersenne Twister algorithm and it's faster than the second (LCG).


More info:

tomloprod
  • 7,472
  • 6
  • 48
  • 66
3

I recomend you to use the PHP function http://www.php.net/tempnam

$file=tempnam('tmpdownload', 'Ergebnis_'.date(Y.m.d).'_').'.pdf';
echo $file;
/var/www/html/tmpdownload/Ergebnis_20071004_Xbn6PY.pdf

Or http://www.php.net/tmpfile

<?php
$temp = tmpfile();
fwrite($temp, "writing to tempfile");
fseek($temp, 0);
echo fread($temp, 1024);
fclose($temp); // this removes the file
?>
FDisk
  • 8,493
  • 2
  • 47
  • 52
  • This won't work well, because tempnam will e.g. detect that AbCdEf file does not exist and therefore return that name. But you append .pdf and there could already be AbCdEf.pdf file (which tempnam could not detect) – k3a Apr 15 '12 at 14:38
  • 1
    This is wrong. The tempnam() actually creates the file, and the OP stated this as the reason for not using tempnam. You solution will fail because the file created will not be the file you are looking for (file xyz is created, you are looking for file xyz.pdf). – crafter Mar 13 '14 at 05:53
2

Better use Unix timestamp with the user id.

$filename='file_'.time().'_'.$id.'.jepg';
Pang
  • 9,564
  • 146
  • 81
  • 122
J Shubham
  • 609
  • 7
  • 21
2

Consider using an uuid for the filename. Consider the uniqid function. http://php.net/uniqid

Michael MacDonald
  • 708
  • 1
  • 6
  • 24
2

You could use part of the date and time in order to create a unique file name, that way it isn't duplicated when invoked more than once.

barfoon
  • 27,481
  • 26
  • 92
  • 138
0

My idea is to use a recursive function to see if the filename exists, and if it does, iterate to the next integer:

function check_revision($filename, $rev){
    $new_filename = $filename."-".$rev.".csv";
    if(file_exists($new_filename)){
        $new_filename = check_revision($filename, $rev++);
    }
    return $new_filename;
}

$revision = 1;
$filename = "whatever";
$filename = check_revision($filename, $revision);
kurdtpage
  • 3,142
  • 1
  • 24
  • 24
-1
function gen_filename($dir) {
    if (!@is_dir($dir)) {
        @mkdir($dir, 0777, true);
    }
    $filename = uniqid('MyApp.', true).".pdf";
    if (@is_file($dir."/".$filename)) {
        return $this->gen_filename($dir);
    }
    return $filename;
}
Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
-1

Update 2020

hash_file('md5', $file_pathname)

This way you will prevent duplications.

FabianoLothor
  • 2,752
  • 4
  • 25
  • 39
  • 2
    Does it? When two users upload a file with the same name the md5 hash will still be the same over the file path. So this isn't unique at all. – Floris Oct 15 '20 at 12:43