Compare commits

...

240 Commits

Author SHA1 Message Date
Ceda EI 9b59c4c89b Merge upstream changes 2022-02-06 00:05:12 +05:30
Konstantin Rybakov 00d84614c2
Merge pull request #389 from Lockszmith/Fix-Dockerfile
Fixes #376 + .dockerignore improvement
2022-01-12 18:04:00 +03:00
Konstantin Rybakov 52e7cef7ef
Merge pull request #402 from Sueqkjs/master
fixed spell
2022-01-12 17:08:29 +03:00
Sueqkjs fbff1bc201
fixed spell
I fixed the spelling because it didn't work~ (crying)
2021-12-27 22:11:12 +09:00
Denis Berezin 7af15cc32d
Merge pull request #384 from brunosaboia/remove-trailing-spaces
Remove trailing whitespaces
2021-11-22 17:44:40 +03:00
Denis Berezin 7f397ce753
Merge pull request #398 from toptal/npm-audit-issues
Fix high severity npm issues
2021-11-22 17:35:14 +03:00
Denis Berezin 8f8b039f65 Fix high severity npm issues 2021-11-22 17:33:31 +03:00
Denis Berezin eeaf2d7b18
Merge pull request #393 from jmartin84/391
fixed STORAGE_USERNAME typo in dockerfile
2021-11-22 17:20:06 +03:00
Justen Martin db0b7d6444
fixed STORAGE_USERNAME typo in dockerfile 2021-10-06 22:40:46 -05:00
lksz db6e7603f9 Fixes #376 + .dockerignore improvement 2021-09-10 01:02:26 -04:00
Ceda EI 20fb7f9bc2 Merge upstream new version 2021-08-25 00:16:57 +05:30
Bruno Saboia ad5d7549d7 Remove trailing whitespaces
Dockerfile contained useless trailing whitespaces, which could generate
"ghost" diffs.
2021-07-14 11:00:42 +02:00
Ceda EI 991f26e871 Serve plain text if Accept header isn't set 2020-10-23 15:18:37 +05:30
Ceda EI e2293900de Return text/plain if html isn't explicitly specified in Accept header 2020-10-23 15:10:05 +05:30
John Crepezzi 5d2965ffc5
Merge pull request #350 from seejohnrun/specify-config-on-boot
Allow setting config.js alternative on boot
2020-10-06 22:15:59 -04:00
John Crepezzi f255928af7 Allow setting config.js alternative on boot
Closes #105
2020-10-06 22:15:13 -04:00
John Crepezzi a108dbadc5
Merge pull request #349 from seejohnrun/add-head-support
Add support for HEAD requests
2020-10-06 22:07:19 -04:00
John Crepezzi c409aca080 Add support for HEAD requests
On regular document endpoints, and on raw endpoints
2020-10-06 22:05:22 -04:00
John Crepezzi 219424550b Fix local name 2020-10-06 21:02:16 -04:00
John Crepezzi f147acb51c Switch to using pg.Pool 2020-10-06 21:01:14 -04:00
John Crepezzi 9a692ed652 Get the client working as expected with pg 8 2020-10-06 21:01:10 -04:00
John Crepezzi 3a17c86a0f Upgrade pg to the most recent version
This isn't going to actually work yet, just getting things in place
2020-10-06 21:01:01 -04:00
John Crepezzi 677a22987a
Merge pull request #122 from Roundaround/mongodb
Added mongodb document store adapter
2020-10-06 01:46:59 -04:00
John Crepezzi 89d912c6ff
Merge pull request #347 from seejohnrun/fix-memcached
Fix memcached client fetch for key not found
2020-10-06 01:39:52 -04:00
John Crepezzi 4cac6713ef Fix memcached client fetch for key not found
The memcached client wasn't correctly handling looking up a key that
didn't exist.  Now we only try to push the expiration forward if there
is actually a value in memcached.

