From ed8c61ea955e12541ce6ffda6a64cf958ea13dfd Mon Sep 17 00:00:00 2001 From: Hajamieli Date: Sat, 12 Aug 2023 06:15:32 +0300 Subject: [PATCH] upload --- .dockerignore | 7 ++ .gitignore | 5 ++ Dockerfile | 10 +++ fileTypes.ts | 19 +++++ getFileHash.ts | 16 ++++ mongodb.ts | 10 +++ routes.ts | 219 +++++++++++++++++++++++++++++++++++++++++++++++++ server.ts | 34 ++++++++ 8 files changed, 320 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 fileTypes.ts create mode 100644 getFileHash.ts create mode 100644 mongodb.ts create mode 100644 routes.ts create mode 100644 server.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d67a29b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +.git +.gitignore +readme.md +build +.env +./uploads +./tmp \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f3086e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +uploads/* +tmp/* +.env +site +site/* \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6455a9a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM denoland/deno:latest as base + +WORKDIR /app + +COPY . ./ + +RUN deno cache server.ts + +CMD ["run", "--allow-net", "--allow-write", "--allow-read", "--allow-env", "server.ts"] + diff --git a/fileTypes.ts b/fileTypes.ts new file mode 100644 index 0000000..de099e3 --- /dev/null +++ b/fileTypes.ts @@ -0,0 +1,19 @@ + +const allowedFiletypes = new Map() +allowedFiletypes.set("image/jpeg", ".jpg") +allowedFiletypes.set("image/png", ".png") +allowedFiletypes.set("image/avif", ".avif") +allowedFiletypes.set("image/gif", ".gif") +allowedFiletypes.set("image/webp", ".webp") +allowedFiletypes.set("image/apng", ".apng") +allowedFiletypes.set("image/bmp", ".bmp") +allowedFiletypes.set("image/svg+xml", ".svg") +allowedFiletypes.set("image/tiff", ".tiff") +allowedFiletypes.set("video/webm", ".webm") +allowedFiletypes.set("video/mp4",".mp4") +allowedFiletypes.set("video/mpeg",".mpeg") +allowedFiletypes.set("video/x-msvideo", ".avi") +allowedFiletypes.set("application/epub+zip", ".epub") +allowedFiletypes.set("application/pdf", ".pdf") + +export default allowedFiletypes \ No newline at end of file diff --git a/getFileHash.ts b/getFileHash.ts new file mode 100644 index 0000000..9312167 --- /dev/null +++ b/getFileHash.ts @@ -0,0 +1,16 @@ +import { crypto, toHashString } from 'https://deno.land/std@0.176.0/crypto/mod.ts'; + +const getFileBuffer = (filePath: string) => { + const file = Deno.openSync(filePath); + const buf = new Uint8Array(file.statSync().size); + file.readSync(buf); + file.close(); + return buf; +}; + +const getBuffer = (data: BufferSource) => toHashString(crypto.subtle.digestSync('SHA-256', data)); + +const getFileHash = (filePath: string) => getBuffer(getFileBuffer(filePath)); + + +export default getFileHash diff --git a/mongodb.ts b/mongodb.ts new file mode 100644 index 0000000..82ba191 --- /dev/null +++ b/mongodb.ts @@ -0,0 +1,10 @@ +import { Bson, MongoClient } from "https://deno.land/x/mongo@v0.31.1/mod.ts"; +import "https://deno.land/std@0.178.0/dotenv/load.ts"; + +const client = new MongoClient(); + +await client.connect(Deno.env.get("MONGOURL")); + +const db = client.database('hajabot') + +export default db; \ No newline at end of file diff --git a/routes.ts b/routes.ts new file mode 100644 index 0000000..0f2d1c8 --- /dev/null +++ b/routes.ts @@ -0,0 +1,219 @@ +import { RouterContext } from "https://deno.land/x/oak/mod.ts" +import "https://deno.land/std@0.178.0/dotenv/load.ts"; +import { writeFileSync, copy } from "https://deno.land/std/fs/mod.ts"; +import { renderFileToString } from "https://deno.land/x/dejs/mod.ts"; + +import getFileHash from './getFileHash.ts'; +import allowedFiletypes from './fileTypes.ts'; +import db from './mongodb.ts'; +const imageCollection = db.collection('images'); +const userTable = db.collection('users'); + + + +export const icons = async (ctx: RouterContext, next: any) => { + const identifier = ctx.params.filename + + console.log(["t_192.png", "t_48.png", "t_96.png"].indexOf(identifier)) + + if (["t_192.png", "t_48.png", "t_96.png"].indexOf(identifier) != -1){ + const img = await Deno.readFile(`${Deno.cwd()}/site/i/${ctx.params.filename}`); + ctx.response.type = "image/png"; + ctx.response.body = img; + } else { + next() + } +} + +export const announce = async (ctx: RouterContext) => { + const body = await ctx.request.body({ type: 'json'}).value; + + const count = await imageCollection.findOne({hash:body.hash}); + + if(body.size >= 50000000 && body.usercode == "anonymous"){ + ctx.response.status = 403 + } + else if(count){ + ctx.response.body = {file: count.filename}; + ctx.response.status = 409 + }else{ + if(body.size >= 50000000){ + const users = await userTable.findOne({token:body.usercode}); + if(users){ + ctx.response.status = 200 + }else{ + ctx.response.status = 403 + } + }else{ + ctx.response.status = 200 + } + } +} + +export const chunks = async (ctx: RouterContext) => { + const hash = ctx.request.headers.get("file-hash"); + const number = ctx.request.headers.get("file-hash"); + + const body = await ctx.request.body({ type: 'form-data'}); + const formData = await body.value.read(); + + const size = ctx.request.headers.get("content-range").split("/")[1] + const data = await Deno.readFile(formData.files[0].filename) + + Deno.writeFileSync(`${Deno.env.get("TMPPATH")}${hash}.${ctx.request.headers.get("content-number")}.temp`, data) + + ctx.response.status = 200 +} + +export const mergeChunks = async (ctx: RouterContext) => { + const body = await ctx.request.body({ type: 'json'}).value; + + const ts = new Date(); + let newname = `${ts.valueOf()}${allowedFiletypes.get(body.type)}` + + let files = [] + for await (const dirEntry of Deno.readDir(Deno.env.get("TMPPATH"))) { + if(dirEntry.name.includes(body.hash) && dirEntry.isFile){ + files.push(dirEntry.name) + } + + } + + const tempFile = await Deno.makeTempFile(); + + for (let i = 0; i < files.length; i++) { + let file = files.find(elem => elem.includes(`.${i}.temp`)) + const data = await Deno.readFileSync(`${Deno.env.get("TMPPATH")}${file}`) + + await Deno.writeFileSync(tempFile, data, {"append": true}) + Deno.remove(`${Deno.env.get("TMPPATH")}${file}`); + } + + await copy(tempFile, `${Deno.env.get("IMAGEPATH")}${newname}`) + + const id = await imageCollection.insertOne({ + hash:body.hash, + mime:body.type, + filename:newname, + usercode:body.usercode, + time:ts + }); + + + const newhash = await getFileHash(tempFile) + if(body.hash == newhash){ + ctx.response.body = {"file":newname} + ctx.response.status = 201 + }else{ + ctx.response.status = 418 + } +} + +export const getImage = async (ctx: RouterContext) => { + const identifier = ctx.params.filename; + + const info = await imageCollection.findOne({filename:identifier}); + + if(!info){ + ctx.response.status = 404; + ctx.response.body = {message: 'image does not exist'}; + return + }else{ + const img = await Deno.readFile(`${Deno.env.get("IMAGEPATH")}${identifier}`); + + ctx.response.type = info.mime; + ctx.response.body = img; + } + +} + +export const deleteImage = async (ctx: RouterContext) => { + const identifier = ctx.params.filename; + + const count = await imageCollection.findOne({filename:identifier}); + + if(!count){ + ctx.response.status = 404; + ctx.response.body = {message: 'image does not exist'}; + return + }else{ + console.log(count) + + if(ctx.request.headers.get("usercode")==count.usercode){ + const deleted = await imageCollection.deleteOne({_id:count._id}) + if(!deleted){ + ctx.response.status = 500; + }else{ + await Deno.remove(`${Deno.env.get("IMAGEPATH")}${count.filename}`); + ctx.response.status = 204 + } + + }else{ + ctx.response.body = {message: 'Unauthorized'}; + ctx.response.status = 401 + + } + } +} + +export const uploadImage = async (ctx: RouterContext) => { + const body = await ctx.request.body({ type: 'form-data'}).value; + const formData = await body.read(); + + const usercode = ctx.request.headers.get("usercode") ? ctx.request.headers.get("usercode") : "anonymous"; + + let promises = []; + let files = []; + + formData.files.forEach(file => promises.push(new Promise(async (resolve, reject)=>{ + if(!allowedFiletypes.get(file.contentType)){ + reject("rejected filetype") + }else{ + const timestamp = new Date() + const hash = getFileHash(file.filename) + + const count = await imageCollection.findOne({hash:hash}); + + if(!count){ + const newFilename = `${timestamp.valueOf()}${allowedFiletypes.get(file.contentType)}` + copy(file.filename, `${Deno.env.get("IMAGEPATH")}${newFilename}`, {overwrite: true}) + const id = await imageCollection.insertOne({ + hash:hash, + mime:file.contentType, + filename:newFilename, + usercode:usercode, + time:timestamp + }); + + files.push(newFilename) + resolve(newFilename) + }else{ + files.push(count.filename) + resolve(count.filename) + } + } + }))) + + const result = await Promise.allSettled(promises) + + const rejected = await result.filter(obj => Object.keys(obj).some(key => obj[key].includes("rejected"))) + + if(rejected.length >= 1){ + ctx.response.status = 405; + ctx.response.body = {message:rejected[0].reason} + }else{ + if(files.length > 1){ + ctx.response.body = files + }else{ + ctx.response.body = {"img":files[0]} + } + + ctx.response.status = 201 + } +} + + + + + + diff --git a/server.ts b/server.ts new file mode 100644 index 0000000..ed82960 --- /dev/null +++ b/server.ts @@ -0,0 +1,34 @@ +import { Application, Router } from "https://deno.land/x/oak/mod.ts"; +import { getImage, uploadImage, deleteImage, chunks, mergeChunks, announce, icons} from './routes.ts'; +import "https://deno.land/std@0.178.0/dotenv/load.ts"; + + +const router = new Router(); + +router.get('/', async(ctx: RouterContext, next: any) => { + try { + await ctx.send({ + root: `${Deno.cwd()}/site`, + index: "index.html" + }) + } catch { + await next() + } + +}) +.get('/i/:filename', icons) +.post('/upload', uploadImage) +.post('/announce', announce) +.post('/uploadchunk', chunks) +.post('/finish', mergeChunks) +.delete('/image/:filename', deleteImage) +.get('/image/:filename', getImage) + + +const app = new Application(); + +app.use(router.routes()) +app.use(router.allowedMethods()); + +app.listen({ port: Deno.env.get("PORT") }); +console.log(`Server is running on port ${Deno.env.get("PORT")}`)