Merge pull request #1514 from nextcloud/fixsvgsanitize

Properly sanitize avatars in upload
This commit is contained in:
John Molakvoæ 2020-03-13 09:03:52 +01:00 committed by GitHub
commit 1d41416dec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 89 additions and 11 deletions

13
package-lock.json generated
View File

@ -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
}

View File

@ -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",

View File

@ -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