<commit_before>import crypto from 'crypto';
import winston from 'winston';
import buildAvatarURLForSize from '../../lib/buildAvatarURLForSize';
import EnhancedError from '../../lib/EnhancedError';
import { APP_PRIORITIES, TYPE_USER } from '../../lib/Constants';
import { bus, config } from '../../core-server';
import { urlTos3 } from '../../lib/upload';

function getDate({ long } = {}) {
	const date = new Date();

	if (long) {
		return date.toISOString().replace(/[\-:]/g, '').replace(/\.\d+/g, '');
	} else {
		return date.getUTCFullYear().toString() +
		('0' + date.getUTCMonth().toString()).substr(-2) +
		('0' + date.getUTCDate().toString()).substr(-2);
	}
}

function getExpiration() {
	const validity = config.s3.validity;
	return (new Date(Date.now() + validity)).toISOString();
}

// For old bucket
function getOldExpiration() {
	const validity = config.oldS3.validity;
	return (new Date(Date.now() + validity)).toISOString();
}

function sign(key, data) {
	return crypto.createHmac('sha256', key).update(data).digest();
}

function getKeyPrefix(userId, uploadType, contentId) {
	switch (uploadType) {
	case 'avatar':
		return `a/${userId}/`;
	case 'content':
		return `c/${userId}/${contentId}/`;
	default:
		throw new Error('Invalid upload type specified: ' + uploadType);
	}
}

// For old bucket
function getOldKeyPrefix(userId, uploadType, textId) {
	switch (uploadType) {
	case 'content':
		return 'uploaded/' + uploadType + '/' + userId + '/' + textId + '/';
	default:
		throw new Error('Invalid upload type specified: ' + uploadType);
	}
}

function getCredential() {
	return `${config.s3.accessKey}/${getDate()}/${config.s3.uploadRegion}/${config.s3.service}/${config.s3.signatureVersion}`;
}

// For old bucket
function getOldCredential() {
	return `${config.oldS3.accessKey}/${getDate()}/${config.oldS3.region}/${config.oldS3.service}/${config.oldS3.signatureVersion}`;
}

function getPolicy(keyPrefix) {
	return new Buffer(JSON.stringify({
		expiration: getExpiration(),
		conditions: [
			{ bucket: config.s3.uploadBucket },
			{ acl: config.s3.acl },
			[ 'starts-with', '$key', keyPrefix ],
			{ success_action_status: '201' },
			{ 'x-amz-credential': getCredential() },
			{ 'x-amz-algorithm': config.s3.algorithm },
			{ 'x-amz-date': getDate({ long: true }) },
		],
	})).toString('base64');
}

// For old bucket
function getOldPolicy(keyPrefix) {
	return new Buffer(JSON.stringify({
		expiration: getOldExpiration(),
		conditions: [
			{ bucket: config.oldS3.bucket },
			{ acl: config.oldS3.acl },
			[ 'starts-with', '$key', keyPrefix ],
			{ success_action_status: '201' },
			{ 'x-amz-credential': getOldCredential() },
			{ 'x-amz-algorithm': config.oldS3.algorithm },
			{ 'x-amz-date': getDate({ long: true }) },
		],
	})).toString('base64');
}

function getSignature(policy) {
	const kDate = sign('AWS4' + config.s3.secretKey, getDate());
	const kRegion = sign(kDate, config.s3.uploadRegion);
	const kService = sign(kRegion, config.s3.service);
	const signingKey = sign(kService, config.s3.signatureVersion);
	const signature = sign(signingKey, policy).toString('hex');
	return signature;
}

// For old bucket
function getOldSignature(policy) {
	const kDate = sign('AWS4' + config.oldS3.secretKey, getDate());
	const kRegion = sign(kDate, config.oldS3.region);
	const kService = sign(kRegion, config.oldS3.service);
	const signingKey = sign(kService, config.oldS3.signatureVersion);
	const signature = sign(signingKey, policy).toString('hex');
	return signature;
}

