hasSvgExtension( $filename ) ) { return $data; } // Respect it if something else removed svg from the allowed list. $allowed = is_array( $mimes ) ? $mimes : get_allowed_mime_types(); if ( ! isset( $allowed['svg'] ) ) { return $data; } return array( 'ext' => 'svg', 'type' => 'image/svg+xml', 'proper_filename' => $data['proper_filename'] ?? false, ); } /** * Sanitize an uploaded SVG in place, before WordPress moves it. * * We look at the filename and not $file['type']. That value comes from * the browser's Content-Type header, so anyone can fake it. */ public function validateSvgUpload( array $file ): array { if ( ! $this->hasSvgExtension( $file['name'] ?? '' ) ) { return $file; } $tmp_name = isset( $file['tmp_name'] ) && is_string( $file['tmp_name'] ) ? $file['tmp_name'] : ''; $file_size = '' !== $tmp_name ? wp_filesize( $tmp_name ) : false; if ( false === $file_size ) { $file['error'] = __( 'Unable to read SVG file.', 'flexify-blocks' ); return $file; } if ( $file_size > self::MAX_BYTES ) { $file['error'] = __( 'SVG file is too large.', 'flexify-blocks' ); return $file; } $source = file_get_contents( $tmp_name ); if ( false === $source ) { $file['error'] = __( 'Unable to read SVG file.', 'flexify-blocks' ); return $file; } $clean = $this->sanitize( $source ); if ( is_wp_error( $clean ) ) { $file['error'] = $clean->get_error_message(); return $file; } if ( false === file_put_contents( $tmp_name, $clean ) ) { $file['error'] = __( 'Unable to save sanitized SVG file.', 'flexify-blocks' ); } return $file; } /* --------------------------------------------------------------------- * Sanitizing * ------------------------------------------------------------------ */ /** * Validate an SVG string and return a cleaned copy, or a WP_Error if the * file should be rejected. * * @return string|\WP_Error */ private function sanitize( string $svg ) { if ( '' === trim( $svg ) ) { return new \WP_Error( 'svg_empty', __( 'Unable to read SVG file.', 'flexify-blocks' ) ); } if ( strlen( $svg ) > self::MAX_BYTES ) { return new \WP_Error( 'svg_too_large', __( 'SVG file is too large.', 'flexify-blocks' ) ); } // Cheap early exit. External DTDs and entities are how XXE attacks // work, so any DOCTYPE is refused rather than parsed "safely". if ( preg_match( '/doctypeError(); } $doc = $this->parse( $svg ); if ( ! $doc || ! $doc->documentElement || 'svg' !== strtolower( $doc->documentElement->localName ) || ( $doc->documentElement->namespaceURI && self::SVG_NAMESPACE !== $doc->documentElement->namespaceURI ) ) { return new \WP_Error( 'svg_invalid', __( 'Invalid SVG markup.', 'flexify-blocks' ) ); } // The string check above can't see through other encodings (UTF-16, // for one), so check the parsed document as well. if ( null !== $doc->doctype ) { return $this->doctypeError(); } // A script means the file is hostile or badly made. Reject it // instead of quietly stripping it. The wildcard namespace catches // prefixed variants too. if ( $doc->getElementsByTagNameNS( '*', 'script' )->length > 0 ) { return new \WP_Error( 'svg_script', __( 'SVG contains a