cleaned up all chunk routes
This commit is contained in:
@@ -56,47 +56,21 @@ class FileController {
|
||||
}
|
||||
};
|
||||
|
||||
getFullThumbnail = async (req: RequestTypeFullUser, res: Response) => {
|
||||
getFullThumbnail = async (
|
||||
req: RequestTypeFullUser,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
if (!req.user) {
|
||||
return;
|
||||
}
|
||||
let responseSent = false;
|
||||
try {
|
||||
const user = req.user;
|
||||
const fileID = req.params.id;
|
||||
|
||||
const { decipher, readStream, file } =
|
||||
await this.chunkService.getFullThumbnail(user, fileID);
|
||||
|
||||
readStream.on("error", (e: Error) => {
|
||||
console.log("Get full thumbnail read stream error", e);
|
||||
if (!responseSent) {
|
||||
responseSent = true;
|
||||
res.status(500).send("Server error getting full thumbnail");
|
||||
}
|
||||
});
|
||||
|
||||
decipher.on("error", (e: Error) => {
|
||||
console.log("Get full thumbnail decipher error", e);
|
||||
if (!responseSent) {
|
||||
responseSent = true;
|
||||
res.status(500).send("Server error gettingfull thumbnail");
|
||||
}
|
||||
});
|
||||
|
||||
res.set("Content-Type", "binary/octet-stream");
|
||||
res.set(
|
||||
"Content-Disposition",
|
||||
'attachment; filename="' + file.filename + '"'
|
||||
);
|
||||
res.set("Content-Length", file.metadata.size.toString());
|
||||
|
||||
readStream.pipe(decipher).pipe(res);
|
||||
await this.chunkService.getFullThumbnail(user, fileID, res);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
console.log("\nGet Thumbnail Full Error File Route:", e.message);
|
||||
}
|
||||
res.status(500).send("Server error getting image");
|
||||
next(e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -121,35 +95,18 @@ class FileController {
|
||||
}
|
||||
};
|
||||
|
||||
getPublicDownload = async (req: RequestType, res: Response) => {
|
||||
let responseSent = false;
|
||||
getPublicDownload = async (
|
||||
req: RequestType,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const ID = req.params.id;
|
||||
const tempToken = req.params.tempToken;
|
||||
|
||||
const { readStream, decipher, file } =
|
||||
await this.chunkService.getPublicDownload(ID, tempToken, res);
|
||||
|
||||
readStream.on("error", (e: Error) => {
|
||||
console.log("read stream error", e);
|
||||
});
|
||||
|
||||
decipher.on("error", (e: Error) => {
|
||||
console.log("decipher stream error", e);
|
||||
});
|
||||
|
||||
res.set("Content-Type", "binary/octet-stream");
|
||||
res.set(
|
||||
"Content-Disposition",
|
||||
'attachment; filename="' + file.filename + '"'
|
||||
);
|
||||
res.set("Content-Length", file.metadata.size.toString());
|
||||
|
||||
readStream.pipe(decipher).pipe(res);
|
||||
await this.chunkService.getPublicDownload(ID, tempToken, res);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
console.log("\nGet Public Download Error File Route:", e.message);
|
||||
}
|
||||
next(e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -397,65 +354,23 @@ class FileController {
|
||||
}
|
||||
};
|
||||
|
||||
streamVideo = async (req: RequestTypeFullUser, res: Response) => {
|
||||
streamVideo = async (
|
||||
req: RequestTypeFullUser,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
if (!req.user) {
|
||||
return;
|
||||
}
|
||||
let responseSent = false;
|
||||
|
||||
try {
|
||||
const user = req.user;
|
||||
const fileID = req.params.id;
|
||||
const headers = req.headers;
|
||||
|
||||
const { decipher, readStream, head } =
|
||||
await this.chunkService.streamVideo(user, fileID, headers);
|
||||
|
||||
const cleanUp = () => {
|
||||
if (readStream) readStream.destroy();
|
||||
if (decipher) decipher.end();
|
||||
};
|
||||
|
||||
const handleError = (e: Error) => {
|
||||
console.log("stream video read stream error", e);
|
||||
cleanUp();
|
||||
};
|
||||
|
||||
readStream.on("error", handleError);
|
||||
|
||||
decipher.on("error", handleError);
|
||||
|
||||
readStream.on("end", () => {
|
||||
if (!responseSent) {
|
||||
responseSent = true;
|
||||
res.end();
|
||||
}
|
||||
cleanUp();
|
||||
});
|
||||
|
||||
readStream.on("close", () => {
|
||||
cleanUp();
|
||||
});
|
||||
|
||||
decipher.on("end", () => {
|
||||
if (!responseSent) {
|
||||
responseSent = true;
|
||||
res.end();
|
||||
}
|
||||
cleanUp();
|
||||
});
|
||||
|
||||
decipher.on("close", () => {
|
||||
cleanUp();
|
||||
});
|
||||
|
||||
res.writeHead(206, head);
|
||||
|
||||
readStream.pipe(decipher).pipe(res);
|
||||
await this.chunkService.streamVideo(user, fileID, headers, res);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
console.log("\nStream Video Error File Route:", e.message);
|
||||
}
|
||||
next(e);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import getFolderBusboyData from "./utils/getFolderUploadBusboyData";
|
||||
import { getStorageActions } from "./actions/helper-actions";
|
||||
import getThumbnailData from "./utils/getThumbnailData";
|
||||
import getFileData from "./utils/getFileData";
|
||||
import getPublicFileData from "./utils/getPublicFileData";
|
||||
|
||||
const fileDB = new FileDB();
|
||||
const folderDB = new FolderDB();
|
||||
@@ -352,38 +353,20 @@ class StorageService {
|
||||
return bufferData;
|
||||
};
|
||||
|
||||
getFullThumbnail = async (user: UserInterface, fileID: string) => {
|
||||
const userID = user._id;
|
||||
|
||||
const file = await fileDB.getFileInfo(fileID, userID.toString());
|
||||
|
||||
if (!file) throw new NotFoundError("File Thumbnail Not Found");
|
||||
|
||||
const password = user.getEncryptionKey();
|
||||
|
||||
const IV = file.metadata.IV;
|
||||
|
||||
if (!password) throw new ForbiddenError("Invalid Encryption Key");
|
||||
|
||||
const readStreamParams = createGenericParams({
|
||||
filePath: file.metadata.filePath,
|
||||
Key: file.metadata.s3ID,
|
||||
});
|
||||
|
||||
const readStream = storageActions.createReadStream(readStreamParams);
|
||||
|
||||
const CIPHER_KEY = crypto.createHash("sha256").update(password).digest();
|
||||
|
||||
const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV);
|
||||
|
||||
return {
|
||||
readStream,
|
||||
decipher,
|
||||
file,
|
||||
};
|
||||
getFullThumbnail = async (
|
||||
user: UserInterface,
|
||||
fileID: string,
|
||||
res: Response
|
||||
) => {
|
||||
await getFileData(res, fileID, user);
|
||||
};
|
||||
|
||||
streamVideo = async (user: UserInterface, fileID: string, headers: any) => {
|
||||
streamVideo = async (
|
||||
user: UserInterface,
|
||||
fileID: string,
|
||||
headers: any,
|
||||
res: Response
|
||||
) => {
|
||||
const userID = user._id;
|
||||
const currentFile = await fileDB.getFileInfo(fileID, userID.toString());
|
||||
|
||||
@@ -397,20 +380,17 @@ class StorageService {
|
||||
|
||||
const range = headers.range;
|
||||
const parts = range.replace(/bytes=/, "").split("-");
|
||||
let start = parseInt(parts[0], 10);
|
||||
let end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
|
||||
const IV = currentFile.metadata.IV;
|
||||
const start = parseInt(parts[0], 10);
|
||||
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
|
||||
const chunksize = end - start + 1;
|
||||
|
||||
let head = {
|
||||
const head = {
|
||||
"Content-Range": "bytes " + start + "-" + end + "/" + fileSize,
|
||||
"Accept-Ranges": "bytes",
|
||||
"Content-Length": chunksize,
|
||||
"Content-Type": "video/mp4",
|
||||
};
|
||||
|
||||
let currentIV = IV;
|
||||
|
||||
let fixedStart = 0;
|
||||
let fixedEnd =
|
||||
currentFile.length % 16 === 0
|
||||
@@ -428,75 +408,13 @@ class StorageService {
|
||||
fixedStart = 0;
|
||||
}
|
||||
|
||||
const readStreamParams = createGenericParams({
|
||||
filePath: currentFile.metadata.filePath,
|
||||
Key: currentFile.metadata.s3ID,
|
||||
});
|
||||
res.writeHead(206, head);
|
||||
|
||||
if (fixedStart !== 0 && start !== 0) {
|
||||
currentIV = (await storageActions.getPrevIV(
|
||||
readStreamParams,
|
||||
fixedStart - 16
|
||||
)) as Buffer;
|
||||
}
|
||||
|
||||
const readStream = storageActions.createReadStreamWithRange(
|
||||
readStreamParams,
|
||||
fixedStart,
|
||||
fixedEnd
|
||||
);
|
||||
|
||||
const CIPHER_KEY = crypto.createHash("sha256").update(password).digest();
|
||||
|
||||
const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, currentIV);
|
||||
|
||||
decipher.setAutoPadding(false);
|
||||
|
||||
return {
|
||||
readStream,
|
||||
decipher,
|
||||
file: currentFile,
|
||||
head,
|
||||
};
|
||||
await getFileData(res, fileID, user, { start: fixedStart, end: fixedEnd });
|
||||
};
|
||||
|
||||
getPublicDownload = async (fileID: string, tempToken: any, res: Response) => {
|
||||
const file = await fileDB.getPublicFile(fileID);
|
||||
|
||||
if (!file || !file.metadata.link || file.metadata.link !== tempToken) {
|
||||
throw new NotAuthorizedError("File Not Public");
|
||||
}
|
||||
|
||||
if (file.metadata.linkType === "one") {
|
||||
await fileDB.removeOneTimePublicLink(fileID);
|
||||
}
|
||||
|
||||
const user = await userDB.getUserInfo(file.metadata.owner);
|
||||
|
||||
if (!user) throw new NotFoundError("User Not Found");
|
||||
|
||||
const password = user.getEncryptionKey();
|
||||
|
||||
if (!password) throw new ForbiddenError("Invalid Encryption Key");
|
||||
|
||||
const IV = file.metadata.IV;
|
||||
|
||||
const readStreamParams = createGenericParams({
|
||||
filePath: file.metadata.filePath,
|
||||
Key: file.metadata.s3ID,
|
||||
});
|
||||
|
||||
const readStream = storageActions.createReadStream(readStreamParams);
|
||||
|
||||
const CIPHER_KEY = crypto.createHash("sha256").update(password).digest();
|
||||
|
||||
const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV);
|
||||
|
||||
return {
|
||||
readStream,
|
||||
decipher,
|
||||
file: file,
|
||||
};
|
||||
await getPublicFileData(res, fileID, tempToken);
|
||||
};
|
||||
|
||||
deleteMulti = async (
|
||||
|
||||
@@ -12,7 +12,12 @@ const fileDB = new FileDB();
|
||||
|
||||
const storageActions = getStorageActions();
|
||||
|
||||
const proccessData = (res: Response, fileID: string, user: UserInterface) => {
|
||||
const proccessData = (
|
||||
res: Response,
|
||||
fileID: string,
|
||||
user: UserInterface,
|
||||
range?: { start: number; end: number }
|
||||
) => {
|
||||
const eventEmitter = new EventEmitter();
|
||||
|
||||
const processFile = async () => {
|
||||
@@ -32,12 +37,27 @@ const proccessData = (res: Response, fileID: string, user: UserInterface) => {
|
||||
Key: currentFile.metadata.s3ID,
|
||||
});
|
||||
|
||||
const readStream = storageActions.createReadStream(readStreamParams);
|
||||
let readStream;
|
||||
|
||||
if (range) {
|
||||
readStream = storageActions.createReadStreamWithRange(
|
||||
readStreamParams,
|
||||
range.start,
|
||||
range.end
|
||||
);
|
||||
} else {
|
||||
readStream = storageActions.createReadStream(readStreamParams);
|
||||
}
|
||||
|
||||
const CIPHER_KEY = crypto.createHash("sha256").update(password).digest();
|
||||
|
||||
const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV);
|
||||
|
||||
if (range) {
|
||||
// TODO: Check if we need this for video streaming
|
||||
decipher.setAutoPadding(false);
|
||||
}
|
||||
|
||||
decipher.on("error", (e: Error) => {
|
||||
eventEmitter.emit("error", e);
|
||||
});
|
||||
@@ -50,12 +70,14 @@ const proccessData = (res: Response, fileID: string, user: UserInterface) => {
|
||||
eventEmitter.emit("error", e);
|
||||
});
|
||||
|
||||
res.set("Content-Type", "binary/octet-stream");
|
||||
res.set(
|
||||
"Content-Disposition",
|
||||
'attachment; filename="' + currentFile.filename + '"'
|
||||
);
|
||||
res.set("Content-Length", currentFile.metadata.size.toString());
|
||||
if (!range) {
|
||||
res.set("Content-Type", "binary/octet-stream");
|
||||
res.set(
|
||||
"Content-Disposition",
|
||||
'attachment; filename="' + currentFile.filename + '"'
|
||||
);
|
||||
res.set("Content-Length", currentFile.metadata.size.toString());
|
||||
}
|
||||
|
||||
readStream
|
||||
.pipe(decipher)
|
||||
@@ -73,9 +95,14 @@ const proccessData = (res: Response, fileID: string, user: UserInterface) => {
|
||||
return eventEmitter;
|
||||
};
|
||||
|
||||
const getFileData = (res: Response, fileID: string, user: UserInterface) => {
|
||||
const getFileData = (
|
||||
res: Response,
|
||||
fileID: string,
|
||||
user: UserInterface,
|
||||
range?: { start: number; end: number }
|
||||
) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const eventEmitter = proccessData(res, fileID, user);
|
||||
const eventEmitter = proccessData(res, fileID, user, range);
|
||||
eventEmitter.on("finish", (data) => {
|
||||
resolve(data);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import { EventEmitter } from "stream";
|
||||
import { UserInterface } from "../../../models/user-model";
|
||||
import { Response } from "express";
|
||||
import ForbiddenError from "../../../utils/ForbiddenError";
|
||||
import NotFoundError from "../../../utils/NotFoundError";
|
||||
import crypto from "crypto";
|
||||
import { createGenericParams } from "./storageHelper";
|
||||
import { getStorageActions } from "../actions/helper-actions";
|
||||
import FileDB from "../../../db/mongoDB/fileDB";
|
||||
import NotAuthorizedError from "../../../utils/NotAuthorizedError";
|
||||
import UserDB from "../../../db/mongoDB/userDB";
|
||||
|
||||
const fileDB = new FileDB();
|
||||
const userDB = new UserDB();
|
||||
|
||||
const storageActions = getStorageActions();
|
||||
|
||||
const proccessData = (res: Response, fileID: string, tempToken: string) => {
|
||||
const eventEmitter = new EventEmitter();
|
||||
|
||||
const processFile = async () => {
|
||||
try {
|
||||
const file = await fileDB.getPublicFile(fileID);
|
||||
|
||||
if (!file || !file.metadata.link || file.metadata.link !== tempToken) {
|
||||
throw new NotAuthorizedError("File Not Public");
|
||||
}
|
||||
|
||||
if (file.metadata.linkType === "one") {
|
||||
await fileDB.removeOneTimePublicLink(fileID);
|
||||
}
|
||||
|
||||
const user = await userDB.getUserInfo(file.metadata.owner);
|
||||
|
||||
if (!user) throw new NotFoundError("User Not Found");
|
||||
|
||||
const password = user.getEncryptionKey();
|
||||
|
||||
if (!password) throw new ForbiddenError("Invalid Encryption Key");
|
||||
|
||||
const IV = file.metadata.IV;
|
||||
|
||||
const readStreamParams = createGenericParams({
|
||||
filePath: file.metadata.filePath,
|
||||
Key: file.metadata.s3ID,
|
||||
});
|
||||
|
||||
const readStream = storageActions.createReadStream(readStreamParams);
|
||||
|
||||
const CIPHER_KEY = crypto.createHash("sha256").update(password).digest();
|
||||
|
||||
const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV);
|
||||
|
||||
decipher.on("error", (e: Error) => {
|
||||
eventEmitter.emit("error", e);
|
||||
});
|
||||
|
||||
readStream.on("error", (e: Error) => {
|
||||
eventEmitter.emit("error", e);
|
||||
});
|
||||
|
||||
res.on("error", (e: Error) => {
|
||||
eventEmitter.emit("error", e);
|
||||
});
|
||||
|
||||
res.set("Content-Type", "binary/octet-stream");
|
||||
res.set(
|
||||
"Content-Disposition",
|
||||
'attachment; filename="' + file.filename + '"'
|
||||
);
|
||||
res.set("Content-Length", file.metadata.size.toString());
|
||||
|
||||
readStream
|
||||
.pipe(decipher)
|
||||
.pipe(res)
|
||||
.on("finish", () => {
|
||||
eventEmitter.emit("finish");
|
||||
});
|
||||
} catch (e) {
|
||||
eventEmitter.emit("error", e);
|
||||
}
|
||||
};
|
||||
|
||||
processFile();
|
||||
|
||||
return eventEmitter;
|
||||
};
|
||||
|
||||
const getPublicFileData = (
|
||||
res: Response,
|
||||
fileID: string,
|
||||
tempToken: string
|
||||
) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const eventEmitter = proccessData(res, fileID, tempToken);
|
||||
eventEmitter.on("finish", (data) => {
|
||||
resolve(data);
|
||||
});
|
||||
eventEmitter.on("error", (e) => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export default getPublicFileData;
|
||||
Reference in New Issue
Block a user