';
$main_content .= '
'.$user_link.'
';
$main_content .= $date;
- $main_content .= '
'.$main_message['content'].$attachment.'
';
+ $main_content .= ''
+ .Security::remove_XSS($main_message['content'], STUDENT, true)
+ .$attachment.'
';
$main_content .= '';
$main_content .= '';
@@ -1779,4 +1781,20 @@ public static function getLikesButton($messageId, $userId, $groupId = 0)
return $btnLike.PHP_EOL.$btnDislike;
}
+
+ /**
+ * Reports whether the given user is sender or receiver of the given message
+ */
+ public static function isUserOwner(int $userId, int $messageId): bool
+ {
+ $table = Database::get_main_table(TABLE_MESSAGE);
+ $sql = "SELECT id FROM $table
+ WHERE id = $messageId
+ AND (user_receiver_id = $userId OR user_sender_id = $userId)";
+ $res = Database::query($sql);
+ if (Database::num_rows($res) === 1) {
+ return true;
+ }
+ return false;
+ }
}
diff --git a/public/main/inc/lib/online.inc.php b/public/main/inc/lib/online.inc.php
index 77a90280209..b5bbc6f99db 100644
--- a/public/main/inc/lib/online.inc.php
+++ b/public/main/inc/lib/online.inc.php
@@ -76,10 +76,17 @@ function LoginCheck($uid)
function preventMultipleLogin($userId)
{
$table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ONLINE);
- $userId = intval($userId);
+ $userId = (int) $userId;
if ('true' === api_get_setting('prevent_multiple_simultaneous_login')) {
if (!empty($userId) && !api_is_anonymous()) {
$isFirstLogin = Session::read('first_user_login');
+ $currentIp = Session::read('current_ip');
+ $differentIp = false;
+ if (!empty($currentIp) && api_get_real_ip() !== $currentIp) {
+ $isFirstLogin = null;
+ $differentIp = true;
+ }
+
if (empty($isFirstLogin)) {
$sql = "SELECT login_id FROM $table
WHERE login_user_id = $userId
@@ -94,7 +101,7 @@ function preventMultipleLogin($userId)
$userIsReallyOnline = user_is_online($userId);
// Trying double login.
- if (!empty($loginData) && true == $userIsReallyOnline) {
+ if (!empty($loginData) && true == $userIsReallyOnline || $differentIp) {
session_regenerate_id();
Session::destroy();
header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=multiple_connection_not_allowed');
@@ -102,6 +109,7 @@ function preventMultipleLogin($userId)
} else {
// First time
Session::write('first_user_login', 1);
+ Session::write('current_ip', api_get_real_ip());
}
}
}
diff --git a/public/main/inc/lib/pdf.lib.php b/public/main/inc/lib/pdf.lib.php
index 9c10155b7e2..68f70d906ee 100644
--- a/public/main/inc/lib/pdf.lib.php
+++ b/public/main/inc/lib/pdf.lib.php
@@ -7,7 +7,10 @@
use Masterminds\HTML5;
use Mpdf\HTMLParserMode;
use Mpdf\Mpdf;
+use Mpdf\MpdfException;
use Mpdf\Output\Destination;
+use Mpdf\Utils\UtfString;
+use Symfony\Component\DomCrawler\Crawler;
/**
* Class PDF.
@@ -29,6 +32,8 @@ class PDF
* @param string $orientation orientation "P" = Portrait "L" = Landscape
* @param array $params
* @param Template $template
+ *
+ * @throws MpdfException
*/
public function __construct(
$pageFormat = 'A4',
@@ -37,31 +42,31 @@ public function __construct(
$template = null
) {
$this->template = $template;
- /* More info @ http://mpdf1.com/manual/index.php?tid=184&searchstring=mPDF */
+ /* More info @ http://mpdf1.com/manual/index.php?tid=184&searchstring=Mpdf */
if (!in_array($orientation, ['P', 'L'])) {
$orientation = 'P';
}
//left, right, top, bottom, margin_header, margin footer
- $params['left'] = isset($params['left']) ? $params['left'] : 15;
- $params['right'] = isset($params['right']) ? $params['right'] : 15;
- $params['top'] = isset($params['top']) ? $params['top'] : 30;
- $params['bottom'] = isset($params['bottom']) ? $params['bottom'] : 30;
- $params['margin_footer'] = isset($params['margin_footer']) ? $params['margin_footer'] : 8;
-
- $this->params['filename'] = isset($params['filename']) ? $params['filename'] : api_get_local_time();
- $this->params['pdf_title'] = isset($params['pdf_title']) ? $params['pdf_title'] : '';
- $this->params['course_info'] = isset($params['course_info']) ? $params['course_info'] : api_get_course_info();
- $this->params['session_info'] = isset($params['session_info']) ? $params['session_info'] : api_get_session_info(api_get_session_id());
- $this->params['course_code'] = isset($params['course_code']) ? $params['course_code'] : api_get_course_id();
- $this->params['add_signatures'] = isset($params['add_signatures']) ? $params['add_signatures'] : [];
- $this->params['show_real_course_teachers'] = isset($params['show_real_course_teachers']) ? $params['show_real_course_teachers'] : false;
- $this->params['student_info'] = isset($params['student_info']) ? $params['student_info'] : false;
- $this->params['show_grade_generated_date'] = isset($params['show_grade_generated_date']) ? $params['show_grade_generated_date'] : false;
- $this->params['show_teacher_as_myself'] = isset($params['show_teacher_as_myself']) ? $params['show_teacher_as_myself'] : true;
+ $params['left'] = $params['left'] ?? 15;
+ $params['right'] = $params['right'] ?? 15;
+ $params['top'] = $params['top'] ?? 30;
+ $params['bottom'] = $params['bottom'] ?? 30;
+ $params['margin_footer'] = $params['margin_footer'] ?? 8;
+
+ $this->params['filename'] = $params['filename'] ?? api_get_local_time();
+ $this->params['pdf_title'] = $params['pdf_title'] ?? '';
+ $this->params['course_info'] = $params['course_info'] ?? api_get_course_info();
+ $this->params['session_info'] = $params['session_info'] ?? api_get_session_info(api_get_session_id());
+ $this->params['course_code'] = $params['course_code'] ?? api_get_course_id();
+ $this->params['add_signatures'] = $params['add_signatures'] ?? [];
+ $this->params['show_real_course_teachers'] = $params['show_real_course_teachers'] ?? false;
+ $this->params['student_info'] = $params['student_info'] ?? false;
+ $this->params['show_grade_generated_date'] = $params['show_grade_generated_date'] ?? false;
+ $this->params['show_teacher_as_myself'] = $params['show_teacher_as_myself'] ?? true;
$localTime = api_get_local_time();
- $this->params['pdf_date'] = isset($params['pdf_date']) ? $params['pdf_date'] : api_format_date($localTime, DATE_TIME_FORMAT_LONG);
- $this->params['pdf_date_only'] = isset($params['pdf_date']) ? $params['pdf_date'] : api_format_date($localTime, DATE_FORMAT_LONG);
+ $this->params['pdf_date'] = $params['pdf_date'] ?? api_format_date($localTime, DATE_TIME_FORMAT_LONG);
+ $this->params['pdf_date_only'] = $params['pdf_date'] ?? api_format_date($localTime, DATE_FORMAT_LONG);
$params = [
'tempDir' => Container::getParameter('kernel.cache_dir').'/mpdf',
@@ -306,9 +311,23 @@ public function html_to_pdf(
$filename = basename($filename, '.htm');
}
+ $webPath = api_get_path(WEB_PATH);
+
$documentHtml = @file_get_contents($file);
$documentHtml = preg_replace($clean_search, '', $documentHtml);
+ $crawler = new Crawler($documentHtml);
+ $crawler
+ ->filter('link[rel="stylesheet"]')
+ ->each(function (Crawler $node) use ($webPath) {
+ $linkUrl = $node->link()->getUri();
+ if (!str_starts_with($linkUrl, $webPath)) {
+ $node->getNode(0)->parentNode->removeChild($node->getNode(0));
+ }
+ })
+ ;
+ $documentHtml = $crawler->outerHtml();
+
//absolute path for frames.css //TODO: necessary?
$absolute_css_path = api_get_path(WEB_CODE_PATH).'css/'.api_get_setting('stylesheets.stylesheets').'/frames.css';
$documentHtml = str_replace('href="./css/frames.css"', $absolute_css_path, $documentHtml);
@@ -773,7 +792,7 @@ public function format_pdf($courseInfo, $complete = true, $disablePagination = f
}
if (!empty($watermark_text)) {
$this->pdf->SetWatermarkText(
- strcode2utf($watermark_text),
+ UtfString::strcode2utf($watermark_text),
0.1
);
$this->pdf->showWatermarkText = true;
@@ -921,11 +940,25 @@ private static function fixImagesPaths($documentHtml, array $courseInfo = null,
$documentPath = $courseInfo ? $sysCoursePath.$courseInfo['path'].'/document/' : '';
+ $notFoundImagePath = Display::return_icon(
+ 'closed-circle.png',
+ get_lang('FileNotFound'),
+ [],
+ ICON_SIZE_TINY,
+ false,
+ true
+ );
+
/** @var \DOMElement $element */
foreach ($elements as $element) {
$src = $element->getAttribute('src');
$src = trim($src);
+ if (api_filename_has_blacklisted_stream_wrapper($src)) {
+ $element->setAttribute('src', $notFoundImagePath);
+ continue;
+ }
+
if (false !== strpos($src, $protocol)) {
continue;
}
diff --git a/public/main/inc/lib/pear/HTML/QuickForm.php b/public/main/inc/lib/pear/HTML/QuickForm.php
index dcca0c17975..81f1993cb3e 100644
--- a/public/main/inc/lib/pear/HTML/QuickForm.php
+++ b/public/main/inc/lib/pear/HTML/QuickForm.php
@@ -257,11 +257,9 @@ public function __construct(
public function protect()
{
- $token = $this->getSubmitValue('protect_token');
+ $token = Security::get_existing_token();
if (null === $token) {
$token = Security::get_token();
- } else {
- $token = Security::get_existing_token();
}
$this->addHidden('protect_token', $token);
$this->setToken($token);
@@ -1409,22 +1407,12 @@ function getRequiredNote()
*/
public function validate()
{
- if (count($this->_rules) == 0 && count($this->_formRules) == 0 && $this->isSubmitted()) {
- return 0 === count($this->_errors);
- } elseif (!$this->isSubmitted()) {
-
+ if (!$this->isSubmitted()) {
return false;
}
- if (null !== $this->getToken()) {
- $check = Security::check_token('form', $this);
- Security::clear_token();
- if (false === $check) {
- return false;
- }
- }
-
- $registry =& HTML_QuickForm_RuleRegistry::singleton();
+ if (count($this->_rules) > 0 || count($this->_formRules) > 0) {
+ $registry =& HTML_QuickForm_RuleRegistry::singleton();
foreach ($this->_rules as $target => $rules) {
$submitValue = $this->getSubmitValue($target);
@@ -1516,7 +1504,25 @@ public function validate()
}
}
- return (0 == count($this->_errors));
+ if (count($this->_errors) > 0) {
+ return false;
+ }
+ }
+
+ if (null !== $this->getToken()) {
+ $check = Security::check_token('form', $this);
+ Security::clear_token();
+ if (false === $check) {
+ // Redirect to the same URL + show token not validated message.
+ $url = $this->getAttribute('action');
+ Display::addFlash(Display::return_message(get_lang('NotValidated'), 'warning'));
+ api_location($url);
+
+ return false;
+ }
+ }
+
+ return true;
}
/**
diff --git a/public/main/inc/lib/promotion.lib.php b/public/main/inc/lib/promotion.lib.php
index 1b81c3efbbc..54e58dc28ab 100644
--- a/public/main/inc/lib/promotion.lib.php
+++ b/public/main/inc/lib/promotion.lib.php
@@ -213,14 +213,9 @@ public function return_form($url, $action = 'add')
$id = isset($_GET['id']) ? (int) $_GET['id'] : '';
- $form->addElement('header', '', $header);
- $form->addElement('hidden', 'id', $id);
- $form->addElement(
- 'text',
- 'name',
- get_lang('Name'),
- ['size' => '70', 'id' => 'name']
- );
+ $form->addHeader($header);
+ $form->addHidden('id', $id);
+ $form->addText('name', get_lang('Name'), true, ['size' => '70', 'id' => 'name']);
$form->addHtmlEditor(
'description',
get_lang('Description'),
diff --git a/public/main/inc/lib/security.lib.php b/public/main/inc/lib/security.lib.php
index e7c3410858b..aa1acb1a5ff 100644
--- a/public/main/inc/lib/security.lib.php
+++ b/public/main/inc/lib/security.lib.php
@@ -13,13 +13,13 @@
* http://www.phpsec.org/
* The principles here are that all data is tainted (most scripts of Chamilo are
* open to the public or at least to a certain public that could be malicious
- * under specific circumstances). We use the white list approach, where as we
+ * under specific circumstances). We use the white list approach, whereas we
* consider that data can only be used in the database or in a file if it has
* been filtered.
*
* For session fixation, use ...
* For session hijacking, use get_ua() and check_ua()
- * For Cross-Site Request Forgeries, use get_token() and check_tocken()
+ * For Cross-Site Request Forgeries, use get_token() and check_token()
* For basic filtering, use filter()
* For files inclusions (using dynamic paths) use check_rel_path() and check_abs_path()
*
@@ -45,13 +45,13 @@ class Security
* Checks if the absolute path (directory) given is really under the
* checker path (directory).
*
- * @param string Absolute path to be checked (with trailing slash)
- * @param string Checker path under which the path
- * should be (absolute path, with trailing slash, get it from api_get_path(SYS_COURSE_PATH))
+ * @param string $abs_path Absolute path to be checked (with trailing slash)
+ * @param string $checker_path Checker path under which the path
+ * should be (absolute path, with trailing slash, get it from api_get_path(SYS_COURSE_PATH))
*
* @return bool True if the path is under the checker, false otherwise
*/
- public static function check_abs_path($abs_path, $checker_path)
+ public static function check_abs_path(string $abs_path, string $checker_path): bool
{
// The checker path must be set.
if (empty($checker_path)) {
@@ -59,8 +59,7 @@ public static function check_abs_path($abs_path, $checker_path)
}
// Clean $abs_path.
- $abs_path = str_replace(['//', '../'], ['/', ''], $abs_path);
- $true_path = str_replace("\\", '/', realpath($abs_path));
+ $true_path = self::cleanPath($abs_path);
$checker_path = str_replace("\\", '/', realpath($checker_path));
if (empty($checker_path)) {
@@ -84,17 +83,24 @@ public static function check_abs_path($abs_path, $checker_path)
return false;
}
+ public static function cleanPath(string $absPath): string
+ {
+ $absPath = str_replace(['//', '../'], ['/', ''], $absPath);
+
+ return str_replace("\\", '/', realpath($absPath));
+ }
+
/**
* Checks if the relative path (directory) given is really under the
* checker path (directory).
*
- * @param string Relative path to be checked (relative to the current directory) (with trailing slash)
- * @param string Checker path under which the path
- * should be (absolute path, with trailing slash, get it from api_get_path(SYS_COURSE_PATH))
+ * @param string $rel_path Relative path to be checked (relative to the current directory) (with trailing slash)
+ * @param string $checker_path Checker path under which the path
+ * should be (absolute path, with trailing slash, get it from api_get_path(SYS_COURSE_PATH))
*
* @return bool True if the path is under the checker, false otherwise
*/
- public static function check_rel_path($rel_path, $checker_path)
+ public static function check_rel_path(string $rel_path, string $checker_path): bool
{
// The checker path must be set.
if (empty($checker_path)) {
@@ -120,48 +126,47 @@ public static function check_rel_path($rel_path, $checker_path)
* other languages' files extensions).
*
* @param string $filename Unfiltered filename
- *
- * @return string
*/
- public static function filter_filename($filename)
+ public static function filter_filename(string $filename): string
{
return disable_dangerous_file($filename);
}
- /**
- * @return string
- */
- public static function getTokenFromSession()
+ public static function getTokenFromSession(string $prefix = ''): string
{
- return Session::read('sec_token');
+ $secTokenVariable = self::generateSecTokenVariable($prefix);
+
+ return Session::read($secTokenVariable);
}
/**
* This function checks that the token generated in get_token() has been kept (prevents
* Cross-Site Request Forgeries attacks).
*
- * @param string The array in which to get the token ('get' or 'post')
+ * @param string $requestType The array in which to get the token ('get' or 'post')
+ * @param ?FormValidator $form
*
* @return bool True if it's the right token, false otherwise
*/
- public static function check_token($request_type = 'post', FormValidator $form = null)
+ public static function check_token(string $requestType = 'post', FormValidator $form = null, string $prefix = ''): bool
{
- $sessionToken = Session::read('sec_token');
- switch ($request_type) {
+ $secTokenVariable = self::generateSecTokenVariable($prefix);
+ $sessionToken = Session::read($secTokenVariable);
+ switch ($requestType) {
case 'request':
- if (!empty($sessionToken) && isset($_REQUEST['sec_token']) && $sessionToken === $_REQUEST['sec_token']) {
+ if (!empty($sessionToken) && isset($_REQUEST[$secTokenVariable]) && $sessionToken === $_REQUEST[$secTokenVariable]) {
return true;
}
return false;
case 'get':
- if (!empty($sessionToken) && isset($_GET['sec_token']) && $sessionToken === $_GET['sec_token']) {
+ if (!empty($sessionToken) && isset($_GET[$secTokenVariable]) && $sessionToken === $_GET[$secTokenVariable]) {
return true;
}
return false;
case 'post':
- if (!empty($sessionToken) && isset($_POST['sec_token']) && $sessionToken === $_POST['sec_token']) {
+ if (!empty($sessionToken) && isset($_POST[$secTokenVariable]) && $sessionToken === $_POST[$secTokenVariable]) {
return true;
}
@@ -175,11 +180,9 @@ public static function check_token($request_type = 'post', FormValidator $form =
return false;
default:
- if (!empty($sessionToken) && isset($request_type) && $sessionToken === $request_type) {
+ if (!empty($sessionToken) && isset($requestType) && $sessionToken === $requestType) {
return true;
}
-
- return false;
}
return false; // Just in case, don't let anything slip.
@@ -191,12 +194,12 @@ public static function check_token($request_type = 'post', FormValidator $form =
*
* @return bool True if the user agent is the same, false otherwise
*/
- public static function check_ua()
+ public static function check_ua(): bool
{
$security = Session::read('sec_ua');
$securitySeed = Session::read('sec_ua_seed');
- if ($_SERVER['HTTP_USER_AGENT'].$securitySeed === $security) {
+ if ($security === $_SERVER['HTTP_USER_AGENT'].$securitySeed) {
return true;
}
@@ -206,9 +209,11 @@ public static function check_ua()
/**
* Clear the security token from the session.
*/
- public static function clear_token()
+ public static function clear_token(string $prefix = ''): void
{
- Session::erase('sec_token');
+ $secTokenVariable = self::generateSecTokenVariable($prefix);
+
+ Session::erase($secTokenVariable);
}
/**
@@ -221,11 +226,12 @@ public static function clear_token()
*
* @return string Hidden-type input ready to insert into a form
*/
- public static function get_HTML_token()
+ public static function get_HTML_token(string $prefix = ''): string
{
+ $secTokenVariable = self::generateSecTokenVariable($prefix);
$token = md5(uniqid(rand(), true));
- $string = '