如果临时文件不是由PHP创建的,则move_uploaded_file不起作用

时间:2016-06-07 14:21:19

标签: php yii2

如果我使用创建文件,

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),但文件上传。

2 个答案:

答案 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()可能是您最不担心的事情。