mirror of https://github.com/nextcloud/contacts
Merge pull request #1514 from nextcloud/fixsvgsanitize
Properly sanitize avatars in upload
This commit is contained in:
commit
1d41416dec
|
@ -1204,6 +1204,11 @@
|
|||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@mattkrick/sanitize-svg": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@mattkrick/sanitize-svg/-/sanitize-svg-0.2.1.tgz",
|
||||
"integrity": "sha512-9T5xb8pq0GLNuKmKbXLvILOi1bQeu9FzAup+dB3zWRgzOVh40yE0YqWY/lrKzBrpj968ZaKTxegTwU1zyRtfBA=="
|
||||
},
|
||||
"@nextcloud/auth": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/auth/-/auth-1.2.1.tgz",
|
||||
|
@ -1575,9 +1580,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"acorn": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz",
|
||||
"integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==",
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
|
||||
"integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn-jsx": {
|
||||
|
@ -4800,7 +4805,7 @@
|
|||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz",
|
||||
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz",
|
||||
"integrity": "sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=",
|
||||
"dev": true
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"stylelint:fix": "stylelint src --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mattkrick/sanitize-svg": "^0.2.1",
|
||||
"@nextcloud/auth": "^1.2.1",
|
||||
"@nextcloud/dialogs": "^1.2.1",
|
||||
"@nextcloud/initial-state": "^1.1.0",
|
||||
|
|
|
@ -91,6 +91,7 @@ import { ActionLink, ActionButton } from '@nextcloud/vue'
|
|||
import { getFilePickerBuilder } from '@nextcloud/dialogs'
|
||||
import { generateRemoteUrl } from '@nextcloud/router'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import sanitizeSVG from '@mattkrick/sanitize-svg'
|
||||
|
||||
const axios = () => import('axios')
|
||||
|
||||
|
@ -147,22 +148,92 @@ export default {
|
|||
if (file && file.size && file.size <= 1 * 1024 * 1024) {
|
||||
const reader = new FileReader()
|
||||
const self = this
|
||||
let type = ''
|
||||
|
||||
reader.onload = function(e) {
|
||||
// only getting the raw binary base64
|
||||
self.setPhoto(reader.result.split(',').pop(), file.type)
|
||||
reader.onloadend = async function(e) {
|
||||
try {
|
||||
// We got an ArrayBuffer, checking the true mime type...
|
||||
if (typeof e.target.result === 'object') {
|
||||
const uint = new Uint8Array(e.target.result)
|
||||
const bytes = []
|
||||
uint.forEach((byte) => {
|
||||
bytes.push(byte.toString(16))
|
||||
})
|
||||
const hex = bytes.join('').toUpperCase()
|
||||
|
||||
if (self.getMimetype(hex).startsWith('image/')) {
|
||||
type = self.getMimetype(hex)
|
||||
// we got a valid image, read it again as base64
|
||||
reader.readAsDataURL(file)
|
||||
return
|
||||
}
|
||||
throw new Error('Wrong image mimetype')
|
||||
}
|
||||
|
||||
// else we got the base64 and we're good to go!
|
||||
const imageBase64 = e.target.result.split(',').pop()
|
||||
|
||||
if (e.target.result.indexOf('image/svg') > -1) {
|
||||
const imageSvg = atob(imageBase64)
|
||||
const cleanSvg = await sanitizeSVG(imageSvg)
|
||||
if (!cleanSvg) {
|
||||
throw new Error('Unsafe svg image')
|
||||
}
|
||||
}
|
||||
|
||||
// All is well! Set the photo
|
||||
self.setPhoto(imageBase64, type)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
OC.Notification.showTemporary(t('contacts', 'Invalid image'))
|
||||
} finally {
|
||||
self.resetPicker()
|
||||
}
|
||||
}
|
||||
|
||||
reader.readAsDataURL(file)
|
||||
// start by reading the magic bytes to detect proper photo mimetype
|
||||
const blob = file.slice(0, 4)
|
||||
reader.readAsArrayBuffer(blob)
|
||||
} else {
|
||||
OC.Notification.showTemporary(t('contacts', 'Image is too big (max 1MB).'))
|
||||
// reset input
|
||||
event.target.value = ''
|
||||
this.loading = false
|
||||
this.resetPicker()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset image pciker input
|
||||
*/
|
||||
resetPicker() {
|
||||
// reset input
|
||||
this.$refs.uploadInput.value = ''
|
||||
this.loading = false
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the mimetype based on the first magix byte
|
||||
*
|
||||
* @param {string} signature the first 4 bytes
|
||||
* @returns {string} the mimetype
|
||||
*/
|
||||
getMimetype(signature) {
|
||||
switch (signature) {
|
||||
case '89504E47':
|
||||
return 'image/png'
|
||||
case '47494638':
|
||||
return 'image/gif'
|
||||
case '3C3F786D':
|
||||
case '3C737667':
|
||||
return 'image/svg+xml'
|
||||
case 'FFD8FFDB':
|
||||
case 'FFD8FFE0':
|
||||
case 'FFD8FFE1':
|
||||
return 'image/jpeg'
|
||||
default:
|
||||
return 'application/octet-stream'
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the contact photo
|
||||
*
|
||||
|
@ -269,6 +340,7 @@ export default {
|
|||
this.updateHeightWidth(this.$refs.img.naturalHeight, this.$refs.img.naturalWidth)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the current height and width data
|
||||
* based on the viewer maximum size
|
||||
|
|
Loading…
Reference in New Issue