fix: #262 double encoded html in dialog title and translations

pull/339/head
Sebastien Marinier 2025-05-16 17:57:14 +02:00
parent 6425be9740
commit a16d9951fd
4 changed files with 126 additions and 14 deletions

View File

@ -43,8 +43,10 @@
"@octokit/rest": "^18.0.4",
"archiver": "^5.0.0",
"colors": "^1.4.0",
"dompurify": "^3.2.5",
"dotenv": "^16.0.0",
"execa": "^6.1.0",
"html-react-parser": "^5.2.5",
"libxmljs": "^1.0.11",
"qrcode.react": "^2.0.0",
"react-copy-to-clipboard": "^5.0.2",

View File

@ -1,4 +1,6 @@
import { Access, Permission } from './Api';
import parse from 'html-react-parser';
import DOMPurify from 'dompurify';
export const AccessOptions = {
[Access.Public]: t('bbb', 'Public'),
@ -14,3 +16,7 @@ export const PermissionsOptions = {
[Permission.Moderator]: t('bbb', 'moderator'),
[Permission.User]: t('bbb', 'user'),
};
export function html_sanitize_and_parse(str: string): string {
return parse(DOMPurify.sanitize(str, { USE_PROFILES: { html: true } }));
}

View File

@ -5,14 +5,14 @@ import { Access, Room, Permission, RoomShare, api, Restriction } from '../Common
import Dialog from './Dialog';
import ShareWith from './ShareWith';
import { SubmitInput } from './SubmitInput';
import { AccessOptions } from '../Common/Translation';
import { AccessOptions, html_sanitize_and_parse } from '../Common/Translation';
const descriptions: { [key: string]: string } = {
name: t('bbb', 'Descriptive name of this room.'),
welcome: t('bbb', 'This message is shown to all users in the chat area after they joined.'),
maxParticipants: t('bbb', 'Sets a limit on the number of participants for this room. Zero means there is no limit.'),
recording: t('bbb', 'If enabled, the moderator is able to start the recording.'),
access: t('bbb', 'Explanation of the different concepts that constitute access options :<br>- Public: Anyone who has the link can join.- <br>Internal: Only Nextcloud users can join.- <br>Password: Only guests who have the password can join..- <br>Waiting room: A moderator must accept each guest before they can join.- <br>Restricted : Only selected users and groups can access this room.'),
access: t('bbb', 'Explanation of the different concepts that constitute access options :<br> - Public: Anyone who has the link can join.<br> - Internal: Only Nextcloud users can join.<br> - Password: Only guests who have the password can join.<br> - Waiting room: A moderator must accept each guest before they can join.<br> - Restricted : Only selected users and groups can access this room.'),
moderator: t('bbb', 'A moderator is able to manage all participants in a meeting including kicking, muting or selecting a presenter. Users with the role moderator are also able to close a meeting or change the default settings.'),
requireModerator: t('bbb', 'If enabled, normal users have to wait until a moderator is in the room.'),
moderatorToken: t('bbb', 'If enabled, a moderator URL is generated which allows access with moderator permission.'),
@ -70,7 +70,7 @@ const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, op
</label>
<SubmitInput initialValue={room[field]} type={type} name={field} onSubmitValue={value => updateProperty(field, value)} min={minParticipantsLimit} max={maxParticipantsLimit} />
{descriptions[field] && <em>{descriptions[field]}</em>}
{descriptions[field] && <em>{html_sanitize_and_parse(descriptions[field])}</em>}
</div>
);
}
@ -90,7 +90,7 @@ const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, op
})}
</select>
{(value === Access.Password && room.password) && <CopyToClipboard text={room.password} options={{format:'text/plain'}}><input type="text" readOnly={true} className="icon-clippy" value={room.password} /></CopyToClipboard>}
{descriptions[field] && <em>{descriptions[field]}</em>}
{descriptions[field] && <em>{html_sanitize_and_parse(descriptions[field])}</em>}
</div>
);
}
@ -103,7 +103,7 @@ const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, op
}
return (
<Dialog open={open} onClose={() => setOpen(false)} title={t('bbb', 'Edit "{room}"', { room: room.name })}>
<Dialog open={open} onClose={() => setOpen(false)} title={html_sanitize_and_parse(t('bbb', 'Edit "{room}"', { room: room.name }))}>
<div className="bbb-form-element">
<h3>{t('bbb', 'Room URL')}</h3>
<div className="bbb-input-container">
@ -132,7 +132,7 @@ const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, op
{room.access === Access.InternalRestricted &&
<div className="bbb-form-element bbb-form-shareWith">
<span className="icon icon-details icon-visible"></span><em>{t('bbb', 'Access') + ' : ' + descriptions.internalRestrictedShareWith}</em>
<span className="icon icon-details icon-visible"></span><em>{t('bbb', 'Access') + ' : ' + html_sanitize_and_parse(descriptions.internalRestrictedShareWith)}</em>
</div>
}
@ -144,7 +144,7 @@ const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, op
onChange={(event) => updateProperty('everyoneIsModerator', event.target.checked)} />
<label htmlFor={'bbb-everyoneIsModerator-' + room.id}>{t('bbb', 'Every participant is moderator')}</label>
</div>
<em>{descriptions.moderator}</em>
<em>{html_sanitize_and_parse(descriptions.moderator)}</em>
<div className="bbb-mt-1">
<input id={'bbb-moderatorToken-' + room.id}
@ -155,7 +155,7 @@ const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, op
<label htmlFor={'bbb-moderatorToken-' + room.id}>{t('bbb', 'Moderator access via URL')}</label>
</div>
{!!room.moderatorToken && <CopyToClipboard text={api.getRoomUrl(room, true)}><input type="text" readOnly={true} className="icon-clippy" value={api.getRoomUrl(room, true)} /></CopyToClipboard>}
<em>{descriptions.moderatorToken}</em>
<em>{html_sanitize_and_parse(descriptions.moderatorToken)}</em>
</div>
<div className="bbb-form-element">
@ -170,7 +170,7 @@ const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, op
onChange={(event) => updateProperty('record', event.target.checked)} />
<label htmlFor={'bbb-record-' + room.id}>{t('bbb', 'Recording')}</label>
</div>
<p><em>{descriptions.recording}</em></p>
<p><em>{html_sanitize_and_parse(descriptions.recording)}</em></p>
</div>
<div>
<div>
@ -181,7 +181,7 @@ const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, op
onChange={(event) => updateProperty('requireModerator', event.target.checked)} />
<label htmlFor={'bbb-requireModerator-' + room.id}>{t('bbb', 'Require moderator to start room')}</label>
</div>
<p><em>{descriptions.requireModerator}</em></p>
<p><em>{html_sanitize_and_parse(descriptions.requireModerator)}</em></p>
</div>
<div>
<div>
@ -192,7 +192,7 @@ const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, op
onChange={(event) => updateProperty('listenOnly', event.target.checked)} />
<label htmlFor={'bbb-listenOnly-' + room.id}>{t('bbb', 'Listen only option')}</label>
</div>
<p><em>{descriptions.listenOnly}</em></p>
<p><em>{html_sanitize_and_parse(descriptions.listenOnly)}</em></p>
</div>
<div>
<div>
@ -203,7 +203,7 @@ const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, op
onChange={(event) => updateProperty('mediaCheck', !event.target.checked)} />
<label htmlFor={'bbb-mediaCheck-' + room.id}>{t('bbb', 'Skip media check before usage')}</label>
</div>
<p><em>{descriptions.mediaCheck}</em></p>
<p><em>{html_sanitize_and_parse(descriptions.mediaCheck)}</em></p>
</div>
<div>
<div>
@ -214,7 +214,7 @@ const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, op
onChange={(event) => updateProperty('cleanLayout', event.target.checked)} />
<label htmlFor={'bbb-cleanLayout-' + room.id}>{t('bbb', 'Clean layout')}</label>
</div>
<p><em>{descriptions.cleanLayout}</em></p>
<p><em>{html_sanitize_and_parse(descriptions.cleanLayout)}</em></p>
</div>
<div>
<div>
@ -225,7 +225,7 @@ const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, op
onChange={(event) => updateProperty('joinMuted', event.target.checked)} />
<label htmlFor={'bbb-joinMuted-' + room.id}>{t('bbb', 'Join meeting muted')}</label>
</div>
<p><em>{descriptions.joinMuted}</em></p>
<p><em>{html_sanitize_and_parse(descriptions.joinMuted)}</em></p>
</div>
</div>
</Dialog>

104
yarn.lock
View File

@ -1752,6 +1752,11 @@
dependencies:
"@types/node" "*"
"@types/trusted-types@^2.0.7":
version "2.0.7"
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==
"@types/webpack-env@^1.15.2":
version "1.16.3"
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.16.3.tgz#b776327a73e561b71e7881d0cd6d34a1424db86a"
@ -2837,11 +2842,48 @@ dom-helpers@^5.0.1:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"
dom-serializer@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
dependencies:
domelementtype "^2.3.0"
domhandler "^5.0.2"
entities "^4.2.0"
dom-walk@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84"
integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==
domelementtype@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
domhandler@5.0.3, domhandler@^5.0.2, domhandler@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
dependencies:
domelementtype "^2.3.0"
dompurify@^3.2.5:
version "3.2.5"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.2.5.tgz#11b108656a5fb72b24d916df17a1421663d7129c"
integrity sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==
optionalDependencies:
"@types/trusted-types" "^2.0.7"
domutils@^3.2.1:
version "3.2.2"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78"
integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==
dependencies:
dom-serializer "^2.0.0"
domelementtype "^2.3.0"
domhandler "^5.0.3"
dot-prop@^5.1.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
@ -2908,6 +2950,16 @@ enhanced-resolve@^5.0.0, enhanced-resolve@^5.9.2:
graceful-fs "^4.2.4"
tapable "^2.2.0"
entities@^4.2.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
entities@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.0.tgz#09c9e29cb79b0a6459a9b9db9efb418ac5bb8e51"
integrity sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==
envinfo@^7.7.3:
version "7.8.1"
resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475"
@ -3674,11 +3726,39 @@ hosted-git-info@^4.0.1:
dependencies:
lru-cache "^6.0.0"
html-dom-parser@5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/html-dom-parser/-/html-dom-parser-5.1.1.tgz#9efb2cfa055f6a71de1bb2f07c5b019db5004f9e"
integrity sha512-+o4Y4Z0CLuyemeccvGN4bAO20aauB2N9tFEAep5x4OW34kV4PTarBHm6RL02afYt2BMKcr0D2Agep8S3nJPIBg==
dependencies:
domhandler "5.0.3"
htmlparser2 "10.0.0"
html-react-parser@^5.2.5:
version "5.2.5"
resolved "https://registry.yarnpkg.com/html-react-parser/-/html-react-parser-5.2.5.tgz#4a0d62c129d5d5c63cc49f986c946552d737190c"
integrity sha512-bRPdv8KTqG9CEQPMNGksDqmbiRfVQeOidry8pVetdh/1jQ1Edx4KX5m0lWvDD89Pt4CqTYjK1BLz6NoNVxN/Uw==
dependencies:
domhandler "5.0.3"
html-dom-parser "5.1.1"
react-property "2.0.2"
style-to-js "1.1.16"
html-tags@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140"
integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==
htmlparser2@10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-10.0.0.tgz#77ad249037b66bf8cc99c6e286ef73b83aeb621d"
integrity sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==
dependencies:
domelementtype "^2.3.0"
domhandler "^5.0.3"
domutils "^3.2.1"
entities "^6.0.0"
https-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
@ -3789,6 +3869,11 @@ ini@^1.3.4, ini@^1.3.5:
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
inline-style-parser@0.2.4:
version "0.2.4"
resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.4.tgz#f4af5fe72e612839fcd453d989a586566d695f22"
integrity sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==
inquirer@^8.2.1:
version "8.2.1"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.1.tgz#e00022e3e8930a92662f760f020686530a84671d"
@ -5100,6 +5185,11 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-property@2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.2.tgz#d5ac9e244cef564880a610bc8d868bd6f60fdda6"
integrity sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==
react-select@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.2.2.tgz#3d5edf0a60f1276fd5f29f9f90a305f0a25a5189"
@ -5720,6 +5810,20 @@ style-search@^0.1.0:
resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"
integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=
style-to-js@1.1.16:
version "1.1.16"
resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.16.tgz#e6bd6cd29e250bcf8fa5e6591d07ced7575dbe7a"
integrity sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==
dependencies:
style-to-object "1.0.8"
style-to-object@1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.8.tgz#67a29bca47eaa587db18118d68f9d95955e81292"
integrity sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==
dependencies:
inline-style-parser "0.2.4"
stylelint-config-recommended-scss@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-5.0.2.tgz#193f483861c76a36ece24c52eb6baca4838f4a48"