代码改变世界

19 与文件系统和服务器的交互 (1)

2016-08-24 13:27  yojiaku  阅读(322)  评论(0编辑  收藏  举报
第19章 与文件系统和服务器的交互

在文件上传前,先看一下php.ini文件中关于文件上传的几个指令:
1. file_uploads 控制是否荀彧HTTP方式的文件上传。循序值为On或Off 默认值为On(启用)

2. upload_tmp_dir 指定上传的文件在被处理之前的临时保存目录。如果没有设置该选项,将使用系统默认值 默认值为NULL
(如果没有配置这个选项,文件上传功能就无法实现,必须给这个选项设置值,比如:upload_tmp_dir = "e:/fileloadtmp" 代表在E盘目录下有一个fileloadtmp目录,并且给这个目录读写权利)

3. upload_max_filesize 控制允许上传的文件最大大小。如果文件大小大于该值,PHP将创建一个文件大小为0的占位符 默认值为2M
(如果想上传一个50M的文件,必须设定upload_max_filesize = 50M)

4. post_max_size 控制PHP可以接受的,通过post方法上传数据的最大值,该值必须大于upload_max_filesize设置值,因为它是所有post数据的大小,包括了任何上传的文件 默认值为8M
(如果post数据超出限制,那么$_POST 和 $_FILES将会为空;加入设置了upload_max_filesize = 50M,那么可以就设置 post_max_size = 100M;另外,如果启用了内存限制,那么该值应当小于memory_limit选项的值。)

5. 在php.ini的Resource Limits区域有3个选项:
· max_execution_time = 30
每个PHP页面运行的最大时间值(单位s),默认30秒。当我们上传一个较大的文件,例如50M的文件,很可能要几分钟才能上传完,但php默认页面最久执行时间为30秒,超过30秒,该脚本就停止执行,这就导致出现无法打开网页的情况。因此我们可以把值设置的较大些,如 max_execution_time = 600。 如果设置为0,则表示无时间限制。
· max_input_time = 60
每个PHP脚本解析请求数据所用的时间(单位秒),默认60秒。当我们上传大文件时,可以将这个值设置的较大些。 如果设置为0,则表示无时间限制。
· memory_limit = 128M
这个选项用来设置单个PHP脚本所能申请到的最大内存空间。这有助于防止写得不好的脚本消耗光服务器上的可用内存。如果不需要任何内存上的限制将其设为 -1。

php.ini配置上传文件功能示例:假设要上传一个50M的大文件
file_uploads = On
upload_tmp_dir = "e:/fileloadtmp"
upload_max_filesize = 50M
post_max_filesize = 100M
max_execution_time = 600
max_input_time = 600
memory_limit = 128M
(memory_limit > post_max_size > upload_max_filesize)

接下来,我们再看一看文件上传的HTML代码:
为了实现文件上传功能,我们需要用到一些专门用于文件上传的html语法——程序清单19-1 upload.html 上传文件的html表单
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Administration - upload files</title>
</head>
<body>
    <h1>Upload new news files</h1>
    <form action = "upload.php" method="post" enctype="multipart/form-data">
        <div>
            <input type="hidden" name="MAX_FILE_SIZE" value="1000000" />
            <label for="userfile">Upload a file:</label>
            <br />
            <input type="file" name="userfile" id="userfile" />
            <input type="submit" value="Semd File" />
        </div>
    </form>

<!-- 该表单运用了post方法。
        · 在<form> 标记中,必须设置属性enctype = "multipart/form-data",这样。服务器就可以知道上传的文件带有常规的表单信息。
        · 必须有一个可以设置上传文件最大长度的表单域。这是一个隐藏的域,如下:
         <input type="hidden" name="MAX_FILE_SIZE" value="1000000" />
         MAX_FILE_SIZE表单域是可选的,该值也可以在服务器端设置。然而,如果在这个表单中使用,名字必须是MAX_FILE_SIZE,其值是允许人们上传文件的最大长度值(按字节运算)。
         在这里,我们将其设置为1000000B(几乎是1MB)
        · 需要指定文件类型:
        <input type="file" name="userfile" id="userfile" />
        可以为文件选择喜欢的任何名字,但必须记住,将在PHP接收脚本中使用这个名字来访问文件。-->
</body>
</html>

  

然后,我们可以编写处理文件的PHP
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Uploading...</title>
</head>
<body>
    <h1>Uploading file...</h1>
    <?php
    if ($_FILES['userfile']['error']>0)
    {
        echo "Problem:";
        switch ($_FILES['userfile']['error'])
        {
            case 1: echo "File exceeded upload_max_filesize";
                break;
            case 2: echo "File exceeded max_file_size";
                break;
            case 3: echo "File only partially uploaded";
                break;
            case 4: echo "No file uploaded";
                break;
            case 6: echo "Cannot upload file: No temp directory specified";
                break;
            case 7: echo "Upload failed: Cannot write to disk";
                break;
        }
        exit;
    }
    /* 错误代码:
        · UPLOAD_ERROR_OK: 值为0,表示没有发生任何错误;
        · UPLOAD_ERR_INI_SIZE: 值为1,表示上传的文件大小超出了约定值。
        · UPLOAD_ERR_FROM_SIZE: 值为2,表示上传文件大小超出了html表单的MAX_FILE_SIZE所指定的最大值
        · UPLOAD_ERR_PARTIAL: 值为3,表示文件只被部分上传
        · UPLOAD_ERR_NO_FILE: 值为4,表示没有上传任何文件
        · UPLOAD_ERR_TMP_DIR: 值为6,表示在php.ini文件中没有指定临时目录
        · UPLOAD_ERR_CANT_WRITE: 值为7,表示将文件写入磁盘失败
    */
    // Dose the file have the right MIMI type?
    if($_FILES['userfile']['type'] != 'text/plain')
    {
        echo "Problem: file is not plain text";
        exit;
    }

    // put the file where we'd like it
    $upfile = 'uploads/'.$_FILES['userfile']['name'];

    if(is_uploaded_file($_FILES['userfile']['tmp_name']))
    {
        if(!move_uploaded_file($_FILES['userfile']['tmp_name'], $upfile))
        {
            echo "Problem: Could not move file to destination directory";
            exit;
        }
    }
    else
    {
        echo "Problem: Possible file upload attack. Filename:";
        echo $_FILES['userfile']['name'];
        exit;
    }

    echo "File uploaded successfully!<br /><br />";

    //remove possible HTML and PHP tags from the file's contents
    $contents = file_get_contents($upfile);
    $contents = strip_tags($contents);
    file_put_contents($_FILES['userfile']['name'], $contents);

    // show what was uploaded
    echo "<p> Preview of uploaded file contents:<br /><hr />";
    echo nl2br($contents);
    echo "<br /><hr />";
    ?>
</body>
</html>

  

在PHP中,需要处理的数据保存在超级全局数组$_FILES中,如果开启了register_globals指令,也可以直接通过变量名称访问这些信息。
保存$_FILES数组中的元素时,将同时保存html表单的<file>标记名称,表单元素名称是userfile,因此该数组将具有如下所示的内容:
· 存储在$_FILES['userfile']['tmp_name']变量中的值就是文件在Web服务器中临时存储的位置
· 存储在$_FILES['userfile']['name']变量中的值就是用户系统中的文件名称
· 存储在$_FILES['userfile']['size']变量中的值就是文件的字节大小
· 存储在$_FILES['userfile']['type']变量中的值就是文件的MIME类型,例如:text/plain 或者 image/gif
· 存储在$_FILES['userfile']['error']变量中的值将是任何与文件上传相关的错误代码

在我们引用的例子中,需要在Web服务器的根目录下创建一个名为uploads的目录,(在xampp中的web服务器根目录为htdocs)

运行时,我遇到了如下问题:
Warning: move_uploaded_file() expects exactly 2 parameters, 1 given in E:\xampp\htdocs\19sendFile\upload.php on line 51
Problem: Could not move file to destination directory
这个时候我回到upload.php文件第51行:
if(!move_uploaded_file($_FILES['userfile']['tmp_name']))
发现我少给move_uploaded_file一个参数:$upfile(文件移动的位置)
于是,我添加了一个参数:
if(!move_uploaded_file($_FILES['userfile']['tmp_name'], $upfile))
然后我刷新界面,发现又遇到了一个问题:
Warning: move_uploaded_file(/uploads/firstsightLove.txt): failed to open stream: No such file or directory in E:\xampp\htdocs\19sendFile\upload.php on line 51
Warning: move_uploaded_file(): Unable to move 'E:\xampp\tmp\php7A62.tmp' to '/uploads/firstsightLove.txt' in E:\xampp\htdocs\19sendFile\upload.php on line 51
Problem: Could not move file to destination directory
可以看出,应该是存放文件的目录出现问题。我这样改:
$upfile = 'uploads/'.$_FILES['userfile']['name'];
把原来的'/uploads/'改成了'uploads/',还将uploads文件夹放在了与upload.php一个目录下
然后刷新界面:File uploaded successfully!
啦啦啦,顺利解决文件上传的问题啦!
但是,又出现了新的问题:
Fatal error: Call to undefined function n12br() in E:\xampp\htdocs\19sendFile\upload.php on line 73
大概意思就是说n12br()函数不存在,O(∩_∩)O,我发现我错误地把nl2br()中的l字母写成了数字1,(>﹏<。)~呜呜呜……
在我纠正了所有的错误后,上传文件,华丽丽地发现中文显示为乱码!!!
鉴于这个问题有点麻烦,此小白决定先往后看看书,再回来解决这个问题。

效果展示:
没有上传文件时的界面:

上传文件时的界面:

上传成功的界面:

再来看一看文档目录:

注意这里我们上传的文件:firstsightLove.txt出现在了uploads目录下也出现在了19senFile的目录下


o(* ̄▽ ̄*)o到现在为止,我们已经做好一个简单的文件上传了!

但是,在文件上传时,需要注意几个问题:
· 前面的例子时假设用户已经在某些地方通过了身份验证。我们不应该允许任何人都可以上传文件到网站
· 如果允许不信任的或者没有通过身份验证的用户上传文件,就不许对文件的内容做出严格的规定。一个好办法就是将我们认为是“安全”的文件进行重命名
· 要降低用户“浏览Web服务器目录”的风险,你可以使用basename()函数来修改所接收文件的名称。
这个函数将过滤作为文件名称一部分而传入的目录路径,这是在服务器的不同目录下放置文件的常见攻击手段。
该函数示例如下:
<?php
$path = "/home/httpd/html/index.php";
$file1 = basename($path);
$file2 = basename($path, ".php");
print $file1."<br />"; // the value of $file1 is "index.php"
print $file2."<br />"; // the value of $file2 is "index"
· 如果使用的是基于Windows的机器,通常情况下,必须确保文件路径用“\\”或“/”替代“\”
· 就像我们以上脚本所做的,使用用户提供的文件名称可能会导致一系列的问题。最明显的问题就是如果上传的文件名称已经存在,可能会意外的覆盖已有文件。
还有一个不是非常明显的风险就是不同的操作系统,甚至是本地的不同语言设置将允许文件名称中包含不同的合法字符集。
对于一个被上传的文件来说,其文件名称可能就包含了对系统来说是不合法的字符。
· 如果文件上传脚本的运行出现问题,请检查PHP配置文件。需要将upload_tmp_dir指令设置成指向有访问权限的目录。
如果要上传文件,同时可能需要调整memory_limit指令。