Also while I'm in here, allow expiration to be left blank.
2020-10-06 01:36:46 -04:00
John Crepezzi f3b0de745b
Merge pull request #200 from kevinhaendel/master
Added "user-select" option to line numbers & messages
2020-10-06 01:21:16 -04:00
John Crepezzi cc8a99752f
Merge pull request #271 from mklkj/fix-content-type
Fix contentType header in save request
2020-10-06 01:18:34 -04:00
John Crepezzi 6853d077e7 Merge branch 'master' into fix-content-type 2020-10-06 01:18:22 -04:00
John Crepezzi 80a2b6f0dd
Merge pull request #241 from meseta/master
Add Google Datastore sorage handler
2020-10-06 01:15:24 -04:00
John Crepezzi 4f68b3d7d6 Merge branch 'master' into meseta/master 2020-10-06 01:13:07 -04:00
John Crepezzi ef0ca40533 Downgrade pg for now
Will make a PR to use the new APIs soon
2020-10-06 00:54:12 -04:00
John Crepezzi f372ef18de
Merge pull request #345 from seejohnrun/fix-json-highlighting
Use the now-separate json mode for json highlighting
2020-10-06 00:37:21 -04:00
John Crepezzi 181a3a2bfa Use the now-separate json mode for json highlighting
Closes #267
2020-10-06 00:36:41 -04:00
John Crepezzi 61d08afb3b
Merge pull request #344 from seejohnrun/upgrade-highlight-js
Update highlight JS to the most recent version (10.2.1)
2020-10-06 00:10:59 -04:00
John Crepezzi 1ba025328d Update highlight JS to the most recent version 2020-10-06 00:07:07 -04:00
John Crepezzi a79fb39f54 Merge branch 'master' of github.com:seejohnrun/haste-server 2020-10-05 23:52:13 -04:00
John Crepezzi 3a72d74537 Fix security vulnerabilities from outdated packages
Closes #258
2020-10-05 23:50:28 -04:00
John Crepezzi e9ae74b7a9
Merge pull request #322 from sethsmoe/patch-1
remove 1px margin from textarea, fixes useless scrollbar
2020-09-22 15:42:35 -04:00
John Crepezzi c305e9a83d
Merge pull request #342 from seejohnrun/dependabot/npm_and_yarn/bl-4.0.3
Bump bl from 4.0.2 to 4.0.3
2020-09-22 15:41:12 -04:00
dependabot[bot] 16bce4c83d
Bump bl from 4.0.2 to 4.0.3
Bumps [bl](https://github.com/rvagg/bl) from 4.0.2 to 4.0.3.
- [Release notes](https://github.com/rvagg/bl/releases)
- [Commits](https://github.com/rvagg/bl/compare/v4.0.2...v4.0.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-22 19:40:54 +00:00
John Crepezzi 661997cd73
Merge pull request #334 from ourforks/master
[Security] Update dependencies to reduce risk
2020-09-22 15:40:27 -04:00
John Crepezzi 159f989d08
Merge pull request #335 from emillen/docker-support
Docker support
2020-09-22 15:39:20 -04:00
emil-lengman 139df62ec4 add newline to stop github complaining 2020-08-22 22:27:05 +02:00
emil-lengman bae6387bb7 forgot to rename some vars 2020-08-22 22:25:10 +02:00
emil-lengman bb7b9571a7 write some documentation for the Docker solution 2020-08-22 22:22:08 +02:00
emil-lengman a4dc29fb2b its supposed to be milliseconds 2020-08-22 22:12:54 +02:00
emil-lengman 342f56ce1a use same password and username env vars for all types 2020-08-22 21:56:58 +02:00
emil-lengman 05ecc90764 add file path 2020-08-22 21:47:48 +02:00
emil-lengman 69cf505a90 remove pg connect string, add rethink user and password 2020-08-22 21:29:41 +02:00
emil-lengman 9f41993566 also install rethinkdb and aws-sdk 2020-08-22 21:06:02 +02:00
emil-lengman 5c9311fb85 remove unused import 2020-08-22 20:56:45 +02:00
emil-lengman 5a8d52a5e3 add healthcheck, and stopsignal, plus export the correct port 2020-08-22 20:56:11 +02:00
emil-lengman 0f145b4444 pin versions 2020-08-22 20:48:32 +02:00
emil-lengman aef4bb5edb add dockerignore file 2020-08-22 20:45:27 +02:00
emil-lengman 36c854ef1b move creating the config file to a js file 2020-08-22 20:44:32 +02:00
emil-lengman edd428ff37 fix some names for env vars 2020-08-22 20:43:39 +02:00
emil-lengman 0612ba001e basic docker-compose for running the project together with memcached 2020-08-22 17:33:40 +02:00
emil-lengman 064680003d basic dockerfile with default env vars 2020-08-22 17:33:19 +02:00
emil-lengman 655f2af45a script for turning env-vars into config.js 2020-08-22 17:33:01 +02:00
Reece Dunham ce03749c2f Update dependencies to reduce security risk
Signed-off-by: Reece Dunham <me@rdil.rocks>
2020-08-12 23:45:18 +00:00
Ceda EI 24ed412f50 Redo the UI. Light Theme. 2020-07-16 17:25:37 +05:30
Ceda EI 7bd0fcc621 Turn background light. 2020-07-15 20:47:26 +05:30
Ceda EI e718672b58 Use tomorrow instead of tomorrow-night-bright. 2020-07-15 18:14:33 +05:30
epdn f6084b4339
remove 1px margin from textarea, fixes useless scrollbar 2020-05-18 09:34:57 +01:00
John Crepezzi 9b0a5ff0a3
Merge pull request #291 from j3parker/s3-document-store
Add an Amazon S3 document store
2020-02-27 11:39:12 -05:00
Ceda EI a4ad0e1fa6 Update about.md 2019-07-27 20:41:23 +05:30
Ceda EI af28e0c5d9 [Breaking] Change config.js to config.json 2019-07-27 20:40:50 +05:30
Ceda EI 240a9f7fde Serve local jquery. Use tomorrow-night-bright instead of solarized_dark 2019-07-17 16:48:33 +05:30
Ceda EI dca3237a71 Change the look. 2019-07-17 16:38:49 +05:30
Ceda EI a541630848 Add /:id/raw 2019-07-17 16:38:13 +05:30
Ceda EI e067323714 Change about.md 2019-07-17 16:30:37 +05:30
Jacob Parker 1fff48568f Document the IAM permissions 2019-07-08 16:59:04 +01:00
Jacob Parker b4c666fbcf Add an Amazon S3 document store 2019-06-28 19:25:49 +01:00
John Crepezzi b866c33c93
Merge pull request #173 from sebastiansterk/master
removed padding for #box for correct view
2019-04-05 17:30:42 -04:00
Mikołaj Pich 035cf0e91e
Fix content type 2018-12-22 15:11:37 +01:00
John Crepezzi f3838ab4a8
Merge pull request #251 from seejohnrun/handle-redis-disconnect
Handle redis error and re-establish connection
2018-09-19 10:38:34 -04:00
John Crepezzi bf2b1c957a Handle redis error and re-establish connection 2018-09-19 10:37:34 -04:00
Yuan Gao 86bbc1899d
Update README.md 2018-09-01 21:12:30 +01:00
Yuan Gao d41d7491d4
rename to google-datastore, and use Date.now() 2018-09-01 21:11:58 +01:00
Yuan 5fb43eb67c added condition for this.expire not defined 2018-08-28 01:28:26 +01:00
Yuan 1eeef4ede4 restored using null 2018-08-28 01:21:37 +01:00
Yuan ebc749c5e0 updated readme 2018-08-28 00:37:21 +01:00
Yuan b0bbb72f35 updated to use Date(null) 2018-08-28 00:35:09 +01:00
Yuan 2213c3874a updated readme 2018-08-27 23:48:19 +01:00
Yuan 6ebd72a86c updated readme 2018-08-27 23:34:56 +01:00
Yuan b6814a1445 bugfixes 2018-08-27 23:15:02 +01:00
Yuan e3d18efdc6 added npm package 2018-08-27 23:01:37 +01:00
Yuan 869fb65738 added googledatastore handler 2018-08-27 22:59:05 +01:00
Yuan Gao 56b939124e
Merge pull request #2 from seejohnrun/master
update from source
2018-08-27 22:58:29 +01:00
John Crepezzi ee1c1c0856
Merge pull request #231 from seejohnrun/ensure-raw-utf8
Added charset to raw content type
2018-07-12 14:26:05 -04:00
John Crepezzi b087ac8dd1 Added charset to raw content type
Closes #230
2018-07-12 14:25:27 -04:00
John Crepezzi d922667f56
Merge pull request #221 from PassTheMayo/patch-1
Fixed unnecessary logging when document not found
2018-04-30 19:45:16 -04:00
Jacob Gunther 5f6fefa7a6
Fixed unnecessary logging when document not found 2018-04-30 16:40:28 -05:00
John Crepezzi faa7e679ca
Merge pull request #216 from PassTheMayo/master
Fixed RethinkDB document store
2018-04-16 11:53:28 -04:00
Jacob Gunther cd3bf26dbe
Use local method for md5 2018-04-16 10:52:53 -05:00
Jacob Gunther 830dc1bc43 Use uploads table 2018-04-15 23:16:39 -05:00
Jacob Gunther dc0f151a7f Fixed bug in RethinkDB document store and use classes 2018-04-15 23:16:08 -05:00
John Crepezzi 7f625e22f7
Merge pull request #203 from seejohnrun/rewrite_memcached
Rewrite the memcached client
2018-04-13 16:10:42 -04:00
John Crepezzi 528b7b07a8
Merge pull request #215 from Razzeee/patch-1
Update docs to real defaults
2018-04-09 13:08:36 -04:00
Razzeee 2b81e67ce7
Update docs to real defaults 2018-04-09 19:05:21 +02:00
John Crepezzi 827e7b51b5 Rewrite the memcached client
* Update syntax to ES6
* Use `memcached` instead of `memcache`
* Fix restrictions where expirations weren't pushed forward on GET
* Fix a bug where we were unnecessarily bumping expirations on key search

Closes #201
2018-02-16 09:52:44 -05:00
Kevin Händel 16d529e935
Added "user-select" option to line numbers & messages
This prevents copying unnecessary text after selecting it via Ctrl + A
2018-02-11 00:35:27 +01:00
John Crepezzi ad7702aaf4
Merge pull request #194 from szepeviktor/patch-1
Change to HTTPS in about.md
2018-01-22 10:30:12 -05:00
Viktor Szépe f5fbc8d19e
Change to HTTPS in about.md
Please also change the repo URL here on GitHub
2018-01-22 14:51:15 +01:00
John Crepezzi 0a8923bf12
Merge pull request #192 from C0rn3j/master
Add note about paste expiration, cosmetic fixes.
2017-12-30 20:24:01 -05:00
Martin 4d572a2ec0
convert relative path to absolute 2017-12-30 17:11:29 +01:00
Martin d9a53d3e6e
Add note about paste expiration, cosmetic fixes. 2017-12-29 20:42:11 +01:00
John Crepezzi 8da37ea5de
Merge pull request #190 from PassTheMayo/patch-1
Oh noes! I didn't even notice that I had a typo...
2017-12-11 10:51:12 -05:00
Jacob Gunther ff0fccd6c2
Oh noes! I didn't even notice that I had a typo... 2017-12-11 09:50:50 -06:00
John Crepezzi 63c4576633
Merge pull request #189 from PassTheMayo/master
Added RethinkDB storage option & fixed config to use proper JSON
2017-12-11 10:46:45 -05:00
Jacob Gunther b31d143bcd
Revert config.js to previous state 2017-12-11 09:45:37 -06:00
Jacob Gunther 0d8aec8d61
Oops, forgot to fix that file name 2017-12-11 09:44:23 -06:00
Jacob Gunther 1f9fdd205d
Undid changes to server.js 2017-12-11 09:28:27 -06:00
Jacob Gunther cdd0cf3739
Fixed requested changes to RethinkDB handler 2017-12-11 09:27:44 -06:00
PassTheMayo ba5c6b8d16 Added RethinkDB storage option & fixed config to use proper JSON 2017-12-09 18:34:00 -06:00
John Crepezzi cfef588283
Merge pull request #181 from seejohnrun/simplify_uglify
Upgrade uglify and simplify usage
2017-10-31 21:20:23 -04:00
John Crepezzi 318c5f7ba6 Upgrade uglify and simplify usage
- Upgrade to the most recent version of uglify
- Use the `UglifyJS.minify(code)` helper which does exactly what we want
2017-10-31 21:19:22 -04:00
John Crepezzi ee03e7cd78
Merge pull request #180 from seejohnrun/es6_generators
ES6 generators
2017-10-31 21:11:32 -04:00
John Crepezzi 3b6934e348 Phonetic key generator to es6 and add some tests 2017-10-31 21:10:25 -04:00
John Crepezzi f161cc33b4 Added tests and converted dictionary key generator to es6 2017-10-31 20:55:59 -04:00
John Crepezzi 40f1f2588e Update some es6 2017-10-31 20:55:51 -04:00
John Crepezzi e4e025f67e Convert random generator to es6 and add some specs for it directly 2017-10-31 20:40:43 -04:00
John Crepezzi e12805a8aa
Merge pull request #179 from seejohnrun/upgrade_testing
Upgrade testing libraries
2017-10-31 20:04:37 -04:00
John Crepezzi e76c845f16 Upgrade testing libraries
- Upgrade mocha
- Remove should due to limited usage and old style (at least by rspec standards)
- Move spec -> test which is the default
- Update tests accordingly for the above
2017-10-31 20:03:30 -04:00
John Crepezzi 072418695e
Merge pull request #178 from seejohnrun/upgrade_highlight
Upgrade highlight.js
2017-10-31 19:49:20 -04:00
John Crepezzi 584b66bc66 Upgrade highlight.js 2017-10-31 19:48:55 -04:00
Sebastian Sterk f8db455f74 removed padding for #box for correct view 2017-10-13 15:13:04 +02:00
John Crepezzi f19c5d1049 Fix typo in README
Closes #165
2017-08-03 11:45:51 +00:00
John Crepezzi c5b859ec98 Bump node engine version & fix asset compression on start 2017-07-11 21:13:33 -04:00
John Crepezzi 2ee93a7409 Merge pull request #160 from seejohnrun/complete_eslint
Fix eslint
2017-06-26 12:38:57 -04:00
John Crepezzi bf1dbb68b8 Fix eslint 2017-06-26 12:38:17 -04:00
John Crepezzi cf28e23d8e Merge pull request #159 from seejohnrun/add_eslint
Added eslint and fixed an issue from #158
2017-06-26 12:29:10 -04:00
John Crepezzi 5939dec185 Added eslint and fixed an issue from #158 2017-06-26 12:19:36 -04:00
John Crepezzi 3ed1d775ac Merge pull request #158 from KlasafGeijerstam/master
Added dictionary key generator
2017-06-26 12:11:44 -04:00
John Crepezzi 87b1c76aaf One more 2017-06-26 12:11:19 -04:00
John Crepezzi 4599203bdf A few style nit-picks 2017-06-26 12:10:57 -04:00
Klas af Geijerstam d66bc9a6c4 Removed unused lines 2017-06-26 18:09:13 +02:00
Klas af Geijerstam 80f0618736 Updated dictionary.js
Now expects a newline separated dictionary, supports both \n and \n\r
2017-06-26 18:03:18 +02:00
Klas af Geijerstam ac2bceefbb Added missing ) 2017-06-26 17:42:24 +02:00
Klas af Geijerstam dbf4f6b5dd Removed usage of random-js
Replaced random-js with vanilla JS random
2017-06-26 17:39:32 +02:00
Klas af Geijerstam 8e9205cecc Update dictionary.js 2017-06-26 17:37:04 +02:00
Klas af Geijerstam e54a860172 Added dictionary.js
A key generator that uses a dictionary to create its keys
2017-06-26 17:17:52 +02:00
John Crepezzi 5a8697cdd8 Fix typo in about.md 2017-05-02 17:32:34 -04:00
John Crepezzi 091ea973a8 Merge pull request #138 from Wohlstand/patch-2
Added a note about Redis's password field
2017-04-21 07:51:23 -04:00
John Crepezzi 939b7221ab Merge pull request #146 from seejohnrun/dont_expire_static_documents
Don't expire static documents on raw read
2017-01-28 11:58:12 -05:00
John Crepezzi 934aaf7f51 Don't expire static documents on raw read 2017-01-28 11:57:25 -05:00
Vitaly Novichkov 930e21ccb7 Added a note about Redis's password field 2016-11-06 02:25:51 +04:00
John Crepezzi eb5c8eef6a Merge pull request #135 from seejohnrun/horizontal_scroll_fix
Fix horizontal scroll overflow
2016-09-30 10:40:15 -05:00
John Crepezzi 03dd611a86 Fix horizontal scroll overflow
Caused by highlight.js upgrade
2016-09-30 09:57:46 -05:00
John Crepezzi f24376b192 Merge pull request #129 from Wohlstand/patch-1
Fix working error under Firefox
2016-09-19 09:36:56 -04:00
Vitaly Novichkov eea359d0ec Update index.html
Short charset command better for HTML5
2016-09-13 15:11:20 +04:00
John Crepezzi af9a71549b Upgrade highlight.js
Also adds swift support

Closes #69
2016-09-12 20:10:15 -04:00
John Crepezzi 3178676fba Merge pull request #131 from seejohnrun/update_doc
Update documentation to mention redis-server
2016-08-22 13:23:11 -04:00
John Crepezzi 3bdfab8219 Update documentation to mention redis-server 2016-08-22 13:22:49 -04:00
John Crepezzi a3a24d9765 Merge pull request #127 from Gwemox/master
Compatibility with screen reader
2016-08-17 14:03:13 -04:00
Vitaly Novichkov 8afb53e77e Fix working error under Firefox
On some computers because undeclared charset, highlight.min.js parsing incorrectly while some charsets (For example, Cyrillic-1251) are toggled. Problem is going away when I manually toggling Europan or UTF-8 charset.
2016-08-03 14:15:58 +04:00
Thibault Buathier d6d9cf40f9 Compatibility with screen reader 2016-07-30 16:15:53 +02:00
Thibault Buathier 1010a142e2 Compatibility with screen reader 2016-07-30 16:15:26 +02:00
Evan Steinkerchner d3db5e2a5d Added mongodb document store adapter 2016-06-10 16:43:43 -04:00
John Crepezzi 0209375865 Revert "Added cloudron external provider"
This reverts commit 00a9d9c312.
2016-03-12 13:09:28 -05:00
John Crepezzi 00a9d9c312 Added cloudron external provider 2016-03-12 13:07:58 -05:00
John Crepezzi 6835eef468 Merge pull request #109 from seejohnrun/rate_limiting
Added user-configurable rate limiting
2016-03-10 11:44:57 -10:00
John Crepezzi 4626fd9c8d Merge pull request #81 from pangeacake/master
Add postgres information to README.md
2016-03-06 19:06:04 -05:00
John Crepezzi fbb6e63c37 Added a note to the README 2016-03-06 16:34:33 -05:00
John Crepezzi 84c909a5db Added user-configurable rate limiting 2016-03-06 16:20:40 -05:00
John Crepezzi 45e19bc7cc fix indentation 2015-12-27 12:59:59 -05:00
John Crepezzi 233bc6ff16 Merge pull request #77 from abn/redis-auth
Support authentication for redis store if password provided
2015-12-27 12:58:24 -05:00
PangeaCake 360b325ced Smaller typo 2015-01-07 14:30:12 -08:00
PangeaCake e93f98112b Add `pg` as dependency and update node version
One of the dependencies seemed to be broken with the previous node version, but this node version worked perfectly
2015-01-07 14:27:46 -08:00
PangeaCake 05cb051bc8 Tiny fix 2015-01-07 14:25:43 -08:00
PangeaCake 031cdd738a Add postgres information to README.md 2015-01-07 14:24:50 -08:00
Arun Babu Neelicattu c92ab077c0 Support authentication for redis store if password provided 2014-11-21 23:17:19 +10:00
John Crepezzi 6c31389327 Merge pull request #64 from lidl/master
Make table creation comment a one-liner.
2014-07-16 07:54:00 -04:00
lidl a8d4f3c300 Make table creation comment a one-liner. 2014-06-27 18:40:05 -04:00
John Crepezzi ab029eae2f Added postgres adapter 2014-06-09 16:50:43 -04:00
John Crepezzi 447d0aae76 PG basis 2014-06-09 14:48:35 -04:00
John Crepezzi 4870158430 Merge branch 'master' of github.com:seejohnrun/haste-server 2014-04-21 14:17:09 -04:00
John Crepezzi 0471b059a0 Support a form-data POST API
Closes #54
2014-04-21 14:16:23 -04:00
John Crepezzi 5bbe50b481 Merge pull request #59 from jomo/phonetic
Phonetic key improvements
2014-04-15 11:00:49 -04:00
JonApps bda2749879 oops 🍺 2014-03-25 02:23:31 +01:00
JonApps 028aa96b13 phonetic keys can begin with vowel + added missing \'z\' to consontants 2014-03-25 02:20:05 +01:00
John Crepezzi 2deda5b68a Merge pull request #56 from joeykrim/patch-1
Small typo
2014-02-27 19:26:31 -05:00
joeykrim ee7098457e Small typo
Changed "its all open source" to "it's all open source"
2014-02-27 17:57:37 -05:00
John Crepezzi 7a08960414 Merge branch 'master' of github.com:seejohnrun/haste-server 2013-11-24 11:54:34 -05:00
John Crepezzi 89909747f1 Don't depend on err.message for redis errors [#49] 2013-11-24 11:54:01 -05:00
John Crepezzi 202e695e07 Remove GA from index.html on Master 2013-10-31 08:44:33 -04:00
John Crepezzi 48e8e79659 Remove support from README 2013-08-13 13:06:37 -04:00
John Crepezzi abb49f2cf3 update about.md 2013-03-12 21:59:10 -04:00
John Crepezzi d1cd2a5213 Proper 2013-01-11 10:00:35 -08:00
John Crepezzi 27317844e0 Remove nl 2013-01-11 09:57:10 -08:00
John Crepezzi ee74e2fa90 Merge branch 'production' of github.com:seejohnrun/haste-server into production 2012-12-29 18:15:45 -05:00
John Crepezzi 5d8bd2e6f8 Merge branch 'master' into production 2012-12-29 18:15:32 -05:00
John Crepezzi cd4c7aeab8 Merge pull request #37 from naftis/patch-1
Bugfix to solarized_dark.css
2012-12-28 07:10:36 -08:00
naftis e37c3cf1b9 Bugfix to solarized_dark.css
Fixed bug with font-style: italic and WebKit.
WebKit makes line-height bigger, when italic is used.
2012-12-28 16:11:21 +02:00
John Crepezzi 8858bab985 Merge branch 'master' into production 2012-12-23 10:54:15 -05:00
John Crepezzi afb0c332cc Added shift modifier to twitter shortcut
Closes #29
2012-12-23 10:53:53 -05:00
John Crepezzi 82c58c5c0c Merge branch 'master' into production 2012-12-19 08:18:14 -05:00
John Crepezzi 46bdd27431 Fix for type name ;)
Closes #28
2012-12-19 08:17:52 -05:00
John Crepezzi 1adfba1a37 Merge branch 'master' into production 2012-12-19 08:13:36 -05:00
John Crepezzi 54e55b1b0d Added JSON to extension map (JS)
Closes #28
2012-12-19 08:12:08 -05:00
John Crepezzi 08d37cc7f7 Added support section 2012-10-22 14:40:52 -04:00
John Crepezzi aa781957e8 Update Copyright 2012-09-27 13:46:27 -04:00
John Crepezzi c00477c93c Update Copyright 2012-09-27 13:46:09 -04:00
John Crepezzi 035f09ac05 GA 2012-09-27 13:43:53 -04:00
John Crepezzi 36e00bb29e Remove 'localhost' references 2012-09-27 12:03:52 -04:00
John Crepezzi 10623873e8 Allow host setting by ENV 2012-09-27 12:01:00 -04:00
John Crepezzi e536ba1019 Move to an available npm version 2012-09-27 11:56:49 -04:00
John Crepezzi 85fc36d710 Update npm version 2012-09-27 11:56:15 -04:00
John Crepezzi 5d5ae164f3 Set up node engine version 2012-09-27 11:54:40 -04:00
John Crepezzi 79309c75df Bump version to 0.1.0 2012-09-27 11:51:15 -04:00
John Crepezzi 4b58c8d356 Added more loggin 2012-09-27 11:50:56 -04:00
John Crepezzi 8f0d6260b0 change how redistogo install works 2012-09-27 11:50:12 -04:00
John Crepezzi 93a83a35da Logging 2012-09-27 11:47:23 -04:00
John Crepezzi 4efc5d47d9 Allow redistogo 2012-09-27 11:46:53 -04:00
John Crepezzi ff8ef54e34 Procfile 2012-09-27 11:38:14 -04:00
John Crepezzi 814a49812a Update server config path 2012-09-19 14:28:52 -04:00
John Crepezzi e0610bc1be Fix multiple document loading
Closes #32
2012-08-13 11:33:20 -04:00
John Crepezzi 962976c204 Pad the right 2012-06-22 15:33:07 -04:00
John Crepezzi 16080bdc16 Update description - preparing for npm push 2012-04-21 23:49:39 -04:00
John Crepezzi 20ce741341 Fix indentation 2012-04-07 23:51:48 -04:00
John Crepezzi 13bb094fb3 Revert "Refactor frontend"
This reverts commit 1950cc8db0.
2012-03-19 18:17:39 -04:00
John Crepezzi b43a55ffda Merge pull request #25 from zaeleus/backbone
Refactor frontend
2012-03-19 15:11:08 -07:00
John Crepezzi 45cbdcce70 Force down connect version 2012-03-02 14:07:59 -05:00
Michael Macias 1950cc8db0 Refactor frontend
* restructured JavaScript using backbone.js
* replaced highlight.js with CodeMirror for its editor
* added CodeMirror Solarized (dark) theme based on Ethan Schoonover's solarized.vim
* changed `POST /document` to accept real JSON
* cleaned up template and stylesheet
2012-02-18 02:40:56 -06:00
John Crepezzi 90cfe0ec57 Upgrade jquery to 1.7.1 2012-02-07 17:52:48 -05:00
John Crepezzi 87e28548b9 Explicitly set encoding
Closes #24
2012-02-07 17:52:31 -05:00
John Crepezzi ca9d4c18f7 Added a note on WinHaste to about.md 2012-01-27 09:47:56 -05:00
John Crepezzi f147c05ff1 Add more detail to README.md 2012-01-27 09:46:24 -05:00
John Crepezzi 7a2851aeeb Merge branch 'master' of github.com:seejohnrun/haste-server 2012-01-24 22:29:38 -05:00
John Crepezzi ef96704f2c Basic coffeescript highlighting
From https://github.com/dnagir/highlightjs-coffeescript

Closes #2
2012-01-24 22:28:58 -05:00
John Crepezzi 8b7ac341e7 FileDocumentStore fix
Closes #20
2012-01-24 00:01:38 -05:00
John Crepezzi 713914aecd Merge pull request #18 from tfausak/patch-1
Fix typo in about.md ("easist" -> "easiest").
2012-01-23 19:27:24 -08:00
Taylor Fausak 36e7a43c14 Fix typo in about.md ("easist" -> "easiest"). 2012-01-23 20:12:33 -06:00
John Crepezzi 047a686248 Open raw in same window 2012-01-23 11:40:09 -05:00
John Crepezzi 347ba57ad3 Merge branch 'raw_icon' 2012-01-23 11:38:36 -05:00
Brian Dawson d2bd78956b Merge branch 'master' of github.com:seejohnrun/haste-server
Conflicts:
	static/function-icons.png
2012-01-22 23:42:41 -05:00
Brian Dawson 1fe81d2447 update function icons 2012-01-22 23:41:40 -05:00
John Crepezzi f37d1ad401 Clean up some code and move document stores into subfolder 2012-01-21 15:19:55 -05:00
John Crepezzi 483fce891d Added memcached support 2012-01-17 14:43:33 -05:00
John Crepezzi 6e4c087319 Remove hashlib dependency and switch to mocha for testing 2012-01-13 11:17:15 -05:00
45 changed files with 3273 additions and 519 deletions

8
.dockerignore Normal file
View File

@ -0,0 +1,8 @@
Dockerfile
.git
npm-debug.log
node_modules
*.swp
*.swo
data
*.DS_Store

2
.eslintignore Normal file
View File

@ -0,0 +1,2 @@
**/*.min.js
config.js

25
.eslintrc.json Normal file
View File

@ -0,0 +1,25 @@
{
"env": {
"es6": true,
"node": true
},
"extends": "eslint:recommended",
"rules": {
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
}
}

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ node_modules
*.swo *.swo
data data
*.DS_Store *.DS_Store
config.json

68
Dockerfile Normal file
View File

@ -0,0 +1,68 @@
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
Procfile Normal file
View File

@ -0,0 +1 @@
web: node server.js

316
README.md
View File

@ -1,6 +1,9 @@
# Haste # 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) 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: Major design objectives:
@ -8,11 +11,14 @@ Major design objectives:
* Be really simple * Be really simple
* Be easy to set up and use * Be easy to set up and use
Haste works really well with a little utility called haste-client, allowing you to do things like: 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` `cat something | haste`
which will output a URL to share containing the contents of `cat something`'s STDOUT 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 ## Tested Browsers
@ -25,20 +31,31 @@ which will output a URL to share containing the contents of `cat something`'s ST
1. Download the package, and expand it 1. Download the package, and expand it
2. Explore the settings inside of config.js, but the defaults should be good 2. Explore the settings inside of config.js, but the defaults should be good
3. `npm install` 3. `npm install`
4. `npm start` 4. `npm start` (you may specify an optional `<config-path>` as well)
## Settings ## Settings
* `host` - the host the server runs on (default localhost) * `host` - the host the server runs on (default localhost)
* `port` - the port the server runs on (default 7777) * `port` - the port the server runs on (default 7777)
* `keyLength` - the length of the keys to user (default 10) * `keyLength` - the length of the keys to user (default 10)
* `maxLength` - maximum length of a paste (default none) * `maxLength` - maximum length of a paste (default 400000)
* `staticMaxAge` - max age for static assets (86400) * `staticMaxAge` - max age for static assets (86400)
* `recompressStatisAssets` - whether or not to compile static js assets (true) * `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. * `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) * `storage` - storage options (see below)
* `logging` - logging preferences * `logging` - logging preferences
* `keyGenerator` - key generator options (see below) * `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 ## Key Generation
@ -48,7 +65,7 @@ Attempts to generate phonetic keys, similar to `pwgen`
``` json ``` json
{ {
"type": "phonetic" "type": "phonetic"
} }
``` ```
@ -58,49 +75,285 @@ Generates a random key
``` json ``` json
{ {
"type": "random", "type": "random",
"keyspace": "abcdef" "keyspace": "abcdef"
} }
``` ```
The _optional_ keySpace argument is a string of acceptable characters for the key. The _optional_ keySpace argument is a string of acceptable characters
for the key.
## Storage ## Storage
### File ### File
To use file storage (the default) change the storage section in `config.js` to something like: To use file storage (the default) change the storage section in `config.js` to
something like:
``` json ``` json
{ {
"path": "./data", "path": "./data",
"type": "file" "type": "file"
} }
``` ```
Where `path` represents where you want the files stored 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 ### Redis
To use redis storage you must install the redis package in npm globall using To use redis storage you must install the `redis` package in npm, and have
`redis-server` running on the machine.
`npm install redis --global` `npm install redis`
Once you've done that, your config section should look like: Once you've done that, your config section should look like:
``` json ``` json
{ {
"type": "redis", "type": "redis",
"host": "localhost", "host": "localhost",
"port": 6379, "port": 6379,
"db": 2 "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. 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. 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 ## Author
John Crepezzi <john.crepezzi@gmail.com> John Crepezzi <john.crepezzi@gmail.com>
@ -109,15 +362,28 @@ John Crepezzi <john.crepezzi@gmail.com>
(The MIT License) (The MIT License)
Copyright © 2011 John Crepezzi 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: 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 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 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: ### Other components:
* jQuery: MIT/GPL license * jQuery: MIT/GPL license
* highlight.js: Copyright © 2006, Ivan Sagalaev * highlight.js: Copyright © 2006, Ivan Sagalaev
* highlightjs-coffeescript: WTFPL - Copyright © 2011, Dmytrii Nagirniak

View File

@ -1 +0,0 @@
* add feedback for errors to UI - esp. too long

View File

@ -1,52 +1,33 @@
# Haste # Haste
Sharing code is a good thing, and it should be _really_ easy to do it. 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. A lot of times, I want to show you something I'm seeing - and that's where we
use pastebins.
Haste is the prettiest, easist to use pastebin ever made. Haste is the prettiest, easiest to use pastebin ever made.
## Basic Usage ## Basic Usage
Type what you want me to see, click "Save", and then copy the URL. Send that URL Type what you want me to see, click "Save", and then copy the URL. Send that
to someone and they'll see what you see. URL to someone and they'll see what you see.
To make a new entry, click "New" (or type 'control + n') To make a new entry, click "New" (or type 'control + n')
## From the Console ## From the Console
Most of the time I want to show you some text, its coming from my current console session. [bin-client](git.webionite.com/ceda_ei/bin-client)
We should make it really easy to take code from the console and send it to people.
`cat something | haste` # http://hastebin.com/1238193 Add the following to your bashrc/zshrc
```
You can even take this a step further, and cut out the last step of copying the URL with: export MKR_BIN='https://bin.webionite.com/'
export HASTEBIN=1
* osx: `cat something | haste | pbcopy` ```
* linux: `cat something | haste | xsel`
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.
## 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 ## Open Source
Haste can easily be installed behind your network, and its all 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://git.webionite.com/Webionite/haste-server)
* [haste-server](https://github.com/seejohnrun/haste-server)
## Author ## Author

19
docker-compose.yaml Normal file
View File

@ -0,0 +1,19 @@
version: '3.0'
services:
haste-server:
build: .
networks:
- db-network
environment:
- STORAGE_TYPE=memcached
- STORAGE_HOST=memcached
- STORAGE_PORT=11211
ports:
- 7777:7777
memcached:
image: memcached:latest
networks:
- db-network
networks:
db-network:

108
docker-entrypoint.js Normal file
View File

@ -0,0 +1,108 @@
const {
HOST,
PORT,
KEY_LENGTH,
MAX_LENGTH,
STATIC_MAX_AGE,
RECOMPRESS_STATIC_ASSETS,
STORAGE_TYPE,
STORAGE_HOST,
STORAGE_PORT,
STORAGE_EXPIRE_SECONDS,
STORAGE_DB,
STORAGE_AWS_BUCKET,
STORAGE_AWS_REGION,
STORAGE_PASSWORD,
STORAGE_USERNAME,
STORAGE_FILEPATH,
LOGGING_LEVEL,
LOGGING_TYPE,
LOGGING_COLORIZE,
KEYGENERATOR_TYPE,
KEY_GENERATOR_KEYSPACE,
RATE_LIMITS_NORMAL_TOTAL_REQUESTS,
RATE_LIMITS_NORMAL_EVERY_MILLISECONDS,
RATE_LIMITS_WHITELIST_TOTAL_REQUESTS,
RATE_LIMITS_WHITELIST_EVERY_MILLISECONDS,
RATE_LIMITS_WHITELIST,
RATE_LIMITS_BLACKLIST_TOTAL_REQUESTS,
RATE_LIMITS_BLACKLIST_EVERY_MILLISECONDS,
RATE_LIMITS_BLACKLIST,
DOCUMENTS,
} = process.env;
const config = {
host: HOST,
port: PORT,
keyLength: KEY_LENGTH,
maxLength: MAX_LENGTH,
staticMaxAge: 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: STORAGE_PORT,
expire: 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));

9
docker-entrypoint.sh Normal file
View File

@ -0,0 +1,9 @@
#!/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 "$@"

View File

@ -1,4 +1,5 @@
var winston = require('winston'); var winston = require('winston');
var Busboy = require('busboy');
// For handling serving stored documents // For handling serving stored documents
@ -15,60 +16,83 @@ var DocumentHandler = function(options) {
DocumentHandler.defaultKeyLength = 10; DocumentHandler.defaultKeyLength = 10;
// Handle retrieving a document // Handle retrieving a document
DocumentHandler.prototype.handleGet = function(key, response, skipExpire) { 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) { this.store.get(key, function(ret) {
if (ret) { if (ret) {
winston.verbose('retrieved document', { key: key }); winston.verbose('retrieved document', { key: key });
response.writeHead(200, { 'content-type': 'application/json' }); response.writeHead(200, { 'content-type': 'application/json' });
response.end(JSON.stringify({ data: ret, key: key })); if (request.method === 'HEAD') {
response.end();
} else {
response.end(JSON.stringify({ data: ret, key: key }));
}
} }
else { else {
winston.warn('document not found', { key: key }); winston.warn('document not found', { key: key });
response.writeHead(404, { 'content-type': 'application/json' }); response.writeHead(404, { 'content-type': 'application/json' });
response.end(JSON.stringify({ message: 'Document not found.' })); if (request.method === 'HEAD') {
response.end();
} else {
response.end(JSON.stringify({ message: 'Document not found.' }));
}
} }
}, skipExpire); }, skipExpire);
}; };
// Handle retrieving the raw version of a document // Handle retrieving the raw version of a document
DocumentHandler.prototype.handleRawGet = function(key, response, skipExpire) { 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) { this.store.get(key, function(ret) {
if (ret) { if (ret) {
winston.verbose('retrieved raw document', { key: key }); winston.verbose('retrieved raw document', { key: key });
response.writeHead(200, { 'content-type': 'text/plain' }); response.writeHead(200, { 'content-type': 'text/plain; charset=UTF-8' });
response.end(ret); if (request.method === 'HEAD') {
response.end();
} else {
response.end(ret);
}
} }
else { else {
winston.warn('raw document not found', { key: key }); winston.warn('raw document not found', { key: key });
response.writeHead(404, { 'content-type': 'application/json' }); response.writeHead(404, { 'content-type': 'application/json' });
response.end(JSON.stringify({ message: 'Document not found.' })); if (request.method === 'HEAD') {
response.end();
} else {
response.end(JSON.stringify({ message: 'Document not found.' }));
}
} }
}, skipExpire); }, skipExpire);
}; };
// Handle adding a new Document // Handle adding a new Document
DocumentHandler.prototype.handlePost = function(request, response) { DocumentHandler.prototype.handlePost = function (request, response) {
var _this = this; var _this = this;
var buffer = ''; var buffer = '';
var cancelled = false; var cancelled = false;
request.on('data', function(data) {
if (!buffer) { // What to do when done
response.writeHead(200, { 'content-type': 'application/json' }); var onSuccess = function () {
} // Check length
buffer += data.toString();
if (_this.maxLength && buffer.length > _this.maxLength) { if (_this.maxLength && buffer.length > _this.maxLength) {
cancelled = true; cancelled = true;
winston.warn('document >maxLength', { maxLength: _this.maxLength }); winston.warn('document >maxLength', { maxLength: _this.maxLength });
response.writeHead(400, { 'content-type': 'application/json' }); response.writeHead(400, { 'content-type': 'application/json' });
response.end(JSON.stringify({ message: 'Document exceeds maximum length.' })); response.end(
JSON.stringify({ message: 'Document exceeds maximum length.' })
);
return;
} }
}); // And then save if we should
request.on('end', function(end) { _this.chooseKey(function (key) {
if (cancelled) return; _this.store.set(key, buffer, function (res) {
_this.chooseKey(function(key) {
_this.store.set(key, buffer, function(res) {
if (res) { if (res) {
winston.verbose('added document', { key: key }); winston.verbose('added document', { key: key });
response.writeHead(200, { 'content-type': 'application/json' });
response.end(JSON.stringify({ key: key })); response.end(JSON.stringify({ key: key }));
} }
else { else {
@ -78,17 +102,42 @@ DocumentHandler.prototype.handlePost = function(request, response) {
} }
}); });
}); });
}); };
request.on('error', function(error) {
winston.error('connection error: ' + error.message); // If we should, parse a form to grab the data
response.writeHead(500, { 'content-type': 'application/json' }); var ct = request.headers['content-type'];
response.end(JSON.stringify({ message: 'Connection error.' })); 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;
});
}
}; };
// Get a random key that hasn't been already used // Keep choosing keys until one isn't taken
DocumentHandler.prototype.chooseKey = function(callback) { DocumentHandler.prototype.chooseKey = function(callback) {
var key = this.keyGenerator.createKey(this.keyLength); var key = this.acceptableKey();
var _this = this; var _this = this;
this.store.get(key, function(ret) { this.store.get(key, function(ret) {
if (ret) { if (ret) {
@ -96,7 +145,11 @@ DocumentHandler.prototype.chooseKey = function(callback) {
} else { } else {
callback(key); callback(key);
} }
}); }, true); // Don't bump expirations when key searching
};
DocumentHandler.prototype.acceptableKey = function() {
return this.keyGenerator.createKey(this.keyLength);
}; };
module.exports = DocumentHandler; module.exports = DocumentHandler;

View File

@ -0,0 +1,56 @@
/*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;

View File

@ -1,7 +1,7 @@
var fs = require('fs'); var fs = require('fs');
var crypto = require('crypto');
var winston = require('winston'); var winston = require('winston');
var hashlib = require('hashlib');
// For storing in files // For storing in files
// options[type] = file // options[type] = file
@ -12,12 +12,21 @@ var FileDocumentStore = function(options) {
this.expire = options.expire; this.expire = options.expire;
}; };
// Save data in a file, key as md5 - since we don't know what we could be passed here // 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) { FileDocumentStore.prototype.set = function(key, data, callback, skipExpire) {
try { try {
var _this = this; var _this = this;
fs.mkdir(this.basePath, '700', function() { fs.mkdir(this.basePath, '700', function() {
fs.writeFile(_this.basePath + '/' + hashlib.md5(key), data, 'utf8', function(err) { var fn = _this.basePath + '/' + FileDocumentStore.md5(key);
fs.writeFile(fn, data, 'utf8', function(err) {
if (err) { if (err) {
callback(false); callback(false);
} }
@ -37,7 +46,8 @@ FileDocumentStore.prototype.set = function(key, data, callback, skipExpire) {
// Get data from a file from key // Get data from a file from key
FileDocumentStore.prototype.get = function(key, callback, skipExpire) { FileDocumentStore.prototype.get = function(key, callback, skipExpire) {
var _this = this; var _this = this;
fs.readFile(this.basePath + '/' + hashlib.md5(key), 'utf8', function(err, data) { var fn = this.basePath + '/' + FileDocumentStore.md5(key);
fs.readFile(fn, 'utf8', function(err, data) {
if (err) { if (err) {
callback(false); callback(false);
} }

View File

@ -0,0 +1,89 @@
/*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;

View File

@ -0,0 +1,54 @@
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;

View File

@ -0,0 +1,88 @@
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;

View File

@ -0,0 +1,80 @@
/*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 = options.expire;
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;

View File

@ -1,6 +1,5 @@
var redis = require('redis'); var redis = require('redis');
var winston = require('winston'); var winston = require('winston');
var hashlib = require('hashlib');
// For storing in redis // For storing in redis
// options[type] = redis // options[type] = redis
@ -9,9 +8,13 @@ var hashlib = require('hashlib');
// options[db] - The db to use (default 0) // options[db] - The db to use (default 0)
// options[expire] - The time to live for each key set (default never) // options[expire] - The time to live for each key set (default never)
var RedisDocumentStore = function(options) { var RedisDocumentStore = function(options, client) {
this.expire = options.expire; this.expire = options.expire;
if (!RedisDocumentStore.client) { if (client) {
winston.info('using predefined redis client');
RedisDocumentStore.client = client;
} else if (!RedisDocumentStore.client) {
winston.info('configuring redis');
RedisDocumentStore.connect(options); RedisDocumentStore.connect(options);
} }
}; };
@ -22,9 +25,21 @@ RedisDocumentStore.connect = function(options) {
var port = options.port || 6379; var port = options.port || 6379;
var index = options.db || 0; var index = options.db || 0;
RedisDocumentStore.client = redis.createClient(port, host); RedisDocumentStore.client = redis.createClient(port, host);
RedisDocumentStore.client.select(index, function(err, reply) { // 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) { if (err) {
winston.error('error connecting to redis index ' + index, { error: err.message }); winston.error(
'error connecting to redis index ' + index,
{ error: err }
);
process.exit(1); process.exit(1);
} }
else { else {
@ -36,7 +51,7 @@ RedisDocumentStore.connect = function(options) {
// Save file in a key // Save file in a key
RedisDocumentStore.prototype.set = function(key, data, callback, skipExpire) { RedisDocumentStore.prototype.set = function(key, data, callback, skipExpire) {
var _this = this; var _this = this;
RedisDocumentStore.client.set(key, data, function(err, reply) { RedisDocumentStore.client.set(key, data, function(err) {
if (err) { if (err) {
callback(false); callback(false);
} }
@ -52,7 +67,7 @@ RedisDocumentStore.prototype.set = function(key, data, callback, skipExpire) {
// Expire a key in expire time if set // Expire a key in expire time if set
RedisDocumentStore.prototype.setExpiration = function(key) { RedisDocumentStore.prototype.setExpiration = function(key) {
if (this.expire) { if (this.expire) {
RedisDocumentStore.client.expire(key, this.expire, function(err, reply) { RedisDocumentStore.client.expire(key, this.expire, function(err) {
if (err) { if (err) {
winston.error('failed to set expiry on key: ' + key); winston.error('failed to set expiry on key: ' + key);
} }

View File

@ -0,0 +1,46 @@
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;

View File

@ -0,0 +1,32 @@
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;
}
};

View File

@ -1,32 +1,27 @@
// Draws inspiration from pwgen and http://tools.arantius.com/password // Draws inspiration from pwgen and http://tools.arantius.com/password
var PhoneticKeyGenerator = function(options) {
// No options const randOf = (collection) => {
return () => {
return collection[Math.floor(Math.random() * collection.length)];
};
}; };
// Generate a phonetic key // Helper methods to get an random vowel or consonant
PhoneticKeyGenerator.prototype.createKey = function(keyLength) { const randVowel = randOf('aeiou');
var text = ''; const randConsonant = randOf('bcdfghjklmnpqrstvwxyz');
for (var i = 0; i < keyLength; i++) {
text += (i % 2 == 0) ? this.randConsonant() : this.randVowel(); 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;
} }
return text;
}; };
PhoneticKeyGenerator.consonants = 'bcdfghjklmnpqrstvwxy';
PhoneticKeyGenerator.vowels = 'aeiou';
// Get an random vowel
PhoneticKeyGenerator.prototype.randVowel = function() {
return PhoneticKeyGenerator.vowels[
Math.floor(Math.random() * PhoneticKeyGenerator.vowels.length)
];
};
// Get an random consonant
PhoneticKeyGenerator.prototype.randConsonant = function() {
return PhoneticKeyGenerator.consonants[
Math.floor(Math.random() * PhoneticKeyGenerator.consonants.length)
];
};
module.exports = PhoneticKeyGenerator;

View File

@ -1,14 +1,20 @@
var RandomKeyGenerator = function(options) { module.exports = class RandomKeyGenerator {
this.keyspace = options.keyspace || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
};
// Generate a random key // Initialize a new generator with the given keySpace
RandomKeyGenerator.prototype.createKey = function(keyLength) { constructor(options = {}) {
var text = ''; this.keyspace = options.keyspace || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < keyLength; i++) {
text += this.keyspace.charAt(Math.floor(Math.random() * this.keyspace.length));
} }
return text;
};
module.exports = RandomKeyGenerator; // 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;
}
};

1652
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,52 +1,47 @@
{ {
"name": "haste",
"name": "haste", "version": "0.1.0",
"version": "0.0.1", "private": true,
"description": "Private Pastebin Server",
"private": true, "keywords": [
"paste",
"description": "Private Paste", "pastebin"
],
"keywords": [ "paste", "pastebin" ], "author": {
"name": "John Crepezzi",
"author": { "email": "john.crepezzi@gmail.com",
"name": "John Crepezzi", "url": "http://seejohncode.com/"
"email": "john.crepezzi@gmail.com",
"url": "http://seejohncode.com/"
},
"main": "haste",
"dependencies": {
"winston": "*",
"hashlib": "*",
"connect": "*",
"uglify-js": "*"
},
"devDependencies": {
"jasmine-node": "*"
}, },
"main": "haste",
"bundledDependencies": [], "dependencies": {
"busboy": "0.2.4",
"engines": { "connect": "^3.7.0",
"node": "*" "connect-ratelimit": "0.0.7",
}, "connect-route": "0.1.5",
"pg": "^8.0.0",
"bin": { "redis": "0.8.1",
"haste-server": "./server.js" "redis-url": "0.1.0",
}, "st": "^2.0.0",
"uglify-js": "3.1.6",
"files": [ "server.js", "lib", "static" ], "winston": "^2.0.0"
},
"directories": { "devDependencies": {
"lib": "./lib" "mocha": "^8.1.3"
}, },
"bundledDependencies": [],
"scripts": { "bin": {
"start": "node server.js", "haste-server": "./server.js"
"test": "jasmine-node spec" },
} "files": [
"server.js",
"lib",
"static"
],
"directories": {
"lib": "./lib"
},
"scripts": {
"start": "node server.js",
"test": "mocha --recursive"
}
} }

View File

@ -1,6 +1,6 @@
{ {
"host": "localhost", "host": "0.0.0.0",
"port": 7777, "port": 7777,
"keyLength": 10, "keyLength": 10,
@ -23,12 +23,17 @@
"type": "phonetic" "type": "phonetic"
}, },
"rateLimits": {
"categories": {
"normal": {
"totalRequests": 500,
"every": 60000
}
}
},
"storage": { "storage": {
"type": "redis", "type": "file"
"host": "localhost",
"port": 6379,
"db": 2,
"expire": 2592000
}, },
"documents": { "documents": {

166
server.js
View File

@ -1,22 +1,29 @@
// vim: set ts=2 sw=2 sts=2 et:
var http = require('http'); var http = require('http');
var url = require('url');
var fs = require('fs'); var fs = require('fs');
var uglify = require('uglify-js');
var winston = require('winston'); var winston = require('winston');
var connect = require('connect'); 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'); var DocumentHandler = require('./lib/document_handler');
// Load the configuration and set some defaults // Load the configuration and set some defaults
var config = JSON.parse(fs.readFileSync('config.js', 'utf8')); var config = require('./config.json');
config.port = config.port || 7777; config.port = process.env.PORT || config.port || 7777;
config.host = config.host || 'localhost'; config.host = process.env.HOST || config.host || 'localhost';
// Set up the logger // Set up the logger
if (config.logging) { if (config.logging) {
try { try {
winston.remove(winston.transports.Console); winston.remove(winston.transports.Console);
} catch(er) { } } catch(e) {
/* was not present */
}
var detail, type; var detail, type;
for (var i = 0; i < config.logging.length; i++) { for (var i = 0; i < config.logging.length; i++) {
detail = config.logging[i]; detail = config.logging[i];
@ -34,42 +41,48 @@ if (!config.storage) {
if (!config.storage.type) { if (!config.storage.type) {
config.storage.type = 'file'; config.storage.type = 'file';
} }
var Store = require('./lib/' + config.storage.type + '_document_store');
var preferredStore = new Store(config.storage); 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 // Compress the static javascript assets
if (config.recompressStaticAssets) { if (config.recompressStaticAssets) {
var jsp = require("uglify-js").parser;
var pro = require("uglify-js").uglify;
var list = fs.readdirSync('./static'); var list = fs.readdirSync('./static');
for (var i = 0; i < list.length; i++) { for (var j = 0; j < list.length; j++) {
var item = list[i]; var item = list[j];
var orig_code, ast;
if ((item.indexOf('.js') === item.length - 3) && (item.indexOf('.min.js') === -1)) { if ((item.indexOf('.js') === item.length - 3) && (item.indexOf('.min.js') === -1)) {
dest = item.substring(0, item.length - 3) + '.min' + item.substring(item.length - 3); var dest = item.substring(0, item.length - 3) + '.min' + item.substring(item.length - 3);
orig_code = fs.readFileSync('./static/' + item, 'utf8'); var orig_code = fs.readFileSync('./static/' + item, 'utf8');
ast = jsp.parse(orig_code);
ast = pro.ast_mangle(ast); fs.writeFileSync('./static/' + dest, uglify.minify(orig_code).code, 'utf8');
ast = pro.ast_squeeze(ast);
fs.writeFileSync('./static/' + dest, pro.gen_code(ast), 'utf8');
winston.info('compressed ' + item + ' into ' + dest); winston.info('compressed ' + item + ' into ' + dest);
} }
} }
} }
// Send the static documents into the preferred store, skipping expirations // Send the static documents into the preferred store, skipping expirations
var path, data;
for (var name in config.documents) { for (var name in config.documents) {
var path = config.documents[name]; path = config.documents[name];
fs.readFile(path, 'utf8', function(err, data) { data = fs.readFileSync(path, 'utf8');
if (data && !err) { winston.info('loading static document', { name: name, path: path });
preferredStore.set(name, data, function(cb) { if (data) {
winston.info('loaded static document', { name: name, path: path }); preferredStore.set(name, data, function(cb) {
}, true); winston.debug('loaded static document', { success: cb });
} }, true);
else { }
winston.warn('failed to load static document', { name: name, path: path }); else {
} winston.warn('failed to load static document', { name: name, path: path });
}); }
} }
// Pick up a key generator // Pick up a key generator
@ -86,38 +99,69 @@ var documentHandler = new DocumentHandler({
keyGenerator: keyGenerator keyGenerator: keyGenerator
}); });
// Set the server up with a static cache var app = connect();
connect.createServer(
// First look for api calls // Rate limit all requests
connect.router(function(app) { if (config.rateLimits) {
// get raw documents - support getting with extension config.rateLimits.end = true;
app.get('/raw/:id', function(request, response, next) { app.use(connect_rate_limit(config.rateLimits));
var skipExpire = !!config.documents[request.params.id]; }
var key = request.params.id.split('.')[0];
return documentHandler.handleRawGet(key, response, skipExpire); function raw(request, response) {
}); return documentHandler.handleRawGet(request, response, config);
// add documents }
app.post('/documents', function(request, response, next) {
return documentHandler.handlePost(request, response); // first look at API calls
}); app.use(route(function(router) {
// get documents // get raw documents - support getting with extension
app.get('/documents/:id', function(request, response, next) { router.get('/raw/:id', raw);
var skipExpire = !!config.documents[request.params.id]; router.get('/:id/raw', raw);
return documentHandler.handleGet(request.params.id, response, skipExpire); router.head('/raw/:id', raw);
}); router.head('/:id/raw', raw);
}), // add documents
// Otherwise, static
connect.staticCache(), router.post('/documents', function(request, response) {
connect.static(__dirname + '/static', { maxAge: config.staticMaxAge }), return documentHandler.handlePost(request, response);
// Then we can loop back - and everything else should be a token, });
// so route it back to /index.html
connect.router(function(app) { // get documents
app.get('/:id', function(request, response, next) { router.get('/documents/:id', function(request, response) {
request.url = request.originalUrl = '/index.html'; 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) {
if (request.headers.accept && request.headers.accept.includes('html')) {
request.sturl = '/';
next(); next();
}); } else {
}), return raw(request, response);
connect.static(__dirname + '/static', { maxAge: config.staticMaxAge }) }
).listen(config.port, config.host); });
}));
// 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); winston.info('listening on ' + config.host + ':' + config.port);

View File

@ -1,19 +0,0 @@
var DocumentHandler = require('../lib/document_handler');
describe('document_handler', function() {
describe('randomKey', function() {
it('should choose a key of the proper length', function() {
var dh = new DocumentHandler({ keyLength: 6 });
expect(dh.randomKey().length).toBe(6);
});
it('should choose a default key length', function() {
var dh = new DocumentHandler();
expect(dh.keyLength).toBe(DocumentHandler.defaultKeyLength);
});
});
});

View File

@ -1,89 +0,0 @@
var RedisDocumentStore = require('../lib/redis_document_store');
var winston = require('winston');
winston.remove(winston.transports.Console);
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() {
var store = new RedisDocumentStore({ expire: 10 });
runs(function() {
var _this = this;
store.set('hello1', 'world', function(worked) {
_this.result = worked;
});
});
waitsFor(function() {
return typeof(this.result) === 'boolean';
});
runs(function() {
var _this = this;
RedisDocumentStore.client.ttl('hello1', function(err, res) {
expect(res).toBeGreaterThan(1);
_this.done = true;
});
});
waitsFor(function() {
return this.done;
});
});
it('should not set an expiration when told not to', function() {
var store = new RedisDocumentStore({ expire: 10 });
runs(function() {
var _this = this;
store.set('hello2', 'world', function(worked) {
_this.result = worked;
}, true);
});
waitsFor(function() {
return typeof(this.result) === 'boolean';
});
runs(function() {
var _this = this;
RedisDocumentStore.client.ttl('hello2', function(err, res) {
expect(res).toBe(-1);
_this.done = true;
});
});
waitsFor(function() {
return this.done;
});
});
it('should not set an expiration when expiration is off', function() {
var store = new RedisDocumentStore({ expire: false });
runs(function() {
var _this = this;
store.set('hello3', 'world', function(worked) {
_this.result = worked;
});
});
waitsFor(function() {
return typeof(this.result) === 'boolean';
});
runs(function() {
var _this = this;
RedisDocumentStore.client.ttl('hello3', function(err, res) {
expect(res).toBe(-1);
_this.done = true;
});
});
waitsFor(function() {
return this.done;
});
});
});
});

