Compare commits

...

22 Commits

Author SHA1 Message Date
lamp da1b0ab58c no 405 2023-03-03 02:39:46 -06:00
lamp ff8b626b8a rename env var 2022-01-18 15:38:30 -06:00
lamp e7b770b177 tweak 2022-01-18 15:29:15 -06:00
lamp 5d203d2ca7 tweak 2022-01-18 15:24:22 -06:00
lamp 4e99509d96 Merge branch 'master' of gitea.moe:lamp/qonq 2021-12-05 02:40:36 -06:00
lamp 16afa0abc5 add antiscrape 2021-12-05 02:40:33 -06:00
lamp 794b84a152 Update 'README.md' 2021-12-05 01:18:50 -06:00
lamp c09993d9bc simple binary upload instead of form 2021-12-05 01:17:35 -06:00
lamp 2c0d3ef78e rename HOSTNAME env because conflict 2021-12-05 00:44:10 -06:00
lamp 2448d69f1f hmm 2021-12-03 16:51:58 -06:00
lamp 7f57c0ed23 Merge branch 'master' of gitea.moe:lamp/qonq 2021-10-22 17:43:22 -05:00
lamp 292ff9297d custom url gen 2021-10-22 17:42:17 -05:00
lamp 9897bcd577 Update 'README.md' 2021-10-22 16:51:20 -05:00
lamp 847950c726 favicon 2021-10-22 16:49:31 -05:00
lamp 6da1f4d556 404 handler 2021-10-22 16:30:40 -05:00
lamp a4e44fe884 support filecode in url 2021-10-22 16:22:16 -05:00
lamp f7971fef0f Merge branch 'master' of gitea.moe:lamp/qonq 2021-10-22 15:10:24 -05:00
lamp 96436fe340 async refactor 2021-10-22 15:10:18 -05:00
lamp 7f44d5b676 some refactor
also fixed trust proxy bug
2021-10-22 14:40:07 -05:00
lamp 9eed8a87d5 we don't need to care about the post url 2021-10-22 14:06:53 -05:00
lamp 312c95adb7 update gitignore 2021-10-22 14:04:54 -05:00
lamp 1b1601fc7c vscode dont track files 2021-10-22 14:00:56 -05:00
10 changed files with 149 additions and 85 deletions
+11 -6
View File
@@ -1,6 +1,11 @@
files/*
node_modules
*.log
!.gitkeep
ecosystem.config.js
.env
*
!/.vscode
!/.gitignore
!/qonq.js
!/discord-preloader.js
!/package.json
!/package-lock.json
!/README.md
!/files/www/qonq.js
!/favicon.ico
!/antiscrape.js
+5
View File
@@ -0,0 +1,5 @@
{
"files.watcherExclude": {
"**/files/**": true
}
}
+12 -11
View File
@@ -2,7 +2,7 @@
Simple file host that puts the file code in the hostname. The leftmost part of the Host (i.e. `1234` of `1234.qonq.gq`) is used to serve a corresponding folder (i.e. `files/1234/`); if the folder contains one file then it is served directly, but if it contains multiple files then it is served as a webroot with a directory index. Designed for use as a ShareX Custom Uploader, but you can drop any lowercase alphanumeric folder into the files directory to serve it instantly.
Note: Please refrain from using your own file server if you cannot commit to maintaining it indefinitely, because all links you post will depend on your server and dead servers cause link rot.
You can also put the file code in the URL of the main domain. For example, https://1234.qonq.gq/ and https://qonq.gq/1234/ are the same. If there is no code in either the URL or subdomain, then the default code is `www`, which means https://www.qonq.gq/ and https://qonq.gq/ are the same, and you can put content in `files/www` for it (`index.html` etc).
## Usage
Clone repository and install dependencies with `npm ci`.
@@ -17,12 +17,13 @@ The following environment variables are optional.
| Variable | Description | Default |
|----------|-------------|---------|
| `HOSTNAMES` | Comma-separated list of hostnames that will be chosen at random for the URL sent back after an upload. | request hostname |
| `DISCORD_WEBHOOK` | Discord webhook url that uploads will be sent to to make them pre-load the embed, which theoretically makes it faster. | undefined (disabled) |
| `PORT` | TCP port to listen on | 8568 |
| `ADDRESS` | Address to bind to | 0.0.0.0 (all) |
| `FILES_DIR` | Directory to store the files in | files (relative of working directory) |
| `TMP_DIR` | Directory to store pending uploads in | os.tmpdir() |
| `BASE_HOSTNAME` | Overrides the hostname for generating URLs. | host header on the upload request |
| `DISCORD_WEBHOOK` | Discord webhook url that newly-uploaded URLs will be sent to to pre-load their embed, which theoretically makes them embed again faster. | `undefined` (disabled) |
| `PORT` | TCP port to listen on | `8568` |
| `ADDRESS` | Address to bind to | `'0.0.0.0'` (all) |
| `FILES_DIR` | Directory to store the files in | `'files'` (relative of working directory) |
| `TRUST_PROXY` | Value for express's [`'trust proxy'`](https://expressjs.com/en/5x/api.html#trust.proxy.options.table) setting | `'loopback'` |
Run with `node qonq.js` or your favorite init system.
@@ -33,12 +34,12 @@ Run with `node qonq.js` or your favorite init system.
"Version": "12.4.1",
"DestinationType": "ImageUploader, TextUploader, FileUploader",
"RequestMethod": "POST",
"RequestURL": "http://localhost:8568/upload",
"RequestURL": "https://qonq.gq/",
"Headers": {
"authentication": "paste value of AUTH_TOKEN here"
"authentication": "paste value of AUTH_TOKEN here",
"filename": "$filename$"
},
"Body": "MultipartFormData",
"FileFormName": "file"
"Body": "Binary"
}
```
You can import this custom uploader config and modify the Request URL and authentication fields as necessary.
+15
View File
@@ -0,0 +1,15 @@
// someone could iterate over all ~1.6 million possible file codes to download all files.
// prevent this by banning IP addresses that request too many non-existant files.
var ip404 = {};
module.exports = (req, res, next) => {
if (ip404[req.ip]?.size > 10)
return res.status(403).send("Banned");
res.on("finish", () => {
if (res.statusCode == 404 && req.filecode) {
if (!ip404[req.ip]) ip404[req.ip] = new Set();
ip404[req.ip].add(req.filecode);
}
});
next();
};
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 734 B

-1
View File
@@ -1 +0,0 @@
+1
View File
@@ -0,0 +1 @@
../../qonq.js
+50 -14
View File
@@ -8,7 +8,7 @@
"colors": "^1.4.0",
"dotenv": "^10.0.0",
"express": "^4.17.1",
"formidable": "^1.2.2",
"serve-favicon": "^2.5.0",
"serve-index": "^1.9.1"
}
},
@@ -214,14 +214,6 @@
"node": ">= 0.8"
}
},
"node_modules/formidable": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz",
"integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==",
"funding": {
"url": "https://ko-fi.com/tunnckoCore/commissions"
}
},
"node_modules/forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@@ -445,6 +437,31 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
},
"node_modules/serve-favicon": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.5.0.tgz",
"integrity": "sha1-k10kDN/g9YBTB/3+ln2IlCosvPA=",
"dependencies": {
"etag": "~1.8.1",
"fresh": "0.5.2",
"ms": "2.1.1",
"parseurl": "~1.3.2",
"safe-buffer": "5.1.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/serve-favicon/node_modules/ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
},
"node_modules/serve-favicon/node_modules/safe-buffer": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
},
"node_modules/serve-index": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
@@ -717,11 +734,6 @@
"unpipe": "~1.0.0"
}
},
"formidable": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz",
"integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q=="
},
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@@ -890,6 +902,30 @@
}
}
},
"serve-favicon": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.5.0.tgz",
"integrity": "sha1-k10kDN/g9YBTB/3+ln2IlCosvPA=",
"requires": {
"etag": "~1.8.1",
"fresh": "0.5.2",
"ms": "2.1.1",
"parseurl": "~1.3.2",
"safe-buffer": "5.1.1"
},
"dependencies": {
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
},
"safe-buffer": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
}
}
},
"serve-index": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
+1 -1
View File
@@ -3,7 +3,7 @@
"colors": "^1.4.0",
"dotenv": "^10.0.0",
"express": "^4.17.1",
"formidable": "^1.2.2",
"serve-favicon": "^2.5.0",
"serve-index": "^1.9.1"
}
}
+44 -42
View File
@@ -1,20 +1,19 @@
require("dotenv").config();
process.env.NODE_ENV = "production";
var colors = require("colors");
var express = require("express");
var formidable = require("formidable");
var serveIndex = require("serve-index");
var serveFavicon = require("serve-favicon");
var path = require("path");
var fs = require("fs");
try { var customGen = require("./customGen"); } catch(e) {}
var FILES_DIR = process.env.FILES_DIR || "files";
if (process.env.HOSTNAMES) {
var HOSTNAMES = process.env.HOSTNAMES.split(',');
var randomHostname = () => HOSTNAMES[Math.floor(Math.random() * HOSTNAMES.length)];
}
var app = express();
app.enable('trust proxy', '127.0.0.1');
app.set("env", "production");
app.set('trust proxy', process.env.TRUST_PROXY || "loopback");
app.listen(process.env.PORT || 8568, process.env.ADDRESS);
app.use((req,res,next) => { res.header("Access-Control-Allow-Origin", '*'); next(); });
app.use((req, res, next)=>{
var d = new Date;
@@ -25,38 +24,48 @@ app.use((req, res, next)=>{
next();
});
app.post("/upload", (req, res, next) => {
if (req.headers.authentication != process.env.AUTH_TOKEN) return res.status(403).send("Unauthorized");
var form = new formidable.IncomingForm({
maxFileSize: 2**30, // 1 GiB
maxFields: 1,
uploadDir: process.env.TMP_DIR
});
form.parse(req, function(err, fields, files) {
if (err) return next(err);
var file = files.file;
if (!file) return res.sendStatus(400);
do {
var filecode = Math.random().toString(36).slice(2).substring(0,4);
} while ( fs.existsSync(path.join(FILES_DIR, filecode)) && !console.log("oof") );
try {
fs.mkdirSync(path.join(FILES_DIR, filecode));
fs.renameSync(file.path, path.join(FILES_DIR, filecode, file.name));
} catch(e) {
return next(e);
app.use(serveFavicon("favicon.ico"));
app.post("*", (req, res, next) => {
if (req.headers.authentication != process.env.AUTH_TOKEN) return void res.status(403).send("Unauthorized");
(function trymkdir() {
if (customGen) {
var g = customGen(req);
var filecode = g.next().value;
}
let url = `${req.protocol}://${filecode}.${randomHostname?.() || req.hostname}`;
res.send(url);
filecode ||= Math.random().toString(36).slice(2).substring(0,4);
var webroot = path.join(FILES_DIR, filecode);
fs.mkdir(webroot, function (error) {
if (error) if (error.code == "EEXIST") {
console.log("oof");
trymkdir();
} else next(error);
else {
var filename = req.headers.filename || "file";
var filepath = path.join(webroot, filename);
req.pipe(fs.createWriteStream(filepath));
req.on("close", () => {
var url = g?.next().value || `${req.protocol}://${filecode}.${process.env.BASE_HOSTNAME || req.hostname}`;
res.type('text').send(url);
require("./discord-preloader.js")(url);
});
}
});
})();
});
app.get("*", function(req, res, next){
let filecode = req.hostname.split('.')[0]; // for home page just create file in directory named as the last level of base hostname
let webroot = path.join(FILES_DIR, filecode);
let webrootdirlist = fs.readdirSync(webroot);
app.use(require("./antiscrape"));
app.get(['/', '/:code/', '/:code/*'], function(req, res, next){
var subdomain = req.subdomains.at(-1);
req.filecode = subdomain || req.params.code;
if (!req.filecode) req.filecode = "www";
var webroot = path.join(FILES_DIR, req.filecode);
fs.readdir(webroot, function(error, webrootdirlist) {
if (error) return void next(error.code == "ENOENT" ? "route" : error);
if (webrootdirlist.length > 1) {
req.url = path.join(filecode, req.url);
if (subdomain) req.url = path.join(req.filecode, req.url);
next();
} else if (webrootdirlist.length == 1) {
res.sendFile(webrootdirlist[0], {
@@ -66,12 +75,5 @@ app.get("*", function(req, res, next){
} else {
res.sendStatus(204);
}
});
app.get("*", express.static(FILES_DIR), serveIndex(FILES_DIR, {icons: true}));
app.use(function (error, req, res, next) {
res.status(error.code == "ENOENT" ? 404 : console.error(error.stack) || 500).type("text/plain").send(error.toString());
});
app.listen(process.env.PORT || 8568, process.env.ADDRESS);
});
}, express.static(FILES_DIR), serveIndex(FILES_DIR, {icons: true}));