Loading client/src/entry/analysis/modules/CenterFrame.vue +91 −40 Original line number Diff line number Diff line <script setup lang="ts"> import { computed, ref, watch } from "vue"; import { useUserStore } from "@/stores/userStore"; import { withPrefix } from "@/utils/redirect"; import Alert from "@/components/Alert.vue"; import LoadingSpan from "@/components/LoadingSpan.vue"; const emit = defineEmits(["load"]); const props = withDefaults( defineProps<{ id?: string; src?: string; isPreview?: boolean; }>(), { id: "frame", src: "", isPreview: false, } ); const { isAdmin } = useUserStore(); const srcWithRoot = computed(() => withPrefix(props.src)); const sanitizedImport = ref(false); const sanitizedToolId = ref<String | false>(false); const isLoading = ref(true); watch( () => srcWithRoot.value, async () => { sanitizedImport.value = false; sanitizedToolId.value = false; if (props.isPreview) { try { const response = await fetch(srcWithRoot.value, { method: "HEAD" }); const isImported = response.headers.get("x-sanitized-job-imported"); const toolId = response.headers.get("x-sanitized-tool-id"); if (isImported !== null) { sanitizedImport.value = true; } else if (toolId !== null) { sanitizedToolId.value = toolId; } } catch (e) { // I guess that's fine and the center panel will show something console.error(e); } } }, { immediate: true } ); const plainText = "Contents are shown as plain text."; const sanitizedMessage = computed(() => { if (sanitizedImport.value) { return `Dataset has been imported. ${plainText}`; } else if (sanitizedToolId.value) { return `Dataset created by a tool that is not known to create safe HTML. ${plainText}`; } return undefined; }); function onLoad(ev: Event) { isLoading.value = false; const iframe = ev.currentTarget as HTMLIFrameElement; const location = iframe?.contentWindow && iframe.contentWindow.location; try { if (location && location.host && location.pathname != "/") { emit("load"); } } catch (err) { console.warn("CenterFrame - onLoad location access forbidden.", ev, location); } } </script> <template> <div> <Alert v-if="sanitizedMessage" :dismissible="true" variant="warning"> {{ sanitizedMessage }} <p v-if="isAdmin && sanitizedToolId"> <router-link to="/admin/sanitize_allow">Review Allowlist</router-link> if outputs of {{ sanitizedToolId }} are trusted and should be shown as HTML. </p> </Alert> <LoadingSpan v-if="isLoading">Loading ...</LoadingSpan> <iframe :id="id" :name="id" Loading @@ -9,38 +93,5 @@ width="100%" height="100%" @load="onLoad" /> </div> </template> <script> import { withPrefix } from "utils/redirect"; export default { props: { id: { type: String, default: "frame", }, src: { type: String, default: "", }, }, computed: { srcWithRoot() { return withPrefix(this.src); }, }, methods: { onLoad(ev) { const iframe = ev.currentTarget; const location = iframe.contentWindow && iframe.contentWindow.location; try { if (location && location.host && location.pathname != "/") { this.$emit("load"); } } catch (err) { console.warn("CenterFrame - onLoad location access forbidden.", ev, location); } }, }, }; </script> client/src/entry/analysis/router.js +1 −0 Original line number Diff line number Diff line Loading @@ -238,6 +238,7 @@ export function getRouter(Galaxy) { component: CenterFrame, props: (route) => ({ src: `/datasets/${route.params.datasetId}/display/?preview=True`, isPreview: true, }), }, { Loading lib/galaxy/datatypes/data.py +2 −0 Original line number Diff line number Diff line Loading @@ -641,8 +641,10 @@ class Data(metaclass=DataMeta): content_type = "text/html" if from_dataset.creating_job.imported: content_type = "text/plain" headers["x-sanitized-job-imported"] = True if not from_dataset.creating_job.tool_id.startswith(tuple(trans.app.config.sanitize_allowlist)): content_type = "text/plain" headers["x-sanitized-tool-id"] = from_dataset.creating_job.tool_id headers["content-type"] = content_type return open(filename, mode="rb") Loading Loading
client/src/entry/analysis/modules/CenterFrame.vue +91 −40 Original line number Diff line number Diff line <script setup lang="ts"> import { computed, ref, watch } from "vue"; import { useUserStore } from "@/stores/userStore"; import { withPrefix } from "@/utils/redirect"; import Alert from "@/components/Alert.vue"; import LoadingSpan from "@/components/LoadingSpan.vue"; const emit = defineEmits(["load"]); const props = withDefaults( defineProps<{ id?: string; src?: string; isPreview?: boolean; }>(), { id: "frame", src: "", isPreview: false, } ); const { isAdmin } = useUserStore(); const srcWithRoot = computed(() => withPrefix(props.src)); const sanitizedImport = ref(false); const sanitizedToolId = ref<String | false>(false); const isLoading = ref(true); watch( () => srcWithRoot.value, async () => { sanitizedImport.value = false; sanitizedToolId.value = false; if (props.isPreview) { try { const response = await fetch(srcWithRoot.value, { method: "HEAD" }); const isImported = response.headers.get("x-sanitized-job-imported"); const toolId = response.headers.get("x-sanitized-tool-id"); if (isImported !== null) { sanitizedImport.value = true; } else if (toolId !== null) { sanitizedToolId.value = toolId; } } catch (e) { // I guess that's fine and the center panel will show something console.error(e); } } }, { immediate: true } ); const plainText = "Contents are shown as plain text."; const sanitizedMessage = computed(() => { if (sanitizedImport.value) { return `Dataset has been imported. ${plainText}`; } else if (sanitizedToolId.value) { return `Dataset created by a tool that is not known to create safe HTML. ${plainText}`; } return undefined; }); function onLoad(ev: Event) { isLoading.value = false; const iframe = ev.currentTarget as HTMLIFrameElement; const location = iframe?.contentWindow && iframe.contentWindow.location; try { if (location && location.host && location.pathname != "/") { emit("load"); } } catch (err) { console.warn("CenterFrame - onLoad location access forbidden.", ev, location); } } </script> <template> <div> <Alert v-if="sanitizedMessage" :dismissible="true" variant="warning"> {{ sanitizedMessage }} <p v-if="isAdmin && sanitizedToolId"> <router-link to="/admin/sanitize_allow">Review Allowlist</router-link> if outputs of {{ sanitizedToolId }} are trusted and should be shown as HTML. </p> </Alert> <LoadingSpan v-if="isLoading">Loading ...</LoadingSpan> <iframe :id="id" :name="id" Loading @@ -9,38 +93,5 @@ width="100%" height="100%" @load="onLoad" /> </div> </template> <script> import { withPrefix } from "utils/redirect"; export default { props: { id: { type: String, default: "frame", }, src: { type: String, default: "", }, }, computed: { srcWithRoot() { return withPrefix(this.src); }, }, methods: { onLoad(ev) { const iframe = ev.currentTarget; const location = iframe.contentWindow && iframe.contentWindow.location; try { if (location && location.host && location.pathname != "/") { this.$emit("load"); } } catch (err) { console.warn("CenterFrame - onLoad location access forbidden.", ev, location); } }, }, }; </script>
client/src/entry/analysis/router.js +1 −0 Original line number Diff line number Diff line Loading @@ -238,6 +238,7 @@ export function getRouter(Galaxy) { component: CenterFrame, props: (route) => ({ src: `/datasets/${route.params.datasetId}/display/?preview=True`, isPreview: true, }), }, { Loading
lib/galaxy/datatypes/data.py +2 −0 Original line number Diff line number Diff line Loading @@ -641,8 +641,10 @@ class Data(metaclass=DataMeta): content_type = "text/html" if from_dataset.creating_job.imported: content_type = "text/plain" headers["x-sanitized-job-imported"] = True if not from_dataset.creating_job.tool_id.startswith(tuple(trans.app.config.sanitize_allowlist)): content_type = "text/plain" headers["x-sanitized-tool-id"] = from_dataset.creating_job.tool_id headers["content-type"] = content_type return open(filename, mode="rb") Loading