Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

yarn pre-commit
npx lint-staged
3 changes: 2 additions & 1 deletion src/common/Testimonial/TestimonialCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { format } from 'date-fns';
import * as allLocales from 'date-fns/locale';
import { email2Slug } from 'common/services/string';
import sanitizeHTML from 'common/utils/sanitizeHTML';

const TestimonialCard = ({ home, quote, name, avatarUrl, category, created_at, email }) => {
const [formattedDate] = useState(() => {
Expand Down Expand Up @@ -59,7 +60,7 @@ const TestimonialCard = ({ home, quote, name, avatarUrl, category, created_at, e
>
<p
className="leading-relaxed text-gray-700"
dangerouslySetInnerHTML={{ __html: replaceWithBr() }}
dangerouslySetInnerHTML={{ __html: sanitizeHTML(replaceWithBr()) }}
/>
</blockquote>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/common/badges-dashboard/BadgeDetails.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Badge from './Badge';
import sanitizeHTML from 'common/utils/sanitizeHTML';
import './badge.css';

const BadgeDetails = ({ badge, onClose }) => {
Expand All @@ -9,7 +10,7 @@ const BadgeDetails = ({ badge, onClose }) => {
return `<a href="${url}" target="_blank" rel="noopener noreferrer" class="text-blue-500 hover:underline">${name}</a>`;
});

return <span dangerouslySetInnerHTML={{ __html: descriptionWithLinks }} />;
return <span dangerouslySetInnerHTML={{ __html: sanitizeHTML(descriptionWithLinks) }} />;
};

return (
Expand Down
12 changes: 12 additions & 0 deletions src/common/utils/sanitizeHTML.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import DOMPurify from 'dompurify';

/**
* Sanitizes an HTML string using DOMPurify to prevent XSS attacks.
* Use this utility whenever you need to render dynamic HTML via dangerouslySetInnerHTML.
*
* @param {string} html - The raw HTML string to sanitize.
* @returns {string} - A sanitized HTML string safe to use with dangerouslySetInnerHTML.
*/
const sanitizeHTML = (html) => DOMPurify.sanitize(html ?? '');

export default sanitizeHTML;
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ function SelectionSortVisualizer() {
const handleSort = async () => {
const arrCopy = [...arr];
const outputElements = document.getElementById('output-visualizer');
// Safe: clears the container to empty string (no user data injected).
// All subsequent DOM mutations use createElement/createTextNode (XSS-safe).
outputElements.innerHTML = '';

for (let i = 0; i < arrCopy.length - 1; i++) {
Expand Down
3 changes: 2 additions & 1 deletion src/plays/devblog/Pages/Article.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import axios from 'axios';
import { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import sanitizeHTML from 'common/utils/sanitizeHTML';
import Loading from '../components/Loading';

const Article = () => {
Expand Down Expand Up @@ -50,7 +51,7 @@ const Article = () => {

<div
className="mt-10 devBlog-article"
dangerouslySetInnerHTML={{ __html: article.body_html }}
dangerouslySetInnerHTML={{ __html: sanitizeHTML(article.body_html) }}
/>
</div>
) : (
Expand Down
7 changes: 4 additions & 3 deletions src/plays/fun-quiz/EndScreen.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// vendors
import { Fragment, useState } from 'react';
import sanitizeHTML from 'common/utils/sanitizeHTML';

// css
import './FrontScreen.scss';
Expand All @@ -16,17 +17,17 @@ const EndScreen = ({ quizSummary, redirectHome }) => {
<div className="question-number">Question: {currentQuestion?.qNo}</div>
<li
dangerouslySetInnerHTML={{
__html: `${currentQuestion?.question}`
__html: sanitizeHTML(`${currentQuestion?.question}`)
}}
/>
<span
dangerouslySetInnerHTML={{
__html: `<br/><b>Ans</b>: ${currentQuestion?.correct_answer}<br/>`
__html: sanitizeHTML(`<br/><b>Ans</b>: ${currentQuestion?.correct_answer}<br/>`)
}}
/>
<span
dangerouslySetInnerHTML={{
__html: `<b>Your Answer</b>: ${currentQuestion?.your_answer}`
__html: sanitizeHTML(`<b>Your Answer</b>: ${currentQuestion?.your_answer}`)
}}
/>
</div>
Expand Down
5 changes: 3 additions & 2 deletions src/plays/fun-quiz/QuizScreen.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useState, useCallback, useRef } from 'react';
import sanitizeHTML from 'common/utils/sanitizeHTML';

import './QuizScreen.scss';

Expand Down Expand Up @@ -149,15 +150,15 @@ function QuizScreen({ category, getQuizSummary }) {
<div className={`timer ${timer <= 5 && 'caution'}`}>{timer}</div>
<div className="question-info">Question: {questionNumber + 1}</div>
<div className="question">
<h1 dangerouslySetInnerHTML={{ __html: currentQuestion?.question }} />
<h1 dangerouslySetInnerHTML={{ __html: sanitizeHTML(currentQuestion?.question) }} />
</div>
<div className="options">
{currentQuestion?.options?.map((option, index) => {
return (
<div className="single-opt" key={index}>
<div
className={itemClassDisplayController(option)}
dangerouslySetInnerHTML={{ __html: option }}
dangerouslySetInnerHTML={{ __html: sanitizeHTML(option) }}
onClick={handleAnswerClick(option)}
/>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/plays/markdown-editor/Output.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from 'react';
import sanitizeHTML from 'common/utils/sanitizeHTML';

const Output = ({ md, text, mdPreviewBox }) => {
return (
<div
className="md-editor output-div"
dangerouslySetInnerHTML={{ __html: md.render(text) }}
dangerouslySetInnerHTML={{ __html: sanitizeHTML(md.render(text)) }}
id={mdPreviewBox}
/>
);
Expand Down
5 changes: 1 addition & 4 deletions src/plays/text-to-speech/TextToSpeech.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,7 @@ function TextToSpeech(props) {
<div className="tts-output-box">
{convertedText ? (
<>
<p
className="tts-output-text"
dangerouslySetInnerHTML={{ __html: convertedText }}
/>
<p className="tts-output-text">{convertedText}</p>

<button className="tts-speaker-btn" onClick={handleSpeak}>
{isSpeaking ? <FaStop size={28} /> : <FaVolumeUp size={28} />}
Expand Down
9 changes: 7 additions & 2 deletions src/plays/tube2tunes/Tube2tunes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,21 @@ function Tube2tunes(props) {
.then((res) => {
if (res.data.status === 'processing') {
setProcessingMsg(true);
setLoading(false);
} else if (res.data.status === 'fail') {
setFailedMsg(true);
setLoading(false);
} else {
setUrlResult(res.data.link);
setTitle(res.data.title);
setLoading(false);
}
})
.catch(() => {
// console.log('Error: ', err);
.catch((err) => {
setError(true);
setLoading(false);
// eslint-disable-next-line no-console
console.error('Error: ', err);
});

inputUrlRef.current.value = '';
Expand Down
Loading