最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

uploads - Override json encoding in rest api

programmeradmin0浏览0评论

I'm trying to create a secure download plugin.

I found the rest api the best option for this task, but i cant change headers like Content-Type:. In the register_rest_route callback these headers already set. If i set Content-disposition: attachment, filename=asd.txt i get ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION in chrome.

I know rest is not meant to handle these tasks, but i couldn't find any alternatives. If there is, pls tell me about it.

The data downloaded seems like it's json encoded.

I'm sick of wordpress "magic". These tasks would be so easy using plain PHP but wordpress just makes it complicated.

Please someone tell me how to disable these magic headers *** or recommend me a way for secured (php) downloads in my plugin.

(No, i don't want to use third party plugins. I've tried many of them. None of them worked so far... :D These whole wordpress thing is a huge step backwards after symfony...)

Here is my experimental code:

add_action('rest_api_init', function() {
    register_rest_route('secure-downloads/v1', '/download/(?P<filename>.*)', 
    array(
        'methods' => 'GET',
        'callback' => 'secure_downloads_handle_download'
    ));
});
function secure_downloads_handle_download(WP_REST_Request $request)
{
    $filename = urldecode($request->offsetGet('filename'));
    if(strpos($filename, '/..'))
    {
        return new WP_REST_Response('', 403);
    }
    $path = SECURE_DOWNLOADS_UPLOAD_DIR . $filename;
    return new WP_REST_Response(file_get_contents($path), 200, array(
        'Content-Type' => 'application/octet-stream',
        'Content-Transfer-Encoding' => 'Binary',
        'Content-disposition' => 'attachment, filename=\\' . $filename . '\\'
    ));
}

I'm trying to create a secure download plugin.

I found the rest api the best option for this task, but i cant change headers like Content-Type:. In the register_rest_route callback these headers already set. If i set Content-disposition: attachment, filename=asd.txt i get ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION in chrome.

I know rest is not meant to handle these tasks, but i couldn't find any alternatives. If there is, pls tell me about it.

The data downloaded seems like it's json encoded.

I'm sick of wordpress "magic". These tasks would be so easy using plain PHP but wordpress just makes it complicated.

Please someone tell me how to disable these magic headers *** or recommend me a way for secured (php) downloads in my plugin.

(No, i don't want to use third party plugins. I've tried many of them. None of them worked so far... :D These whole wordpress thing is a huge step backwards after symfony...)

Here is my experimental code:

add_action('rest_api_init', function() {
    register_rest_route('secure-downloads/v1', '/download/(?P<filename>.*)', 
    array(
        'methods' => 'GET',
        'callback' => 'secure_downloads_handle_download'
    ));
});
function secure_downloads_handle_download(WP_REST_Request $request)
{
    $filename = urldecode($request->offsetGet('filename'));
    if(strpos($filename, '/..'))
    {
        return new WP_REST_Response('', 403);
    }
    $path = SECURE_DOWNLOADS_UPLOAD_DIR . $filename;
    return new WP_REST_Response(file_get_contents($path), 200, array(
        'Content-Type' => 'application/octet-stream',
        'Content-Transfer-Encoding' => 'Binary',
        'Content-disposition' => 'attachment, filename=\\' . $filename . '\\'
    ));
}
Share Improve this question asked Jul 5, 2017 at 15:22 JumiJumi 1511 silver badge5 bronze badges
Add a comment  | 

3 Answers 3

Reset to default 1

It should be enough to add the content disposition field.

But specifically it's Content-Disposition not Content-disposition

I would also add some validation to your filename parameter thats being passed to file_get_contents to ensure it exists and it's valid. Else you might be vulnerable to directory traversal attacks or remote URL requests

Here is what i found out:

The ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION error was the bad composition of the Content-Disposition header. Here is the correct one:

array(
    'Content-Disposition' => 'attachment; filename="' . $filename . '"'
)

Note the semicolon after attachment, the doublequotes around the filename and the capital D in disposition word as @Tom J Nowell pointed out.

This solved the headers problem but the WP_REST_Response still outputs the files like they were json encoded. So i went the conventional way:

header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Type: application/octet-stream');
readfile($path);
exit;

There is a hook for exactly this situation that avoids prematurely stopping script execution, and mirrors WP's handling of HEAD-method requests. Put something similar to this at the end of your REST response handler function:

...

add_filter(
    'rest_pre_serve_request',
    function($served, $result, $request, $rest_server) use ($path) {
        if (!$served) { # ensure nothing else has handled the request
            $served = true;
            if ('HEAD' !== $request->get_method()) {
                # output your response content here but do not return or exit
                readfile($path);
            }
        }
        return $served;
    },
    1, # change priority to suit
    4
);

$result = new WP_REST_Response();
$result->set_headers([
    'Content-Type' => mime_content_type($path),
    'Content-Length' => filesize($path),
    ...
]);
return $result;
发布评论

评论列表(0)

  1. 暂无评论