View File

@ -1,5 +1,5 @@
body { body {
background: #002B36; background: #fcfcfc;
padding: 20px 50px; padding: 20px 50px;
margin: 0px; margin: 0px;
} }
@ -9,7 +9,7 @@ body {
textarea { textarea {
background: transparent; background: transparent;
border: 0px; border: 0px;
color: #fff; color: #000;
padding: 0px; padding: 0px;
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -17,20 +17,23 @@ textarea {
outline: none; outline: none;
resize: none; resize: none;
font-size: 13px; font-size: 13px;
margin-top: 0;
margin-bottom: 0;
} }
/* the line numbers */ /* the line numbers */
#linenos { #linenos {
color: #7d7d7d; color: #7d7d7d;
z-index: -1000; z-index: -1000;
position: absolute; position: absolute;
top: 20px; /* top: 20px; */
left: 0px; left: 0px;
width: 30px; /* 30 to get 20 away from box */ width: 30px; /* 30 to get 20 away from box */
font-size: 13px; font-size: 13px;
font-family: monospace; font-family: monospace;
text-align: right; text-align: right;
user-select: none;
} }
/* code box when locked */ /* code box when locked */
@ -42,6 +45,8 @@ textarea {
border: 0px; border: 0px;
outline: none; outline: none;
font-size: 13px; font-size: 13px;
overflow: inherit;
background: #fcfcfc;
} }
#box code { #box code {
@ -52,32 +57,35 @@ textarea {
/* key */ /* key */
#key { #key {
position: fixed; display: flex;
top: 0px; justify-content: space-between;
right: 0px; width: 100%;
z-index: +1000; /* watch out */ position: sticky;
flex-wrap: row;
} }
#box1 { #box1 {
padding: 5px; padding: 5px;
text-align: center; text-align: center;
background: #00222b; background: #fcfcfc;
} }
#box2 { #box2 {
background: #08323c; display: flex;
justify-content: right;
background: #fcfcfc;
font-size: 0px; font-size: 0px;
padding: 0px 5px; padding: 0px 5px;
} }
#box1 a.logo, #box1 a.logo:visited { a.logo, a.logo:visited {
display: inline-block; display: inline-block;
background: url(logo.png); background: url(logo.png);
width: 126px; width: 126px;
min-width: 126px;
height: 42px; height: 42px;
} }
#box1 a.logo:hover { a.logo:hover {
background-position: 0 bottom; background-position: 0 bottom;
} }
@ -111,18 +119,26 @@ textarea {
} }
#box3, #messages li { #box3, #messages li {
background: #173e48; position: absolute;
right: 100px;
background: #fcfcfc;
font-family: Helvetica, sans-serif; font-family: Helvetica, sans-serif;
font-size: 12px; font-size: 12px;
line-height: 14px; line-height: 14px;
padding: 10px 15px; padding: 10px 15px;
user-select: none;
} }
#box3 .label, #messages li { #box3 .label {
color: #fff; color: #000;
font-weight: bold; font-weight: bold;
} }
#messages li {
color: #FFF;
right: 50px;
}
#box3 .shortcut { #box3 .shortcut {
color: #c4dce3; color: #c4dce3;
font-weight: normal; font-weight: normal;
@ -147,18 +163,15 @@ textarea {
#box2 .function.twitter { background-position: -153px top; } #box2 .function.twitter { background-position: -153px top; }
#box2 .function.enabled.twitter { background-position: -153px center; } #box2 .function.enabled.twitter { background-position: -153px center; }
#box2 .function.enabled.twitter:hover { background-position: -153px bottom; } #box2 .function.enabled.twitter:hover { background-position: -153px bottom; }
#box2 .button-picture{ border-width: 0; font-size: inherit; }
#messages { #messages {
position:fixed;
top:0px;
right:138px;
margin:0; margin:0;
padding:0; padding:0;
width:400px;
} }
#messages li { #messages li {
background:rgba(23,62,72,0.8); background:rgba(252,252,252,0.8);
margin:0 auto; margin:0 auto;
list-style:none; list-style:none;
} }
@ -166,3 +179,4 @@ textarea {
#messages li.error { #messages li.error {
background:rgba(102,8,0,0.8); background:rgba(102,8,0,0.8);
} }

