PHP Conference Japan 2024

tempnam

(PHP 4, PHP 5, PHP 7, PHP 8)

tempnam建立具有唯一檔名的檔案

描述

tempnam(string $directory, string $prefix): string|false

在指定的目錄中建立具有唯一檔名的檔案,存取權限設定為 0600。如果目錄不存在或不可寫,tempnam() 可能會在系統的暫存目錄中產生檔案,並傳回該檔案的完整路徑,包括其名稱。

參數

directory

將建立暫存檔名的目錄。

prefix

產生的暫存檔名的前綴。

注意僅使用前綴的前 63 個字元,其餘的將被忽略。Windows 僅使用前綴的前三個字元。

回傳值

返回新的暫存檔名(含路徑),失敗時返回 false

變更紀錄

版本 描述
7.1.0 當回退到系統的暫存目錄時,tempnam() 現在會發出通知。

範例

範例 #1 tempnam() 範例

<?php
$tmpfname
= tempnam("/tmp", "FOO");

$handle = fopen($tmpfname, "w");
fwrite($handle, "writing to tempfile");
fclose($handle);

// 在這裡做一些事情

unlink($tmpfname);
?>

注意事項

注意如果 PHP 無法在指定的 directory 參數中建立檔案,它將回退到系統預設值。在 NTFS 上,如果指定的 directory 包含超過 65534 個檔案,也會發生這種情況。

參見

新增筆記

使用者貢獻筆記 22 則筆記

78
koyama
15 年前
請注意使用空白的 $dir 作為在系統暫存目錄中建立暫存檔案的「技巧」。

<?php
$tmpfname
= tempnam('', 'FOO'); // 不好
?>

如果 open_basedir 限制生效,則此技巧將不起作用。您將收到類似以下的警告訊息:

警告:tempnam() [function.tempnam]:open_basedir 限制生效。
File() 不在允許的路徑內:(/var/www/vhosts/example.com/httpdocs:/tmp)

以下方法有效:

<?php
$tmpfname
= tempnam(sys_get_temp_dir(), 'FOO'); // 很好
?>
25
Sebastian Kun
19 年前
如果您查看 C 函式 tempnam(3) 的 Linux 手冊頁面,您將在最後看到「永遠不要使用此函式。請改用 mkstemp(3)」。但是 php 的 tempnam() 函式實際上並未使用 tmpnam(3),因此沒有問題(在 Linux 下,如果 mkstemp(3) 可用,它將使用 mkstemp(3))。
15
php at REMOVEMEkennel17 dot co dot uk
19 年前
請注意,tempnam 會傳回暫存檔案的完整路徑,而不僅僅是檔名。
15
stanislav dot eckert at vizson dot de
7 年前
請注意,此函式可能會在 PHP 7.1.0 及更高版本中拋出通知。這是一個錯誤修正:https://bugs.php.net/bug.php?id=69489

您可以放置一個地址運算子 (@) 來抑制通知

<?php

if ($tmp = @tempnam() !== false) {
// ...
}

?>

或者您可以嘗試在您的 php.ini 中將 "upload_tmp_dir" 設定設為您系統的暫存資料夾路徑。不確定最後一個是否可以防止通知。
7
Jason Pell
17 年前
我想要保證檔案將在指定的目錄中建立,否則該函式應傳回 FALSE,我有一個簡單的函式可以工作,但我不確定它是否存在潛在的安全問題。

function dir_tempnam($dir, $prefix)
{
$real_dir_path = realpath($dir);
if (substr($real_dir_path, -1) != '/')
$real_dir_path .= '/';

$tempfile = tempnam($real_dir_path, $prefix);
$name = basename($tempfile);

if(is_file($real_dir_path.$name))
return $name;
else
{
@unlink($name);
return FALSE;
}
}

此函式僅傳回指定目錄中暫存檔案的名稱,或 FALSE。

顯然它可以傳回整個 $tempfile,但在我的情況下,我實際上想要將 basename 值分開。
13
Artur Graniszewski
14 年前
tempnam() 函式不支援由 stream_register_wrapper() 註冊的自訂串流封裝器。

舉例來說,如果你嘗試在 Windows 平台上使用 tempnam(),PHP 會嘗試在 %TMP% 資料夾(通常是:C:\WINDOWS\Temp)中產生獨特檔名,而不會有任何警告或通知。

<?php

// << ...自訂串流封裝器放在這裡...>>

echo '<pre>';
error_reporting(E_ALL);
ini_set('display_errors', true);
clearstatcache();
stream_register_wrapper('test', 'MemoryStream');

mkdir('test://aaa');
mkdir('test://aaa/cc');
mkdir('test://aaa/dd');
echo
'PHP '.PHP_VERSION;
echo
'<br />node exists: '.file_exists('test://aaa/cc');
echo
'<br />node is writable: '.is_writable('test://aaa/cc');
echo
'<br />node is dir: '.is_dir('test://aaa/cc');
echo
'<br />tempnam in dir: '.tempnam('test://aaa/cc', 'tmp');
echo
"<br /></pre>";

?>

輸出結果
--------------------
PHP 5.2.13
node exists: 1
node is writable: 1
node is dir: 1
tempnam in dir: C:\Windows\Temp\tmp1D03.tmp

如果你想要建立暫存檔,你必須建立自己的函式(可能會使用 opendir() 和 fopen($filename, "x") 函式)。
6
anakin dot skyw at gmx dot de
20 年前
>在 UNIX 下(你可以重新命名成一個已存在檔案,所以我使用了 link),你必須移除連結和連結的目標。

你不能這樣做嗎?
<?php
if ($newFileCreated) {
unlink ($sysFileName);
return
$newFileName;
}
?>
並獲得與 Windows 版本相同的語意?
9
Ron Korving
18 年前
這個函式會建立一個暫存目錄。如果 unlink() 和 mkdir() 之間,有其他程序建立了相同的目錄或檔案,先前的範例可能會出錯。這個實作方式也比較快。

<?php
function tempdir($dir, $prefix='', $mode=0700)
{
if (
substr($dir, -1) != '/') $dir .= '/';

do
{
$path = $dir.$prefix.mt_rand(0, 9999999);
} while (!
mkdir($path, $mode));

return
$path;
}
?>
8
bishop
20 年前
在動態網站上,建立具有特定副檔名的暫存檔是很常見的需求。這種需求主要是因為 Microsoft 瀏覽器會根據檔案的副檔名來識別下載檔案的 MIME 類型。

沒有單一的 PHP 函式可以建立具有特定副檔名的暫存檔名,而且,正如已經顯示的,除非你使用 PHP 的原子基本操作,否則會出現競爭條件。

我只使用以下的基本操作,並利用作業系統相關的行為來安全地建立具有特定後綴、前綴和目錄的檔案。請享用。

<?php
function secure_tmpname($postfix = '.tmp', $prefix = 'tmp', $dir = null) {
// 驗證參數
if (! (isset($postfix) && is_string($postfix))) {
return
false;
}
if (! (isset(
$prefix) && is_string($prefix))) {
return
false;
}
if (! isset(
$dir)) {
$dir = getcwd();
}

// 尋找暫存名稱
$tries = 1;
do {
// 取得已知、獨特的暫存檔名
$sysFileName = tempnam($dir, $prefix);
if (
$sysFileName === false) {
return
false;
}

// 加上副檔名
$newFileName = $sysFileName . $postfix;
if (
$sysFileName == $newFileName) {
return
$sysFileName;
}

// 將建立的暫存檔移動或指向新的檔名
// 注意:如果新的檔名存在,這些操作會失敗
$newFileCreated = (isWindows() ? @rename($sysFileName, $newFileName) : @link($sysFileName, $newFileName));
if (
$newFileCreated) {
return
$newFileName;
}

unlink ($sysFileName);
$tries++;
} while (
$tries <= 5);

return
false;
}
?>

isWindows 函式大部分留給讀者練習。以下是一個起點

<?php
function isWindows() {
return (
DIRECTORY_SEPARATOR == '\\' ? true : false);
}
?>

