Merge remote-tracking branch 'origin/support/3.2' into develop

# Conflicts:
#	application/utils.inc.php
This commit is contained in:
bdalsass
2025-05-16 14:22:12 +02:00
3 changed files with 75 additions and 88 deletions

View File

@@ -524,9 +524,7 @@ EOF
*/ */
public function Render($oPage, $bEditMode = false, $aExtraParams = array(), $bCanEdit = true) public function Render($oPage, $bEditMode = false, $aExtraParams = array(), $bCanEdit = true)
{ {
if (!array_key_exists('dashboard_div_id', $aExtraParams)) { $aExtraParams['dashboard_div_id'] = utils::Sanitize($aExtraParams['dashboard_div_id'] ?? null, $this->GetId(), utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
$aExtraParams['dashboard_div_id'] = utils::Sanitize($this->GetId(), '', 'element_identifier');
}
/** @var \DashboardLayoutMultiCol $oLayout */ /** @var \DashboardLayoutMultiCol $oLayout */
$oLayout = new $this->sLayoutClass(); $oLayout = new $this->sLayoutClass();
@@ -1052,7 +1050,7 @@ EOF
$sSelectorHtml .= '</div>'; $sSelectorHtml .= '</div>';
$sFile = addslashes($this->GetDefinitionFile()); $sFile = addslashes($this->GetDefinitionFile());
$sReloadURL = $this->GetReloadURL(); $sReloadURL = json_encode($this->GetReloadURL());
$bFromDashboardPage = isset($aAjaxParams['from_dashboard_page']) ? isset($aAjaxParams['from_dashboard_page']) : false; $bFromDashboardPage = isset($aAjaxParams['from_dashboard_page']) ? isset($aAjaxParams['from_dashboard_page']) : false;
if ($bFromDashboardPage) { if ($bFromDashboardPage) {
@@ -1141,7 +1139,6 @@ JS
->AddCSSClass('ibo-action-button'); ->AddCSSClass('ibo-action-button');
$oToolbar->AddSubBlock($oActionButton); $oToolbar->AddSubBlock($oActionButton);
$aActions = array(); $aActions = array();
$sFile = addslashes(utils::LocalPath($this->sDefinitionFile)); $sFile = addslashes(utils::LocalPath($this->sDefinitionFile));
$sJSExtraParams = json_encode($aExtraParams); $sJSExtraParams = json_encode($aExtraParams);
@@ -1166,7 +1163,7 @@ JS
$oToolbar->AddSubBlock($oActionButton) $oToolbar->AddSubBlock($oActionButton)
->AddSubBlock($oActionsMenu); ->AddSubBlock($oActionsMenu);
$sReloadURL = $this->GetReloadURL(); $sReloadURL = json_encode($this->GetReloadURL());
$oPage->add_script( $oPage->add_script(
<<<EOF <<<EOF
function EditDashboard(sId, sDashboardFile, aExtraParams) function EditDashboard(sId, sDashboardFile, aExtraParams)
@@ -1273,7 +1270,7 @@ EOF
$sTitle = json_encode($this->sTitle); $sTitle = json_encode($this->sTitle);
$sFile = json_encode($this->GetDefinitionFile()); $sFile = json_encode($this->GetDefinitionFile());
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php'; $sUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php';
$sReloadURL = $this->GetReloadURL(); $sReloadURL = json_encode($this->GetReloadURL());
$sExitConfirmationMessage = addslashes(Dict::S('UI:NavigateAwayConfirmationMessage')); $sExitConfirmationMessage = addslashes(Dict::S('UI:NavigateAwayConfirmationMessage'));
$sCancelConfirmationMessage = addslashes(Dict::S('UI:CancelConfirmationMessage')); $sCancelConfirmationMessage = addslashes(Dict::S('UI:CancelConfirmationMessage'));

View File

@@ -521,8 +521,8 @@ class utils
// For URL // For URL
case static::ENUM_SANITIZATION_FILTER_URL: case static::ENUM_SANITIZATION_FILTER_URL:
// N°6350 - returns only valid URLs $retValue = filter_var($value, FILTER_SANITIZE_URL);
$retValue = filter_var($value, FILTER_VALIDATE_URL); $retValue = filter_var($retValue, FILTER_VALIDATE_URL);
break; break;
default: default:
@@ -554,44 +554,44 @@ class utils
switch($sError) switch($sError)
{ {
case UPLOAD_ERR_OK: case UPLOAD_ERR_OK:
$sTmpName = is_null($sIndex) ? $aFileInfo['tmp_name'] : $aFileInfo['tmp_name'][$sIndex]; $sTmpName = is_null($sIndex) ? $aFileInfo['tmp_name'] : $aFileInfo['tmp_name'][$sIndex];
$sMimeType = is_null($sIndex) ? $aFileInfo['type'] : $aFileInfo['type'][$sIndex]; $sMimeType = is_null($sIndex) ? $aFileInfo['type'] : $aFileInfo['type'][$sIndex];
$sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex]; $sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex];
$doc_content = file_get_contents($sTmpName); $doc_content = file_get_contents($sTmpName);
$sMimeType = self::GetFileMimeType($sTmpName); $sMimeType = self::GetFileMimeType($sTmpName);
$oDocument = new ormDocument($doc_content, $sMimeType, $sName); $oDocument = new ormDocument($doc_content, $sMimeType, $sName);
break; break;
case UPLOAD_ERR_NO_FILE: case UPLOAD_ERR_NO_FILE:
// no file to load, it's a normal case, just return an empty document // no file to load, it's a normal case, just return an empty document
break; break;
case UPLOAD_ERR_FORM_SIZE: case UPLOAD_ERR_FORM_SIZE:
case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_INI_SIZE:
throw new FileUploadException(Dict::Format('UI:Error:UploadedFileTooBig', ini_get('upload_max_filesize'))); throw new FileUploadException(Dict::Format('UI:Error:UploadedFileTooBig', ini_get('upload_max_filesize')));
break; break;
case UPLOAD_ERR_PARTIAL: case UPLOAD_ERR_PARTIAL:
throw new FileUploadException(Dict::S('UI:Error:UploadedFileTruncated.')); throw new FileUploadException(Dict::S('UI:Error:UploadedFileTruncated.'));
break; break;
case UPLOAD_ERR_NO_TMP_DIR: case UPLOAD_ERR_NO_TMP_DIR:
throw new FileUploadException(Dict::S('UI:Error:NoTmpDir')); throw new FileUploadException(Dict::S('UI:Error:NoTmpDir'));
break; break;
case UPLOAD_ERR_CANT_WRITE: case UPLOAD_ERR_CANT_WRITE:
throw new FileUploadException(Dict::Format('UI:Error:CannotWriteToTmp_Dir', ini_get('upload_tmp_dir'))); throw new FileUploadException(Dict::Format('UI:Error:CannotWriteToTmp_Dir', ini_get('upload_tmp_dir')));
break; break;
case UPLOAD_ERR_EXTENSION: case UPLOAD_ERR_EXTENSION:
$sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex]; $sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex];
throw new FileUploadException(Dict::Format('UI:Error:UploadStoppedByExtension_FileName', $sName)); throw new FileUploadException(Dict::Format('UI:Error:UploadStoppedByExtension_FileName', $sName));
break; break;
default: default:
throw new FileUploadException(Dict::Format('UI:Error:UploadFailedUnknownCause_Code', $sError)); throw new FileUploadException(Dict::Format('UI:Error:UploadFailedUnknownCause_Code', $sError));
break; break;
} }
} }
@@ -889,10 +889,10 @@ class utils
$aDateRegexps = array_values($aSpec); $aDateRegexps = array_values($aSpec);
} }
$sDateRegexp = str_replace($aDateTokens, $aDateRegexps, $sFormat); $sDateRegexp = str_replace($aDateTokens, $aDateRegexps, $sFormat);
if (preg_match('!^(?<head>)'.$sDateRegexp.'(?<tail>)$!', $sDate, $aMatches)) if (preg_match('!^(?<head>)'.$sDateRegexp.'(?<tail>)$!', $sDate, $aMatches))
{ {
$sYear = isset($aMatches['year']) ? $aMatches['year'] : 0; $sYear = isset($aMatches['year']) ? $aMatches['year'] : 0;
$sMonth = isset($aMatches['month']) ? $aMatches['month'] : 1; $sMonth = isset($aMatches['month']) ? $aMatches['month'] : 1;
$sDay = isset($aMatches['day']) ? $aMatches['day'] : 1; $sDay = isset($aMatches['day']) ? $aMatches['day'] : 1;
@@ -901,11 +901,11 @@ class utils
$sSecond = isset($aMatches['second']) ? $aMatches['second'] : 0; $sSecond = isset($aMatches['second']) ? $aMatches['second'] : 0;
return strtotime("$sYear-$sMonth-$sDay $sHour:$sMinute:$sSecond"); return strtotime("$sYear-$sMonth-$sDay $sHour:$sMinute:$sSecond");
} }
else else
{ {
return false; return false;
} }
// http://www.spaweditor.com/scripts/regex/index.php // http://www.spaweditor.com/scripts/regex/index.php
} }
/** /**
@@ -1334,8 +1334,8 @@ class utils
return Session::GetLog(); return Session::GetLog();
} }
static function DebugBacktrace($iLimit = 5) static function DebugBacktrace($iLimit = 5)
{ {
$aFullTrace = debug_backtrace(); $aFullTrace = debug_backtrace();
$aLightTrace = array(); $aLightTrace = array();
for($i=1; ($i<=$iLimit && $i < count($aFullTrace)); $i++) // Skip the last function call... which is the call to this function ! for($i=1; ($i<=$iLimit && $i < count($aFullTrace)); $i++) // Skip the last function call... which is the call to this function !
@@ -1343,7 +1343,7 @@ class utils
$aLightTrace[$i] = $aFullTrace[$i]['function'].'(), called from line '.$aFullTrace[$i]['line'].' in '.$aFullTrace[$i]['file']; $aLightTrace[$i] = $aFullTrace[$i]['function'].'(), called from line '.$aFullTrace[$i]['line'].' in '.$aFullTrace[$i]['file'];
} }
echo "<p><pre>".print_r($aLightTrace, true)."</pre></p>\n"; echo "<p><pre>".print_r($aLightTrace, true)."</pre></p>\n";
} }
/** /**
* Execute the given iTop PHP script, passing it the current credentials * Execute the given iTop PHP script, passing it the current credentials
@@ -1539,7 +1539,7 @@ class utils
if (strlen($sUrl) < SERVER_MAX_URL_LENGTH) { if (strlen($sUrl) < SERVER_MAX_URL_LENGTH) {
// Static menus: Email this page, CSV Export & Add to Dashboard // Static menus: Email this page, CSV Export & Add to Dashboard
$aResult[] = new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), $aResult[] = new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'),
"mailto:?body=".urlencode($sUrl).' ' // Add an extra space to make it work in Outlook "mailto:?body=".urlencode($sUrl).' ' // Add an extra space to make it work in Outlook
); );
} }
@@ -1947,7 +1947,7 @@ SQL;
CURLOPT_HEADER => false, // don't return the headers in the output CURLOPT_HEADER => false, // don't return the headers in the output
CURLOPT_FOLLOWLOCATION => true, // follow redirects CURLOPT_FOLLOWLOCATION => true, // follow redirects
CURLOPT_ENCODING => "", // handle all encodings CURLOPT_ENCODING => "", // handle all encodings
CURLOPT_USERAGENT => static::GetConfig()->Get('http.request.user_agent'), // who am i CURLOPT_USERAGENT => "spider", // who am i
CURLOPT_AUTOREFERER => true, // set referer on redirect CURLOPT_AUTOREFERER => true, // set referer on redirect
CURLOPT_CONNECTTIMEOUT => 120, // timeout on connect CURLOPT_CONNECTTIMEOUT => 120, // timeout on connect
CURLOPT_TIMEOUT => 120, // timeout on response CURLOPT_TIMEOUT => 120, // timeout on response
@@ -1961,7 +1961,7 @@ SQL;
CURLOPT_HTTPHEADER => $aHTTPHeaders, CURLOPT_HTTPHEADER => $aHTTPHeaders,
); );
$aAllOptions = $aCurlOptions + $aOptions; $aAllOptions = $aCurlOptions + $aOptions;
$ch = curl_init($sUrl); $ch = curl_init($sUrl);
curl_setopt_array($ch, $aAllOptions); curl_setopt_array($ch, $aAllOptions);
$response = curl_exec($ch); $response = curl_exec($ch);
@@ -1986,7 +1986,7 @@ SQL;
/** /**
* Get a standard list of character sets * Get a standard list of character sets
* *
* @param array $aAdditionalEncodings Additional values * @param array $aAdditionalEncodings Additional values
* @return array of iconv code => english label, sorted by label * @return array of iconv code => english label, sorted by label
*/ */
public static function GetPossibleEncodings($aAdditionalEncodings = array()) public static function GetPossibleEncodings($aAdditionalEncodings = array())
@@ -2221,13 +2221,13 @@ SQL;
case 'image/gif': case 'image/gif':
case 'image/jpeg': case 'image/jpeg':
case 'image/png': case 'image/png':
$img = @imagecreatefromstring($oImage->GetData()); $img = @imagecreatefromstring($oImage->GetData());
break; break;
default: default:
// Unsupported image type, return the image as-is // Unsupported image type, return the image as-is
//throw new Exception("Unsupported image type: '".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used."); //throw new Exception("Unsupported image type: '".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
return $oImage; return $oImage;
} }
if ($img === false) if ($img === false)
{ {
@@ -2259,16 +2259,16 @@ SQL;
switch ($oImage->GetMimeType()) switch ($oImage->GetMimeType())
{ {
case 'image/gif': case 'image/gif':
imagegif($new); // send image to output buffer imagegif($new); // send image to output buffer
break; break;
case 'image/jpeg': case 'image/jpeg':
imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality
break; break;
case 'image/png': case 'image/png':
imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression
break; break;
} }
$oResampledImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName()); $oResampledImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName());
@ob_end_clean(); @ob_end_clean();
@@ -2298,16 +2298,16 @@ SQL;
$data .= mt_rand(); $data .= mt_rand();
$hash = strtoupper(hash('ripemd128', $uid . md5($data))); $hash = strtoupper(hash('ripemd128', $uid . md5($data)));
$sUUID = '{' . $sUUID = '{' .
substr($hash, 0, 8) . substr($hash, 0, 8) .
'-' . '-' .
substr($hash, 8, 4) . substr($hash, 8, 4) .
'-' . '-' .
substr($hash, 12, 4) . substr($hash, 12, 4) .
'-' . '-' .
substr($hash, 16, 4) . substr($hash, 16, 4) .
'-' . '-' .
substr($hash, 20, 12) . substr($hash, 20, 12) .
'}'; '}';
return $sUUID; return $sUUID;
} }
@@ -2319,7 +2319,7 @@ SQL;
*/ */
public static function GetCurrentModuleName($iCallDepth = 0) public static function GetCurrentModuleName($iCallDepth = 0)
{ {
return ModuleService::GetInstance()->GetCurrentModuleName($iCallDepth + 1); return ModuleService::GetInstance()->GetCurrentModuleName($iCallDepth + 1);
} }
/** /**
@@ -2366,7 +2366,7 @@ SQL;
*/ */
public static function GetCurrentModuleSetting($sProperty, $defaultvalue = null) public static function GetCurrentModuleSetting($sProperty, $defaultvalue = null)
{ {
return ModuleService::GetInstance()->GetCurrentModuleSetting($sProperty, $defaultvalue); return ModuleService::GetInstance()->GetCurrentModuleSetting($sProperty, $defaultvalue);
} }
/** /**
@@ -2375,7 +2375,7 @@ SQL;
*/ */
public static function GetCompiledModuleVersion($sModuleName) public static function GetCompiledModuleVersion($sModuleName)
{ {
return ModuleService::GetInstance()->GetCompiledModuleVersion($sModuleName); return ModuleService::GetInstance()->GetCompiledModuleVersion($sModuleName);
} }
/** /**
@@ -3109,29 +3109,19 @@ TXT
$aMentionMatches = []; $aMentionMatches = [];
$sText = html_entity_decode($sText); $sText = html_entity_decode($sText);
$aMentionAllowedClasses = MetaModel::GetConfig()->Get('mentions.allowed_classes'); preg_match_all('/<a\s*([^>]*)data-object-class="([^"]*)"\s.*data-object-key="([^"]*)"/Ui', $sText, $aMentionMatches);
preg_match_all('/<a\s*([^>]*)data-object-class="([^"]*)"\s.*data-object-key="([^"]*)"\s*([^>]*)>(.*)<\/a>/Ui', $sText, $aMentionMatches);
foreach ($aMentionMatches[0] as $iMatchIdx => $sCompleteMatch) { foreach ($aMentionMatches[0] as $iMatchIdx => $sCompleteMatch) {
$sMatchedClass = $aMentionMatches[2][$iMatchIdx]; $sMatchedClass = $aMentionMatches[2][$iMatchIdx];
$sMatchedId = $aMentionMatches[3][$iMatchIdx]; $sMatchedId = $aMentionMatches[3][$iMatchIdx];
$sMatchedName = $aMentionMatches[5][$iMatchIdx];
$sMentionPrefix = array_search($sMatchedClass, $aMentionAllowedClasses); // Prepare array for matched class if not already present
if ($sMentionPrefix === false) { if (!array_key_exists($sMatchedClass, $aMentionedObjects)) {
continue; $aMentionedObjects[$sMatchedClass] = array();
} }
//tests if the name starts with $sMentionPrefix (e.g. '@' for 'Contact' class) // Add matched ID if not already there
if (str_starts_with($sMatchedName, $sMentionPrefix) === false) { if (!in_array($sMatchedId, $aMentionedObjects[$sMatchedClass])) {
continue; $aMentionedObjects[$sMatchedClass][] = $sMatchedId;
} }
// Prepare array for matched class if not already present
if (!array_key_exists($sMatchedClass, $aMentionedObjects)) {
$aMentionedObjects[$sMatchedClass] = array();
}
// Add matched ID if not already there
if (!in_array($sMatchedId, $aMentionedObjects[$sMatchedClass])) {
$aMentionedObjects[$sMatchedClass][] = $sMatchedId;
}
} }
return $aMentionedObjects; return $aMentionedObjects;

View File

@@ -776,7 +776,7 @@ class utilsTest extends ItopTestCase
'bad element_identifier' => [utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER, 'AD05nb+', 'AD05nb'], 'bad element_identifier' => [utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER, 'AD05nb+', 'AD05nb'],
'array' => [utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER, ['AD05nb+','apply_modify'], ['AD05nb','apply_modify']], 'array' => [utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER, ['AD05nb+','apply_modify'], ['AD05nb','apply_modify']],
'good url' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https://www.w3schools.com', 'https://www.w3schools.com'], 'good url' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https://www.w3schools.com', 'https://www.w3schools.com'],
'bad url' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https://www.w3schoo<EFBFBD><EFBFBD>ls.co<EFBFBD>m', null], 'bad url' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https//www.w3schools.com', null],
'url with injection' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https://demo.combodo.com/simple/pages/UI.php?operation=full_text&text=<img zzz src=x onerror=alert(1) //>', null], 'url with injection' => [utils::ENUM_SANITIZATION_FILTER_URL, 'https://demo.combodo.com/simple/pages/UI.php?operation=full_text&text=<img zzz src=x onerror=alert(1) //>', null],
'raw_data' => ['raw_data', '<Test>\s😃😃😃', '<Test>\s😃😃😃'], 'raw_data' => ['raw_data', '<Test>\s😃😃😃', '<Test>\s😃😃😃'],
]; ];