fix/vue-quill-xss_CVE-2021-3163 #114

Merged
jkeffects merged 4 commits from fix/vue-quill-xss_CVE-2021-3163 into develop 2025-07-18 13:53:51 +00:00
8 changed files with 34 additions and 24 deletions
Showing only changes of commit 08c3698dd8 - Show all commits

View file

@ -4,7 +4,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { defineComponent, type PropType } from "vue"; import { defineComponent, type PropType } from "vue";
import Quill, { Delta } from "quill"; import Quill, { Delta, type QuillOptions } from "quill";
import "quill/dist/quill.core.css"; import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css"; import "quill/dist/quill.snow.css";
@ -14,21 +14,24 @@ type RangeStatic = { index: number; length: number };
<script lang="ts"> <script lang="ts">
export default defineComponent({ export default defineComponent({
props: { props: {
options: {
type: Object as PropType<QuillOptions>,
default: {},
},
toolbar: { toolbar: {
type: [String, Array, Object], type: [String, Array, Object],
default: "", default: "",
}, },
modules: {
type: Object as PropType<Record<string, unknown>>,
default: {},
},
content: { content: {
type: [String, Object] as PropType<string | Delta>, type: [String, Object] as PropType<string | Delta | null>,
default: "", default: "",
}, },
contentType: { contentType: {
type: String as PropType<"delta" | "html" | "text">, type: String as PropType<"delta" | "html" | "text">,
default: "text", default: "text",
validator: (value: string) => {
return ["delta", "html", "text"].includes(value);
},
}, },
readonly: { type: Boolean, default: false }, readonly: { type: Boolean, default: false },
placeholder: String, placeholder: String,
@ -51,6 +54,16 @@ export default defineComponent({
instance: undefined as undefined | Quill, instance: undefined as undefined | Quill,
}; };
}, },
computed: {
value: {
get(): string | Delta {
return this.content ?? "";
},
set(val: string | Delta) {
this.$emit("update:content", val);
},
},
},
mounted() { mounted() {
this.instance = new Quill(this.$refs.quill as HTMLElement, { this.instance = new Quill(this.$refs.quill as HTMLElement, {
theme: "snow", theme: "snow",
@ -59,6 +72,7 @@ export default defineComponent({
}, },
placeholder: this.placeholder, placeholder: this.placeholder,
readOnly: this.readonly, readOnly: this.readonly,
...this.options,
}); });
this.instance.on("selection-change", (range, oldRange, source) => { this.instance.on("selection-change", (range, oldRange, source) => {
@ -70,12 +84,12 @@ export default defineComponent({
this.$emit("selectionChange", { range, oldRange, source }); this.$emit("selectionChange", { range, oldRange, source });
}); });
this.instance.on("text-change", (delta, oldDelta, source) => { this.instance.on("text-change", (delta, oldDelta, source) => {
this.$emit("update:content", this.getContent()); this.value = this.getContent();
this.$emit("textChange", { delta, oldDelta, source }); this.$emit("textChange", { delta, oldDelta, source });
}); });
this.$emit("ready", this.instance as Quill); this.$emit("ready", this.instance as Quill);
this.setContent(this.content); this.setContent(this.value);
}, },
beforeUnmount() { beforeUnmount() {
this.instance = undefined; this.instance = undefined;
@ -91,6 +105,7 @@ export default defineComponent({
} }
}, },
setContent(content: Delta | string) { setContent(content: Delta | string) {
if (content == "") return;
if (this.contentType === "delta") { if (this.contentType === "delta") {
if (typeof content !== "string") { if (typeof content !== "string") {
this.instance?.setContents(content); this.instance?.setContents(content);
@ -98,6 +113,9 @@ export default defineComponent({
} else if (this.contentType === "html") { } else if (this.contentType === "html") {
if (typeof content === "string" && this.instance) { if (typeof content === "string" && this.instance) {
this.instance.root.innerHTML = content; this.instance.root.innerHTML = content;
// this.instance.clipboard.dangerouslyPasteHTML(content);
// let delta = this.instance.clipboard.convert({ html: content });
// this.instance?.setContents(delta);
} }
} else { } else {
if (typeof content === "string") { if (typeof content === "string") {

View file

@ -52,8 +52,7 @@ import { defineComponent } from "vue";
import { mapActions, mapState, mapWritableState } from "pinia"; import { mapActions, mapState, mapWritableState } from "pinia";
import Spinner from "@/components/Spinner.vue"; import Spinner from "@/components/Spinner.vue";
import { useNewsletterStore } from "@/stores/admin/club/newsletter/newsletter"; import { useNewsletterStore } from "@/stores/admin/club/newsletter/newsletter";
import { QuillEditor } from "@vueup/vue-quill"; import QuillEditor from "@/components/QuillEditor.vue";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
import { toolbarOptions } from "@/helpers/quillConfig"; import { toolbarOptions } from "@/helpers/quillConfig";
import { useAbilityStore } from "@/stores/ability"; import { useAbilityStore } from "@/stores/ability";
</script> </script>

View file

@ -107,8 +107,7 @@
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { mapActions, mapState, mapWritableState } from "pinia"; import { mapActions, mapState, mapWritableState } from "pinia";
import Spinner from "@/components/Spinner.vue"; import Spinner from "@/components/Spinner.vue";
import { QuillEditor } from "@vueup/vue-quill"; import QuillEditor from "@/components/QuillEditor.vue";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
import { toolbarOptions } from "@/helpers/quillConfig"; import { toolbarOptions } from "@/helpers/quillConfig";
import { useNewsletterDatesStore } from "@/stores/admin/club/newsletter/newsletterDates"; import { useNewsletterDatesStore } from "@/stores/admin/club/newsletter/newsletterDates";
import { useAbilityStore } from "@/stores/ability"; import { useAbilityStore } from "@/stores/ability";

View file

@ -18,13 +18,12 @@
<label for="summary">Zusammenfassung</label> <label for="summary">Zusammenfassung</label>
<QuillEditor <QuillEditor
id="summary" id="summary"
theme="snow"
placeholder="Zusammenfassung zum Newsletter..." placeholder="Zusammenfassung zum Newsletter..."
style="height: 250px; max-height: 250px; min-height: 250px" style="height: 250px; max-height: 250px; min-height: 250px"
contentType="html" contentType="html"
:toolbar="toolbarOptions" :toolbar="toolbarOptions"
v-model:content="activeNewsletterObj.description" v-model:content="activeNewsletterObj.description"
:enable="can('create', 'club', 'newsletter')" :readonly="!can('create', 'club', 'newsletter')"
:style="!can('create', 'club', 'newsletter') ? 'opacity: 75%; background: rgb(243 244 246)' : ''" :style="!can('create', 'club', 'newsletter') ? 'opacity: 75%; background: rgb(243 244 246)' : ''"
/> />
</div> </div>
@ -42,8 +41,7 @@ import { defineComponent } from "vue";
import { mapActions, mapState, mapWritableState } from "pinia"; import { mapActions, mapState, mapWritableState } from "pinia";
import Spinner from "@/components/Spinner.vue"; import Spinner from "@/components/Spinner.vue";
import { useNewsletterStore } from "@/stores/admin/club/newsletter/newsletter"; import { useNewsletterStore } from "@/stores/admin/club/newsletter/newsletter";
import { QuillEditor } from "@vueup/vue-quill"; import QuillEditor from "@/components/QuillEditor.vue";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
import { toolbarOptions } from "@/helpers/quillConfig"; import { toolbarOptions } from "@/helpers/quillConfig";
import { useAbilityStore } from "@/stores/ability"; import { useAbilityStore } from "@/stores/ability";
</script> </script>

View file

@ -78,8 +78,7 @@
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { mapActions, mapState, mapWritableState } from "pinia"; import { mapActions, mapState, mapWritableState } from "pinia";
import Spinner from "@/components/Spinner.vue"; import Spinner from "@/components/Spinner.vue";
import { QuillEditor } from "@vueup/vue-quill"; import QuillEditor from "@/components/QuillEditor.vue";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
import { toolbarOptions } from "@/helpers/quillConfig"; import { toolbarOptions } from "@/helpers/quillConfig";
import { useProtocolAgendaStore } from "@/stores/admin/club/protocol/protocolAgenda"; import { useProtocolAgendaStore } from "@/stores/admin/club/protocol/protocolAgenda";
import { useAbilityStore } from "@/stores/ability"; import { useAbilityStore } from "@/stores/ability";

View file

@ -78,8 +78,7 @@
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { mapActions, mapState, mapWritableState } from "pinia"; import { mapActions, mapState, mapWritableState } from "pinia";
import Spinner from "@/components/Spinner.vue"; import Spinner from "@/components/Spinner.vue";
import { QuillEditor } from "@vueup/vue-quill"; import QuillEditor from "@/components/QuillEditor.vue";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
import { toolbarOptions } from "@/helpers/quillConfig"; import { toolbarOptions } from "@/helpers/quillConfig";
import { useProtocolDecisionStore } from "@/stores/admin/club/protocol/protocolDecision"; import { useProtocolDecisionStore } from "@/stores/admin/club/protocol/protocolDecision";
import { useAbilityStore } from "@/stores/ability"; import { useAbilityStore } from "@/stores/ability";

View file

@ -64,8 +64,7 @@ import { defineComponent } from "vue";
import { mapActions, mapState, mapWritableState } from "pinia"; import { mapActions, mapState, mapWritableState } from "pinia";
import Spinner from "@/components/Spinner.vue"; import Spinner from "@/components/Spinner.vue";
import { useProtocolStore } from "@/stores/admin/club/protocol/protocol"; import { useProtocolStore } from "@/stores/admin/club/protocol/protocol";
import { QuillEditor } from "@vueup/vue-quill"; import QuillEditor from "@/components/QuillEditor.vue";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
import { toolbarOptions } from "@/helpers/quillConfig"; import { toolbarOptions } from "@/helpers/quillConfig";
import { useAbilityStore } from "@/stores/ability"; import { useAbilityStore } from "@/stores/ability";
</script> </script>

View file

@ -95,8 +95,7 @@
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { mapActions, mapState, mapWritableState } from "pinia"; import { mapActions, mapState, mapWritableState } from "pinia";
import Spinner from "@/components/Spinner.vue"; import Spinner from "@/components/Spinner.vue";
import { QuillEditor } from "@vueup/vue-quill"; import QuillEditor from "@/components/QuillEditor.vue";
import "@vueup/vue-quill/dist/vue-quill.snow.css";
import { toolbarOptions } from "@/helpers/quillConfig"; import { toolbarOptions } from "@/helpers/quillConfig";
import { useProtocolVotingStore } from "@/stores/admin/club/protocol/protocolVoting"; import { useProtocolVotingStore } from "@/stores/admin/club/protocol/protocolVoting";
import { useAbilityStore } from "@/stores/ability"; import { useAbilityStore } from "@/stores/ability";