From 74db2010d9fdb95c17f97769f3f51635b918a315 Mon Sep 17 00:00:00 2001
From: Max Bischof <106820326+bischofmax@users.noreply.github.com>
Date: Mon, 17 Feb 2025 12:09:28 +0100
Subject: [PATCH] BC-8345 - Add validation if value contains < directly
followed by a string (#3584)
---
controllers/courses.js | 5 ++-
locales/de.json | 1 +
locales/en.json | 1 +
locales/es.json | 1 +
locales/uk.json | 1 +
static/scripts/administration/school.js | 8 +++++
static/scripts/course.js | 7 +++++
static/scripts/courses.js | 4 ---
.../scripts/helpers/openingTagValidation.js | 20 ++++++++++++
static/scripts/homework/edit.js | 8 +++++
static/scripts/settings.js | 7 +++++
static/scripts/topicEdit.js | 31 +++++++++++++++++++
views/courses/create-course.hbs | 1 +
views/courses/edit-course.hbs | 1 +
14 files changed, 89 insertions(+), 7 deletions(-)
create mode 100644 static/scripts/course.js
create mode 100644 static/scripts/helpers/openingTagValidation.js
diff --git a/controllers/courses.js b/controllers/courses.js
index b7dfeeb7ac..c9235d7876 100644
--- a/controllers/courses.js
+++ b/controllers/courses.js
@@ -603,7 +603,7 @@ router.post('/', (req, res, next) => {
api(req)
.post('/courses/', {
- json: req.body, // TODO: sanitize
+ json: { ...req.body, name: req.body.name.trim() }, // TODO: sanitize
})
.then((course) => {
createEventsForCourse(req, res, course).then(() => {
@@ -882,10 +882,9 @@ router.patch('/:courseId', async (req, res, next) => {
// so temporarily add yourself to the list of teachers
req.body.teacherIds.push(currentUserId);
}
-
await deleteEventsForCourse(req, res, courseId);
await api(req).patch(`/courses/${courseId}`, {
- json: req.body,
+ json: { ...req.body, name: req.body.name.trim() },
});
// due to eventual consistency we need to get the course again from server
// instead of using the response from patch
diff --git a/locales/de.json b/locales/de.json
index 4bd317af23..70a5372c5f 100644
--- a/locales/de.json
+++ b/locales/de.json
@@ -1747,6 +1747,7 @@
"noTopicFoundWithCode": "Es wurde kein Thema für diesen Code gefunden.",
"pageMoved": "Diese Seite wurde verschoben.",
"passwordsAreDifferent": "Passwörter stimmen nicht überein.",
+ "containsOpeningTag": "Bitte Leerzeichen nach Kleiner-als-Zeichen einfügen.",
"hasLowerCase": "Passwort muss Kleinbuchstaben enthalten.",
"hasUpperCase": "Passwort muss Großbuchstaben enthalten.",
"hasNumber": "Passwort muss eine Zahl enthalten.",
diff --git a/locales/en.json b/locales/en.json
index d5ffbdefcb..8cc099587e 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -1747,6 +1747,7 @@
"noTopicFoundWithCode": "No topic was found for this code.",
"pageMoved": "This page has been moved.",
"passwordsAreDifferent": "Passwords do not match.",
+ "containsOpeningTag": "Please insert a space after the less-than sign.",
"hasLowerCase": "Password must contain lowercase letters.",
"hasUpperCase": "Password must contain uppercase letters.",
"hasNumber": "Password must contain a number.",
diff --git a/locales/es.json b/locales/es.json
index bd582d447d..41ff11c7bc 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -1747,6 +1747,7 @@
"noTopicFoundWithCode": "No se ha encontrado ningún tema para este código.",
"pageMoved": "Esta página se ha movido.",
"passwordsAreDifferent": "Las contraseñas no coinciden.",
+ "containsOpeningTag": "Inserte un espacio después del signo menos-que.",
"hasLowerCase": "El contraseña debe contener letras minúsculas.",
"hasUpperCase": "El contraseña debe contener letras mayúsculas.",
"hasNumber": "La contraseña debe contener un número.",
diff --git a/locales/uk.json b/locales/uk.json
index 05441fd81f..5d41593fa7 100644
--- a/locales/uk.json
+++ b/locales/uk.json
@@ -146,6 +146,7 @@
"noTopicFoundWithCode": "Не знайдено теми для цього коду.",
"pageMoved": "Цю сторінку переміщено.",
"passwordsAreDifferent": "Паролі не збігаються.",
+ "containsOpeningTag": "Будь ласка, вставте пробіл після знаку менше.",
"hasLowerCase": "Пароль повинен містити малі літери.",
"hasUpperCase": "Пароль повинен містити великі літери.",
"hasNumber": "Пароль повинен містити цифру.",
diff --git a/static/scripts/administration/school.js b/static/scripts/administration/school.js
index d7bc7168d1..1754fadaed 100644
--- a/static/scripts/administration/school.js
+++ b/static/scripts/administration/school.js
@@ -1,3 +1,5 @@
+import validateInputOnOpeningTag from '../helpers/openingTagValidation';
+
function transformToBase64(imageSrc) {
const img = new Image();
const canvas = document.querySelector('#logo-canvas');
@@ -96,3 +98,9 @@ if (messengerInput && messengerSubOptions) {
setMessengerSubOptionsViability(event.target.checked);
});
}
+
+$(document).ready(() => {
+ const schoolName = document.getElementsByName('name')[0];
+
+ if (schoolName) schoolName.addEventListener('keyup', () => validateInputOnOpeningTag(schoolName));
+});
diff --git a/static/scripts/course.js b/static/scripts/course.js
new file mode 100644
index 0000000000..5a4857ced8
--- /dev/null
+++ b/static/scripts/course.js
@@ -0,0 +1,7 @@
+import validateInputOnOpeningTag from './helpers/openingTagValidation';
+
+$(document).ready(() => {
+ const courseName = document.getElementsByName('name')[0];
+
+ if (courseName) courseName.addEventListener('keyup', () => validateInputOnOpeningTag(courseName));
+});
diff --git a/static/scripts/courses.js b/static/scripts/courses.js
index 62b3eb10c7..f349aeca5f 100644
--- a/static/scripts/courses.js
+++ b/static/scripts/courses.js
@@ -11,10 +11,6 @@ $(document).ready(() => {
$('.safari-workaround').show();
}
- $('.js-course-name-input').change(function courseNameInput() {
- $(this).val($(this).val().trim());
- });
-
$('.btn-hidden-toggle').click(function hiddenToggle(e) {
e.stopPropagation();
e.preventDefault();
diff --git a/static/scripts/helpers/openingTagValidation.js b/static/scripts/helpers/openingTagValidation.js
new file mode 100644
index 0000000000..f2c911f044
--- /dev/null
+++ b/static/scripts/helpers/openingTagValidation.js
@@ -0,0 +1,20 @@
+function containsOpeningTagFollowedByString(input) {
+ const regex = /<[^<\s]+/;
+ const result = regex.test(input);
+
+ return result;
+};
+
+export default function validateInputOnOpeningTag(input) {
+ // Firefox needs blur event to trigger validation
+ input.blur();
+ input.focus();
+
+ if (containsOpeningTagFollowedByString(input.value)) {
+ input.setCustomValidity($t('global.text.containsOpeningTag'));
+ } else {
+ input.setCustomValidity('');
+ }
+
+ input.reportValidity();
+};
diff --git a/static/scripts/homework/edit.js b/static/scripts/homework/edit.js
index 4aec15b98e..71a7955335 100644
--- a/static/scripts/homework/edit.js
+++ b/static/scripts/homework/edit.js
@@ -1,6 +1,10 @@
+import validateInputOnOpeningTag from '../helpers/openingTagValidation';
+
const moment = require('moment-timezone');
const Mousetrap = require('../mousetrap/mousetrap');
+
+
window.addEventListener('DOMContentLoaded', () => {
const lang = $('html').attr('lang');
$.datetimepicker.setLocale(lang || 'de');
@@ -131,4 +135,8 @@ window.addEventListener('DOMContentLoaded', () => {
window.history.back();
}
});
+
+ const taskName = document.getElementsByName('name')[0];
+
+ if (taskName) taskName.addEventListener('keyup', () => validateInputOnOpeningTag(taskName));
});
diff --git a/static/scripts/settings.js b/static/scripts/settings.js
index 3fc0e28b63..11bdbb683b 100644
--- a/static/scripts/settings.js
+++ b/static/scripts/settings.js
@@ -1,5 +1,6 @@
import './pwd.js';
import { validatePassword, validateConfirmationPassword } from './helpers/passwordValidations';
+import validateInputOnOpeningTag from './helpers/openingTagValidation';
$(document).ready(function() {
@@ -11,6 +12,12 @@ $(document).ready(function() {
if (password) password.addEventListener('keyup', () => validatePassword(password));
if (confirm_password) confirm_password.addEventListener('keyup', () => validateConfirmationPassword(password, confirm_password));
+ const firstName = document.getElementsByName('firstName')[0];
+ if (firstName) firstName.addEventListener('keyup', () => validateInputOnOpeningTag(firstName));
+
+ const lastName = document.getElementsByName('lastName')[0];
+ if (lastName) lastName.addEventListener('keyup', () => validateInputOnOpeningTag(lastName));
+
// TODO: replace with something cooler
var reloadSite = function() {
delete_cookie("notificationPermission");
diff --git a/static/scripts/topicEdit.js b/static/scripts/topicEdit.js
index 17435a08ba..6045b71f94 100644
--- a/static/scripts/topicEdit.js
+++ b/static/scripts/topicEdit.js
@@ -7,6 +7,37 @@ import { arrayMove, SortableContainer, SortableElement, SortableHandle } from 'r
import shortid from 'shortid';
import ckeditorConfig from './ckeditor/ckeditor-config';
import showFallbackImageOnError from './helpers/showFallbackImageOnError';
+import validateInputOnOpeningTag from './helpers/openingTagValidation';
+
+$(document).ready(() => {
+ const lessonName = document.getElementsByName('name')[0];
+ if (lessonName) lessonName.addEventListener('keyup', () => validateInputOnOpeningTag(lessonName));
+
+ document.querySelectorAll('[name^="contents["][name$="[title]"]')
+ .forEach((element) => element.addEventListener('keyup', () => validateInputOnOpeningTag(element)));
+
+ // Observers if new content blocks are added to the page and ads the validation to the title input
+ const observer = new MutationObserver((mutations) => {
+ mutations.forEach((mutation) => {
+ if (mutation.addedNodes.length) {
+ mutation.addedNodes.forEach((node) => {
+ if (node.nodeType === 1) {
+ const inputs = node.querySelectorAll('[name^="contents["][name$="[title]"]');
+
+ inputs.forEach((input) => {
+ input.addEventListener('keyup', () => validateInputOnOpeningTag(input));
+ });
+ }
+ });
+ }
+ });
+ });
+
+ observer.observe(document.body, {
+ childList: true,
+ subtree: true,
+ });
+});
/**
* A wrapper for each block including a title field, remove, sortable, ...
diff --git a/views/courses/create-course.hbs b/views/courses/create-course.hbs
index 694e294271..35504bcd5e 100644
--- a/views/courses/create-course.hbs
+++ b/views/courses/create-course.hbs
@@ -12,6 +12,7 @@
+
diff --git a/views/courses/edit-course.hbs b/views/courses/edit-course.hbs
index 91eb01fcc3..8cfe0ffecb 100644
--- a/views/courses/edit-course.hbs
+++ b/views/courses/edit-course.hbs
@@ -11,6 +11,7 @@
+