2017-08-25 16:42:30 +00:00
|
|
|
import gm from 'gm'
|
2017-11-23 16:18:26 +00:00
|
|
|
import url from 'url'
|
2017-08-25 16:42:30 +00:00
|
|
|
import path from 'path'
|
2017-12-01 11:35:47 +00:00
|
|
|
import uuid from 'uuid/v4'
|
2017-08-25 16:42:30 +00:00
|
|
|
|
2017-11-23 16:18:26 +00:00
|
|
|
import http from '../../scripts/http'
|
|
|
|
|
2017-11-30 21:13:14 +00:00
|
|
|
const fs = require('fs-extra')
|
2017-08-25 16:42:30 +00:00
|
|
|
|
|
|
|
const uploads = path.join(__dirname, '../../', 'usercontent')
|
2017-09-09 11:15:11 +00:00
|
|
|
const images = path.join(uploads, 'images')
|
2017-08-25 16:42:30 +00:00
|
|
|
const maxFileSize = 1000000
|
|
|
|
const imageTypes = {
|
|
|
|
'image/png': '.png',
|
|
|
|
'image/jpg': '.jpg',
|
|
|
|
'image/jpeg': '.jpeg'
|
|
|
|
}
|
|
|
|
|
2017-09-09 11:15:11 +00:00
|
|
|
function decodeBase64Image (dataString) {
|
|
|
|
let matches = dataString.match(/^data:([A-Za-z-+/]+);base64,(.+)$/)
|
|
|
|
let response = {}
|
|
|
|
|
|
|
|
if (matches.length !== 3) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
response.type = matches[1]
|
|
|
|
response.data = Buffer.from(matches[2], 'base64')
|
|
|
|
|
|
|
|
return response
|
|
|
|
}
|
|
|
|
|
2017-08-25 16:42:30 +00:00
|
|
|
function saneFields (fields) {
|
|
|
|
let out = {}
|
|
|
|
|
|
|
|
for (let i in fields) {
|
|
|
|
let entry = fields[i]
|
|
|
|
if (typeof entry === 'object' && entry.length === 1 && !isNaN(parseInt(entry[0]))) {
|
|
|
|
out[i] = parseInt(entry[0])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
async function bailOut (file, error) {
|
2017-11-30 21:13:14 +00:00
|
|
|
await fs.unlink(file)
|
|
|
|
throw new Error(error)
|
2017-08-25 16:42:30 +00:00
|
|
|
}
|
|
|
|
|
2017-09-09 11:15:11 +00:00
|
|
|
async function imageBase64 (baseObj) {
|
|
|
|
if (!baseObj) return null
|
|
|
|
let imgData = decodeBase64Image(baseObj)
|
|
|
|
|
|
|
|
if (!imgData) return null
|
|
|
|
if (!imageTypes[imgData.type]) return null
|
|
|
|
|
2017-12-01 11:35:47 +00:00
|
|
|
let imageName = 'base64-' + uuid()
|
2017-09-09 11:15:11 +00:00
|
|
|
let ext = imageTypes[imgData.type] || '.png'
|
|
|
|
|
|
|
|
imageName += ext
|
|
|
|
|
|
|
|
let fpath = path.join(images, imageName)
|
|
|
|
|
|
|
|
try {
|
2017-11-30 21:13:14 +00:00
|
|
|
await fs.writeFile(fpath, imgData.data)
|
2017-09-09 11:15:11 +00:00
|
|
|
} catch (e) {
|
|
|
|
console.error(e)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
return {file: fpath}
|
|
|
|
}
|
|
|
|
|
2017-11-23 16:18:26 +00:00
|
|
|
async function downloadImage (imgUrl, designation) {
|
|
|
|
if (!imgUrl) return null
|
|
|
|
if (!designation) designation = 'download'
|
|
|
|
|
2017-12-01 11:35:47 +00:00
|
|
|
let imageName = designation + '-' + uuid()
|
2017-11-23 16:18:26 +00:00
|
|
|
let uridata = url.parse(imgUrl)
|
|
|
|
let pathdata = path.parse(uridata.path)
|
|
|
|
|
|
|
|
imageName += pathdata.ext || '.png'
|
|
|
|
|
|
|
|
try {
|
|
|
|
await http.Download(imgUrl, path.join(images, imageName))
|
|
|
|
} catch (e) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2017-11-30 21:13:14 +00:00
|
|
|
return imageName
|
2017-11-23 16:18:26 +00:00
|
|
|
}
|
|
|
|
|
2017-09-01 12:02:20 +00:00
|
|
|
async function uploadImage (identifier, fields, files) {
|
2017-11-30 21:13:14 +00:00
|
|
|
if (!files.image) throw new Error('No image file')
|
2017-08-25 16:42:30 +00:00
|
|
|
|
|
|
|
let file = files.image[0]
|
|
|
|
if (file.size > maxFileSize) return bailOut(file.path, 'Image is too large! 1 MB max')
|
|
|
|
|
|
|
|
fields = saneFields(fields)
|
|
|
|
|
|
|
|
// Get file info, generate a file name
|
|
|
|
let fileHash = crypto.randomBytes(12).toString('hex')
|
|
|
|
let contentType = file.headers['content-type']
|
|
|
|
if (!contentType) return bailOut(file.path, 'Invalid of missing content-type header')
|
|
|
|
|
|
|
|
file = file.path
|
|
|
|
|
|
|
|
// Make sure content type is allowed
|
|
|
|
let match = false
|
|
|
|
for (let i in imageTypes) {
|
|
|
|
if (i === contentType) {
|
|
|
|
match = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!match) return bailOut(file, 'Invalid image type. Only PNG, JPG and JPEG files are allowed.')
|
|
|
|
let extension = imageTypes[contentType]
|
2017-09-01 12:02:20 +00:00
|
|
|
let fileName = identifier + '-' + fileHash + extension
|
2017-08-25 16:42:30 +00:00
|
|
|
|
|
|
|
// Check for cropping
|
|
|
|
if (fields.x == null || fields.y == null || fields.width == null || fields.height == null) {
|
|
|
|
return bailOut(file, 'Images can only be cropped on the server side due to security reasons.')
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fields.x < 0 || fields.y < 0 || fields.x > fields.width + fields.x || fields.y > fields.height + fields.y) {
|
|
|
|
return bailOut(file, 'Impossible crop.')
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check 1 : 1 aspect ratio
|
|
|
|
if (Math.floor(fields.width / fields.height) !== 1) {
|
|
|
|
return bailOut(file, 'Avatars can only have an aspect ratio of 1:1')
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fields.scaleX) {
|
|
|
|
fields.x *= fields.scaleX
|
|
|
|
fields.width *= fields.scaleX
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fields.scaleY) {
|
|
|
|
fields.y *= fields.scaleY
|
|
|
|
fields.height *= fields.scaleY
|
|
|
|
}
|
|
|
|
|
|
|
|
// Crop
|
|
|
|
try {
|
|
|
|
await new Promise(function (resolve, reject) {
|
|
|
|
gm(file)
|
|
|
|
.crop(fields.width, fields.height, fields.x, fields.y)
|
2017-09-09 11:15:11 +00:00
|
|
|
.write(path.join(images, fileName), (err) => {
|
2017-08-25 16:42:30 +00:00
|
|
|
if (err) return reject(err)
|
|
|
|
resolve(fileName)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2017-11-30 21:13:14 +00:00
|
|
|
await fs.unlink(file)
|
2017-08-25 16:42:30 +00:00
|
|
|
} catch (e) {
|
|
|
|
console.error(e)
|
|
|
|
return bailOut(file, 'An error occured while cropping.')
|
|
|
|
}
|
|
|
|
|
|
|
|
return {file: fileName}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
2017-11-23 16:18:26 +00:00
|
|
|
downloadImage: downloadImage,
|
2017-09-09 11:15:11 +00:00
|
|
|
uploadImage: uploadImage,
|
|
|
|
imageBase64: imageBase64,
|
|
|
|
types: imageTypes
|
2017-08-25 16:42:30 +00:00
|
|
|
}
|