如同 tempnam(),這個函式要求你稍後清理自己的檔案。在 UNIX 下(你可以重新命名成一個已存在檔案,所以我使用了 link),你必須移除連結和連結的目標。清理工作完全留給讀者自行處理。
8
divinity76+yaoiporn at gmail dot com
11 年前
如果你不想自己處理刪除檔案的問題,而且你不需要自訂的前綴,你可以使用
$file_location=stream_get_meta_data(tmpfile())['uri'];
檔案會自動建立,並在腳本關閉時自動刪除(感謝 tmpfile()),我發現這對 CURLOPT_COOKIEFILE 非常有用(它需要檔案位置,而不是句柄)。
6
wapmorgan
10 年前
請注意,如果沒有傳遞第二個參數 <prefix>,tempnam 會回傳 NULL(而不是 false)
<?php
var_dump
(tempnam(sys_get_temp_dir())); // NULL
?>
也會產生警告
Warning: tempnam() expects exactly 2 parameters, 1 given in php shell code ...
4
tux ARROBA cenobioracing PUNTO com
18 年前
請注意,在 Windows NT 和其他 Windows 版本上,如果你有一個變數 $work_dir,其中包含指向你網站根目錄上某個目錄(或任何其他目錄)的路徑。請注意以下事項
<?php
$work_dir
= 'C:/some/path/to/document_root/dir';
file_exists($working_dir); // 回傳 true
is_writable($working_dir); // 回傳 true
$tempfile = tempnam($working_dir,'img');
//$temfile 現在包含一個系統範圍的暫存目錄檔案,例如 'C:/WINNT.SBS/img444.tmp',而不是我們傳遞給它的目錄
//那是因為我們需要給予 I_USR (IIS 使用者) 使用者寫入權限到 $working_dir,儘管根據前面提到的函式,它似乎已經具有寫入權限...
//如果你只想使用 tempnam 預設回傳的系統範圍暫存目錄,你還需要給予 I_USR 使用者寫入權限才能寫入該檔案...
?>
5
tomas at slax dot org
14 年前
請注意:函式不是原子的。如果許多程序同時呼叫相同的函式,你可能會遇到不必要的行為。

如果你需要自己的 tempnam 變體,請使用類似以下的程式碼

<?php
function tempnam_sfx($path, $suffix)
{
do
{
$file = $path."/".mt_rand().$suffix;
$fp = @fopen($file, 'x');
}
while(!
$fp);

fclose($fp);
return
$file;
}

// 像這樣呼叫它:
$file = tempnam_sfx("/tmp", ".jpg");
?>

如果需要,您可以將 mt_rand() 替換為其他隨機名稱產生器。
4
lreilly at lanl dot gov
22 年前
請小心您使用的正斜線和反斜線。像這樣看似無害的程式碼...

$uploaddir = "C:/Program Files/Apache Group/Apache2/htdocs/sasdap/uploads/";
$tempFile = tempnam ($uploaddir, "TMPANAL");
$fp = fopen($tmpfname, "w");
fwrite($fp, $iqdata);
//fclose($fp);

... 在輸出 $tempFile 時可能會顯示一些奇怪的東西";

例如: /Program Files/Apache Group/Apache2/htdocs/sasdap/uploads/\TMP3D.tmp

必須... 記得... 使用... 反斜線...

- Lee P. Reilly
2
Anonymous
12 年前
tempnam 不會在未授權的區域建立檔案。
這表示您需要有權限才能存取暫存目錄 ($dir),才能在那裡建立檔案。
3
phpdoc at rickbradley dot com
21 年前
下方提供的 "newtempnam" 配方(由 "tempnam" 於 "2003 年 7 月 23 日 08:56" 發佈)至少有一個競爭條件。while 迴圈會檢查以確保相關檔案不存在,然後再建立檔案。在存在性測試和 fopen() 呼叫之間,攻擊者有機會建立相關檔案。

這是一個典型的競爭條件,雖然看似難以利用,但針對這種草率的檔案建立方式,有許多知名的攻擊方法。

