Php 当文件存储在webroot之外时,是否上载和下载文件?(菲律宾)

Php 当文件存储在webroot之外时,是否上载和下载文件?(菲律宾),php,html,mysql,file-upload,download,Php,Html,Mysql,File Upload,Download,我正在构建一个web应用程序,它本质上是一个工作板——用户创建“工作/项目”,然后将其显示为一个全球“待办事项”列表,供公司其他人查看 我尝试实现的功能之一是“附件”功能,允许用户上传文件作为每个项目数据的一部分。 其想法是允许用户安全地上传文件,然后允许其他用户下载附件 例如,如果我们正在为客户创建产品包装,那么最好能够将客户的徽标(a.pdf或其他格式)作为项目数据的一部分,以便使用我们的工作板查看项目的任何设计师都可以下载该文件 结合使用常见的上传技术并参考一本PHP书籍(Larry U

我正在构建一个web应用程序,它本质上是一个工作板——用户创建“工作/项目”,然后将其显示为一个全球“待办事项”列表,供公司其他人查看

我尝试实现的功能之一是“附件”功能,允许用户上传文件作为每个项目数据的一部分。

其想法是允许用户安全地上传文件,然后允许其他用户下载附件

例如,如果我们正在为客户创建产品包装,那么最好能够将客户的徽标(a.pdf或其他格式)作为项目数据的一部分,以便使用我们的工作板查看项目的任何设计师都可以下载该文件


结合使用常见的上传技术并参考一本PHP书籍(Larry Ullman编写的动态网站的PHP和MySQL),我构建了以下PHP脚本:

[..]
// check if the uploads form has been submitted:
if($_SERVER['REQUEST_METHOD'] == 'POST') {

//check if the $_FILES global has been set:
if (isset($_FILES['upload'])) {

    //create a function to rewrite the $_FILES global (for readability):
    function reArrayFiles($file) {
        $file_ary = array();
        $file_count = count(array_filter($file['name']));
        $file_keys = array_keys($file);
        for ($i=0; $i<$file_count; $i++) {
            foreach ($file_keys as $key) {
                $file_ary[$i][$key] = $file[$key][$i];
            }
        }
        return $file_ary;
    }

    //create a variable to contain the returned data & call the function 
        //**Quick note: I thought simply stating 'reArrayFiles($_FILES['upload']);' would be enough, but I guess not
    $file_ary = reArrayFiles($_FILES['upload']);

    //establish an array of allowed MIME file types for the uploads:
    $allowed = array(
        'image/pjpeg', //.jpeg
        'image/jpeg',
        'image/JPG',
        'image/X-PNG', //.png
        'image/PNG',
        'image/png',
        'image/x-png',
        'image/gif', //.gif
        'application/pdf', //.pdf
        'application/msword', //.doc
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document', //.docx
        'application/vnd.ms-excel', //.xls
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', //.xlsx
        'text/csv', //.csv
        'text/plain', //.txt
        'text/rtf', //.rtf
    );

    //these are two arrays for containing statements and errors that occured for each individual file upload
    //so I can choose exactly where these "errors" print on page, rather than printing where the script as a whole is called
    $statement = array();
    $upload_error = array();

    //multi-file upload, so perform checks and actions on EACH file upload individually:
    foreach ($file_ary as $upload) {

        //validate the uploaded file's MIME type using finfo:
        $fileinfo = finfo_open(FILEINFO_MIME_TYPE); //open handle

        //read the file's MIME type (using magic btyes), then check if it is w/i the allowed file types array
        if ( in_array((finfo_file($fileinfo, $upload['tmp_name'])), $allowed) ) {

            //check the file's MIME type AGAIN, but this time using the rewritten $_FILES['type'] global
            //it may be redundant to check the file type twice, but I felt this was necessary because some files made it past the first conditional
            if ( in_array($upload['type'], $allowed) && ($upload['size'] < 26214400) ) {

                //set desired file structure to store files:
                //the tmp directory is one level outside my webroot
                //the '$job_data[0]' variable/value is the unique job_id of each project
                //here, it is used to create a folder for each project's uploads -- in order to keep them organized
                $structure = "../tmp/uploads/job_" . $job_data[0] . "/";

                //check if the folder exists:
                if (file_exists($structure) && is_dir($structure)) {

                    //if directory already exists, get file count: (files only - no directories or subdirectories)
                    $i = 0;
                    if (($handle = opendir($structure))) {
                        while (($file = readdir($handle)) !== false) {
                            if (!in_array($file, array('.','..')) && !is_dir($structure.$file)) 
                                $i++;
                        }
                        closedir($handle);
                        $file_count = $i;
                    }
                } else {
                    //directory does not exist, so create it
                    //files are NOT counted b/c new directories shouldn't have any files) -- '$file_count == 0'
                    mkdir($structure);
                }

                //if file count is less than 10, allow file upload:
                //this limits the project so it can only have a maximum of 10 attachments
                if ($file_count < 10) {

                    if (move_uploaded_file($upload['tmp_name'], "$structure{$upload['name']}")) {
                        $statement[] = '<p>The file has been uploaded!</p>';
                    } else {
                        $statement[] = '<p class="error">The file could not be transfered from its temporary location -- Possible file upload attack!</p>';
                    }

                } else if ($file_count >= 10) { 

                    //if there are already 10 or more attachments, DO NOT upload files, return statement/error
                    $statement[] = '<p class="error">Only 10 attachments are allowed per Project.</p>'; 
                } 

            //ELSE FOR 2ND FILE TYPE CHECK:
            } else {
                $statement[] = '<p class="error">Invalid basic file type.</p>';
            }

            //set an error msg to $upload_error array if rewritten $_FILES['error'] global is not 0
            //this section of code omitted; literally every upload script does this
            if ($upload['error'] > 0) {
                switch ($upload['error']) {
                    [...]
                }
            }

            //remove the temp file if it still exists after the move/upload
            if ( file_exists($upload['tmp_name']) && is_file($upload['tmp_name']) ) {
                unlink ($upload['tmp_name']);
            }

        //ELSE FOR 1ST FILE TYPE CHECK
        } else {
            $statement[] = '<p class="error">Invalid MIME file type.</p>';
        }

        //close the finfo module
        finfo_close($fileinfo);

    } //END OF FOREACH

} //END OF isset($_FILES['upload']) conditional

}//END OF $_SERVER['REQUEST_METHOD'] == 'POST' conditional
[…]
//检查上传表单是否已提交:
如果($\u服务器['REQUEST\u METHOD']=='POST'){
//检查是否已设置$\u全局文件:
如果(isset($_文件['upload'])){
//创建一个函数来重写$\u全局文件(为了可读性):
函数reArrayFiles($file){
$file_ary=array();
$file_count=count(数组_过滤器($file['name']);
$file\u keys=数组\u keys($file);
对于($i=0;$i相关(1):
理论是:您将文件存储在webroot之外的某个位置,以防止直接访问和在服务器端执行。当用户提供正确的参数时,您必须能够找到它。(但是,您应该小心,文件是如何呈现给用户的?在这里,数据库可能会很有用)如果出于安全考虑,您必须确保用户无法访问他本不应该访问的文件(例如,当他拥有正确的下载链接但没有权限时,因为到目前为止,您的下载链接似乎不太神秘)

您在问题中提到的脚本非常好,尽管我可能只使用
fpassthru
而不是feof-fread-echo循环。这里的想法基本上是找出mime类型,将其添加到标题中,然后将内容转储到输出流中

关于第(2)款: 使用数据库,尤其是与准备好的语句一起使用,是非常安全的,并且提供了一些额外的可能性(例如向附件添加一些注释、时间戳、文件大小、重新排序等等)

您没有检查
上传名称
,这很可能是
。/../your webroot/index.php
或类似的内容。我的建议是将上传的文件存储为非想象的“文件ID”并将该id与原始文件名一起存储在数据库中。您可能还应该删除任何前导的多个点、斜杠(“目录”)和类似内容


加载栏…我想这就是我的口味。

为了清晰起见,我删除了我的评论,并将其放在了原始帖子中…尽管下载链接是基本的,但我还是要添加登录验证/权限,这样我们就清楚了(但感谢提醒)…我明白你关于数据库使用的意思,并且在检查$upload时有很好的收获['name']--我会解决的。
<form enctype="multipart/form-data" action="edit-job.php" method="post">
<input type="hidden" name="MAX_FILE_SIZE" value="26214400"/>
<fieldset>
    <legend>Upload Project Files</legend>
    <input type="file" name="upload[]"/>
    <input type="file" name="upload[]"/>
    <input type="file" name="upload[]"/>
    <input type="file" name="upload[]"/>
    <input type="file" name="upload[]"/>
    <p>Max Upload Size = 25MB</p>
    <p><b>Supported file types:</b> .jpeg, .png, .gif, .pdf, .doc, .docx, .xls, .xlsx, .csv, .txt, .rtf</p>
</fieldset>
<input type="submit" name="submit" value="Edit Job"/>
</form>