fix blob: URLS, corractly calculate image preview viwport size

This commit is contained in:
or-else
2019-08-17 20:07:15 +03:00
parent c14fb857bc
commit 1b4bba051e
10 changed files with 95 additions and 34 deletions
+26 -1
View File
@@ -91,7 +91,7 @@ export function isUrlRelative(url) {
return !/^\s*([a-z][a-z0-9+.-]*:|\/\/)/im.test(url);
}
// Ensure URL is valid and does not present an XSS risk. Optional allowedSchemes may contain an array of
// Ensure URL does not present an XSS risk. Optional allowedSchemes may contain an array of
// strings with permitted URL schemes, such as ['ftp', 'ftps']; otherwise accept http and https only.
export function sanitizeUrl(url, allowedSchemes) {
if (!url) {
@@ -105,6 +105,10 @@ export function sanitizeUrl(url, allowedSchemes) {
if (!/^([a-z][a-z0-9+.-]*:|\/\/)/i.test(url)) {
return url;
}
// Blob URLs are safe.
if (/^blob:http/.test(url)) {
return url;
}
// Absolute URL. Accept only safe schemes, or no scheme.
const schemes = Array.isArray(allowedSchemes) ? allowedSchemes.join('|') : 'http|https';
@@ -115,3 +119,24 @@ export function sanitizeUrl(url, allowedSchemes) {
return url;
}
// Ensure URL is suitable for <img src="url"> field: the URL must be a relative URL or
// have http:, https:, or data: scheme. In case of data: scheme, the URL must start with
// a 'data:image/XXXX;base64,'.
export function sanitizeImageUrl(url) {
if (!url) {
return null;
}
const sanitizedUrl = sanitizeUrl(url);
if (sanitizedUrl) {
return sanitizedUrl;
}
// Is this a data: URL of an image?
if (/data:image\/[a-z0-9.-]+;base64,/i.test(url.trim())) {
return url;
}
return null;
}
-1
View File
@@ -496,7 +496,6 @@ class TinodeWeb extends React.Component {
credCode: undefined,
myUserId: this.tinode.getCurrentUserID()
});
console.log("handleLoginSuccessful");
// Subscribe, fetch topic desc, the list of subscriptions. Messages are not fetched.
me.subscribe(
me.startMetaQuery().
+2 -2
View File
@@ -6,7 +6,7 @@ import { Drafty } from 'tinode-sdk'
import Attachment from './attachment.jsx';
import LetterTile from './letter-tile.jsx';
import ReceivedMarker from './received-marker.jsx'
import { sanitizeUrl } from '../lib/utils.js';
import { sanitizeImageUrl, sanitizeUrl } from '../lib/utils.js';
export default class ChatMessage extends React.Component {
constructor(props) {
@@ -164,7 +164,7 @@ function draftyFormatter(style, data, values, key) {
Math.min(this.props.viewportWidth - REM_SIZE * 4, REM_SIZE * 36), REM_SIZE * 24, false);
dim = dim || {dstWidth: BROKEN_IMAGE_SIZE, dstHeight: BROKEN_IMAGE_SIZE};
attr.style = { width: dim.dstWidth + 'px', height: dim.dstHeight + 'px' };
attr.src = sanitizeUrl(attr.src);
attr.src = sanitizeImageUrl(attr.src);
if (attr.src) {
attr.onClick = this.handlePreviewImage;
attr.className += ' image-clickable';
+17 -10
View File
@@ -9,21 +9,27 @@ export default class ImagePreview extends React.PureComponent {
constructor(props) {
super(props);
this.state = {};
this.state = {
width: 0,
height: 0
};
}
componentDidMount() {
this.setState({
width: this.container.clientWidth,
height: this.container.clientHeight
});
assignWidth(node) {
if (node && !this.state.width) {
const bounds = node.getBoundingClientRect();
this.setState({
width: bounds.width | 0,
height: bounds.height | 0
});
}
}
render() {
if (!this.props.content) {
return null;
}
const instance = this;
const dim = fitImageSize(this.props.content.width, this.props.content.height,
this.state.width, this.state.height, false);
const size = dim ? { width: dim.dstWidth + 'px', height: dim.dstHeight + 'px' } :
@@ -32,9 +38,10 @@ export default class ImagePreview extends React.PureComponent {
size.maxHeight = '100%';
let filename = this.props.content.filename;
const maxlength = (this.props.content.width / REM_SIZE) | 0;
// Averate font aspect ratio is ~0.5; File name takes 1/3 of the viewport width.
const maxlength = Math.max(((this.state.width / REM_SIZE / 1.5) | 0) - 2, 12);
if (filename.length > maxlength) {
filename = filename.slice(0, maxlength-2) + '...' + filename.slice(2-maxlength);
filename = filename.slice(0, maxlength/2 - 1) + '...' + filename.slice(1 - maxlength/2);
}
const width = this.props.content.width || '-';
const height = this.props.content.height || '-';
@@ -47,7 +54,7 @@ export default class ImagePreview extends React.PureComponent {
</a>
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onClose();}}><i className="material-icons gray">close</i></a>
</div>
<div id="image-preview-container" ref={function(ref) {instance.container = ref;}}>
<div id="image-preview-container" ref={(node) => this.assignWidth(node)}>
<img src={this.props.content.url} style={size} />
</div>
<div id="image-preview-footer">