diff --git a/package.json b/package.json index 5d72d8e..fa2bd12 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/ts/Common/Translation.ts b/ts/Common/Translation.ts index 8d78cf7..38b5939 100644 --- a/ts/Common/Translation.ts +++ b/ts/Common/Translation.ts @@ -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 } })); +} diff --git a/ts/Manager/EditRoomDialog.tsx b/ts/Manager/EditRoomDialog.tsx index ddf184f..d7307d5 100644 --- a/ts/Manager/EditRoomDialog.tsx +++ b/ts/Manager/EditRoomDialog.tsx @@ -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 :
- Public: Anyone who has the link can join.-
Internal: Only Nextcloud users can join.-
Password: Only guests who have the password can join..-
Waiting room: A moderator must accept each guest before they can join.-
Restricted : Only selected users and groups can access this room.'), + access: t('bbb', 'Explanation of the different concepts that constitute access options :
- Public: Anyone who has the link can join.
- Internal: Only Nextcloud users can join.
- Password: Only guests who have the password can join.
- Waiting room: A moderator must accept each guest before they can join.
- 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 = ({ room, restriction, updateProperty, op updateProperty(field, value)} min={minParticipantsLimit} max={maxParticipantsLimit} /> - {descriptions[field] && {descriptions[field]}} + {descriptions[field] && {html_sanitize_and_parse(descriptions[field])}} ); } @@ -90,7 +90,7 @@ const EditRoomDialog: React.FC = ({ room, restriction, updateProperty, op })} {(value === Access.Password && room.password) && } - {descriptions[field] && {descriptions[field]}} + {descriptions[field] && {html_sanitize_and_parse(descriptions[field])}} ); } @@ -103,7 +103,7 @@ const EditRoomDialog: React.FC = ({ room, restriction, updateProperty, op } return ( - setOpen(false)} title={t('bbb', 'Edit "{room}"', { room: room.name })}> + setOpen(false)} title={html_sanitize_and_parse(t('bbb', 'Edit "{room}"', { room: room.name }))}>

{t('bbb', 'Room URL')}

@@ -132,7 +132,7 @@ const EditRoomDialog: React.FC = ({ room, restriction, updateProperty, op {room.access === Access.InternalRestricted &&
- {t('bbb', 'Access') + ' : ' + descriptions.internalRestrictedShareWith} + {t('bbb', 'Access') + ' : ' + html_sanitize_and_parse(descriptions.internalRestrictedShareWith)}
} @@ -144,7 +144,7 @@ const EditRoomDialog: React.FC = ({ room, restriction, updateProperty, op onChange={(event) => updateProperty('everyoneIsModerator', event.target.checked)} />
- {descriptions.moderator} + {html_sanitize_and_parse(descriptions.moderator)}
= ({ room, restriction, updateProperty, op
{!!room.moderatorToken && } - {descriptions.moderatorToken} + {html_sanitize_and_parse(descriptions.moderatorToken)}
@@ -170,7 +170,7 @@ const EditRoomDialog: React.FC = ({ room, restriction, updateProperty, op onChange={(event) => updateProperty('record', event.target.checked)} />
-

{descriptions.recording}

+

{html_sanitize_and_parse(descriptions.recording)}

@@ -181,7 +181,7 @@ const EditRoomDialog: React.FC = ({ room, restriction, updateProperty, op onChange={(event) => updateProperty('requireModerator', event.target.checked)} />
-

{descriptions.requireModerator}

+

{html_sanitize_and_parse(descriptions.requireModerator)}

@@ -192,7 +192,7 @@ const EditRoomDialog: React.FC = ({ room, restriction, updateProperty, op onChange={(event) => updateProperty('listenOnly', event.target.checked)} />
-

{descriptions.listenOnly}

+

{html_sanitize_and_parse(descriptions.listenOnly)}

@@ -203,7 +203,7 @@ const EditRoomDialog: React.FC = ({ room, restriction, updateProperty, op onChange={(event) => updateProperty('mediaCheck', !event.target.checked)} />
-

{descriptions.mediaCheck}

+

{html_sanitize_and_parse(descriptions.mediaCheck)}

@@ -214,7 +214,7 @@ const EditRoomDialog: React.FC = ({ room, restriction, updateProperty, op onChange={(event) => updateProperty('cleanLayout', event.target.checked)} />
-

{descriptions.cleanLayout}

+

{html_sanitize_and_parse(descriptions.cleanLayout)}

@@ -225,7 +225,7 @@ const EditRoomDialog: React.FC = ({ room, restriction, updateProperty, op onChange={(event) => updateProperty('joinMuted', event.target.checked)} />
-

{descriptions.joinMuted}

+

{html_sanitize_and_parse(descriptions.joinMuted)}

diff --git a/yarn.lock b/yarn.lock index afde959..a94bcb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"