diff --git a/h5p-default-storage.class.php b/h5p-default-storage.class.php index 8d2daf8..38af307 100644 --- a/h5p-default-storage.class.php +++ b/h5p-default-storage.class.php @@ -426,6 +426,18 @@ class H5PDefaultStorage implements \H5PFileStorage { $path = "{$this->path}/content/{$contentId}/{$file}"; if (file_exists($path)) { unlink($path); + + // Clean up any empty parent directories to avoid cluttering the file system + $parts = explode('/', $path); + while (array_pop($parts) !== NULL) { + $dir = implode('/', $parts); + if (is_dir($dir) && count(scandir($dir)) === 2) { // empty contains '.' and '..' + rmdir($dir); // Remove empty parent + } + else { + return; // Not empty + } + } } } diff --git a/h5p.classes.php b/h5p.classes.php index 32aeb38..7c0e546 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1724,7 +1724,7 @@ class H5PCore { public static $coreApi = array( 'majorVersion' => 1, - 'minorVersion' => 13 + 'minorVersion' => 14 ); public static $styles = array( 'styles/h5p.css', @@ -1746,7 +1746,7 @@ class H5PCore { 'js/h5p-utils.js', ); - public static $defaultContentWhitelist = 'json png jpg jpeg gif bmp tif tiff svg eot ttf woff woff2 otf webm mp4 ogg mp3 wav txt pdf rtf doc docx xls xlsx ppt pptx odt ods odp xml csv diff patch swf md textile'; + public static $defaultContentWhitelist = 'json png jpg jpeg gif bmp tif tiff svg eot ttf woff woff2 otf webm mp4 ogg mp3 wav txt pdf rtf doc docx xls xlsx ppt pptx odt ods odp xml csv diff patch swf md textile vtt webvtt'; public static $defaultLibraryWhitelistExtras = 'js css'; public $librariesJsonData, $contentJsonData, $mainJsonData, $h5pF, $fs, $h5pD, $disableFileCheck; @@ -2790,16 +2790,8 @@ class H5PCore { * @return string token */ public static function createToken($action) { - if (!isset($_SESSION['h5p_token'])) { - // Create an unique key which is used to create action tokens for this session. - $_SESSION['h5p_token'] = uniqid(); - } - - // Timefactor - $time_factor = self::getTimeFactor(); - // Create and return token - return substr(hash('md5', $action . $time_factor . $_SESSION['h5p_token']), -16, 13); + return self::hashToken($action, self::getTimeFactor()); } /** @@ -2810,6 +2802,31 @@ class H5PCore { return ceil(time() / (86400 / 2)); } + /** + * Generate a unique hash string based on action, time and token + * + * @param string $action + * @param int $time_factor + * @return string + */ + private static function hashToken($action, $time_factor) { + if (!isset($_SESSION['h5p_token'])) { + // Create an unique key which is used to create action tokens for this session. + if (function_exists('random_bytes')) { + $_SESSION['h5p_token'] = base64_encode(random_bytes(15)); + } + else if (function_exists('openssl_random_pseudo_bytes')) { + $_SESSION['h5p_token'] = base64_encode(openssl_random_pseudo_bytes(15)); + } + else { + $_SESSION['h5p_token'] = uniqid('', TRUE); + } + } + + // Create hash and return + return substr(hash('md5', $action . $time_factor . $_SESSION['h5p_token']), -16, 13); + } + /** * Verify if the given token is valid for the given action. * @@ -2818,9 +2835,12 @@ class H5PCore { * @return boolean valid token */ public static function validToken($action, $token) { + // Get the timefactor $time_factor = self::getTimeFactor(); - return $token === substr(hash('md5', $action . $time_factor . $_SESSION['h5p_token']), -16, 13) || // Under 12 hours - $token === substr(hash('md5', $action . ($time_factor - 1) . $_SESSION['h5p_token']), -16, 13); // Between 12-24 hours + + // Check token to see if it's valid + return $token === self::hashToken($action, $time_factor) || // Under 12 hours + $token === self::hashToken($action, $time_factor - 1); // Between 12-24 hours } /** @@ -3163,7 +3183,7 @@ class H5PContentValidator { $stylePatterns[] = '/^font-size: *[0-9.]+(em|px|%) *;?$/i'; } if (isset($semantics->font->family) && $semantics->font->family) { - $stylePatterns[] = '/^font-family: *[a-z0-9," ]+;?$/i'; + $stylePatterns[] = '/^font-family: *[-a-z0-9," ]+;?$/i'; } if (isset($semantics->font->color) && $semantics->font->color) { $stylePatterns[] = '/^color: *(#[a-f0-9]{3}[a-f0-9]{3}?|rgba?\([0-9, ]+\)) *;?$/i'; @@ -3400,6 +3420,11 @@ class H5PContentValidator { $file->path = $matches[5]; } + // Remove temporary files suffix + if (substr($file->path, -4, 4) === '#tmp') { + $file->path = substr($file->path, 0, strlen($file->path) - 4); + } + // Make sure path and mime does not have any special chars $file->path = htmlspecialchars($file->path, ENT_QUOTES, 'UTF-8', FALSE); if (isset($file->mime)) { diff --git a/js/h5p-confirmation-dialog.js b/js/h5p-confirmation-dialog.js index d34f620..a6dd998 100644 --- a/js/h5p-confirmation-dialog.js +++ b/js/h5p-confirmation-dialog.js @@ -315,7 +315,7 @@ H5P.ConfirmationDialog = (function (EventDispatcher) { if (resizeIFrame && options.instance) { var minHeight = parseInt(popup.offsetHeight, 10) + exitButtonOffset + (2 * shadowOffset); - wrapperElement.style.minHeight = minHeight + 'px'; + self.setViewPortMinimumHeight(minHeight); options.instance.trigger('resize'); resizeIFrame = false; } @@ -340,10 +340,21 @@ H5P.ConfirmationDialog = (function (EventDispatcher) { setTimeout(function () { popupBackground.classList.add('hidden'); wrapperElement.removeChild(popupBackground); + self.setViewPortMinimumHeight(null); }, 100); return this; }; + + /** + * Sets the minimum height of the view port + * + * @param {number|null} minHeight + */ + this.setViewPortMinimumHeight = function(minHeight) { + var container = document.querySelector('.h5p-container') || document.body; + container.style.minHeight = (typeof minHeight === 'number') ? (minHeight + 'px') : minHeight; + }; } ConfirmationDialog.prototype = Object.create(EventDispatcher.prototype); diff --git a/js/h5p.js b/js/h5p.js index 4b408b4..aee90e2 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -133,9 +133,26 @@ H5P.init = function (target) { // Check if we should add and display a fullscreen button for this H5P. if (contentData.fullScreen == 1 && H5P.fullscreenSupported) { - H5P.jQuery('