diff --git a/keyserver/src/creators/upload-creator.js b/keyserver/src/creators/upload-creator.js --- a/keyserver/src/creators/upload-creator.js +++ b/keyserver/src/creators/upload-creator.js @@ -11,17 +11,27 @@ import createIDs from './id-creator.js'; import { dbQuery, SQL } from '../database/database.js'; -import { getUploadURL } from '../fetchers/upload-fetchers.js'; +import { makeUploadURI } from '../fetchers/upload-fetchers.js'; import type { Viewer } from '../session/viewer.js'; +type UploadContent = + | { + +storage: 'keyserver', + +buffer: Buffer, + } + | { + +storage: 'blob_service', + +blobHolder: string, + }; + export type UploadInput = { - name: string, - mime: string, - mediaType: MediaType, - buffer: Buffer, - dimensions: Dimensions, - loop: boolean, - encryptionKey?: string, + +name: string, + +mime: string, + +mediaType: MediaType, + +content: UploadContent, + +dimensions: Dimensions, + +loop: boolean, + +encryptionKey?: string, }; async function createUploads( viewer: Viewer, @@ -35,11 +45,17 @@ const uploadRows = uploadInfos.map(uploadInfo => { const id = ids.shift(); const secret = crypto.randomBytes(8).toString('hex'); - const { dimensions, mediaType, loop, encryptionKey } = uploadInfo; + const { content, dimensions, mediaType, loop, encryptionKey } = uploadInfo; + const buffer = + content.storage === 'keyserver' ? content.buffer : Buffer.alloc(0); + const blobHolder = + content.storage === 'blob_service' ? content.blobHolder : undefined; + const uri = makeUploadURI(blobHolder, id, secret); + return { uploadResult: { id, - uri: getUploadURL(id, secret), + uri, dimensions, mediaType, loop, @@ -50,10 +66,10 @@ mediaType, uploadInfo.name, uploadInfo.mime, - uploadInfo.buffer, + buffer, secret, Date.now(), - JSON.stringify({ ...dimensions, loop, encryptionKey }), + JSON.stringify({ ...dimensions, loop, blobHolder, encryptionKey }), ], }; }); diff --git a/keyserver/src/fetchers/upload-fetchers.js b/keyserver/src/fetchers/upload-fetchers.js --- a/keyserver/src/fetchers/upload-fetchers.js +++ b/keyserver/src/fetchers/upload-fetchers.js @@ -12,6 +12,7 @@ ThreadFetchMediaResult, ThreadFetchMediaRequest, } from 'lib/types/thread-types.js'; +import { makeBlobServiceURI } from 'lib/utils/blob-service.js'; import { isDev } from 'lib/utils/dev-utils.js'; import { ServerError } from 'lib/utils/errors.js'; @@ -29,7 +30,7 @@ secret: string, ): Promise { const query = SQL` - SELECT content, mime + SELECT content, mime, extra FROM uploads WHERE id = ${id} AND secret = ${secret} `; @@ -39,7 +40,11 @@ throw new ServerError('invalid_parameters'); } const [row] = result; - const { content, mime } = row; + const { content, mime, extra } = row; + const { blobHolder } = JSON.parse(extra); + if (blobHolder) { + throw new ServerError('resource_unavailable'); + } return { content, mime }; } @@ -51,7 +56,7 @@ ): Promise { // We use pos + 1 because SQL is 1-indexed whereas js is 0-indexed const query = SQL` - SELECT SUBSTRING(content, ${pos + 1}, ${len}) AS content, mime + SELECT SUBSTRING(content, ${pos + 1}, ${len}) AS content, mime, extra FROM uploads WHERE id = ${id} AND secret = ${secret} `; @@ -61,7 +66,13 @@ throw new ServerError('invalid_parameters'); } const [row] = result; - const { content, mime } = row; + const { content, mime, extra } = row; + if (extra) { + const { blobHolder } = JSON.parse(extra); + if (blobHolder) { + throw new ServerError('resource_unavailable'); + } + } return { content, mime, @@ -71,7 +82,7 @@ // Returns total size in bytes. async function getUploadSize(id: string, secret: string): Promise { const query = SQL` - SELECT LENGTH(content) AS length + SELECT LENGTH(content) AS length, extra FROM uploads WHERE id = ${id} AND secret = ${secret} `; @@ -82,7 +93,13 @@ } const [row] = result; - const { length } = row; + const { length, extra } = row; + if (extra) { + const { blobHolder } = JSON.parse(extra); + if (blobHolder) { + throw new ServerError('resource_unavailable'); + } + } return length; } @@ -97,14 +114,21 @@ return `${baseDomain}${uploadPath}`; } +function makeUploadURI(holder: ?string, id: string, secret: string): string { + if (holder) { + return makeBlobServiceURI(holder); + } + return getUploadURL(id, secret); +} + function imagesFromRow(row: Object): Image | EncryptedImage { const uploadExtra = JSON.parse(row.uploadExtra); - const { width, height } = uploadExtra; + const { width, height, blobHolder } = uploadExtra; const { uploadType: type, uploadSecret: secret } = row; const id = row.uploadID.toString(); const dimensions = { width, height }; - const uri = getUploadURL(id, secret); + const uri = makeUploadURI(blobHolder, id, secret); const isEncrypted = !!uploadExtra.encryptionKey; if (type !== 'photo') { throw new ServerError('invalid_parameters'); @@ -188,15 +212,17 @@ const media = uploads.map(upload => { const { uploadID, uploadType, uploadSecret, uploadExtra } = upload; - const { width, height, encryptionKey } = JSON.parse(uploadExtra); + const { width, height, encryptionKey, blobHolder } = + JSON.parse(uploadExtra); const dimensions = { width, height }; + const uri = makeUploadURI(blobHolder, uploadID, uploadSecret); if (uploadType === 'photo') { if (encryptionKey) { return { type: 'encrypted_photo', id: uploadID.toString(), - holder: getUploadURL(uploadID, uploadSecret), + holder: uri, encryptionKey, dimensions, }; @@ -204,24 +230,31 @@ return { type: 'photo', id: uploadID.toString(), - uri: getUploadURL(uploadID, uploadSecret), + uri, dimensions, }; } const { thumbnailID, thumbnailUploadSecret, thumbnailUploadExtra } = upload; + const { + encryptionKey: thumbnailEncryptionKey, + blobHolder: thumbnailBlobHolder, + } = JSON.parse(thumbnailUploadExtra); + const thumbnailURI = makeUploadURI( + thumbnailBlobHolder, + thumbnailID, + thumbnailUploadSecret, + ); if (encryptionKey) { - const { encryptionKey: thumbnailEncryptionKey } = - JSON.parse(thumbnailUploadExtra); return { type: 'encrypted_video', id: uploadID.toString(), - holder: getUploadURL(uploadID, uploadSecret), + holder: uri, encryptionKey, dimensions, thumbnailID, - thumbnailHolder: getUploadURL(thumbnailID, thumbnailUploadSecret), + thumbnailHolder: thumbnailURI, thumbnailEncryptionKey, }; } @@ -229,10 +262,10 @@ return { type: 'video', id: uploadID.toString(), - uri: getUploadURL(uploadID, uploadSecret), + uri, dimensions, thumbnailID, - thumbnailURI: getUploadURL(thumbnailID, thumbnailUploadSecret), + thumbnailURI, }; }); @@ -279,13 +312,16 @@ const primaryUploadID = mediaMessageContent.uploadID; const primaryUpload = uploadMap[primaryUploadID]; - const primaryUploadSecret = primaryUpload.uploadSecret; - const primaryUploadURI = getUploadURL(primaryUploadID, primaryUploadSecret); - const uploadExtra = JSON.parse(primaryUpload.uploadExtra); - const { width, height, loop, encryptionKey } = uploadExtra; + const { width, height, loop, blobHolder, encryptionKey } = uploadExtra; const dimensions = { width, height }; + const primaryUploadURI = makeUploadURI( + blobHolder, + primaryUploadID, + primaryUpload.uploadSecret, + ); + if (mediaMessageContent.type === 'photo') { if (encryptionKey) { media.push({ @@ -309,12 +345,13 @@ const thumbnailUploadID = mediaMessageContent.thumbnailUploadID; const thumbnailUpload = uploadMap[thumbnailUploadID]; - const thumbnailUploadSecret = thumbnailUpload.uploadSecret; - const thumbnailUploadURI = getUploadURL( + const thumbnailUploadExtra = JSON.parse(thumbnailUpload.uploadExtra); + const { blobHolder: thumbnailBlobHolder } = thumbnailUploadExtra; + const thumbnailUploadURI = makeUploadURI( + thumbnailBlobHolder, thumbnailUploadID, - thumbnailUploadSecret, + thumbnailUpload.uploadSecret, ); - const thumbnailUploadExtra = JSON.parse(thumbnailUpload.uploadExtra); if (encryptionKey) { const video = { @@ -349,6 +386,7 @@ fetchUploadChunk, getUploadSize, getUploadURL, + makeUploadURI, imagesFromRow, fetchImages, fetchMediaForThread, diff --git a/keyserver/src/uploads/media-utils.js b/keyserver/src/uploads/media-utils.js --- a/keyserver/src/uploads/media-utils.js +++ b/keyserver/src/uploads/media-utils.js @@ -76,7 +76,7 @@ name: initialName, mime: inputMimeType, mediaType, - buffer: initialBuffer, + content: { storage: 'keyserver', buffer: initialBuffer }, dimensions: inputDimensions, loop: inputLoop, encryptionKey: inputEncryptionKey, @@ -101,7 +101,7 @@ mime: mime, mediaType: mediaType, name: initialName, - buffer: initialBuffer, + content: { storage: 'keyserver', buffer: initialBuffer }, dimensions: inputDimensions, loop: inputLoop, }; @@ -160,7 +160,7 @@ mime, mediaType: 'photo', name, - buffer: initialBuffer, + content: { storage: 'keyserver', buffer: initialBuffer }, dimensions: initialDimensions, loop: inputLoop, }; @@ -210,7 +210,7 @@ mime: targetMIME, mediaType: 'photo', name: convertedName, - buffer: convertedBuffer, + content: { storage: 'keyserver', buffer: convertedBuffer }, dimensions: convertedDimensions, loop: inputLoop, };