How do I create a temporary file with a specified extension in php.
I came across tempnam() but using it the extension can't be specified.
Easiest way i have found is to create tempfile and then just rename it. For example:
$tmpfname = tempnam(sys_get_temp_dir(), "Pre_");
rename($tmpfname, $tmpfname .= '.pdf');
my way is using tempnam
$file = tempnam(sys_get_temp_dir(), 'prefix');
file_put_contents($file.'.extension', $data);
{
//use your file
}
unlink($file);//to delete an empty file that tempnam creates
unlink($file.'.extension');//to delete your file
This might simulate mkstemp() (see http://linux.die.net/man/3/mkstemp) a bit, achieving what you want to do:
function mkstemp( $template ) {
$attempts = 238328; // 62 x 62 x 62
$letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$length = strlen($letters) - 1;
if( mb_strlen($template) < 6 || !strstr($template, 'XXXXXX') )
return FALSE;
for( $count = 0; $count < $attempts; ++$count) {
$random = "";
for($p = 0; $p < 6; $p++) {
$random .= $letters[mt_rand(0, $length)];
}
$randomFile = str_replace("XXXXXX", $random, $template);
if( !($fd = #fopen($randomFile, "x+")) )
continue;
return $fd;
}
return FALSE;
}
So you could do:
if( ($f = mkstemp("test-XXXXXX.txt")) ) {
fwrite($f, "test\n");
fclose($f);
}
Let's say tempnam() gives you a file of "filename". You move it to "filename.ext". At any point, tempnam() can give you "filename" again. If you check for "filename.ext", reject the filename given by tempnam(), and call it again, you still end up with the possibility that between one step and another, a file will get created with the same name. (This is discussed in the user comments on the documentation page for tempnam(): https://www.php.net/manual/en/function.tempnam.php.)
However, if you just leave the file created by tempnam() alone (not deleting "filename" until you delete "filename.ext") and work with that filename + the extension, then there is no chance that tempnam() will use that filename again (as far as I can see). Yes, it is messy to have "filename" and "filename.ext" for every single file. On the other hand, it solves the problem.
public static function makeTempFileInFolder($prefix, $suffix, $folder="")
{
if (strlen($folder)==0) $folder = sys_get_temp_dir();
do {
$file = $folder."/".$prefix.rand(1,99999).time().$suffix;
} while (file_exists($file));
return $file;
}
The same as tempnam() except the additional parameter:
function tempnamp($dir, $prefix, $postfix) {
$maxAttempts = 1000;
// Trim trailing slashes from $dir.
$dir = rtrim($dir, DIRECTORY_SEPARATOR);
// If we don't have permission to create a directory, fail, otherwise we will
// be stuck in an endless loop.
if (!is_dir($dir) || !is_writable($dir)) return false;
// Make sure characters in prefix and postfix are safe.
if (strpbrk($prefix, '\\/:*?"<>|') !== false) return false;
if (strpbrk($postfix, '\\/:*?"<>|') !== false) return false;
// Attempt to create a random file until it works.
$attempts = 0;
do
{
$path = $dir.DIRECTORY_SEPARATOR.$prefix.mt_rand(100000, mt_getrandmax()).$postfix;
$fp = #fopen($path, 'x+');
} while (!$fp && $attempts++ < $maxAttempts);
if ($fp) fclose($fp);
return $path;
}
That 'p' at the end of the name stands for 'postfix'.
I prefer this solution:
$uid = uniqid('', true);
$path = sys_get_temp_dir() . "some_prefix_$uid.myextension";
Note: I do not put the prefix in uniqid because, IMHO, it's not its duty
Maybe using
move_uploaded_file($tmp_name, "$uploads_dir/$name.myextension");
See http://php.net/manual/en/function.move-uploaded-file.php#example-2209
Rename does it, find the extension with pathinfo and then replace with the extension you want.
$tmpfname = tempnam(sys_get_temp_dir(), 'FOO');
$newname = str_replace(pathinfo($tmpfname, PATHINFO_EXTENSION),'pdf',$tmpfname);
rename($tmpfname, $newname);
//do what you want with $newname
unlink($newname);
Related
I have a number of different hosting accounts set up for clients and need to calculate the amount of storage space being used on each account, which would update regularly.
I have a database set up to record each clients storage usage.
I attempted this first using a PHP file on each account, run by a Cron Job. If run manually by myself, it would output the correct filesize and update the correct size to the database, although when run from the Cron Job, it would output 0.
I then attempted to run this file from a Cron Job from the main account but figured this wouldn't actually work as my hosting would block files from another server and I would end up with the same result as before.
I am now playing around with FTP access to each account from a Cron Job from the main account which looks something like below, the only problem is I don't know how to calculate directory size rather than single file sizes using FTP access, and don't know how to reiterate this way? Hoping somebody might be able to help here before I end up going around in circles?
I will also add the previous first attempt too.
$ftp_conn = ftp_connect($ftp_host, 21, 420) or die("Could not connect to server");
$ftp_login = ftp_login($ftp_conn, $ftp_username, 'mypassword');
$total_size = 0;
$contents = ftp_nlist($ftp_conn, ".");
// output $contents
foreach($contents as $folder){
while($search == true){
if($folder == '..' || $folder == '.'){
} else {
$file = $folder;
$res = ftp_size($ftp_conn, $file);
if ($res != -1) {
$total_size = $total_size + $res;
} else {
$total_size = $total_size;
}
}
}
}
ftp_close($ftp_conn);
This doesn't work as it doesn't calculate folder sizes and I don't know how to open the reiterate using this method?
This second script did work but would only work if opened manually, and return 0 if run by the cron job.
class Directory_Calculator {
function calculate_whole_directory($directory)
{
if ($handle = opendir($directory))
{
$size = 0;
$folders = 0;
$files = 0;
while (false !== ($file = readdir($handle)))
{
if ($file != "." && $file != "..")
{
if(is_dir($directory.$file))
{
$array = $this->calculate_whole_directory($directory.$file.'/');
$size += $array['size'];
$files += $array['files'];
$folders += $array['folders'];
}
else
{
$size += filesize($directory.$file);
$files++;
}
}
}
closedir($handle);
}
$folders++;
return array('size' => $size, 'files' => $files, 'folders' => $folders);
}
}
/* Path to Directory - IMPORTANT: with '/' at the end */
$directory = '../public_html/';
// return an array with: size, total files & folders
$array = $directory_size->size($directory);
$size_of_site = $array['size'];
echo $size_of_site;
Please bare in mind that I am currently testing and none of the MySQLi or PHP scripts are secure yet.
If your server supports MLSD command and you have PHP 7.2 or newer, you can use ftp_mlsd function:
function calculate_whole_directory($ftp_conn, $directory)
{
$files = ftp_mlsd($ftp_conn, $directory) or die("Cannot list $directory");
$result = 0;
foreach ($files as $file)
{
if (($file["type"] == "cdir") || ($file["type"] == "pdir"))
{
$size = 0;
}
else if ($file["type"] == "dir")
{
$size = calculate_whole_directory($ftp_conn, $directory."/".$file["name"]);
}
else
{
$size = intval($file["size"]);
}
$result += $size;
}
return $result;
}
If you do not have PHP 7.2, you can try to implement the MLSD command on your own. For a start, see user comment of the ftp_rawlist command:
https://www.php.net/manual/en/function.ftp-rawlist.php#101071
If you cannot use MLSD, you will particularly have problems telling if an entry is a file or folder. While you can use the ftp_size trick, as you do, calling ftp_size for each entry can take ages.
But if you need to work against one specific FTP server only, you can use ftp_rawlist to retrieve a file listing in a platform-specific format and parse that.
The following code assumes a common *nix format.
function calculate_whole_directory($ftp_conn, $directory)
{
$lines = ftp_rawlist($ftp_conn, $directory) or die("Cannot list $directory");
$result = 0;
foreach ($lines as $line)
{
$tokens = preg_split("/\s+/", $line, 9);
$name = $tokens[8];
if ($tokens[0][0] === 'd')
{
$size = calculate_whole_directory($ftp_conn, "$directory/$name");
}
else
{
$size = intval($tokens[4]);
}
$result += $size;
}
return $result;
}
Based on PHP FTP recursive directory listing.
Regarding cron: I'd guess that the cron does not start your script with a correct working directory, so you calculate a size of a non-existing directory.
Use an absolute path here:
$directory = '../public_html/';
Though you better add some error checking so that you can see yourself what goes wrong.
file_exists isn't working. I've looked at a few examples and still no go. Program does not detect the file. The path of my file is /var/www/osbs/PHPAPI/recording.mp3 and the website root is inside osbs. The location of this file is inside PHPAPI that is why I do not put full path in file_put_contents. The program is able to make the original recording.mp3 but not any appended versions of it.
<?php
$actual_name = pathinfo("PHPAPI/recording.mp3",PATHINFO_FILENAME);
$original_name = $actual_name;
$extension = pathinfo("PHPAPI/recording.mp3",PATHINFO_EXTENSION);
if ($_GET["RecordingUrl"]) {
if (file_exists("/var/www/osbs/PHPAPI/".$actual_name.".".$extension)) {
$actual_name = find_new_name($original_name, $extension);
}
else {
$actual_name = $original_name;
}
$name = $actual_name.".".$extension;
file_put_contents($name, file_get_contents($_GET["RecordingUrl"]));
}
function find_new_name ( $file, $extension ) {
$name = $file.".".$extension;
$i = 0;
while(file_exists("/var/www/osbs/PHPAPI/".$name)){
$new_name = $file.$i;
$name = $new_name.".".$extension;
$i++;
}
return $new_name;
}
?>
Your issue is with the file_put_contents. You need to specify a full path, and you only specify a file name. Try echoing $name just before using it, you'll see it's not a path, just a filename.
I would recommend you to set a constant at the begining of the file with the path instead of sometimes relying on relative paths and sometimes relying on absolute paths.
<?php
const SAVE_PATH = "/var/www/osbs/";
$actual_name = pathinfo(SAVE_PATH."PHPAPI/recording.mp3",PATHINFO_FILENAME);
$original_name = $actual_name;
$extension = pathinfo(SAVE_PATH."PHPAPI/recording.mp3",PATHINFO_EXTENSION);
if (isset($_GET["RecordingUrl"]) && $_GET["RecordingUrl"]) {
if (file_exists(SAVE_PATH."PHPAPI/".$actual_name.".".$extension)) {
$actual_name = find_new_name($original_name, $extension);
}
else {
$actual_name = $original_name;
}
$name = $actual_name.".".$extension;
file_put_contents(SAVE_PATH.'PHPAPI/'.$name, file_get_contents($_GET["RecordingUrl"]));
}
function find_new_name ( $file, $extension ) {
$name = $file.".".$extension;
$i = 0;
while(file_exists(SAVE_PATH."PHPAPI/".$name)){
$new_name = $file.$i;
$name = $new_name.".".$extension;
$i++;
}
return $new_name;
}
?>
What I changed:
Defined a const SAVE_PATH = "/var/www/osbs/";
Use the new constant everywhere. No more relative sometimes and absolute sometimes, it's all absolute.
Used the constant in file_put_contents (THIS IS THE ACTUAL FIX, YOU NEED A FULL PATH HERE)
Added an additional check to make sure RecordingUrl isset, otherwise you get a PHP warning when it's not set.
The problem seems to be in the first line of your script:
$actual_name = pathinfo("PHPAPI/recording.mp3", PATHINFO_FILENAME);
This will assign recording.mp3 to $actual_filename. You are then checking for recording.mp3.mp3 by concatenating the extension to the filename. I think you want to use PATHINFO_BASENAME which will return the filename sans extension.
Are you sure about the path? /PHPAPI looks for the file inside /PHPAPI, not the expected /var/www/osbs/PHPAPI/. You should check for PHPAPI/$filename instead.
You have to put your all logical in the find_new_name() function. That would made your code clearer
if ($_GET["RecordingUrl"]) {
$name = find_new_name("PHPAPI/recording.mp3");
file_put_contents($name, file_get_contents($_GET["RecordingUrl"]));
}
function find_new_name($name) {
$info = pathinfo($name);
$name = $info['basename'];
$i = 0;
while (file_exists("$info[dirname]/$name")) {
$name = sprintf('%s%d.%s', $info['filename'], ++$i, $info['extension']);
}
return "$info[dirname]/$name";
}
You forgot the path with the file_put_contents().
It should be:
file_put_contents("PHPAPI/".$name, file_get_contents($_GET["RecordingUrl"]));
Or:
file_put_contents("/var/www/osbs/PHPAPI/".$name, file_get_contents($_GET["RecordingUrl"]));
You are confused between URL of a file and its PATH
Your httdoc(or public_html ) root is /var/www/osbs/PHPAPI
But Your filesystem root is '/'
Try
file_put_contents( __DIR__.'/'.$name, file_get_contents($_GET["RecordingUrl"]));
there are lots of bad practices in your code
'file_exists' and a few other file calls like fstat are cached by php. This is documented in the manual for file_exists. Your first call when the file does not exists is saved and returned in subsequent calls. Use 'clearstatcache()' between calls to clear the cache.
A "little" refactoring:
Absolute path everywhere
Transparent function, more self-explanatory name, simpler use of argument
Protection against malicious input ($_POST really doesn't cut it)
Why file_put_contents() when you want to actually copy()
<?php
define("SRC_PATH", "/var/www/osbs/whereverYourSrcIs/");
define("SAVE_PATH", "/var/www/osbs/PHPAPI/");
function findAvailableName($name) {
$i = 1;
$pathinfo = pathinfo($name);
while(file_exists($name)) {
$name = $pathinfo['dirname'] . '/' . $pathinfo['filename'] . "." . $i++ . "." . $pathinfo['extension'];
}
return $name;
}
if (isset($_GET["RecordingUrl"]) && $_GET["RecordingUrl"]) {
if (strpos('/' . $_GET['RecordingUrl'] . '/', '/../') !== false) {
die("invalid input, don't be evil");
}
copy(SRC_PATH . $_GET["RecordingUrl"], findAvailableName(SAVE_PATH . "recording.mp3"));
}
So with your help I have been able to assemble the below.
$dir = "./reporting/live-metrics/";
$des = "./reporting/historic-metrics/";
$ctime = time();
foreach (glob($dir."*") as $file) {
$live = file_get_contents($file);
if (strpos($live, 'CORO') === false && filemtime($file) < time() - 1 * 10) {
$exclude[] = $live;
$lines = file( $file , FILE_IGNORE_NEW_LINES );
$lines[3] = 'Taken Down';
$lines[5] = $ctime;
file_put_contents( $file , implode( "\n", $lines ) );
if (!file_exists($des.basename($file).PHP_EOL)) {
mkdir($des.basename($file), 0777, true);
}
rename($file,$des.$ctime);
}
}
My issue is that I am attempting to move the file to the new directory created but I am having a little issue with it. No matter what I do I can only get it to move to $des, i cant seem to get ti to move to the dynamicaly created directory for each specific file. I am assuming it has to do with the fact I am not using rename to its correct params. Below are the some of the combinations I have tried to get it to rename and move.
rename($file,$des.basename($file).PHP_EOL.$ctime); //doesn't move or rename
rename($file,$des.basename($file).$ctime); //adds to historic-metrics/ as jason1465519298
I also tried creating a function and setting the rename to call on that. eg.
$path = $des.basename($file).PHP_EOL;
rename($file,$path.$ctime);
Currently the script is great up until the moving the file. It will move it to ./reporting/historic-metrics/ but I would like it to move to the directory just created. EG, if the file it is currently handeling is called 'Jason' then it will create ./reporting/historic-metrics/Jason but move the file to ./reporting/historic-metrics/
There seems to be two possibilities:
Source or destination file paths may be wrong, you can print and check
The newly created destination directory is getting the
correct permission & ownership.
Otherwise your script looks OK.
I finally got it. My main issue was trying to get the filepath to send the file to. After a few interruptions and rethinking my approach I came up with the below. I know it isn't pretty or as slim as could be made but it does the job perfectly.
$dir = "./reporting/live-metrics/";
$des = "./reporting/historic-metrics/";
$ctime = time();
foreach (glob($dir."*") as $file) {
$live = file_get_contents($file);
if (strpos($live, 'CORO') === false && filemtime($file) < time() - 1 * 10) {
$exclude[] = $live;
$lines = file( $file , FILE_IGNORE_NEW_LINES );
$lines[3] = 'Taken Down';
$lines[5] = $ctime;
file_put_contents( $file , implode( "\n", $lines ) );
if (!file_exists($des.basename($file).PHP_EOL)) {
mkdir($des.basename($file), 0777, true);
}
$user = basename($file); //Gets file name that was used in mkdir
$path = (String) $des.$user."/"; //Compiles variables into string
rename($file,$path.$ctime);
}
}
I am writing an application in PHP where the user submits a form of data and a file name is chosen based off of the data, like so:
$filename = "./savelocation/".$name."_".$identification."_".$date.'.txt';
I am trying to use the file_exists() function to check to see if a file with the same name exists. If it does, the final name is changed to prevent overwriting the submitted form data. Here is my implementation:
$file = "./savelocation/".$name."_".$identification."_".$date.'.txt';
$file = preg_replace('/\s+/', '', $file);
$filepath = "./savelocation/".$name."_".$identification."_".$date.'.txt';
if(file_exists($filepath))
{
$file = "./savelocation/"."INVALIDFILE".'.txt';
}
This prevents people from overwriting applications by changing the name to a single file which acts as the 'default file' in which it doesn't matter if it is overwritten. However, I know this is wrong. My logic was that the if statement would return true, which would execute the code inside of the statement changing the file name to the 'default file'. Is this even a good way to prevent duplicate submissions?
Try this...if there is a match on the file name, break from the loop and redirect
$userFile = $name."_".$identification."_".$date.'.txt;
$fileArray = glob('./savelocation/*');
$arrCount = count($fileArray);
$i = 1;
$msg = null;
foreach ($fileArray as $FA) {
$fileSubstring = str_replace("\.\/savelocation\/", "", $FA);
if ($i > $arrCount) {
break;
} else if ($userFile === $fileSubstring) {
$msg = 'repeat';
break;
} else null;
$i++;
}
if (isset($msg)) header('location: PageThatChastisesUser.php');
Alternatively, if you tweak your code a bit to change your file name, this should work:
if(file_exists($file)) {
$file = str_replace("\.txt", "duplicate\.txt", $file);
}
Change the file name in a way that identifies itself to you as a duplicate.
Here's one way of doing it:
$file = "./savelocation/".$name."_".$identification."_".$date.'.txt';
$file = preg_replace('/\s+/', '', $filen);
$filepath = "./savelocation/".$name."_".$identification."_".$date.'.txt';
$i = 1;
while(file_exists($filepath))
{
$filepath = "./savelocation/".$name."_".$identification."_".$date.'_'.$i.'.txt';
$i++;
}
I need to check if a file exists but I don't know the extension.
IE I would like to do:
if(file_exists('./uploads/filename')):
// do something
endif;
Of course that wont work as it has no extension. the extension will be either jpg, jpeg, png, gif
Any ideas of a way of doing this without doing a loop ?
You would have to do a glob():
$result = glob ("./uploads/filename.*");
and see whether $result contains anything.
I've got the same need, and tried to use glob but this function seems to not be portable :
See notes from http://php.net/manual/en/function.glob.php :
Note: This function isn't available on some systems (e.g. old Sun OS).
Note: The GLOB_BRACE flag is not available on some non GNU systems, like Solaris.
It also more slower than opendir, take a look at : Which is faster: glob() or opendir()
So I've made a snippet function that does the same thing :
function resolve($name) {
// reads informations over the path
$info = pathinfo($name);
if (!empty($info['extension'])) {
// if the file already contains an extension returns it
return $name;
}
$filename = $info['filename'];
$len = strlen($filename);
// open the folder
$dh = opendir($info['dirname']);
if (!$dh) {
return false;
}
// scan each file in the folder
while (($file = readdir($dh)) !== false) {
if (strncmp($file, $filename, $len) === 0) {
if (strlen($name) > $len) {
// if name contains a directory part
$name = substr($name, 0, strlen($name) - $len) . $file;
} else {
// if the name is at the path root
$name = $file;
}
closedir($dh);
return $name;
}
}
// file not found
closedir($dh);
return false;
}
Usage :
$file = resolve('/var/www/my-website/index');
echo $file; // will output /var/www/my-website/index.html (for example)
Hope that could helps someone,
Ioan