details: https://code.tryton.org/tryton/commit/e4a4914c5bcc
branch: 7.0
user: Cédric Krier <[email protected]>
date: Mon Oct 20 10:10:07 2025 +0200
description:
Use sandboxed iframe to display document
The HTML element used to display the document is based on the mimetype.
And by default a sandboxed iframe is used to isolate the unsafe content
from
the parent context.
Closes #14290
(grafted from 7909b26211575e6b5dc02156dd1327bb73c55673)
diffstat:
sao/CHANGELOG | 1 +
sao/src/sao.less | 7 ++++-
sao/src/view/form.js | 65 +++++++++++++++++++++++++++++++++++----------------
3 files changed, 51 insertions(+), 22 deletions(-)
diffs (125 lines):
diff -r 179ccfc6acaa -r e4a4914c5bcc sao/CHANGELOG
--- a/sao/CHANGELOG Thu Sep 25 17:32:00 2025 +0200
+++ b/sao/CHANGELOG Mon Oct 20 10:10:07 2025 +0200
@@ -1,3 +1,4 @@
+* Use sandboxed iframe to display document (issue14290)
Version 7.0.37 - 2025-10-02
---------------------------
diff -r 179ccfc6acaa -r e4a4914c5bcc sao/src/sao.less
--- a/sao/src/sao.less Thu Sep 25 17:32:00 2025 +0200
+++ b/sao/src/sao.less Mon Oct 20 10:10:07 2025 +0200
@@ -761,9 +761,14 @@
}
}
.form-document {
- object {
+ object, img {
object-fit: scale-down;
object-position: center top;
+ }
+ iframe {
+ border: 0;
+ }
+ object, iframe, img {
width: 100%;
height: 75vh;
@media screen and (max-width: @screen-sm-max) {
diff -r 179ccfc6acaa -r e4a4914c5bcc sao/src/view/form.js
--- a/sao/src/view/form.js Thu Sep 25 17:32:00 2025 +0200
+++ b/sao/src/view/form.js Mon Oct 20 10:10:07 2025 +0200
@@ -4510,15 +4510,42 @@
'class': this.class_,
});
- this.object = jQuery('<object/>', {
+ this.content = this._create_content().appendTo(this.el);
+ },
+ _create_content: function(mimetype, url) {
+ let tag_name = 'iframe';
+ if (mimetype) {
+ if (mimetype.startsWith('image/')) {
+ tag_name = 'img';
+ } else if (mimetype == 'application/pdf') {
+ tag_name = 'object';
+ }
+ }
+ let content = jQuery(`<${tag_name}/>`, {
'class': 'center-block',
- }).appendTo(this.el);
- if (attributes.height) {
- this.object.css('height', parseInt(attributes.height, 10));
- }
- if (attributes.width) {
- this.object.css('width', parseInt(attributes.width, 10));
- }
+ });
+ if (tag_name == 'iframe') {
+ content.attr('sandbox', '');
+ }
+ if (this.attributes.height) {
+ content.css('height', parseInt(this.attributes.height, 10));
+ }
+ if (this.attributes.width) {
+ content.css('width', parseInt(this.attributes.width, 10));
+ }
+ if (url) {
+ // set onload before data/src to be always called
+ content.get().onload = function() {
+ this.onload = null;
+ window.URL.revokeObjectURL(url);
+ };
+ if (tag_name== 'object') {
+ content.attr('data', url);
+ } else {
+ content.attr('src', url);
+ }
+ }
+ return content;
},
display: function() {
Sao.View.Form.Document._super.display.call(this);
@@ -4534,34 +4561,30 @@
filename = filename_field.get_client(record);
}
data.done(data => {
- var url, blob;
if (record !== this.record) {
return;
}
// in case onload was not yet triggered
- window.URL.revokeObjectURL(this.object.attr('data'));
+ let url = this.content.attr('data') ||
+ this.content.attr('src');
+ window.URL.revokeObjectURL(url);
+ let mimetype;
if (!data) {
url = null;
} else {
- var mimetype = Sao.common.guess_mimetype(filename);
+ mimetype = Sao.common.guess_mimetype(filename);
if (mimetype == 'application/octet-binary') {
mimetype = null;
}
- blob = new Blob([data], {
+ let blob = new Blob([data], {
'type': mimetype,
});
url = window.URL.createObjectURL(blob);
}
// duplicate object to force refresh on buggy browsers
- const object = this.object.clone();
- // set onload before data to be always called
- object.get(0).onload = function() {
- this.onload = null;
- window.URL.revokeObjectURL(url);
- };
- object.attr('data', url);
- this.object.replaceWith(object);
- this.object = object;
+ let content = this._create_content(mimetype, url);
+ this.content.replaceWith(content);
+ this.content = content;
});
},
});