Compare commits
No commits in common. 'master' and 'gh-pages' have entirely different histories.
50 changed files with 385 additions and 4335 deletions
@ -1,8 +0,0 @@ |
|||||||
Dockerfile |
|
||||||
.git |
|
||||||
npm-debug.log |
|
||||||
node_modules |
|
||||||
*.swp |
|
||||||
*.swo |
|
||||||
data |
|
||||||
*.DS_Store |
|
@ -1,2 +0,0 @@ |
|||||||
**/*.min.js |
|
||||||
config.js |
|
@ -1,25 +0,0 @@ |
|||||||
{ |
|
||||||
"env": { |
|
||||||
"es6": true, |
|
||||||
"node": true |
|
||||||
}, |
|
||||||
"extends": "eslint:recommended", |
|
||||||
"rules": { |
|
||||||
"indent": [ |
|
||||||
"error", |
|
||||||
2 |
|
||||||
], |
|
||||||
"linebreak-style": [ |
|
||||||
"error", |
|
||||||
"unix" |
|
||||||
], |
|
||||||
"quotes": [ |
|
||||||
"error", |
|
||||||
"single" |
|
||||||
], |
|
||||||
"semi": [ |
|
||||||
"error", |
|
||||||
"always" |
|
||||||
] |
|
||||||
} |
|
||||||
} |
|
@ -1 +0,0 @@ |
|||||||
* @toptal/site-acquisition-eng |
|
@ -1,30 +0,0 @@ |
|||||||
name: Close inactive issues and PRs |
|
||||||
on: |
|
||||||
workflow_dispatch: |
|
||||||
schedule: |
|
||||||
- cron: "30 1 * * *" |
|
||||||
|
|
||||||
jobs: |
|
||||||
close-stale: |
|
||||||
runs-on: ubuntu-latest |
|
||||||
permissions: |
|
||||||
issues: write |
|
||||||
pull-requests: write |
|
||||||
steps: |
|
||||||
- uses: actions/stale@v3 |
|
||||||
with: |
|
||||||
days-before-stale: 30 |
|
||||||
days-before-close: 14 |
|
||||||
stale-issue-label: "stale" |
|
||||||
stale-pr-label: "stale" |
|
||||||
|
|
||||||
exempt-issue-labels: backlog,triage,nostale |
|
||||||
exempt-pr-labels: backlog,triage,nostale |
|
||||||
|
|
||||||
stale-pr-message: "This PR is stale because it has been open for 30 days with no activity." |
|
||||||
close-pr-message: "This PR was closed because it has been inactive for 14 days since being marked as stale." |
|
||||||
|
|
||||||
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." |
|
||||||
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." |
|
||||||
|
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }} |
|
@ -1,7 +0,0 @@ |
|||||||
npm-debug.log |
|
||||||
node_modules |
|
||||||
*.swp |
|
||||||
*.swo |
|
||||||
data |
|
||||||
*.DS_Store |
|
||||||
docker-compose.override.yml |
|
@ -1,68 +0,0 @@ |
|||||||
FROM node:14.8.0-stretch |
|
||||||
|
|
||||||
RUN mkdir -p /usr/src/app && \ |
|
||||||
chown node:node /usr/src/app |
|
||||||
|
|
||||||
USER node:node |
|
||||||
|
|
||||||
WORKDIR /usr/src/app |
|
||||||
|
|
||||||
COPY --chown=node:node . . |
|
||||||
|
|
||||||
RUN npm install && \ |
|
||||||
npm install redis@0.8.1 && \ |
|
||||||
npm install pg@4.1.1 && \ |
|
||||||
npm install memcached@2.2.2 && \ |
|
||||||
npm install aws-sdk@2.738.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= |
|
||||||
|
|
||||||
ENV LOGGING_LEVEL=verbose \ |
|
||||||
LOGGING_TYPE=Console \ |
|
||||||
LOGGING_COLORIZE=true |
|
||||||
|
|
||||||
ENV HOST=0.0.0.0\ |
|
||||||
PORT=7777\ |
|
||||||
KEY_LENGTH=10\ |
|
||||||
MAX_LENGTH=400000\ |
|
||||||
STATIC_MAX_AGE=86400\ |
|
||||||
RECOMPRESS_STATIC_ASSETS=true |
|
||||||
|
|
||||||
ENV KEYGENERATOR_TYPE=phonetic \ |
|
||||||
KEYGENERATOR_KEYSPACE= |
|
||||||
|
|
||||||
ENV RATELIMITS_NORMAL_TOTAL_REQUESTS=500\ |
|
||||||
RATELIMITS_NORMAL_EVERY_MILLISECONDS=60000 \ |
|
||||||
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= \ |
|
||||||
# comma separated list for the blacklisted \ |
|
||||||
RATELIMITS_BLACKLIST=example1.blacklist,example2.blacklist |
|
||||||
ENV DOCUMENTS=about=./about.md |
|
||||||
|
|
||||||
EXPOSE ${PORT} |
|
||||||
STOPSIGNAL SIGINT |
|
||||||
ENTRYPOINT [ "bash", "docker-entrypoint.sh" ] |
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s \ |
|
||||||
--retries=3 CMD [ "sh", "-c", "echo -n 'curl localhost:7777... '; \ |
|
||||||
(\ |
|
||||||
curl -sf localhost:7777 > /dev/null\ |
|
||||||
) && echo OK || (\ |
|
||||||
echo Fail && exit 2\ |
|
||||||
)"] |
|
||||||
CMD ["npm", "start"] |
|
@ -1,389 +0,0 @@ |
|||||||
# Haste |
|
||||||
|
|
||||||
haste is an open-source pastebin software written in node.js, which is easily |
|
||||||
installable in any network. It can be backed by either redis or filesystem, |
|
||||||
and has a very easy adapter interface for other stores. A publicly available |
|
||||||
version can be found at [hastebin.com](http://hastebin.com) |
|
||||||
|
|
||||||
Major design objectives: |
|
||||||
|
|
||||||
* Be really pretty |
|
||||||
* Be really simple |
|
||||||
* Be easy to set up and use |
|
||||||
|
|
||||||
Haste works really well with a little utility called |
|
||||||
[haste-client](https://github.com/seejohnrun/haste-client), allowing you |
|
||||||
to do things like: |
|
||||||
|
|
||||||
`cat something | haste` |
|
||||||
|
|
||||||
which will output a URL to share containing the contents of `cat something`'s |
|
||||||
STDOUT. Check the README there for more details and usages. |
|
||||||
|
|
||||||
## Tested Browsers |
|
||||||
|
|
||||||
* Firefox 8 |
|
||||||
* Chrome 17 |
|
||||||
* Safari 5.3 |
|
||||||
|
|
||||||
## Installation |
|
||||||
|
|
||||||
1. Download the package, and expand it |
|
||||||
2. Explore the settings inside of config.js, but the defaults should be good |
|
||||||
3. `npm install` |
|
||||||
4. `npm start` (you may specify an optional `<config-path>` as well) |
|
||||||
|
|
||||||
## Settings |
|
||||||
|
|
||||||
* `host` - the host the server runs on (default localhost) |
|
||||||
* `port` - the port the server runs on (default 7777) |
|
||||||
* `keyLength` - the length of the keys to user (default 10) |
|
||||||
* `maxLength` - maximum length of a paste (default 400000) |
|
||||||
* `staticMaxAge` - max age for static assets (86400) |
|
||||||
* `recompressStaticAssets` - whether or not to compile static js assets (true) |
|
||||||
* `documents` - static documents to serve (ex: http://hastebin.com/about.com) |
|
||||||
in addition to static assets. These will never expire. |
|
||||||
* `storage` - storage options (see below) |
|
||||||
* `logging` - logging preferences |
|
||||||
* `keyGenerator` - key generator options (see below) |
|
||||||
* `rateLimits` - settings for rate limiting (see below) |
|
||||||
|
|
||||||
## Rate Limiting |
|
||||||
|
|
||||||
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`. |
|
||||||
|
|
||||||
See the README for [connect-ratelimit](https://github.com/dharmafly/connect-ratelimit) |
|
||||||
for more information! |
|
||||||
|
|
||||||
## Key Generation |
|
||||||
|
|
||||||
### Phonetic |
|
||||||
|
|
||||||
Attempts to generate phonetic keys, similar to `pwgen` |
|
||||||
|
|
||||||
``` json |
|
||||||
{ |
|
||||||
"type": "phonetic" |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
### Random |
|
||||||
|
|
||||||
Generates a random key |
|
||||||
|
|
||||||
``` json |
|
||||||
{ |
|
||||||
"type": "random", |
|
||||||
"keyspace": "abcdef" |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
The _optional_ keySpace argument is a string of acceptable characters |
|
||||||
for the key. |
|
||||||
|
|
||||||
## Storage |
|
||||||
|
|
||||||
### File |
|
||||||
|
|
||||||
To use file storage (the default) change the storage section in `config.js` to |
|
||||||
something like: |
|
||||||
|
|
||||||
``` json |
|
||||||
{ |
|
||||||
"path": "./data", |
|
||||||
"type": "file" |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
where `path` represents where you want the files stored. |
|
||||||
|
|
||||||
File storage currently does not support paste expiration, you can follow [#191](https://github.com/seejohnrun/haste-server/issues/191) for status updates. |
|
||||||
|
|
||||||
### Redis |
|
||||||
|
|
||||||
To use redis storage you must install the `redis` package in npm, and have |
|
||||||
`redis-server` running on the machine. |
|
||||||
|
|
||||||
`npm install redis` |
|
||||||
|
|
||||||
Once you've done that, your config section should look like: |
|
||||||
|
|
||||||
``` json |
|
||||||
{ |
|
||||||
"type": "redis", |
|
||||||
"host": "localhost", |
|
||||||
"port": 6379, |
|
||||||
"db": 2 |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
You can also set an `expire` option to the number of seconds to expire keys in. |
|
||||||
This is off by default, but will constantly kick back expirations on each view |
|
||||||
or post. |
|
||||||
|
|
||||||
All of which are optional except `type` with very logical default values. |
|
||||||
|
|
||||||
If your Redis server is configured for password authentification, use the `password` field. |
|
||||||
|
|
||||||
### Postgres |
|
||||||
|
|
||||||
To use postgres storage you must install the `pg` package in npm |
|
||||||
|
|
||||||
`npm install pg` |
|
||||||
|
|
||||||
Once you've done that, your config section should look like: |
|
||||||
|
|
||||||
``` json |
|
||||||
{ |
|
||||||
"type": "postgres", |
|
||||||
"connectionUrl": "postgres://user:password@host:5432/database" |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
You can also just set the environment variable for `DATABASE_URL` to your database connection url. |
|
||||||
|
|
||||||
You will have to manually add a table to your postgres database: |
|
||||||
|
|
||||||
`create table entries (id serial primary key, key varchar(255) not null, value text not null, expiration int, unique(key));` |
|
||||||
|
|
||||||
You can also set an `expire` option to the number of seconds to expire keys in. |
|
||||||
This is off by default, but will constantly kick back expirations on each view |
|
||||||
or post. |
|
||||||
|
|
||||||
All of which are optional except `type` with very logical default values. |
|
||||||
|
|
||||||
### MongoDB |
|
||||||
|
|
||||||
To use mongodb storage you must install the 'mongodb' package in npm |
|
||||||
|
|
||||||
`npm install mongodb` |
|
||||||
|
|
||||||
Once you've done that, your config section should look like: |
|
||||||
|
|
||||||
``` json |
|
||||||
{ |
|
||||||
"type": "mongo", |
|
||||||
"connectionUrl": "mongodb://localhost:27017/database" |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
You can also just set the environment variable for `DATABASE_URL` to your database connection url. |
|
||||||
|
|
||||||
Unlike with postgres you do NOT have to create the table in your mongo database prior to running. |
|
||||||
|
|
||||||
You can also set an `expire` option to the number of seconds to expire keys in. |
|
||||||
This is off by default, but will constantly kick back expirations on each view or post. |
|
||||||
|
|
||||||
### Memcached |
|
||||||
|
|
||||||
To use memcache storage you must install the `memcached` package via npm |
|
||||||
|
|
||||||
`npm install memcached` |
|
||||||
|
|
||||||
Once you've done that, your config section should look like: |
|
||||||
|
|
||||||
``` json |
|
||||||
{ |
|
||||||
"type": "memcached", |
|
||||||
"host": "127.0.0.1", |
|
||||||
"port": 11211 |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
You can also set an `expire` option to the number of seconds to expire keys in. |
|
||||||
This behaves just like the redis expirations, but does not push expirations |
|
||||||
forward on GETs. |
|
||||||
|
|
||||||
All of which are optional except `type` with very logical default values. |
|
||||||
|
|
||||||
### RethinkDB |
|
||||||
|
|
||||||
To use the RethinkDB storage system, you must install the `rethinkdbdash` package via npm |
|
||||||
|
|
||||||
`npm install rethinkdbdash` |
|
||||||
|
|
||||||
Once you've done that, your config section should look like this: |
|
||||||
|
|
||||||
``` json |
|
||||||
{ |
|
||||||
"type": "rethinkdb", |
|
||||||
"host": "127.0.0.1", |
|
||||||
"port": 28015, |
|
||||||
"db": "haste" |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
In order for this to work, the database must be pre-created before the script is ran. |
|
||||||
Also, you must create an `uploads` table, which will store all the data for uploads. |
|
||||||
|
|
||||||
You can optionally add the `user` and `password` properties to use a user system. |
|
||||||
|
|
||||||
### Google Datastore |
|
||||||
|
|
||||||
To use the Google Datastore storage system, you must install the `@google-cloud/datastore` package via npm |
|
||||||
|
|
||||||
`npm install @google-cloud/datastore` |
|
||||||
|
|
||||||
Once you've done that, your config section should look like this: |
|
||||||
|
|
||||||
``` json |
|
||||||
{ |
|
||||||
"type": "google-datastore" |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
Authentication is handled automatically by [Google Cloud service account credentials](https://cloud.google.com/docs/authentication/getting-started), by providing authentication details to the GOOGLE_APPLICATION_CREDENTIALS environmental variable. |
|
||||||
|
|
||||||
### Amazon S3 |
|
||||||
|
|
||||||
To use [Amazon S3](https://aws.amazon.com/s3/) as a storage system, you must |
|
||||||
install the `aws-sdk` package via npm: |
|
||||||
|
|
||||||
`npm install aws-sdk` |
|
||||||
|
|
||||||
Once you've done that, your config section should look like this: |
|
||||||
|
|
||||||
```json |
|
||||||
{ |
|
||||||
"type": "amazon-s3", |
|
||||||
"bucket": "your-bucket-name", |
|
||||||
"region": "us-east-1" |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
Authentication is handled automatically by the client. Check |
|
||||||
[Amazon's documentation](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html) |
|
||||||
for more information. You will need to grant your role these permissions to |
|
||||||
your bucket: |
|
||||||
|
|
||||||
```json |
|
||||||
{ |
|
||||||
"Version": "2012-10-17", |
|
||||||
"Statement": [ |
|
||||||
{ |
|
||||||
"Action": [ |
|
||||||
"s3:GetObject", |
|
||||||
"s3:PutObject" |
|
||||||
], |
|
||||||
"Effect": "Allow", |
|
||||||
"Resource": "arn:aws:s3:::your-bucket-name-goes-here/*" |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
## Docker |
|
||||||
|
|
||||||
### Build image |
|
||||||
|
|
||||||
```bash |
|
||||||
docker build --tag haste-server . |
|
||||||
``` |
|
||||||
|
|
||||||
### Run container |
|
||||||
|
|
||||||
For this example we will run haste-server, and connect it to a redis server |
|
||||||
|
|
||||||
```bash |
|
||||||
docker run --name haste-server-container --env STORAGE_TYPE=redis --env STORAGE_HOST=redis-server --env STORAGE_PORT=6379 haste-server |
|
||||||
``` |
|
||||||
|
|
||||||
### Use docker-compose example |
|
||||||
|
|
||||||
There is an example `docker-compose.yml` which runs haste-server together with memcached |
|
||||||
|
|
||||||
```bash |
|
||||||
docker-compose up |
|
||||||
``` |
|
||||||
|
|
||||||
### Configuration |
|
||||||
|
|
||||||
The docker image is configured using environmental variables as you can see in the example above. |
|
||||||
|
|
||||||
Here is a list of all the environment variables |
|
||||||
|
|
||||||
### Storage |
|
||||||
|
|
||||||
| Name | Default value | Description | |
|
||||||
| :--------------------: | :-----------: | :-----------------------------------------------------------------------------------------------------------: | |
|
||||||
| STORAGE_TYPE | memcached | Type of storage . Accepted values: "memcached","redis","postgres","rethinkdb", "amazon-s3", and "file" | |
|
||||||
| STORAGE_HOST | 127.0.0.1 | Storage host. Applicable for types: memcached, redis, postgres, and rethinkdb | |
|
||||||
| STORAGE_PORT | 11211 | Port on the storage host. Applicable for types: memcached, redis, postgres, and rethinkdb | |
|
||||||
| STORAGE_EXPIRE_SECONDS | 2592000 | Number of seconds to expire keys in. Applicable for types. Redis, postgres, memcached. `expire` option to the | |
|
||||||
| STORAGE_DB | 2 | The name of the database. Applicable for redis, postgres, and rethinkdb | |
|
||||||
| STORAGE_PASSWORD | | Password for database. Applicable for redis, postges, rethinkdb . | |
|
||||||
| STORAGE_USERNAME | | Database username. Applicable for postgres, and rethinkdb | |
|
||||||
| STORAGE_AWS_BUCKET | | Applicable for amazon-s3. This is the name of the S3 bucket | |
|
||||||
| STORAGE_AWS_REGION | | Applicable for amazon-s3. The region in which the bucket is located | |
|
||||||
| STORAGE_FILEPATH | | Path to file to save data to. Applicable for type file | |
|
||||||
|
|
||||||
### Logging |
|
||||||
|
|
||||||
| Name | Default value | Description | |
|
||||||
| :---------------: | :-----------: | :---------: | |
|
||||||
| LOGGING_LEVEL | verbose | | |
|
||||||
| LOGGING_TYPE= | Console | |
|
||||||
| LOGGING_COLORIZE= | true | |
|
||||||
|
|
||||||
### Basics |
|
||||||
|
|
||||||
| Name | Default value | Description | |
|
||||||
| :----------------------: | :--------------: | :---------------------------------------------------------------------------------------: | |
|
||||||
| HOST | 0.0.0.0 | The hostname which the server answers on | |
|
||||||
| PORT | 7777 | The port on which the server is running | |
|
||||||
| KEY_LENGTH | 10 | the length of the keys to user | |
|
||||||
| MAX_LENGTH | 400000 | maximum length of a paste | |
|
||||||
| STATIC_MAX_AGE | 86400 | max age for static assets | |
|
||||||
| RECOMPRESS_STATIC_ASSETS | true | whether or not to compile static js assets | |
|
||||||
| KEYGENERATOR_TYPE | phonetic | Type of key generator. Acceptable values: "phonetic", or "random" | |
|
||||||
| KEYGENERATOR_KEYSPACE | | keySpace argument is a string of acceptable characters | |
|
||||||
| DOCUMENTS | about=./about.md | Comma separated list of static documents to serve. ex: \n about=./about.md,home=./home.md | |
|
||||||
|
|
||||||
### Rate limits |
|
||||||
|
|
||||||
| Name | Default value | Description | |
|
||||||
| :----------------------------------: | :-----------------------------------: | :--------------------------------------------------------------------------------------: | |
|
||||||
| RATELIMITS_NORMAL_TOTAL_REQUESTS | 500 | By default anyone uncategorized will be subject to 500 requests in the defined timespan. | |
|
||||||
| RATELIMITS_NORMAL_EVERY_MILLISECONDS | 60000 | The timespan to allow the total requests for uncategorized users | |
|
||||||
| RATELIMITS_WHITELIST_TOTAL_REQUESTS | | By default client names in the whitelist will not have their requests limited. | |
|
||||||
| RATELIMITS_WHITELIST_EVERY_SECONDS | | By default client names in the whitelist will not have their requests limited. | |
|
||||||
| RATELIMITS_WHITELIST | example1.whitelist,example2.whitelist | Comma separated list of the clients which are in the whitelist pool | |
|
||||||
| RATELIMITS_BLACKLIST_TOTAL_REQUESTS | | By default client names in the blacklist will be subject to 0 requests per hours. | |
|
||||||
| RATELIMITS_BLACKLIST_EVERY_SECONDS | | By default client names in the blacklist will be subject to 0 requests per hours | |
|
||||||
| RATELIMITS_BLACKLIST | example1.blacklist,example2.blacklist | Comma separated list of the clients which are in the blacklistpool. | |
|
||||||
|
|
||||||
## Author |
|
||||||
|
|
||||||
John Crepezzi <john.crepezzi@gmail.com> |
|
||||||
|
|
||||||
## License |
|
||||||
|
|
||||||
(The MIT License) |
|
||||||
|
|
||||||
Copyright © 2011-2012 John Crepezzi |
|
||||||
|
|
||||||
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 |
|
||||||
|
|
||||||
### Other components: |
|
||||||
|
|
||||||
* jQuery: MIT/GPL license |
|
||||||
* highlight.js: Copyright © 2006, Ivan Sagalaev |
|
||||||
* highlightjs-coffeescript: WTFPL - Copyright © 2011, Dmytrii Nagirniak |
|
@ -1,61 +0,0 @@ |
|||||||
# Haste |
|
||||||
|
|
||||||
Sharing code is a good thing, and it should be _really_ easy to do it. |
|
||||||
A lot of times, I want to show you something I'm seeing - and that's where we |
|
||||||
use pastebins. |
|
||||||
|
|
||||||
Haste is the prettiest, easiest to use pastebin ever made. |
|
||||||
|
|
||||||
## Basic Usage |
|
||||||
|
|
||||||
Type what you want me to see, click "Save", and then copy the URL. Send that |
|
||||||
URL to someone and they'll see what you see. |
|
||||||
|
|
||||||
To make a new entry, click "New" (or type 'control + n') |
|
||||||
|
|
||||||
## From the Console |
|
||||||
|
|
||||||
Most of the time I want to show you some text, it's coming from my current |
|
||||||
console session. We should make it really easy to take code from the console |
|
||||||
and send it to people. |
|
||||||
|
|
||||||
`cat something | haste` # https://hastebin.com/1238193 |
|
||||||
|
|
||||||
You can even take this a step further, and cut out the last step of copying the |
|
||||||
URL with: |
|
||||||
|
|
||||||
* osx: `cat something | haste | pbcopy` |
|
||||||
* linux: `cat something | haste | xsel -b` |
|
||||||
* windows: check out [WinHaste](https://github.com/ajryan/WinHaste) |
|
||||||
|
|
||||||
After running that, the STDOUT output of `cat something` will show up at a URL |
|
||||||
which has been conveniently copied to your clipboard. |
|
||||||
|
|
||||||
That's all there is to that, and you can install it with `gem install haste` |
|
||||||
right now. |
|
||||||
* osx: you will need to have an up to date version of Xcode |
|
||||||
* linux: you will need to have rubygems and ruby-devel installed |
|
||||||
|
|
||||||
## Duration |
|
||||||
|
|
||||||
Pastes will stay for 30 days from their last view. They may be removed earlier |
|
||||||
and without notice. |
|
||||||
|
|
||||||
## Privacy |
|
||||||
|
|
||||||
While the contents of hastebin.com are not directly crawled by any search robot |
|
||||||
that obeys "robots.txt", there should be no great expectation of privacy. Post |
|
||||||
things at your own risk. Not responsible for any loss of data or removed |
|
||||||
pastes. |
|
||||||
|
|
||||||
## Open Source |
|
||||||
|
|
||||||
Haste can easily be installed behind your network, and it's all open source! |
|
||||||
|
|
||||||
* [haste-client](https://github.com/seejohnrun/haste-client) |
|
||||||
* [haste-server](https://github.com/seejohnrun/haste-server) |
|
||||||
|
|
||||||
## Author |
|
||||||
|
|
||||||
Code by John Crepezzi <john.crepezzi@gmail.com> |
|
||||||
Key Design by Brian Dawson <bridawson@gmail.com> |
|
@ -1,43 +0,0 @@ |
|||||||
{ |
|
||||||
|
|
||||||
"host": "0.0.0.0", |
|
||||||
"port": 7777, |
|
||||||
|
|
||||||
"keyLength": 10, |
|
||||||
|
|
||||||
"maxLength": 400000, |
|
||||||
|
|
||||||
"staticMaxAge": 86400, |
|
||||||
|
|
||||||
"recompressStaticAssets": true, |
|
||||||
|
|
||||||
"logging": [ |
|
||||||
{ |
|
||||||
"level": "verbose", |
|
||||||
"type": "Console", |
|
||||||
"colorize": true |
|
||||||
} |
|
||||||
], |
|
||||||
|
|
||||||
"keyGenerator": { |
|
||||||
"type": "phonetic" |
|
||||||
}, |
|
||||||
|
|
||||||
"rateLimits": { |
|
||||||
"categories": { |
|
||||||
"normal": { |
|
||||||
"totalRequests": 500, |
|
||||||
"every": 60000 |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
"storage": { |
|
||||||
"type": "file" |
|
||||||
}, |
|
||||||
|
|
||||||
"documents": { |
|
||||||
"about": "./about.md" |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,12 +0,0 @@ |
|||||||
version: '3.0' |
|
||||||
services: |
|
||||||
haste-server: |
|
||||||
build: . |
|
||||||
environment: |
|
||||||
- STORAGE_TYPE=memcached |
|
||||||
- STORAGE_HOST=memcached |
|
||||||
- STORAGE_PORT=11211 |
|
||||||
ports: |
|
||||||
- 7777:7777 |
|
||||||
memcached: |
|
||||||
image: memcached:latest |
|
@ -1,108 +0,0 @@ |
|||||||
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, |
|
||||||
} = process.env; |
|
||||||
|
|
||||||
const config = { |
|
||||||
host: HOST, |
|
||||||
port: Number(PORT), |
|
||||||
|
|
||||||
keyLength: Number(KEY_LENGTH), |
|
||||||
|
|
||||||
maxLength: Number(MAX_LENGTH), |
|
||||||
|
|
||||||
staticMaxAge: Number(STATIC_MAX_AGE), |
|
||||||
|
|
||||||
recompressStaticAssets: RECOMPRESS_STATIC_ASSETS, |
|
||||||
|
|
||||||
logging: [ |
|
||||||
{ |
|
||||||
level: LOGGING_LEVEL, |
|
||||||
type: LOGGING_TYPE, |
|
||||||
colorize: LOGGING_COLORIZE, |
|
||||||
}, |
|
||||||
], |
|
||||||
|
|
||||||
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, |
|
||||||
}, |
|
||||||
}, |
|
||||||
|
|
||||||
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, |
|
||||||
}; |
|
||||||
|
|
||||||
console.log(JSON.stringify(config)); |
|
@ -1,9 +0,0 @@ |
|||||||
#!/bin/bash |
|
||||||
|
|
||||||
# We use this file to translate environmental variables to .env files used by the application |
|
||||||
|
|
||||||
set -e |
|
||||||
|
|
||||||
node ./docker-entrypoint.js > ./config.js |
|
||||||
|
|
||||||
exec "$@" |
|
@ -0,0 +1,43 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<meta charset="utf-8"> |
||||||
|
<meta http-equiv="X-UA-Compatible" content="chrome=1"> |
||||||
|
<title>Haste-server by seejohnrun</title> |
||||||
|
|
||||||
|
<link rel="stylesheet" href="stylesheets/styles.css"> |
||||||
|
<link rel="stylesheet" href="stylesheets/pygment_trac.css"> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> |
||||||
|
<!--[if lt IE 9]> |
||||||
|
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> |
||||||
|
<![endif]--> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div class="wrapper"> |
||||||
|
<header> |
||||||
|
<h1>Haste-server</h1> |
||||||
|
<p>open source pastebin written in node.js</p> |
||||||
|
<p class="view"><a href="https://github.com/seejohnrun/haste-server">View the Project on GitHub <small>seejohnrun/haste-server</small></a></p> |
||||||
|
<ul> |
||||||
|
<li><a href="https://github.com/seejohnrun/haste-server/zipball/master">Download <strong>ZIP File</strong></a></li> |
||||||
|
<li><a href="https://github.com/seejohnrun/haste-server/tarball/master">Download <strong>TAR Ball</strong></a></li> |
||||||
|
<li><a href="https://github.com/seejohnrun/haste-server">Fork On <strong>GitHub</strong></a></li> |
||||||
|
</ul> |
||||||
|
</header> |
||||||
|
<section> |
||||||
|
<h1>Pastebin</h1> |
||||||
|
|
||||||
|
<p>haste-server is an open source pastebin. You can see an example at <a href="http://hastebin.com">hastebin.com</a>, and it's easy to install locally.</p> |
||||||
|
|
||||||
|
<h2>GitHub</h2> |
||||||
|
|
||||||
|
<p>Check out the project on <a href="https://github.com/seejohnrun/haste-server">github</a></p> |
||||||
|
</section> |
||||||
|
<footer> |
||||||
|
<p>This project is maintained by <a href="https://github.com/seejohnrun">seejohnrun</a></p> |
||||||
|
<p><small>Hosted on GitHub Pages — Theme by <a href="https://github.com/orderedlist">orderedlist</a></small></p> |
||||||
|
</footer> |
||||||
|
</div> |
||||||
|
<script src="javascripts/scale.fix.js"></script> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,17 @@ |
|||||||
|
var metas = document.getElementsByTagName('meta'); |
||||||
|
var i; |
||||||
|
if (navigator.userAgent.match(/iPhone/i)) { |
||||||
|
for (i=0; i<metas.length; i++) { |
||||||
|
if (metas[i].name == "viewport") { |
||||||
|
metas[i].content = "width=device-width, minimum-scale=1.0, maximum-scale=1.0"; |
||||||
|
} |
||||||
|
} |
||||||
|
document.addEventListener("gesturestart", gestureStart, false); |
||||||
|
} |
||||||
|
function gestureStart() { |
||||||
|
for (i=0; i<metas.length; i++) { |
||||||
|
if (metas[i].name == "viewport") { |
||||||
|
metas[i].content = "width=device-width, minimum-scale=0.25, maximum-scale=1.6"; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,155 +0,0 @@ |
|||||||
var winston = require('winston'); |
|
||||||
var 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; |
|
||||||
}; |
|
||||||
|
|
||||||
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]; |
|
||||||
|
|
||||||
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]; |
|
||||||
|
|
||||||
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; |
|
||||||
|
|
||||||
// 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; |
|
||||||
} |
|
||||||
// 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.acceptableKey = function() { |
|
||||||
return this.keyGenerator.createKey(this.keyLength); |
|
||||||
}; |
|
||||||
|
|
||||||
module.exports = DocumentHandler; |
|
@ -1,56 +0,0 @@ |
|||||||
/*global require,module,process*/ |
|
||||||
|
|
||||||
var AWS = require('aws-sdk'); |
|
||||||
var winston = require('winston'); |
|
||||||
|
|
||||||
var AmazonS3DocumentStore = function(options) { |
|
||||||
this.expire = options.expire; |
|
||||||
this.bucket = options.bucket; |
|
||||||
this.client = new AWS.S3({region: options.region}); |
|
||||||
}; |
|
||||||
|
|
||||||
AmazonS3DocumentStore.prototype.get = function(key, callback, skipExpire) { |
|
||||||
var _this = this; |
|
||||||
|
|
||||||
var req = { |
|
||||||
Bucket: _this.bucket, |
|
||||||
Key: key |
|
||||||
}; |
|
||||||
|
|
||||||
_this.client.getObject(req, function(err, data) { |
|
||||||
if(err) { |
|
||||||
callback(false); |
|
||||||
} |
|
||||||
else { |
|
||||||
callback(data.Body.toString('utf-8')); |
|
||||||
if (_this.expire && !skipExpire) { |
|
||||||
winston.warn('amazon s3 store cannot set expirations on keys'); |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
AmazonS3DocumentStore.prototype.set = function(key, data, callback, skipExpire) { |
|
||||||
var _this = this; |
|
||||||
|
|
||||||
var req = { |
|
||||||
Bucket: _this.bucket, |
|
||||||
Key: key, |
|
||||||
Body: data, |
|
||||||
ContentType: 'text/plain' |
|
||||||
}; |
|
||||||
|
|
||||||
_this.client.putObject(req, function(err, data) { |
|
||||||
if (err) { |
|
||||||
callback(false); |
|
||||||
} |
|
||||||
else { |
|
||||||
callback(true); |
|
||||||
if (_this.expire && !skipExpire) { |
|
||||||
winston.warn('amazon s3 store cannot set expirations on keys'); |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = AmazonS3DocumentStore; |
|
@ -1,63 +0,0 @@ |
|||||||
var fs = require('fs'); |
|
||||||
var crypto = require('crypto'); |
|
||||||
|
|
||||||
var 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; |
|
||||||
}; |
|
||||||
|
|
||||||
// Generate md5 of a string
|
|
||||||
FileDocumentStore.md5 = function(str) { |
|
||||||
var 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); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// 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'); |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
module.exports = FileDocumentStore; |
|
@ -1,89 +0,0 @@ |
|||||||
/*global require,module,process*/ |
|
||||||
|
|
||||||
const Datastore = require('@google-cloud/datastore'); |
|
||||||
const winston = require('winston'); |
|
||||||
|
|
||||||
class GoogleDatastoreDocumentStore { |
|
||||||
|
|
||||||
// Create a new store with options
|
|
||||||
constructor(options) { |
|
||||||
this.kind = "Haste"; |
|
||||||
this.expire = options.expire; |
|
||||||
this.datastore = new Datastore(); |
|
||||||
} |
|
||||||
|
|
||||||
// Save file in a key
|
|
||||||
set(key, data, callback, skipExpire) { |
|
||||||
var expireTime = (skipExpire || this.expire === undefined) ? null : new Date(Date.now() + this.expire * 1000); |
|
||||||
|
|
||||||
var taskKey = this.datastore.key([this.kind, key]) |
|
||||||
var task = { |
|
||||||
key: taskKey, |
|
||||||
data: [ |
|
||||||
{ |
|
||||||
name: 'value', |
|
||||||
value: data, |
|
||||||
excludeFromIndexes: true |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: 'expiration', |
|
||||||
value: expireTime |
|
||||||
} |
|
||||||
] |
|
||||||
}; |
|
||||||
|
|
||||||
this.datastore.insert(task).then(() => { |
|
||||||
callback(true); |
|
||||||
}) |
|
||||||
.catch(err => { |
|
||||||
callback(false); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
// Get a file from a key
|
|
||||||
get(key, callback, skipExpire) { |
|
||||||
var taskKey = this.datastore.key([this.kind, key]) |
|
||||||
|
|
||||||
this.datastore.get(taskKey).then((entity) => { |
|
||||||
if (skipExpire || entity[0]["expiration"] == null) { |
|
||||||
callback(entity[0]["value"]); |
|
||||||
} |
|
||||||
else { |
|
||||||
// check for expiry
|
|
||||||
if (entity[0]["expiration"] < new Date()) { |
|
||||||
winston.info("document expired", {key: key, expiration: entity[0]["expiration"], check: new Date(null)}); |
|
||||||
callback(false); |
|
||||||
} |
|
||||||
else { |
|
||||||
// update expiry
|
|
||||||
var task = { |
|
||||||
key: taskKey, |
|
||||||
data: [ |
|
||||||
{ |
|
||||||
name: 'value', |
|
||||||
value: entity[0]["value"], |
|
||||||
excludeFromIndexes: true |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: 'expiration', |
|
||||||
value: new Date(Date.now() + this.expire * 1000) |
|
||||||
} |
|
||||||
] |
|
||||||
}; |
|
||||||
this.datastore.update(task).then(() => { |
|
||||||
}) |
|
||||||
.catch(err => { |
|
||||||
winston.error("failed to update expiration", {error: err}); |
|
||||||
}); |
|
||||||
callback(entity[0]["value"]); |
|
||||||
} |
|
||||||
} |
|
||||||
}) |
|
||||||
.catch(err => { |
|
||||||
winston.error("Error retrieving value from Google Datastore", {error: err}); |
|
||||||
callback(false); |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = GoogleDatastoreDocumentStore; |
|
@ -1,54 +0,0 @@ |
|||||||
const memcached = require('memcached'); |
|
||||||
const winston = require('winston'); |
|
||||||
|
|
||||||
class MemcachedDocumentStore { |
|
||||||
|
|
||||||
// Create a new store with options
|
|
||||||
constructor(options) { |
|
||||||
this.expire = options.expire; |
|
||||||
|
|
||||||
const host = options.host || '127.0.0.1'; |
|
||||||
const port = options.port || 11211; |
|
||||||
const url = `${host}:${port}`; |
|
||||||
this.connect(url); |
|
||||||
} |
|
||||||
|
|
||||||
// Create a connection
|
|
||||||
connect(url) { |
|
||||||
this.client = new memcached(url); |
|
||||||
|
|
||||||
winston.info(`connecting to memcached on ${url}`); |
|
||||||
|
|
||||||
this.client.on('failure', function(error) { |
|
||||||
winston.info('error connecting to memcached', {error}); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
// Save file in a key
|
|
||||||
set(key, data, callback, skipExpire) { |
|
||||||
this.client.set(key, data, skipExpire ? 0 : this.expire || 0, (error) => { |
|
||||||
callback(!error); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
// Get a file from a key
|
|
||||||
get(key, callback, skipExpire) { |
|
||||||
this.client.get(key, (error, data) => { |
|
||||||
const value = error ? false : data; |
|
||||||
|
|
||||||
callback(value); |
|
||||||
|
|
||||||
// Update the key so that the expiration is pushed forward
|
|
||||||
if (value && !skipExpire) { |
|
||||||
this.set(key, data, (updateSucceeded) => { |
|
||||||
if (!updateSucceeded) { |
|
||||||
winston.error('failed to update expiration on GET', {key}); |
|
||||||
} |
|
||||||
}, skipExpire); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
module.exports = MemcachedDocumentStore; |
|
@ -1,88 +0,0 @@ |
|||||||
|
|
||||||
|
|
||||||
var MongoClient = require('mongodb').MongoClient, |
|
||||||
winston = require('winston'); |
|
||||||
|
|
||||||
var MongoDocumentStore = function (options) { |
|
||||||
this.expire = options.expire; |
|
||||||
this.connectionUrl = process.env.DATABASE_URl || options.connectionUrl; |
|
||||||
}; |
|
||||||
|
|
||||||
MongoDocumentStore.prototype.set = function (key, data, callback, skipExpire) { |
|
||||||
var now = Math.floor(new Date().getTime() / 1000), |
|
||||||
that = this; |
|
||||||
|
|
||||||
this.safeConnect(function (err, db) { |
|
||||||
if (err) |
|
||||||
return callback(false); |
|
||||||
|
|
||||||
db.collection('entries').update({ |
|
||||||
'entry_id': key, |
|
||||||
$or: [ |
|
||||||
{ expiration: -1 }, |
|
||||||
{ expiration: { $gt: now } } |
|
||||||
] |
|
||||||
}, { |
|
||||||
'entry_id': key, |
|
||||||
'value': data, |
|
||||||
'expiration': that.expire && !skipExpire ? that.expire + now : -1 |
|
||||||
}, { |
|
||||||
upsert: true |
|
||||||
}, function (err, existing) { |
|
||||||
if (err) { |
|
||||||
winston.error('error persisting value to mongodb', { error: err }); |
|
||||||
return callback(false); |
|
||||||
} |
|
||||||
|
|
||||||
callback(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
MongoDocumentStore.prototype.get = function (key, callback, skipExpire) { |
|
||||||
var now = Math.floor(new Date().getTime() / 1000), |
|
||||||
that = this; |
|
||||||
|
|
||||||
this.safeConnect(function (err, db) { |
|
||||||
if (err) |
|
||||||
return callback(false); |
|
||||||
|
|
||||||
db.collection('entries').findOne({ |
|
||||||
'entry_id': key, |
|
||||||
$or: [ |
|
||||||
{ expiration: -1 }, |
|
||||||
{ expiration: { $gt: now } } |
|
||||||
] |
|
||||||
}, function (err, entry) { |
|
||||||
if (err) { |
|
||||||
winston.error('error persisting value to mongodb', { error: err }); |
|
||||||
return callback(false); |
|
||||||
} |
|
||||||
|
|
||||||
callback(entry === null ? false : entry.value); |
|
||||||
|
|
||||||
if (entry !== null && entry.expiration !== -1 && that.expire && !skipExpire) { |
|
||||||
db.collection('entries').update({ |
|
||||||
'entry_id': key |
|
||||||
}, { |
|
||||||
$set: { |
|
||||||
'expiration': that.expire + now |
|
||||||
} |
|
||||||
}, function (err, result) { }); |
|
||||||
} |
|
||||||
}); |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
MongoDocumentStore.prototype.safeConnect = function (callback) { |
|
||||||
MongoClient.connect(this.connectionUrl, function (err, db) { |
|
||||||
if (err) { |
|
||||||
winston.error('error connecting to mongodb', { error: err }); |
|
||||||
callback(err); |
|
||||||
} else { |
|
||||||
callback(undefined, db); |
|
||||||
} |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
module.exports = MongoDocumentStore; |
|
@ -1,80 +0,0 @@ |
|||||||
/*global require,module,process*/ |
|
||||||
|
|
||||||
var winston = require('winston'); |
|
||||||
const {Pool} = require('pg'); |
|
||||||
|
|
||||||
// create table entries (id serial primary key, key varchar(255) not null, value text not null, expiration int, unique(key));
|
|
||||||
|
|
||||||
// A postgres document store
|
|
||||||
var PostgresDocumentStore = function (options) { |
|
||||||
this.expireJS = parseInt(options.expire, 10); |
|
||||||
|
|
||||||
const connectionString = process.env.DATABASE_URL || options.connectionUrl; |
|
||||||
this.pool = new Pool({connectionString}); |
|
||||||
}; |
|
||||||
|
|
||||||
PostgresDocumentStore.prototype = { |
|
||||||
|
|
||||||
// Set a given key
|
|
||||||
set: function (key, data, callback, skipExpire) { |
|
||||||
var now = Math.floor(new Date().getTime() / 1000); |
|
||||||
var that = this; |
|
||||||
this.safeConnect(function (err, client, done) { |
|
||||||
if (err) { return callback(false); } |
|
||||||
client.query('INSERT INTO entries (key, value, expiration) VALUES ($1, $2, $3)', [ |
|
||||||
key, |
|
||||||
data, |
|
||||||
that.expireJS && !skipExpire ? that.expireJS + now : null |
|
||||||
], function (err) { |
|
||||||
if (err) { |
|
||||||
winston.error('error persisting value to postgres', { error: err }); |
|
||||||
return callback(false); |
|
||||||
} |
|
||||||
callback(true); |
|
||||||
done(); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}, |
|
||||||
|
|
||||||
// Get a given key's data
|
|
||||||
get: function (key, callback, skipExpire) { |
|
||||||
var now = Math.floor(new Date().getTime() / 1000); |
|
||||||
var that = this; |
|
||||||
this.safeConnect(function (err, client, done) { |
|
||||||
if (err) { return callback(false); } |
|
||||||
client.query('SELECT id,value,expiration from entries where KEY = $1 and (expiration IS NULL or expiration > $2)', [key, now], function (err, result) { |
|
||||||
if (err) { |
|
||||||
winston.error('error retrieving value from postgres', { error: err }); |
|
||||||
return callback(false); |
|
||||||
} |
|
||||||
callback(result.rows.length ? result.rows[0].value : false); |
|
||||||
if (result.rows.length && that.expireJS && !skipExpire) { |
|
||||||
client.query('UPDATE entries SET expiration = $1 WHERE ID = $2', [ |
|
||||||
that.expireJS + now, |
|
||||||
result.rows[0].id |
|
||||||
], function (err) { |
|
||||||
if (!err) { |
|
||||||
done(); |
|
||||||
} |
|
||||||
}); |
|
||||||
} else { |
|
||||||
done(); |
|
||||||
} |
|
||||||
}); |
|
||||||
}); |
|
||||||
}, |
|
||||||
|
|
||||||
// A connection wrapper
|
|
||||||
safeConnect: function (callback) { |
|
||||||
this.pool.connect((error, client, done) => { |
|
||||||
if (error) { |
|
||||||
winston.error('error connecting to postgres', {error}); |
|
||||||
callback(error); |
|
||||||
} else { |
|
||||||
callback(undefined, client, done); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
module.exports = PostgresDocumentStore; |
|
@ -1,89 +0,0 @@ |
|||||||
var redis = require('redis'); |
|
||||||
var winston = require('winston'); |
|
||||||
|
|
||||||
// For storing in redis
|
|
||||||
// options[type] = redis
|
|
||||||
// options[host] - The host to connect to (default localhost)
|
|
||||||
// options[port] - The port to connect to (default 5379)
|
|
||||||
// options[db] - The db to use (default 0)
|
|
||||||
// options[expire] - The time to live for each key set (default never)
|
|
||||||
|
|
||||||
var RedisDocumentStore = function(options, client) { |
|
||||||
this.expire = options.expire; |
|
||||||
if (client) { |
|
||||||
winston.info('using predefined redis client'); |
|
||||||
RedisDocumentStore.client = client; |
|
||||||
} else if (!RedisDocumentStore.client) { |
|
||||||
winston.info('configuring redis'); |
|
||||||
RedisDocumentStore.connect(options); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// Create a connection according to config
|
|
||||||
RedisDocumentStore.connect = function(options) { |
|
||||||
var host = options.host || '127.0.0.1'; |
|
||||||
var port = options.port || 6379; |
|
||||||
var index = options.db || 0; |
|
||||||
RedisDocumentStore.client = redis.createClient(port, host); |
|
||||||
// authenticate if password is provided
|
|
||||||
if (options.password) { |
|
||||||
RedisDocumentStore.client.auth(options.password); |
|
||||||
} |
|
||||||
|
|
||||||
RedisDocumentStore.client.on('error', function(err) { |
|
||||||
winston.error('redis disconnected', err); |
|
||||||
}); |
|
||||||
|
|
||||||
RedisDocumentStore.client.select(index, function(err) { |
|
||||||
if (err) { |
|
||||||
winston.error( |
|
||||||
'error connecting to redis index ' + index, |
|
||||||
{ error: err } |
|
||||||
); |
|
||||||
process.exit(1); |
|
||||||
} |
|
||||||
else { |
|
||||||
winston.info('connected to redis on ' + host + ':' + port + '/' + index); |
|
||||||
} |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
// Save file in a key
|
|
||||||
RedisDocumentStore.prototype.set = function(key, data, callback, skipExpire) { |
|
||||||
var _this = this; |
|
||||||
RedisDocumentStore.client.set(key, data, function(err) { |
|
||||||
if (err) { |
|
||||||
callback(false); |
|
||||||
} |
|
||||||
else { |
|
||||||
if (!skipExpire) { |
|
||||||
_this.setExpiration(key); |
|
||||||
} |
|
||||||
callback(true); |
|
||||||
} |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
// Expire a key in expire time if set
|
|
||||||
RedisDocumentStore.prototype.setExpiration = function(key) { |
|
||||||
if (this.expire) { |
|
||||||
RedisDocumentStore.client.expire(key, this.expire, function(err) { |
|
||||||
if (err) { |
|
||||||
winston.error('failed to set expiry on key: ' + key); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// Get a file from a key
|
|
||||||
RedisDocumentStore.prototype.get = function(key, callback, skipExpire) { |
|
||||||
var _this = this; |
|
||||||
RedisDocumentStore.client.get(key, function(err, reply) { |
|
||||||
if (!err && !skipExpire) { |
|
||||||
_this.setExpiration(key); |
|
||||||
} |
|
||||||
callback(err ? false : reply); |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
module.exports = RedisDocumentStore; |
|
@ -1,46 +0,0 @@ |
|||||||
const crypto = require('crypto'); |
|
||||||
const rethink = require('rethinkdbdash'); |
|
||||||
const winston = require('winston'); |
|
||||||
|
|
||||||
const md5 = (str) => { |
|
||||||
const md5sum = crypto.createHash('md5'); |
|
||||||
md5sum.update(str); |
|
||||||
return md5sum.digest('hex'); |
|
||||||
}; |
|
||||||
|
|
||||||
class RethinkDBStore { |
|
||||||
constructor(options) { |
|
||||||
this.client = rethink({ |
|
||||||
silent: true, |
|
||||||
host: options.host || '127.0.0.1', |
|
||||||
port: options.port || 28015, |
|
||||||
db: options.db || 'haste', |
|
||||||
user: options.user || 'admin', |
|
||||||
password: options.password || '' |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
set(key, data, callback) { |
|
||||||
this.client.table('uploads').insert({ id: md5(key), data: data }).run((error) => { |
|
||||||
if (error) { |
|
||||||
callback(false); |
|
||||||
winston.error('failed to insert to table', error); |
|
||||||
return; |
|
||||||
} |
|
||||||
callback(true); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
get(key, callback) { |
|
||||||
this.client.table('uploads').get(md5(key)).run((error, result) => { |
|
||||||
if (error || !result) { |
|
||||||
callback(false); |
|
||||||
if (error) winston.error('failed to insert to table', error); |
|
||||||
return; |
|
||||||
} |
|
||||||
callback(result.data); |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = RethinkDBStore; |
|
@ -1,32 +0,0 @@ |
|||||||
const fs = require('fs'); |
|
||||||
|
|
||||||
module.exports = class DictionaryGenerator { |
|
||||||
|
|
||||||
constructor(options, readyCallback) { |
|
||||||
// Check options format
|
|
||||||
if (!options) throw Error('No options passed to generator'); |
|
||||||
if (!options.path) throw Error('No dictionary path specified in options'); |
|
||||||
|
|
||||||
// Load dictionary
|
|
||||||
fs.readFile(options.path, 'utf8', (err, data) => { |
|
||||||
if (err) throw err; |
|
||||||
|
|
||||||
this.dictionary = data.split(/[\n\r]+/); |
|
||||||
|
|
||||||
if (readyCallback) readyCallback(); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
// Generates a dictionary-based key, of keyLength words
|
|
||||||
createKey(keyLength) { |
|
||||||
let text = ''; |
|
||||||
|
|
||||||
for (let i = 0; i < keyLength; i++) { |
|
||||||
const index = Math.floor(Math.random() * this.dictionary.length); |
|
||||||
text += this.dictionary[index]; |
|
||||||
} |
|
||||||
|
|
||||||
return text; |
|
||||||
} |
|
||||||
|
|
||||||
}; |
|
@ -1,27 +0,0 @@ |
|||||||
// Draws inspiration from pwgen and http://tools.arantius.com/password
|
|
||||||
|
|
||||||
const randOf = (collection) => { |
|
||||||
return () => { |
|
||||||
return collection[Math.floor(Math.random() * collection.length)]; |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
// Helper methods to get an random vowel or consonant
|
|
||||||
const randVowel = randOf('aeiou'); |
|
||||||
const randConsonant = randOf('bcdfghjklmnpqrstvwxyz'); |
|
||||||
|
|
||||||
module.exports = class PhoneticKeyGenerator { |
|
||||||
|
|
||||||
// Generate a phonetic key of alternating consonant & vowel
|
|
||||||
createKey(keyLength) { |
|
||||||
let text = ''; |
|
||||||
const start = Math.round(Math.random()); |
|
||||||
|
|
||||||
for (let i = 0; i < keyLength; i++) { |
|
||||||
text += (i % 2 == start) ? randConsonant() : randVowel(); |
|
||||||
} |
|
||||||
|
|
||||||
return text; |
|
||||||
} |
|
||||||
|
|
||||||
}; |
|
@ -1,20 +0,0 @@ |
|||||||
module.exports = class RandomKeyGenerator { |
|
||||||
|
|
||||||
// Initialize a new generator with the given keySpace
|
|
||||||
constructor(options = {}) { |
|
||||||
this.keyspace = options.keyspace || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; |
|
||||||
} |
|
||||||
|
|
||||||
// Generate a key of the given length
|
|
||||||
createKey(keyLength) { |
|
||||||
var text = ''; |
|
||||||
|
|
||||||
for (var i = 0; i < keyLength; i++) { |
|
||||||
const index = Math.floor(Math.random() * this.keyspace.length); |
|
||||||
text += this.keyspace.charAt(index); |
|
||||||
} |
|
||||||
|
|
||||||
return text; |
|
||||||
} |
|
||||||
|
|
||||||
}; |
|
File diff suppressed because it is too large
Load Diff
@ -1,47 +0,0 @@ |
|||||||
{ |
|
||||||
"name": "haste", |
|
||||||
"version": "0.1.0", |
|
||||||
"private": true, |
|
||||||
"description": "Private Pastebin Server", |
|
||||||
"keywords": [ |
|
||||||
"paste", |
|
||||||
"pastebin" |
|
||||||
], |
|
||||||
"author": { |
|
||||||
"name": "John Crepezzi", |
|
||||||
"email": "john.crepezzi@gmail.com", |
|
||||||
"url": "http://seejohncode.com/" |
|
||||||
}, |
|
||||||
"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" |
|
||||||
}, |
|
||||||
"devDependencies": { |
|
||||||
"mocha": "^8.1.3" |
|
||||||
}, |
|
||||||
"bundledDependencies": [], |
|
||||||
"bin": { |
|
||||||
"haste-server": "./server.js" |
|
||||||
}, |
|
||||||
"files": [ |
|
||||||
"server.js", |
|
||||||
"lib", |
|
||||||
"static" |
|
||||||
], |
|
||||||
"directories": { |
|
||||||
"lib": "./lib" |
|
||||||
}, |
|
||||||
"scripts": { |
|
||||||
"start": "node server.js", |
|
||||||
"test": "mocha --recursive" |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1 @@ |
|||||||
|
{"name":"Haste-server","body":"# Pastebin\r\n\r\nhaste-server is an open source pastebin. You can see an example at [hastebin.com](http://hastebin.com), and it's easy to install locally.\r\n\r\n## GitHub\r\n\r\nCheck out the project on [github](https://github.com/seejohnrun/haste-server)","tagline":"open source pastebin written in node.js","google":"","note":"Don't delete this file! It's used internally to help with page regeneration."} |
@ -1,164 +0,0 @@ |
|||||||
var http = require('http'); |
|
||||||
var 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'); |
|
||||||
|
|
||||||
var DocumentHandler = require('./lib/document_handler'); |
|
||||||
|
|
||||||
// Load the configuration and set some defaults
|
|
||||||
const configPath = process.argv.length <= 2 ? 'config.js' : 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 */ |
|
||||||
} |
|
||||||
|
|
||||||
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); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// build the store from the config on-demand - so that we don't load it
|
|
||||||
// for statics
|
|
||||||
if (!config.storage) { |
|
||||||
config.storage = { type: 'file' }; |
|
||||||
} |
|
||||||
if (!config.storage.type) { |
|
||||||
config.storage.type = 'file'; |
|
||||||
} |
|
||||||
|
|
||||||
var 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); |
|
||||||
} |
|
||||||
|
|
||||||
// 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'); |
|
||||||
|
|
||||||
fs.writeFileSync('./static/' + dest, uglify.minify(orig_code).code, 'utf8'); |
|
||||||
winston.info('compressed ' + item + ' into ' + dest); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// 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 }); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Pick up a key generator
|
|
||||||
var pwOptions = config.keyGenerator || {}; |
|
||||||
pwOptions.type = pwOptions.type || 'random'; |
|
||||||
var gen = require('./lib/key_generators/' + pwOptions.type); |
|
||||||
var keyGenerator = new gen(pwOptions); |
|
||||||
|
|
||||||
// Configure the document handler
|
|
||||||
var documentHandler = new DocumentHandler({ |
|
||||||
store: preferredStore, |
|
||||||
maxLength: config.maxLength, |
|
||||||
keyLength: config.keyLength, |
|
||||||
keyGenerator: keyGenerator |
|
||||||
}); |
|
||||||
|
|
||||||
var app = connect(); |
|
||||||
|
|
||||||
// Rate limit all requests
|
|
||||||
if (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
|
|
||||||
|
|
||||||
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); |
|
||||||
}); |
|
||||||
|
|
||||||
// add documents
|
|
||||||
|
|
||||||
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); |
|
||||||
}); |
|
||||||
|
|
||||||
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 |
|
||||||
})); |
|
||||||
|
|
||||||
// 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(); |
|
||||||
}); |
|
||||||
})); |
|
||||||
|
|
||||||
// And match index
|
|
||||||
app.use(connect_st({ |
|
||||||
path: __dirname + '/static', |
|
||||||
content: { maxAge: config.staticMaxAge }, |
|
||||||
index: 'index.html' |
|
||||||
})); |
|
||||||
|
|
||||||
http.createServer(app).listen(config.port, config.host); |
|
||||||
|
|
||||||
winston.info('listening on ' + config.host + ':' + config.port); |
|
@ -1,175 +0,0 @@ |
|||||||
body { |
|
||||||
background: #002B36; |
|
||||||
padding: 20px 50px; |
|
||||||
margin: 0px; |
|
||||||
} |
|
||||||
|
|
||||||
/* 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; |
|
||||||
} |
|
||||||
|
|
||||||
/* 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; |
|
||||||
} |
|
||||||
|
|
||||||
/* code box when locked */ |
|
||||||
|
|
||||||
#box { |
|
||||||
padding: 0px; |
|
||||||
margin: 0px; |
|
||||||
width: 100%; |
|
||||||
border: 0px; |
|
||||||
outline: none; |
|
||||||
font-size: 13px; |
|
||||||
overflow: inherit; |
|
||||||
} |
|
||||||
|
|
||||||
#box code { |
|
||||||
padding: 0px; |
|
||||||
background: transparent !important; /* don't hide hastebox */ |
|
||||||
} |
|
||||||
|
|
||||||
/* key */ |
|
||||||
|
|
||||||
#key { |
|
||||||
position: fixed; |
|
||||||
top: 0px; |
|
||||||
right: 0px; |
|
||||||
z-index: +1000; /* watch out */ |
|
||||||
} |
|
||||||
|
|
||||||
#box1 { |
|
||||||
padding: 5px; |
|
||||||
text-align: center; |
|
||||||
background: #00222b; |
|
||||||
} |
|
||||||
|
|
||||||
#box2 { |
|
||||||
background: #08323c; |
|
||||||
font-size: 0px; |
|
||||||
padding: 0px 5px; |
|
||||||
} |
|
||||||
|
|
||||||
#box1 a.logo, #box1 a.logo:visited { |
|
||||||
display: inline-block; |
|
||||||
background: url(logo.png); |
|
||||||
width: 126px; |
|
||||||
height: 42px; |
|
||||||
} |
|
||||||
|
|
||||||
#box1 a.logo:hover { |
|
||||||
background-position: 0 bottom; |
|
||||||
} |
|
||||||
|
|
||||||
#box2 .function { |
|
||||||
background: url(function-icons.png); |
|
||||||
width: 32px; |
|
||||||
height: 37px; |
|
||||||
display: inline-block; |
|
||||||
position: relative; |
|
||||||
} |
|
||||||
|
|
||||||
#box2 .link embed { |
|
||||||
vertical-align: bottom; /* fix for zeroClipboard style */ |
|
||||||
} |
|
||||||
|
|
||||||
#box2 .function.enabled:hover { |
|
||||||
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; |
|
||||||
} |
|
||||||
|
|
||||||
#box3, #messages li { |
|
||||||
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; |
|
||||||
} |
|
||||||
|
|
||||||
#box3 .shortcut { |
|
||||||
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.new { background-position: -42px top; } |
|
||||||
#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; |
|
||||||
} |
|
||||||
|
|
||||||
#messages li { |
|
||||||
background:rgba(23,62,72,0.8); |
|
||||||
margin:0 auto; |
|
||||||
list-style:none; |
|
||||||
} |
|
||||||
|
|
||||||
#messages li.error { |
|
||||||
background:rgba(102,8,0,0.8); |
|
||||||
} |
|
||||||
|
|
@ -1,398 +0,0 @@ |
|||||||
/* global $, hljs, window, document */ |
|
||||||
|
|
||||||
///// represents a single document
|
|
||||||
|
|
||||||
var haste_document = function() { |
|
||||||
this.locked = false; |
|
||||||
}; |
|
||||||
|
|
||||||
// Escapes HTML tag characters
|
|
||||||
haste_document.prototype.htmlEscape = function(s) { |
|
||||||
return s |
|
||||||
.replace(/&/g, '&') |
|
||||||
.replace(/>/g, '>') |
|
||||||
.replace(/</g, '<') |
|
||||||
.replace(/"/g, '"'); |
|
||||||
}; |
|
||||||
|
|
||||||
// Get this document from the server and lock it here
|
|
||||||
haste_document.prototype.load = function(key, callback, lang) { |
|
||||||
var _this = this; |
|
||||||
$.ajax('/documents/' + key, { |
|
||||||
type: 'get', |
|
||||||
dataType: 'json', |
|
||||||
success: function(res) { |
|
||||||
_this.locked = true; |
|
||||||
_this.key = key; |
|
||||||
_this.data = res.data; |
|
||||||
try { |
|
||||||
var high; |
|
||||||
if (lang === 'txt') { |
|
||||||
high = { value: _this.htmlEscape(res.data) }; |
|
||||||
} |
|
||||||
else if (lang) { |
|
||||||
high = hljs.highlight(lang, res.data); |
|
||||||
} |
|
||||||
else { |
|
||||||
high = hljs.highlightAuto(res.data); |
|
||||||
} |
|
||||||
} catch(err) { |
|
||||||
// failed highlight, fall back on auto
|
|
||||||
high = hljs.highlightAuto(res.data); |
|
||||||
} |
|
||||||
callback({ |
|
||||||
value: high.value, |
|
||||||
key: key, |
|
||||||
language: high.language || lang, |
|
||||||
lineCount: res.data.split('\n').length |
|
||||||
}); |
|
||||||
}, |
|
||||||
error: function() { |
|
||||||
callback(false); |
|
||||||
} |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
// Save this document to the server and lock it here
|
|
||||||
haste_document.prototype.save = function(data, callback) { |
|
||||||
if (this.locked) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
this.data = data; |
|
||||||
var _this = this; |
|
||||||
$.ajax('/documents', { |
|
||||||
type: 'post', |
|
||||||
data: data, |
|
||||||
dataType: 'json', |
|
||||||
contentType: 'text/plain; charset=utf-8', |
|
||||||
success: function(res) { |
|
||||||
_this.locked = true; |
|
||||||
_this.key = res.key; |
|
||||||
var high = hljs.highlightAuto(data); |
|
||||||
callback(null, { |
|
||||||
value: high.value, |
|
||||||
key: res.key, |
|
||||||
language: high.language, |
|
||||||
lineCount: data.split('\n').length |
|
||||||
}); |
|
||||||
}, |
|
||||||
error: function(res) { |
|
||||||
try { |
|
||||||
callback($.parseJSON(res.responseText)); |
|
||||||
} |
|
||||||
catch (e) { |
|
||||||
callback({message: 'Something went wrong!'}); |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
///// represents the paste application
|
|
||||||
|
|
||||||
var haste = function(appName, options) { |
|
||||||
this.appName = appName; |
|
||||||
this.$textarea = $('textarea'); |
|
||||||
this.$box = $('#box'); |
|
||||||
this.$code = $('#box code'); |
|
||||||
this.$linenos = $('#linenos'); |
|
||||||
this.options = options; |
|
||||||
this.configureShortcuts(); |
|
||||||
this.configureButtons(); |
|
||||||
// If twitter is disabled, hide the button
|
|
||||||
if (!options.twitter) { |
|
||||||
$('#box2 .twitter').hide(); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// Set the page title - include the appName
|
|
||||||
haste.prototype.setTitle = function(ext) { |
|
||||||
var title = ext ? this.appName + ' - ' + ext : this.appName; |
|
||||||
document.title = title; |
|
||||||
}; |
|
||||||
|
|
||||||
// Show a message box
|
|
||||||
haste.prototype.showMessage = function(msg, cls) { |
|
||||||
var msgBox = $('<li class="'+(cls || 'info')+'">'+msg+'</li>'); |
|
||||||
$('#messages').prepend(msgBox); |
|
||||||
setTimeout(function() { |
|
||||||
msgBox.slideUp('fast', function() { $(this).remove(); }); |
|
||||||
}, 3000); |
|
||||||
}; |
|
||||||
|
|
||||||
// Show the light key
|
|
||||||
haste.prototype.lightKey = function() { |
|
||||||
this.configureKey(['new', 'save']); |
|
||||||
}; |
|
||||||
|
|
||||||
// Show the full key
|
|
||||||
haste.prototype.fullKey = function() { |
|
||||||
this.configureKey(['new', 'duplicate', 'twitter', 'raw']); |
|
||||||
}; |
|
||||||
|
|
||||||
// Set the key up for certain things to be enabled
|
|
||||||
haste.prototype.configureKey = function(enable) { |
|
||||||
var $this, i = 0; |
|
||||||
$('#box2 .function').each(function() { |
|
||||||
$this = $(this); |
|
||||||
for (i = 0; i < enable.length; i++) { |
|
||||||
if ($this.hasClass(enable[i])) { |
|
||||||
$this.addClass('enabled'); |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
||||||
$this.removeClass('enabled'); |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
// Remove the current document (if there is one)
|
|
||||||
// and set up for a new one
|
|
||||||
haste.prototype.newDocument = function(hideHistory) { |
|
||||||
this.$box.hide(); |
|
||||||
this.doc = new haste_document(); |
|
||||||
if (!hideHistory) { |
|
||||||
window.history.pushState(null, this.appName, '/'); |
|
||||||
} |
|
||||||
this.setTitle(); |
|
||||||
this.lightKey(); |
|
||||||
this.$textarea.val('').show('fast', function() { |
|
||||||
this.focus(); |
|
||||||
}); |
|
||||||
this.removeLineNumbers(); |
|
||||||
}; |
|
||||||
|
|
||||||
// Map of common extensions
|
|
||||||
// Note: this list does not need to include anything that IS its extension,
|
|
||||||
// due to the behavior of lookupTypeByExtension and lookupExtensionByType
|
|
||||||
// Note: optimized for lookupTypeByExtension
|
|
||||||
haste.extensionMap = { |
|
||||||
rb: 'ruby', py: 'python', pl: 'perl', php: 'php', scala: 'scala', go: 'go', |
|
||||||
xml: 'xml', html: 'xml', htm: 'xml', css: 'css', js: 'javascript', vbs: 'vbscript', |
|
||||||
lua: 'lua', pas: 'delphi', java: 'java', cpp: 'cpp', cc: 'cpp', m: 'objectivec', |
|
||||||
vala: 'vala', sql: 'sql', sm: 'smalltalk', lisp: 'lisp', ini: 'ini', |
|
||||||
diff: 'diff', bash: 'bash', sh: 'bash', tex: 'tex', erl: 'erlang', hs: 'haskell', |
|
||||||
md: 'markdown', txt: '', coffee: 'coffee', swift: 'swift' |
|
||||||
}; |
|
||||||
|
|
||||||
// Look up the extension preferred for a type
|
|
||||||
// If not found, return the type itself - which we'll place as the extension
|
|
||||||
haste.prototype.lookupExtensionByType = function(type) { |
|
||||||
for (var key in haste.extensionMap) { |
|
||||||
if (haste.extensionMap[key] === type) return key; |
|
||||||
} |
|
||||||
return type; |
|
||||||
}; |
|
||||||
|
|
||||||
// Look up the type for a given extension
|
|
||||||
// If not found, return the extension - which we'll attempt to use as the type
|
|
||||||
haste.prototype.lookupTypeByExtension = function(ext) { |
|
||||||
return haste.extensionMap[ext] || ext; |
|
||||||
}; |
|
||||||
|
|
||||||
// Add line numbers to the document
|
|
||||||
// For the specified number of lines
|
|
||||||
haste.prototype.addLineNumbers = function(lineCount) { |
|
||||||
var h = ''; |
|
||||||
for (var i = 0; i < lineCount; i++) { |
|
||||||
h += (i + 1).toString() + '<br/>'; |
|
||||||
} |
|
||||||
$('#linenos').html(h); |
|
||||||
}; |
|
||||||
|
|
||||||
// Remove the line numbers
|
|
||||||
haste.prototype.removeLineNumbers = function() { |
|
||||||
$('#linenos').html('>'); |
|
||||||
}; |
|
||||||
|
|
||||||
// Load a document and show it
|
|
||||||
haste.prototype.loadDocument = function(key) { |
|
||||||
// Split the key up
|
|
||||||
var parts = key.split('.', 2); |
|
||||||
// Ask for what we want
|
|
||||||
var _this = this; |
|
||||||
_this.doc = new haste_document(); |
|
||||||
_this.doc.load(parts[0], function(ret) { |
|
||||||
if (ret) { |
|
||||||
_this.$code.html(ret.value); |
|
||||||
_this.setTitle(ret.key); |
|
||||||
_this.fullKey(); |
|
||||||
_this.$textarea.val('').hide(); |
|
||||||
_this.$box.show().focus(); |
|
||||||
_this.addLineNumbers(ret.lineCount); |
|
||||||
} |
|
||||||
else { |
|
||||||
_this.newDocument(); |
|
||||||
} |
|
||||||
}, this.lookupTypeByExtension(parts[1])); |
|
||||||
}; |
|
||||||
|
|
||||||
// Duplicate the current document - only if locked
|
|
||||||
haste.prototype.duplicateDocument = function() { |
|
||||||
if (this.doc.locked) { |
|
||||||
var currentData = this.doc.data; |
|
||||||
this.newDocument(); |
|
||||||
this.$textarea.val(currentData); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// Lock the current document
|
|
||||||
haste.prototype.lockDocument = function() { |
|
||||||
var _this = this; |
|
||||||
this.doc.save(this.$textarea.val(), function(err, ret) { |
|
||||||
if (err) { |
|
||||||
_this.showMessage(err.message, 'error'); |
|
||||||
} |
|
||||||
else if (ret) { |
|
||||||
_this.$code.html(ret.value); |
|
||||||
_this.setTitle(ret.key); |
|
||||||
var file = '/' + ret.key; |
|
||||||
if (ret.language) { |
|
||||||
file += '.' + _this.lookupExtensionByType(ret.language); |
|
||||||
} |
|
||||||
window.history.pushState(null, _this.appName + '-' + ret.key, file); |
|
||||||
_this.fullKey(); |
|
||||||
_this.$textarea.val('').hide(); |
|
||||||
_this.$box.show().focus(); |
|
||||||
_this.addLineNumbers(ret.lineCount); |
|
||||||
} |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
haste.prototype.configureButtons = function() { |
|
||||||
var _this = this; |
|
||||||
this.buttons = [ |
|
||||||
{ |
|
||||||
$where: $('#box2 .save'), |
|
||||||
label: 'Save', |
|
||||||
shortcutDescription: 'control + s', |
|
||||||
shortcut: function(evt) { |
|
||||||
return evt.ctrlKey && (evt.keyCode === 83); |
|
||||||
}, |
|
||||||
action: function() { |
|
||||||
if (_this.$textarea.val().replace(/^\s+|\s+$/g, '') !== '') { |
|
||||||
_this.lockDocument(); |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
{ |
|
||||||
$where: $('#box2 .new'), |
|
||||||
label: 'New', |
|
||||||
shortcut: function(evt) { |
|
||||||
return evt.ctrlKey && evt.keyCode === 78; |
|
||||||
}, |
|
||||||
shortcutDescription: 'control + n', |
|
||||||
action: function() { |
|
||||||
_this.newDocument(!_this.doc.key); |
|
||||||
} |
|
||||||
}, |
|
||||||
{ |
|
||||||
$where: $('#box2 .duplicate'), |
|
||||||
label: 'Duplicate & Edit', |
|
||||||
shortcut: function(evt) { |
|
||||||
return _this.doc.locked && evt.ctrlKey && evt.keyCode === 68; |
|
||||||
}, |
|
||||||
shortcutDescription: 'control + d', |
|
||||||
action: function() { |
|
||||||
_this.duplicateDocument(); |
|
||||||
} |
|
||||||
}, |
|
||||||
{ |
|
||||||
$where: $('#box2 .raw'), |
|
||||||
label: 'Just Text', |
|
||||||
shortcut: function(evt) { |
|
||||||
return evt.ctrlKey && evt.shiftKey && evt.keyCode === 82; |
|
||||||
}, |
|
||||||
shortcutDescription: 'control + shift + r', |
|
||||||
action: function() { |
|
||||||
window.location.href = '/raw/' + _this.doc.key; |
|
||||||
} |
|
||||||
}, |
|
||||||
{ |
|
||||||
$where: $('#box2 .twitter'), |
|
||||||
label: 'Twitter', |
|
||||||
shortcut: function(evt) { |
|
||||||
return _this.options.twitter && _this.doc.locked && evt.shiftKey && evt.ctrlKey && evt.keyCode == 84; |
|
||||||
}, |
|
||||||
shortcutDescription: 'control + shift + t', |
|
||||||
action: function() { |
|
||||||
window.open('https://twitter.com/share?url=' + encodeURI(window.location.href)); |
|
||||||
} |
|
||||||
} |
|
||||||
]; |
|
||||||
for (var i = 0; i < this.buttons.length; i++) { |
|
||||||
this.configureButton(this.buttons[i]); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
haste.prototype.configureButton = function(options) { |
|
||||||
// Handle the click action
|
|
||||||
options.$where.click(function(evt) { |
|
||||||
evt.preventDefault(); |
|
||||||
if (!options.clickDisabled && $(this).hasClass('enabled')) { |
|
||||||
options.action(); |
|
||||||
} |
|
||||||
}); |
|
||||||
// Show the label
|
|
||||||
options.$where.mouseenter(function() { |
|
||||||
$('#box3 .label').text(options.label); |
|
||||||
$('#box3 .shortcut').text(options.shortcutDescription || ''); |
|
||||||
$('#box3').show(); |
|
||||||
$(this).append($('#pointer').remove().show()); |
|
||||||
}); |
|
||||||
// Hide the label
|
|
||||||
options.$where.mouseleave(function() { |
|
||||||
$('#box3').hide(); |
|
||||||
$('#pointer').hide(); |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
// Configure keyboard shortcuts for the textarea
|
|
||||||
haste.prototype.configureShortcuts = function() { |
|
||||||
var _this = this; |
|
||||||
$(document.body).keydown(function(evt) { |
|
||||||
var button; |
|
||||||
for (var i = 0 ; i < _this.buttons.length; i++) { |
|
||||||
button = _this.buttons[i]; |
|
||||||
if (button.shortcut && button.shortcut(evt)) { |
|
||||||
evt.preventDefault(); |
|
||||||
button.action(); |
|
||||||
return; |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
///// Tab behavior in the textarea - 2 spaces per tab
|
|
||||||
$(function() { |
|
||||||
|
|
||||||
$('textarea').keydown(function(evt) { |
|
||||||
if (evt.keyCode === 9) { |
|
||||||
evt.preventDefault(); |
|
||||||
var myValue = ' '; |
|
||||||
// http://stackoverflow.com/questions/946534/insert-text-into-textarea-with-jquery
|
|
||||||
// For browsers like Internet Explorer
|
|
||||||
if (document.selection) { |
|
||||||
this.focus(); |
|
||||||
var sel = document.selection.createRange(); |
|
||||||
sel.text = myValue; |
|
||||||
this.focus(); |
|
||||||
} |
|
||||||
// Mozilla and Webkit
|
|
||||||
else if (this.selectionStart || this.selectionStart == '0') { |
|
||||||
var startPos = this.selectionStart; |
|
||||||
var endPos = this.selectionEnd; |
|
||||||
var 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; |
|
||||||
this.selectionEnd = startPos + myValue.length; |
|
||||||
this.scrollTop = scrollTop; |
|
||||||
} |
|
||||||
else { |
|
||||||
this.value += myValue; |
|
||||||
this.focus(); |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
}); |
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 6.1 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 2.8 KiB |
@ -1,68 +0,0 @@ |
|||||||
<html> |
|
||||||
|
|
||||||
<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"/> |
|
||||||
|
|
||||||
<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> |
|
||||||
|
|
||||||
<meta name="robots" content="noindex,nofollow"/> |
|
||||||
|
|
||||||
<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> |
|
||||||
|
|
||||||
<body> |
|
||||||
<ul id="messages"></ul> |
|
||||||
|
|
||||||
<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> |
|
||||||
|
|
||||||
<div id="linenos"></div> |
|
||||||
<pre id="box" style="display:none;" class="hljs" tabindex="0"><code></code></pre> |
|
||||||
<textarea spellcheck="false" style="display:none;"></textarea> |
|
||||||
|
|
||||||
</body> |
|
||||||
|
|
||||||
</html> |
|
Before Width: | Height: | Size: 4.6 KiB |
@ -1,4 +0,0 @@ |
|||||||
User-agent: * |
|
||||||
Disallow: /* |
|
||||||
Allow: /?okparam= |
|
||||||
Allow: /$ |
|
@ -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; |
|
||||||
} |
|
@ -0,0 +1,69 @@ |
|||||||
|
.highlight { background: #ffffff; } |
||||||
|
.highlight .c { color: #999988; font-style: italic } /* Comment */ |
||||||
|
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ |
||||||
|
.highlight .k { font-weight: bold } /* Keyword */ |
||||||
|
.highlight .o { font-weight: bold } /* Operator */ |
||||||
|
.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ |
||||||
|
.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ |
||||||
|
.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ |
||||||
|
.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ |
||||||
|
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ |
||||||
|
.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */ |
||||||
|
.highlight .ge { font-style: italic } /* Generic.Emph */ |
||||||
|
.highlight .gr { color: #aa0000 } /* Generic.Error */ |
||||||
|
.highlight .gh { color: #999999 } /* Generic.Heading */ |
||||||
|
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ |
||||||
|
.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */ |
||||||
|
.highlight .go { color: #888888 } /* Generic.Output */ |
||||||
|
.highlight .gp { color: #555555 } /* Generic.Prompt */ |
||||||
|
.highlight .gs { font-weight: bold } /* Generic.Strong */ |
||||||
|
.highlight .gu { color: #800080; font-weight: bold; } /* Generic.Subheading */ |
||||||
|
.highlight .gt { color: #aa0000 } /* Generic.Traceback */ |
||||||
|
.highlight .kc { font-weight: bold } /* Keyword.Constant */ |
||||||
|
.highlight .kd { font-weight: bold } /* Keyword.Declaration */ |
||||||
|
.highlight .kn { font-weight: bold } /* Keyword.Namespace */ |
||||||
|
.highlight .kp { font-weight: bold } /* Keyword.Pseudo */ |
||||||
|
.highlight .kr { font-weight: bold } /* Keyword.Reserved */ |
||||||
|
.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ |
||||||
|
.highlight .m { color: #009999 } /* Literal.Number */ |
||||||
|
.highlight .s { color: #d14 } /* Literal.String */ |
||||||
|
.highlight .na { color: #008080 } /* Name.Attribute */ |
||||||
|
.highlight .nb { color: #0086B3 } /* Name.Builtin */ |
||||||
|
.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ |
||||||
|
.highlight .no { color: #008080 } /* Name.Constant */ |
||||||
|
.highlight .ni { color: #800080 } /* Name.Entity */ |
||||||
|
.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ |
||||||
|
.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ |
||||||
|
.highlight .nn { color: #555555 } /* Name.Namespace */ |
||||||
|
.highlight .nt { color: #000080 } /* Name.Tag */ |
||||||
|
.highlight .nv { color: #008080 } /* Name.Variable */ |
||||||
|
.highlight .ow { font-weight: bold } /* Operator.Word */ |
||||||
|
.highlight .w { color: #bbbbbb } /* Text.Whitespace */ |
||||||
|
.highlight .mf { color: #009999 } /* Literal.Number.Float */ |
||||||
|
.highlight .mh { color: #009999 } /* Literal.Number.Hex */ |
||||||
|
.highlight .mi { color: #009999 } /* Literal.Number.Integer */ |
||||||
|
.highlight .mo { color: #009999 } /* Literal.Number.Oct */ |
||||||
|
.highlight .sb { color: #d14 } /* Literal.String.Backtick */ |
||||||
|
.highlight .sc { color: #d14 } /* Literal.String.Char */ |
||||||
|
.highlight .sd { color: #d14 } /* Literal.String.Doc */ |
||||||
|
.highlight .s2 { color: #d14 } /* Literal.String.Double */ |
||||||
|
.highlight .se { color: #d14 } /* Literal.String.Escape */ |
||||||
|
.highlight .sh { color: #d14 } /* Literal.String.Heredoc */ |
||||||
|
.highlight .si { color: #d14 } /* Literal.String.Interpol */ |
||||||
|
.highlight .sx { color: #d14 } /* Literal.String.Other */ |
||||||
|
.highlight .sr { color: #009926 } /* Literal.String.Regex */ |
||||||
|
.highlight .s1 { color: #d14 } /* Literal.String.Single */ |
||||||
|
.highlight .ss { color: #990073 } /* Literal.String.Symbol */ |
||||||
|
.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ |
||||||
|
.highlight .vc { color: #008080 } /* Name.Variable.Class */ |
||||||
|
.highlight .vg { color: #008080 } /* Name.Variable.Global */ |
||||||
|
.highlight .vi { color: #008080 } /* Name.Variable.Instance */ |
||||||
|
.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ |
||||||
|
|
||||||
|
.type-csharp .highlight .k { color: #0000FF } |
||||||
|
.type-csharp .highlight .kt { color: #0000FF } |
||||||
|
.type-csharp .highlight .nf { color: #000000; font-weight: normal } |
||||||
|
.type-csharp .highlight .nc { color: #2B91AF } |
||||||
|
.type-csharp .highlight .nn { color: #000000 } |
||||||
|
.type-csharp .highlight .s { color: #A31515 } |
||||||
|
.type-csharp .highlight .sc { color: #A31515 } |
@ -0,0 +1,255 @@ |
|||||||
|
@import url(https://fonts.googleapis.com/css?family=Lato:300italic,700italic,300,700); |
||||||
|
|
||||||
|
body { |
||||||
|
padding:50px; |
||||||
|
font:14px/1.5 Lato, "Helvetica Neue", Helvetica, Arial, sans-serif; |
||||||
|
color:#777; |
||||||
|
font-weight:300; |
||||||
|
} |
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 { |
||||||
|
color:#222; |
||||||
|
margin:0 0 20px; |
||||||
|
} |
||||||
|
|
||||||
|
p, ul, ol, table, pre, dl { |
||||||
|
margin:0 0 20px; |
||||||
|
} |
||||||
|
|
||||||
|
h1, h2, h3 { |
||||||
|
line-height:1.1; |
||||||
|
} |
||||||
|
|
||||||
|
h1 { |
||||||
|
font-size:28px; |
||||||
|
} |
||||||
|
|
||||||
|
h2 { |
||||||
|
color:#393939; |
||||||
|
} |
||||||
|
|
||||||
|
h3, h4, h5, h6 { |
||||||
|
color:#494949; |
||||||
|
} |
||||||
|
|
||||||
|
a { |
||||||
|
color:#39c; |
||||||
|
font-weight:400; |
||||||
|
text-decoration:none; |
||||||
|
} |
||||||
|
|
||||||
|
a small { |
||||||
|
font-size:11px; |
||||||
|
color:#777; |
||||||
|
margin-top:-0.6em; |
||||||
|
display:block; |
||||||
|
} |
||||||
|
|
||||||
|
.wrapper { |
||||||
|
width:860px; |
||||||
|
margin:0 auto; |
||||||
|
} |
||||||
|
|
||||||
|
blockquote { |
||||||
|
border-left:1px solid #e5e5e5; |
||||||
|
margin:0; |
||||||
|
padding:0 0 0 20px; |
||||||
|
font-style:italic; |
||||||
|
} |
||||||
|
|
||||||
|
code, pre { |
||||||
|
font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal; |
||||||
|
color:#333; |
||||||
|
font-size:12px; |
||||||
|
} |
||||||
|
|
||||||
|
pre { |
||||||
|
padding:8px 15px; |
||||||
|
background: #f8f8f8; |
||||||
|
border-radius:5px; |
||||||
|
border:1px solid #e5e5e5; |
||||||
|
overflow-x: auto; |
||||||
|
} |
||||||
|
|
||||||
|
table { |
||||||
|
width:100%; |
||||||
|
border-collapse:collapse; |
||||||
|
} |
||||||
|
|
||||||
|
th, td { |
||||||
|
text-align:left; |
||||||
|
padding:5px 10px; |
||||||
|
border-bottom:1px solid #e5e5e5; |
||||||
|
} |
||||||
|
|
||||||
|
dt { |
||||||
|
color:#444; |
||||||
|
font-weight:700; |
||||||
|
} |
||||||
|
|
||||||
|
th { |
||||||
|
color:#444; |
||||||
|
} |
||||||
|
|
||||||
|
img { |
||||||
|
max-width:100%; |
||||||
|
} |
||||||
|
|
||||||
|
header { |
||||||
|
width:270px; |
||||||
|
float:left; |
||||||
|
position:fixed; |
||||||
|
} |
||||||
|
|
||||||
|
header ul { |
||||||
|
list-style:none; |
||||||
|
height:40px; |
||||||
|
|
||||||
|
padding:0; |
||||||
|
|
||||||
|
background: #eee; |
||||||
|
background: -moz-linear-gradient(top, #f8f8f8 0%, #dddddd 100%); |
||||||
|
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd)); |
||||||
|
background: -webkit-linear-gradient(top, #f8f8f8 0%,#dddddd 100%); |
||||||
|
background: -o-linear-gradient(top, #f8f8f8 0%,#dddddd 100%); |
||||||
|
background: -ms-linear-gradient(top, #f8f8f8 0%,#dddddd 100%); |
||||||
|
background: linear-gradient(top, #f8f8f8 0%,#dddddd 100%); |
||||||
|
|
||||||
|
border-radius:5px; |
||||||
|
border:1px solid #d2d2d2; |
||||||
|
box-shadow:inset #fff 0 1px 0, inset rgba(0,0,0,0.03) 0 -1px 0; |
||||||
|
width:270px; |
||||||
|
} |
||||||
|
|
||||||
|
header li { |
||||||
|
width:89px; |
||||||
|
float:left; |
||||||
|
border-right:1px solid #d2d2d2; |
||||||
|
height:40px; |
||||||
|
} |
||||||
|
|
||||||
|
header ul a { |
||||||
|
line-height:1; |
||||||
|
font-size:11px; |
||||||
|
color:#999; |
||||||
|
display:block; |
||||||
|
text-align:center; |
||||||
|
padding-top:6px; |
||||||
|
height:40px; |
||||||
|
} |
||||||
|
|
||||||
|
strong { |
||||||
|
color:#222; |
||||||
|
font-weight:700; |
||||||
|
} |
||||||
|
|
||||||
|
header ul li + li { |
||||||
|
width:88px; |
||||||
|
border-left:1px solid #fff; |
||||||
|
} |
||||||
|
|
||||||
|
header ul li + li + li { |
||||||
|
border-right:none; |
||||||
|
width:89px; |
||||||
|
} |
||||||
|
|
||||||
|
header ul a strong { |
||||||
|
font-size:14px; |
||||||
|
display:block; |
||||||
|
color:#222; |
||||||
|
} |
||||||
|
|
||||||
|
section { |
||||||
|
width:500px; |
||||||
|
float:right; |
||||||
|
padding-bottom:50px; |
||||||
|
} |
||||||
|
|
||||||
|
small { |
||||||
|
font-size:11px; |
||||||
|
} |
||||||
|
|
||||||
|
hr { |
||||||
|
border:0; |
||||||
|
background:#e5e5e5; |
||||||
|
height:1px; |
||||||
|
margin:0 0 20px; |
||||||
|
} |
||||||
|
|
||||||
|
footer { |
||||||
|
width:270px; |
||||||
|
float:left; |
||||||
|
position:fixed; |
||||||
|
bottom:50px; |
||||||
|
} |
||||||
|
|
||||||
|
@media print, screen and (max-width: 960px) { |
||||||
|
|
||||||
|
div.wrapper { |
||||||
|
width:auto; |
||||||
|
margin:0; |
||||||
|
} |
||||||
|
|
||||||
|
header, section, footer { |
||||||
|
float:none; |
||||||
|
position:static; |
||||||
|
width:auto; |
||||||
|
} |
||||||
|
|
||||||
|
header { |
||||||
|
padding-right:320px; |
||||||
|
} |
||||||
|
|
||||||
|
section { |
||||||
|
border:1px solid #e5e5e5; |
||||||
|
border-width:1px 0; |
||||||
|
padding:20px 0; |
||||||
|
margin:0 0 20px; |
||||||
|
} |
||||||
|
|
||||||
|
header a small { |
||||||
|
display:inline; |
||||||
|
} |
||||||
|
|
||||||
|
header ul { |
||||||
|
position:absolute; |
||||||
|
right:50px; |
||||||
|
top:52px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@media print, screen and (max-width: 720px) { |
||||||
|
body { |
||||||
|
word-wrap:break-word; |
||||||
|
} |
||||||
|
|
||||||
|
header { |
||||||
|
padding:0; |
||||||
|
} |
||||||
|
|
||||||
|
header ul, header p.view { |
||||||
|
position:static; |
||||||
|
} |
||||||
|
|
||||||
|
pre, code { |
||||||
|
word-wrap:normal; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@media print, screen and (max-width: 480px) { |
||||||
|
body { |
||||||
|
padding:15px; |
||||||
|
} |
||||||
|
|
||||||
|
header ul { |
||||||
|
display:none; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@media print { |
||||||
|
body { |
||||||
|
padding:0.4in; |
||||||
|
font-size:12pt; |
||||||
|
color:#444; |
||||||
|
} |
||||||
|
} |
@ -1,26 +0,0 @@ |
|||||||
/* global describe, it */ |
|
||||||
|
|
||||||
var assert = require('assert'); |
|
||||||
|
|
||||||
var DocumentHandler = require('../lib/document_handler'); |
|
||||||
var Generator = require('../lib/key_generators/random'); |
|
||||||
|
|
||||||
describe('document_handler', function() { |
|
||||||
|
|
||||||
describe('randomKey', function() { |
|
||||||
|
|
||||||
it('should choose a key of the proper length', function() { |
|
||||||
var gen = new Generator(); |
|
||||||
var dh = new DocumentHandler({ keyLength: 6, keyGenerator: gen }); |
|
||||||
assert.equal(6, dh.acceptableKey().length); |
|
||||||
}); |
|
||||||
|
|
||||||
it('should choose a default key length', function() { |
|
||||||
var gen = new Generator(); |
|
||||||
var dh = new DocumentHandler({ keyGenerator: gen }); |
|
||||||
assert.equal(dh.keyLength, DocumentHandler.defaultKeyLength); |
|
||||||
}); |
|
||||||
|
|
||||||
}); |
|
||||||
|
|
||||||
}); |
|
@ -1,34 +0,0 @@ |
|||||||
/* global describe, it */ |
|
||||||
|
|
||||||
const assert = require('assert'); |
|
||||||
|
|
||||||
const fs = require('fs'); |
|
||||||
|
|
||||||
const Generator = require('../../lib/key_generators/dictionary'); |
|
||||||
|
|
||||||
describe('DictionaryGenerator', function() { |
|
||||||
describe('options', function() { |
|
||||||
it('should throw an error if given no options', () => { |
|
||||||
assert.throws(() => { |
|
||||||
new Generator(); |
|
||||||
}, Error); |
|
||||||
}); |
|
||||||
|
|
||||||
it('should throw an error if given no path', () => { |
|
||||||
assert.throws(() => { |
|
||||||
new Generator({}); |
|
||||||
}, Error); |
|
||||||
}); |
|
||||||
}); |
|
||||||
describe('generation', function() { |
|
||||||
it('should return a key of the proper number of words from the given dictionary', () => { |
|
||||||
const path = '/tmp/haste-server-test-dictionary'; |
|
||||||
const words = ['cat']; |
|
||||||
fs.writeFileSync(path, words.join('\n')); |
|
||||||
|
|
||||||
const gen = new Generator({path}, () => { |
|
||||||
assert.equal('catcatcat', gen.createKey(3)); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
@ -1,35 +0,0 @@ |
|||||||
/* global describe, it */ |
|
||||||
|
|
||||||
const assert = require('assert'); |
|
||||||
|
|
||||||
const Generator = require('../../lib/key_generators/phonetic'); |
|
||||||
|
|
||||||
const vowels = 'aeiou'; |
|
||||||
const consonants = 'bcdfghjklmnpqrstvwxyz'; |
|
||||||
|
|
||||||
describe('PhoneticKeyGenerator', () => { |
|
||||||
describe('generation', () => { |
|
||||||
it('should return a key of the proper length', () => { |
|
||||||
const gen = new Generator(); |
|
||||||
assert.equal(6, gen.createKey(6).length); |
|
||||||
}); |
|
||||||
|
|
||||||
it('should alternate consonants and vowels', () => { |
|
||||||
const gen = new Generator(); |
|
||||||
|
|
||||||
const key = gen.createKey(3); |
|
||||||
|
|
||||||
// if it starts with a consonant, we expect cvc
|
|
||||||
// if it starts with a vowel, we expect vcv
|
|
||||||
if(consonants.includes(key[0])) { |
|
||||||
assert.ok(consonants.includes(key[0])); |
|
||||||
assert.ok(consonants.includes(key[2])); |
|
||||||
assert.ok(vowels.includes(key[1])); |
|
||||||
} else { |
|
||||||
assert.ok(vowels.includes(key[0])); |
|
||||||
assert.ok(vowels.includes(key[2])); |
|
||||||
assert.ok(consonants.includes(key[1])); |
|
||||||
} |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
@ -1,24 +0,0 @@ |
|||||||
/* global describe, it */ |
|
||||||
|
|
||||||
const assert = require('assert'); |
|
||||||
|
|
||||||
const Generator = require('../../lib/key_generators/random'); |
|
||||||
|
|
||||||
describe('RandomKeyGenerator', () => { |
|
||||||
describe('generation', () => { |
|
||||||
it('should return a key of the proper length', () => { |
|
||||||
const gen = new Generator(); |
|
||||||
assert.equal(gen.createKey(6).length, 6); |
|
||||||
}); |
|
||||||
|
|
||||||
it('should use a key from the given keyset if given', () => { |
|
||||||
const gen = new Generator({keyspace: 'A'}); |
|
||||||
assert.equal(gen.createKey(6), 'AAAAAA'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('should not use a key from the given keyset if not given', () => { |
|
||||||
const gen = new Generator({keyspace: 'A'}); |
|
||||||
assert.ok(!gen.createKey(6).includes('B')); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
@ -1,54 +0,0 @@ |
|||||||
/* global it, describe, afterEach */ |
|
||||||
|
|
||||||
var assert = require('assert'); |
|
||||||
|
|
||||||
var winston = require('winston'); |
|
||||||
winston.remove(winston.transports.Console); |
|
||||||
|
|
||||||
var RedisDocumentStore = require('../lib/document_stores/redis'); |
|
||||||
|
|
||||||
describe('redis_document_store', function() { |
|
||||||
|
|
||||||
/* reconnect to redis on each test */ |
|
||||||
afterEach(function() { |
|
||||||
if (RedisDocumentStore.client) { |
|
||||||
RedisDocumentStore.client.quit(); |
|
||||||
RedisDocumentStore.client = false; |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
describe('set', function() { |
|
||||||
|
|
||||||
it('should be able to set a key and have an expiration set', function(done) { |
|
||||||
var store = new RedisDocumentStore({ expire: 10 }); |
|
||||||
store.set('hello1', 'world', function() { |
|
||||||
RedisDocumentStore.client.ttl('hello1', function(err, res) { |
|
||||||
assert.ok(res > 1); |
|
||||||
done(); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
it('should not set an expiration when told not to', function(done) { |
|
||||||
var store = new RedisDocumentStore({ expire: 10 }); |
|
||||||
store.set('hello2', 'world', function() { |
|
||||||
RedisDocumentStore.client.ttl('hello2', function(err, res) { |
|
||||||
assert.equal(-1, res); |
|
||||||
done(); |
|
||||||
}); |
|
||||||
}, true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('should not set an expiration when expiration is off', function(done) { |
|
||||||
var store = new RedisDocumentStore({ expire: false }); |
|
||||||
store.set('hello3', 'world', function() { |
|
||||||
RedisDocumentStore.client.ttl('hello3', function(err, res) { |
|
||||||
assert.equal(-1, res); |
|
||||||
done(); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
}); |
|
||||||
|
|
||||||
}); |
|
Loading…
Reference in new issue