move_uploaded_file()
不起作用
tempnam
。
我正在使用Yii2的UploadedFile::getInstance()
方法来创建文件实例。现在,这里的不同之处在于我在制作实例之前填充了$_FILES
,如下所示:
function addToFiles($key, $model, $url) {
$tempName = tempnam(ini_get('upload_tmp_dir'), 'php_files');
$originalName = basename(parse_url($url, PHP_URL_PATH));
$curl = new \common\components\Curl();
$imgRawData = $curl->get($url);
$fp = fopen($tempName, 'w');
fwrite($fp, $imgRawData);
fclose($fp);
$_FILES[$model] = array(
'name' => [$key => $originalName],
'type' => [$key => 'image/jpeg'],
'tmp_name' => [$key => $tempName],
'error' => [$key => 0],
'size' => [$key => strlen($imgRawData)],
);
}
然后,我创建一个如下的实例:
$file = UploadedFile::getInstance($instance, $attr);
转储$file
:
yii\web\UploadedFile#1
(
[name] => '20421_33_t.jpg'
[tempName] => 'D:\\xampp\\tmp\\php8005.tmp'
[type] => 'image/jpeg'
[size] => 2527
[error] => 0
)
现在,如果我打电话:
$file->saveAs($folder . '/' . $filename);
它只返回false
,没有任何警告或异常。
现在,我做了一些挖掘,发现Yii在move_uploaded_file()
方法中使用saveAs()
(没有任何错误抑制运算符)。以下是核心saveAs()
方法。
public function saveAs($file, $deleteTempFile = true)
{
if ($this->error == UPLOAD_ERR_OK) {
if ($deleteTempFile) {
return move_uploaded_file($this->tempName, $file);
} elseif (is_uploaded_file($this->tempName)) {
return copy($this->tempName, $file);
}
}
return false;
}
根据move_uploaded_file()
documentation,当返回错误时应该发出警告,但我什么都没得到!在我的设置中启用了错误报告+ Yii2的调试器,它应该在出现错误时突破!
问题是 - 这是设计吗?或者这是一个错误?或许我正在做一些可怕的错误?
顺便说一句,我并不是在寻找替代解决方案,因为我已经拥有它。我很想知道为什么这不起作用,比什么都重要。
需要注意的事项:
$folder
是可写的。 is_dir($folder) && is_writable($folder)
返回true。
$file->tempName
存在而非只读! readfile($file->tempName)
可以毫无问题地读取文件。
我甚至试过chmod($file->tempName , 0777);
,但它没有用。
如果我不使用addToFiles
方法,并且从表单上传相同的图像文件,则相同的方法可以正常工作。在这种情况下,实例的转储完全相同(只有tempName明显不同,例如D:\\xampp\\tmp\\phpDBCC.tmp
),但文件上传。
答案 0 :(得分:4)
这是预期的,你需要找到其他方法。
问题一:你不应该修改$_*
超级全局变量。有不同的东西可以打破。是希望它们是不可变的代码。如果你很幸运,你可以通过这个,但不要改变它,但要找到一种从这些文件中抽象出来并模拟抽象的方法。
问题二:move_uploaded_files()
是一个特殊目的函数。目的是绕过安全性测量(如open_basedir或,historical,safe_mode)来访问PHP内部所拥有的特定文件。这是必需的,因为在PHP可以处理请求并正确识别要运行的脚本之前,从客户端接收上载的文件。除了在特定请求开始时由PHP创建的文件外,此函数不应与其他文件一起使用。
要提供适当的解决方案,您必须详细说明您实际想要实现的目标。我的猜测是你想从其他地方获取数据并且不想让文件损坏。或者那个目的做这样的事情:
$tempName = tempnam($folder, 'php_files');
/* .... */
$fp = fopen($tempName, 'w');
/* ... */
fclose($fp);
rename($tempName, $folder . '/' . $filename);
请注意我直接写信给$folder
。这确保我们使用相同的设备,因此我们在存储文件时看到我们拥有所需的权限和足够的磁盘空间。 rename()
然后非常快,因为我们在同一台设备上。
这仍然不是完美的,因为脚本可能因任何原因在两者之间崩溃。这会将临时文件留在那里。所以你需要一个清理程序。
答案 1 :(得分:2)
PHP有rename(),这个函数可以(顾名思义)移动文件。是的,我很讽刺,这个名字很糟糕。
与任何其他有机进化的生物一样,PHP并不是没有重复的功能,但让move_uploaded_files()能够不移动某些文件的整个目的;更具体地说,那些碰巧在请求的路径中但从未被用户上传的那些,例如:
move_uploaded_files('/etc/passwd', '/var/www/html/img'); // false!
老实说,我对这个功能试图攻击的攻击向量没有最微妙的想法,但我不是安全专家,功能功能很明确。
关于缺乏警告,它就像记录在案一样。让我们仔细阅读(重点是我的):
返回值
成功时返回
TRUE
。如果filename不是有效的上传文件,则不会执行任何操作,并且 move_uploaded_file()将返回
FALSE
。如果文件名i 是有效上传文件,但某些人无法移动 原因,不会发生任何操作,并且move_uploaded_file()将返回
FALSE
。此外,还会发出警告。
理由?也许他们认为使用move_uploaded_file()
移动未在第一位上传的文件不需要进一步解释,但系统错误如磁盘已满或权限被拒绝可能会使用某些细节。
我可以想到你可能想要伪造上传的完全有效的场景(单元测试在我脑海中浮现),但这应该基于一个完全重新设计的架构,包括存根,模拟和所有那些花哨的东西,其中move_uploaded_files()可能是您最不担心的事情。