php - 如何在PHP扩展中捕获上传的文件数据

标签 php c++ c php-extension

我现在正在用 c/c++ 编写一个 PHP 扩展。用户上传文件(可以是 POST 或 PUT 方法,但我可以将其限制为仅 POST)。我需要捕捉 文件数据,同时 上传,而不将其写入服务器上的磁盘。我需要处理 数据和(也许, 视情况而定)将其发送到其他地方或将其保存到磁盘。 我当然知道,我可以处理文件 上传后(保存在服务器的磁盘上),但我会 喜欢避免它。 我还需要做一些相反的事情:我需要生成一个文件“在 飞”并发送 给用户。生成文件的所有元数据都是事先已知的 (例如尺寸、名称)。

找了好久没找到 任何接近解决方案的东西。 是否有任何示例或现有的 PHP 扩展可以做某事 像这样(至少类似的东西)?

最佳答案

我无法评论 Hook 上传过程,但对于下载部分你需要:

  1. 处理下载请求和发送 http header 的 php 脚本;
    必须注意根据 RFC 2183 的文件名,实际上只允许使用 us-ascii。
  2. php 扩展中的一个函数/方法,用于将数据流式传输到浏览器

php脚本

这是一个完整的 php 脚本,它还检查是否只请求了所需文件的范围:

<?php

// sanity checks ...



// script must not timeout
set_time_limit(0);
// user abortion is checked in extension while streaming the data
ignore_user_abort(true);


$filename = $_GET['filename'];
// TODO determine filesize
$filesize = 0;
$offset = 0;
$range_len = -1;
$have_valid_range = false;

if (isset($_SERVER['HTTP_RANGE']))
{
    // split 'bytes=n-m'
    list($range_type, $range) = explode('=', $_SERVER['HTTP_RANGE']);
    // split 'n-m' or 'n-'
    $range = explode('-', $range);
    // range type can only be 'bytes', check it anyway
    $have_valid_range = ($range_type == 'bytes') && is_array($range);
    if (!$have_valid_range)
    {
        header('HTTP/1.1 416 Requested Range Not Satisfiable', true, 416);
        exit;
    }

    if ($range[0] > $filesize)
    {
        $range[0] = $filesize;
    }
    if ((!$range[1]             )   || 
        ($range[1] > $filesize  )   )
    {
        $range[1] = $filesize;
    }
    $offset = $range[0];
    $range_len = $range[1]-$range[0]+1;
}

$attachment_filename = 'xyz';


// send metadata
header('Accept-Ranges: bytes');
if ($have_valid_range)
{
    header('HTTP/1.1 206 Partial Content', true, 206);
    header('Content-Length: ' . $range_len);
    header('Content-Range: bytes ' . $range[0] . '-' . $range[1] . ($filesize > 0 ? ('/' . $filesize) : ''));
}
else if ($filesize > 0)
{
    header('Content-Length: ' . $filesize);
}

// a note about the suggested filename for saving the attachment:
// It's not as easy as one might think!
// We deal (in our php scripts) with utf-8 and the filename is either the export profile's name or a term 
// entered by the user in the download form. Now the big problem is:
// According to the rfc for the Content-Disposition header only us-ascii characters are allowed! 
// (see http://greenbytes.de/tech/webdav/rfc2183.html, section "the filename parameter")
// However, all major browsers accept the filename to be encoded in iso-8859-1 (at least).
// There are other forms like: filename*="utf-8''<urlencoded filename>" but not 
// all browsers support this (most notably IE, only firefox and opera at the moment);
// (see http://greenbytes.de/tech/tc2231/ for testcases)
// 
// Additionally, IE doesn't like so much the '.' and ';' because it treats them as the beginning of the file extension,  
// and then thinks that it deals with a .*&%$§ file instead of a .zip file.
// The double quote '"' is already used as a delimiter for the filename parameter and it's unclear to me 
// how browsers would handle it.
// 
// Hence the procedure to produce a safe suggested filename as the least common denominator is as follows:
// Replace characters to be known as problematic with an underscore and encode the filename in iso-8859-1;
// Note that '?' (they can also result from utf8_decode()), '*', '<', '>', '|', ';', ':', '.', '\' are replaced by 
// firefox and IE with '_' anyway, additionally '#' by IE - meaning that they offer a filename with the mentioned 
// characters replaced by the underscore, i.e.: abc äöü +~*?ß=}'!§$%&/()´`<>|,-_:__@?\_{[]#.zip  -->  abc äöü +~__ß=}'!§$%&_()´`___,-____@___{[]#.zip 
$safe_attachment_fname = utf8_decode(str_replace(array('.', ';', '"'), '_', $attachment_filename)) . '.zip';
$filename_param = 'filename="' . $safe_attachment_fname . '"';

header('Content-Transfer-Encoding: binary');
header('Content-Type: application/zip');
header('Content-Disposition: attachment; ' . $filename_param);
// file can be cached forever by clients and proxies
header('Cache-Control: public');


// disable output buffering, stream directly to the browser;
// in fact, this is a must, otherwise php might crash
while (ob_get_level())
    ob_end_flush();


// stream data
ext_downstreamdata($filename, $offset, $range_len);

?>

从 C/C++ 流式传输

现在,对于 c++ 部分,上面 php 脚本中提到的函数 ext_downstreamdata() 完全是特定于实现的,但数据流本身可以通用化。

例如我的任务是将多层应用程序中的文件数据直接从应用程序服务器流式传输到浏览器。

这是一个函数,它作为 C++ 代码中流函数的回调函数,接收指向数据及其长度的指针(返回 Windows 错误代码):

unsigned long stream2browser(const void* pdata, size_t nLen)
{
    if (nLen)
    {
        // fetch zend's tls stuff
        TSRMLS_FETCH();

        // send data via the zend engine to the browser;
        // this method uses whatever output buffer mechanism (compression, ...) is in use;
        // It's a really good idea to turn off all output buffer levels in the php script because of 
        // strange crashes somewhere within the zend engine (or in one of the extensions?)
        // I did some debugging and the browser really crashes and it's not our fault, turning off the output 
        // buffering solves all problems; you turn it off like this in the script:
        //  <code>
        //  while (ob_get_level())
        //      ob_end_flush();
        //  </code>
        // I'm thinking to use an unbuffered output function (e.g. php_ub_body_write) but don't know for sure how to use it, so 
        // still stay away from it and rely on the script having disabled all output buffers

        // note: php_write returns int but this value is the bytes sent to the browser (which is nLen)
        size_t nSent = php_write((void*) pdata, uint(nLen) TSRMLS_CC);
        if (nSent < nLen)
        {
            if (PG(connection_status) & PHP_CONNECTION_ABORTED)
                return ERROR_CANCELLED;
            else
                return ERROR_NOT_CAPABLE;
        }
    }

    return ERROR_SUCCESS;
}

关于php - 如何在PHP扩展中捕获上传的文件数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19041667/

相关文章:

计算矩阵中的行总和

PHP MySQL 外部数据库连接超时

php - 表名错误,php/mysql

c++ - 不错的简短且可免费下载的 STL 教程

c - 总和字节的 SSE 代码。错误在哪里?

c - memcpy() 具有不同的数据类型

php - 如何在不加载 xlsx 文件的情况下设置事件工作表?

php - 在 Woocommerce 中获取订单项目数据值

c++ - 将 .open 与 ifsteam 对象一起使用时出现问题

C++ 前置和后置增量