Changeset 63216
- Timestamp:
- 08/12/2026 12:32:34 PM (12 days ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/6.8/src/wp-includes/class-wp-image-editor-imagick.php
r60047 r63216 21 21 */ 22 22 protected $image; 23 24 /** 25 * Temporarily stores stream image data while processing internally. 26 * 27 * @see self::pdf_load_source() 28 * 29 * @since 7.0.4 30 * 31 * @var string|null 32 */ 33 private $stream_file_data = null; 34 35 /** 36 * Temporarily stores the parsed given name for an image while processing internally. 37 * 38 * @see self::pdf_load_source() 39 * 40 * @since 7.0.4 41 * 42 * @var string|null 43 */ 44 private $image_given_name = null; 23 45 24 46 public function __destruct() { … … 131 153 } 132 154 133 if ( ! is_file( $this->file ) && ! wp_is_stream( $this->file ) ) { 155 $is_stream = wp_is_stream( $this->file ); 156 $is_file = ! $is_stream && is_file( $this->file ); 157 158 // Only allow loading files or streams. 159 if ( ! $is_file && ! $is_stream ) { 134 160 return new WP_Error( 'error_loading_image', __( 'File does not exist?' ), $this->file ); 135 161 } 162 163 // Establish the provided filename based on the kind of resource being loaded. 164 $given_filename = $this->file; 165 if ( 0 === strncasecmp( $given_filename, 'file://', 7 ) ) { 166 $given_filename = basename( substr( $given_filename, 7 ) ); // 7 is the strlen of 'file://'. 167 } elseif ( 1 === preg_match( '~^https?://~i', $this->file ) ) { 168 /* 169 * For URLs, it will be the final path segment. 170 * 171 * Example: 172 * 173 * https://wordpress.org/i/happy.png?size=40px 174 * ╰───────╯ 175 * this is the given filename 176 * 177 * If the stream returns a `Content-Disposition` header it would 178 * provide an alternative name, but this is used as a reasonable 179 * proxy to avoid adding the additional complexity of reading and 180 * parsing the returned HTTP headers. 181 */ 182 $url_path = wp_parse_url( $this->file, PHP_URL_PATH ); 183 184 // This URL can not be parsed, so it is not a valid image resource. 185 if ( false === $url_path ) { 186 return new WP_Error( 'error_loading_image', __( 'File is not an image.' ), $this->file ); 187 } 188 189 /** 190 * The URL has an empty path, so continue with an empty string. 191 * 192 * This is the case with a URL such as `https://example.com?file_id=123` 193 */ 194 if ( null === $url_path ) { 195 $url_path = ''; 196 } 197 198 $last_path_at = strrpos( $url_path, '/' ); 199 $given_filename = is_int( $last_path_at ) ? substr( $url_path, $last_path_at + 1 ) : $url_path; 200 $given_filename = rawurldecode( $given_filename ); 201 } 202 203 /* 204 * Strip off any potential `Imagick` format specifiers. 205 * 206 * If a real file exists with the identified format specifier, then 207 * `Imagick` may not treat it as a format, but WordPress will reject 208 * it anyway to avoid adding more complexity into this detection. 209 * 210 * `Imagick` reads only the first `FORMAT:` specifier on a name, but 211 * stripping a segment would promote a second specifier to the front 212 * of the name handed to `Imagick`, which would then honor it. 213 * 214 * Loop to capture all format specifiers for comparison. 215 * 216 * Exclude Windows drive-letter prefixes from here. 217 */ 218 $imagick_formats = array(); 219 while ( 220 false !== ( $format_ends_at = strpos( $given_filename, ':' ) ) && 221 1 !== preg_match( '~^[a-z]:~i', $given_filename ) 222 ) { 223 $imagick_formats[] = strtoupper( substr( $given_filename, 0, $format_ends_at ) ); 224 $given_filename = substr( $given_filename, $format_ends_at + 1 ); 225 } 226 227 $file_extension = strtolower( pathinfo( $given_filename, PATHINFO_EXTENSION ) ); 136 228 137 229 /* … … 141 233 wp_raise_memory_limit( 'image' ); 142 234 235 /** 236 * Read the resource header for MIME sniffing. 237 * 238 * For files, which will be passed into Imagick by their file names, avoid 239 * eagerly loading the entire contents into PHP memory. For streams, however, 240 * it’s more important to avoid validating a separate copy of the file data 241 * than is later fetched by Imagick, so go ahead and load the entire payload, 242 * then pass it to Imagick as the data blob itself. 243 * 244 * @link https://mimesniff.spec.whatwg.org/#reading-the-resource-header 245 */ 143 246 try { 144 $this->image = new Imagick(); 145 $file_extension = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) ); 146 147 if ( 'pdf' === $file_extension ) { 148 $pdf_loaded = $this->pdf_load_source(); 247 if ( $is_file ) { 248 $file_data = file_get_contents( $this->file, false, null, 0, 1445 ); 249 } else { 250 $file_data = file_get_contents( $this->file ); 251 } 252 } catch ( Exception $e ) { 253 $file_data = false; 254 } 255 if ( false === $file_data ) { 256 return new WP_Error( 'error_loading_image', __( 'File does not exist?' ), $this->file ); 257 } 258 259 $pdf_extensions = array( 260 'ai', 261 'epdf', 262 'pdf', 263 'pdfa', 264 'pocketmod', 265 ); 266 267 // Reject files claiming to be PDFs which lack the required signature. 268 $has_pdf_extension = in_array( $file_extension, $pdf_extensions, true ); 269 $has_pdf_signature = str_starts_with( $file_data, '%PDF-' ); 270 if ( $has_pdf_extension && ! $has_pdf_signature ) { 271 return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file ); 272 } 273 274 $ps_formats = array( 275 'DPS', 276 'EPI', 277 'EPS', 278 'EPSF', 279 'EPSI', 280 'PS', 281 'WPG', 282 ); 283 284 $ps_extensions = array( 285 'dps', 286 'epi', 287 'eps', 288 'eps2', 289 'eps3', 290 'epsf', 291 'epsi', 292 'ept', 293 'ept2', 294 'ept3', 295 'ps', 296 'ps2', 297 'ps3', 298 'wpg', 299 ); 300 301 // Reject files which Imagick will parse as PostScript. 302 if ( 303 array() !== array_intersect( $imagick_formats, $ps_formats ) || 304 in_array( $file_extension, $ps_extensions, true ) || 305 str_starts_with( $file_data, '%!' ) || 306 str_starts_with( $file_data, "\x04%!" ) || 307 str_starts_with( $file_data, "\xC5\xD0\xD3\xC6" ) || 308 str_starts_with( $file_data, "\xFFWPC" ) 309 ) { 310 return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file ); 311 } 312 313 $compressed_extensions = array( 314 'gz', 315 'bz2', 316 'svgz', 317 'z', 318 'wmz', 319 ); 320 321 /* 322 * Reject compressed archives that Imagick will transparently decompress. 323 * Unfortunately this rejects `.svgz` because there’s no intermediate step 324 * in the loading process. `Imagick` would decompress the file, then look 325 * to see what kind of content was decompressed instead of asserting SVG. 326 */ 327 if ( 328 in_array( $file_extension, $compressed_extensions, true ) || 329 str_starts_with( $file_data, "\x1F\x8B\x08" ) || // gzip 330 str_starts_with( $file_data, 'BZh' ) || // bzip2 331 str_starts_with( $file_data, "\x1F\x9D" ) // compress 332 ) { 333 return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file ); 334 } 335 336 try { 337 $this->image = new Imagick(); 338 339 if ( $has_pdf_signature ) { 340 /* 341 * Load these values for use in the helper method without forcing a change 342 * of its expected arguments, but then free them after calling to prevent 343 * keeping them around in memory and bloating the app. 344 */ 345 $this->stream_file_data = $is_stream ? $file_data : null; 346 $this->image_given_name = $given_filename; 347 $pdf_loaded = $this->pdf_load_source(); 348 $this->stream_file_data = null; 349 $this->image_given_name = null; 149 350 150 351 if ( is_wp_error( $pdf_loaded ) ) { … … 152 353 } 153 354 } else { 154 if ( wp_is_stream( $this->file ) ) { 155 // Due to reports of issues with streams with `Imagick::readImageFile()`, uses `Imagick::readImageBlob()` instead. 156 $this->image->readImageBlob( file_get_contents( $this->file ), $this->file ); 355 if ( $is_stream ) { 356 $this->image->readImageBlob( $file_data, $given_filename ); 157 357 } else { 158 358 $this->image->readImage( $this->file ); … … 169 369 } 170 370 171 if ( 'pdf' === $file_extension) {371 if ( $has_pdf_signature ) { 172 372 $this->remove_pdf_alpha_channel(); 173 373 } … … 1111 1311 } 1112 1312 1113 try { 1114 /* 1115 * When generating thumbnails from cropped PDF pages, Imagemagick uses the uncropped 1116 * area (resulting in unnecessary whitespace) unless the following option is set. 1117 */ 1118 $this->image->setOption( 'pdf:use-cropbox', true ); 1119 1120 /* 1121 * Reading image after Imagick instantiation because `setResolution` 1122 * only applies correctly before the image is read. 1123 */ 1124 $this->image->readImage( $filename ); 1125 } catch ( Exception $e ) { 1126 // Attempt to run `gs` without the `use-cropbox` option. See #48853. 1127 $this->image->setOption( 'pdf:use-cropbox', false ); 1128 1129 $this->image->readImage( $filename ); 1130 } 1131 1132 return true; 1313 foreach ( array( 'true', 'false' ) as $use_cropbox ) { 1314 try { 1315 /** 1316 * When generating thumbnails from cropped PDF pages, Imagemagick uses the uncropped 1317 * area (resulting in unnecessary whitespace) unless the following option is set. 1318 * 1319 * However, it sometimes fails, so if that happens, run it without the option. 1320 * 1321 * @ticket 48853 1322 */ 1323 $this->image->setOption( 'pdf:use-cropbox', $use_cropbox ); 1324 1325 /* 1326 * Reading image after Imagick instantiation because `setResolution` 1327 * only applies correctly before the image is read. 1328 */ 1329 if ( is_string( $this->stream_file_data ) ) { 1330 $this->image->setFilename( 'PDF:unknown.pdf[0]' ); 1331 $this->image->readImageBlob( $this->stream_file_data, $this->image_given_name ); 1332 } else { 1333 $this->image->readImage( $filename ); 1334 } 1335 1336 return true; 1337 } catch ( Exception $e ) { 1338 continue; 1339 } 1340 } 1341 1342 return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file ); 1133 1343 } 1134 1344 }
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)