Changeset 51939
- Timestamp:
- 10/27/2021 02:58:24 PM (3 years ago)
- Location:
- trunk
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-admin/includes/file.php
r51899 r51939 1113 1113 * @since 2.5.0 1114 1114 * @since 5.2.0 Signature Verification with SoftFail was added. 1115 * @since 5.9.0 Support for Content-Disposition filename was added. 1115 1116 * 1116 1117 * @param string $url The URL of the file to download. … … 1181 1182 1182 1183 return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ), $data ); 1184 } 1185 1186 $content_disposition = wp_remote_retrieve_header( $response, 'content-disposition' ); 1187 1188 if ( $content_disposition ) { 1189 $content_disposition = strtolower( $content_disposition ); 1190 1191 if ( 0 === strpos( $content_disposition, 'attachment; filename=' ) ) { 1192 $tmpfname_disposition = sanitize_file_name( substr( $content_disposition, 21 ) ); 1193 } else { 1194 $tmpfname_disposition = ''; 1195 } 1196 1197 // Potential file name must be valid string 1198 if ( $tmpfname_disposition && is_string( $tmpfname_disposition ) && ( 0 === validate_file( $tmpfname_disposition ) ) ) { 1199 if ( rename( $tmpfname, $tmpfname_disposition ) ) { 1200 $tmpfname = $tmpfname_disposition; 1201 } 1202 1203 if ( ( $tmpfname !== $tmpfname_disposition ) && file_exists( $tmpfname_disposition ) ) { 1204 unlink( $tmpfname_disposition ); 1205 } 1206 } 1183 1207 } 1184 1208 -
trunk/tests/phpunit/tests/admin/includesFile.php
r51639 r51939 77 77 public function __return_5() { 78 78 return 5; 79 } 80 81 /** 82 * @ticket 38231 83 * @dataProvider data_download_url_should_respect_filename_from_content_disposition_header 84 * 85 * @covers ::download_url 86 * 87 * @param $filter A callback containing a fake Content-Disposition header. 88 */ 89 public function test_download_url_should_respect_filename_from_content_disposition_header( $filter ) { 90 add_filter( 'pre_http_request', array( $this, $filter ), 10, 3 ); 91 92 $filename = download_url( 'url_with_content_disposition_header' ); 93 $this->assertStringContainsString( 'filename-from-content-disposition-header', $filename ); 94 $this->assertFileExists( $filename ); 95 $this->unlink( $filename ); 96 97 remove_filter( 'pre_http_request', array( $this, $filter ) ); 98 } 99 100 /** 101 * Data provider for test_download_url_should_respect_filename_from_content_disposition_header. 102 * 103 * @return array 104 */ 105 public function data_download_url_should_respect_filename_from_content_disposition_header() { 106 return array( 107 'valid parameters' => array( 'filter_content_disposition_header_with_filename' ), 108 'path traversal' => array( 'filter_content_disposition_header_with_filename_with_path_traversal' ), 109 'no quotes' => array( 'filter_content_disposition_header_with_filename_without_quotes' ), 110 ); 111 } 112 113 /** 114 * Filter callback for data_download_url_should_respect_filename_from_content_disposition_header. 115 * 116 * @since 5.9.0 117 * 118 * @return array 119 */ 120 public function filter_content_disposition_header_with_filename( $response, $args, $url ) { 121 return array( 122 'response' => array( 123 'code' => 200, 124 ), 125 'headers' => array( 126 'content-disposition' => 'attachment; filename="filename-from-content-disposition-header.txt"', 127 ), 128 ); 129 } 130 131 /** 132 * Filter callback for data_download_url_should_respect_filename_from_content_disposition_header. 133 * 134 * @since 5.9.0 135 * 136 * @return array 137 */ 138 public function filter_content_disposition_header_with_filename_with_path_traversal( $response, $args, $url ) { 139 return array( 140 'response' => array( 141 'code' => 200, 142 ), 143 'headers' => array( 144 'content-disposition' => 'attachment; filename="../../filename-from-content-disposition-header.txt"', 145 ), 146 ); 147 } 148 149 /** 150 * Filter callback for data_download_url_should_respect_filename_from_content_disposition_header. 151 * 152 * @since 5.9.0 153 * 154 * @return array 155 */ 156 public function filter_content_disposition_header_with_filename_without_quotes( $response, $args, $url ) { 157 return array( 158 'response' => array( 159 'code' => 200, 160 ), 161 'headers' => array( 162 'content-disposition' => 'attachment; filename=filename-from-content-disposition-header.txt', 163 ), 164 ); 165 } 166 167 /** 168 * @ticket 38231 169 * @dataProvider data_download_url_should_reject_filename_from_invalid_content_disposition_header 170 * 171 * @covers ::download_url 172 * 173 * @param $filter A callback containing a fake Content-Disposition header. 174 */ 175 public function test_download_url_should_reject_filename_from_invalid_content_disposition_header( $filter ) { 176 add_filter( 'pre_http_request', array( $this, $filter ), 10, 3 ); 177 178 $filename = download_url( 'url_with_content_disposition_header' ); 179 $this->assertStringContainsString( 'url_with_content_disposition_header', $filename ); 180 $this->unlink( $filename ); 181 182 remove_filter( 'pre_http_request', array( $this, $filter ) ); 183 } 184 185 /** 186 * Data provider for test_download_url_should_reject_filename_from_invalid_content_disposition_header. 187 * 188 * @return array 189 */ 190 public function data_download_url_should_reject_filename_from_invalid_content_disposition_header() { 191 return array( 192 'no context' => array( 'filter_content_disposition_header_with_filename_without_context' ), 193 'inline context' => array( 'filter_content_disposition_header_with_filename_with_inline_context' ), 194 'form-data context' => array( 'filter_content_disposition_header_with_filename_with_form_data_context' ), 195 ); 196 } 197 198 /** 199 * Filter callback for data_download_url_should_reject_filename_from_invalid_content_disposition_header. 200 * 201 * @since 5.9.0 202 * 203 * @return array 204 */ 205 public function filter_content_disposition_header_with_filename_without_context( $response, $args, $url ) { 206 return array( 207 'response' => array( 208 'code' => 200, 209 ), 210 'headers' => array( 211 'content-disposition' => 'filename="filename-from-content-disposition-header.txt"', 212 ), 213 ); 214 } 215 216 /** 217 * Filter callback for data_download_url_should_reject_filename_from_invalid_content_disposition_header. 218 * 219 * @since 5.9.0 220 * 221 * @return array 222 */ 223 public function filter_content_disposition_header_with_filename_with_inline_context( $response, $args, $url ) { 224 return array( 225 'response' => array( 226 'code' => 200, 227 ), 228 'headers' => array( 229 'content-disposition' => 'inline; filename="filename-from-content-disposition-header.txt"', 230 ), 231 ); 232 } 233 234 /** 235 * Filter callback for data_download_url_should_reject_filename_from_invalid_content_disposition_header. 236 * 237 * @since 5.9.0 238 * 239 * @return array 240 */ 241 public function filter_content_disposition_header_with_filename_with_form_data_context( $response, $args, $url ) { 242 return array( 243 'response' => array( 244 'code' => 200, 245 ), 246 'headers' => array( 247 'content-disposition' => 'form-data; name="file"; filename="filename-from-content-disposition-header.txt"', 248 ), 249 ); 79 250 } 80 251
Note: See TracChangeset
for help on using the changeset viewer.