mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
fix: memory blow on large files sending through URL
This commit is contained in:
@@ -345,24 +345,35 @@ export const encryptedStream = async(
|
|||||||
|
|
||||||
const mediaKey = Crypto.randomBytes(32)
|
const mediaKey = Crypto.randomBytes(32)
|
||||||
const { cipherKey, iv, macKey } = await getMediaKeys(mediaKey, mediaType)
|
const { cipherKey, iv, macKey } = await getMediaKeys(mediaKey, mediaType)
|
||||||
const encWriteStream = new Readable({ read: () => {} })
|
|
||||||
|
const encFilePath = join(
|
||||||
|
getTmpFilesDirectory(),
|
||||||
|
mediaType + generateMessageIDV2() + "-enc"
|
||||||
|
);
|
||||||
|
const encFileWriteStream = createWriteStream(encFilePath);
|
||||||
|
|
||||||
let bodyPath: string | undefined
|
let originalFileStream: WriteStream | undefined;
|
||||||
let writeStream: WriteStream | undefined
|
let originalFilePath: string | undefined;
|
||||||
let didSaveToTmpPath = false
|
|
||||||
if(type === 'file') {
|
if (saveOriginalFileIfRequired) {
|
||||||
bodyPath = (media as WAMediaPayloadURL).url.toString()
|
originalFilePath = join(
|
||||||
} else if(saveOriginalFileIfRequired) {
|
getTmpFilesDirectory(),
|
||||||
bodyPath = join(getTmpFilesDirectory(), mediaType + generateMessageIDV2())
|
mediaType + generateMessageIDV2() + "-original"
|
||||||
writeStream = createWriteStream(bodyPath)
|
);
|
||||||
didSaveToTmpPath = true
|
originalFileStream = createWriteStream(originalFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
let fileLength = 0
|
let fileLength = 0
|
||||||
const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv)
|
const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv)
|
||||||
let hmac = Crypto.createHmac('sha256', macKey!).update(iv)
|
const hmac = Crypto.createHmac('sha256', macKey!).update(iv)
|
||||||
let sha256Plain = Crypto.createHash('sha256')
|
const sha256Plain = Crypto.createHash('sha256')
|
||||||
let sha256Enc = Crypto.createHash('sha256')
|
const sha256Enc = Crypto.createHash('sha256')
|
||||||
|
|
||||||
|
const onChunk = (buff: Buffer) => {
|
||||||
|
sha256Enc.update(buff);
|
||||||
|
hmac.update(buff);
|
||||||
|
encFileWriteStream.write(buff);
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for await (const data of stream) {
|
for await (const data of stream) {
|
||||||
@@ -379,68 +390,62 @@ export const encryptedStream = async(
|
|||||||
data: { media, type }
|
data: { media, type }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
sha256Plain = sha256Plain.update(data)
|
if (originalFileStream) {
|
||||||
if(writeStream && !writeStream.write(data)) {
|
if (!originalFileStream.write(data)) {
|
||||||
await once(writeStream, 'drain')
|
await once(originalFileStream, "drain");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sha256Plain.update(data)
|
||||||
onChunk(aes.update(data))
|
onChunk(aes.update(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
onChunk(aes.final())
|
onChunk(aes.final())
|
||||||
|
|
||||||
const mac = hmac.digest().slice(0, 10)
|
const mac = hmac.digest().slice(0, 10)
|
||||||
sha256Enc = sha256Enc.update(mac)
|
sha256Enc.update(mac)
|
||||||
|
|
||||||
const fileSha256 = sha256Plain.digest()
|
const fileSha256 = sha256Plain.digest()
|
||||||
const fileEncSha256 = sha256Enc.digest()
|
const fileEncSha256 = sha256Enc.digest()
|
||||||
|
|
||||||
encWriteStream.push(mac)
|
encFileWriteStream.write(mac);
|
||||||
encWriteStream.push(null)
|
|
||||||
|
|
||||||
writeStream?.end()
|
encFileWriteStream.end();
|
||||||
stream.destroy()
|
originalFileStream?.end?.();
|
||||||
|
stream.destroy();
|
||||||
|
|
||||||
logger?.debug('encrypted data successfully')
|
logger?.debug('encrypted data successfully')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mediaKey,
|
mediaKey,
|
||||||
encWriteStream,
|
originalFilePath,
|
||||||
bodyPath,
|
encFilePath,
|
||||||
mac,
|
mac,
|
||||||
fileEncSha256,
|
fileEncSha256,
|
||||||
fileSha256,
|
fileSha256,
|
||||||
fileLength,
|
fileLength
|
||||||
didSaveToTmpPath
|
|
||||||
}
|
}
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
// destroy all streams with error
|
// destroy all streams with error
|
||||||
encWriteStream.destroy()
|
encFileWriteStream.destroy()
|
||||||
writeStream?.destroy()
|
originalFileStream?.destroy?.()
|
||||||
aes.destroy()
|
aes.destroy()
|
||||||
hmac.destroy()
|
hmac.destroy()
|
||||||
sha256Plain.destroy()
|
sha256Plain.destroy()
|
||||||
sha256Enc.destroy()
|
sha256Enc.destroy()
|
||||||
stream.destroy()
|
stream.destroy()
|
||||||
|
|
||||||
if(didSaveToTmpPath) {
|
|
||||||
try {
|
try {
|
||||||
await fs.unlink(bodyPath!)
|
await fs.unlink(encFilePath)
|
||||||
} catch(err) {
|
if (originalFilePath) await fs.unlink(originalFilePath)
|
||||||
logger?.error({ err }, 'failed to save to tmp path')
|
} catch(err) {
|
||||||
}
|
logger?.error({ err }, 'failed deleting tmp files')
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChunk(buff: Buffer) {
|
|
||||||
sha256Enc = sha256Enc.update(buff)
|
|
||||||
hmac = hmac.update(buff)
|
|
||||||
encWriteStream.push(buff)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEF_HOST = 'mmg.whatsapp.net'
|
const DEF_HOST = 'mmg.whatsapp.net'
|
||||||
@@ -620,7 +625,8 @@ export const getWAUploadToServer = (
|
|||||||
url,
|
url,
|
||||||
stream,
|
stream,
|
||||||
{
|
{
|
||||||
...options,
|
...options,
|
||||||
|
maxRedirects: 0,
|
||||||
headers: {
|
headers: {
|
||||||
...options.headers || { },
|
...options.headers || { },
|
||||||
'Content-Type': 'application/octet-stream',
|
'Content-Type': 'application/octet-stream',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Boom } from '@hapi/boom'
|
import { Boom } from '@hapi/boom'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { randomBytes } from 'crypto'
|
import { randomBytes } from 'crypto'
|
||||||
import { promises as fs } from 'fs'
|
import { createReadStream, promises as fs } from 'fs'
|
||||||
import { type Transform } from 'stream'
|
import { type Transform } from 'stream'
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
import { MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults'
|
import { MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults'
|
||||||
@@ -155,15 +155,14 @@ export const prepareWAMessageMedia = async(
|
|||||||
(typeof uploadData['jpegThumbnail'] === 'undefined')
|
(typeof uploadData['jpegThumbnail'] === 'undefined')
|
||||||
const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true
|
const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true
|
||||||
const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true
|
const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true
|
||||||
const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation
|
const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation
|
||||||
const {
|
const {
|
||||||
mediaKey,
|
mediaKey,
|
||||||
encWriteStream,
|
encFilePath,
|
||||||
bodyPath,
|
originalFilePath,
|
||||||
fileEncSha256,
|
fileEncSha256,
|
||||||
fileSha256,
|
fileSha256,
|
||||||
fileLength,
|
fileLength
|
||||||
didSaveToTmpPath
|
|
||||||
} = await encryptedStream(
|
} = await encryptedStream(
|
||||||
uploadData.media,
|
uploadData.media,
|
||||||
options.mediaTypeOverride || mediaType,
|
options.mediaTypeOverride || mediaType,
|
||||||
@@ -178,7 +177,7 @@ export const prepareWAMessageMedia = async(
|
|||||||
const [{ mediaUrl, directPath }] = await Promise.all([
|
const [{ mediaUrl, directPath }] = await Promise.all([
|
||||||
(async() => {
|
(async() => {
|
||||||
const result = await options.upload(
|
const result = await options.upload(
|
||||||
encWriteStream,
|
createReadStream(encFilePath),
|
||||||
{ fileEncSha256B64, mediaType, timeoutMs: options.mediaUploadTimeoutMs }
|
{ fileEncSha256B64, mediaType, timeoutMs: options.mediaUploadTimeoutMs }
|
||||||
)
|
)
|
||||||
logger?.debug({ mediaType, cacheableKey }, 'uploaded media')
|
logger?.debug({ mediaType, cacheableKey }, 'uploaded media')
|
||||||
@@ -190,7 +189,7 @@ export const prepareWAMessageMedia = async(
|
|||||||
const {
|
const {
|
||||||
thumbnail,
|
thumbnail,
|
||||||
originalImageDimensions
|
originalImageDimensions
|
||||||
} = await generateThumbnail(bodyPath!, mediaType as 'image' | 'video', options)
|
} = await generateThumbnail(originalFilePath!, mediaType as 'image' | 'video', options)
|
||||||
uploadData.jpegThumbnail = thumbnail
|
uploadData.jpegThumbnail = thumbnail
|
||||||
if(!uploadData.width && originalImageDimensions) {
|
if(!uploadData.width && originalImageDimensions) {
|
||||||
uploadData.width = originalImageDimensions.width
|
uploadData.width = originalImageDimensions.width
|
||||||
@@ -202,12 +201,12 @@ export const prepareWAMessageMedia = async(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(requiresDurationComputation) {
|
if(requiresDurationComputation) {
|
||||||
uploadData.seconds = await getAudioDuration(bodyPath!)
|
uploadData.seconds = await getAudioDuration(originalFilePath!)
|
||||||
logger?.debug('computed audio duration')
|
logger?.debug('computed audio duration')
|
||||||
}
|
}
|
||||||
|
|
||||||
if(requiresWaveformProcessing) {
|
if(requiresWaveformProcessing) {
|
||||||
uploadData.waveform = await getAudioWaveform(bodyPath!, logger)
|
uploadData.waveform = await getAudioWaveform(originalFilePath!, logger)
|
||||||
logger?.debug('processed waveform')
|
logger?.debug('processed waveform')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,17 +221,13 @@ export const prepareWAMessageMedia = async(
|
|||||||
])
|
])
|
||||||
.finally(
|
.finally(
|
||||||
async() => {
|
async() => {
|
||||||
encWriteStream.destroy()
|
try {
|
||||||
// remove tmp files
|
await fs.unlink(encFilePath)
|
||||||
if(didSaveToTmpPath && bodyPath) {
|
if (originalFilePath) await fs.unlink(originalFilePath)
|
||||||
try {
|
logger?.debug('removed tmp files')
|
||||||
await fs.access(bodyPath)
|
} catch(error) {
|
||||||
await fs.unlink(bodyPath)
|
logger?.warn('failed to remove tmp file')
|
||||||
logger?.debug('removed tmp file')
|
}
|
||||||
} catch(error) {
|
|
||||||
logger?.warn('failed to remove tmp file')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user