export function getResponse(policyReq) {
	const keyPrefix = getKeyPrefix(policyReq.auth.user, policyReq.uploadType, policyReq.contentId),
		policy = getPolicy(keyPrefix),
		signature = getSignature(policy),
		upload_url = `https://${config.s3.uploadBucket}/`,
		thumbnail_url = `https://${config.s3.generateBucket}/`,
		filename = policyReq.filename;
	let key = keyPrefix,
		thumbnail = thumbnail_url + keyPrefix,
		url;

	if (/\s{2,}/.test(filename)) {
		throw new Error('S3 does not support filenames with multiple spaces');
	}

	const ext = filename.split('.').pop() || 'jpg';

	switch (policyReq.uploadType) {
	case 'avatar':
		key += 'avatar.' + ext;
		url = upload_url + key;
		thumbnail += '256.jpg';
		break;
	case 'content':
		key += 'content.' + ext;
		url = upload_url + key;
		thumbnail += '320.jpg';
		break;
	}

	return {
		request_url: upload_url,
		original: url,
		thumbnail,
		policy: {
			key,
			acl: config.s3.acl,
			policy,
			success_action_status: '201',
			'x-amz-algorithm': config.s3.algorithm,
			'x-amz-credential': getCredential(),
			'x-amz-date': getDate({ long: true }),
			'x-amz-signature': signature,<commit_msg>fix(image-upload): use full S3 URL for upload<commit_after>import crypto from 'crypto';
import winston from 'winston';
import buildAvatarURLForSize from '../../lib/buildAvatarURLForSize';
import EnhancedError from '../../lib/EnhancedError';
import { APP_PRIORITIES, TYPE_USER } from '../../lib/Constants';
import { bus, config } from '../../core-server';
import { urlTos3 } from '../../lib/upload';

function getDate({ long } = {}) {
	const date = new Date();

	if (long) {
		return date.toISOString().replace(/[\-:]/g, '').replace(/\.\d+/g, '');
	} else {
		return date.getUTCFullYear().toString() +
		('0' + date.getUTCMonth().toString()).substr(-2) +
		('0' + date.getUTCDate().toString()).substr(-2);
	}
}

function getExpiration() {
	const validity = config.s3.validity;
	return (new Date(Date.now() + validity)).toISOString();
}

// For old bucket
function getOldExpiration() {
	const validity = config.oldS3.validity;
	return (new Date(Date.now() + validity)).toISOString();
}

function sign(key, data) {
	return crypto.createHmac('sha256', key).update(data).digest();
}

function getKeyPrefix(userId, uploadType, contentId) {
	switch (uploadType) {
	case 'avatar':
		return `a/${userId}/`;
	case 'content':
		return `c/${userId}/${contentId}/`;
	default:
		throw new Error('Invalid upload type specified: ' + uploadType);
	}
}

// For old bucket
function getOldKeyPrefix(userId, uploadType, textId) {
	switch (uploadType) {
	case 'content':
		return 'uploaded/' + uploadType + '/' + userId + '/' + textId + '/';
	default:
		throw new Error('Invalid upload type specified: ' + uploadType);
	}
}

function getCredential() {
	return `${config.s3.accessKey}/${getDate()}/${config.s3.uploadRegion}/${config.s3.service}/${config.s3.signatureVersion}`;
}

// For old bucket
function getOldCredential() {
	return `${config.oldS3.accessKey}/${getDate()}/${config.oldS3.region}/${config.oldS3.service}/${config.oldS3.signatureVersion}`;
}

function getPolicy(keyPrefix) {
	return new Buffer(JSON.stringify({
		expiration: getExpiration(),
		conditions: [
			{ bucket: config.s3.uploadBucket },
			{ acl: config.s3.acl },
			[ 'starts-with', '$key', keyPrefix ],
			{ success_action_status: '201' },
			{ 'x-amz-credential': getCredential() },
			{ 'x-amz-algorithm': config.s3.algorithm },
			{ 'x-amz-date': getDate({ long: true }) },
		],
	})).toString('base64');
}

// For old bucket
function getOldPolicy(keyPrefix) {
	return new Buffer(JSON.stringify({
		expiration: getOldExpiration(),
		conditions: [
			{ bucket: config.oldS3.bucket },
			{ acl: config.oldS3.acl },
			[ 'starts-with', '$key', keyPrefix ],
			{ success_action_status: '201' },
			{ 'x-amz-credential': getOldCredential() },
			{ 'x-amz-algorithm': config.oldS3.algorithm },
			{ 'x-amz-date': getDate({ long: true }) },
		],
	})).toString('base64');
}

function getSignature(policy) {
	const kDate = sign('AWS4' + config.s3.secretKey, getDate());
	const kRegion = sign(kDate, config.s3.uploadRegion);
	const kService = sign(kRegion, config.s3.service);
	const signingKey = sign(kService, config.s3.signatureVersion);
	const signature = sign(signingKey, policy).toString('hex');
	return signature;
}

// For old bucket
function getOldSignature(policy) {
	const kDate = sign('AWS4' + config.oldS3.secretKey, getDate());
	const kRegion = sign(kDate, config.oldS3.region);
	const kService = sign(kRegion, config.oldS3.service);
	const signingKey = sign(kService, config.oldS3.signatureVersion);
	const signature = sign(signingKey, policy).toString('hex');
	return signature;
}

export function getResponse(policyReq) {
	const keyPrefix = getKeyPrefix(policyReq.auth.user, policyReq.uploadType, policyReq.contentId),
		policy = getPolicy(keyPrefix),
		signature = getSignature(policy),
		requestUrl = `http://${config.s3.uploadBucket}.${config.s3.service}-${config.s3.uploadRegion}.amazonaws.com/`,
		filename = policyReq.filename;

	let key = keyPrefix,
		original = `https://${config.s3.uploadBucket}/`,
		thumbnail = `https://${config.s3.generateBucket}/${keyPrefix}`;

	if (/\s{2,}/.test(filename)) {
		throw new Error('S3 does not support filenames with multiple spaces');
	}

	const ext = filename.split('.').pop() || 'jpg';

	switch (policyReq.uploadType) {
	case 'avatar':
		key += 'avatar.' + ext;
		original += key;
		thumbnail += '256.jpg';
		break;
	case 'content':
		key += 'content.' + ext;
		original += key;
		thumbnail += '320.jpg';
		break;
	}

	return {
		request_url: requestUrl,
		original,
		thumbnail,
		policy: {
			key,
			acl: config.s3.acl,
			policy,
			success_action_status: '201',
			'x-amz-algorithm': config.s3.algorithm,
			'x-amz-credential': getCredential(),
			'x-amz-date': getDate({ long: true }),
			'x-amz-signature': signature,
{'fraction_non_alphanumeric': 0.18053040792416658, 'fraction_numerical': 0.012993929065928214, 'mean_word_length': 4.359589041095891, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '119096', 'n_tokens_mistral': 3414, 'n_tokens_neox': 3243, 'n_words': 658}