From 4f4de924f71dcab351eccd3fae1f255651c7cc68 Mon Sep 17 00:00:00 2001 From: erseco Date: Sat, 26 Sep 2026 08:05:14 +0100 Subject: [PATCH 1/4] Apply the style package allowlist to nested entries When config.xml sat at the archive root, verify_entries() skipped every entry containing a slash before checking its extension, so a style ZIP could write x/shell.php or x/.htaccess into uploads/exelearning-styles/{slug}/. Only directory entries are skipped now; every file, at any depth, must have an allowed extension. Directory entries in single-root-folder packages, which were rejected as extensionless, are accepted too. Also escape the entry name quoted in rejection messages: core prints settings errors unescaped, so a crafted entry name ran as markup in wp-admin. --- includes/class-style-package.php | 15 +++--- tests/unit/StylePackageTest.php | 85 ++++++++++++++++++++++++++++++++ tests/unit/StylesServiceTest.php | 1 - 3 files changed, 92 insertions(+), 9 deletions(-) diff --git a/includes/class-style-package.php b/includes/class-style-package.php index 0464b20..136ae07 100644 --- a/includes/class-style-package.php +++ b/includes/class-style-package.php @@ -151,7 +151,7 @@ private static function scan_entries( $zip ) { sprintf( /* translators: %s: offending entry name. */ __( 'Rejected unsafe archive entry: %s', 'exelearning' ), - $name + esc_html( $name ) ) ); } @@ -192,12 +192,11 @@ private static function scan_entries( $zip ) { */ private static function verify_entries( array $entries, $prefix ) { foreach ( $entries as $entry ) { - if ( '' === $prefix ) { - // Subdirectories at the root are allowed when config.xml is at the root. - if ( false !== strpos( $entry['name'], '/' ) ) { - continue; - } - } elseif ( 0 !== strpos( $entry['name'], $prefix ) ) { + // Directory entries carry no file type; every file below is still checked. + if ( '/' === substr( $entry['name'], -1 ) ) { + continue; + } + if ( '' !== $prefix && 0 !== strpos( $entry['name'], $prefix ) ) { return new WP_Error( 'zip_mixed_roots', __( 'The archive must contain a single root folder or place all files at the root.', 'exelearning' ) @@ -209,7 +208,7 @@ private static function verify_entries( array $entries, $prefix ) { sprintf( /* translators: %s: offending filename. */ __( 'File type not allowed in style package: %s', 'exelearning' ), - $entry['name'] + esc_html( $entry['name'] ) ) ); } diff --git a/tests/unit/StylePackageTest.php b/tests/unit/StylePackageTest.php index 34d39f9..58293f1 100644 --- a/tests/unit/StylePackageTest.php +++ b/tests/unit/StylePackageTest.php @@ -139,6 +139,85 @@ public function test_validate_bad_extension() { wp_delete_file( $zip_path ); } + /** + * A root-level config.xml must not exempt nested files from the extension + * allowlist: server-executable or server-config files are refused at any depth. + * + * @dataProvider nested_forbidden_entry_provider + * + * @param string $entry Nested entry name. + */ + public function test_validate_rejects_nested_forbidden_file_when_config_sits_at_the_root( $entry ) { + $zip_path = $this->make_zip( + array( + 'config.xml' => $this->sample_config_xml( 'acme' ), + 'style.css' => 'body{}', + $entry => 'payload', + ) + ); + $result = ExeLearning_Style_Package::validate( $zip_path, self::MAX ); + wp_delete_file( $zip_path ); + $this->assertInstanceOf( 'WP_Error', $result ); + $this->assertSame( 'zip_bad_extension', $result->get_error_code() ); + } + + public function nested_forbidden_entry_provider() { + return array( + 'php' => array( 'x/shell.php' ), + 'htaccess' => array( 'x/.htaccess' ), + 'user.ini' => array( 'x/.user.ini' ), + 'phtml' => array( 'deep/er/a.phtml' ), + 'no suffix' => array( 'fonts/LICENSE' ), + ); + } + + /** + * Directory entries carry no file type: archives made by tools that store + * them (most do) validate, with or without a root folder. + */ + public function test_validate_accepts_directory_entries() { + $rooted = $this->make_zip( + array( + 'config.xml' => $this->sample_config_xml( 'acme' ), + 'img/' => null, + 'img/logo.png' => 'binary', + ) + ); + $folder = $this->make_zip( + array( + 'acme/' => null, + 'acme/config.xml' => $this->sample_config_xml( 'acme' ), + 'acme/img/' => null, + 'acme/img/logo.png' => 'binary', + ) + ); + $rooted_result = ExeLearning_Style_Package::validate( $rooted, self::MAX ); + $folder_result = ExeLearning_Style_Package::validate( $folder, self::MAX ); + wp_delete_file( $rooted ); + wp_delete_file( $folder ); + $this->assertIsArray( $rooted_result ); + $this->assertIsArray( $folder_result ); + $this->assertSame( 'acme/', $folder_result['prefix'] ); + } + + /** + * Rejection messages quote the entry name escaped, since core prints + * settings errors without escaping. + */ + public function test_validate_escapes_entry_name_in_error_message() { + $zip_path = $this->make_zip( + array( + 'config.xml' => $this->sample_config_xml( 'acme' ), + '.x' => 'x', + ) + ); + $result = ExeLearning_Style_Package::validate( $zip_path, self::MAX ); + wp_delete_file( $zip_path ); + $this->assertInstanceOf( 'WP_Error', $result ); + $this->assertStringNotContainsString( 'get_error_message() ); + $this->assertStringContainsString( '<img', $result->get_error_message() ); + } + public function test_validate_accepts_root_package() { $zip_path = $this->make_zip( array( @@ -294,6 +373,8 @@ public function allowed_filename_provider() { 'css' => array( 'style.css', true ), 'svg nested' => array( 'icons/a.svg', true ), 'php' => array( 'evil.php', false ), + 'php upper' => array( 'x/SHELL.PHP', false ), + 'htaccess' => array( 'x/.htaccess', false ), 'no extension' => array( 'Makefile', false ), 'directory' => array( 'assets/', false ), 'empty' => array( '', false ), @@ -423,6 +504,10 @@ private function make_zip( array $entries ) { $zip = new ZipArchive(); $this->assertTrue( true === $zip->open( $path, ZipArchive::CREATE ) ); foreach ( $entries as $name => $contents ) { + if ( '/' === substr( $name, -1 ) ) { + $zip->addEmptyDir( rtrim( $name, '/' ) ); + continue; + } $zip->addFromString( $name, $contents ); } $zip->close(); diff --git a/tests/unit/StylesServiceTest.php b/tests/unit/StylesServiceTest.php index e34ebd8..5bd1185 100644 --- a/tests/unit/StylesServiceTest.php +++ b/tests/unit/StylesServiceTest.php @@ -509,7 +509,6 @@ public function test_build_theme_registry_override_publishes_icons_from_icons_fo 'icons/alert.svg' => '', 'icons/photo.JPG' => 'JPEG', 'icons/readme.txt' => 'ignore', - 'icons/no-extension' => 'ignore', ) ); $this->assertIsArray( ExeLearning_Styles_Service::install_from_zip( $zip_path ) ); From 76e07dc845aa4e42dfb416786cd52b2f17313372 Mon Sep 17 00:00:00 2001 From: erseco Date: Sat, 26 Sep 2026 08:20:17 +0100 Subject: [PATCH 2/4] Regenerate translation catalogs --- languages/exelearning-ca.po | 22 +++++++++++----------- languages/exelearning-ca_valencia.po | 22 +++++++++++----------- languages/exelearning-de_DE.po | 22 +++++++++++----------- languages/exelearning-eo.po | 22 +++++++++++----------- languages/exelearning-es_ES.po | 22 +++++++++++----------- languages/exelearning-eu.po | 22 +++++++++++----------- languages/exelearning-gl_ES.po | 22 +++++++++++----------- languages/exelearning-it_IT.po | 22 +++++++++++----------- languages/exelearning-pt_PT.po | 22 +++++++++++----------- languages/exelearning-ro_RO.po | 22 +++++++++++----------- languages/exelearning.pot | 22 +++++++++++----------- 11 files changed, 121 insertions(+), 121 deletions(-) diff --git a/languages/exelearning-ca.po b/languages/exelearning-ca.po index 2e8e389..67ce48e 100644 --- a/languages/exelearning-ca.po +++ b/languages/exelearning-ca.po @@ -635,46 +635,46 @@ msgstr "L'arxiu conté més d'un config.xml." msgid "The style package is missing config.xml." msgstr "Al paquet d'estil li falta config.xml." -#: includes/class-style-package.php:203 +#: includes/class-style-package.php:202 msgid "The archive must contain a single root folder or place all files at the root." msgstr "L'arxiu ha de contenir una única carpeta arrel o situar tots els fitxers a l'arrel." #. translators: %s: offending filename. -#: includes/class-style-package.php:211 +#: includes/class-style-package.php:210 #, php-format msgid "File type not allowed in style package: %s" msgstr "Tipus de fitxer no permès al paquet d'estil: %s" -#: includes/class-style-package.php:232 +#: includes/class-style-package.php:231 msgid "Failed to reopen ZIP archive." msgstr "No s'ha pogut tornar a obrir l'arxiu ZIP." -#: includes/class-style-package.php:243 +#: includes/class-style-package.php:242 msgid "Refused unsafe archive entry during extraction." msgstr "S'ha rebutjat una entrada d'arxiu insegura durant l'extracció." -#: includes/class-style-package.php:290 +#: includes/class-style-package.php:289 msgid "Refused path traversal during extraction." msgstr "S'ha rebutjat un salt de ruta durant l'extracció." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:296 +#: includes/class-style-package.php:300 msgid "Failed to create a directory from the archive." msgstr "No s'ha pogut crear un directori de l'arxiu." -#: includes/class-style-package.php:306 +#: includes/class-style-package.php:305 msgid "Failed to read a file from the archive." msgstr "No s'ha pogut llegir un fitxer de l'arxiu." -#: includes/class-style-package.php:310 +#: includes/class-style-package.php:309 msgid "Failed to write an extracted file." msgstr "No s'ha pogut escriure un fitxer extret." -#: includes/class-style-package.php:396 +#: includes/class-style-package.php:395 msgid "config.xml is not valid XML." msgstr "config.xml no és XML vàlid." -#: includes/class-style-package.php:401 +#: includes/class-style-package.php:400 msgid "config.xml must declare a element." msgstr "config.xml ha de declarar un element ." diff --git a/languages/exelearning-ca_valencia.po b/languages/exelearning-ca_valencia.po index 66eb4fd..ce4d21d 100644 --- a/languages/exelearning-ca_valencia.po +++ b/languages/exelearning-ca_valencia.po @@ -635,46 +635,46 @@ msgstr "L'arxiu conté més d'un config.xml." msgid "The style package is missing config.xml." msgstr "Al paquet d'estil li falta config.xml." -#: includes/class-style-package.php:203 +#: includes/class-style-package.php:202 msgid "The archive must contain a single root folder or place all files at the root." msgstr "L'arxiu ha de contenir una única carpeta arrel o situar tots els fitxers a l'arrel." #. translators: %s: offending filename. -#: includes/class-style-package.php:211 +#: includes/class-style-package.php:210 #, php-format msgid "File type not allowed in style package: %s" msgstr "Tipus de fitxer no permès al paquet d'estil: %s" -#: includes/class-style-package.php:232 +#: includes/class-style-package.php:231 msgid "Failed to reopen ZIP archive." msgstr "No s'ha pogut tornar a obrir l'arxiu ZIP." -#: includes/class-style-package.php:243 +#: includes/class-style-package.php:242 msgid "Refused unsafe archive entry during extraction." msgstr "S'ha rebutjat una entrada d'arxiu insegura durant l'extracció." -#: includes/class-style-package.php:290 +#: includes/class-style-package.php:289 msgid "Refused path traversal during extraction." msgstr "S'ha rebutjat un salt de ruta durant l'extracció." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:296 +#: includes/class-style-package.php:300 msgid "Failed to create a directory from the archive." msgstr "No s'ha pogut crear un directori de l'arxiu." -#: includes/class-style-package.php:306 +#: includes/class-style-package.php:305 msgid "Failed to read a file from the archive." msgstr "No s'ha pogut llegir un fitxer de l'arxiu." -#: includes/class-style-package.php:310 +#: includes/class-style-package.php:309 msgid "Failed to write an extracted file." msgstr "No s'ha pogut escriure un fitxer extret." -#: includes/class-style-package.php:396 +#: includes/class-style-package.php:395 msgid "config.xml is not valid XML." msgstr "config.xml no és XML vàlid." -#: includes/class-style-package.php:401 +#: includes/class-style-package.php:400 msgid "config.xml must declare a element." msgstr "config.xml ha de declarar un element ." diff --git a/languages/exelearning-de_DE.po b/languages/exelearning-de_DE.po index fe59264..474e49c 100644 --- a/languages/exelearning-de_DE.po +++ b/languages/exelearning-de_DE.po @@ -635,46 +635,46 @@ msgstr "Das Archiv enthält mehr als eine config.xml." msgid "The style package is missing config.xml." msgstr "Im Stilpaket fehlt die config.xml." -#: includes/class-style-package.php:203 +#: includes/class-style-package.php:202 msgid "The archive must contain a single root folder or place all files at the root." msgstr "Das Archiv muss einen einzigen Stammordner enthalten oder alle Dateien im Stammverzeichnis ablegen." #. translators: %s: offending filename. -#: includes/class-style-package.php:211 +#: includes/class-style-package.php:210 #, php-format msgid "File type not allowed in style package: %s" msgstr "Dateityp im Stilpaket nicht zulässig: %s" -#: includes/class-style-package.php:232 +#: includes/class-style-package.php:231 msgid "Failed to reopen ZIP archive." msgstr "ZIP-Archiv konnte nicht erneut geöffnet werden." -#: includes/class-style-package.php:243 +#: includes/class-style-package.php:242 msgid "Refused unsafe archive entry during extraction." msgstr "Unsicherer Archiveintrag bei der Extraktion abgelehnt." -#: includes/class-style-package.php:290 +#: includes/class-style-package.php:289 msgid "Refused path traversal during extraction." msgstr "Pfad-Traversal bei der Extraktion abgelehnt." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:296 +#: includes/class-style-package.php:300 msgid "Failed to create a directory from the archive." msgstr "Verzeichnis konnte nicht aus dem Archiv erstellt werden." -#: includes/class-style-package.php:306 +#: includes/class-style-package.php:305 msgid "Failed to read a file from the archive." msgstr "Datei konnte nicht aus dem Archiv gelesen werden." -#: includes/class-style-package.php:310 +#: includes/class-style-package.php:309 msgid "Failed to write an extracted file." msgstr "Eine extrahierte Datei konnte nicht geschrieben werden." -#: includes/class-style-package.php:396 +#: includes/class-style-package.php:395 msgid "config.xml is not valid XML." msgstr "config.xml ist kein gültiges XML." -#: includes/class-style-package.php:401 +#: includes/class-style-package.php:400 msgid "config.xml must declare a element." msgstr "config.xml muss ein -Element deklarieren." diff --git a/languages/exelearning-eo.po b/languages/exelearning-eo.po index c22e3ff..d45641d 100644 --- a/languages/exelearning-eo.po +++ b/languages/exelearning-eo.po @@ -635,46 +635,46 @@ msgstr "La arkivo enhavas pli ol unu config.xml." msgid "The style package is missing config.xml." msgstr "En la stilpakaĵo mankas config.xml." -#: includes/class-style-package.php:203 +#: includes/class-style-package.php:202 msgid "The archive must contain a single root folder or place all files at the root." msgstr "La arkivo devas enhavi ununuran radikan dosierujon aŭ meti ĉiujn dosierojn en la radikon." #. translators: %s: offending filename. -#: includes/class-style-package.php:211 +#: includes/class-style-package.php:210 #, php-format msgid "File type not allowed in style package: %s" msgstr "Dosiertipo ne permesata en la stilpakaĵo: %s" -#: includes/class-style-package.php:232 +#: includes/class-style-package.php:231 msgid "Failed to reopen ZIP archive." msgstr "Ne eblis remalfermi la ZIP-arkivon." -#: includes/class-style-package.php:243 +#: includes/class-style-package.php:242 msgid "Refused unsafe archive entry during extraction." msgstr "Malsekura arkiverero malakceptita dum la malpakado." -#: includes/class-style-package.php:290 +#: includes/class-style-package.php:289 msgid "Refused path traversal during extraction." msgstr "Vojtravado malakceptita dum la malpakado." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:296 +#: includes/class-style-package.php:300 msgid "Failed to create a directory from the archive." msgstr "Ne eblis krei dosierujon el la arkivo." -#: includes/class-style-package.php:306 +#: includes/class-style-package.php:305 msgid "Failed to read a file from the archive." msgstr "Ne eblis legi dosieron el la arkivo." -#: includes/class-style-package.php:310 +#: includes/class-style-package.php:309 msgid "Failed to write an extracted file." msgstr "Ne eblis skribi malpakitan dosieron." -#: includes/class-style-package.php:396 +#: includes/class-style-package.php:395 msgid "config.xml is not valid XML." msgstr "config.xml ne estas valida XML." -#: includes/class-style-package.php:401 +#: includes/class-style-package.php:400 msgid "config.xml must declare a element." msgstr "config.xml devas deklari elementon ." diff --git a/languages/exelearning-es_ES.po b/languages/exelearning-es_ES.po index 1751dbe..c7b6244 100644 --- a/languages/exelearning-es_ES.po +++ b/languages/exelearning-es_ES.po @@ -635,46 +635,46 @@ msgstr "El archivo contiene más de un config.xml." msgid "The style package is missing config.xml." msgstr "Al paquete de estilo le falta config.xml." -#: includes/class-style-package.php:203 +#: includes/class-style-package.php:202 msgid "The archive must contain a single root folder or place all files at the root." msgstr "El archivo debe contener una única carpeta raíz o tener todos los ficheros en la raíz." #. translators: %s: offending filename. -#: includes/class-style-package.php:211 +#: includes/class-style-package.php:210 #, php-format msgid "File type not allowed in style package: %s" msgstr "Tipo de archivo no permitido en el paquete de estilo: %s" -#: includes/class-style-package.php:232 +#: includes/class-style-package.php:231 msgid "Failed to reopen ZIP archive." msgstr "No se pudo reabrir el archivo ZIP." -#: includes/class-style-package.php:243 +#: includes/class-style-package.php:242 msgid "Refused unsafe archive entry during extraction." msgstr "Se rechazó una entrada no segura del archivo durante la extracción." -#: includes/class-style-package.php:290 +#: includes/class-style-package.php:289 msgid "Refused path traversal during extraction." msgstr "Se rechazó un intento de escape de ruta durante la extracción." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:296 +#: includes/class-style-package.php:300 msgid "Failed to create a directory from the archive." msgstr "No se pudo crear un directorio del archivo." -#: includes/class-style-package.php:306 +#: includes/class-style-package.php:305 msgid "Failed to read a file from the archive." msgstr "No se pudo leer un archivo del archivo comprimido." -#: includes/class-style-package.php:310 +#: includes/class-style-package.php:309 msgid "Failed to write an extracted file." msgstr "No se pudo escribir un archivo extraído." -#: includes/class-style-package.php:396 +#: includes/class-style-package.php:395 msgid "config.xml is not valid XML." msgstr "config.xml no es XML válido." -#: includes/class-style-package.php:401 +#: includes/class-style-package.php:400 msgid "config.xml must declare a element." msgstr "config.xml debe declarar un elemento ." diff --git a/languages/exelearning-eu.po b/languages/exelearning-eu.po index 53b808d..72f30f9 100644 --- a/languages/exelearning-eu.po +++ b/languages/exelearning-eu.po @@ -635,46 +635,46 @@ msgstr "Artxiboak config.xml bat baino gehiago ditu." msgid "The style package is missing config.xml." msgstr "Estilo-paketeari config.xml falta zaio." -#: includes/class-style-package.php:203 +#: includes/class-style-package.php:202 msgid "The archive must contain a single root folder or place all files at the root." msgstr "Artxiboak erro-karpeta bakar bat izan behar du edo fitxategi guztiak erroan jarri." #. translators: %s: offending filename. -#: includes/class-style-package.php:211 +#: includes/class-style-package.php:210 #, php-format msgid "File type not allowed in style package: %s" msgstr "Estilo-paketean onartzen ez den fitxategi mota: %s" -#: includes/class-style-package.php:232 +#: includes/class-style-package.php:231 msgid "Failed to reopen ZIP archive." msgstr "Ezin izan da ZIP artxiboa berriro ireki." -#: includes/class-style-package.php:243 +#: includes/class-style-package.php:242 msgid "Refused unsafe archive entry during extraction." msgstr "Erauzketa garaian artxibo-sarrera ez segurua baztertu da." -#: includes/class-style-package.php:290 +#: includes/class-style-package.php:289 msgid "Refused path traversal during extraction." msgstr "Erauzketa garaian bide-zeharkatzea baztertu da." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:296 +#: includes/class-style-package.php:300 msgid "Failed to create a directory from the archive." msgstr "Ezin izan da artxibotik direktorio bat sortu." -#: includes/class-style-package.php:306 +#: includes/class-style-package.php:305 msgid "Failed to read a file from the archive." msgstr "Ezin izan da artxibotik fitxategi bat irakurri." -#: includes/class-style-package.php:310 +#: includes/class-style-package.php:309 msgid "Failed to write an extracted file." msgstr "Ezin izan da erauzitako fitxategi bat idatzi." -#: includes/class-style-package.php:396 +#: includes/class-style-package.php:395 msgid "config.xml is not valid XML." msgstr "config.xml ez da baliozko XML." -#: includes/class-style-package.php:401 +#: includes/class-style-package.php:400 msgid "config.xml must declare a element." msgstr "config.xml-ek elementu bat deklaratu behar du." diff --git a/languages/exelearning-gl_ES.po b/languages/exelearning-gl_ES.po index b388104..ffcc0f4 100644 --- a/languages/exelearning-gl_ES.po +++ b/languages/exelearning-gl_ES.po @@ -635,46 +635,46 @@ msgstr "O arquivo contén máis dun config.xml." msgid "The style package is missing config.xml." msgstr "Ao paquete de estilo fáltalle config.xml." -#: includes/class-style-package.php:203 +#: includes/class-style-package.php:202 msgid "The archive must contain a single root folder or place all files at the root." msgstr "O arquivo debe conter unha única carpeta raíz ou situar todos os ficheiros na raíz." #. translators: %s: offending filename. -#: includes/class-style-package.php:211 +#: includes/class-style-package.php:210 #, php-format msgid "File type not allowed in style package: %s" msgstr "Tipo de ficheiro non permitido no paquete de estilo: %s" -#: includes/class-style-package.php:232 +#: includes/class-style-package.php:231 msgid "Failed to reopen ZIP archive." msgstr "Non se puido reabrir o arquivo ZIP." -#: includes/class-style-package.php:243 +#: includes/class-style-package.php:242 msgid "Refused unsafe archive entry during extraction." msgstr "Rexeitouse unha entrada de arquivo insegura durante a extracción." -#: includes/class-style-package.php:290 +#: includes/class-style-package.php:289 msgid "Refused path traversal during extraction." msgstr "Rexeitouse un salto de ruta durante a extracción." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:296 +#: includes/class-style-package.php:300 msgid "Failed to create a directory from the archive." msgstr "Non se puido crear un directorio do arquivo." -#: includes/class-style-package.php:306 +#: includes/class-style-package.php:305 msgid "Failed to read a file from the archive." msgstr "Non se puido ler un ficheiro do arquivo." -#: includes/class-style-package.php:310 +#: includes/class-style-package.php:309 msgid "Failed to write an extracted file." msgstr "Non se puido escribir un ficheiro extraído." -#: includes/class-style-package.php:396 +#: includes/class-style-package.php:395 msgid "config.xml is not valid XML." msgstr "config.xml non é XML válido." -#: includes/class-style-package.php:401 +#: includes/class-style-package.php:400 msgid "config.xml must declare a element." msgstr "config.xml debe declarar un elemento ." diff --git a/languages/exelearning-it_IT.po b/languages/exelearning-it_IT.po index 9650c3f..9a72471 100644 --- a/languages/exelearning-it_IT.po +++ b/languages/exelearning-it_IT.po @@ -635,46 +635,46 @@ msgstr "L'archivio contiene più di un config.xml." msgid "The style package is missing config.xml." msgstr "Nel pacchetto di stile manca config.xml." -#: includes/class-style-package.php:203 +#: includes/class-style-package.php:202 msgid "The archive must contain a single root folder or place all files at the root." msgstr "L'archivio deve contenere un'unica cartella radice o collocare tutti i file nella radice." #. translators: %s: offending filename. -#: includes/class-style-package.php:211 +#: includes/class-style-package.php:210 #, php-format msgid "File type not allowed in style package: %s" msgstr "Tipo di file non consentito nel pacchetto di stile: %s" -#: includes/class-style-package.php:232 +#: includes/class-style-package.php:231 msgid "Failed to reopen ZIP archive." msgstr "Impossibile riaprire l'archivio ZIP." -#: includes/class-style-package.php:243 +#: includes/class-style-package.php:242 msgid "Refused unsafe archive entry during extraction." msgstr "Voce di archivio non sicura rifiutata durante l'estrazione." -#: includes/class-style-package.php:290 +#: includes/class-style-package.php:289 msgid "Refused path traversal during extraction." msgstr "Attraversamento di percorso rifiutato durante l'estrazione." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:296 +#: includes/class-style-package.php:300 msgid "Failed to create a directory from the archive." msgstr "Impossibile creare una directory dall'archivio." -#: includes/class-style-package.php:306 +#: includes/class-style-package.php:305 msgid "Failed to read a file from the archive." msgstr "Impossibile leggere un file dall'archivio." -#: includes/class-style-package.php:310 +#: includes/class-style-package.php:309 msgid "Failed to write an extracted file." msgstr "Impossibile scrivere un file estratto." -#: includes/class-style-package.php:396 +#: includes/class-style-package.php:395 msgid "config.xml is not valid XML." msgstr "config.xml non è XML valido." -#: includes/class-style-package.php:401 +#: includes/class-style-package.php:400 msgid "config.xml must declare a element." msgstr "config.xml deve dichiarare un elemento ." diff --git a/languages/exelearning-pt_PT.po b/languages/exelearning-pt_PT.po index 7ad27ab..b722d4f 100644 --- a/languages/exelearning-pt_PT.po +++ b/languages/exelearning-pt_PT.po @@ -635,46 +635,46 @@ msgstr "O arquivo contém mais do que um config.xml." msgid "The style package is missing config.xml." msgstr "Falta config.xml no pacote de estilo." -#: includes/class-style-package.php:203 +#: includes/class-style-package.php:202 msgid "The archive must contain a single root folder or place all files at the root." msgstr "O arquivo deve conter uma única pasta raiz ou colocar todos os ficheiros na raiz." #. translators: %s: offending filename. -#: includes/class-style-package.php:211 +#: includes/class-style-package.php:210 #, php-format msgid "File type not allowed in style package: %s" msgstr "Tipo de ficheiro não permitido no pacote de estilo: %s" -#: includes/class-style-package.php:232 +#: includes/class-style-package.php:231 msgid "Failed to reopen ZIP archive." msgstr "Falha ao reabrir o arquivo ZIP." -#: includes/class-style-package.php:243 +#: includes/class-style-package.php:242 msgid "Refused unsafe archive entry during extraction." msgstr "Entrada de arquivo insegura recusada durante a extração." -#: includes/class-style-package.php:290 +#: includes/class-style-package.php:289 msgid "Refused path traversal during extraction." msgstr "Travessia de caminho recusada durante a extração." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:296 +#: includes/class-style-package.php:300 msgid "Failed to create a directory from the archive." msgstr "Falha ao criar um diretório a partir do arquivo." -#: includes/class-style-package.php:306 +#: includes/class-style-package.php:305 msgid "Failed to read a file from the archive." msgstr "Falha ao ler um ficheiro do arquivo." -#: includes/class-style-package.php:310 +#: includes/class-style-package.php:309 msgid "Failed to write an extracted file." msgstr "Falha ao escrever um ficheiro extraído." -#: includes/class-style-package.php:396 +#: includes/class-style-package.php:395 msgid "config.xml is not valid XML." msgstr "config.xml não é XML válido." -#: includes/class-style-package.php:401 +#: includes/class-style-package.php:400 msgid "config.xml must declare a element." msgstr "config.xml deve declarar um elemento ." diff --git a/languages/exelearning-ro_RO.po b/languages/exelearning-ro_RO.po index 5f43771..586c4da 100644 --- a/languages/exelearning-ro_RO.po +++ b/languages/exelearning-ro_RO.po @@ -636,46 +636,46 @@ msgstr "Arhiva conține mai mult de un config.xml." msgid "The style package is missing config.xml." msgstr "Pachetului de stil îi lipsește config.xml." -#: includes/class-style-package.php:203 +#: includes/class-style-package.php:202 msgid "The archive must contain a single root folder or place all files at the root." msgstr "Arhiva trebuie să conțină un singur folder rădăcină sau să plaseze toate fișierele în rădăcină." #. translators: %s: offending filename. -#: includes/class-style-package.php:211 +#: includes/class-style-package.php:210 #, php-format msgid "File type not allowed in style package: %s" msgstr "Tip de fișier nepermis în pachetul de stil: %s" -#: includes/class-style-package.php:232 +#: includes/class-style-package.php:231 msgid "Failed to reopen ZIP archive." msgstr "Redeschiderea arhivei ZIP a eșuat." -#: includes/class-style-package.php:243 +#: includes/class-style-package.php:242 msgid "Refused unsafe archive entry during extraction." msgstr "Intrare de arhivă nesigură refuzată în timpul extragerii." -#: includes/class-style-package.php:290 +#: includes/class-style-package.php:289 msgid "Refused path traversal during extraction." msgstr "Traversare de cale refuzată în timpul extragerii." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:296 +#: includes/class-style-package.php:300 msgid "Failed to create a directory from the archive." msgstr "Crearea unui director din arhivă a eșuat." -#: includes/class-style-package.php:306 +#: includes/class-style-package.php:305 msgid "Failed to read a file from the archive." msgstr "Citirea unui fișier din arhivă a eșuat." -#: includes/class-style-package.php:310 +#: includes/class-style-package.php:309 msgid "Failed to write an extracted file." msgstr "Scrierea unui fișier extras a eșuat." -#: includes/class-style-package.php:396 +#: includes/class-style-package.php:395 msgid "config.xml is not valid XML." msgstr "config.xml nu este XML valid." -#: includes/class-style-package.php:401 +#: includes/class-style-package.php:400 msgid "config.xml must declare a element." msgstr "config.xml trebuie să declare un element ." diff --git a/languages/exelearning.pot b/languages/exelearning.pot index 1a07742..07ad95a 100644 --- a/languages/exelearning.pot +++ b/languages/exelearning.pot @@ -634,46 +634,46 @@ msgstr "" msgid "The style package is missing config.xml." msgstr "" -#: includes/class-style-package.php:203 +#: includes/class-style-package.php:202 msgid "The archive must contain a single root folder or place all files at the root." msgstr "" #. translators: %s: offending filename. -#: includes/class-style-package.php:211 +#: includes/class-style-package.php:210 #, php-format msgid "File type not allowed in style package: %s" msgstr "" -#: includes/class-style-package.php:232 +#: includes/class-style-package.php:231 msgid "Failed to reopen ZIP archive." msgstr "" -#: includes/class-style-package.php:243 +#: includes/class-style-package.php:242 msgid "Refused unsafe archive entry during extraction." msgstr "" -#: includes/class-style-package.php:290 +#: includes/class-style-package.php:289 msgid "Refused path traversal during extraction." msgstr "" -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:296 +#: includes/class-style-package.php:300 msgid "Failed to create a directory from the archive." msgstr "" -#: includes/class-style-package.php:306 +#: includes/class-style-package.php:305 msgid "Failed to read a file from the archive." msgstr "" -#: includes/class-style-package.php:310 +#: includes/class-style-package.php:309 msgid "Failed to write an extracted file." msgstr "" -#: includes/class-style-package.php:396 +#: includes/class-style-package.php:395 msgid "config.xml is not valid XML." msgstr "" -#: includes/class-style-package.php:401 +#: includes/class-style-package.php:400 msgid "config.xml must declare a element." msgstr "" From 6ee495b02d6944417032b94da3e850cec058b0eb Mon Sep 17 00:00:00 2001 From: erseco Date: Sat, 26 Sep 2026 09:27:48 +0100 Subject: [PATCH 3/4] Check the root folder before skipping directory entries Skipping directory entries first let a directory outside the package's root folder (evil/ next to acme/) pass verify_entries(), although it breaks the single-root rule that zip_mixed_roots enforces. extract_safely() dropped it anyway, but the check now runs in the right order. --- includes/class-style-package.php | 8 ++++---- tests/unit/StylePackageTest.php | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/includes/class-style-package.php b/includes/class-style-package.php index 136ae07..8d829dc 100644 --- a/includes/class-style-package.php +++ b/includes/class-style-package.php @@ -192,16 +192,16 @@ private static function scan_entries( $zip ) { */ private static function verify_entries( array $entries, $prefix ) { foreach ( $entries as $entry ) { - // Directory entries carry no file type; every file below is still checked. - if ( '/' === substr( $entry['name'], -1 ) ) { - continue; - } if ( '' !== $prefix && 0 !== strpos( $entry['name'], $prefix ) ) { return new WP_Error( 'zip_mixed_roots', __( 'The archive must contain a single root folder or place all files at the root.', 'exelearning' ) ); } + // Directory entries carry no file type; every file below is still checked. + if ( '/' === substr( $entry['name'], -1 ) ) { + continue; + } if ( ! self::is_allowed_filename( $entry['name'] ) ) { return new WP_Error( 'zip_bad_extension', diff --git a/tests/unit/StylePackageTest.php b/tests/unit/StylePackageTest.php index 58293f1..b8b3e3f 100644 --- a/tests/unit/StylePackageTest.php +++ b/tests/unit/StylePackageTest.php @@ -200,6 +200,25 @@ public function test_validate_accepts_directory_entries() { $this->assertSame( 'acme/', $folder_result['prefix'] ); } + /** + * A directory outside the package's root folder still breaks the single-root + * rule, even though directories carry no file type. + */ + public function test_validate_rejects_a_directory_outside_the_root_folder() { + $zip_path = $this->make_zip( + array( + 'acme/' => null, + 'acme/config.xml' => $this->sample_config_xml( 'acme' ), + 'acme/style.css' => 'body{}', + 'evil/' => null, + ) + ); + $result = ExeLearning_Style_Package::validate( $zip_path, self::MAX ); + wp_delete_file( $zip_path ); + $this->assertInstanceOf( 'WP_Error', $result ); + $this->assertSame( 'zip_mixed_roots', $result->get_error_code() ); + } + /** * Rejection messages quote the entry name escaped, since core prints * settings errors without escaping. From f44aa8078e9b9978ede713bbbfc4dd7c4f173921 Mon Sep 17 00:00:00 2001 From: erseco Date: Sat, 26 Sep 2026 09:27:50 +0100 Subject: [PATCH 4/4] Regenerate translation catalogs --- languages/exelearning-ca.po | 2 +- languages/exelearning-ca_valencia.po | 2 +- languages/exelearning-de_DE.po | 2 +- languages/exelearning-eo.po | 2 +- languages/exelearning-es_ES.po | 2 +- languages/exelearning-eu.po | 2 +- languages/exelearning-gl_ES.po | 2 +- languages/exelearning-it_IT.po | 2 +- languages/exelearning-pt_PT.po | 2 +- languages/exelearning-ro_RO.po | 2 +- languages/exelearning.pot | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/languages/exelearning-ca.po b/languages/exelearning-ca.po index 67ce48e..10b1c7b 100644 --- a/languages/exelearning-ca.po +++ b/languages/exelearning-ca.po @@ -635,7 +635,7 @@ msgstr "L'arxiu conté més d'un config.xml." msgid "The style package is missing config.xml." msgstr "Al paquet d'estil li falta config.xml." -#: includes/class-style-package.php:202 +#: includes/class-style-package.php:198 msgid "The archive must contain a single root folder or place all files at the root." msgstr "L'arxiu ha de contenir una única carpeta arrel o situar tots els fitxers a l'arrel." diff --git a/languages/exelearning-ca_valencia.po b/languages/exelearning-ca_valencia.po index ce4d21d..f883f03 100644 --- a/languages/exelearning-ca_valencia.po +++ b/languages/exelearning-ca_valencia.po @@ -635,7 +635,7 @@ msgstr "L'arxiu conté més d'un config.xml." msgid "The style package is missing config.xml." msgstr "Al paquet d'estil li falta config.xml." -#: includes/class-style-package.php:202 +#: includes/class-style-package.php:198 msgid "The archive must contain a single root folder or place all files at the root." msgstr "L'arxiu ha de contenir una única carpeta arrel o situar tots els fitxers a l'arrel." diff --git a/languages/exelearning-de_DE.po b/languages/exelearning-de_DE.po index 474e49c..6f58010 100644 --- a/languages/exelearning-de_DE.po +++ b/languages/exelearning-de_DE.po @@ -635,7 +635,7 @@ msgstr "Das Archiv enthält mehr als eine config.xml." msgid "The style package is missing config.xml." msgstr "Im Stilpaket fehlt die config.xml." -#: includes/class-style-package.php:202 +#: includes/class-style-package.php:198 msgid "The archive must contain a single root folder or place all files at the root." msgstr "Das Archiv muss einen einzigen Stammordner enthalten oder alle Dateien im Stammverzeichnis ablegen." diff --git a/languages/exelearning-eo.po b/languages/exelearning-eo.po index d45641d..c4bfa32 100644 --- a/languages/exelearning-eo.po +++ b/languages/exelearning-eo.po @@ -635,7 +635,7 @@ msgstr "La arkivo enhavas pli ol unu config.xml." msgid "The style package is missing config.xml." msgstr "En la stilpakaĵo mankas config.xml." -#: includes/class-style-package.php:202 +#: includes/class-style-package.php:198 msgid "The archive must contain a single root folder or place all files at the root." msgstr "La arkivo devas enhavi ununuran radikan dosierujon aŭ meti ĉiujn dosierojn en la radikon." diff --git a/languages/exelearning-es_ES.po b/languages/exelearning-es_ES.po index c7b6244..421c60c 100644 --- a/languages/exelearning-es_ES.po +++ b/languages/exelearning-es_ES.po @@ -635,7 +635,7 @@ msgstr "El archivo contiene más de un config.xml." msgid "The style package is missing config.xml." msgstr "Al paquete de estilo le falta config.xml." -#: includes/class-style-package.php:202 +#: includes/class-style-package.php:198 msgid "The archive must contain a single root folder or place all files at the root." msgstr "El archivo debe contener una única carpeta raíz o tener todos los ficheros en la raíz." diff --git a/languages/exelearning-eu.po b/languages/exelearning-eu.po index 72f30f9..e40fffb 100644 --- a/languages/exelearning-eu.po +++ b/languages/exelearning-eu.po @@ -635,7 +635,7 @@ msgstr "Artxiboak config.xml bat baino gehiago ditu." msgid "The style package is missing config.xml." msgstr "Estilo-paketeari config.xml falta zaio." -#: includes/class-style-package.php:202 +#: includes/class-style-package.php:198 msgid "The archive must contain a single root folder or place all files at the root." msgstr "Artxiboak erro-karpeta bakar bat izan behar du edo fitxategi guztiak erroan jarri." diff --git a/languages/exelearning-gl_ES.po b/languages/exelearning-gl_ES.po index ffcc0f4..f74a98e 100644 --- a/languages/exelearning-gl_ES.po +++ b/languages/exelearning-gl_ES.po @@ -635,7 +635,7 @@ msgstr "O arquivo contén máis dun config.xml." msgid "The style package is missing config.xml." msgstr "Ao paquete de estilo fáltalle config.xml." -#: includes/class-style-package.php:202 +#: includes/class-style-package.php:198 msgid "The archive must contain a single root folder or place all files at the root." msgstr "O arquivo debe conter unha única carpeta raíz ou situar todos os ficheiros na raíz." diff --git a/languages/exelearning-it_IT.po b/languages/exelearning-it_IT.po index 9a72471..87da2ef 100644 --- a/languages/exelearning-it_IT.po +++ b/languages/exelearning-it_IT.po @@ -635,7 +635,7 @@ msgstr "L'archivio contiene più di un config.xml." msgid "The style package is missing config.xml." msgstr "Nel pacchetto di stile manca config.xml." -#: includes/class-style-package.php:202 +#: includes/class-style-package.php:198 msgid "The archive must contain a single root folder or place all files at the root." msgstr "L'archivio deve contenere un'unica cartella radice o collocare tutti i file nella radice." diff --git a/languages/exelearning-pt_PT.po b/languages/exelearning-pt_PT.po index b722d4f..013ceb5 100644 --- a/languages/exelearning-pt_PT.po +++ b/languages/exelearning-pt_PT.po @@ -635,7 +635,7 @@ msgstr "O arquivo contém mais do que um config.xml." msgid "The style package is missing config.xml." msgstr "Falta config.xml no pacote de estilo." -#: includes/class-style-package.php:202 +#: includes/class-style-package.php:198 msgid "The archive must contain a single root folder or place all files at the root." msgstr "O arquivo deve conter uma única pasta raiz ou colocar todos os ficheiros na raiz." diff --git a/languages/exelearning-ro_RO.po b/languages/exelearning-ro_RO.po index 586c4da..4334107 100644 --- a/languages/exelearning-ro_RO.po +++ b/languages/exelearning-ro_RO.po @@ -636,7 +636,7 @@ msgstr "Arhiva conține mai mult de un config.xml." msgid "The style package is missing config.xml." msgstr "Pachetului de stil îi lipsește config.xml." -#: includes/class-style-package.php:202 +#: includes/class-style-package.php:198 msgid "The archive must contain a single root folder or place all files at the root." msgstr "Arhiva trebuie să conțină un singur folder rădăcină sau să plaseze toate fișierele în rădăcină." diff --git a/languages/exelearning.pot b/languages/exelearning.pot index 07ad95a..8afe3ac 100644 --- a/languages/exelearning.pot +++ b/languages/exelearning.pot @@ -634,7 +634,7 @@ msgstr "" msgid "The style package is missing config.xml." msgstr "" -#: includes/class-style-package.php:202 +#: includes/class-style-package.php:198 msgid "The archive must contain a single root folder or place all files at the root." msgstr ""