mirror of
https://github.com/MrUnknownDE/unknownbin.git
synced 2026-04-06 00:32:08 +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)
|
||||
.idea
|
||||
HastebinPlus.iml
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/node
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=node
|
||||
|
||||
# IDE (Microsoft Visual Studio)
|
||||
obj/
|
||||
Microsoft.NodejsTools.WebRole.dll
|
||||
.ntvs_analysis.dat
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.suo
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Application
|
||||
data
|
||||
node_modules
|
||||
*.min.css
|
||||
*.min.js
|
||||
!jquery.min.js
|
||||
!highlight.min.js
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# NodeJS
|
||||
npm-debug.log
|
||||
# Runtime data
|
||||
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
|
||||
Hastebin Plus is an open-source Pastebin software written in node.js, which is easily installable in any network.
|
||||
It bases upon [haste](https://github.com/seejohnrun/haste-server) and got enhanced in matters of **Design, Speed and Simplicity**.
|
||||
# unknownBIN
|
||||
unknownBIN is a secure and modern open-source Pastebin software written in node.js.
|
||||
It is a fork of the original Hastebin and Hastebin Plus, modernized for security and performance.
|
||||
|
||||
## Features
|
||||
* 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
|
||||
* Duplicate & edit pastes
|
||||
* Raw paste-view
|
||||
* Secure, unpredictable paste IDs
|
||||
* Modernized backend with security enhancements
|
||||
|
||||
## Installation
|
||||
[](https://heroku.com/deploy?template=https://github.com/MarvinMenzerath/HastebinPlus)
|
||||
|
||||
1. Install Git and node.js: `sudo apt-get install git nodejs`
|
||||
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)
|
||||
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`
|
||||
3. Change into the directory: `cd unknownbin`
|
||||
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
|
||||
1. Pull changes from this repository: `git pull`
|
||||
1. Pull changes from the repository: `git pull`
|
||||
2. Install new dependencies: `npm install`
|
||||
3. Re-build static assets: `npm run build`
|
||||
4. Restart the application.
|
||||
|
||||
## Settings
|
||||
| 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` |
|
||||
| `keyLength` | The length of the pastes' key | `10` |
|
||||
| `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 |
|
||||
|
||||
### Default Config
|
||||
@@ -43,23 +47,6 @@ It bases upon [haste](https://github.com/seejohnrun/haste-server) and got enhanc
|
||||
"maxLength": 500000,
|
||||
"createKey": "",
|
||||
"documents": {
|
||||
"about": "./README.md",
|
||||
"javaTest": "./documents/test.java"
|
||||
"about": "./README.md"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 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');
|
||||
|
||||
var KeyGenerator = require('./key_generator.js');
|
||||
const KeyGenerator = require('./key_generator.js');
|
||||
|
||||
// handles creating new and requesting existing documents
|
||||
var DocumentHandler = function(options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
this.store = options.store;
|
||||
this.maxLength = options.maxLength || 50000;
|
||||
this.keyLength = options.keyLength || 10;
|
||||
this.createKey = options.createKey || '';
|
||||
this.keyGenerator = new KeyGenerator();
|
||||
class DocumentHandler {
|
||||
constructor(options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
this.store = options.store;
|
||||
this.logger = options.logger;
|
||||
this.maxLength = options.maxLength || 50000;
|
||||
this.keyLength = options.keyLength || 10;
|
||||
this.createKey = options.createKey || '';
|
||||
this.keyGenerator = new KeyGenerator();
|
||||
|
||||
if (this.createKey !== '') {
|
||||
logger.info("Creation-Key:", this.createKey);
|
||||
}
|
||||
};
|
||||
if (this.createKey !== '') {
|
||||
this.logger.info("Creation-Key is configured.");
|
||||
}
|
||||
}
|
||||
|
||||
// handles existing documents
|
||||
DocumentHandler.prototype.handleGet = function(key, res) {
|
||||
this.store.get(key, function(ret) {
|
||||
if (ret) {
|
||||
logger.verbose('Open paste:', key);
|
||||
res.writeHead(200, {'content-type': 'application/json'});
|
||||
res.end(JSON.stringify({key: key, data: ret.replace(/\t/g, ' ')}));
|
||||
} else {
|
||||
logger.verbose('Paste not found:', key);
|
||||
res.writeHead(404, {'content-type': 'application/json'});
|
||||
res.end(JSON.stringify({message: 'Paste not found.'}));
|
||||
}
|
||||
});
|
||||
};
|
||||
// handles existing documents
|
||||
handleGet(key, res) {
|
||||
this.store.get(key, (ret) => {
|
||||
if (ret) {
|
||||
this.logger.verbose(`Open paste: ${key}`);
|
||||
res.writeHead(200, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify({ key: key, data: ret.replace(/\t/g, ' ') }));
|
||||
} else {
|
||||
this.logger.verbose(`Paste not found: ${key}`);
|
||||
res.writeHead(404, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify({ message: 'Paste not found.' }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// handles exisiting documents (raw)
|
||||
DocumentHandler.prototype.handleRawGet = function(key, res) {
|
||||
this.store.get(key, function(ret) {
|
||||
if (ret) {
|
||||
logger.verbose('Open paste:', key);
|
||||
res.writeHead(200, {'content-type': 'text/plain'});
|
||||
res.end(ret);
|
||||
} else {
|
||||
logger.verbose('Paste not found:', key);
|
||||
res.writeHead(404, {'content-type': 'application/json'});
|
||||
res.end(JSON.stringify({message: 'Paste not found.'}));
|
||||
}
|
||||
});
|
||||
};
|
||||
// handles existing documents (raw)
|
||||
handleRawGet(key, res) {
|
||||
this.store.get(key, (ret) => {
|
||||
if (ret) {
|
||||
this.logger.verbose(`Open raw paste: ${key}`);
|
||||
res.writeHead(200, { 'content-type': 'text/plain; charset=utf-8' });
|
||||
res.end(ret);
|
||||
} else {
|
||||
this.logger.verbose(`Paste not found: ${key}`);
|
||||
res.writeHead(404, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify({ message: 'Paste not found.' }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// handles creating new documents
|
||||
DocumentHandler.prototype.handlePost = function(req, res) {
|
||||
var _this = this;
|
||||
var buffer = '';
|
||||
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;
|
||||
// handles creating new documents
|
||||
handlePost(req, res) {
|
||||
let buffer = '';
|
||||
let cancelled = false;
|
||||
|
||||
if (_this.createKey !== '') {
|
||||
if (!buffer.startsWith(_this.createKey)) {
|
||||
logger.warn('Error adding new paste: wrong key');
|
||||
res.writeHead(400, {'content-type': 'application/json'});
|
||||
res.end(JSON.stringify({message: 'Error adding new paste: wrong key'}));
|
||||
return;
|
||||
}
|
||||
buffer = buffer.substring(_this.createKey.length);
|
||||
}
|
||||
req.on('data', (data) => {
|
||||
if (cancelled) return;
|
||||
buffer += data.toString();
|
||||
if (this.maxLength && buffer.length > this.maxLength) {
|
||||
cancelled = true;
|
||||
this.logger.warn('Paste exceeds maximum length.');
|
||||
res.writeHead(400, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify({ message: 'Paste exceeds maximum length.' }));
|
||||
}
|
||||
});
|
||||
|
||||
_this.chooseKey(function(key) {
|
||||
_this.store.set(key, buffer, function(success) {
|
||||
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.'}));
|
||||
});
|
||||
};
|
||||
req.on('end', () => {
|
||||
if (cancelled) return;
|
||||
|
||||
// creates new keys until one is not taken
|
||||
DocumentHandler.prototype.chooseKey = function(callback) {
|
||||
var key = this.acceptableKey();
|
||||
var _this = this;
|
||||
this.store.get(key, function(success) {
|
||||
if (success) {
|
||||
_this.chooseKey(callback);
|
||||
} else {
|
||||
callback(key);
|
||||
}
|
||||
});
|
||||
};
|
||||
if (this.createKey !== '') {
|
||||
if (!buffer.startsWith(this.createKey)) {
|
||||
this.logger.warn('Error adding new paste: wrong key');
|
||||
res.writeHead(403, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify({ message: 'Error adding new paste: wrong key' }));
|
||||
return;
|
||||
}
|
||||
buffer = buffer.substring(this.createKey.length);
|
||||
}
|
||||
|
||||
// creates a new key using the key-generator
|
||||
DocumentHandler.prototype.acceptableKey = function() {
|
||||
return this.keyGenerator.createKey(this.keyLength);
|
||||
};
|
||||
this.chooseKey((key) => {
|
||||
this.store.set(key, buffer, (success) => {
|
||||
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');
|
||||
var crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
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
|
||||
var FileDocumentStore = function(options) {
|
||||
this.basePath = options.path || './data';
|
||||
logger.info('Path to data: ' + this.basePath);
|
||||
};
|
||||
class FileDocumentStore {
|
||||
constructor(options) {
|
||||
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
|
||||
FileDocumentStore.prototype.set = function(key, data, callback) {
|
||||
var _this = this;
|
||||
fs.mkdir(this.basePath, '700', function(err) {
|
||||
fs.writeFile(_this.basePath + '/' + key, data, 'utf8', function(err) {
|
||||
if (err) {
|
||||
callback(false);
|
||||
} else {
|
||||
callback(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
// saves a new file to the filesystem
|
||||
set(key, data, callback) {
|
||||
if (!validKeyRegex.test(key)) {
|
||||
this.logger.warn(`Invalid key provided for set: ${key}`);
|
||||
return callback(false);
|
||||
}
|
||||
|
||||
// gets an exisiting file from the filesystem
|
||||
FileDocumentStore.prototype.get = function(key, callback) {
|
||||
var _this = this;
|
||||
fs.readFile(this.basePath + '/' + key, 'utf8', function(err, data) {
|
||||
if (err) {
|
||||
callback(false);
|
||||
} else {
|
||||
callback(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
const resolvedBasePath = path.resolve(this.basePath);
|
||||
const filePath = path.resolve(resolvedBasePath, key);
|
||||
|
||||
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() {
|
||||
this.keyspace = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
};
|
||||
const crypto = require('crypto');
|
||||
|
||||
KeyGenerator.prototype.createKey = function(keyLength) {
|
||||
var key = '';
|
||||
var index;
|
||||
for (var i = 0; i < keyLength; i++) {
|
||||
index = Math.floor(Math.random() * this.keyspace.length);
|
||||
key += this.keyspace.charAt(index);
|
||||
}
|
||||
return key;
|
||||
};
|
||||
class KeyGenerator {
|
||||
constructor() {
|
||||
this.keyspace = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
}
|
||||
|
||||
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",
|
||||
"version": "1.0.0",
|
||||
"version": "2.0.0",
|
||||
"private": true,
|
||||
"description": "unknownBIN is an open-source Pastebin software written in node.js.",
|
||||
"keywords": [
|
||||
"paste",
|
||||
"pastebin",
|
||||
"haste",
|
||||
"hastebin",
|
||||
"code",
|
||||
"syntax highlighting"
|
||||
],
|
||||
"description": "A secure and modern open-source Pastebin software written in node.js.",
|
||||
"author": {
|
||||
"name": "MrUnknownDE",
|
||||
"email": "me@mrunk.de",
|
||||
@@ -18,12 +10,14 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"clean-css": "~3.4.19",
|
||||
"express": "~4.14.0",
|
||||
"uglify-js": "~2.7.0",
|
||||
"winston": "~2.2.0"
|
||||
"clean-css": "^5.3.3",
|
||||
"express": "^4.19.2",
|
||||
"helmet": "^7.1.0",
|
||||
"uglify-js": "^3.17.4",
|
||||
"winston": "^3.13.0"
|
||||
},
|
||||
"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');
|
||||
var url = require('url');
|
||||
var fs = require('fs');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
var express = require('express');
|
||||
var logger = require('winston');
|
||||
const express = require('express');
|
||||
const helmet = require('helmet');
|
||||
const winston = require('winston');
|
||||
|
||||
var DocumentHandler = require('./lib/document_handler.js');
|
||||
var FileStorage = require('./lib/file_storage.js');
|
||||
const DocumentHandler = require('./lib/document_handler.js');
|
||||
const FileStorage = require('./lib/file_storage.js');
|
||||
|
||||
// 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.host = process.env.HOST || config.host || 'localhost';
|
||||
config.host = process.env.HOST || config.host || '0.0.0.0';
|
||||
|
||||
// logger-setup
|
||||
logger.remove(logger.transports.Console);
|
||||
logger.add(logger.transports.Console, {colorize: true, level: 'verbose'});
|
||||
logger.info('Welcome to Hastebin Plus!');
|
||||
const logger = winston.createLogger({
|
||||
level: 'verbose',
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple()
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.Console()
|
||||
]
|
||||
});
|
||||
|
||||
logger.info('Welcome to unknownBIN!');
|
||||
|
||||
// init file-storage
|
||||
var fileStorage = new FileStorage(config.dataPath);
|
||||
const fileStorage = new FileStorage({ path: config.dataPath, logger: logger });
|
||||
|
||||
// load static documents into file-storage
|
||||
for (var name in config.documents) {
|
||||
var path = config.documents[name];
|
||||
var data = fs.readFileSync(path, 'utf8');
|
||||
if (data) {
|
||||
fileStorage.set(name, data, function(success) {});
|
||||
logger.verbose('Created document: ' + name + " ==> " + path);
|
||||
} else {
|
||||
logger.warn('Unable to find document: ' + name + " ==> " + path);
|
||||
}
|
||||
if (config.documents) {
|
||||
for (const name in config.documents) {
|
||||
const docPath = config.documents[name];
|
||||
try {
|
||||
const data = fs.readFileSync(docPath, 'utf8');
|
||||
fileStorage.set(name, data, (success) => {
|
||||
if (success) {
|
||||
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
|
||||
var documentHandler = new DocumentHandler({
|
||||
store: fileStorage,
|
||||
maxLength: config.maxLength,
|
||||
keyLength: config.keyLength,
|
||||
createKey: config.createKey
|
||||
const documentHandler = new DocumentHandler({
|
||||
store: fileStorage,
|
||||
maxLength: config.maxLength,
|
||||
keyLength: config.keyLength,
|
||||
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
|
||||
var app = express();
|
||||
const app = express();
|
||||
|
||||
app.get('/raw/:id', function(req, res) {
|
||||
return documentHandler.handleRawGet(req.params.id, res);
|
||||
// Use helmet for basic security headers
|
||||
app.use(helmet());
|
||||
|
||||
app.get('/raw/:id', (req, res) => {
|
||||
return documentHandler.handleRawGet(req.params.id, res);
|
||||
});
|
||||
app.post('/documents', function(req, res) {
|
||||
return documentHandler.handlePost(req, res);
|
||||
app.post('/documents', (req, res) => {
|
||||
return documentHandler.handlePost(req, res);
|
||||
});
|
||||
app.get('/documents/:id', function(req, 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.get('/documents/:id', (req, res) => {
|
||||
return documentHandler.handleGet(req.params.id, res);
|
||||
});
|
||||
|
||||
app.listen(config.port, config.host);
|
||||
logger.info('Listening on ' + config.host + ':' + config.port);
|
||||
app.use(express.static(path.join(__dirname, 'static')));
|
||||
|
||||
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 {
|
||||
background: #002B36;
|
||||
height: 90%;
|
||||
color: #839496;
|
||||
font-family: sans-serif;
|
||||
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 {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
flex-grow: 1;
|
||||
padding: 1em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
textarea {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: #fff;
|
||||
color: #eee8d5;
|
||||
font-family: monospace;
|
||||
font-size: 1em;
|
||||
height: 100%;
|
||||
@@ -34,6 +65,7 @@ textarea {
|
||||
outline: none;
|
||||
padding: 0 0 4em 0;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#code code {
|
||||
@@ -60,77 +92,4 @@ pre .line::before {
|
||||
-moz-user-select: none;
|
||||
-ms-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
|
||||
var haste = function() {
|
||||
this.appName = "Hastebin Plus";
|
||||
const Haste = function() {
|
||||
this.appName = "unknownBIN";
|
||||
this.$textarea = $('textarea');
|
||||
this.$box = $('#code');
|
||||
this.$code = $('#code code');
|
||||
@@ -9,26 +8,26 @@ var haste = function() {
|
||||
};
|
||||
|
||||
// set title (browser window)
|
||||
haste.prototype.setTitle = function(ext) {
|
||||
var title = ext ? this.appName + ' - ' + ext : this.appName;
|
||||
Haste.prototype.setTitle = function(ext) {
|
||||
const title = ext ? `${this.appName} - ${ext}` : this.appName;
|
||||
document.title = title;
|
||||
};
|
||||
|
||||
// show the light key
|
||||
haste.prototype.lightKey = function() {
|
||||
Haste.prototype.lightKey = function() {
|
||||
this.configureKey(['new', 'save']);
|
||||
};
|
||||
|
||||
// show the full key
|
||||
haste.prototype.fullKey = function() {
|
||||
Haste.prototype.fullKey = function() {
|
||||
this.configureKey(['new', 'duplicate', 'raw']);
|
||||
};
|
||||
|
||||
// enable certain keys
|
||||
haste.prototype.configureKey = function(enable) {
|
||||
Haste.prototype.configureKey = function(enable) {
|
||||
$('#tools .function').each(function() {
|
||||
var $this = $(this);
|
||||
for (var i = 0; i < enable.length; i++) {
|
||||
const $this = $(this);
|
||||
for (let i = 0; i < enable.length; i++) {
|
||||
if ($this.hasClass(enable[i])) {
|
||||
$this.addClass('enabled');
|
||||
return true;
|
||||
@@ -39,9 +38,9 @@ haste.prototype.configureKey = function(enable) {
|
||||
};
|
||||
|
||||
// setup a new, blank document
|
||||
haste.prototype.newDocument = function(hideHistory) {
|
||||
Haste.prototype.newDocument = function(hideHistory) {
|
||||
this.$box.hide();
|
||||
this.doc = new haste_document();
|
||||
this.doc = new HasteDocument();
|
||||
if (!hideHistory) {
|
||||
window.history.pushState(null, this.appName, '/');
|
||||
}
|
||||
@@ -53,14 +52,14 @@ haste.prototype.newDocument = function(hideHistory) {
|
||||
};
|
||||
|
||||
// load an existing document
|
||||
haste.prototype.loadDocument = function(key) {
|
||||
var _this = this;
|
||||
_this.doc = new haste_document();
|
||||
Haste.prototype.loadDocument = function(key) {
|
||||
const _this = this;
|
||||
_this.doc = new HasteDocument();
|
||||
_this.doc.load(key, function(ret) {
|
||||
if (ret) {
|
||||
_this.$code.html(ret.value);
|
||||
_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.$textarea.val('').hide();
|
||||
_this.$box.show();
|
||||
@@ -71,22 +70,22 @@ haste.prototype.loadDocument = function(key) {
|
||||
};
|
||||
|
||||
// duplicate the current document
|
||||
haste.prototype.duplicateDocument = function() {
|
||||
Haste.prototype.duplicateDocument = function() {
|
||||
if (this.doc.locked) {
|
||||
var currentData = this.doc.data;
|
||||
const currentData = this.doc.data;
|
||||
this.newDocument();
|
||||
this.$textarea.val(currentData);
|
||||
}
|
||||
};
|
||||
|
||||
// lock the current document
|
||||
haste.prototype.lockDocument = function() {
|
||||
var _this = this;
|
||||
Haste.prototype.lockDocument = function() {
|
||||
const _this = this;
|
||||
this.doc.save(this.$textarea.val(), function(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.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.$textarea.val('').hide();
|
||||
_this.$box.show();
|
||||
@@ -95,8 +94,8 @@ haste.prototype.lockDocument = function() {
|
||||
};
|
||||
|
||||
// configure buttons and their shortcuts
|
||||
haste.prototype.configureButtons = function() {
|
||||
var _this = this;
|
||||
Haste.prototype.configureButtons = function() {
|
||||
const _this = this;
|
||||
this.buttons = [
|
||||
{
|
||||
$where: $('#tools .save'),
|
||||
@@ -133,17 +132,17 @@ haste.prototype.configureButtons = function() {
|
||||
return evt.ctrlKey && evt.shiftKey && evt.keyCode === 82;
|
||||
},
|
||||
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]);
|
||||
}
|
||||
};
|
||||
|
||||
// handles the button-click
|
||||
haste.prototype.configureButton = function(options) {
|
||||
Haste.prototype.configureButton = function(options) {
|
||||
options.$where.click(function(evt) {
|
||||
evt.preventDefault();
|
||||
if (!options.clickDisabled && $(this).hasClass('enabled')) {
|
||||
@@ -153,11 +152,11 @@ haste.prototype.configureButton = function(options) {
|
||||
};
|
||||
|
||||
// enables the configured shortcuts
|
||||
haste.prototype.configureShortcuts = function() {
|
||||
var _this = this;
|
||||
Haste.prototype.configureShortcuts = function() {
|
||||
const _this = this;
|
||||
$(document.body).keydown(function(evt) {
|
||||
var button;
|
||||
for (var i = 0; i < _this.buttons.length; i++) {
|
||||
let button;
|
||||
for (let i = 0; i < _this.buttons.length; i++) {
|
||||
button = _this.buttons[i];
|
||||
if (button.shortcut && button.shortcut(evt)) {
|
||||
evt.preventDefault();
|
||||
@@ -169,12 +168,12 @@ haste.prototype.configureShortcuts = function() {
|
||||
};
|
||||
|
||||
// represents a single document
|
||||
var haste_document = function() {
|
||||
const HasteDocument = function() {
|
||||
this.locked = false;
|
||||
};
|
||||
|
||||
// escape HTML-characters
|
||||
haste_document.prototype.htmlEscape = function(s) {
|
||||
HasteDocument.prototype.htmlEscape = function(s) {
|
||||
return s
|
||||
.replace(/&/g, '&')
|
||||
.replace(/>/g, '>')
|
||||
@@ -183,40 +182,39 @@ haste_document.prototype.htmlEscape = function(s) {
|
||||
};
|
||||
|
||||
// load a document from the server
|
||||
haste_document.prototype.load = function(key, callback) {
|
||||
var _this = this;
|
||||
$.ajax('/documents/' + key, {
|
||||
HasteDocument.prototype.load = function(key, callback) {
|
||||
const _this = this;
|
||||
$.ajax(`/documents/${key}`, {
|
||||
type: 'get',
|
||||
dataType: 'json',
|
||||
success: function(res) {
|
||||
_this.locked = true;
|
||||
_this.key = key;
|
||||
_this.data = res.data;
|
||||
high = hljs.highlightAuto(res.data).value;
|
||||
const high = hljs.highlightAuto(res.data).value;
|
||||
callback({
|
||||
value: high.replace(/.+/g, "<span class=\"line\">$&</span>").replace(/^\s*[\r\n]/gm, "<span class=\"line\"></span>\n"),
|
||||
key: key,
|
||||
});
|
||||
},
|
||||
error: function(err) {
|
||||
error: function() {
|
||||
callback(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// sends the document to the server
|
||||
haste_document.prototype.save = function(data, callback) {
|
||||
HasteDocument.prototype.save = function(data, callback) {
|
||||
if (this.locked) return false;
|
||||
|
||||
this.data = data;
|
||||
var _this = this;
|
||||
$.ajax('/documents', {
|
||||
type: 'post',
|
||||
data: data.trim(),
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
contentType: 'text/plain; charset=utf-8',
|
||||
success: function(res) {
|
||||
new haste().loadDocument(res.key);
|
||||
new Haste().loadDocument(res.key);
|
||||
},
|
||||
error: function(res) {
|
||||
try {
|
||||
@@ -234,16 +232,16 @@ $(function() {
|
||||
// allow usage of tabs
|
||||
if (evt.keyCode === 9) {
|
||||
evt.preventDefault();
|
||||
var myValue = ' ';
|
||||
const myValue = ' ';
|
||||
if (document.selection) {
|
||||
this.focus();
|
||||
sel = document.selection.createRange();
|
||||
let sel = document.selection.createRange();
|
||||
sel.text = myValue;
|
||||
this.focus();
|
||||
} else if (this.selectionStart || this.selectionStart == '0') {
|
||||
var startPos = this.selectionStart;
|
||||
var endPos = this.selectionEnd;
|
||||
var scrollTop = this.scrollTop;
|
||||
const startPos = this.selectionStart;
|
||||
const endPos = this.selectionEnd;
|
||||
const scrollTop = this.scrollTop;
|
||||
this.value = this.value.substring(0, startPos) + myValue + this.value.substring(endPos, this.value.length);
|
||||
this.focus();
|
||||
this.selectionStart = startPos + myValue.length;
|
||||
@@ -256,8 +254,8 @@ $(function() {
|
||||
}
|
||||
});
|
||||
|
||||
var app = new haste();
|
||||
var path = window.location.pathname;
|
||||
const app = new Haste();
|
||||
const path = window.location.pathname;
|
||||
if (path === '/') {
|
||||
app.newDocument(true);
|
||||
} 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>
|
||||
<html>
|
||||
<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">
|
||||
<link rel="stylesheet" type="text/css" href="application.min.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="highlight.min.css"/>
|
||||
@@ -11,15 +11,15 @@
|
||||
<script src="application.min.js"></script>
|
||||
</head>
|
||||
<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">
|
||||
<pre id="code" style="display:none;" tabindex="0"><code></code></pre>
|
||||
<textarea spellcheck="false" style="display:none;"></textarea>
|
||||
</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>
|
||||
</html>
|
||||
</html>
|
||||
Reference in New Issue
Block a user