實作安全檔案建立所需的原子基本操作在 PHP 語言層級上不可用。這進一步突顯了 PHP 語言開發人員需要依賴該語言的安全性基本操作(包括 tempnam() 和 tempfile()),而不是自己實作。
3
dmhouse at gmail dot com
17 年前
Guillaume Paramelle 在下方的評論值得強調:tempnam() 不會接受其第一個目錄的相對路徑。如果您傳遞一個相對路徑,它(至少在 Windows XP 上)會在系統暫存目錄中建立暫存檔。

將相對路徑轉換為絕對路徑最簡單的方法是預先加入 getcwd()

<?php
$file
= tempnam('files/temp', 'tmp'); // 錯誤!
$file = tempnam(getcwd() . 'files/tmp', 'tmp') // 正確.
?>
1
david at frankieandshadow dot com
6 年前
Debian 9 Stretch 新增了私有暫存目錄,因此,如果您在 Apache 中以 mod_php 執行時使用此函式,並期望該檔案可以提供給第三方使用(例如,在 shell 命令或從暫存檔載入 MariaDB 時),它將不再運作:其他處理程序將無法看到該檔案(不過相同的 PHP 將能夠讀回該檔案)。

您需要使用第一個參數將暫存檔放置在特定的位置(在 HTML 文件樹之外),並具備可與其他方分享的權限,或者變更私有 tmp 的設定,例如

cp /lib/systemd/system/apache2.service /etc/systemd/system/
# 編輯 /etc/systemd/system/ 以將 PrivateTmp=true 變更為 false
systemctl daemon-reload
service apache2 restart

在共用伺服器上選擇不同的位置會比較好 - 新增私有 tmp 檔案是有原因的。

這是在最近升級作業系統時,困擾我很久的問題。
2
hm2k at php dot net
15 年前
<?php

function tempdir($dir=false,$prefix='php') {
$tempfile=tempnam('','');
if (
file_exists($tempfile)) { unlink($tempfile); }
mkdir($tempfile);
if (
is_dir($tempfile)) { return $tempfile; }
}

/*範例*/

echo tempdir();
// 傳回: /tmp/8e9MLi

?>
1
anthon at piwik dot org
14 年前
tempnam() 建立的檔案將具有反映套用至預設值 (例如,0600 或 -rw-------) 的目前 umask 的檔案權限。無論是在啟動網頁伺服器處理程序之前設定 umask,還是透過先前呼叫 PHP 的 umask() 函式設定 umask,情況都是如此。

例如,如果目前的 umask 為 0022,則建立的暫存檔權限為 0600 (擁有者讀取/寫入)。

此外,如果目前的 umask 為 0222,則建立的暫存檔權限為 0400 (擁有者唯讀)。(如果您的程式碼之後嘗試開啟暫存檔進行寫入,這會是個問題。)

務必記住,umask 會撤銷權限。在上述範例中,群組、其他或執行權限都未設定。

請參閱:umask()、chmod()。
-3
KOmaSHOOTER at gmx dot de
19 年前
此範例會建立名為 "user.txt" 的檔案
在目錄 www.XXXXX.XX/restricted/ 中
<?php
$tmpfname
= tempnam($_ENV["DOCUMENT_ROOT"]."/restricted", "FOO");
$handle = fopen($tmpfname, "w");
fwrite($handle, "writing to tempfile");
fclose($handle);

// 在這裡執行一些動作
//echo($_ENV["DOCUMENT_ROOT"]);
copy($tmpfname,'user.txt');
?>
-3
Nick Smith
19 年前
值得注意的是,如果您提供的 'dir' 不存在,則會被靜默忽略,並使用系統 /tmp 目錄。至少在 Linux PHP v4.1.2 下是這樣。

我有一個腳本在安全模式關閉的情況下似乎運作正常,但我沒有意識到我的 'dir' 參數有錯字(因此檔案都放在 /tmp 中),一旦安全模式開啟,我開始收到錯誤訊息,因為腳本的其餘部分無法從系統 /tmp 資料夾讀取檔案。
To Top