View File

@ -1,3 +1,5 @@
/* global $, hljs, window, document */
///// represents a single document ///// represents a single document
var haste_document = function() { var haste_document = function() {
@ -42,10 +44,10 @@ haste_document.prototype.load = function(key, callback, lang) {
value: high.value, value: high.value,
key: key, key: key,
language: high.language || lang, language: high.language || lang,
lineCount: res.data.split("\n").length lineCount: res.data.split('\n').length
}); });
}, },
error: function(err) { error: function() {
callback(false); callback(false);
} }
}); });
@ -62,6 +64,7 @@ haste_document.prototype.save = function(data, callback) {
type: 'post', type: 'post',
data: data, data: data,
dataType: 'json', dataType: 'json',
contentType: 'text/plain; charset=utf-8',
success: function(res) { success: function(res) {
_this.locked = true; _this.locked = true;
_this.key = res.key; _this.key = res.key;
@ -70,7 +73,7 @@ haste_document.prototype.save = function(data, callback) {
value: high.value, value: high.value,
key: res.key, key: res.key,
language: high.language, language: high.language,
lineCount: data.split("\n").length lineCount: data.split('\n').length
}); });
}, },
error: function(res) { error: function(res) {
@ -165,9 +168,9 @@ haste.extensionMap = {
rb: 'ruby', py: 'python', pl: 'perl', php: 'php', scala: 'scala', go: 'go', rb: 'ruby', py: 'python', pl: 'perl', php: 'php', scala: 'scala', go: 'go',
xml: 'xml', html: 'xml', htm: 'xml', css: 'css', js: 'javascript', vbs: 'vbscript', xml: 'xml', html: 'xml', htm: 'xml', css: 'css', js: 'javascript', vbs: 'vbscript',
lua: 'lua', pas: 'delphi', java: 'java', cpp: 'cpp', cc: 'cpp', m: 'objectivec', lua: 'lua', pas: 'delphi', java: 'java', cpp: 'cpp', cc: 'cpp', m: 'objectivec',
vala: 'vala', cs: 'cs', sql: 'sql', sm: 'smalltalk', lisp: 'lisp', ini: 'ini', vala: 'vala', sql: 'sql', sm: 'smalltalk', lisp: 'lisp', ini: 'ini',
diff: 'diff', bash: 'bash', sh: 'bash', tex: 'tex', erl: 'erlang', hs: 'haskell', diff: 'diff', bash: 'bash', sh: 'bash', tex: 'tex', erl: 'erlang', hs: 'haskell',
md: 'markdown', txt: '' md: 'markdown', txt: '', coffee: 'coffee', swift: 'swift'
}; };
// Look up the extension preferred for a type // Look up the extension preferred for a type
@ -274,7 +277,7 @@ haste.prototype.configureButtons = function() {
$where: $('#box2 .new'), $where: $('#box2 .new'),
label: 'New', label: 'New',
shortcut: function(evt) { shortcut: function(evt) {
return evt.ctrlKey && evt.keyCode === 78 return evt.ctrlKey && evt.keyCode === 78;
}, },
shortcutDescription: 'control + n', shortcutDescription: 'control + n',
action: function() { action: function() {
@ -300,16 +303,16 @@ haste.prototype.configureButtons = function() {
}, },
shortcutDescription: 'control + shift + r', shortcutDescription: 'control + shift + r',
action: function() { action: function() {
window.open('/raw/' + _this.doc.key); window.location.href = '/raw/' + _this.doc.key;
} }
}, },
{ {
$where: $('#box2 .twitter'), $where: $('#box2 .twitter'),
label: 'Twitter', label: 'Twitter',
shortcut: function(evt) { shortcut: function(evt) {
return _this.options.twitter && _this.doc.locked && evt.ctrlKey && evt.keyCode == 84; return _this.options.twitter && _this.doc.locked && evt.shiftKey && evt.ctrlKey && evt.keyCode == 84;
}, },
shortcutDescription: 'control + t', shortcutDescription: 'control + shift + t',
action: function() { action: function() {
window.open('https://twitter.com/share?url=' + encodeURI(window.location.href)); window.open('https://twitter.com/share?url=' + encodeURI(window.location.href));
} }
@ -329,14 +332,14 @@ haste.prototype.configureButton = function(options) {
} }
}); });
// Show the label // Show the label
options.$where.mouseenter(function(evt) { options.$where.mouseenter(function() {
$('#box3 .label').text(options.label); $('#box3 .label').text(options.label);
$('#box3 .shortcut').text(options.shortcutDescription || ''); $('#box3 .shortcut').text(options.shortcutDescription || '');
$('#box3').show(); $('#box3').show();
$(this).append($('#pointer').remove().show()); $(this).append($('#pointer').remove().show());
}); });
// Hide the label // Hide the label
options.$where.mouseleave(function(evt) { options.$where.mouseleave(function() {
$('#box3').hide(); $('#box3').hide();
$('#pointer').hide(); $('#pointer').hide();
}); });
@ -369,7 +372,7 @@ $(function() {
// For browsers like Internet Explorer // For browsers like Internet Explorer
if (document.selection) { if (document.selection) {
this.focus(); this.focus();
sel = document.selection.createRange(); var sel = document.selection.createRange();
sel.text = myValue; sel.text = myValue;
this.focus(); this.focus();
} }

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

View File

@ -1,13 +1,12 @@
<html> <html>
<head> <head>
<title>Webionite hastebin</title>
<title>hastebin</title> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="solarized_dark.css"/> <link rel="stylesheet" type="text/css" href="tomorrow.css"/>
<link rel="stylesheet" type="text/css" href="application.css"/> <link rel="stylesheet" type="text/css" href="application.css"/>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script> <script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="highlight.min.js"></script> <script type="text/javascript" src="highlight.min.js"></script>
<script type="text/javascript" src="application.min.js"></script> <script type="text/javascript" src="application.min.js"></script>
@ -39,30 +38,27 @@
</head> </head>
<body> <body>
<ul id="messages"></ul>
<div id="key"> <div id="key">
<div id="pointer" style="display:none;"></div> <a href="/about.md" class="logo"></a>
<div id="box1"> <div>
<a href="/about.md" class="logo"></a> <div id="pointer" style="display:none;"></div>
</div> <div id="box2">
<div id="box2"> <button class="save function button-picture">Save</button>
<div class="save function"></div> <button class="new function button-picture">New</button>
<div class="new function"></div> <button class="duplicate function button-picture">Duplicate &amp; Edit</button>
<div class="duplicate function"></div> <button class="raw function button-picture">Just Text</button>
<div class="raw function"></div> </div>
<div class="twitter function"></div> <div id="box3" style="display:none;">
</div> <div class="label"></div>
<div id="box3" style="display:none;"> <div class="shortcut"></div>
<div class="label"></div> </div>
<div class="shortcut"></div> <ul id="messages"></ul>
</div> </div>
</div> </div>
<div id="linenos"></div> <div id="linenos"></div>
<pre id="box" style="display:none;" tabindex="0"><code></code></pre> <pre id="box" style="display:none;" class="hljs" tabindex="0"><code></code></pre>
<textarea spellcheck="false" style="display:none;"></textarea> <textarea spellcheck="false" style="display:none;"></textarea>
</body> </body>
</html> </html>

4
static/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -1,98 +0,0 @@
/*
Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull <sourdrums@gmail.com>
*/
pre code {
display: block; padding: 0.5em;
background: #002b36; color: #92a0a0;
}
pre .comment,
pre .template_comment,
pre .diff .header,
pre .doctype,
pre .lisp .string,
pre .javadoc {
color: #586e75;
font-style: italic;
}
pre .keyword,
pre .css .rule .keyword,
pre .winutils,
pre .javascript .title,
pre .method,
pre .addition,
pre .css .tag,
pre .lisp .title {
color: #859900;
}
pre .number,
pre .command,
pre .string,
pre .tag .value,
pre .phpdoc,
pre .tex .formula,
pre .regexp,
pre .hexcolor {
color: #2aa198;
}
pre .title,
pre .localvars,
pre .function .title,
pre .chunk,
pre .decorator,
pre .builtin,
pre .built_in,
pre .lisp .title,
pre .identifier,
pre .title .keymethods,
pre .id,
pre .header {
color: #268bd2;
}
pre .tag .title,
pre .rules .property,
pre .django .tag .keyword {
font-weight: bold;
}
pre .attribute,
pre .variable,
pre .instancevar,
pre .lisp .body,
pre .smalltalk .number,
pre .constant,
pre .class .title,
pre .parent,
pre .haskell .label {
color: #b58900;
}
pre .preprocessor,
pre .pi,
pre .shebang,
pre .symbol,
pre .diff .change,
pre .special,
pre .keymethods,
pre .attr_selector,
pre .important,
pre .subst,
pre .cdata {
color: #cb4b16;
}
pre .deletion {
color: #dc322f;
}
pre .tex .formula,
pre .code {
background: #073642;
}

72
static/tomorrow.css Normal file
View File

@ -0,0 +1,72 @@
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
/* Tomorrow Comment */
.hljs-comment,
.hljs-quote {
color: #8e908c;
}
/* Tomorrow Red */
.hljs-variable,
.hljs-template-variable,
.hljs-tag,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class,
.hljs-regexp,
.hljs-deletion {
color: #c82829;
}
/* Tomorrow Orange */
.hljs-number,
.hljs-built_in,
.hljs-builtin-name,
.hljs-literal,
.hljs-type,
.hljs-params,
.hljs-meta,
.hljs-link {
color: #f5871f;
}
/* Tomorrow Yellow */
.hljs-attribute {
color: #eab700;
}
/* Tomorrow Green */
.hljs-string,
.hljs-symbol,
.hljs-bullet,
.hljs-addition {
color: #718c00;
}
/* Tomorrow Blue */
.hljs-title,
.hljs-section {
color: #4271ae;
}
/* Tomorrow Purple */
.hljs-keyword,
.hljs-selector-tag {
color: #8959a8;
}
.hljs {
display: block;
overflow-x: auto;
background: white;
color: #4d4d4c;
padding: 0.5em;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}

View File

@ -0,0 +1,26 @@
/* 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);
});
});
});

View File

@ -0,0 +1,33 @@
/* global describe, it */
const assert = require('assert');
const fs = require('fs');
const Generator = require('../../lib/key_generators/dictionary');
describe('RandomKeyGenerator', function() {
describe('randomKey', 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);
});
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));
});
});
});
});

View File

@ -0,0 +1,27 @@
/* global describe, it */
const assert = require('assert');
const Generator = require('../../lib/key_generators/phonetic');
const vowels = 'aeiou';
const consonants = 'bcdfghjklmnpqrstvwxyz';
describe('RandomKeyGenerator', () => {
describe('randomKey', () => {
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);
assert.ok(consonants.includes(key[0]));
assert.ok(consonants.includes(key[2]));
assert.ok(vowels.includes(key[1]));
});
});
});

View File

@ -0,0 +1,19 @@
/* global describe, it */
const assert = require('assert');
const Generator = require('../../lib/key_generators/random');
describe('RandomKeyGenerator', () => {
describe('randomKey', () => {
it('should return a key of the proper length', () => {
const gen = new Generator();
assert.equal(6, gen.createKey(6).length);
});
it('should use a key from the given keyset if given', () => {
const gen = new Generator({keyspace: 'A'});
assert.equal('AAAAAA', gen.createKey(6));
});
});
});

View File

@ -0,0 +1,54 @@
/* 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();
});
});
});
});
});