mirror of
https://github.com/gosticks/wordpress-develop.git
synced 2026-08-11 20:30:23 +00:00
Admin/HTTP API: add suggested filename support to download_url().
This change allows for external clients to supply a suggested filename via a `Content-Disposition` response header. This filename is processed through `sanitize_file_name()` to ensure it is allowable (on the server, MIME's, etc...) and `validate_file()` to prevent directory traversal. If the suggested filename fails the above processing/checks, that suggestion is discarded and the standard temporary filename (generated by WordPress) is used. If no `Content-Disposition` header is found in the response headers, the standard temporary filename continues to be used as per normal. Included in this change are 6 additional PHPUnit tests with 9 assertions. These tests confirm that valid filename values are correctly saved, and invalid filename values are correctly rejected. Props cklosows, costdev, dd32, johnjamesjacoby, ocean90, psrpinto. Fixes #38231. git-svn-id: https://develop.svn.wordpress.org/trunk@51939 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
@@ -78,6 +78,177 @@ class Tests_Admin_IncludesFile extends WP_UnitTestCase {
|
||||
return 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ticket 38231
|
||||
* @dataProvider data_download_url_should_respect_filename_from_content_disposition_header
|
||||
*
|
||||
* @covers ::download_url
|
||||
*
|
||||
* @param $filter A callback containing a fake Content-Disposition header.
|
||||
*/
|
||||
public function test_download_url_should_respect_filename_from_content_disposition_header( $filter ) {
|
||||
add_filter( 'pre_http_request', array( $this, $filter ), 10, 3 );
|
||||
|
||||
$filename = download_url( 'url_with_content_disposition_header' );
|
||||
$this->assertStringContainsString( 'filename-from-content-disposition-header', $filename );
|
||||
$this->assertFileExists( $filename );
|
||||
$this->unlink( $filename );
|
||||
|
||||
remove_filter( 'pre_http_request', array( $this, $filter ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_download_url_should_respect_filename_from_content_disposition_header.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function data_download_url_should_respect_filename_from_content_disposition_header() {
|
||||
return array(
|
||||
'valid parameters' => array( 'filter_content_disposition_header_with_filename' ),
|
||||
'path traversal' => array( 'filter_content_disposition_header_with_filename_with_path_traversal' ),
|
||||
'no quotes' => array( 'filter_content_disposition_header_with_filename_without_quotes' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter callback for data_download_url_should_respect_filename_from_content_disposition_header.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_content_disposition_header_with_filename( $response, $args, $url ) {
|
||||
return array(
|
||||
'response' => array(
|
||||
'code' => 200,
|
||||
),
|
||||
'headers' => array(
|
||||
'content-disposition' => 'attachment; filename="filename-from-content-disposition-header.txt"',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter callback for data_download_url_should_respect_filename_from_content_disposition_header.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_content_disposition_header_with_filename_with_path_traversal( $response, $args, $url ) {
|
||||
return array(
|
||||
'response' => array(
|
||||
'code' => 200,
|
||||
),
|
||||
'headers' => array(
|
||||
'content-disposition' => 'attachment; filename="../../filename-from-content-disposition-header.txt"',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter callback for data_download_url_should_respect_filename_from_content_disposition_header.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_content_disposition_header_with_filename_without_quotes( $response, $args, $url ) {
|
||||
return array(
|
||||
'response' => array(
|
||||
'code' => 200,
|
||||
),
|
||||
'headers' => array(
|
||||
'content-disposition' => 'attachment; filename=filename-from-content-disposition-header.txt',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ticket 38231
|
||||
* @dataProvider data_download_url_should_reject_filename_from_invalid_content_disposition_header
|
||||
*
|
||||
* @covers ::download_url
|
||||
*
|
||||
* @param $filter A callback containing a fake Content-Disposition header.
|
||||
*/
|
||||
public function test_download_url_should_reject_filename_from_invalid_content_disposition_header( $filter ) {
|
||||
add_filter( 'pre_http_request', array( $this, $filter ), 10, 3 );
|
||||
|
||||
$filename = download_url( 'url_with_content_disposition_header' );
|
||||
$this->assertStringContainsString( 'url_with_content_disposition_header', $filename );
|
||||
$this->unlink( $filename );
|
||||
|
||||
remove_filter( 'pre_http_request', array( $this, $filter ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_download_url_should_reject_filename_from_invalid_content_disposition_header.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function data_download_url_should_reject_filename_from_invalid_content_disposition_header() {
|
||||
return array(
|
||||
'no context' => array( 'filter_content_disposition_header_with_filename_without_context' ),
|
||||
'inline context' => array( 'filter_content_disposition_header_with_filename_with_inline_context' ),
|
||||
'form-data context' => array( 'filter_content_disposition_header_with_filename_with_form_data_context' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter callback for data_download_url_should_reject_filename_from_invalid_content_disposition_header.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_content_disposition_header_with_filename_without_context( $response, $args, $url ) {
|
||||
return array(
|
||||
'response' => array(
|
||||
'code' => 200,
|
||||
),
|
||||
'headers' => array(
|
||||
'content-disposition' => 'filename="filename-from-content-disposition-header.txt"',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter callback for data_download_url_should_reject_filename_from_invalid_content_disposition_header.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_content_disposition_header_with_filename_with_inline_context( $response, $args, $url ) {
|
||||
return array(
|
||||
'response' => array(
|
||||
'code' => 200,
|
||||
),
|
||||
'headers' => array(
|
||||
'content-disposition' => 'inline; filename="filename-from-content-disposition-header.txt"',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter callback for data_download_url_should_reject_filename_from_invalid_content_disposition_header.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_content_disposition_header_with_filename_with_form_data_context( $response, $args, $url ) {
|
||||
return array(
|
||||
'response' => array(
|
||||
'code' => 200,
|
||||
),
|
||||
'headers' => array(
|
||||
'content-disposition' => 'form-data; name="file"; filename="filename-from-content-disposition-header.txt"',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a WP_Error object is returned when invalid input is passed as the `$url` parameter.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user