├── img
├── busy.gif
├── icon_new.png
├── icon_raw.png
├── icon_send.png
└── icon_clone.png
├── .gitignore
├── robots.txt
├── lib
├── serversalt.php
├── vizhash_gd_zero.php
└── rain.tpl.class.php
├── README.md
├── js
├── highlight.styles
│ └── monokai.css
├── base64.js
├── sjcl.js
├── rawinflate.js
├── zerobin.js
└── rawdeflate.js
├── CHANGELOG.md
├── tpl
└── page.html
├── css
└── zerobin.css
└── index.php
/img/busy.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sebsauvage/ZeroBin/HEAD/img/busy.gif
--------------------------------------------------------------------------------
/img/icon_new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sebsauvage/ZeroBin/HEAD/img/icon_new.png
--------------------------------------------------------------------------------
/img/icon_raw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sebsauvage/ZeroBin/HEAD/img/icon_raw.png
--------------------------------------------------------------------------------
/img/icon_send.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sebsauvage/ZeroBin/HEAD/img/icon_send.png
--------------------------------------------------------------------------------
/img/icon_clone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sebsauvage/ZeroBin/HEAD/img/icon_clone.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore data/ and tmp/
2 | data/
3 | tmp/
4 | # Ignore for safety
5 | .htaccess
6 | .htpasswd
--------------------------------------------------------------------------------
/robots.txt:
--------------------------------------------------------------------------------
1 | # Might as well keep robots away for performance and privacy reasons.
2 | User-agent: *
3 | Allow: /index.php
4 | Disallow: /
5 |
--------------------------------------------------------------------------------
/lib/serversalt.php:
--------------------------------------------------------------------------------
1 | ',LOCK_EX);
31 | $items=explode('|',file_get_contents($saltfile));
32 | return $items[1];
33 |
34 | }
35 |
36 | ?>
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## This repo is unmaintained and outdated! Do not use this code anymore! :warning:
2 |
3 | **If you still use this old code switch to the new maintained repo:**
4 | **:arrow_forward: https://github.com/PrivateBin/PrivateBin**
5 |
6 | You will get more features and up-to-date code.
7 |
8 | ====
9 | ----
10 |
11 | ZeroBin 0.19 Alpha
12 |
13 | ==== THIS IS ALPHA SOFTWARE - USE AT YOUR OWN RISKS ====
14 |
15 | ZeroBin is a minimalist, opensource online pastebin where the server
16 | has zero knowledge of pasted data. Data is encrypted/decrypted in the
17 | browser using 256 bits AES.
18 |
19 | More information on the project page:
20 | http://sebsauvage.net/wiki/doku.php?id=php:zerobin
21 |
22 | ------------------------------------------------------------------------------
23 |
24 | Copyright (c) 2012 Sébastien SAUVAGE (sebsauvage.net)
25 |
26 | This software is provided 'as-is', without any express or implied warranty.
27 | In no event will the authors be held liable for any damages arising from
28 | the use of this software.
29 |
30 | Permission is granted to anyone to use this software for any purpose,
31 | including commercial applications, and to alter it and redistribute it
32 | freely, subject to the following restrictions:
33 |
34 | 1. The origin of this software must not be misrepresented; you must
35 | not claim that you wrote the original software. If you use this
36 | software in a product, an acknowledgment in the product documentation
37 | would be appreciated but is not required.
38 |
39 | 2. Altered source versions must be plainly marked as such, and must
40 | not be misrepresented as being the original software.
41 |
42 | 3. This notice may not be removed or altered from any source distribution.
43 |
44 | ------------------------------------------------------------------------------
45 |
--------------------------------------------------------------------------------
/js/highlight.styles/monokai.css:
--------------------------------------------------------------------------------
1 | /*
2 | Monokai style - ported by Luigi Maselli - http://grigio.org
3 | */
4 |
5 | pre code {
6 | display: block; padding: 0.5em;
7 | background: #272822;
8 | }
9 |
10 | pre .tag,
11 | pre .tag .title,
12 | pre .keyword,
13 | pre .literal,
14 | pre .change,
15 | pre .winutils,
16 | pre .flow,
17 | pre .lisp .title,
18 | pre .clojure .built_in,
19 | pre .nginx .title,
20 | pre .tex .special {
21 | color: #F92672;
22 | }
23 |
24 | pre code {
25 | color: #DDD;
26 | }
27 |
28 | pre code .constant {
29 | color: #66D9EF;
30 | }
31 |
32 | pre .class .title {
33 | color: white;
34 | }
35 |
36 | pre .attribute,
37 | pre .symbol,
38 | pre .symbol .string,
39 | pre .value,
40 | pre .regexp {
41 | color: #BF79DB;
42 | }
43 |
44 | pre .tag .value,
45 | pre .string,
46 | pre .subst,
47 | pre .title,
48 | pre .haskell .type,
49 | pre .preprocessor,
50 | pre .ruby .class .parent,
51 | pre .built_in,
52 | pre .sql .aggregate,
53 | pre .django .template_tag,
54 | pre .django .variable,
55 | pre .smalltalk .class,
56 | pre .javadoc,
57 | pre .django .filter .argument,
58 | pre .smalltalk .localvars,
59 | pre .smalltalk .array,
60 | pre .attr_selector,
61 | pre .pseudo,
62 | pre .addition,
63 | pre .stream,
64 | pre .envvar,
65 | pre .apache .tag,
66 | pre .apache .cbracket,
67 | pre .tex .command,
68 | pre .prompt {
69 | color: #A6E22E;
70 | }
71 |
72 | pre .comment,
73 | pre .java .annotation,
74 | pre .python .decorator,
75 | pre .template_comment,
76 | pre .pi,
77 | pre .doctype,
78 | pre .deletion,
79 | pre .shebang,
80 | pre .apache .sqbracket,
81 | pre .tex .formula {
82 | color: #75715E;
83 | }
84 |
85 | pre .keyword,
86 | pre .literal,
87 | pre .css .id,
88 | pre .phpdoc,
89 | pre .title,
90 | pre .haskell .type,
91 | pre .vbscript .built_in,
92 | pre .sql .aggregate,
93 | pre .rsl .built_in,
94 | pre .smalltalk .class,
95 | pre .diff .header,
96 | pre .chunk,
97 | pre .winutils,
98 | pre .bash .variable,
99 | pre .apache .tag,
100 | pre .tex .special,
101 | pre .request,
102 | pre .status {
103 | font-weight: bold;
104 | }
105 |
106 | pre .coffeescript .javascript,
107 | pre .javascript .xml,
108 | pre .tex .formula,
109 | pre .xml .javascript,
110 | pre .xml .vbscript,
111 | pre .xml .css,
112 | pre .xml .cdata {
113 | opacity: 0.5;
114 | }
115 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # ZeroBin version history #
2 |
3 | * **Alpha 0.8 (2012-04-11):**
4 | * Source code not published yet.
5 | * Interface completely redesigned. Icons added.
6 | * Now properly supports IE6/7 (ugly display, but it works. "Clone" button is disabled though.)
7 | * Added one level of depth for storage directories (This is better for higher load servers).
8 | * php version is now checked (min: 5.2.6)
9 | * Better checks on posted json data on server.
10 | * Added "1 year" expiration.
11 | * URLs are now converted to clickable links. This include http, https, ftp and magnet links.
12 | * Clickable links include ''rel="nofollow"'' to discourage SEO.
13 | * On my public service (http://sebsauvage.net/paste/)
14 | * All data will be deleted (you were warned - this is a test service)
15 | * Default paste expiration is now 1 month to prevent clogging-up my host.
16 | * **Alpha 0.9 (2012-04-11):**
17 | * Oh bummer... IE 8 is as shitty as IE6/7: Its does not seem to support ''white-space:pre-wrap'' correctly. I had to activate the special handling mode. I still have to test IE 9.
18 | * **Alpha 0.10 (2012-04-12):**
19 | * IE9 does not seem to correctly support ''pre-wrap'' either. Special handling mode activated for all version of IE<10. (Note: **ALL other browsers** correctly support this feature.)
20 | * **Alpha 0.11 (2012-04-12):**
21 | * Automatically ignore parameters (such as &utm_source=...) added //after// the anchor by some stupid Web 2.0 services.
22 | * First public release.
23 | * **Alpha 0.12 (2012-04-18):**
24 | * **DISCUSSIONS !** Now you can enable discussions on your pastes. Of course, posted comments and nickname are also encrypted and the server cannot see them.
25 | * This feature implies a change in storage format. You will have to delete all previous pastes in your ZeroBin.
26 | * Added [[php:vizhash_gd|Vizhash]] as avatars, so you can match posters IP addresses without revealing them. (Same image = same IP). Of course the IP address cannot be deduced from the Vizhash.
27 | * Remaining time before expiration is now displayed.
28 | * Explicit tags were added to CSS and jQuery selectors (eg. div#aaa instead of #aaa) to speed up browser.
29 | * Better cleaning of the URL (to make sure the key is not broken by some stupid redirection service)
30 | * **Alpha 0.13 (2012-04-18):**
31 | * FIXED: ''imageantialias()'' call removed because it's not really usefull and can be a problem on most hosts (if GD is not compiled in php).
32 | * FIXED: $error not properly initialized in index.php
33 | * **Alpha 0.14 (2012-04-20):**
34 | * ADDED: GD presence is checked.
35 | * CHANGED: Traffic limiter data files moved to data/ (→easier rights management)
36 | * ADDED: "Burn after reading" implemented. Opening the URL will display the paste and immediately destroy it on server.
37 | * **Alpha 0.15 (2012-04-20):**
38 | * FIXED: 2 minor corrections to avoid notices in php log.
39 | * FIXED: Sources converted to UTF-8.
40 | * **Alpha 0.15 (2012-04-20):**
41 | * FIXED: 2 minor corrections to avoid notices in php log.
42 | * FIXED: Sources converted to UTF-8.
43 | * **Alpha 0.16**:
44 | * FIXED minor php warnings.
45 | * FIXED: zerobin.js reformated and properly commented.
46 | * FIXED: Directory structure re-organized.
47 | * CHANGED: URL shortening button was removed. (It was bad for privacy.)
48 | * **Alpha 0.17 (2013-02-23)**:
49 | * ADDED: Deletion URL.
50 | * small refactoring.
51 | * improved regex checks.
52 | * larger server alt on installation.
53 | * **Alpha 0.18 (2013-02-24)**:
54 | * ADDED: The resulting URL is automatically selected after pressing "Send". You just have to press CTRL+C.
55 | * ADDED: Automatic syntax highlighting for 53 languages using highlight.js
56 | * ADDED: "5 minutes" and "1 week" expirations.
57 | * ADDED: "Raw text" button.
58 | * jQuery upgraded to 1.9.1
59 | * sjcl upgraded to GitHub master 2013-02-23
60 | * base64.js upgraded to 1.7
61 | * FIXED: Dates in discussion are now proper local dates.
62 | * ADDED: Robot meta tags in HTML to prevent search engines indexing.
63 | * ADDED: Better json checking (including entropy).
64 | * ADDED: Added version to js/css assets URLs in order to prevent some abusive caches to serve an obsolete version of these files when ZeroBin is upgraded.
65 | * "Burn after reading" option has been moved out of Expiration combo to a separate checkbox. Reason is: You can prevent a read-once paste to be available ad vitam eternam on the net.
66 | * **Alpha 0.19 (2013-07-05)**:
67 | * Corrected XSS security flaw which affected IE<10. Other browsers were not affected.
68 | * Corrected spacing display in IE<10.
69 |
70 |
--------------------------------------------------------------------------------
/tpl/page.html:
--------------------------------------------------------------------------------
1 |
2 |
26 | ZeroBin is a minimalist, opensource online pastebin where the server has zero knowledge of pasted data.
27 | Data is encrypted/decrypted in the browser using 256 bits AES.
28 | More information on the project page.
29 | ▶ Note: This is a test service:
30 | Data may be deleted anytime. Kittens will die if you abuse this service.
31 |
32 |
ZeroBin
33 |
Because ignorance is bliss
34 |
{$VERSION}
35 |
36 |
ZeroBin requires a modern browser to work.
37 |
Still using Internet Explorer ? Do yourself a favor, switch to a modern browser:
38 | Firefox,
39 | Opera,
40 | Chrome,
41 | Safari...
42 |
43 |
{$STATUS}
44 |
{$ERRORMESSAGE|htmlspecialchars}
45 |
46 |
47 |
48 |
49 |
50 |
Expires:
51 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
Discussion
84 |
85 |
86 |
{$CIPHERDATA}
87 |
88 |
89 |
--------------------------------------------------------------------------------
/lib/vizhash_gd_zero.php:
--------------------------------------------------------------------------------
1 | generate('hello');
10 | header('Content-type: image/png');
11 | echo $data;
12 | exit;
13 | */
14 | require_once "serversalt.php";
15 | class vizhash16x16
16 | {
17 | private $VALUES;
18 | private $VALUES_INDEX;
19 | private $width;
20 | private $height;
21 | private $salt;
22 | function __construct()
23 | {
24 | $this->width=16;
25 | $this->height=16;
26 | $this->salt = getServerSalt();
27 | }
28 |
29 | // Generate a 16x16 png corresponding to $text.
30 | // Input: $text (string)
31 | // Output: PNG data. Or empty string if GD is not available.
32 | function generate($text)
33 | {
34 | if (!function_exists('gd_info')) return '';
35 |
36 | // We hash the input string.
37 | $hash=hash('sha1',$text.$this->salt).hash('md5',$text.$this->salt);
38 | $hash=$hash.strrev($hash); # more data to make graphics
39 |
40 | // We convert the hash into an array of integers.
41 | $this->VALUES=array();
42 | for($i=0; $iVALUES,hexdec(substr($hash,$i,2))); }
43 | $this->VALUES_INDEX=0; // to walk the array.
44 |
45 | // Then use these integers to drive the creation of an image.
46 | $image = imagecreatetruecolor($this->width,$this->height);
47 |
48 | $r0 = $this->getInt();$r=$r0;
49 | $g0 = $this->getInt();$g=$g0;
50 | $b0 = $this->getInt();$b=$b0;
51 |
52 | // First, create an image with a specific gradient background.
53 | $op='v'; if (($this->getInt()%2)==0) { $op='h'; };
54 | $image = $this->degrade($image,$op,array($r0,$g0,$b0),array(0,0,0));
55 |
56 | for($i=0; $i<7; $i=$i+1)
57 | {
58 | $action=$this->getInt();
59 | $color = imagecolorallocate($image, $r,$g,$b);
60 | $r = ($r0 + $this->getInt()/25)%256;
61 | $g = ($g0 + $this->getInt()/25)%256;
62 | $b = ($b0 + $this->getInt()/25)%256;
63 | $r0=$r; $g0=$g; $b0=$b;
64 | $this->drawshape($image,$action,$color);
65 | }
66 |
67 | $color = imagecolorallocate($image,$this->getInt(),$this->getInt(),$this->getInt());
68 | $this->drawshape($image,$this->getInt(),$color);
69 | ob_start();
70 | imagepng($image);
71 | $imagedata = ob_get_contents();
72 | ob_end_clean();
73 | imagedestroy($image);
74 |
75 | return $imagedata;
76 | }
77 |
78 | private function getInt() // Returns a single integer from the $VALUES array (0...255)
79 | {
80 | $v= $this->VALUES[$this->VALUES_INDEX];
81 | $this->VALUES_INDEX++;
82 | $this->VALUES_INDEX %= count($this->VALUES); // Warp around the array
83 | return $v;
84 | }
85 | private function getX() // Returns a single integer from the array (roughly mapped to image width)
86 | {
87 | return $this->width*$this->getInt()/256;
88 | }
89 |
90 | private function getY() // Returns a single integer from the array (roughly mapped to image height)
91 | {
92 | return $this->height*$this->getInt()/256;
93 | }
94 |
95 | # Gradient function taken from:
96 | # http://www.supportduweb.com/scripts_tutoriaux-code-source-41-gd-faire-un-degrade-en-php-gd-fonction-degrade-imagerie.html
97 | private function degrade($img,$direction,$color1,$color2)
98 | {
99 | if($direction=='h') { $size = imagesx($img); $sizeinv = imagesy($img); }
100 | else { $size = imagesy($img); $sizeinv = imagesx($img);}
101 | $diffs = array(
102 | (($color2[0]-$color1[0])/$size),
103 | (($color2[1]-$color1[1])/$size),
104 | (($color2[2]-$color1[2])/$size)
105 | );
106 | for($i=0;$i<$size;$i++)
107 | {
108 | $r = $color1[0]+($diffs[0]*$i);
109 | $g = $color1[1]+($diffs[1]*$i);
110 | $b = $color1[2]+($diffs[2]*$i);
111 | if($direction=='h') { imageline($img,$i,0,$i,$sizeinv,imagecolorallocate($img,$r,$g,$b)); }
112 | else { imageline($img,0,$i,$sizeinv,$i,imagecolorallocate($img,$r,$g,$b)); }
113 | }
114 | return $img;
115 | }
116 |
117 | private function drawshape($image,$action,$color)
118 | {
119 | switch($action%7)
120 | {
121 | case 0:
122 | ImageFilledRectangle ($image,$this->getX(),$this->getY(),$this->getX(),$this->getY(),$color);
123 | break;
124 | case 1:
125 | case 2:
126 | ImageFilledEllipse ($image, $this->getX(), $this->getY(), $this->getX(), $this->getY(), $color);
127 | break;
128 | case 3:
129 | $points = array($this->getX(), $this->getY(), $this->getX(), $this->getY(), $this->getX(), $this->getY(),$this->getX(), $this->getY());
130 | ImageFilledPolygon ($image, $points, 4, $color);
131 | break;
132 | case 4:
133 | case 5:
134 | case 6:
135 | $start=$this->getInt()*360/256; $end=$start+$this->getInt()*180/256;
136 | ImageFilledArc ($image, $this->getX(), $this->getY(), $this->getX(), $this->getY(),$start,$end,$color,IMG_ARC_PIE);
137 | break;
138 | }
139 | }
140 | }
141 |
142 | ?>
--------------------------------------------------------------------------------
/js/base64.js:
--------------------------------------------------------------------------------
1 | /*
2 | * $Id: base64.js,v 1.7 2012/08/23 10:30:18 dankogai Exp dankogai $
3 | *
4 | * Licensed under the MIT license.
5 | * http://www.opensource.org/licenses/mit-license.php
6 | *
7 | * References:
8 | * http://en.wikipedia.org/wiki/Base64
9 | */
10 |
11 | (function(global){
12 |
13 | var b64chars
14 | = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
15 |
16 | var b64charcodes = function(){
17 | var a = [];
18 | var codeA = 'A'.charCodeAt(0);
19 | var codea = 'a'.charCodeAt(0);
20 | var code0 = '0'.charCodeAt(0);
21 | for (var i = 0; i < 26; i ++) a.push(codeA + i);
22 | for (var i = 0; i < 26; i ++) a.push(codea + i);
23 | for (var i = 0; i < 10; i ++) a.push(code0 + i);
24 | a.push('+'.charCodeAt(0));
25 | a.push('/'.charCodeAt(0));
26 | return a;
27 | }();
28 |
29 | var b64tab = function(bin){
30 | var t = {};
31 | for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i;
32 | return t;
33 | }(b64chars);
34 |
35 | var stringToArray = function(s){
36 | var a = [];
37 | for (var i = 0, l = s.length; i < l; i ++) a[i] = s.charCodeAt(i);
38 | return a;
39 | };
40 |
41 | var convertUTF8ArrayToBase64 = function(bin){
42 | var padlen = 0;
43 | while (bin.length % 3){
44 | bin.push(0);
45 | padlen++;
46 | };
47 | var b64 = [];
48 | for (var i = 0, l = bin.length; i < l; i += 3){
49 | var c0 = bin[i], c1 = bin[i+1], c2 = bin[i+2];
50 | if (c0 >= 256 || c1 >= 256 || c2 >= 256)
51 | throw 'unsupported character found';
52 | var n = (c0 << 16) | (c1 << 8) | c2;
53 | b64.push(
54 | b64charcodes[ n >>> 18],
55 | b64charcodes[(n >>> 12) & 63],
56 | b64charcodes[(n >>> 6) & 63],
57 | b64charcodes[ n & 63]
58 | );
59 | }
60 | while (padlen--) b64[b64.length - padlen - 1] = '='.charCodeAt(0);
61 | return chunkStringFromCharCodeApply(b64);
62 | };
63 |
64 | var convertBase64ToUTF8Array = function(b64){
65 | b64 = b64.replace(/[^A-Za-z0-9+\/]+/g, '');
66 | var bin = [];
67 | var padlen = b64.length % 4;
68 | for (var i = 0, l = b64.length; i < l; i += 4){
69 | var n = ((b64tab[b64.charAt(i )] || 0) << 18)
70 | | ((b64tab[b64.charAt(i+1)] || 0) << 12)
71 | | ((b64tab[b64.charAt(i+2)] || 0) << 6)
72 | | ((b64tab[b64.charAt(i+3)] || 0));
73 | bin.push(
74 | ( n >> 16 ),
75 | ( (n >> 8) & 0xff ),
76 | ( n & 0xff )
77 | );
78 | }
79 | bin.length -= [0,0,2,1][padlen];
80 | return bin;
81 | };
82 |
83 | var convertUTF16ArrayToUTF8Array = function(uni){
84 | var bin = [];
85 | for (var i = 0, l = uni.length; i < l; i++){
86 | var n = uni[i];
87 | if (n < 0x80)
88 | bin.push(n);
89 | else if (n < 0x800)
90 | bin.push(
91 | 0xc0 | (n >>> 6),
92 | 0x80 | (n & 0x3f));
93 | else
94 | bin.push(
95 | 0xe0 | ((n >>> 12) & 0x0f),
96 | 0x80 | ((n >>> 6) & 0x3f),
97 | 0x80 | (n & 0x3f));
98 | }
99 | return bin;
100 | };
101 |
102 | var convertUTF8ArrayToUTF16Array = function(bin){
103 | var uni = [];
104 | for (var i = 0, l = bin.length; i < l; i++){
105 | var c0 = bin[i];
106 | if (c0 < 0x80){
107 | uni.push(c0);
108 | }else{
109 | var c1 = bin[++i];
110 | if (c0 < 0xe0){
111 | uni.push(((c0 & 0x1f) << 6) | (c1 & 0x3f));
112 | }else{
113 | var c2 = bin[++i];
114 | uni.push(
115 | ((c0 & 0x0f) << 12) | ((c1 & 0x3f) << 6) | (c2 & 0x3f)
116 | );
117 | }
118 | }
119 | }
120 | return uni;
121 | };
122 |
123 | var convertUTF8StringToBase64 = function(bin){
124 | return convertUTF8ArrayToBase64(stringToArray(bin));
125 | };
126 |
127 | var convertBase64ToUTF8String = function(b64){
128 | return chunkStringFromCharCodeApply(convertBase64ToUTF8Array(b64));
129 | };
130 |
131 | var convertUTF8StringToUTF16Array = function(bin){
132 | return convertUTF8ArrayToUTF16Array(stringToArray(bin));
133 | };
134 |
135 | var convertUTF8ArrayToUTF16String = function(bin){
136 | return chunkStringFromCharCodeApply(convertUTF8ArrayToUTF16Array(bin));
137 | };
138 |
139 | var convertUTF8StringToUTF16String = function(bin){
140 | return chunkStringFromCharCodeApply(
141 | convertUTF8ArrayToUTF16Array(stringToArray(bin))
142 | );
143 | };
144 |
145 | var convertUTF16StringToUTF8Array = function(uni){
146 | return convertUTF16ArrayToUTF8Array(stringToArray(uni));
147 | };
148 |
149 | var convertUTF16ArrayToUTF8String = function(uni){
150 | return chunkStringFromCharCodeApply(convertUTF16ArrayToUTF8Array(uni));
151 | };
152 |
153 | var convertUTF16StringToUTF8String = function(uni){
154 | return chunkStringFromCharCodeApply(
155 | convertUTF16ArrayToUTF8Array(stringToArray(uni))
156 | );
157 | };
158 |
159 | /*
160 | * String.fromCharCode.apply will only handle arrays as big as 65536,
161 | * after that it'll return a truncated string with no warning.
162 | */
163 | var chunkStringFromCharCodeApply = function(arr){
164 | var strs = [], i;
165 | for (i = 0; i < arr.length; i += 65536){
166 | strs.push(String.fromCharCode.apply(String, arr.slice(i, i+65536)));
167 | }
168 | return strs.join('');
169 | };
170 |
171 | if (global.btoa){
172 | var btoa = global.btoa;
173 | var convertUTF16StringToBase64 = function (uni){
174 | return btoa(convertUTF16StringToUTF8String(uni));
175 | };
176 | }
177 | else {
178 | var btoa = convertUTF8StringToBase64;
179 | var convertUTF16StringToBase64 = function (uni){
180 | return convertUTF8ArrayToBase64(convertUTF16StringToUTF8Array(uni));
181 | };
182 | }
183 |
184 | if (global.atob){
185 | var atob = global.atob;
186 | var convertBase64ToUTF16String = function (b64){
187 | return convertUTF8StringToUTF16String(atob(b64));
188 | };
189 | }
190 | else {
191 | var atob = convertBase64ToUTF8String;
192 | var convertBase64ToUTF16String = function (b64){
193 | return convertUTF8ArrayToUTF16String(convertBase64ToUTF8Array(b64));
194 | };
195 | }
196 |
197 | global.Base64 = {
198 | convertUTF8ArrayToBase64:convertUTF8ArrayToBase64,
199 | convertByteArrayToBase64:convertUTF8ArrayToBase64,
200 | convertBase64ToUTF8Array:convertBase64ToUTF8Array,
201 | convertBase64ToByteArray:convertBase64ToUTF8Array,
202 | convertUTF16ArrayToUTF8Array:convertUTF16ArrayToUTF8Array,
203 | convertUTF16ArrayToByteArray:convertUTF16ArrayToUTF8Array,
204 | convertUTF8ArrayToUTF16Array:convertUTF8ArrayToUTF16Array,
205 | convertByteArrayToUTF16Array:convertUTF8ArrayToUTF16Array,
206 | convertUTF8StringToBase64:convertUTF8StringToBase64,
207 | convertBase64ToUTF8String:convertBase64ToUTF8String,
208 | convertUTF8StringToUTF16Array:convertUTF8StringToUTF16Array,
209 | convertUTF8ArrayToUTF16String:convertUTF8ArrayToUTF16String,
210 | convertByteArrayToUTF16String:convertUTF8ArrayToUTF16String,
211 | convertUTF8StringToUTF16String:convertUTF8StringToUTF16String,
212 | convertUTF16StringToUTF8Array:convertUTF16StringToUTF8Array,
213 | convertUTF16StringToByteArray:convertUTF16StringToUTF8Array,
214 | convertUTF16ArrayToUTF8String:convertUTF16ArrayToUTF8String,
215 | convertUTF16StringToUTF8String:convertUTF16StringToUTF8String,
216 | convertUTF16StringToBase64:convertUTF16StringToBase64,
217 | convertBase64ToUTF16String:convertBase64ToUTF16String,
218 | fromBase64:convertBase64ToUTF8String,
219 | toBase64:convertUTF8StringToBase64,
220 | atob:atob,
221 | btoa:btoa,
222 | utob:convertUTF16StringToUTF8String,
223 | btou:convertUTF8StringToUTF16String,
224 | encode:convertUTF16StringToBase64,
225 | encodeURI:function(u){
226 | return convertUTF16StringToBase64(u).replace(/[+\/]/g, function(m0){
227 | return m0 == '+' ? '-' : '_';
228 | }).replace(/=+$/, '');
229 | },
230 | decode:function(a){
231 | return convertBase64ToUTF16String(a.replace(/[-_]/g, function(m0){
232 | return m0 == '-' ? '+' : '/';
233 | }));
234 | }
235 | };
236 |
237 | })(this);
238 |
--------------------------------------------------------------------------------
/css/zerobin.css:
--------------------------------------------------------------------------------
1 | /* ZeroBin 0.19 - http://sebsauvage.net/wiki/doku.php?id=php:zerobin */
2 |
3 |
4 | /* CSS Reset from YUI 3.4.1 (build 4118) - Copyright 2011 Yahoo! Inc. All rights reserved.
5 | Licensed under the BSD License. - http://yuilibrary.com/license/ */
6 | html{color:#000;background:#FFF}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal}ol,ul{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}q:before,q:after{content:''}abbr,acronym{border:0;font-variant:normal}sup{vertical-align:text-top}sub{vertical-align:text-bottom}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit}input,textarea,select{*font-size:100%}legend{color:#000}
7 |
8 | html {
9 | background-color:#455463;
10 | color:white;
11 | min-height:100%;
12 | background-image: linear-gradient(bottom, #0F1823 0%, #455463 100%);
13 | background-image: -o-linear-gradient(bottom, #0F1823 0%, #455463 100%);
14 | background-image: -moz-linear-gradient(bottom, #0F1823 0%, #455463 100%);
15 | background-image: -webkit-linear-gradient(bottom, #0F1823 0%, #455463 100%);
16 | background-image: -ms-linear-gradient(bottom, #0F1823 0%, #455463 100%);
17 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #0F1823), color-stop(1, #455463));
18 | }
19 |
20 | body {
21 | font-family: Arial, Helvetica, sans-serif;
22 | font-size: 0.8em;
23 | margin-bottom:15px;
24 | padding-left:60px; padding-right:60px;
25 | }
26 |
27 | a { color:#0F388F; }
28 |
29 | h1 {
30 | font-size:3.5em;
31 | font-weight:700;
32 | color:#000;
33 | position:relative;
34 | display:inline;
35 | cursor:pointer;
36 | }
37 |
38 | h1:before {
39 | content:attr(title);
40 | position:absolute;
41 | color:rgba(255,255,255,0.15);
42 | top:1px;
43 | left:1px;
44 | cursor:pointer;
45 | }
46 |
47 | h2 {
48 | color:#000;
49 | font-size:1em;
50 | display:inline;
51 | font-style:italic;
52 | font-weight:bold;
53 | position:relative;
54 | bottom:8px;}
55 |
56 | h3 {
57 | color:#94a3b4;
58 | font-size:0.7em;
59 | display:inline;
60 | position:relative;
61 | bottom:8px;}
62 |
63 | #aboutbox {
64 | font-size:0.85em;
65 | color: #94a3b4;
66 | padding: 4px 8px 4px 16px;
67 | position:relative;
68 | top:10px;
69 | border-left: 2px solid #94a3b4;
70 | float:right;
71 | width:60%;
72 | }
73 |
74 | div#aboutbox a { color: #94a3b4; }
75 | textarea#message,div#cleartext,.replymessage, code {
76 | clear:both;
77 | color:black;
78 | background-color:#fff;
79 | white-space:pre-wrap;
80 | font-family:Consolas,"Lucida Console","DejaVu Sans Mono",Monaco,monospace;
81 | font-size:9pt;
82 | border: 1px solid #28343F;
83 | padding:5px;
84 | box-sizing:border-box;
85 | -webkit-box-sizing:border-box;
86 | -moz-box-sizing:border-box;
87 | -ms-box-sizing:border-box;
88 | -o-box-sizing:border-box;
89 | width:100%;
90 | }
91 |
92 |
93 | div#status {
94 | clear:both;
95 | padding:5px 10px;
96 | }
97 |
98 |
99 | div#pasteresult {
100 | background-color:#1F2833;
101 | color:white;
102 | padding:4px 12px;
103 | clear:both;
104 | -moz-box-shadow: inset 0px 2px 2px #000;
105 | -webkit-box-shadow: inset 0px 2px 2px #000;
106 | box-shadow: inset 0px 2px 5px #000;
107 | }
108 | div#pasteresult a { color:white; }
109 | div#pasteresult button { margin-left:11px }
110 | div#deletelink { float:right; }
111 | div#toolbar, div#status { margin-bottom:5px; }
112 | #copyhint { color: #666; font-size:8pt; }
113 |
114 | button,.button,div#expiration {
115 | color:#fff;
116 | background-color:#323B47;
117 | background-repeat:no-repeat;
118 | background-position:center left;
119 | padding:4px 8px;
120 | font-size:1em;
121 | margin-right:5px;
122 | display:inline;
123 | background-image: linear-gradient(bottom, #323B47 0%, #51606E 100%);
124 | background-image: -o-linear-gradient(bottom, #323B47 0%, #51606E 100%);
125 | background-image: -moz-linear-gradient(bottom, #323B47 0%, #51606E 100%);
126 | background-image: -webkit-linear-gradient(bottom, #323B47 0%, #51606E 100%);
127 | background-image: -ms-linear-gradient(bottom, #323B47 0%, #51606E 100%);
128 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #323B47), color-stop(1, #51606E));
129 | border: 1px solid #28343F;
130 | -moz-box-shadow: inset 0px 1px 2px #647384;
131 | -webkit-box-shadow: inset 0px 1px 2px #647384;
132 | box-shadow: inset 0px 1px 2px #647384;
133 | -webkit-border-radius: 3px;
134 | -moz-border-radius: 3px;
135 | border-radius: 3px;
136 | -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box;
137 | }
138 | button:hover {
139 | background-image: linear-gradient(bottom, #424B57 0%, #61707E 100%);
140 | background-image: -o-linear-gradient(bottom, #424B57 0%, #61707E 100%);
141 | background-image: -moz-linear-gradient(bottom, #424B57 0%, #61707E 100%);
142 | background-image: -webkit-linear-gradient(bottom, #424B57 0%, #61707E 100%);
143 | background-image: -ms-linear-gradient(bottom, #424B57 0%, #61707E 100%);
144 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #424B57), color-stop(1, #61707E));
145 | }
146 | button:active {
147 | background-image: linear-gradient(bottom, #51606E 0%, #323B47 100%);
148 | background-image: -o-linear-gradient(bottom, #51606E 0%, #323B47 100%);
149 | background-image: -moz-linear-gradient(bottom, #51606E 0%, #323B47 100%);
150 | background-image: -webkit-linear-gradient(bottom, #51606E 0%, #323B47 100%);
151 | background-image: -ms-linear-gradient(bottom, #51606E 0%, #323B47 100%);
152 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #51606E), color-stop(1, #323B47));
153 | position:relative;
154 | top:1px;
155 | }
156 | button:disabled, .buttondisabled {
157 | background:#ccc;
158 | color:#888;
159 | top:0px;
160 | }
161 | button img {
162 | margin-right:8px;
163 | position:relative;
164 | top:2px;
165 | }
166 |
167 | div#expiration, div#rawtextbutton, div#burnafterreadingoption, div#opendisc, div#syntaxcoloringoption {
168 | background-color:#414D5A;
169 | padding:6px 8px;
170 | margin:0px 5px 0px 0px;;
171 | position: relative;
172 | bottom:1px; /* WTF ? Why is this shifted by 1 pixel ? */
173 | }
174 | div#expiration select {
175 | color:#eee;
176 | background: transparent;
177 | border: none;
178 | }
179 |
180 |
181 | div#expiration select option {
182 | color:#eee;
183 | background: #414D5A;
184 | background-color:#414D5A;
185 | }
186 |
187 | div#remainingtime {
188 | color: #94a3b4;
189 | display:inline;
190 | font-size:0.85em;
191 | }
192 |
193 | .foryoureyesonly {
194 | color: yellow !important;
195 | font-size: 1em !important;
196 | font-weight:bold !important;
197 | }
198 |
199 | button#newbutton { float:right; margin-right:0px;margin-bottom:5px; display:inline; }
200 | input { color:#777; font-size:1em; padding:6px; border: 1px solid #28343F; }
201 |
202 | .nonworking {
203 | background-color:#fff;
204 | color:#000;
205 | width:100%;
206 | text-align:center;
207 | font-weight:bold;
208 | font-size:10pt;
209 | -webkit-border-radius:4px;
210 | -moz-border-radius:4px;
211 | border-radius:4px;
212 | padding:5px;
213 | }
214 |
215 | div#ienotice {
216 | background-color:#7E98AF;
217 | color:#000;
218 | font-size:0.85em;
219 | padding:3px 5px;
220 | text-align:center;
221 | -webkit-border-radius:4px;
222 | -moz-border-radius:4px;
223 | border-radius:4px;
224 | display:none;
225 | }
226 |
227 | div#ienotice a {
228 | color:black;
229 | }
230 |
231 | div#oldienotice {
232 | display:none;
233 | }
234 |
235 | .errorMessage {
236 | background-color:#FF7979 !important;
237 | color:#FF0;
238 | }
239 |
240 |
241 | /* --- discussion related CSS ------- */
242 |
243 |
244 | div#discussion { /* Discussion container */
245 | margin-top:20px;
246 | width:100%;
247 | margin-left:-30px;
248 | min-width:200px;
249 | }
250 |
251 | h4 {
252 | font-size:1.2em;
253 | color: #94A3B4;
254 | font-style:italic;
255 | font-weight:bold;
256 | position:relative;
257 | margin-left:30px;
258 | }
259 |
260 |
261 | div.comment /* One single reply */
262 | {
263 | background-color:#CECED6;
264 | color:#000;
265 | white-space:pre-wrap;
266 | font-family:Consolas,"Lucida Console","DejaVu Sans Mono",Monaco,monospace;
267 | font-size:9pt;
268 | border-left: 1px solid #859AAE;
269 | border-top: 1px solid #859AAE;
270 | padding:5px 0px 5px 5px;
271 | margin-left:30px;
272 | -moz-box-shadow: -3px -3px 5px rgba(0,0,0,0.15);
273 | -webkit-box-shadow: -3px -3px 5px rgba(0,0,0,0.15);
274 | box-shadow: -3px -3px 5px rgba(0,0,0,0.15);
275 | min-width:200px;
276 | overflow:auto;
277 | }
278 | /* FIXME: Add min-width */
279 |
280 | div.reply {
281 | margin: 5px 0px 0px 30px;
282 | }
283 |
284 | div#replystatus {
285 | display:inline;
286 | padding:1px 7px;
287 | font-family: Arial, Helvetica, sans-serif;
288 | }
289 |
290 | div.comment button {
291 | color:#446;
292 | background-color:#aab;
293 | background-repeat:no-repeat;
294 | background-position:center left;
295 | padding:0px 2px;
296 | font-size:0.73em;
297 | margin: 3px 5px 3px 0px;
298 | display:inline;
299 | background-image: linear-gradient(bottom, #aab 0%, #ccc 100%);
300 | background-image: -o-linear-gradient(bottom, #aab 0%, #ccc 100%);
301 | background-image: -moz-linear-gradient(bottom, #aab 0%, #ccc 100%);
302 | background-image: -webkit-linear-gradient(bottom, #aab 0%, #ccc 100%);
303 | background-image: -ms-linear-gradient(bottom, #aab 0%, #ccc 100%);
304 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #aab), color-stop(1, #ccc));
305 | border: 1px solid #ccd;
306 | -moz-box-shadow: inset 0px 1px 2px #ddd;
307 | -webkit-box-shadow: inset 0px 1px 2px #fff;
308 | box-shadow: inset 0px 1px 2px #eee;
309 | -webkit-border-radius: 3px;
310 | -moz-border-radius: 3px;
311 | border-radius: 3px;
312 | -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box;
313 | }
314 | div.comment button:hover {
315 | background-image: linear-gradient(bottom, #ccd 0%, #fff 100%);
316 | background-image: -o-linear-gradient(bottom, #ccd 0%, #fff 100%);
317 | background-image: -moz-linear-gradient(bottom, #ccd 0%, #fff 100%);
318 | background-image: -webkit-linear-gradient(bottom, #ccd 0%, #fff 100%);
319 | background-image: -ms-linear-gradient(bottom, #ccd 0%, #fff 100%);
320 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccd), color-stop(1, #fff));
321 | }
322 | div.comment button:active {
323 | background-image: linear-gradient(bottom, #fff 0%, #889 100%);
324 | background-image: -o-linear-gradient(bottom, #fff 0%, #889 100%);
325 | background-image: -moz-linear-gradient(bottom, #fff 0%, #889 100%);
326 | background-image: -webkit-linear-gradient(bottom, #fff 0%, #889 100%);
327 | background-image: -ms-linear-gradient(bottom, #fff 0%, #889 100%);
328 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(1, #889));
329 | position:relative;
330 | top:1px;
331 | }
332 |
333 | div.comment input {
334 | padding:2px;
335 | }
336 |
337 | textarea#replymessage {
338 | margin-top:5px;
339 | }
340 |
341 | div.commentmeta {
342 | color: #fff;
343 | background-color:#8EA0B2;
344 | margin-bottom:3px;
345 | padding:0px 0px 0px 3px;
346 | }
347 |
348 | span.commentdate {
349 | color: #BFCEDE;
350 | }
351 |
352 | img.vizhash {
353 | width:16px;
354 | height:16px;
355 | position:relative;
356 | top:2px;
357 | left:-3px;
358 | }
359 |
360 | pre a {
361 | color:#58A5B4;
362 | }
363 |
364 | pre a:hover {
365 | color:#64B9C6;
366 | }
367 |
368 |
369 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 | ", LOCK_EX);
28 | chmod($tfilename,0705);
29 | }
30 | require $tfilename;
31 | $tl=$GLOBALS['trafic_limiter'];
32 | if (!empty($tl[$ip]) && ($tl[$ip]+10>=time()))
33 | {
34 | return false;
35 | // FIXME: purge file of expired IPs to keep it small
36 | }
37 | $tl[$ip]=time();
38 | file_put_contents($tfilename, "", LOCK_EX);
39 | return true;
40 | }
41 |
42 | // Constant time string comparison.
43 | // (Used to deter time attacks on hmac checking. See section 2.7 of https://defuse.ca/audits/zerobin.htm)
44 | function slow_equals($a, $b)
45 | {
46 | $diff = strlen($a) ^ strlen($b);
47 | for($i = 0; $i < strlen($a) && $i < strlen($b); $i++)
48 | {
49 | $diff |= ord($a[$i]) ^ ord($b[$i]);
50 | }
51 | return $diff === 0;
52 | }
53 |
54 |
55 | /* Convert paste id to storage path.
56 | The idea is to creates subdirectories in order to limit the number of files per directory.
57 | (A high number of files in a single directory can slow things down.)
58 | eg. "f468483c313401e8" will be stored in "data/f4/68/f468483c313401e8"
59 | High-trafic websites may want to deepen the directory structure (like Squid does).
60 |
61 | eg. input 'e3570978f9e4aa90' --> output 'data/e3/57/'
62 | */
63 | function dataid2path($dataid)
64 | {
65 | return 'data/'.substr($dataid,0,2).'/'.substr($dataid,2,2).'/';
66 | }
67 |
68 | /* Convert paste id to discussion storage path.
69 | eg. 'e3570978f9e4aa90' --> 'data/e3/57/e3570978f9e4aa90.discussion/'
70 | */
71 | function dataid2discussionpath($dataid)
72 | {
73 | return dataid2path($dataid).$dataid.'.discussion/';
74 | }
75 |
76 | // Checks if a json string is a proper SJCL encrypted message.
77 | // False if format is incorrect.
78 | function validSJCL($jsonstring)
79 | {
80 | $accepted_keys=array('iv','v','iter','ks','ts','mode','adata','cipher','salt','ct');
81 |
82 | // Make sure content is valid json
83 | $decoded = json_decode($jsonstring);
84 | if ($decoded==null) return false;
85 | $decoded = (array)$decoded;
86 |
87 | // Make sure required fields are present
88 | foreach($accepted_keys as $k)
89 | {
90 | if (!array_key_exists($k,$decoded)) { return false; }
91 | }
92 |
93 | // Make sure some fields are base64 data
94 | if (base64_decode($decoded['iv'],$strict=true)==null) { return false; }
95 | if (base64_decode($decoded['salt'],$strict=true)==null) { return false; }
96 | if (base64_decode($decoded['cipher'],$strict=true)==null) { return false; }
97 |
98 | // Make sure no additionnal keys were added.
99 | if (count(array_intersect(array_keys($decoded),$accepted_keys))!=10) { return false; }
100 |
101 | // Reject data if entropy is too low
102 | $ct = base64_decode($decoded['ct'], $strict=true);
103 | if (strlen($ct) > strlen(gzdeflate($ct))) return false;
104 |
105 | // Make sure some fields have a reasonable size.
106 | if (strlen($decoded['iv'])>24) return false;
107 | if (strlen($decoded['salt'])>14) return false;
108 | return true;
109 | }
110 |
111 | // Delete a paste and its discussion.
112 | // Input: $pasteid : the paste identifier.
113 | function deletePaste($pasteid)
114 | {
115 | // Delete the paste itself
116 | unlink(dataid2path($pasteid).$pasteid);
117 |
118 | // Delete discussion if it exists.
119 | $discdir = dataid2discussionpath($pasteid);
120 | if (is_dir($discdir))
121 | {
122 | // Delete all files in discussion directory
123 | $dhandle = opendir($discdir);
124 | while (false !== ($filename = readdir($dhandle)))
125 | {
126 | if (is_file($discdir.$filename)) unlink($discdir.$filename);
127 | }
128 | closedir($dhandle);
129 |
130 | // Delete the discussion directory.
131 | rmdir($discdir);
132 | }
133 | }
134 |
135 | if (!empty($_POST['data'])) // Create new paste/comment
136 | {
137 | /* POST contains:
138 | data (mandatory) = json encoded SJCL encrypted text (containing keys: iv,salt,ct)
139 |
140 | All optional data will go to meta information:
141 | expire (optional) = expiration delay (never,5min,10min,1hour,1day,1week,1month,1year,burn) (default:never)
142 | opendiscusssion (optional) = is the discussion allowed on this paste ? (0/1) (default:0)
143 | syntaxcoloring (optional) = should this paste use syntax coloring when displaying.
144 | nickname (optional) = son encoded SJCL encrypted text nickname of author of comment (containing keys: iv,salt,ct)
145 | parentid (optional) = in discussion, which comment this comment replies to.
146 | pasteid (optional) = in discussion, which paste this comment belongs to.
147 | */
148 |
149 | header('Content-type: application/json');
150 | $error = false;
151 |
152 | // Create storage directory if it does not exist.
153 | if (!is_dir('data'))
154 | {
155 | mkdir('data',0705);
156 | file_put_contents('data/.htaccess',"Allow from none\nDeny from all\n", LOCK_EX);
157 | }
158 |
159 | // Make sure last paste from the IP address was more than 10 seconds ago.
160 | if (!trafic_limiter_canPass($_SERVER['REMOTE_ADDR']))
161 | { echo json_encode(array('status'=>1,'message'=>'Please wait 10 seconds between each post.')); exit; }
162 |
163 | // Make sure content is not too big.
164 | $data = $_POST['data'];
165 | if (strlen($data)>2000000)
166 | { echo json_encode(array('status'=>1,'message'=>'Paste is limited to 2 Mb of encrypted data.')); exit; }
167 |
168 | // Make sure format is correct.
169 | if (!validSJCL($data))
170 | { echo json_encode(array('status'=>1,'message'=>'Invalid data.')); exit; }
171 |
172 | // Read additional meta-information.
173 | $meta=array();
174 |
175 | // Read expiration date
176 | if (!empty($_POST['expire']))
177 | {
178 | $expire=$_POST['expire'];
179 | if ($expire=='5min') $meta['expire_date']=time()+5*60;
180 | elseif ($expire=='10min') $meta['expire_date']=time()+10*60;
181 | elseif ($expire=='1hour') $meta['expire_date']=time()+60*60;
182 | elseif ($expire=='1day') $meta['expire_date']=time()+24*60*60;
183 | elseif ($expire=='1week') $meta['expire_date']=time()+7*24*60*60;
184 | elseif ($expire=='1month') $meta['expire_date']=time()+30*24*60*60; // Well this is not *exactly* one month, it's 30 days.
185 | elseif ($expire=='1year') $meta['expire_date']=time()+365*24*60*60;
186 | }
187 |
188 | // Destroy the paste when it is read.
189 | if (!empty($_POST['burnafterreading']))
190 | {
191 | $burnafterreading = $_POST['burnafterreading'];
192 | if ($burnafterreading!='0' && $burnafterreading!='1') { $error=true; }
193 | if ($burnafterreading!='0') { $meta['burnafterreading']=true; }
194 | }
195 |
196 | // Read open discussion flag
197 | if (!empty($_POST['opendiscussion']))
198 | {
199 | $opendiscussion = $_POST['opendiscussion'];
200 | if ($opendiscussion!='0' && $opendiscussion!='1') { $error=true; }
201 | if ($opendiscussion!='0') { $meta['opendiscussion']=true; }
202 | }
203 |
204 | // Should we use syntax coloring when displaying ?
205 | if (!empty($_POST['syntaxcoloring']))
206 | {
207 | $syntaxcoloring = $_POST['syntaxcoloring'];
208 | if ($syntaxcoloring!='0' && $syntaxcoloring!='1') { $error=true; }
209 | if ($syntaxcoloring!='0') { $meta['syntaxcoloring']=true; }
210 | }
211 |
212 | // You can't have an open discussion on a "Burn after reading" paste:
213 | if (isset($meta['burnafterreading'])) unset($meta['opendiscussion']);
214 |
215 | // Optional nickname for comments
216 | if (!empty($_POST['nickname']))
217 | {
218 | $nick = $_POST['nickname'];
219 | if (!validSJCL($nick))
220 | {
221 | $error=true;
222 | }
223 | else
224 | {
225 | $meta['nickname']=$nick;
226 |
227 | // Generation of the anonymous avatar (Vizhash):
228 | // If a nickname is provided, we generate a Vizhash.
229 | // (We assume that if the user did not enter a nickname, he/she wants
230 | // to be anonymous and we will not generate the vizhash.)
231 | $vz = new vizhash16x16();
232 | $pngdata = $vz->generate($_SERVER['REMOTE_ADDR']);
233 | if ($pngdata!='') $meta['vizhash'] = 'data:image/png;base64,'.base64_encode($pngdata);
234 | // Once the avatar is generated, we do not keep the IP address, nor its hash.
235 | }
236 | }
237 |
238 | if ($error)
239 | {
240 | echo json_encode(array('status'=>1,'message'=>'Invalid data.'));
241 | exit;
242 | }
243 |
244 | // Add post date to meta.
245 | $meta['postdate']=time();
246 |
247 | // We just want a small hash to avoid collisions: Half-MD5 (64 bits) will do the trick.
248 | $dataid = substr(hash('md5',$data),0,16);
249 |
250 | $is_comment = (!empty($_POST['parentid']) && !empty($_POST['pasteid'])); // Is this post a comment ?
251 | $storage = array('data'=>$data);
252 | if (count($meta)>0) $storage['meta'] = $meta; // Add meta-information only if necessary.
253 |
254 | if ($is_comment) // The user posts a comment.
255 | {
256 | $pasteid = $_POST['pasteid'];
257 | $parentid = $_POST['parentid'];
258 | if (!preg_match('/\A[a-f\d]{16}\z/',$pasteid)) { echo json_encode(array('status'=>1,'message'=>'Invalid data.')); exit; }
259 | if (!preg_match('/\A[a-f\d]{16}\z/',$parentid)) { echo json_encode(array('status'=>1,'message'=>'Invalid data.')); exit; }
260 |
261 | unset($storage['expire_date']); // Comment do not expire (it's the paste that expires)
262 | unset($storage['opendiscussion']);
263 | unset($storage['syntaxcoloring']);
264 |
265 | // Make sure paste exists.
266 | $storagedir = dataid2path($pasteid);
267 | if (!is_file($storagedir.$pasteid)) { echo json_encode(array('status'=>1,'message'=>'Invalid data.')); exit; }
268 |
269 | // Make sure the discussion is opened in this paste.
270 | $paste=json_decode(file_get_contents($storagedir.$pasteid));
271 | if (!$paste->meta->opendiscussion) { echo json_encode(array('status'=>1,'message'=>'Invalid data.')); exit; }
272 |
273 | $discdir = dataid2discussionpath($pasteid);
274 | $filename = $pasteid.'.'.$dataid.'.'.$parentid;
275 | if (!is_dir($discdir)) mkdir($discdir,$mode=0705,$recursive=true);
276 | if (is_file($discdir.$filename)) // Oups... improbable collision.
277 | {
278 | echo json_encode(array('status'=>1,'message'=>'You are unlucky. Try again.'));
279 | exit;
280 | }
281 |
282 | file_put_contents($discdir.$filename,json_encode($storage), LOCK_EX);
283 | echo json_encode(array('status'=>0,'id'=>$dataid)); // 0 = no error
284 | exit;
285 | }
286 | else // a standard paste.
287 | {
288 | $storagedir = dataid2path($dataid);
289 | if (!is_dir($storagedir)) mkdir($storagedir,$mode=0705,$recursive=true);
290 | if (is_file($storagedir.$dataid)) // Oups... improbable collision.
291 | {
292 | echo json_encode(array('status'=>1,'message'=>'You are unlucky. Try again.'));
293 | exit;
294 | }
295 | // New paste
296 | file_put_contents($storagedir.$dataid,json_encode($storage), LOCK_EX);
297 |
298 | // Generate the "delete" token.
299 | // The token is the hmac of the pasteid signed with the server salt.
300 | // The paste can be delete by calling http://myserver.com/zerobin/?pasteid=&deletetoken=
301 | $deletetoken = hash_hmac('sha1', $dataid , getServerSalt());
302 |
303 | echo json_encode(array('status'=>0,'id'=>$dataid,'deletetoken'=>$deletetoken)); // 0 = no error
304 | exit;
305 | }
306 |
307 | echo json_encode(array('status'=>1,'message'=>'Server error.'));
308 | exit;
309 | }
310 |
311 | /* Process a paste deletion request.
312 | Returns an array ('',$ERRORMESSAGE,$STATUS)
313 | */
314 | function processPasteDelete($pasteid,$deletetoken)
315 | {
316 | if (preg_match('/\A[a-f\d]{16}\z/',$pasteid)) // Is this a valid paste identifier ?
317 | {
318 | $filename = dataid2path($pasteid).$pasteid;
319 | if (!is_file($filename)) // Check that paste exists.
320 | {
321 | return array('','Paste does not exist, has expired or has been deleted.','');
322 | }
323 | }
324 | else
325 | {
326 | return array('','Invalid data','');
327 | }
328 |
329 | if (!slow_equals($deletetoken, hash_hmac('sha1', $pasteid , getServerSalt()))) // Make sure token is valid.
330 | {
331 | return array('','Wrong deletion token. Paste was not deleted.','');
332 | }
333 |
334 | // Paste exists and deletion token is valid: Delete the paste.
335 | deletePaste($pasteid);
336 | return array('','','Paste was properly deleted.');
337 | }
338 |
339 | /* Process a paste fetch request.
340 | Returns an array ($CIPHERDATA,$ERRORMESSAGE,$STATUS)
341 | */
342 | function processPasteFetch($pasteid)
343 | {
344 | if (preg_match('/\A[a-f\d]{16}\z/',$pasteid)) // Is this a valid paste identifier ?
345 | {
346 | $filename = dataid2path($pasteid).$pasteid;
347 | if (!is_file($filename)) // Check that paste exists.
348 | {
349 | return array('','Paste does not exist, has expired or has been deleted.','');
350 | }
351 | }
352 | else
353 | {
354 | return array('','Invalid data','');
355 | }
356 |
357 | // Get the paste itself.
358 | $paste=json_decode(file_get_contents($filename));
359 |
360 | // See if paste has expired.
361 | if (isset($paste->meta->expire_date) && $paste->meta->expire_date