add
This commit is contained in:
commit
d0538907ab
|
@ -0,0 +1,5 @@
|
|||
files/
|
||||
node_modules/
|
||||
tmpfiles.db
|
||||
tmpfiles.db-shm
|
||||
tmpfiles.db-wal
|
|
@ -0,0 +1,262 @@
|
|||
import { rename, readFile, unlink, mkdir, mkdtemp, rmdir } from 'fs/promises';
|
||||
import { createWriteStream } from 'fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { promisify } from 'util';
|
||||
import { pipeline } from 'stream';
|
||||
import filenamify from "filenamify";
|
||||
import Fastify from 'fastify'
|
||||
import FastifyMultipartPlugin from '@fastify/multipart'
|
||||
import FastifyStaticPlugin from '@fastify/static'
|
||||
import Database from 'better-sqlite3';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
import {fileTypeFromFile} from 'file-type';
|
||||
import {filesize} from "filesize";
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime.js'
|
||||
dayjs.extend(relativeTime, {
|
||||
thresholds: [
|
||||
{ l: 's', r: 1 },
|
||||
{ l: 'm', r: 1 },
|
||||
{ l: 'mm', r: 59, d: 'minute' },
|
||||
{ l: 'h', r: 1 },
|
||||
{ l: 'hh', r: 23, d: 'hour' },
|
||||
{ l: 'd', r: 1 },
|
||||
{ l: 'dd', r: 29, d: 'day' },
|
||||
{ l: 'M', r: 1 },
|
||||
{ l: 'MM', r: 11, d: 'month' },
|
||||
{ l: 'y', r: 1 },
|
||||
{ l: 'yy', d: 'year' }
|
||||
]
|
||||
});
|
||||
const fastify = Fastify({logger: true, bodyLimit: 104857600, trustProxy: '127.0.0.1' });
|
||||
const db = new Database('tmpfiles.db', { verbose: console.log });
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.exec("CREATE TABLE IF NOT EXISTS tmpfiles (filename CHAR, size INT, filetype CHAR, visits INT, visitsMax INT, expiry TEXT)");
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const pump = promisify(pipeline);
|
||||
const notfound = await readFile(join(__dirname, "./templates/404.html"));
|
||||
const statement = db.prepare(`INSERT INTO tmpfiles(filename, size, filetype, visits, visitsMax, expiry) VALUES(?, ?, ?, ?, ?, ?)`);
|
||||
const download = await readFile(join(__dirname, "/templates/download.html"));
|
||||
const api = await readFile(join(__dirname, "/templates/api.html"));
|
||||
|
||||
fastify.register(FastifyMultipartPlugin);
|
||||
fastify.register(FastifyStaticPlugin, {
|
||||
root: join(__dirname, 'public'),
|
||||
wildcard: false
|
||||
});
|
||||
|
||||
function makeDLTemplate(file, id, hostname) {
|
||||
var response = download.toString()
|
||||
.replace("__DLHOST__", hostname)
|
||||
.replaceAll("__DLURL__", file.filename)
|
||||
.replace("__DSIZE__", filesize(file.size))
|
||||
.replaceAll("__DID__", id)
|
||||
.replace("__DTIME__", dayjs.unix(file.expiry).fromNow(true));
|
||||
|
||||
if(file.filetype.startsWith("image/")) {
|
||||
response = response.replace("__DPREV__", `<img id="img_preview" src="/dl/${id}/${file.filename}">`);
|
||||
}else{
|
||||
response = response.replace("__DPREV__", "");
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
fastify.get('/api.html', (request, reply) => {
|
||||
return reply.status(200).type("text/html").send(api.toString().replaceAll("__APIHOST__", `${request.protocol}://${request.hostname}`))
|
||||
})
|
||||
|
||||
fastify.get('/:id/:filename', (request, reply) => {
|
||||
if(!/^\d+$/.test(request.params["id"])) {
|
||||
reply.code(400);
|
||||
return reply.send("Bad Request")
|
||||
}
|
||||
|
||||
const id = request.params["id"];
|
||||
const filename = filenamify(request.params["filename"]);
|
||||
const file = db.prepare("SELECT * FROM tmpfiles WHERE rowid = ? AND filename = ?").get(id, filename);
|
||||
if(typeof file === "undefined") {
|
||||
return reply.status(404).type("text/html").send(notfound);
|
||||
}
|
||||
|
||||
const expired = dayjs() > dayjs.unix(file.expiry) || (file.visits >= file.visitsMax && file.visitsMax);
|
||||
if(expired) {
|
||||
return reply.status(404).type("text/html").send(notfound);
|
||||
}
|
||||
|
||||
return reply.status(200).type("text/html").send(makeDLTemplate(file, id, `${request.protocol}://${request.hostname}`));
|
||||
});
|
||||
|
||||
fastify.get('/dl/:id/:filename', (request, reply) => {
|
||||
if(!/^\d+$/.test(request.params["id"])) {
|
||||
reply.code(400);
|
||||
return reply.send("Bad Request")
|
||||
}
|
||||
|
||||
const id = request.params["id"];
|
||||
const filename = filenamify(request.params["filename"]);
|
||||
const file = db.prepare("SELECT * FROM tmpfiles WHERE rowid = ? AND filename = ?").get(id, filename);
|
||||
if(typeof file === "undefined") {
|
||||
return reply.status(404).type("text/html").send(notfound);
|
||||
}
|
||||
|
||||
const expired = dayjs() > dayjs.unix(file.expiry) || (file.visits >= file.visitsMax && file.visitsMax);
|
||||
if(expired) {
|
||||
return reply.status(404).type("text/html").send(notfound);
|
||||
}
|
||||
|
||||
db.prepare("UPDATE tmpfiles SET visits = ? WHERE rowid = ? AND filename = ?").run(file.visits + 1, id, filename);
|
||||
return reply.header("Content-Disposition", "attachment").sendFile(filename, join(__dirname, "files", id));
|
||||
});
|
||||
|
||||
fastify.post('/api/v1/upload', async (req, reply) => {
|
||||
if(!req.headers['content-length'] || !req.isMultipart) {
|
||||
reply.code(400);
|
||||
return reply.send({status: "error", message: "Bad Request, make sure your request matches the API documentation."});
|
||||
}else if(Number(req.headers['content-length']) > req.routeOptions.bodyLimit) {
|
||||
reply.code(413);
|
||||
return reply.send({status: "error", message: "Request Entity Too Large"});
|
||||
}
|
||||
|
||||
const data = await req.file();
|
||||
if(data == null) {
|
||||
reply.code(400);
|
||||
return reply.send({status: "error", message: "Bad Request, the file field is required."});
|
||||
}
|
||||
|
||||
const filename = filenamify(data.filename);
|
||||
const tempdir = await mkdtemp(join(tmpdir(), 'tmpfiles-'));
|
||||
const pending = join(tempdir, filename);
|
||||
await pump(data.file, createWriteStream(pending));
|
||||
if (data.file.truncated) {
|
||||
// This shouldn't happen given the check above
|
||||
await unlink(pending);
|
||||
reply.code(413);
|
||||
return reply.send({status: "error", message: "Request Entity Too Large"});
|
||||
}else{
|
||||
var exp;
|
||||
try {
|
||||
exp = Number(data.fields["max_time"].value);
|
||||
}catch{
|
||||
exp = 60;
|
||||
};
|
||||
if(Number.isNaN(exp) || exp < 1 || exp > 120) {
|
||||
await unlink(pending);
|
||||
reply.code(400);
|
||||
return reply.send({status: "error", message: "Bad Request, max_views must be between 1 and 120."});
|
||||
}
|
||||
|
||||
var max;
|
||||
try {
|
||||
max = Number(data.fields["max_views"].value);
|
||||
}catch{
|
||||
max = 60;
|
||||
};
|
||||
if(Number.isNaN(max) || max < 0 || max > 1000) {
|
||||
await unlink(pending);
|
||||
reply.code(400);
|
||||
return reply.send({status: "error", message: "Bad Request, max_views must be between 0 and 1000."});
|
||||
}
|
||||
|
||||
var mime;
|
||||
const filetype = await fileTypeFromFile(pending);
|
||||
if(typeof filetype === "undefined") {
|
||||
mime = "application/octet-stream";
|
||||
}else{
|
||||
mime = filetype.mime;
|
||||
}
|
||||
|
||||
const expiry = dayjs().add(exp, "minutes").unix();
|
||||
const record = statement.run(filename, Number(req.headers['content-length']), mime, 0, max, Number(expiry));
|
||||
|
||||
if(record.changes) {
|
||||
await mkdir(join(__dirname, "files", record.lastInsertRowid.toString()));
|
||||
await rename(pending, join(__dirname, "files", record.lastInsertRowid.toString(), filename));
|
||||
await rmdir(dirname(pending));
|
||||
return reply.send({status: "success", data: { url: `${req.protocol}://${req.hostname}/${record.lastInsertRowid}/${filename}`}});
|
||||
}else{
|
||||
return reply.status(500).type("text/plain").send({status: "error", message: "Internal Server Error"});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fastify.post('/', async (req, reply) => {
|
||||
if(!req.headers['content-length'] || !req.isMultipart) {
|
||||
reply.code(400);
|
||||
return reply.send("Bad Request")
|
||||
}else if(Number(req.headers['content-length']) > req.routeOptions.bodyLimit) {
|
||||
reply.code(413);
|
||||
return reply.send("Request Entity Too Large");
|
||||
}
|
||||
|
||||
const data = await req.file();
|
||||
if(data == null) {
|
||||
reply.code(400);
|
||||
return reply.send("Bad Request");
|
||||
}
|
||||
|
||||
const filename = filenamify(data.filename);
|
||||
const tempdir = await mkdtemp(join(tmpdir(), 'tmpfiles-'));
|
||||
const pending = join(tempdir, filename);
|
||||
await pump(data.file, createWriteStream(pending));
|
||||
if (data.file.truncated) {
|
||||
// This shouldn't happen given the check above
|
||||
await unlink(pending);
|
||||
reply.code(413);
|
||||
return reply.send("Request Entity Too Large");
|
||||
}else{
|
||||
if(!data.fields["max_time"] || !data.fields["max_views"]) {
|
||||
await unlink(pending);
|
||||
reply.code(400);
|
||||
return reply.send("Bad Request")
|
||||
}
|
||||
|
||||
var exp = Number(data.fields["max_time"].value);
|
||||
if(Number.isNaN(exp) || exp < 1 || exp > 120) {
|
||||
await unlink(pending);
|
||||
reply.code(400);
|
||||
return reply.send("Bad Request");
|
||||
}
|
||||
|
||||
var max = Number(data.fields["max_views"].value);
|
||||
if(Number.isNaN(max) || max < 0 || max > 1000) {
|
||||
await unlink(pending);
|
||||
reply.code(400);
|
||||
return reply.send("Bad Request");
|
||||
}
|
||||
|
||||
var mime;
|
||||
const filetype = await fileTypeFromFile(pending);
|
||||
if(typeof filetype === "undefined") {
|
||||
mime = "application/octet-stream";
|
||||
}else{
|
||||
mime = filetype.mime;
|
||||
}
|
||||
|
||||
const expiry = dayjs().add(exp, "minutes").unix();
|
||||
const record = statement.run(filename, Number(req.headers['content-length']), mime, 0, max, Number(expiry));
|
||||
|
||||
if(record.changes) {
|
||||
await mkdir(join(__dirname, "files", record.lastInsertRowid.toString()));
|
||||
await rename(pending, join(__dirname, "files", record.lastInsertRowid.toString(), filename));
|
||||
await rmdir(dirname(pending));
|
||||
return reply.redirect(302, `${req.protocol}://${req.hostname}/${record.lastInsertRowid}/${filename}`);
|
||||
}else{
|
||||
return reply.status(500).type("text/plain").send("Internal Server Error");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fastify.setNotFoundHandler((_, res) => {
|
||||
res.status(404).type("text/html").send(notfound);
|
||||
});
|
||||
|
||||
fastify.setErrorHandler((_, __, res) => {
|
||||
res.status(500).type("text/plain").send("Internal Server Error");
|
||||
});
|
||||
|
||||
fastify.listen({ port: 8080 }, (err, address) => {
|
||||
if (err) throw err;
|
||||
console.log(`Server is now listening on ${address}`);
|
||||
})
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "tmpfilesclone",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"keywords": [],
|
||||
"author": "MDMCK10 (backend), ecovector3 (frontend)",
|
||||
"type": "module",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@fastify/multipart": "^7.7.3",
|
||||
"@fastify/static": "^6.10.2",
|
||||
"better-sqlite3": "^8.5.0",
|
||||
"dayjs": "^1.11.9",
|
||||
"fastify": "^4.21.0",
|
||||
"file-type": "^18.5.0",
|
||||
"filenamify": "^6.0.0",
|
||||
"filesize": "^10.0.12"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>/tmp/files 2.0 - About</title>
|
||||
<meta http-equiv="content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link href="/css/style.css" media="all" rel="stylesheet" type="text/css" />
|
||||
<link href='//fonts.googleapis.com/css?family=Open+Sans+Condensed:300,700' rel='stylesheet' type='text/css' />
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<header>
|
||||
<h1><a href="/">/tmp/files</a></h1>
|
||||
<h2>Because Sometimes, Older Is Better</h2>
|
||||
</header>
|
||||
<section>
|
||||
<p>Suggestions/Questions/Abuse: <a href="mailto:abuse@illegalcybercri.me">abuse@illegalcybercri.me</a></p><br>
|
||||
<p>Are there any restrictions on what files can be uploaded? Yes! The following are NOT allowed: Malware, CSAM, Copyrighted content, spam, etc...</p><br>
|
||||
</section>
|
||||
<footer>
|
||||
<ul>
|
||||
<li><a href="/">Upload</a></li>
|
||||
<li><a href="/api.html">API</a></li>
|
||||
<li><a href="/about.html">About</a></li>
|
||||
<li><a href="https://computernewb.com/collab-vm/">CollabVM</a></li>
|
||||
<li><a href="https://up.kevinthe.horse/">KevinUpload</a></li>
|
||||
</ul>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1 @@
|
|||
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}*{margin:0 auto;padding:0;font-family:'firasans-regular', sans-serif;-webkit-font-smoothing:antialiased}@font-face{font-family:'firasans-light';src:url("/font/FiraSans-Light.ttf") format("truetype");font-weight:400;font-style:normal}@font-face{font-family:'firasans-regular';src:url("/font/FiraSans-Regular.ttf") format("truetype");font-weight:400;font-style:normal}@font-face{font-family:'firasans-medium';src:url("/font/FiraSans-Medium.ttf") format("truetype");font-weight:400;font-style:normal}body{background:#f08080}a{text-decoration:none;color:#fff}a.download{background:#fff;color:#f08080;padding:8px 14px 6px 14px;display:inline-block;margin-top:6px;transition:box-shadow .3s ease-in}a.download:hover{box-shadow:0 0 12px #ff0000}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:#fff}::-webkit-scrollbar-thumb{background:#ff0000}#container{width:360px;margin:62px;padding:24px;text-align:left}#container header h1,#container header h2,#container header h3{line-height:1.3;text-transform:uppercase;color:#fff}#container header h1{font-size:26px;font-family:'firasans-light', sans-serif}#container header h2{font-size:16px;font-family:'firasans-regular', sans-serif}#container section{margin-top:32px}#container section img{border:2px solid #fff;max-width:360px}#container section p{font-family:'firasans-regular', sans-serif;font-size:12px;color:#fff}#container section p.error{color:#ffde42;font-family:'firasans-medium', sans-serif}#container section p.success{color:#3fb663;font-family:'firasans-medium', sans-serif}#container section form div{display:block}form{margin:0}label{display:block;font-size:12px;color:#fff;padding:0;margin:0;margin-top:24px;width:auto;text-align:left;font-family:'firasans-regular', sans-serif}input{width:auto;max-width:220px;padding:4px;padding-left:0;margin:0;outline:none;font-size:14px;border:none;border-bottom:1px solid #fad2d2;border-right:none;border-radius:0;color:#fff;font-weight:300;-webkit-font-smoothing:antialiased;display:block;background:#f08080}input[type="text"]{max-width:320px}input[type="number"]{max-width:100px;width:auto}input[type="submit"]{background:#fff;color:#f08080;padding:8px 14px 6px 14px;border:none;margin:0 auto;margin-top:24px;width:auto;max-width:450px;text-align:center;cursor:pointer;display:inline-block;transition:box-shadow .3s ease-in}input[type="submit"]:hover{box-shadow:0 0 12px #ff0000}footer{margin-top:48px;margin-bottom:24px}footer ul li{display:inline-block}footer ul li a{color:#fff;display:block;padding:6px 12px;font-family:'firasans-regular', sans-serif;font-size:12px}footer ul li a:hover{text-decoration:underline}footer ul li:first-child a{padding-left:0 !important}@media (max-width: 800px){#container{width:auto;margin:0;padding:24px;text-align:left}#container section img{max-width:100%}}
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,46 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>/tmp/files 2.0 - Because Sometimes, Older Is Better</title>
|
||||
<meta http-equiv="content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link href="/css/style.css" media="all" rel="stylesheet" type="text/css" />
|
||||
<link href='//fonts.googleapis.com/css?family=Open+Sans+Condensed:300,700' rel='stylesheet' type='text/css' />
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<header>
|
||||
<h1><a href="/">/tmp/files 2.0</a></h1>
|
||||
<h2>Because Sometimes, Older Is Better</h2>
|
||||
</header>
|
||||
<section>
|
||||
<form action="/" method="post" enctype="multipart/form-data">
|
||||
<div>
|
||||
<label>Select a file (max 100 MB)</label>
|
||||
<input type="file" name="file" required>
|
||||
</div>
|
||||
<div>
|
||||
<label>Download limit (0 = unlimited)</label>
|
||||
<input type="number" name="max_views" value="0" min="0" max="1000" required>
|
||||
</div>
|
||||
<div>
|
||||
<label>Time limit (in minutes, max 120)</label>
|
||||
<input type="number" name="max_time" value="60" min="1" max="120" required>
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Upload" name="upload">
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<ul>
|
||||
<li><a href="/">Upload</a></li>
|
||||
<li><a href="/api.html">API</a></li>
|
||||
<li><a href="/about.html">About</a></li>
|
||||
<li><a href="https://computernewb.com/collab-vm/">CollabVM</a></li>
|
||||
<li><a href="https://up.kevinthe.horse/">KevinUpload</a></li>
|
||||
</ul>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,2 @@
|
|||
User-agent: archive.org_bot
|
||||
Disallow: /
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,40 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>/tmp/files 2.0 - API</title>
|
||||
<meta http-equiv="content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link href="/css/style.css" media="all" rel="stylesheet" type="text/css" />
|
||||
<link href='//fonts.googleapis.com/css?family=Open+Sans+Condensed:300,700' rel='stylesheet' type='text/css' />
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<header>
|
||||
<h1><a href="/">/tmp/files</a></h1>
|
||||
<h2>Because Sometimes, Older Is Better</h2>
|
||||
</header>
|
||||
<section>
|
||||
<p>You can use our API to automate file uploads.</p>
|
||||
<br>
|
||||
<p>Method: POST</p>
|
||||
<p>Params: file=/path/to/test.jpg; max_views=[1 to 1000]; max_time=[1 to 120];</p>
|
||||
<p>Only the file param is required, the rest, if not specified, will be set to the following default values:</p>
|
||||
<p>max_views=0; max_time=60</p>
|
||||
<p>URL: __APIHOST__/api/v1/upload</p>
|
||||
<br>
|
||||
<p>Example with CURL:</p>
|
||||
<p>curl -F "file=@/Users/myuser/test.jpg" -F "max_views=0" -F "max_time=60" __APIHOST__/api/v1/upload</p>
|
||||
<p>This file has no download limit, and will expire in 1 hour.</p>
|
||||
</section>
|
||||
<footer>
|
||||
<ul>
|
||||
<li><a href="/">Upload</a></li>
|
||||
<li><a href="/api.html">API</a></li>
|
||||
<li><a href="/about.html">About</a></li>
|
||||
<li><a href="https://computernewb.com/collab-vm/">CollabVM</a></li>
|
||||
<li><a href="https://up.kevinthe.horse/">KevinUpload</a></li>
|
||||
</ul>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,49 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>/tmp/files 2.0 - __DLURL__</title>
|
||||
<meta http-equiv="content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link href="/css/style.css" media="all" rel="stylesheet" type="text/css" />
|
||||
<link href='//fonts.googleapis.com/css?family=Open+Sans+Condensed:300,700' rel='stylesheet' type='text/css' />
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<header>
|
||||
<h1><a href="/">/tmp/files 2.0</a></h1>
|
||||
<h2>Because Sometimes, Older Is Better</h2>
|
||||
</header>
|
||||
<section>
|
||||
<table style="color: #ffffff; font-size: 12px;">
|
||||
<tr>
|
||||
<th style="width: 80px;">Filename</th>
|
||||
<td>__DLURL__</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Size</th>
|
||||
<td>__DSIZE__</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>URL</th>
|
||||
<td><a target="_blank" href="/dl/__DID__/__DLURL__">__DLHOST__/dl/__DID__/__DLURL__</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Deleted in</th>
|
||||
<td>__DTIME__</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br>__DPREV__
|
||||
<p><a class="download" href="/dl/__DID__/__DLURL__">Download</a></p>
|
||||
</section>
|
||||
<footer>
|
||||
<ul>
|
||||
<li><a href="/">Upload</a></li>
|
||||
<li><a href="/api.html">API</a></li>
|
||||
<li><a href="/about.html">About</a></li>
|
||||
<li><a href="https://computernewb.com/collab-vm/">CollabVM</a></li>
|
||||
<li><a href="https://up.kevinthe.horse/">KevinUpload</a></li>
|
||||
</ul>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue