diff --git a/backend/services/ChunkService/FileSystemService.ts b/backend/services/ChunkService/FileSystemService.ts index dde8d5a..db79838 100644 --- a/backend/services/ChunkService/FileSystemService.ts +++ b/backend/services/ChunkService/FileSystemService.ts @@ -261,6 +261,14 @@ class FileSystemService implements ChunkInterface { streamVideo = async(user: UserInterface, fileID: string, headers: any, res: Response, req: Request) => { + // To get this all working correctly with encryption and across + // All browsers took many days, tears, and some of my sanity. + // Shoutout to Tyzoid for helping me with the decryption + // And and helping me understand how the IVs work. + // Also fuck you Apple, Safari is turning into + // Internet explorer at this point. + // Thanks Tim Apple + const userID = user._id; const currentFile: FileInterface = await dbUtilsFile.getFileInfo(fileID, userID); @@ -289,71 +297,60 @@ class FileSystemService implements ChunkInterface { let currentIV = IV; - //let fixedStart = start % 16 === 0 ? start : fixStartChunkLength(start); - let fixedStart = 0; let fixedEnd = currentFile.length; if (start === 0 && end === 1) { - console.log("safari request"); + + // This is for Safari/iOS, Safari will request the first + // Byte before actually playing the video. Needs to be + // 16 bytes. + fixedStart = 0; fixedEnd = 15; + } else { + + // If you're a normal browser, or this isn't Safari's first request + // We need to make it so start is divisible by 16, since AES256 + // Has a block size of 16 bytes. + fixedStart = start % 16 === 0 ? start : fixStartChunkLength(start); } if (+start === 0) { - + + // This math will not work if the start is 0 + // So if it is we just change fixed start back + // To 0. + fixedStart = 0; } - + // We also need to calculate the difference between the start and the + // Fixed start position. Since there will be an offset if the original + // Request is not divisible by 16, it will not return the right part + // Of the file, you will see how we do this in the awaitStreamVideo + // code. + const differenceStart = start - fixedStart; + if (fixedStart !== 0 && start !== 0) { + + // If this isn't the first request, the way AES256 works is when you try to + // Decrypt a certain part of the file that isn't the start, the IV will + // Actually be the 16 bytes ahead of where you are trying to + // Start the decryption. currentIV = await getPrevIVFS(fixedStart - 16, currentFile.metadata.filePath!) as Buffer; } - - console.log("start", start, fixedStart, end, fixedEnd, differenceStart); - let readStream; - - if (start === 0 && end === 1) { - - readStream = fs.createReadStream(currentFile.metadata.filePath!, { - start: fixedStart, - end: fixedEnd, - }); - - } else { - - readStream = fs.createReadStream(currentFile.metadata.filePath!, { - start: fixedStart, - end: fixedEnd, - // highWaterMark: 1024 - }) - } - - // } else if (fixedStart !== 0 && start !== 0) { - - // readStream = fs.createReadStream(currentFile.metadata.filePath!, { - // start: fixedStart + differenceStart, - // end: fixedEnd, - // }); - - // } else { - // readStream = fs.createReadStream(currentFile.metadata.filePath!, { - // start: fixedStart, - // end: fixedEnd, - // }); - // } - - // const readStream = fs.createReadStream(currentFile.metadata.filePath!, { - // start: fixedStart, - // end: fixedEnd, - // }); + const readStream = fs.createReadStream(currentFile.metadata.filePath!, { + start: fixedStart, + end: fixedEnd, + }); const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() @@ -367,31 +364,8 @@ class FileSystemService implements ChunkInterface { readStream.pipe(decipher); - // readStream.on("close", () => { - // console.log("read stream closed") - // }) + await awaitStreamVideo(start, end, differenceStart, decipher, res, req, allStreamsToErrorCatch, readStream); - // req.on("close", () => { - // // console.log("req closed"); - // readStream.destroy(); - // }) - - const tempUUID = req.params.uuid; - - //console.log("temp uuid", tempUUID); - - // return; - // if (start === 0 && end === 1) { - // await awaitStreamVideo(start, end, differenceStart, decipher, res, req, tempUUID, allStreamsToErrorCatch); - // } else { - // await awaitStreamVideo(start, end, differenceStart, decipher, res, req, tempUUID, allStreamsToErrorCatch); - // } - - - - await awaitStreamVideo(start, end, differenceStart, decipher, res, req, tempUUID, allStreamsToErrorCatch, readStream); - - console.log("await stream finished") readStream.destroy(); } diff --git a/backend/services/ChunkService/MongoService.ts b/backend/services/ChunkService/MongoService.ts index 2db7d5f..0d60354 100644 --- a/backend/services/ChunkService/MongoService.ts +++ b/backend/services/ChunkService/MongoService.ts @@ -277,6 +277,14 @@ class MongoService implements ChunkInterface { streamVideo = async(user: UserInterface, fileID: string, headers: any, res: Response, req: Request) => { + // To get this all working correctly with encryption and across + // All browsers took many days, tears, and some of my sanity. + // Shoutout to Tyzoid for helping me with the decryption + // And and helping me understand how the IVs work. + // Also fuck you Apple, Safari is turning into + // Internet explorer at this point. + // Thanks Tim Apple + const userID = user._id; const currentFile = await dbUtilsFile.getFileInfo(fileID, userID); @@ -305,18 +313,51 @@ class MongoService implements ChunkInterface { let currentIV = IV; - let fixedStart = start % 16 === 0 ? start : fixStartChunkLength(start); + let fixedStart = 0; + let fixedEnd = currentFile.length; + + if (start === 0 && end === 1) { + + // This is for Safari/iOS, Safari will request the first + // Byte before actually playing the video. Needs to be + // 16 bytes. + + fixedStart = 0; + fixedEnd = 15; + + } else { + + // If you're a normal browser, or this isn't Safari's first request + // We need to make it so start is divisible by 16, since AES256 + // Has a block size of 16 bytes. + + fixedStart = start % 16 === 0 ? start : fixStartChunkLength(start); + } if (+start === 0) { + // This math will not work if the start is 0 + // So if it is we just change fixed start back + // To 0. + fixedStart = 0; } - const fixedEnd = currentFile.length; + // We also need to calculate the difference between the start and the + // Fixed start position. Since there will be an offset if the original + // Request is not divisible by 16, it will not return the right part + // Of the file, you will see how we do this in the awaitStreamVideo + // code. const differenceStart = start - fixedStart; + if (fixedStart !== 0 && start !== 0) { + + // If this isn't the first request, the way AES256 works is when you try to + // Decrypt a certain part of the file that isn't the start, the IV will + // Actually be the 16 bytes ahead of where you are trying to + // Start the decryption. currentIV = await getPrevIVMongo(fixedStart - 16, fileID) as Buffer; } @@ -341,15 +382,8 @@ class MongoService implements ChunkInterface { readStream.pipe(decipher); - // req.on("close", () => { - // // console.log("req closed"); - // readStream.destroy(); - // }) - - - const tempUUID = req.params.uuid; - - //await awaitStreamVideo(start, end, differenceStart, decipher, res, req, tempUUID, allStreamsToErrorCatch); + await awaitStreamVideo(start, end, differenceStart, decipher, res, req, allStreamsToErrorCatch, readStream); + readStream.destroy(); } diff --git a/backend/services/ChunkService/S3Service.ts b/backend/services/ChunkService/S3Service.ts index da80639..44a8f4d 100644 --- a/backend/services/ChunkService/S3Service.ts +++ b/backend/services/ChunkService/S3Service.ts @@ -241,6 +241,14 @@ class S3Service implements ChunkInterface { streamVideo = async(user: UserInterface, fileID: string, headers: any, res: Response, req: Request) => { + // To get this all working correctly with encryption and across + // All browsers took many days, tears, and some of my sanity. + // Shoutout to Tyzoid for helping me with the decryption + // And and helping me understand how the IVs work. + // Also fuck you Apple, Safari is turning into + // Internet explorer at this point. + // Thanks Tim Apple + const userID = user._id; const currentFile: FileInterface = await dbUtilsFile.getFileInfo(fileID, userID); @@ -271,18 +279,51 @@ class S3Service implements ChunkInterface { let currentIV = IV; - let fixedStart = start % 16 === 0 ? start : fixStartChunkLength(start); - + let fixedStart = 0; + let fixedEnd = currentFile.length; + + if (start === 0 && end === 1) { + + // This is for Safari/iOS, Safari will request the first + // Byte before actually playing the video. Needs to be + // 16 bytes. + + fixedStart = 0; + fixedEnd = 15; + + } else { + + // If you're a normal browser, or this isn't Safari's first request + // We need to make it so start is divisible by 16, since AES256 + // Has a block size of 16 bytes. + + fixedStart = start % 16 === 0 ? start : fixStartChunkLength(start); + } + if (+start === 0) { - + + // This math will not work if the start is 0 + // So if it is we just change fixed start back + // To 0. + fixedStart = 0; } - - const fixedEnd = fileSize % 16 === 0 ? fileSize : fixEndChunkLength(fileSize); //end % 16 === 0 ? end + 15: (fixEndChunkLength(end) - 1) + 16; + + // We also need to calculate the difference between the start and the + // Fixed start position. Since there will be an offset if the original + // Request is not divisible by 16, it will not return the right part + // Of the file, you will see how we do this in the awaitStreamVideo + // code. const differenceStart = start - fixedStart; + if (fixedStart !== 0 && start !== 0) { + + // If this isn't the first request, the way AES256 works is when you try to + // Decrypt a certain part of the file that isn't the start, the IV will + // Actually be the 16 bytes ahead of where you are trying to + // Start the decryption. currentIV = await getPrevIVS3(fixedStart - 16, currentFile.metadata.s3ID!, isPersonal, user) as Buffer; } @@ -303,11 +344,8 @@ class S3Service implements ChunkInterface { s3ReadStream.pipe(decipher); - const tempUUID = req.params.uuid; + await awaitStreamVideo(start, end, differenceStart, decipher, res, req, allStreamsToErrorCatch, s3ReadStream); - console.log("temp uuid", tempUUID); - - //await awaitStreamVideo(start, end, differenceStart, decipher, res, req, tempUUID, allStreamsToErrorCatch); s3ReadStream.destroy(); } diff --git a/backend/services/ChunkService/utils/awaitStreamVideo.ts b/backend/services/ChunkService/utils/awaitStreamVideo.ts index 808c03d..30cd1a2 100644 --- a/backend/services/ChunkService/utils/awaitStreamVideo.ts +++ b/backend/services/ChunkService/utils/awaitStreamVideo.ts @@ -1,31 +1,18 @@ import {Response, Request} from "express" -import tempStorage from "../../../tempStorage/tempStorage"; -import uuid from "uuid"; const StreamSkip = require("stream-skip"); const awaitStreamVideo = (start: number, end:number, differenceStart: number, - decipher: any, res: Response, req: Request, tempUUID: string, streamsToErrorCatch: any[], readStream: any) => { - - // const currentUUID = uuid.v4(); - // tempStorage[tempUUID] = currentUUID; + decipher: any, res: Response, req: Request, streamsToErrorCatch: any[], readStream: any) => { return new Promise((resolve, reject) => { - let firstBytesRemoved = false; - //let sizeCounter = 0; - req.on("close", () => { - console.log("close resolved"); - // streamsToErrorCatch.forEach((stream) => { - // stream.destroy(); - // }) readStream.close(); decipher.destroy(); resolve(); }) req.on("end", () => { - console.log("End resolved"); streamsToErrorCatch.forEach((stream) => { stream.destroy(); }) @@ -33,130 +20,43 @@ const awaitStreamVideo = (start: number, end:number, differenceStart: number, }) readStream.on("close", () => { - console.log("read stream closed") decipher.destroy(); }) + if (+start === 0 && +end === 1) { - console.log("safari first byte request"); + + // This is for Safri/iOS, for whatever reason they ask for the + // First byte, but if I actually try to return the first byte + // It will not work ???, but if I return the first 4 bytes it seems + // To work fine. decipher.on("data", (data: Buffer | string) => { - //console.log("original data size", data.length); - const dataCoverted = data.toString("hex"); let neededData = dataCoverted.substring(0, 8); const dataBack = Buffer.from(neededData, "hex"); - //console.log("safari bytes size", dataBack.length); - res.write(dataBack); }) } else { + // This is where the differnce start comes into play, as I said before + // There will be an offset caused by the 16 block size of AES256. + // So if there was an offset we need to skip over those bytes + // To make sure it returns the exact position the browser is requesting. + // Most browsers will still work fine without this, such as Chrome and Firefox. + // But one browser needs to be pampered and fed the exact right bytes in + // Order to function correctly, can you guess what browser? If you + // Guessed Safari you would be correct. + const streamSkip = new StreamSkip({skip: differenceStart}); decipher.pipe(streamSkip).pipe(res); } - - //decipher.pipe(res); - - - // decipher.on("data", (data: Buffer | string) => { - - // //console.log("data", uuid.v4()); - - - // // if (tempStorage[tempUUID] !== currentUUID) { - - - // // streamsToErrorCatch.forEach((stream) => { - // // stream.destroy(); - // // }) - - // // resolve(); - // // } - - // //console.log("data", currentUUID); - - // //console.log("stream passed", currentUUID); - - // // if (tempStorage[tempUUID] !== undefined && tempStorage[tempUUID] !== currentUUID) { - - // // console.log("New Stream Requested, Desroying old stream"); - // // streamsToErrorCatch[0].destroy(); - // // console.log("Old Stream Desroyed"); - // // delete tempStorage[tempUUID]; - - // // } else { - - // // tempStorage[tempUUID] = currentUUID; - // // } - - // //console.log("data size", data.length) - - // if (+start === 0 && +end === 1) { - - // const dataCoverted = data.toString("hex"); - - // let neededData = dataCoverted.substring(0, 4); - - // const dataBack = Buffer.from(neededData, "hex"); - - // res.write(dataBack); - // //res.flush(); - // // return; - // } else if (!firstBytesRemoved) { - - // const dataCoverted = data.toString("hex"); - - // let neededData = dataCoverted.substring(differenceStart * 2); - - // const dataBack = Buffer.from(neededData, "hex"); - - // firstBytesRemoved = true; - - // //sizeCounter += dataBack.length; - - // res.write(dataBack); - // //res.flush(); - // // return; - // } else { - // res.write(data); - // } - - - - // //res.flush(); - // //sizeCounter += data.length; - // }) - - // decipher.on("close", () => { - // console.log("decipher closed"); - // }) - - // decipher.on("end", () => { - // console.log("decipher resolved"); - // //res.end(); - // //resolve(); - // }) - - // streamsToErrorCatch.forEach((currentStream) => { - - // currentStream.on("error", (e: Error) => { - - // reject({ - // message: "Await Video Stream Input Error", - // code: 500, - // error: e - // }) - - // }) - // }) - }) }