mirror of
https://github.com/MrUnknownDE/unknownbin.git
synced 2026-04-19 14:23:44 +02:00
add some fancy magic :kekw:
This commit is contained in:
164
.gitignore
vendored
164
.gitignore
vendored
@@ -1,22 +1,148 @@
|
|||||||
# IDE (IntelliJ IDEA)
|
# Created by https://www.toptal.com/developers/gitignore/api/node
|
||||||
.idea
|
# Edit at https://www.toptal.com/developers/gitignore?templates=node
|
||||||
HastebinPlus.iml
|
|
||||||
|
|
||||||
# IDE (Microsoft Visual Studio)
|
### Node ###
|
||||||
obj/
|
# Logs
|
||||||
Microsoft.NodejsTools.WebRole.dll
|
logs
|
||||||
.ntvs_analysis.dat
|
*.log
|
||||||
*.njsproj
|
npm-debug.log*
|
||||||
*.sln
|
yarn-debug.log*
|
||||||
*.suo
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
# Application
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
data
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
node_modules
|
|
||||||
*.min.css
|
|
||||||
*.min.js
|
|
||||||
!jquery.min.js
|
|
||||||
!highlight.min.js
|
|
||||||
|
|
||||||
# NodeJS
|
# Runtime data
|
||||||
npm-debug.log
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
### Node Patch ###
|
||||||
|
# Serverless Webpack directories
|
||||||
|
.webpack/
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
|
||||||
|
# SvelteKit build / generate output
|
||||||
|
.svelte-kit
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/node
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
data/*
|
||||||
47
README.md
47
README.md
@@ -1,6 +1,6 @@
|
|||||||
# Hastebin Plus
|
# unknownBIN
|
||||||
Hastebin Plus is an open-source Pastebin software written in node.js, which is easily installable in any network.
|
unknownBIN is a secure and modern open-source Pastebin software written in node.js.
|
||||||
It bases upon [haste](https://github.com/seejohnrun/haste-server) and got enhanced in matters of **Design, Speed and Simplicity**.
|
It is a fork of the original Hastebin and Hastebin Plus, modernized for security and performance.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
* Paste code, logs and ... almost everything!
|
* Paste code, logs and ... almost everything!
|
||||||
@@ -8,19 +8,23 @@ It bases upon [haste](https://github.com/seejohnrun/haste-server) and got enhanc
|
|||||||
* Add static documents
|
* Add static documents
|
||||||
* Duplicate & edit pastes
|
* Duplicate & edit pastes
|
||||||
* Raw paste-view
|
* Raw paste-view
|
||||||
|
* Secure, unpredictable paste IDs
|
||||||
|
* Modernized backend with security enhancements
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
[](https://heroku.com/deploy?template=https://github.com/MarvinMenzerath/HastebinPlus)
|
1. Install Git and node.js (a recent LTS version is recommended).
|
||||||
|
2. Clone this repository: `git clone https://github.com/MrUnknownDE/unknownbin.git unknownbin`
|
||||||
1. Install Git and node.js: `sudo apt-get install git nodejs`
|
3. Change into the directory: `cd unknownbin`
|
||||||
2. Clone this repository: `git clone https://github.com/MarvinMenzerath/HastebinPlus.git hastebin-plus`
|
|
||||||
3. Open `config.json` and change the settings (if you want to)
|
|
||||||
4. Install dependencies: `npm install`
|
4. Install dependencies: `npm install`
|
||||||
5. Start the application: `npm start`
|
5. Build static assets: `npm run build`
|
||||||
|
6. Open `config.json` and change the settings (if you want to).
|
||||||
|
7. Start the application: `npm start`
|
||||||
|
|
||||||
## Update
|
## Update
|
||||||
1. Pull changes from this repository: `git pull`
|
1. Pull changes from the repository: `git pull`
|
||||||
2. Install new dependencies: `npm install`
|
2. Install new dependencies: `npm install`
|
||||||
|
3. Re-build static assets: `npm run build`
|
||||||
|
4. Restart the application.
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
| Key | Description | Default value |
|
| Key | Description | Default value |
|
||||||
@@ -30,7 +34,7 @@ It bases upon [haste](https://github.com/seejohnrun/haste-server) and got enhanc
|
|||||||
| `dataPath` | The directory where all pastes are stored | `./data` |
|
| `dataPath` | The directory where all pastes are stored | `./data` |
|
||||||
| `keyLength` | The length of the pastes' key | `10` |
|
| `keyLength` | The length of the pastes' key | `10` |
|
||||||
| `maxLength` | Maximum chars in a paste | `500000` |
|
| `maxLength` | Maximum chars in a paste | `500000` |
|
||||||
| `createKey` | Needs to be in front of paste to allow creation | ` ` |
|
| `createKey` | Needs to be in front of paste to allow creation | `""` |
|
||||||
| `documents` | Static documents to serve | See below |
|
| `documents` | Static documents to serve | See below |
|
||||||
|
|
||||||
### Default Config
|
### Default Config
|
||||||
@@ -43,23 +47,6 @@ It bases upon [haste](https://github.com/seejohnrun/haste-server) and got enhanc
|
|||||||
"maxLength": 500000,
|
"maxLength": 500000,
|
||||||
"createKey": "",
|
"createKey": "",
|
||||||
"documents": {
|
"documents": {
|
||||||
"about": "./README.md",
|
"about": "./README.md"
|
||||||
"javaTest": "./documents/test.java"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
## Authors
|
|
||||||
* [haste](https://github.com/seejohnrun/haste-server): John Crepezzi - MIT License
|
|
||||||
* [jQuery](https://github.com/jquery/jquery): MIT License
|
|
||||||
* [highlight.js](https://github.com/isagalaev/highlight.js): Ivan Sagalaev - [License](https://github.com/isagalaev/highlight.js/blob/master/LICENSE)
|
|
||||||
* [Application Icon](https://www.iconfinder.com/icons/285631/notepad_icon): [Paomedia](https://www.iconfinder.com/paomedia) - [CC BY 3.0 License](http://creativecommons.org/licenses/by/3.0/)
|
|
||||||
|
|
||||||
## License
|
|
||||||
Copyright (c) 2014-2016 Marvin Menzerath
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
41
build.js
Normal file
41
build.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const UglifyJS = require('uglify-js');
|
||||||
|
const CleanCSS = require('clean-css');
|
||||||
|
|
||||||
|
console.log('Starting build process...');
|
||||||
|
|
||||||
|
const staticDir = path.join(__dirname, 'static');
|
||||||
|
const files = fs.readdirSync(staticDir);
|
||||||
|
|
||||||
|
files.forEach(item => {
|
||||||
|
const fullPath = path.join(staticDir, item);
|
||||||
|
let destPath;
|
||||||
|
|
||||||
|
if (item.endsWith('.css') && !item.endsWith('.min.css')) {
|
||||||
|
destPath = path.join(staticDir, item.replace('.css', '.min.css'));
|
||||||
|
try {
|
||||||
|
const source = fs.readFileSync(fullPath, 'utf8');
|
||||||
|
const result = new CleanCSS().minify(source);
|
||||||
|
fs.writeFileSync(destPath, result.styles, 'utf8');
|
||||||
|
console.log(`Compressed CSS: ${item} -> ${path.basename(destPath)}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error compressing ${item}:`, err);
|
||||||
|
}
|
||||||
|
} else if (item.endsWith('.js') && !item.endsWith('.min.js')) {
|
||||||
|
destPath = path.join(staticDir, item.replace('.js', '.min.js'));
|
||||||
|
try {
|
||||||
|
const source = fs.readFileSync(fullPath, 'utf8');
|
||||||
|
const result = UglifyJS.minify(source);
|
||||||
|
if (result.error) {
|
||||||
|
throw result.error;
|
||||||
|
}
|
||||||
|
fs.writeFileSync(destPath, result.code, 'utf8');
|
||||||
|
console.log(`Compressed JS: ${item} -> ${path.basename(destPath)}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error compressing ${item}:`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Build process finished.');
|
||||||
@@ -1,118 +1,120 @@
|
|||||||
var logger = require('winston');
|
const KeyGenerator = require('./key_generator.js');
|
||||||
|
|
||||||
var KeyGenerator = require('./key_generator.js');
|
|
||||||
|
|
||||||
// handles creating new and requesting existing documents
|
// handles creating new and requesting existing documents
|
||||||
var DocumentHandler = function(options) {
|
class DocumentHandler {
|
||||||
if (!options) {
|
constructor(options) {
|
||||||
options = {};
|
if (!options) {
|
||||||
}
|
options = {};
|
||||||
this.store = options.store;
|
}
|
||||||
this.maxLength = options.maxLength || 50000;
|
this.store = options.store;
|
||||||
this.keyLength = options.keyLength || 10;
|
this.logger = options.logger;
|
||||||
this.createKey = options.createKey || '';
|
this.maxLength = options.maxLength || 50000;
|
||||||
this.keyGenerator = new KeyGenerator();
|
this.keyLength = options.keyLength || 10;
|
||||||
|
this.createKey = options.createKey || '';
|
||||||
|
this.keyGenerator = new KeyGenerator();
|
||||||
|
|
||||||
if (this.createKey !== '') {
|
if (this.createKey !== '') {
|
||||||
logger.info("Creation-Key:", this.createKey);
|
this.logger.info("Creation-Key is configured.");
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// handles existing documents
|
// handles existing documents
|
||||||
DocumentHandler.prototype.handleGet = function(key, res) {
|
handleGet(key, res) {
|
||||||
this.store.get(key, function(ret) {
|
this.store.get(key, (ret) => {
|
||||||
if (ret) {
|
if (ret) {
|
||||||
logger.verbose('Open paste:', key);
|
this.logger.verbose(`Open paste: ${key}`);
|
||||||
res.writeHead(200, {'content-type': 'application/json'});
|
res.writeHead(200, { 'content-type': 'application/json' });
|
||||||
res.end(JSON.stringify({key: key, data: ret.replace(/\t/g, ' ')}));
|
res.end(JSON.stringify({ key: key, data: ret.replace(/\t/g, ' ') }));
|
||||||
} else {
|
} else {
|
||||||
logger.verbose('Paste not found:', key);
|
this.logger.verbose(`Paste not found: ${key}`);
|
||||||
res.writeHead(404, {'content-type': 'application/json'});
|
res.writeHead(404, { 'content-type': 'application/json' });
|
||||||
res.end(JSON.stringify({message: 'Paste not found.'}));
|
res.end(JSON.stringify({ message: 'Paste not found.' }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
// handles exisiting documents (raw)
|
// handles existing documents (raw)
|
||||||
DocumentHandler.prototype.handleRawGet = function(key, res) {
|
handleRawGet(key, res) {
|
||||||
this.store.get(key, function(ret) {
|
this.store.get(key, (ret) => {
|
||||||
if (ret) {
|
if (ret) {
|
||||||
logger.verbose('Open paste:', key);
|
this.logger.verbose(`Open raw paste: ${key}`);
|
||||||
res.writeHead(200, {'content-type': 'text/plain'});
|
res.writeHead(200, { 'content-type': 'text/plain; charset=utf-8' });
|
||||||
res.end(ret);
|
res.end(ret);
|
||||||
} else {
|
} else {
|
||||||
logger.verbose('Paste not found:', key);
|
this.logger.verbose(`Paste not found: ${key}`);
|
||||||
res.writeHead(404, {'content-type': 'application/json'});
|
res.writeHead(404, { 'content-type': 'application/json' });
|
||||||
res.end(JSON.stringify({message: 'Paste not found.'}));
|
res.end(JSON.stringify({ message: 'Paste not found.' }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
// handles creating new documents
|
// handles creating new documents
|
||||||
DocumentHandler.prototype.handlePost = function(req, res) {
|
handlePost(req, res) {
|
||||||
var _this = this;
|
let buffer = '';
|
||||||
var buffer = '';
|
let cancelled = false;
|
||||||
var cancelled = false;
|
|
||||||
req.on('data', function(data) {
|
|
||||||
if (cancelled) return;
|
|
||||||
buffer += data.toString();
|
|
||||||
if (_this.maxLength && buffer.length > _this.maxLength) {
|
|
||||||
cancelled = true;
|
|
||||||
logger.warn('Paste exeeds maximum length.');
|
|
||||||
res.writeHead(400, {'content-type': 'application/json'});
|
|
||||||
res.end(JSON.stringify({message: 'Paste exceeds maximum length.'}));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
req.on('end', function() {
|
|
||||||
if (cancelled) return;
|
|
||||||
|
|
||||||
if (_this.createKey !== '') {
|
req.on('data', (data) => {
|
||||||
if (!buffer.startsWith(_this.createKey)) {
|
if (cancelled) return;
|
||||||
logger.warn('Error adding new paste: wrong key');
|
buffer += data.toString();
|
||||||
res.writeHead(400, {'content-type': 'application/json'});
|
if (this.maxLength && buffer.length > this.maxLength) {
|
||||||
res.end(JSON.stringify({message: 'Error adding new paste: wrong key'}));
|
cancelled = true;
|
||||||
return;
|
this.logger.warn('Paste exceeds maximum length.');
|
||||||
}
|
res.writeHead(400, { 'content-type': 'application/json' });
|
||||||
buffer = buffer.substring(_this.createKey.length);
|
res.end(JSON.stringify({ message: 'Paste exceeds maximum length.' }));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
_this.chooseKey(function(key) {
|
req.on('end', () => {
|
||||||
_this.store.set(key, buffer, function(success) {
|
if (cancelled) return;
|
||||||
if (success) {
|
|
||||||
logger.verbose('New paste:', key);
|
|
||||||
res.writeHead(200, {'content-type': 'application/json'});
|
|
||||||
res.end(JSON.stringify({key: key}));
|
|
||||||
} else {
|
|
||||||
logger.warn('Error adding new paste.');
|
|
||||||
res.writeHead(500, {'content-type': 'application/json'});
|
|
||||||
res.end(JSON.stringify({message: 'Error adding new paste.'}));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
req.on('error', function(error) {
|
|
||||||
logger.error('Connection error: ' + error.message);
|
|
||||||
res.writeHead(500, {'content-type': 'application/json'});
|
|
||||||
res.end(JSON.stringify({message: 'Connection error.'}));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// creates new keys until one is not taken
|
if (this.createKey !== '') {
|
||||||
DocumentHandler.prototype.chooseKey = function(callback) {
|
if (!buffer.startsWith(this.createKey)) {
|
||||||
var key = this.acceptableKey();
|
this.logger.warn('Error adding new paste: wrong key');
|
||||||
var _this = this;
|
res.writeHead(403, { 'content-type': 'application/json' });
|
||||||
this.store.get(key, function(success) {
|
res.end(JSON.stringify({ message: 'Error adding new paste: wrong key' }));
|
||||||
if (success) {
|
return;
|
||||||
_this.chooseKey(callback);
|
}
|
||||||
} else {
|
buffer = buffer.substring(this.createKey.length);
|
||||||
callback(key);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// creates a new key using the key-generator
|
this.chooseKey((key) => {
|
||||||
DocumentHandler.prototype.acceptableKey = function() {
|
this.store.set(key, buffer, (success) => {
|
||||||
return this.keyGenerator.createKey(this.keyLength);
|
if (success) {
|
||||||
};
|
this.logger.verbose(`New paste: ${key}`);
|
||||||
|
res.writeHead(200, { 'content-type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ key: key }));
|
||||||
|
} else {
|
||||||
|
this.logger.warn('Error adding new paste.');
|
||||||
|
res.writeHead(500, { 'content-type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ message: 'Error adding new paste.' }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = DocumentHandler;
|
req.on('error', (error) => {
|
||||||
|
this.logger.error(`Connection error: ${error.message}`);
|
||||||
|
res.writeHead(500, { 'content-type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ message: 'Connection error.' }));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates new keys until one is not taken
|
||||||
|
chooseKey(callback) {
|
||||||
|
const key = this.acceptableKey();
|
||||||
|
this.store.get(key, (success) => {
|
||||||
|
if (success) {
|
||||||
|
this.chooseKey(callback);
|
||||||
|
} else {
|
||||||
|
callback(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new key using the key-generator
|
||||||
|
acceptableKey() {
|
||||||
|
return this.keyGenerator.createKey(this.keyLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = DocumentHandler;
|
||||||
@@ -1,38 +1,75 @@
|
|||||||
var fs = require('fs');
|
const fs = require('fs');
|
||||||
var crypto = require('crypto');
|
const path = require('path');
|
||||||
|
|
||||||
var logger = require('winston');
|
// A simple key regex to prevent path traversal
|
||||||
|
const validKeyRegex = /^[a-zA-Z0-9]+$/;
|
||||||
|
|
||||||
// handles saving and retrieving all documents
|
// handles saving and retrieving all documents
|
||||||
var FileDocumentStore = function(options) {
|
class FileDocumentStore {
|
||||||
this.basePath = options.path || './data';
|
constructor(options) {
|
||||||
logger.info('Path to data: ' + this.basePath);
|
this.basePath = options.path || './data';
|
||||||
};
|
this.logger = options.logger;
|
||||||
|
this.logger.info('Path to data: ' + this.basePath);
|
||||||
|
// Create directory if it does not exist
|
||||||
|
if (!fs.existsSync(this.basePath)) {
|
||||||
|
fs.mkdirSync(this.basePath, { recursive: true, mode: '700' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// saves a new file to the filesystem
|
// saves a new file to the filesystem
|
||||||
FileDocumentStore.prototype.set = function(key, data, callback) {
|
set(key, data, callback) {
|
||||||
var _this = this;
|
if (!validKeyRegex.test(key)) {
|
||||||
fs.mkdir(this.basePath, '700', function(err) {
|
this.logger.warn(`Invalid key provided for set: ${key}`);
|
||||||
fs.writeFile(_this.basePath + '/' + key, data, 'utf8', function(err) {
|
return callback(false);
|
||||||
if (err) {
|
}
|
||||||
callback(false);
|
|
||||||
} else {
|
|
||||||
callback(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// gets an exisiting file from the filesystem
|
const resolvedBasePath = path.resolve(this.basePath);
|
||||||
FileDocumentStore.prototype.get = function(key, callback) {
|
const filePath = path.resolve(resolvedBasePath, key);
|
||||||
var _this = this;
|
|
||||||
fs.readFile(this.basePath + '/' + key, 'utf8', function(err, data) {
|
|
||||||
if (err) {
|
|
||||||
callback(false);
|
|
||||||
} else {
|
|
||||||
callback(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = FileDocumentStore;
|
// Security check to ensure we are not writing outside of the data directory
|
||||||
|
if (path.dirname(filePath) !== resolvedBasePath) {
|
||||||
|
this.logger.error(`Path traversal attempt detected for key: ${key}`);
|
||||||
|
return callback(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFile(filePath, data, 'utf8', (err) => {
|
||||||
|
if (err) {
|
||||||
|
this.logger.error('Error writing file:', err);
|
||||||
|
callback(false);
|
||||||
|
} else {
|
||||||
|
callback(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// gets an existing file from the filesystem
|
||||||
|
get(key, callback) {
|
||||||
|
if (!validKeyRegex.test(key)) {
|
||||||
|
this.logger.warn(`Invalid key provided for get: ${key}`);
|
||||||
|
return callback(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedBasePath = path.resolve(this.basePath);
|
||||||
|
const filePath = path.resolve(resolvedBasePath, key);
|
||||||
|
|
||||||
|
// Security check to ensure we are not reading outside of the data directory
|
||||||
|
if (path.dirname(filePath) !== resolvedBasePath) {
|
||||||
|
this.logger.error(`Path traversal attempt detected for key: ${key}`);
|
||||||
|
return callback(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.readFile(filePath, 'utf8', (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
// Don't log error if file just doesn't exist
|
||||||
|
if (err.code !== 'ENOENT') {
|
||||||
|
this.logger.error('Error reading file:', err);
|
||||||
|
}
|
||||||
|
callback(false);
|
||||||
|
} else {
|
||||||
|
callback(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = FileDocumentStore;
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
var KeyGenerator = function() {
|
const crypto = require('crypto');
|
||||||
this.keyspace = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
||||||
};
|
|
||||||
|
|
||||||
KeyGenerator.prototype.createKey = function(keyLength) {
|
class KeyGenerator {
|
||||||
var key = '';
|
constructor() {
|
||||||
var index;
|
this.keyspace = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
for (var i = 0; i < keyLength; i++) {
|
}
|
||||||
index = Math.floor(Math.random() * this.keyspace.length);
|
|
||||||
key += this.keyspace.charAt(index);
|
|
||||||
}
|
|
||||||
return key;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = KeyGenerator;
|
createKey(keyLength) {
|
||||||
|
const buffer = crypto.randomBytes(keyLength);
|
||||||
|
let key = '';
|
||||||
|
for (let i = 0; i < buffer.length; i++) {
|
||||||
|
key += this.keyspace.charAt(buffer[i] % this.keyspace.length);
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = KeyGenerator;
|
||||||
1122
package-lock.json
generated
1122
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
26
package.json
26
package.json
@@ -1,16 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "unknownBIN",
|
"name": "unknownBIN",
|
||||||
"version": "1.0.0",
|
"version": "2.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "unknownBIN is an open-source Pastebin software written in node.js.",
|
"description": "A secure and modern open-source Pastebin software written in node.js.",
|
||||||
"keywords": [
|
|
||||||
"paste",
|
|
||||||
"pastebin",
|
|
||||||
"haste",
|
|
||||||
"hastebin",
|
|
||||||
"code",
|
|
||||||
"syntax highlighting"
|
|
||||||
],
|
|
||||||
"author": {
|
"author": {
|
||||||
"name": "MrUnknownDE",
|
"name": "MrUnknownDE",
|
||||||
"email": "me@mrunk.de",
|
"email": "me@mrunk.de",
|
||||||
@@ -18,12 +10,14 @@
|
|||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clean-css": "~3.4.19",
|
"clean-css": "^5.3.3",
|
||||||
"express": "~4.14.0",
|
"express": "^4.19.2",
|
||||||
"uglify-js": "~2.7.0",
|
"helmet": "^7.1.0",
|
||||||
"winston": "~2.2.0"
|
"uglify-js": "^3.17.4",
|
||||||
|
"winston": "^3.13.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./server.js"
|
"start": "node ./server.js",
|
||||||
|
"build": "node ./build.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
134
server.js
134
server.js
@@ -1,81 +1,97 @@
|
|||||||
var http = require('http');
|
const fs = require('fs');
|
||||||
var url = require('url');
|
const path = require('path');
|
||||||
var fs = require('fs');
|
|
||||||
|
|
||||||
var express = require('express');
|
const express = require('express');
|
||||||
var logger = require('winston');
|
const helmet = require('helmet');
|
||||||
|
const winston = require('winston');
|
||||||
|
|
||||||
var DocumentHandler = require('./lib/document_handler.js');
|
const DocumentHandler = require('./lib/document_handler.js');
|
||||||
var FileStorage = require('./lib/file_storage.js');
|
const FileStorage = require('./lib/file_storage.js');
|
||||||
|
|
||||||
// load configuration
|
// load configuration
|
||||||
var config = JSON.parse(fs.readFileSync(__dirname + '/config.json', 'utf8'));
|
let config;
|
||||||
|
try {
|
||||||
|
config = JSON.parse(fs.readFileSync(path.join(__dirname, 'config.json'), 'utf8'));
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error reading config.json:', err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
config.port = process.env.PORT || config.port || 8080;
|
config.port = process.env.PORT || config.port || 8080;
|
||||||
config.host = process.env.HOST || config.host || 'localhost';
|
config.host = process.env.HOST || config.host || '0.0.0.0';
|
||||||
|
|
||||||
// logger-setup
|
// logger-setup
|
||||||
logger.remove(logger.transports.Console);
|
const logger = winston.createLogger({
|
||||||
logger.add(logger.transports.Console, {colorize: true, level: 'verbose'});
|
level: 'verbose',
|
||||||
logger.info('Welcome to Hastebin Plus!');
|
format: winston.format.combine(
|
||||||
|
winston.format.colorize(),
|
||||||
|
winston.format.simple()
|
||||||
|
),
|
||||||
|
transports: [
|
||||||
|
new winston.transports.Console()
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info('Welcome to unknownBIN!');
|
||||||
|
|
||||||
// init file-storage
|
// init file-storage
|
||||||
var fileStorage = new FileStorage(config.dataPath);
|
const fileStorage = new FileStorage({ path: config.dataPath, logger: logger });
|
||||||
|
|
||||||
// load static documents into file-storage
|
// load static documents into file-storage
|
||||||
for (var name in config.documents) {
|
if (config.documents) {
|
||||||
var path = config.documents[name];
|
for (const name in config.documents) {
|
||||||
var data = fs.readFileSync(path, 'utf8');
|
const docPath = config.documents[name];
|
||||||
if (data) {
|
try {
|
||||||
fileStorage.set(name, data, function(success) {});
|
const data = fs.readFileSync(docPath, 'utf8');
|
||||||
logger.verbose('Created document: ' + name + " ==> " + path);
|
fileStorage.set(name, data, (success) => {
|
||||||
} else {
|
if (success) {
|
||||||
logger.warn('Unable to find document: ' + name + " ==> " + path);
|
logger.verbose(`Loaded document: ${name} from ${docPath}`);
|
||||||
}
|
} else {
|
||||||
|
logger.warn(`Failed to store document: ${name}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn(`Unable to find or read document: ${name} at ${docPath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// configure the document handler
|
// configure the document handler
|
||||||
var documentHandler = new DocumentHandler({
|
const documentHandler = new DocumentHandler({
|
||||||
store: fileStorage,
|
store: fileStorage,
|
||||||
maxLength: config.maxLength,
|
maxLength: config.maxLength,
|
||||||
keyLength: config.keyLength,
|
keyLength: config.keyLength,
|
||||||
createKey: config.createKey
|
createKey: config.createKey,
|
||||||
|
logger: logger
|
||||||
});
|
});
|
||||||
|
|
||||||
// compress static assets
|
|
||||||
var cssCompressor = require('clean-css');
|
|
||||||
var jsCompressor = require('uglify-js');
|
|
||||||
|
|
||||||
var files = fs.readdirSync(__dirname + '/static');
|
|
||||||
for (var i = 0; i < files.length; i++) {
|
|
||||||
var item = files[i];
|
|
||||||
var dest = "";
|
|
||||||
if ((item.indexOf('.css') === item.length - 4) && (item.indexOf('.min.css') === -1)) {
|
|
||||||
dest = item.substring(0, item.length - 4) + '.min.css';
|
|
||||||
fs.writeFileSync(__dirname + '/static/' + dest, new cssCompressor().minify(fs.readFileSync(__dirname + '/static/' + item, 'utf8')).styles, 'utf8');
|
|
||||||
logger.verbose('Compressed: ' + item + ' ==> ' + dest);
|
|
||||||
} else if ((item.indexOf('.js') === item.length - 3) && (item.indexOf('.min.js') === -1)) {
|
|
||||||
dest = item.substring(0, item.length - 3) + '.min.js';
|
|
||||||
fs.writeFileSync(__dirname + '/static/' + dest, jsCompressor.minify(__dirname + '/static/' + item).code, 'utf8');
|
|
||||||
logger.verbose('Compressed: ' + item + ' ==> ' + dest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup routes and request-handling
|
// setup routes and request-handling
|
||||||
var app = express();
|
const app = express();
|
||||||
|
|
||||||
app.get('/raw/:id', function(req, res) {
|
// Use helmet for basic security headers
|
||||||
return documentHandler.handleRawGet(req.params.id, res);
|
app.use(helmet());
|
||||||
|
|
||||||
|
app.get('/raw/:id', (req, res) => {
|
||||||
|
return documentHandler.handleRawGet(req.params.id, res);
|
||||||
});
|
});
|
||||||
app.post('/documents', function(req, res) {
|
app.post('/documents', (req, res) => {
|
||||||
return documentHandler.handlePost(req, res);
|
return documentHandler.handlePost(req, res);
|
||||||
});
|
});
|
||||||
app.get('/documents/:id', function(req, res) {
|
app.get('/documents/:id', (req, res) => {
|
||||||
return documentHandler.handleGet(req.params.id, res);
|
return documentHandler.handleGet(req.params.id, res);
|
||||||
});
|
|
||||||
app.use(express.static('static'));
|
|
||||||
app.get('/:id', function(req, res, next) {
|
|
||||||
res.sendFile(__dirname + '/static/index.html');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen(config.port, config.host);
|
app.use(express.static(path.join(__dirname, 'static')));
|
||||||
logger.info('Listening on ' + config.host + ':' + config.port);
|
|
||||||
|
app.get('/:id', (req, res, next) => {
|
||||||
|
res.sendFile(path.join(__dirname, '/static/index.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/', (req, res, next) => {
|
||||||
|
res.sendFile(path.join(__dirname, '/static/index.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
app.listen(config.port, config.host, () => {
|
||||||
|
logger.info(`Listening on ${config.host}:${config.port}`);
|
||||||
|
});
|
||||||
@@ -4,20 +4,51 @@ html {
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
background: #002B36;
|
background: #002B36;
|
||||||
height: 90%;
|
color: #839496;
|
||||||
|
font-family: sans-serif;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 1em;
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tools {
|
||||||
|
background: #00212b;
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tools .function {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 8px;
|
||||||
|
margin: 0 4px;
|
||||||
|
color: #839496;
|
||||||
|
border-radius: 3px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tools .function.enabled {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #93a1a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tools .function.enabled:hover {
|
||||||
|
background: #073642;
|
||||||
|
color: #eee8d5;
|
||||||
}
|
}
|
||||||
|
|
||||||
#content {
|
#content {
|
||||||
height: 100%;
|
flex-grow: 1;
|
||||||
padding: 0;
|
padding: 1em;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
color: #fff;
|
color: #eee8d5;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -34,6 +65,7 @@ textarea {
|
|||||||
outline: none;
|
outline: none;
|
||||||
padding: 0 0 4em 0;
|
padding: 0 0 4em 0;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
#code code {
|
#code code {
|
||||||
@@ -60,77 +92,4 @@ pre .line::before {
|
|||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
-ms-user-select: none;
|
-ms-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tools {
|
|
||||||
background: #08323c;
|
|
||||||
bottom: 0;
|
|
||||||
font-size: 0;
|
|
||||||
left: 0;
|
|
||||||
position: fixed;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tools .function {
|
|
||||||
background: url(function-icons.png);
|
|
||||||
display: inline-block;
|
|
||||||
height: 37px;
|
|
||||||
position: relative;
|
|
||||||
width: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tools .link embed {
|
|
||||||
vertical-align: bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tools .function.enabled:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tools .function.save {
|
|
||||||
background-position: -5px top;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tools .function.enabled.save {
|
|
||||||
background-position: -5px center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tools .function.enabled.save:hover {
|
|
||||||
background-position: -5px bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tools .function.new {
|
|
||||||
background-position: -42px top;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tools .function.enabled.new {
|
|
||||||
background-position: -42px center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tools .function.enabled.new:hover {
|
|
||||||
background-position: -42px bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tools .function.duplicate {
|
|
||||||
background-position: -79px top;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tools .function.enabled.duplicate {
|
|
||||||
background-position: -79px center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tools .function.enabled.duplicate:hover {
|
|
||||||
background-position: -79px bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tools .function.raw {
|
|
||||||
background-position: -116px top;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tools .function.enabled.raw {
|
|
||||||
background-position: -116px center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tools .function.enabled.raw:hover {
|
|
||||||
background-position: -116px bottom;
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
// represents the haste-application
|
const Haste = function() {
|
||||||
var haste = function() {
|
this.appName = "unknownBIN";
|
||||||
this.appName = "Hastebin Plus";
|
|
||||||
this.$textarea = $('textarea');
|
this.$textarea = $('textarea');
|
||||||
this.$box = $('#code');
|
this.$box = $('#code');
|
||||||
this.$code = $('#code code');
|
this.$code = $('#code code');
|
||||||
@@ -9,26 +8,26 @@ var haste = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// set title (browser window)
|
// set title (browser window)
|
||||||
haste.prototype.setTitle = function(ext) {
|
Haste.prototype.setTitle = function(ext) {
|
||||||
var title = ext ? this.appName + ' - ' + ext : this.appName;
|
const title = ext ? `${this.appName} - ${ext}` : this.appName;
|
||||||
document.title = title;
|
document.title = title;
|
||||||
};
|
};
|
||||||
|
|
||||||
// show the light key
|
// show the light key
|
||||||
haste.prototype.lightKey = function() {
|
Haste.prototype.lightKey = function() {
|
||||||
this.configureKey(['new', 'save']);
|
this.configureKey(['new', 'save']);
|
||||||
};
|
};
|
||||||
|
|
||||||
// show the full key
|
// show the full key
|
||||||
haste.prototype.fullKey = function() {
|
Haste.prototype.fullKey = function() {
|
||||||
this.configureKey(['new', 'duplicate', 'raw']);
|
this.configureKey(['new', 'duplicate', 'raw']);
|
||||||
};
|
};
|
||||||
|
|
||||||
// enable certain keys
|
// enable certain keys
|
||||||
haste.prototype.configureKey = function(enable) {
|
Haste.prototype.configureKey = function(enable) {
|
||||||
$('#tools .function').each(function() {
|
$('#tools .function').each(function() {
|
||||||
var $this = $(this);
|
const $this = $(this);
|
||||||
for (var i = 0; i < enable.length; i++) {
|
for (let i = 0; i < enable.length; i++) {
|
||||||
if ($this.hasClass(enable[i])) {
|
if ($this.hasClass(enable[i])) {
|
||||||
$this.addClass('enabled');
|
$this.addClass('enabled');
|
||||||
return true;
|
return true;
|
||||||
@@ -39,9 +38,9 @@ haste.prototype.configureKey = function(enable) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// setup a new, blank document
|
// setup a new, blank document
|
||||||
haste.prototype.newDocument = function(hideHistory) {
|
Haste.prototype.newDocument = function(hideHistory) {
|
||||||
this.$box.hide();
|
this.$box.hide();
|
||||||
this.doc = new haste_document();
|
this.doc = new HasteDocument();
|
||||||
if (!hideHistory) {
|
if (!hideHistory) {
|
||||||
window.history.pushState(null, this.appName, '/');
|
window.history.pushState(null, this.appName, '/');
|
||||||
}
|
}
|
||||||
@@ -53,14 +52,14 @@ haste.prototype.newDocument = function(hideHistory) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// load an existing document
|
// load an existing document
|
||||||
haste.prototype.loadDocument = function(key) {
|
Haste.prototype.loadDocument = function(key) {
|
||||||
var _this = this;
|
const _this = this;
|
||||||
_this.doc = new haste_document();
|
_this.doc = new HasteDocument();
|
||||||
_this.doc.load(key, function(ret) {
|
_this.doc.load(key, function(ret) {
|
||||||
if (ret) {
|
if (ret) {
|
||||||
_this.$code.html(ret.value);
|
_this.$code.html(ret.value);
|
||||||
_this.setTitle(ret.key);
|
_this.setTitle(ret.key);
|
||||||
window.history.pushState(null, _this.appName + '-' + ret.key, '/' + ret.key);
|
window.history.pushState(null, `${_this.appName}-${ret.key}`, `/${ret.key}`);
|
||||||
_this.fullKey();
|
_this.fullKey();
|
||||||
_this.$textarea.val('').hide();
|
_this.$textarea.val('').hide();
|
||||||
_this.$box.show();
|
_this.$box.show();
|
||||||
@@ -71,22 +70,22 @@ haste.prototype.loadDocument = function(key) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// duplicate the current document
|
// duplicate the current document
|
||||||
haste.prototype.duplicateDocument = function() {
|
Haste.prototype.duplicateDocument = function() {
|
||||||
if (this.doc.locked) {
|
if (this.doc.locked) {
|
||||||
var currentData = this.doc.data;
|
const currentData = this.doc.data;
|
||||||
this.newDocument();
|
this.newDocument();
|
||||||
this.$textarea.val(currentData);
|
this.$textarea.val(currentData);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// lock the current document
|
// lock the current document
|
||||||
haste.prototype.lockDocument = function() {
|
Haste.prototype.lockDocument = function() {
|
||||||
var _this = this;
|
const _this = this;
|
||||||
this.doc.save(this.$textarea.val(), function(err, ret) {
|
this.doc.save(this.$textarea.val(), function(err, ret) {
|
||||||
if (!err && ret) {
|
if (!err && ret) {
|
||||||
_this.$code.html(ret.value.trim().replace(/.+/g, "<span class=\"line\">$&</span>").replace(/^\s*[\r\n]/gm, "<span class=\"line\"></span>\n"));
|
_this.$code.html(ret.value.trim().replace(/.+/g, "<span class=\"line\">$&</span>").replace(/^\s*[\r\n]/gm, "<span class=\"line\"></span>\n"));
|
||||||
_this.setTitle(ret.key);
|
_this.setTitle(ret.key);
|
||||||
window.history.pushState(null, _this.appName + '-' + ret.key, '/' + ret.key);
|
window.history.pushState(null, `${_this.appName}-${ret.key}`, `/${ret.key}`);
|
||||||
_this.fullKey();
|
_this.fullKey();
|
||||||
_this.$textarea.val('').hide();
|
_this.$textarea.val('').hide();
|
||||||
_this.$box.show();
|
_this.$box.show();
|
||||||
@@ -95,8 +94,8 @@ haste.prototype.lockDocument = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// configure buttons and their shortcuts
|
// configure buttons and their shortcuts
|
||||||
haste.prototype.configureButtons = function() {
|
Haste.prototype.configureButtons = function() {
|
||||||
var _this = this;
|
const _this = this;
|
||||||
this.buttons = [
|
this.buttons = [
|
||||||
{
|
{
|
||||||
$where: $('#tools .save'),
|
$where: $('#tools .save'),
|
||||||
@@ -133,17 +132,17 @@ haste.prototype.configureButtons = function() {
|
|||||||
return evt.ctrlKey && evt.shiftKey && evt.keyCode === 82;
|
return evt.ctrlKey && evt.shiftKey && evt.keyCode === 82;
|
||||||
},
|
},
|
||||||
action: function() {
|
action: function() {
|
||||||
window.location.href = '/raw/' + _this.doc.key;
|
window.location.href = `/raw/${_this.doc.key}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
for (var i = 0; i < this.buttons.length; i++) {
|
for (let i = 0; i < this.buttons.length; i++) {
|
||||||
this.configureButton(this.buttons[i]);
|
this.configureButton(this.buttons[i]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// handles the button-click
|
// handles the button-click
|
||||||
haste.prototype.configureButton = function(options) {
|
Haste.prototype.configureButton = function(options) {
|
||||||
options.$where.click(function(evt) {
|
options.$where.click(function(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
if (!options.clickDisabled && $(this).hasClass('enabled')) {
|
if (!options.clickDisabled && $(this).hasClass('enabled')) {
|
||||||
@@ -153,11 +152,11 @@ haste.prototype.configureButton = function(options) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// enables the configured shortcuts
|
// enables the configured shortcuts
|
||||||
haste.prototype.configureShortcuts = function() {
|
Haste.prototype.configureShortcuts = function() {
|
||||||
var _this = this;
|
const _this = this;
|
||||||
$(document.body).keydown(function(evt) {
|
$(document.body).keydown(function(evt) {
|
||||||
var button;
|
let button;
|
||||||
for (var i = 0; i < _this.buttons.length; i++) {
|
for (let i = 0; i < _this.buttons.length; i++) {
|
||||||
button = _this.buttons[i];
|
button = _this.buttons[i];
|
||||||
if (button.shortcut && button.shortcut(evt)) {
|
if (button.shortcut && button.shortcut(evt)) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
@@ -169,12 +168,12 @@ haste.prototype.configureShortcuts = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// represents a single document
|
// represents a single document
|
||||||
var haste_document = function() {
|
const HasteDocument = function() {
|
||||||
this.locked = false;
|
this.locked = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// escape HTML-characters
|
// escape HTML-characters
|
||||||
haste_document.prototype.htmlEscape = function(s) {
|
HasteDocument.prototype.htmlEscape = function(s) {
|
||||||
return s
|
return s
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, '>')
|
||||||
@@ -183,40 +182,39 @@ haste_document.prototype.htmlEscape = function(s) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// load a document from the server
|
// load a document from the server
|
||||||
haste_document.prototype.load = function(key, callback) {
|
HasteDocument.prototype.load = function(key, callback) {
|
||||||
var _this = this;
|
const _this = this;
|
||||||
$.ajax('/documents/' + key, {
|
$.ajax(`/documents/${key}`, {
|
||||||
type: 'get',
|
type: 'get',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(res) {
|
success: function(res) {
|
||||||
_this.locked = true;
|
_this.locked = true;
|
||||||
_this.key = key;
|
_this.key = key;
|
||||||
_this.data = res.data;
|
_this.data = res.data;
|
||||||
high = hljs.highlightAuto(res.data).value;
|
const high = hljs.highlightAuto(res.data).value;
|
||||||
callback({
|
callback({
|
||||||
value: high.replace(/.+/g, "<span class=\"line\">$&</span>").replace(/^\s*[\r\n]/gm, "<span class=\"line\"></span>\n"),
|
value: high.replace(/.+/g, "<span class=\"line\">$&</span>").replace(/^\s*[\r\n]/gm, "<span class=\"line\"></span>\n"),
|
||||||
key: key,
|
key: key,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
error: function(err) {
|
error: function() {
|
||||||
callback(false);
|
callback(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// sends the document to the server
|
// sends the document to the server
|
||||||
haste_document.prototype.save = function(data, callback) {
|
HasteDocument.prototype.save = function(data, callback) {
|
||||||
if (this.locked) return false;
|
if (this.locked) return false;
|
||||||
|
|
||||||
this.data = data;
|
this.data = data;
|
||||||
var _this = this;
|
|
||||||
$.ajax('/documents', {
|
$.ajax('/documents', {
|
||||||
type: 'post',
|
type: 'post',
|
||||||
data: data.trim(),
|
data: data,
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
contentType: 'application/json; charset=utf-8',
|
contentType: 'text/plain; charset=utf-8',
|
||||||
success: function(res) {
|
success: function(res) {
|
||||||
new haste().loadDocument(res.key);
|
new Haste().loadDocument(res.key);
|
||||||
},
|
},
|
||||||
error: function(res) {
|
error: function(res) {
|
||||||
try {
|
try {
|
||||||
@@ -234,16 +232,16 @@ $(function() {
|
|||||||
// allow usage of tabs
|
// allow usage of tabs
|
||||||
if (evt.keyCode === 9) {
|
if (evt.keyCode === 9) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
var myValue = ' ';
|
const myValue = ' ';
|
||||||
if (document.selection) {
|
if (document.selection) {
|
||||||
this.focus();
|
this.focus();
|
||||||
sel = document.selection.createRange();
|
let sel = document.selection.createRange();
|
||||||
sel.text = myValue;
|
sel.text = myValue;
|
||||||
this.focus();
|
this.focus();
|
||||||
} else if (this.selectionStart || this.selectionStart == '0') {
|
} else if (this.selectionStart || this.selectionStart == '0') {
|
||||||
var startPos = this.selectionStart;
|
const startPos = this.selectionStart;
|
||||||
var endPos = this.selectionEnd;
|
const endPos = this.selectionEnd;
|
||||||
var scrollTop = this.scrollTop;
|
const scrollTop = this.scrollTop;
|
||||||
this.value = this.value.substring(0, startPos) + myValue + this.value.substring(endPos, this.value.length);
|
this.value = this.value.substring(0, startPos) + myValue + this.value.substring(endPos, this.value.length);
|
||||||
this.focus();
|
this.focus();
|
||||||
this.selectionStart = startPos + myValue.length;
|
this.selectionStart = startPos + myValue.length;
|
||||||
@@ -256,8 +254,8 @@ $(function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var app = new haste();
|
const app = new Haste();
|
||||||
var path = window.location.pathname;
|
const path = window.location.pathname;
|
||||||
if (path === '/') {
|
if (path === '/') {
|
||||||
app.newDocument(true);
|
app.newDocument(true);
|
||||||
} else {
|
} else {
|
||||||
@@ -265,4 +263,4 @@ $(function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
hljs.initHighlightingOnLoad();
|
hljs.initHighlightingOnLoad();
|
||||||
1
static/application.min.css
vendored
Normal file
1
static/application.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
html{height:100%}body{background:#002b36;color:#839496;font-family:sans-serif;margin:0;padding:0;display:flex;flex-direction:column;height:100%}#tools{background:#00212b;padding:8px;font-size:14px;flex-shrink:0}#tools .function{display:inline-block;padding:4px 8px;margin:0 4px;color:#839496;border-radius:3px;text-decoration:none}#tools .function.enabled{cursor:pointer;color:#93a1a1}#tools .function.enabled:hover{background:#073642;color:#eee8d5}#content{flex-grow:1;padding:1em;overflow-y:auto}textarea{background:0 0;border:0;color:#eee8d5;font-family:monospace;font-size:1em;height:100%;outline:0;padding:0;resize:none;width:100%}#code{border:0;font-size:1em;margin:0;outline:0;padding:0 0 4em 0;white-space:pre-wrap;word-wrap:break-word}#code code{background:0 0!important;padding:0}pre{counter-reset:line-numbering}pre .line::before{color:#4c6a71;content:counter(line-numbering);counter-increment:line-numbering;display:inline-block;margin-right:1em;text-align:right;width:1.5em!important;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||||
1
static/application.min.js
vendored
Normal file
1
static/application.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
let Haste=function(){this.appName="unknownBIN",this.$textarea=$("textarea"),this.$box=$("#code"),this.$code=$("#code code"),this.configureShortcuts(),this.configureButtons()},HasteDocument=(Haste.prototype.setTitle=function(t){t=t?this.appName+" - "+t:this.appName;document.title=t},Haste.prototype.lightKey=function(){this.configureKey(["new","save"])},Haste.prototype.fullKey=function(){this.configureKey(["new","duplicate","raw"])},Haste.prototype.configureKey=function(o){$("#tools .function").each(function(){var e=$(this);for(let t=0;t<o.length;t++)if(e.hasClass(o[t]))return e.addClass("enabled"),!0;e.removeClass("enabled")})},Haste.prototype.newDocument=function(t){this.$box.hide(),this.doc=new HasteDocument,t||window.history.pushState(null,this.appName,"/"),this.setTitle(),this.lightKey(),this.$textarea.val("").show("fast",function(){this.focus()})},Haste.prototype.loadDocument=function(t){let e=this;e.doc=new HasteDocument,e.doc.load(t,function(t){t?(e.$code.html(t.value),e.setTitle(t.key),window.history.pushState(null,e.appName+"-"+t.key,"/"+t.key),e.fullKey(),e.$textarea.val("").hide(),e.$box.show()):e.newDocument()})},Haste.prototype.duplicateDocument=function(){var t;this.doc.locked&&(t=this.doc.data,this.newDocument(),this.$textarea.val(t))},Haste.prototype.lockDocument=function(){let o=this;this.doc.save(this.$textarea.val(),function(t,e){!t&&e&&(o.$code.html(e.value.trim().replace(/.+/g,'<span class="line">$&</span>').replace(/^\s*[\r\n]/gm,'<span class="line"></span>\n')),o.setTitle(e.key),window.history.pushState(null,o.appName+"-"+e.key,"/"+e.key),o.fullKey(),o.$textarea.val("").hide(),o.$box.show())})},Haste.prototype.configureButtons=function(){let e=this;this.buttons=[{$where:$("#tools .save"),shortcut:function(t){return t.ctrlKey&&83===t.keyCode},action:function(){""!==e.$textarea.val().replace(/^\s+|\s+$/g,"")&&e.lockDocument()}},{$where:$("#tools .new"),shortcut:function(t){return t.ctrlKey&&32===t.keyCode},action:function(){e.newDocument(!e.doc.key)}},{$where:$("#tools .duplicate"),shortcut:function(t){return e.doc.locked&&t.ctrlKey&&68===t.keyCode},action:function(){e.duplicateDocument()}},{$where:$("#tools .raw"),shortcut:function(t){return t.ctrlKey&&t.shiftKey&&82===t.keyCode},action:function(){window.location.href="/raw/"+e.doc.key}}];for(let t=0;t<this.buttons.length;t++)this.configureButton(this.buttons[t])},Haste.prototype.configureButton=function(e){e.$where.click(function(t){t.preventDefault(),!e.clickDisabled&&$(this).hasClass("enabled")&&e.action()})},Haste.prototype.configureShortcuts=function(){let n=this;$(document.body).keydown(function(e){var o;for(let t=0;t<n.buttons.length;t++)if((o=n.buttons[t]).shortcut&&o.shortcut(e))return e.preventDefault(),void o.action()})},function(){this.locked=!1});HasteDocument.prototype.htmlEscape=function(t){return t.replace(/&/g,"&").replace(/>/g,">").replace(/</g,"<").replace(/"/g,""")},HasteDocument.prototype.load=function(e,o){let n=this;$.ajax("/documents/"+e,{type:"get",dataType:"json",success:function(t){n.locked=!0,n.key=e,n.data=t.data;t=hljs.highlightAuto(t.data).value;o({value:t.replace(/.+/g,'<span class="line">$&</span>').replace(/^\s*[\r\n]/gm,'<span class="line"></span>\n'),key:e})},error:function(){o(!1)}})},HasteDocument.prototype.save=function(t,e){if(this.locked)return!1;this.data=t,$.ajax("/documents",{type:"post",data:t,dataType:"json",contentType:"text/plain; charset=utf-8",success:function(t){(new Haste).loadDocument(t.key)},error:function(t){try{e($.parseJSON(t.responseText))}catch(t){e({message:"Something went wrong!"})}}})},$(function(){$("textarea").keydown(function(t){var e,o,n;9===t.keyCode&&(t.preventDefault(),t=" ",document.selection?(this.focus(),document.selection.createRange().text=t,this.focus()):this.selectionStart||"0"==this.selectionStart?(e=this.selectionStart,o=this.selectionEnd,n=this.scrollTop,this.value=this.value.substring(0,e)+t+this.value.substring(o,this.value.length),this.focus(),this.selectionStart=e+t.length,this.selectionEnd=e+t.length,this.scrollTop=n):(this.value+=t,this.focus()))});var t=new Haste,e=window.location.pathname;"/"===e?t.newDocument(!0):t.loadDocument(e.substring(1,e.length))}),hljs.initHighlightingOnLoad();
|
||||||
1
static/highlight.min.css
vendored
Normal file
1
static/highlight.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.hljs{display:block;overflow-x:auto;padding:.5em;background:#002b36;color:#839496}.hljs-comment,.hljs-quote{color:#586e75}.hljs-addition,.hljs-keyword,.hljs-selector-tag{color:#859900}.hljs-doctag,.hljs-literal,.hljs-meta .hljs-meta-string,.hljs-number,.hljs-regexp,.hljs-string{color:#2aa198}.hljs-name,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-title{color:#268bd2}.hljs-attr,.hljs-attribute,.hljs-class .hljs-title,.hljs-template-variable,.hljs-type,.hljs-variable{color:#b58900}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-meta .hljs-keyword,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-subst,.hljs-symbol{color:#cb4b16}.hljs-built_in,.hljs-deletion{color:#dc322f}.hljs-formula{background:#073642}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Hastebin Plus</title>
|
<title>unknownBIN</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
<link rel="stylesheet" type="text/css" href="application.min.css"/>
|
<link rel="stylesheet" type="text/css" href="application.min.css"/>
|
||||||
<link rel="stylesheet" type="text/css" href="highlight.min.css"/>
|
<link rel="stylesheet" type="text/css" href="highlight.min.css"/>
|
||||||
@@ -11,15 +11,15 @@
|
|||||||
<script src="application.min.js"></script>
|
<script src="application.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="tools">
|
||||||
|
<div class="save function" title="Save [Ctrl + S]">Save</div>
|
||||||
|
<div class="new function" title="New [Ctrl + Space]">New</div>
|
||||||
|
<div class="duplicate function" title="Duplicate [Ctrl + D]">Duplicate</div>
|
||||||
|
<div class="raw function" title="Raw [Ctrl + Shift + R]">Raw</div>
|
||||||
|
</div>
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<pre id="code" style="display:none;" tabindex="0"><code></code></pre>
|
<pre id="code" style="display:none;" tabindex="0"><code></code></pre>
|
||||||
<textarea spellcheck="false" style="display:none;"></textarea>
|
<textarea spellcheck="false" style="display:none;"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div id="tools">
|
|
||||||
<div class="save function" title="Save [Ctrl + S]"></div>
|
|
||||||
<div class="new function" title="New [Ctrl + Space]"></div>
|
|
||||||
<div class="duplicate function" title="Duplicate [Ctrl + D]"></div>
|
|
||||||
<div class="raw function" title="Raw [Ctrl + Shift + R]"></div>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user