You've already forked hastebin-ansi
mirror of
https://github.com/armbian/hastebin-ansi.git
synced 2026-01-06 12:30:55 -08:00
some basic hygiene before I can hack
This commit is contained in:
@@ -6,3 +6,6 @@ node_modules
|
||||
*.swo
|
||||
data
|
||||
*.DS_Store
|
||||
.github
|
||||
.idea
|
||||
config.json
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
**/*.min.js
|
||||
config.js
|
||||
config.json
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ node_modules
|
||||
data
|
||||
*.DS_Store
|
||||
docker-compose.override.yml
|
||||
.idea
|
||||
|
||||
34
Dockerfile
34
Dockerfile
@@ -1,4 +1,4 @@
|
||||
FROM node:16-stretch
|
||||
FROM node:16
|
||||
|
||||
RUN mkdir -p /usr/src/app && \
|
||||
chown node:node /usr/src/app
|
||||
@@ -7,25 +7,23 @@ USER node:node
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY --chown=node:node package*.json .
|
||||
|
||||
RUN npm ci
|
||||
|
||||
COPY --chown=node:node . .
|
||||
|
||||
RUN npm install && \
|
||||
npm install redis@0.8.1 && \
|
||||
npm install pg@4.5.7 && \
|
||||
npm install memcached@2.2.2 && \
|
||||
npm install aws-sdk@2.814.0 && \
|
||||
npm install rethinkdbdash@2.3.31
|
||||
|
||||
ENV STORAGE_TYPE=memcached \
|
||||
STORAGE_HOST=127.0.0.1 \
|
||||
STORAGE_PORT=11211\
|
||||
STORAGE_EXPIRE_SECONDS=2592000\
|
||||
STORAGE_DB=2 \
|
||||
STORAGE_AWS_BUCKET= \
|
||||
STORAGE_AWS_REGION= \
|
||||
STORAGE_USENAME= \
|
||||
STORAGE_PASSWORD= \
|
||||
STORAGE_FILEPATH=
|
||||
STORAGE_AWS_BUCKET="" \
|
||||
STORAGE_AWS_REGION="" \
|
||||
STORAGE_USENAME="" \
|
||||
STORAGE_PASSWORD="" \
|
||||
STORAGE_FILEPATH=""
|
||||
|
||||
ENV LOGGING_LEVEL=verbose \
|
||||
LOGGING_TYPE=Console \
|
||||
@@ -39,20 +37,20 @@ ENV HOST=0.0.0.0\
|
||||
RECOMPRESS_STATIC_ASSETS=true
|
||||
|
||||
ENV KEYGENERATOR_TYPE=phonetic \
|
||||
KEYGENERATOR_KEYSPACE=
|
||||
KEYGENERATOR_KEYSPACE=""
|
||||
|
||||
ENV RATELIMITS_NORMAL_TOTAL_REQUESTS=500\
|
||||
RATELIMITS_NORMAL_EVERY_MILLISECONDS=60000 \
|
||||
RATELIMITS_WHITELIST_TOTAL_REQUESTS= \
|
||||
RATELIMITS_WHITELIST_EVERY_MILLISECONDS= \
|
||||
RATELIMITS_WHITELIST_TOTAL_REQUESTS="" \
|
||||
RATELIMITS_WHITELIST_EVERY_MILLISECONDS="" \
|
||||
# comma separated list for the whitelisted \
|
||||
RATELIMITS_WHITELIST=example1.whitelist,example2.whitelist \
|
||||
\
|
||||
RATELIMITS_BLACKLIST_TOTAL_REQUESTS= \
|
||||
RATELIMITS_BLACKLIST_EVERY_MILLISECONDS= \
|
||||
RATELIMITS_BLACKLIST_TOTAL_REQUESTS="" \
|
||||
RATELIMITS_BLACKLIST_EVERY_MILLISECONDS="" \
|
||||
# comma separated list for the blacklisted \
|
||||
RATELIMITS_BLACKLIST=example1.blacklist,example2.blacklist
|
||||
ENV DOCUMENTS=about=./about.md
|
||||
ENV DOCUMENTS="about=./about.md"
|
||||
|
||||
EXPOSE ${PORT}
|
||||
STOPSIGNAL SIGINT
|
||||
|
||||
@@ -39,7 +39,7 @@ STDOUT. Check the README there for more details and usages.
|
||||
## Installation
|
||||
|
||||
1. Download the package, and expand it
|
||||
2. Explore the settings inside of config.js, but the defaults should be good
|
||||
2. Explore the settings inside of config.json, but the defaults should be good
|
||||
3. `npm install`
|
||||
4. `npm start` (you may specify an optional `<config-path>` as well)
|
||||
|
||||
@@ -62,7 +62,7 @@ STDOUT. Check the README there for more details and usages.
|
||||
|
||||
When present, the `rateLimits` option enables built-in rate limiting courtesy
|
||||
of `connect-ratelimit`. Any of the options supported by that library can be
|
||||
used and set in `config.js`.
|
||||
used and set in `config.json`.
|
||||
|
||||
See the README for [connect-ratelimit](https://github.com/dharmafly/connect-ratelimit)
|
||||
for more information!
|
||||
@@ -97,7 +97,7 @@ for the key.
|
||||
|
||||
### File
|
||||
|
||||
To use file storage (the default) change the storage section in `config.js` to
|
||||
To use file storage (the default) change the storage section in `config.json` to
|
||||
something like:
|
||||
|
||||
``` json
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
{
|
||||
|
||||
"host": "0.0.0.0",
|
||||
"port": 7777,
|
||||
|
||||
"keyLength": 10,
|
||||
|
||||
"maxLength": 400000,
|
||||
|
||||
"staticMaxAge": 86400,
|
||||
|
||||
"recompressStaticAssets": true,
|
||||
|
||||
"logging": [
|
||||
{
|
||||
"level": "verbose",
|
||||
@@ -18,11 +12,9 @@
|
||||
"colorize": true
|
||||
}
|
||||
],
|
||||
|
||||
"keyGenerator": {
|
||||
"type": "phonetic"
|
||||
},
|
||||
|
||||
"rateLimits": {
|
||||
"categories": {
|
||||
"normal": {
|
||||
@@ -31,13 +23,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"storage": {
|
||||
"type": "file"
|
||||
},
|
||||
|
||||
"documents": {
|
||||
"about": "./about.md"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,6 @@ services:
|
||||
- STORAGE_HOST=memcached
|
||||
- STORAGE_PORT=11211
|
||||
ports:
|
||||
- 7777:7777
|
||||
- "7777:7777"
|
||||
memcached:
|
||||
image: memcached:latest
|
||||
|
||||
@@ -1,108 +1,108 @@
|
||||
const {
|
||||
HOST,
|
||||
PORT,
|
||||
KEY_LENGTH,
|
||||
MAX_LENGTH,
|
||||
STATIC_MAX_AGE,
|
||||
RECOMPRESS_STATIC_ASSETS,
|
||||
STORAGE_TYPE,
|
||||
STORAGE_HOST,
|
||||
STORAGE_PORT,
|
||||
STORAGE_EXPIRE_SECONDS,
|
||||
STORAGE_DB,
|
||||
STORAGE_AWS_BUCKET,
|
||||
STORAGE_AWS_REGION,
|
||||
STORAGE_PASSWORD,
|
||||
STORAGE_USERNAME,
|
||||
STORAGE_FILEPATH,
|
||||
LOGGING_LEVEL,
|
||||
LOGGING_TYPE,
|
||||
LOGGING_COLORIZE,
|
||||
KEYGENERATOR_TYPE,
|
||||
KEY_GENERATOR_KEYSPACE,
|
||||
RATE_LIMITS_NORMAL_TOTAL_REQUESTS,
|
||||
RATE_LIMITS_NORMAL_EVERY_MILLISECONDS,
|
||||
RATE_LIMITS_WHITELIST_TOTAL_REQUESTS,
|
||||
RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS,
|
||||
RATE_LIMITS_WHITELIST,
|
||||
RATE_LIMITS_BLACKLIST_TOTAL_REQUESTS,
|
||||
RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS,
|
||||
RATE_LIMITS_BLACKLIST,
|
||||
DOCUMENTS,
|
||||
HOST,
|
||||
PORT,
|
||||
KEY_LENGTH,
|
||||
MAX_LENGTH,
|
||||
STATIC_MAX_AGE,
|
||||
RECOMPRESS_STATIC_ASSETS,
|
||||
STORAGE_TYPE,
|
||||
STORAGE_HOST,
|
||||
STORAGE_PORT,
|
||||
STORAGE_EXPIRE_SECONDS,
|
||||
STORAGE_DB,
|
||||
STORAGE_AWS_BUCKET,
|
||||
STORAGE_AWS_REGION,
|
||||
STORAGE_PASSWORD,
|
||||
STORAGE_USERNAME,
|
||||
STORAGE_FILEPATH,
|
||||
LOGGING_LEVEL,
|
||||
LOGGING_TYPE,
|
||||
LOGGING_COLORIZE,
|
||||
KEYGENERATOR_TYPE,
|
||||
KEY_GENERATOR_KEYSPACE,
|
||||
RATE_LIMITS_NORMAL_TOTAL_REQUESTS,
|
||||
RATE_LIMITS_NORMAL_EVERY_MILLISECONDS,
|
||||
RATE_LIMITS_WHITELIST_TOTAL_REQUESTS,
|
||||
RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS,
|
||||
RATE_LIMITS_WHITELIST,
|
||||
RATE_LIMITS_BLACKLIST_TOTAL_REQUESTS,
|
||||
RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS,
|
||||
RATE_LIMITS_BLACKLIST,
|
||||
DOCUMENTS,
|
||||
} = process.env;
|
||||
|
||||
const config = {
|
||||
host: HOST,
|
||||
port: Number(PORT),
|
||||
host: HOST,
|
||||
port: Number(PORT),
|
||||
|
||||
keyLength: Number(KEY_LENGTH),
|
||||
keyLength: Number(KEY_LENGTH),
|
||||
|
||||
maxLength: Number(MAX_LENGTH),
|
||||
maxLength: Number(MAX_LENGTH),
|
||||
|
||||
staticMaxAge: Number(STATIC_MAX_AGE),
|
||||
staticMaxAge: Number(STATIC_MAX_AGE),
|
||||
|
||||
recompressStaticAssets: RECOMPRESS_STATIC_ASSETS,
|
||||
recompressStaticAssets: RECOMPRESS_STATIC_ASSETS,
|
||||
|
||||
logging: [
|
||||
{
|
||||
level: LOGGING_LEVEL,
|
||||
type: LOGGING_TYPE,
|
||||
colorize: LOGGING_COLORIZE,
|
||||
logging: [
|
||||
{
|
||||
level: LOGGING_LEVEL,
|
||||
type: LOGGING_TYPE,
|
||||
colorize: LOGGING_COLORIZE,
|
||||
},
|
||||
],
|
||||
|
||||
keyGenerator: {
|
||||
type: KEYGENERATOR_TYPE,
|
||||
keyspace: KEY_GENERATOR_KEYSPACE,
|
||||
},
|
||||
],
|
||||
|
||||
keyGenerator: {
|
||||
type: KEYGENERATOR_TYPE,
|
||||
keyspace: KEY_GENERATOR_KEYSPACE,
|
||||
},
|
||||
|
||||
rateLimits: {
|
||||
whitelist: RATE_LIMITS_WHITELIST ? RATE_LIMITS_WHITELIST.split(",") : [],
|
||||
blacklist: RATE_LIMITS_BLACKLIST ? RATE_LIMITS_BLACKLIST.split(",") : [],
|
||||
categories: {
|
||||
normal: {
|
||||
totalRequests: RATE_LIMITS_NORMAL_TOTAL_REQUESTS,
|
||||
every: RATE_LIMITS_NORMAL_EVERY_MILLISECONDS,
|
||||
},
|
||||
whitelist:
|
||||
RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS ||
|
||||
RATE_LIMITS_WHITELIST_TOTAL_REQUESTS
|
||||
? {
|
||||
totalRequests: RATE_LIMITS_WHITELIST_TOTAL_REQUESTS,
|
||||
every: RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS,
|
||||
}
|
||||
: null,
|
||||
blacklist:
|
||||
RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS ||
|
||||
RATE_LIMITS_BLACKLIST_TOTAL_REQUESTS
|
||||
? {
|
||||
totalRequests: RATE_LIMITS_WHITELIST_TOTAL_REQUESTS,
|
||||
every: RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS,
|
||||
}
|
||||
: null,
|
||||
rateLimits: {
|
||||
whitelist: RATE_LIMITS_WHITELIST ? RATE_LIMITS_WHITELIST.split(",") : [],
|
||||
blacklist: RATE_LIMITS_BLACKLIST ? RATE_LIMITS_BLACKLIST.split(",") : [],
|
||||
categories: {
|
||||
normal: {
|
||||
totalRequests: RATE_LIMITS_NORMAL_TOTAL_REQUESTS,
|
||||
every: RATE_LIMITS_NORMAL_EVERY_MILLISECONDS,
|
||||
},
|
||||
whitelist:
|
||||
RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS ||
|
||||
RATE_LIMITS_WHITELIST_TOTAL_REQUESTS
|
||||
? {
|
||||
totalRequests: RATE_LIMITS_WHITELIST_TOTAL_REQUESTS,
|
||||
every: RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS,
|
||||
}
|
||||
: null,
|
||||
blacklist:
|
||||
RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS ||
|
||||
RATE_LIMITS_BLACKLIST_TOTAL_REQUESTS
|
||||
? {
|
||||
totalRequests: RATE_LIMITS_WHITELIST_TOTAL_REQUESTS,
|
||||
every: RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
storage: {
|
||||
type: STORAGE_TYPE,
|
||||
host: STORAGE_HOST,
|
||||
port: Number(STORAGE_PORT),
|
||||
expire: Number(STORAGE_EXPIRE_SECONDS),
|
||||
bucket: STORAGE_AWS_BUCKET,
|
||||
region: STORAGE_AWS_REGION,
|
||||
connectionUrl: `postgres://${STORAGE_USERNAME}:${STORAGE_PASSWORD}@${STORAGE_HOST}:${STORAGE_PORT}/${STORAGE_DB}`,
|
||||
db: STORAGE_DB,
|
||||
user: STORAGE_USERNAME,
|
||||
password: STORAGE_PASSWORD,
|
||||
path: STORAGE_FILEPATH,
|
||||
},
|
||||
storage: {
|
||||
type: STORAGE_TYPE,
|
||||
host: STORAGE_HOST,
|
||||
port: Number(STORAGE_PORT),
|
||||
expire: Number(STORAGE_EXPIRE_SECONDS),
|
||||
bucket: STORAGE_AWS_BUCKET,
|
||||
region: STORAGE_AWS_REGION,
|
||||
connectionUrl: `postgres://${STORAGE_USERNAME}:${STORAGE_PASSWORD}@${STORAGE_HOST}:${STORAGE_PORT}/${STORAGE_DB}`,
|
||||
db: STORAGE_DB,
|
||||
user: STORAGE_USERNAME,
|
||||
password: STORAGE_PASSWORD,
|
||||
path: STORAGE_FILEPATH,
|
||||
},
|
||||
|
||||
documents: DOCUMENTS
|
||||
? DOCUMENTS.split(",").reduce((acc, item) => {
|
||||
const keyAndValueArray = item.replace(/\s/g, "").split("=");
|
||||
return { ...acc, [keyAndValueArray[0]]: keyAndValueArray[1] };
|
||||
}, {})
|
||||
: null,
|
||||
documents: DOCUMENTS
|
||||
? DOCUMENTS.split(",").reduce((acc, item) => {
|
||||
const keyAndValueArray = item.replace(/\s/g, "").split("=");
|
||||
return {...acc, [keyAndValueArray[0]]: keyAndValueArray[1]};
|
||||
}, {})
|
||||
: null,
|
||||
};
|
||||
|
||||
console.log(JSON.stringify(config));
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
|
||||
set -e
|
||||
|
||||
node ./docker-entrypoint.js > ./config.js
|
||||
node ./docker-entrypoint.js > ./config.json
|
||||
|
||||
exec "$@"
|
||||
|
||||
@@ -1,155 +1,152 @@
|
||||
var winston = require('winston');
|
||||
var Busboy = require('busboy');
|
||||
const winston = require('winston');
|
||||
const Busboy = require('busboy');
|
||||
|
||||
// For handling serving stored documents
|
||||
|
||||
var DocumentHandler = function(options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
this.keyLength = options.keyLength || DocumentHandler.defaultKeyLength;
|
||||
this.maxLength = options.maxLength; // none by default
|
||||
this.store = options.store;
|
||||
this.keyGenerator = options.keyGenerator;
|
||||
const DocumentHandler = function (options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
this.keyLength = options.keyLength || DocumentHandler.defaultKeyLength;
|
||||
this.maxLength = options.maxLength; // none by default
|
||||
this.store = options.store;
|
||||
this.keyGenerator = options.keyGenerator;
|
||||
};
|
||||
|
||||
DocumentHandler.defaultKeyLength = 10;
|
||||
|
||||
// Handle retrieving a document
|
||||
DocumentHandler.prototype.handleGet = function(request, response, config) {
|
||||
const key = request.params.id.split('.')[0];
|
||||
const skipExpire = !!config.documents[key];
|
||||
DocumentHandler.prototype.handleGet = function (request, response, config) {
|
||||
const key = request.params.id.split('.')[0];
|
||||
const skipExpire = !!config.documents[key];
|
||||
|
||||
this.store.get(key, function(ret) {
|
||||
if (ret) {
|
||||
winston.verbose('retrieved document', { key: key });
|
||||
response.writeHead(200, { 'content-type': 'application/json' });
|
||||
if (request.method === 'HEAD') {
|
||||
response.end();
|
||||
} else {
|
||||
response.end(JSON.stringify({ data: ret, key: key }));
|
||||
}
|
||||
}
|
||||
else {
|
||||
winston.warn('document not found', { key: key });
|
||||
response.writeHead(404, { 'content-type': 'application/json' });
|
||||
if (request.method === 'HEAD') {
|
||||
response.end();
|
||||
} else {
|
||||
response.end(JSON.stringify({ message: 'Document not found.' }));
|
||||
}
|
||||
}
|
||||
}, skipExpire);
|
||||
this.store.get(key, function (ret) {
|
||||
if (ret) {
|
||||
winston.verbose('retrieved document', {key: key});
|
||||
response.writeHead(200, {'content-type': 'application/json'});
|
||||
if (request.method === 'HEAD') {
|
||||
response.end();
|
||||
} else {
|
||||
response.end(JSON.stringify({data: ret, key: key}));
|
||||
}
|
||||
} else {
|
||||
winston.warn('document not found', {key: key});
|
||||
response.writeHead(404, {'content-type': 'application/json'});
|
||||
if (request.method === 'HEAD') {
|
||||
response.end();
|
||||
} else {
|
||||
response.end(JSON.stringify({message: 'Document not found.'}));
|
||||
}
|
||||
}
|
||||
}, skipExpire);
|
||||
};
|
||||
|
||||
// Handle retrieving the raw version of a document
|
||||
DocumentHandler.prototype.handleRawGet = function(request, response, config) {
|
||||
const key = request.params.id.split('.')[0];
|
||||
const skipExpire = !!config.documents[key];
|
||||
DocumentHandler.prototype.handleRawGet = function (request, response, config) {
|
||||
const key = request.params.id.split('.')[0];
|
||||
const skipExpire = !!config.documents[key];
|
||||
|
||||
this.store.get(key, function(ret) {
|
||||
if (ret) {
|
||||
winston.verbose('retrieved raw document', { key: key });
|
||||
response.writeHead(200, { 'content-type': 'text/plain; charset=UTF-8' });
|
||||
if (request.method === 'HEAD') {
|
||||
response.end();
|
||||
} else {
|
||||
response.end(ret);
|
||||
}
|
||||
}
|
||||
else {
|
||||
winston.warn('raw document not found', { key: key });
|
||||
response.writeHead(404, { 'content-type': 'application/json' });
|
||||
if (request.method === 'HEAD') {
|
||||
response.end();
|
||||
} else {
|
||||
response.end(JSON.stringify({ message: 'Document not found.' }));
|
||||
}
|
||||
}
|
||||
}, skipExpire);
|
||||
this.store.get(key, function (ret) {
|
||||
if (ret) {
|
||||
winston.verbose('retrieved raw document', {key: key});
|
||||
response.writeHead(200, {'content-type': 'text/plain; charset=UTF-8'});
|
||||
if (request.method === 'HEAD') {
|
||||
response.end();
|
||||
} else {
|
||||
response.end(ret);
|
||||
}
|
||||
} else {
|
||||
winston.warn('raw document not found', {key: key});
|
||||
response.writeHead(404, {'content-type': 'application/json'});
|
||||
if (request.method === 'HEAD') {
|
||||
response.end();
|
||||
} else {
|
||||
response.end(JSON.stringify({message: 'Document not found.'}));
|
||||
}
|
||||
}
|
||||
}, skipExpire);
|
||||
};
|
||||
|
||||
// Handle adding a new Document
|
||||
DocumentHandler.prototype.handlePost = function (request, response) {
|
||||
var _this = this;
|
||||
var buffer = '';
|
||||
var cancelled = false;
|
||||
const _this = this;
|
||||
let buffer = '';
|
||||
let cancelled = false;
|
||||
|
||||
// What to do when done
|
||||
var onSuccess = function () {
|
||||
// Check length
|
||||
if (_this.maxLength && buffer.length > _this.maxLength) {
|
||||
cancelled = true;
|
||||
winston.warn('document >maxLength', { maxLength: _this.maxLength });
|
||||
response.writeHead(400, { 'content-type': 'application/json' });
|
||||
response.end(
|
||||
JSON.stringify({ message: 'Document exceeds maximum length.' })
|
||||
);
|
||||
return;
|
||||
// What to do when done
|
||||
const onSuccess = function () {
|
||||
// Check length
|
||||
if (_this.maxLength && buffer.length > _this.maxLength) {
|
||||
cancelled = true;
|
||||
winston.warn('document >maxLength', {maxLength: _this.maxLength});
|
||||
response.writeHead(400, {'content-type': 'application/json'});
|
||||
response.end(JSON.stringify({message: 'Document exceeds maximum length.'}));
|
||||
return;
|
||||
}
|
||||
// And then save if we should
|
||||
_this.chooseKey(function (key) {
|
||||
_this.store.set(key, buffer, function (res) {
|
||||
if (res) {
|
||||
winston.verbose('added document', {key: key});
|
||||
response.writeHead(200, {'content-type': 'application/json'});
|
||||
response.end(JSON.stringify({key: key}));
|
||||
} else {
|
||||
winston.verbose('error adding document');
|
||||
response.writeHead(500, {'content-type': 'application/json'});
|
||||
response.end(JSON.stringify({message: 'Error adding document.'}));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// If we should, parse a form to grab the data
|
||||
const ct = request.headers['content-type'];
|
||||
if (ct && ct.split(';')[0] === 'multipart/form-data') {
|
||||
const busboy = new Busboy({headers: request.headers});
|
||||
busboy.on('field', function (field_name, val) {
|
||||
if (field_name === 'data') {
|
||||
buffer = val;
|
||||
}
|
||||
});
|
||||
busboy.on('finish', function () {
|
||||
onSuccess();
|
||||
});
|
||||
request.pipe(busboy);
|
||||
// Otherwise, use our own and just grab flat data from POST body
|
||||
} else {
|
||||
request.on('data', function (data) {
|
||||
buffer += data.toString();
|
||||
});
|
||||
request.on('end', function () {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
onSuccess();
|
||||
});
|
||||
request.on('error', function (error) {
|
||||
winston.error('connection error: ' + error.message);
|
||||
response.writeHead(500, {'content-type': 'application/json'});
|
||||
response.end(JSON.stringify({message: 'Connection error.'}));
|
||||
cancelled = true;
|
||||
});
|
||||
}
|
||||
// And then save if we should
|
||||
_this.chooseKey(function (key) {
|
||||
_this.store.set(key, buffer, function (res) {
|
||||
if (res) {
|
||||
winston.verbose('added document', { key: key });
|
||||
response.writeHead(200, { 'content-type': 'application/json' });
|
||||
response.end(JSON.stringify({ key: key }));
|
||||
}
|
||||
else {
|
||||
winston.verbose('error adding document');
|
||||
response.writeHead(500, { 'content-type': 'application/json' });
|
||||
response.end(JSON.stringify({ message: 'Error adding document.' }));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// If we should, parse a form to grab the data
|
||||
var ct = request.headers['content-type'];
|
||||
if (ct && ct.split(';')[0] === 'multipart/form-data') {
|
||||
var busboy = new Busboy({ headers: request.headers });
|
||||
busboy.on('field', function (fieldname, val) {
|
||||
if (fieldname === 'data') {
|
||||
buffer = val;
|
||||
}
|
||||
});
|
||||
busboy.on('finish', function () {
|
||||
onSuccess();
|
||||
});
|
||||
request.pipe(busboy);
|
||||
// Otherwise, use our own and just grab flat data from POST body
|
||||
} else {
|
||||
request.on('data', function (data) {
|
||||
buffer += data.toString();
|
||||
});
|
||||
request.on('end', function () {
|
||||
if (cancelled) { return; }
|
||||
onSuccess();
|
||||
});
|
||||
request.on('error', function (error) {
|
||||
winston.error('connection error: ' + error.message);
|
||||
response.writeHead(500, { 'content-type': 'application/json' });
|
||||
response.end(JSON.stringify({ message: 'Connection error.' }));
|
||||
cancelled = true;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Keep choosing keys until one isn't taken
|
||||
DocumentHandler.prototype.chooseKey = function(callback) {
|
||||
var key = this.acceptableKey();
|
||||
var _this = this;
|
||||
this.store.get(key, function(ret) {
|
||||
if (ret) {
|
||||
_this.chooseKey(callback);
|
||||
} else {
|
||||
callback(key);
|
||||
}
|
||||
}, true); // Don't bump expirations when key searching
|
||||
DocumentHandler.prototype.chooseKey = function (callback) {
|
||||
const key = this.acceptableKey();
|
||||
const _this = this;
|
||||
this.store.get(key, function (ret) {
|
||||
if (ret) {
|
||||
_this.chooseKey(callback);
|
||||
} else {
|
||||
callback(key);
|
||||
}
|
||||
}, true); // Don't bump expirations when key searching
|
||||
};
|
||||
|
||||
DocumentHandler.prototype.acceptableKey = function() {
|
||||
return this.keyGenerator.createKey(this.keyLength);
|
||||
DocumentHandler.prototype.acceptableKey = function () {
|
||||
return this.keyGenerator.createKey(this.keyLength);
|
||||
};
|
||||
|
||||
module.exports = DocumentHandler;
|
||||
|
||||
@@ -1,63 +1,61 @@
|
||||
var fs = require('fs');
|
||||
var crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
|
||||
var winston = require('winston');
|
||||
const winston = require('winston');
|
||||
|
||||
// For storing in files
|
||||
// options[type] = file
|
||||
// options[path] - Where to store
|
||||
|
||||
var FileDocumentStore = function(options) {
|
||||
this.basePath = options.path || './data';
|
||||
this.expire = options.expire;
|
||||
const FileDocumentStore = function (options) {
|
||||
this.basePath = options.path || './data';
|
||||
this.expire = options.expire;
|
||||
};
|
||||
|
||||
// Generate md5 of a string
|
||||
FileDocumentStore.md5 = function(str) {
|
||||
var md5sum = crypto.createHash('md5');
|
||||
md5sum.update(str);
|
||||
return md5sum.digest('hex');
|
||||
FileDocumentStore.md5 = function (str) {
|
||||
const md5sum = crypto.createHash('md5');
|
||||
md5sum.update(str);
|
||||
return md5sum.digest('hex');
|
||||
};
|
||||
|
||||
// Save data in a file, key as md5 - since we don't know what we could
|
||||
// be passed here
|
||||
FileDocumentStore.prototype.set = function(key, data, callback, skipExpire) {
|
||||
try {
|
||||
var _this = this;
|
||||
fs.mkdir(this.basePath, '700', function() {
|
||||
var fn = _this.basePath + '/' + FileDocumentStore.md5(key);
|
||||
fs.writeFile(fn, data, 'utf8', function(err) {
|
||||
if (err) {
|
||||
callback(false);
|
||||
}
|
||||
else {
|
||||
callback(true);
|
||||
if (_this.expire && !skipExpire) {
|
||||
winston.warn('file store cannot set expirations on keys');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch(err) {
|
||||
callback(false);
|
||||
}
|
||||
FileDocumentStore.prototype.set = function (key, data, callback, skipExpire) {
|
||||
try {
|
||||
const _this = this;
|
||||
fs.mkdir(this.basePath, '700', function () {
|
||||
const fn = _this.basePath + '/' + FileDocumentStore.md5(key);
|
||||
fs.writeFile(fn, data, 'utf8', function (err) {
|
||||
if (err) {
|
||||
callback(false);
|
||||
} else {
|
||||
callback(true);
|
||||
if (_this.expire && !skipExpire) {
|
||||
winston.warn('file store cannot set expirations on keys');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
callback(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Get data from a file from key
|
||||
FileDocumentStore.prototype.get = function(key, callback, skipExpire) {
|
||||
var _this = this;
|
||||
var fn = this.basePath + '/' + FileDocumentStore.md5(key);
|
||||
fs.readFile(fn, 'utf8', function(err, data) {
|
||||
if (err) {
|
||||
callback(false);
|
||||
}
|
||||
else {
|
||||
callback(data);
|
||||
if (_this.expire && !skipExpire) {
|
||||
winston.warn('file store cannot set expirations on keys');
|
||||
}
|
||||
}
|
||||
});
|
||||
FileDocumentStore.prototype.get = function (key, callback, skipExpire) {
|
||||
const _this = this;
|
||||
const fn = this.basePath + '/' + FileDocumentStore.md5(key);
|
||||
fs.readFile(fn, 'utf8', function (err, data) {
|
||||
if (err) {
|
||||
callback(false);
|
||||
} else {
|
||||
callback(data);
|
||||
if (_this.expire && !skipExpire) {
|
||||
winston.warn('file store cannot set expirations on keys');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = FileDocumentStore;
|
||||
|
||||
2659
package-lock.json
generated
2659
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "haste",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.0-rpardini",
|
||||
"private": true,
|
||||
"description": "Private Pastebin Server",
|
||||
"keywords": [
|
||||
@@ -14,19 +14,20 @@
|
||||
},
|
||||
"main": "haste",
|
||||
"dependencies": {
|
||||
"busboy": "0.2.4",
|
||||
"connect": "^3.7.0",
|
||||
"connect-ratelimit": "0.0.7",
|
||||
"connect-route": "0.1.5",
|
||||
"pg": "^8.0.0",
|
||||
"redis": "0.8.1",
|
||||
"redis-url": "0.1.0",
|
||||
"st": "^2.0.0",
|
||||
"uglify-js": "3.1.6",
|
||||
"winston": "^2.0.0"
|
||||
"busboy": "^1",
|
||||
"connect": "^3",
|
||||
"connect-ratelimit": "^0",
|
||||
"connect-route": "^0",
|
||||
"memcached": "^2",
|
||||
"pg": "^8",
|
||||
"redis": "^0",
|
||||
"redis-url": "^0",
|
||||
"st": "^2",
|
||||
"uglify-js": "^3",
|
||||
"winston": "^2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^8.1.3"
|
||||
"mocha": "^10"
|
||||
},
|
||||
"bundledDependencies": [],
|
||||
"bin": {
|
||||
|
||||
192
server.js
192
server.js
@@ -1,162 +1,158 @@
|
||||
var http = require('http');
|
||||
var fs = require('fs');
|
||||
const http = require('http');
|
||||
const fs = require('fs');
|
||||
|
||||
var uglify = require('uglify-js');
|
||||
var winston = require('winston');
|
||||
var connect = require('connect');
|
||||
var route = require('connect-route');
|
||||
var connect_st = require('st');
|
||||
var connect_rate_limit = require('connect-ratelimit');
|
||||
const uglify = require('uglify-js');
|
||||
const winston = require('winston');
|
||||
const connect = require('connect');
|
||||
const route = require('connect-route');
|
||||
const connect_st = require('st');
|
||||
const connect_rate_limit = require('connect-ratelimit');
|
||||
|
||||
var DocumentHandler = require('./lib/document_handler');
|
||||
const DocumentHandler = require('./lib/document_handler');
|
||||
|
||||
// Load the configuration and set some defaults
|
||||
const configPath = process.argv.length <= 2 ? 'config.js' : process.argv[2];
|
||||
const configPath = process.argv.length <= 2 ? 'config.json' : process.argv[2];
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
config.port = process.env.PORT || config.port || 7777;
|
||||
config.host = process.env.HOST || config.host || 'localhost';
|
||||
|
||||
// Set up the logger
|
||||
if (config.logging) {
|
||||
try {
|
||||
winston.remove(winston.transports.Console);
|
||||
} catch(e) {
|
||||
/* was not present */
|
||||
}
|
||||
try {
|
||||
winston.remove(winston.transports.Console);
|
||||
} catch (e) {
|
||||
/* was not present */
|
||||
}
|
||||
|
||||
var detail, type;
|
||||
for (var i = 0; i < config.logging.length; i++) {
|
||||
detail = config.logging[i];
|
||||
type = detail.type;
|
||||
delete detail.type;
|
||||
winston.add(winston.transports[type], detail);
|
||||
}
|
||||
let detail, type;
|
||||
for (let i = 0; i < config.logging.length; i++) {
|
||||
detail = config.logging[i];
|
||||
type = detail.type;
|
||||
delete detail.type;
|
||||
winston.add(winston.transports[type], detail);
|
||||
}
|
||||
}
|
||||
|
||||
// build the store from the config on-demand - so that we don't load it
|
||||
// for statics
|
||||
if (!config.storage) {
|
||||
config.storage = { type: 'file' };
|
||||
config.storage = {type: 'file'};
|
||||
}
|
||||
if (!config.storage.type) {
|
||||
config.storage.type = 'file';
|
||||
config.storage.type = 'file';
|
||||
}
|
||||
|
||||
var Store, preferredStore;
|
||||
let Store, preferredStore;
|
||||
|
||||
if (process.env.REDISTOGO_URL && config.storage.type === 'redis') {
|
||||
var redisClient = require('redis-url').connect(process.env.REDISTOGO_URL);
|
||||
Store = require('./lib/document_stores/redis');
|
||||
preferredStore = new Store(config.storage, redisClient);
|
||||
}
|
||||
else {
|
||||
Store = require('./lib/document_stores/' + config.storage.type);
|
||||
preferredStore = new Store(config.storage);
|
||||
const redisClient = require('redis-url').connect(process.env.REDISTOGO_URL);
|
||||
Store = require('./lib/document_stores/redis');
|
||||
preferredStore = new Store(config.storage, redisClient);
|
||||
} else {
|
||||
Store = require('./lib/document_stores/' + config.storage.type);
|
||||
preferredStore = new Store(config.storage);
|
||||
}
|
||||
|
||||
winston.info('Using storage type: ' + config.storage.type);
|
||||
|
||||
// Compress the static javascript assets
|
||||
if (config.recompressStaticAssets) {
|
||||
var list = fs.readdirSync('./static');
|
||||
for (var j = 0; j < list.length; j++) {
|
||||
var item = list[j];
|
||||
if ((item.indexOf('.js') === item.length - 3) && (item.indexOf('.min.js') === -1)) {
|
||||
var dest = item.substring(0, item.length - 3) + '.min' + item.substring(item.length - 3);
|
||||
var orig_code = fs.readFileSync('./static/' + item, 'utf8');
|
||||
if (false && config.recompressStaticAssets) { // hack, don't do this shit
|
||||
winston.info('Recompressing static assets');
|
||||
const list = fs.readdirSync('./static');
|
||||
list.forEach(item => {
|
||||
if ((item.indexOf('.js') === item.length - 3) && (item.indexOf('.min.js') === -1)) {
|
||||
const dest = item.substring(0, item.length - 3) + '.min' + item.substring(item.length - 3);
|
||||
const orig_code = fs.readFileSync('./static/' + item, 'utf8');
|
||||
|
||||
fs.writeFileSync('./static/' + dest, uglify.minify(orig_code).code, 'utf8');
|
||||
winston.info('compressed ' + item + ' into ' + dest);
|
||||
}
|
||||
}
|
||||
fs.writeFileSync('./static/' + dest, uglify.minify(orig_code).code, 'utf8');
|
||||
winston.info('compressed ' + item + ' into ' + dest);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
winston.warn('Not recompressing static assets');
|
||||
}
|
||||
|
||||
// Send the static documents into the preferred store, skipping expirations
|
||||
var path, data;
|
||||
for (var name in config.documents) {
|
||||
path = config.documents[name];
|
||||
data = fs.readFileSync(path, 'utf8');
|
||||
winston.info('loading static document', { name: name, path: path });
|
||||
if (data) {
|
||||
preferredStore.set(name, data, function(cb) {
|
||||
winston.debug('loaded static document', { success: cb });
|
||||
}, true);
|
||||
}
|
||||
else {
|
||||
winston.warn('failed to load static document', { name: name, path: path });
|
||||
}
|
||||
let path, data;
|
||||
for (let name in config.documents) {
|
||||
path = config.documents[name];
|
||||
data = fs.readFileSync(path, 'utf8');
|
||||
winston.info('loading static document', {name: name, path: path});
|
||||
if (data) {
|
||||
preferredStore.set(name, data, function (cb) {
|
||||
winston.debug('loaded static document', {success: cb});
|
||||
}, true);
|
||||
} else {
|
||||
winston.warn('failed to load static document', {name: name, path: path});
|
||||
}
|
||||
}
|
||||
|
||||
// Pick up a key generator
|
||||
var pwOptions = config.keyGenerator || {};
|
||||
const pwOptions = config.keyGenerator || {};
|
||||
pwOptions.type = pwOptions.type || 'random';
|
||||
var gen = require('./lib/key_generators/' + pwOptions.type);
|
||||
var keyGenerator = new gen(pwOptions);
|
||||
const gen = require('./lib/key_generators/' + pwOptions.type);
|
||||
winston.info('Using key generator: ' + pwOptions.type);
|
||||
const keyGenerator = new gen(pwOptions);
|
||||
|
||||
// Configure the document handler
|
||||
var documentHandler = new DocumentHandler({
|
||||
store: preferredStore,
|
||||
maxLength: config.maxLength,
|
||||
keyLength: config.keyLength,
|
||||
keyGenerator: keyGenerator
|
||||
const documentHandler = new DocumentHandler({
|
||||
store: preferredStore, maxLength: config.maxLength, keyLength: config.keyLength, keyGenerator: keyGenerator
|
||||
});
|
||||
|
||||
var app = connect();
|
||||
const app = connect();
|
||||
|
||||
// Rate limit all requests
|
||||
if (config.rateLimits) {
|
||||
config.rateLimits.end = true;
|
||||
app.use(connect_rate_limit(config.rateLimits));
|
||||
config.rateLimits.end = true;
|
||||
app.use(connect_rate_limit(config.rateLimits));
|
||||
}
|
||||
|
||||
// first look at API calls
|
||||
app.use(route(function(router) {
|
||||
// get raw documents - support getting with extension
|
||||
app.use(route(function (router) {
|
||||
// get raw documents - support getting with extension
|
||||
|
||||
router.get('/raw/:id', function(request, response) {
|
||||
return documentHandler.handleRawGet(request, response, config);
|
||||
});
|
||||
router.get('/raw/:id', function (request, response) {
|
||||
return documentHandler.handleRawGet(request, response, config);
|
||||
});
|
||||
|
||||
router.head('/raw/:id', function(request, response) {
|
||||
return documentHandler.handleRawGet(request, response, config);
|
||||
});
|
||||
router.head('/raw/:id', function (request, response) {
|
||||
return documentHandler.handleRawGet(request, response, config);
|
||||
});
|
||||
|
||||
// add documents
|
||||
// add documents
|
||||
|
||||
router.post('/documents', function(request, response) {
|
||||
return documentHandler.handlePost(request, response);
|
||||
});
|
||||
router.post('/documents', function (request, response) {
|
||||
return documentHandler.handlePost(request, response);
|
||||
});
|
||||
|
||||
// get documents
|
||||
router.get('/documents/:id', function(request, response) {
|
||||
return documentHandler.handleGet(request, response, config);
|
||||
});
|
||||
// get documents
|
||||
router.get('/documents/:id', function (request, response) {
|
||||
return documentHandler.handleGet(request, response, config);
|
||||
});
|
||||
|
||||
router.head('/documents/:id', function(request, response) {
|
||||
return documentHandler.handleGet(request, response, config);
|
||||
});
|
||||
router.head('/documents/:id', function (request, response) {
|
||||
return documentHandler.handleGet(request, response, config);
|
||||
});
|
||||
}));
|
||||
|
||||
// Otherwise, try to match static files
|
||||
app.use(connect_st({
|
||||
path: __dirname + '/static',
|
||||
content: { maxAge: config.staticMaxAge },
|
||||
passthrough: true,
|
||||
index: false
|
||||
path: __dirname + '/static', content: {maxAge: config.staticMaxAge}, passthrough: true, index: false
|
||||
}));
|
||||
|
||||
// Then we can loop back - and everything else should be a token,
|
||||
// so route it back to /
|
||||
app.use(route(function(router) {
|
||||
router.get('/:id', function(request, response, next) {
|
||||
request.sturl = '/';
|
||||
next();
|
||||
});
|
||||
app.use(route(function (router) {
|
||||
router.get('/:id', function (request, response, next) {
|
||||
winston.info("wtf, redirect?");
|
||||
request.sturl = '/';
|
||||
next();
|
||||
});
|
||||
}));
|
||||
|
||||
// And match index
|
||||
app.use(connect_st({
|
||||
path: __dirname + '/static',
|
||||
content: { maxAge: config.staticMaxAge },
|
||||
index: 'index.html'
|
||||
path: __dirname + '/static', content: {maxAge: config.staticMaxAge}, index: 'index.html'
|
||||
}));
|
||||
|
||||
http.createServer(app).listen(config.port, config.host);
|
||||
|
||||
@@ -1,175 +1,219 @@
|
||||
body {
|
||||
background: #002B36;
|
||||
padding: 20px 50px;
|
||||
margin: 0px;
|
||||
background: #0d1117;
|
||||
padding: 20px 50px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* textarea */
|
||||
|
||||
textarea {
|
||||
background: transparent;
|
||||
border: 0px;
|
||||
color: #fff;
|
||||
padding: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: monospace;
|
||||
outline: none;
|
||||
resize: none;
|
||||
font-size: 13px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: #fff;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: monospace;
|
||||
outline: none;
|
||||
resize: none;
|
||||
font-size: 13px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* the line numbers */
|
||||
|
||||
#linenos {
|
||||
color: #7d7d7d;
|
||||
z-index: -1000;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 0px;
|
||||
width: 30px; /* 30 to get 20 away from box */
|
||||
font-size: 13px;
|
||||
font-family: monospace;
|
||||
text-align: right;
|
||||
user-select: none;
|
||||
color: #7d7d7d;
|
||||
z-index: -1000;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 0;
|
||||
width: 30px; /* 30 to get 20 away from box */
|
||||
font-size: 13px;
|
||||
font-family: monospace;
|
||||
text-align: right;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* code box when locked */
|
||||
|
||||
#box {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
width: 100%;
|
||||
border: 0px;
|
||||
outline: none;
|
||||
font-size: 13px;
|
||||
overflow: inherit;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
outline: none;
|
||||
font-size: 13px;
|
||||
overflow: inherit;
|
||||
}
|
||||
|
||||
#box code {
|
||||
padding: 0px;
|
||||
background: transparent !important; /* don't hide hastebox */
|
||||
padding: 0;
|
||||
background: transparent !important; /* don't hide hastebox */
|
||||
}
|
||||
|
||||
/* key */
|
||||
|
||||
#key {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
z-index: +1000; /* watch out */
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: +1000; /* watch out */
|
||||
}
|
||||
|
||||
#box1 {
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
background: #00222b;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
background: #00222b;
|
||||
}
|
||||
|
||||
#box2 {
|
||||
background: #08323c;
|
||||
font-size: 0px;
|
||||
padding: 0px 5px;
|
||||
background: #08323c;
|
||||
font-size: 0;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
#box1 a.logo, #box1 a.logo:visited {
|
||||
display: inline-block;
|
||||
background: url(logo.png);
|
||||
width: 126px;
|
||||
height: 42px;
|
||||
display: inline-block;
|
||||
background: url(logo.png);
|
||||
width: 126px;
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
#box1 a.logo:hover {
|
||||
background-position: 0 bottom;
|
||||
background-position: 0 bottom;
|
||||
}
|
||||
|
||||
#box2 .function {
|
||||
background: url(function-icons.png);
|
||||
width: 32px;
|
||||
height: 37px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
background: url(function-icons.png);
|
||||
width: 32px;
|
||||
height: 37px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#box2 .link embed {
|
||||
vertical-align: bottom; /* fix for zeroClipboard style */
|
||||
vertical-align: bottom; /* fix for zeroClipboard style */
|
||||
}
|
||||
|
||||
#box2 .function.enabled:hover {
|
||||
cursor: hand;
|
||||
cursor: pointer;
|
||||
/* cursor: hand; */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#pointer {
|
||||
display: block;
|
||||
height: 5px;
|
||||
width: 10px;
|
||||
background: url(hover-dropdown-tip.png);
|
||||
bottom: 0px;
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
display: block;
|
||||
height: 5px;
|
||||
width: 10px;
|
||||
background: url(hover-dropdown-tip.png);
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#box3, #messages li {
|
||||
background: #173e48;
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
padding: 10px 15px;
|
||||
user-select: none;
|
||||
background: #173e48;
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
padding: 10px 15px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#box3 .label, #messages li {
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#box3 .shortcut {
|
||||
color: #c4dce3;
|
||||
font-weight: normal;
|
||||
color: #c4dce3;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#box2 .function.save { background-position: -5px top; }
|
||||
#box2 .function.enabled.save { background-position: -5px center; }
|
||||
#box2 .function.enabled.save:hover { background-position: -5px bottom; }
|
||||
#box2 .function.save {
|
||||
background-position: -5px top;
|
||||
}
|
||||
|
||||
#box2 .function.new { background-position: -42px top; }
|
||||
#box2 .function.enabled.new { background-position: -42px center; }
|
||||
#box2 .function.enabled.new:hover { background-position: -42px bottom; }
|
||||
#box2 .function.enabled.save {
|
||||
background-position: -5px center;
|
||||
}
|
||||
|
||||
#box2 .function.duplicate { background-position: -79px top; }
|
||||
#box2 .function.enabled.duplicate { background-position: -79px center; }
|
||||
#box2 .function.enabled.duplicate:hover { background-position: -79px bottom; }
|
||||
#box2 .function.enabled.save:hover {
|
||||
background-position: -5px bottom;
|
||||
}
|
||||
|
||||
#box2 .function.raw { background-position: -116px top; }
|
||||
#box2 .function.enabled.raw { background-position: -116px center; }
|
||||
#box2 .function.enabled.raw:hover { background-position: -116px bottom; }
|
||||
#box2 .function.new {
|
||||
background-position: -42px top;
|
||||
}
|
||||
|
||||
#box2 .function.twitter { background-position: -153px top; }
|
||||
#box2 .function.enabled.twitter { background-position: -153px center; }
|
||||
#box2 .function.enabled.twitter:hover { background-position: -153px bottom; }
|
||||
#box2 .button-picture{ border-width: 0; font-size: inherit; }
|
||||
#box2 .function.enabled.new {
|
||||
background-position: -42px center;
|
||||
}
|
||||
|
||||
#box2 .function.enabled.new:hover {
|
||||
background-position: -42px bottom;
|
||||
}
|
||||
|
||||
#box2 .function.duplicate {
|
||||
background-position: -79px top;
|
||||
}
|
||||
|
||||
#box2 .function.enabled.duplicate {
|
||||
background-position: -79px center;
|
||||
}
|
||||
|
||||
#box2 .function.enabled.duplicate:hover {
|
||||
background-position: -79px bottom;
|
||||
}
|
||||
|
||||
#box2 .function.raw {
|
||||
background-position: -116px top;
|
||||
}
|
||||
|
||||
#box2 .function.enabled.raw {
|
||||
background-position: -116px center;
|
||||
}
|
||||
|
||||
#box2 .function.enabled.raw:hover {
|
||||
background-position: -116px bottom;
|
||||
}
|
||||
|
||||
#box2 .function.twitter {
|
||||
background-position: -153px top;
|
||||
}
|
||||
|
||||
#box2 .function.enabled.twitter {
|
||||
background-position: -153px center;
|
||||
}
|
||||
|
||||
#box2 .function.enabled.twitter:hover {
|
||||
background-position: -153px bottom;
|
||||
}
|
||||
|
||||
#box2 .button-picture {
|
||||
border-width: 0;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
#messages {
|
||||
position:fixed;
|
||||
top:0px;
|
||||
right:138px;
|
||||
margin:0;
|
||||
padding:0;
|
||||
width:400px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 138px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
#messages li {
|
||||
background:rgba(23,62,72,0.8);
|
||||
margin:0 auto;
|
||||
list-style:none;
|
||||
background: rgba(23, 62, 72, 0.8);
|
||||
margin: 0 auto;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#messages li.error {
|
||||
background:rgba(102,8,0,0.8);
|
||||
background: rgba(102, 8, 0, 0.8);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1
static/application.min.js
vendored
1
static/application.min.js
vendored
File diff suppressed because one or more lines are too long
6
static/highlight.min.js
vendored
6
static/highlight.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,68 +1,86 @@
|
||||
<html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<head>
|
||||
|
||||
<title>hastebin</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="solarized_dark.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="application.css"/>
|
||||
<title>hastebin</title>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/github-dark.min.css"
|
||||
integrity="sha512-rO+olRTkcf304DQBxSWxln8JXCzTHlKnIdnMUwYvQa9/Jd4cQaNkItIUj6Z4nvW1dqK0SKXLbn9h4KwZTNtAyw=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"/>
|
||||
<link rel="stylesheet" type="text/css" href="application.css"/>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"
|
||||
integrity="sha512-jGsMH83oKe9asCpkOVkBnUrDDTp8wl+adkB2D+//JtlxO4SrLoJdhbOysIFQJloQFD+C4Fl1rMsQZF76JjV0eQ=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="highlight.min.js"></script>
|
||||
<script type="text/javascript" src="application.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"
|
||||
integrity="sha512-bgHRAiTjGrzHzLyKOnpFvaEpGzJet3z4tZnXGjpsCcqOnAH6VGUx9frc5bcIhKTVLEiCO6vEhNAgx5jtLUYrfA=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<meta name="robots" content="noindex,nofollow"/>
|
||||
<!--<script type="text/javascript" src="application.min.js"></script>-->
|
||||
|
||||
<script type="text/javascript">
|
||||
var app = null;
|
||||
// Handle pops
|
||||
var handlePop = function(evt) {
|
||||
var path = evt.target.location.pathname;
|
||||
if (path === '/') { app.newDocument(true); }
|
||||
else { app.loadDocument(path.substring(1, path.length)); }
|
||||
};
|
||||
// Set up the pop state to handle loads, skipping the first load
|
||||
// to make chrome behave like others:
|
||||
// http://code.google.com/p/chromium/issues/detail?id=63040
|
||||
setTimeout(function() {
|
||||
window.onpopstate = function(evt) {
|
||||
try { handlePop(evt); } catch(err) { /* not loaded yet */ }
|
||||
};
|
||||
}, 1000);
|
||||
// Construct app and load initial path
|
||||
$(function() {
|
||||
app = new haste('hastebin', { twitter: true });
|
||||
handlePop({ target: window });
|
||||
});
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<script type="text/javascript" src="application.js"></script>
|
||||
|
||||
<body>
|
||||
<ul id="messages"></ul>
|
||||
<meta name="robots" content="noindex,nofollow"/>
|
||||
|
||||
<div id="key">
|
||||
<div id="pointer" style="display:none;"></div>
|
||||
<div id="box1">
|
||||
<a href="/about.md" class="logo"></a>
|
||||
</div>
|
||||
<div id="box2">
|
||||
<button class="save function button-picture">Save</button>
|
||||
<button class="new function button-picture">New</button>
|
||||
<button class="duplicate function button-picture">Duplicate & Edit</button>
|
||||
<button class="raw function button-picture">Just Text</button>
|
||||
<button class="twitter function button-picture">Twitter</button>
|
||||
</div>
|
||||
<div id="box3" style="display:none;">
|
||||
<div class="label"></div>
|
||||
<div class="shortcut"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
var app = null;
|
||||
// Handle pops
|
||||
var handlePop = function (evt) {
|
||||
var path = evt.target.location.pathname;
|
||||
if (path === '/') {
|
||||
app.newDocument(true);
|
||||
} else {
|
||||
app.loadDocument(path.substring(1, path.length));
|
||||
}
|
||||
};
|
||||
// Set up the pop state to handle loads, skipping the first load
|
||||
// to make chrome behave like others:
|
||||
// http://code.google.com/p/chromium/issues/detail?id=63040
|
||||
setTimeout(function () {
|
||||
window.onpopstate = function (evt) {
|
||||
try {
|
||||
handlePop(evt);
|
||||
} catch (err) { /* not loaded yet */
|
||||
}
|
||||
};
|
||||
}, 1000);
|
||||
// Construct app and load initial path
|
||||
$(function () {
|
||||
app = new haste('hastebin', {twitter: false});
|
||||
handlePop({target: window});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="linenos"></div>
|
||||
<pre id="box" style="display:none;" class="hljs" tabindex="0"><code></code></pre>
|
||||
<textarea spellcheck="false" style="display:none;"></textarea>
|
||||
</head>
|
||||
|
||||
</body>
|
||||
<body>
|
||||
<ul id="messages"></ul>
|
||||
|
||||
<div id="key">
|
||||
<div id="pointer" style="display:none;"></div>
|
||||
<div id="box1">
|
||||
<!--suppress HtmlUnknownTarget -->
|
||||
<a href="/about.md" class="logo"></a>
|
||||
</div>
|
||||
<div id="box2">
|
||||
<button class="save function button-picture">Save</button>
|
||||
<button class="new function button-picture">New</button>
|
||||
<button class="duplicate function button-picture">Duplicate & Edit</button>
|
||||
<button class="raw function button-picture">Just Text</button>
|
||||
<button class="twitter function button-picture">Twitter</button>
|
||||
</div>
|
||||
<div id="box3" style="display:none;">
|
||||
<div class="label"></div>
|
||||
<div class="shortcut"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="linenos"></div>
|
||||
<pre id="box" style="display:none;" class="hljs" tabindex="0"><code></code></pre>
|
||||
<!--suppress HtmlFormInputWithoutLabel -->
|
||||
<textarea spellcheck="false" style="display:none;"></textarea>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
|
||||
Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull <sourdrums@gmail.com>
|
||||
|
||||
*/
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
background: #002b36;
|
||||
color: #839496;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: #586e75;
|
||||
}
|
||||
|
||||
/* Solarized Green */
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-addition {
|
||||
color: #859900;
|
||||
}
|
||||
|
||||
/* Solarized Cyan */
|
||||
.hljs-number,
|
||||
.hljs-string,
|
||||
.hljs-meta .hljs-meta-string,
|
||||
.hljs-literal,
|
||||
.hljs-doctag,
|
||||
.hljs-regexp {
|
||||
color: #2aa198;
|
||||
}
|
||||
|
||||
/* Solarized Blue */
|
||||
.hljs-title,
|
||||
.hljs-section,
|
||||
.hljs-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class {
|
||||
color: #268bd2;
|
||||
}
|
||||
|
||||
/* Solarized Yellow */
|
||||
.hljs-attribute,
|
||||
.hljs-attr,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-class .hljs-title,
|
||||
.hljs-type {
|
||||
color: #b58900;
|
||||
}
|
||||
|
||||
/* Solarized Orange */
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-subst,
|
||||
.hljs-meta,
|
||||
.hljs-meta .hljs-keyword,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-link {
|
||||
color: #cb4b16;
|
||||
}
|
||||
|
||||
/* Solarized Red */
|
||||
.hljs-built_in,
|
||||
.hljs-deletion {
|
||||
color: #dc322f;
|
||||
}
|
||||
|
||||
.hljs-formula {
|
||||
background: #073642;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
Reference in New Issue
Block a user