zerobin.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  1. /**
  2. * ZeroBin
  3. *
  4. * a zero-knowledge paste bin
  5. *
  6. * @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
  7. * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  8. * @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
  9. * @version 0.20
  10. */
  11. // Immediately start random number generator collector.
  12. sjcl.random.startCollectors();
  13. $(function(){
  14. 'use strict';
  15. /**
  16. * static helper methods
  17. */
  18. var helper = {
  19. /**
  20. * Converts a duration (in seconds) into human readable format.
  21. *
  22. * @param int seconds
  23. * @return string
  24. */
  25. secondsToHuman: function(seconds)
  26. {
  27. if (seconds < 60)
  28. {
  29. var v = Math.floor(seconds);
  30. return v + ' second' + ((v > 1) ? 's' : '');
  31. }
  32. if (seconds < 60 * 60)
  33. {
  34. var v = Math.floor(seconds / 60);
  35. return v + ' minute' + ((v > 1) ? 's' : '');
  36. }
  37. if (seconds < 60 * 60 * 24)
  38. {
  39. var v = Math.floor(seconds / (60 * 60));
  40. return v + ' hour' + ((v > 1) ? 's' : '');
  41. }
  42. // If less than 2 months, display in days:
  43. if (seconds < 60 * 60 * 24 * 60)
  44. {
  45. var v = Math.floor(seconds / (60 * 60 * 24));
  46. return v + ' day' + ((v > 1) ? 's' : '');
  47. }
  48. var v = Math.floor(seconds / (60 * 60 * 24 * 30));
  49. return v + ' month' + ((v > 1) ? 's' : '');
  50. },
  51. /**
  52. * Converts an associative array to an encoded string
  53. * for appending to the anchor.
  54. *
  55. * @param object associative_array Object to be serialized
  56. * @return string
  57. */
  58. hashToParameterString: function(associativeArray)
  59. {
  60. var parameterString = '';
  61. for (key in associativeArray)
  62. {
  63. if(parameterString === '')
  64. {
  65. parameterString = encodeURIComponent(key);
  66. parameterString += '=' + encodeURIComponent(associativeArray[key]);
  67. }
  68. else
  69. {
  70. parameterString += '&' + encodeURIComponent(key);
  71. parameterString += '=' + encodeURIComponent(associativeArray[key]);
  72. }
  73. }
  74. // padding for URL shorteners
  75. parameterString += '&p=p';
  76. return parameterString;
  77. },
  78. /**
  79. * Converts a string to an associative array.
  80. *
  81. * @param string parameter_string String containing parameters
  82. * @return object
  83. */
  84. parameterStringToHash: function(parameterString)
  85. {
  86. var parameterHash = {};
  87. var parameterArray = parameterString.split('&');
  88. for (var i = 0; i < parameterArray.length; i++)
  89. {
  90. var pair = parameterArray[i].split('=');
  91. var key = decodeURIComponent(pair[0]);
  92. var value = decodeURIComponent(pair[1]);
  93. parameterHash[key] = value;
  94. }
  95. return parameterHash;
  96. },
  97. /**
  98. * Get an associative array of the parameters found in the anchor
  99. *
  100. * @return object
  101. */
  102. getParameterHash: function()
  103. {
  104. var hashIndex = window.location.href.indexOf('#');
  105. if (hashIndex >= 0)
  106. {
  107. return this.parameterStringToHash(window.location.href.substring(hashIndex + 1));
  108. }
  109. else
  110. {
  111. return {};
  112. }
  113. },
  114. /**
  115. * Convert all applicable characters to HTML entities
  116. *
  117. * @param string str
  118. * @returns string encoded string
  119. */
  120. htmlEntities: function(str)
  121. {
  122. return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
  123. },
  124. /**
  125. * Text range selection.
  126. * From: http://stackoverflow.com/questions/985272/jquery-selecting-text-in-an-element-akin-to-highlighting-with-your-mouse
  127. *
  128. * @param string element : Indentifier of the element to select (id="").
  129. */
  130. selectText: function(element)
  131. {
  132. var doc = document,
  133. text = doc.getElementById(element),
  134. range,
  135. selection;
  136. // MS
  137. if (doc.body.createTextRange)
  138. {
  139. range = doc.body.createTextRange();
  140. range.moveToElementText(text);
  141. range.select();
  142. }
  143. // all others
  144. else if (window.getSelection)
  145. {
  146. selection = window.getSelection();
  147. range = doc.createRange();
  148. range.selectNodeContents(text);
  149. selection.removeAllRanges();
  150. selection.addRange(range);
  151. }
  152. },
  153. /**
  154. * Set text of a DOM element (required for IE)
  155. * This is equivalent to element.text(text)
  156. *
  157. * @param object element : a DOM element.
  158. * @param string text : the text to enter.
  159. */
  160. setElementText: function(element, text)
  161. {
  162. // For IE<10: Doesn't support white-space:pre-wrap; so we have to do this BIG UGLY STINKING THING.
  163. if ($('#oldienotice').is(':visible')) {
  164. var html = this.htmlEntities(text).replace(/\n/ig,'\r\n<br>');
  165. element.html('<pre>'+html+'</pre>');
  166. }
  167. // for other (sane) browsers:
  168. else
  169. {
  170. element.text(text);
  171. }
  172. },
  173. /**
  174. * Convert URLs to clickable links.
  175. * URLs to handle:
  176. * <code>
  177. * magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7
  178. * http://localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
  179. * http://user:password@localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
  180. * </code>
  181. *
  182. * @param object element : a jQuery DOM element.
  183. */
  184. urls2links: function(element)
  185. {
  186. element.html(
  187. element.html().replace(
  188. /((http|https|ftp):\/\/[\w?=&.\/-;#@~%+-]+(?![\w\s?&.\/;#~%"=-]*>))/ig,
  189. '<a href="$1" rel="nofollow">$1</a>'
  190. )
  191. );
  192. element.html(
  193. element.html().replace(
  194. /((magnet):[\w?=&.\/-;#@~%+-]+)/ig,
  195. '<a href="$1">$1</a>'
  196. )
  197. );
  198. }
  199. };
  200. /**
  201. * filter methods
  202. */
  203. var filter = {
  204. /**
  205. * Compress a message (deflate compression). Returns base64 encoded data.
  206. *
  207. * @param string message
  208. * @return base64 string data
  209. */
  210. compress: function(message)
  211. {
  212. return Base64.toBase64( RawDeflate.deflate( Base64.utob(message) ) );
  213. },
  214. /**
  215. * Decompress a message compressed with compress().
  216. *
  217. * @param base64 string data
  218. * @return string message
  219. */
  220. decompress: function(data)
  221. {
  222. return Base64.btou( RawDeflate.inflate( Base64.fromBase64(data) ) );
  223. },
  224. /**
  225. * Compress, then encrypt message with key.
  226. *
  227. * @param string key
  228. * @param string password
  229. * @param string message
  230. * @return encrypted string data
  231. */
  232. cipher: function(key, password, message)
  233. {
  234. password = password.trim();
  235. if (password.length == 0)
  236. {
  237. return sjcl.encrypt(key, this.compress(message));
  238. }
  239. return sjcl.encrypt(key + sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(password)), this.compress(message));
  240. },
  241. /**
  242. * Decrypt message with key, then decompress.
  243. *
  244. * @param string key
  245. * @param string password
  246. * @param encrypted string data
  247. * @return string readable message
  248. */
  249. decipher: function(key, password, data)
  250. {
  251. if (data != undefined)
  252. {
  253. try
  254. {
  255. return this.decompress(sjcl.decrypt(key, data));
  256. }
  257. catch(err)
  258. {
  259. try
  260. {
  261. return this.decompress(sjcl.decrypt(key + sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(password)), data));
  262. }
  263. catch(err)
  264. {}
  265. }
  266. }
  267. return '';
  268. }
  269. };
  270. var zerobin = {
  271. /**
  272. * Get the current script location (without search or hash part of the URL).
  273. * eg. http://server.com/zero/?aaaa#bbbb --> http://server.com/zero/
  274. *
  275. * @return string current script location
  276. */
  277. scriptLocation: function()
  278. {
  279. var scriptLocation = window.location.href.substring(0,window.location.href.length
  280. - window.location.search.length - window.location.hash.length);
  281. var hashIndex = scriptLocation.indexOf('#');
  282. if (hashIndex !== -1)
  283. {
  284. scriptLocation = scriptLocation.substring(0, hashIndex);
  285. }
  286. return scriptLocation;
  287. },
  288. /**
  289. * Get the pastes unique identifier from the URL
  290. * eg. http://server.com/zero/?c05354954c49a487#xxx --> c05354954c49a487
  291. *
  292. * @return string unique identifier
  293. */
  294. pasteID: function()
  295. {
  296. return window.location.search.substring(1);
  297. },
  298. /**
  299. * Return the deciphering key stored in anchor part of the URL
  300. *
  301. * @return string key
  302. */
  303. pageKey: function()
  304. {
  305. var key = window.location.hash.substring(1); // Get key
  306. // Some stupid web 2.0 services and redirectors add data AFTER the anchor
  307. // (such as &utm_source=...).
  308. // We will strip any additional data.
  309. // First, strip everything after the equal sign (=) which signals end of base64 string.
  310. var i = key.indexOf('=');
  311. if (i > -1)
  312. {
  313. key = key.substring(0, i + 1);
  314. }
  315. // If the equal sign was not present, some parameters may remain:
  316. i = key.indexOf('&');
  317. if (i > -1)
  318. {
  319. key = key.substring(0, i);
  320. }
  321. // Then add trailing equal sign if it's missing
  322. if (key.charAt(key.length - 1) !== '=') key += '=';
  323. return key;
  324. },
  325. /**
  326. * ask the user for the password and return it
  327. *
  328. * @throws error when dialog canceled
  329. * @return string password
  330. */
  331. requestPassword: function()
  332. {
  333. var password = prompt('Please enter the password for this paste:', '');
  334. if (password == null) throw 'password prompt canceled';
  335. if (password.length == 0) return this.requestPassword();
  336. return password;
  337. },
  338. /**
  339. * Show decrypted text in the display area, including discussion (if open)
  340. *
  341. * @param string key : decryption key
  342. * @param array comments : Array of messages to display (items = array with keys ('data','meta')
  343. */
  344. displayMessages: function(key, comments)
  345. {
  346. // Try to decrypt the paste.
  347. var password = this.passwordInput.val();
  348. if (!this.prettyPrint.hasClass('prettyprinted')) {
  349. try
  350. {
  351. var cleartext = filter.decipher(key, password, comments[0].data);
  352. if (cleartext.length == 0)
  353. {
  354. if (password.length == 0) password = this.requestPassword();
  355. cleartext = filter.decipher(key, password, comments[0].data);
  356. }
  357. if (cleartext.length == 0) throw 'failed to decipher message';
  358. this.passwordInput.val(password);
  359. helper.setElementText(this.clearText, cleartext);
  360. helper.setElementText(this.prettyPrint, cleartext);
  361. // Convert URLs to clickable links.
  362. helper.urls2links(this.clearText);
  363. helper.urls2links(this.prettyPrint);
  364. if (typeof prettyPrint == 'function') prettyPrint();
  365. }
  366. catch(err)
  367. {
  368. this.clearText.addClass('hidden');
  369. this.prettyMessage.addClass('hidden');
  370. this.cloneButton.addClass('hidden');
  371. this.showError('Could not decrypt data (Wrong key?)');
  372. return;
  373. }
  374. }
  375. // Display paste expiration.
  376. if (comments[0].meta.expire_date)
  377. {
  378. this.remainingTime.removeClass('foryoureyesonly')
  379. .text('This document will expire in ' + helper.secondsToHuman(comments[0].meta.remaining_time) + '.')
  380. .removeClass('hidden');
  381. }
  382. if (comments[0].meta.burnafterreading)
  383. {
  384. var parent = this;
  385. $.get(this.scriptLocation() + '?pasteid=' + this.pasteID() + '&deletetoken=burnafterreading', 'json')
  386. .fail(function() {
  387. parent.showError('Could not delete the paste, it was not stored in burn after reading mode.');
  388. });
  389. this.remainingTime.addClass('foryoureyesonly')
  390. .text('FOR YOUR EYES ONLY. Don\'t close this window, this message can\'t be displayed again.')
  391. .removeClass('hidden');
  392. // Discourage cloning (as it can't really be prevented).
  393. this.cloneButton.addClass('hidden');
  394. }
  395. // If the discussion is opened on this paste, display it.
  396. if (comments[0].meta.opendiscussion)
  397. {
  398. this.comments.html('');
  399. // iterate over comments
  400. for (var i = 1; i < comments.length; i++)
  401. {
  402. var place = this.comments;
  403. var comment=comments[i];
  404. var cleartext='[Could not decrypt comment; Wrong key?]';
  405. try
  406. {
  407. cleartext = filter.decipher(key, password, comment.data);
  408. }
  409. catch(err)
  410. {}
  411. // If parent comment exists, display below (CSS will automatically shift it right.)
  412. var cname = '#comment_' + comment.meta.parentid;
  413. // If the element exists in page
  414. if ($(cname).length)
  415. {
  416. place = $(cname);
  417. }
  418. var divComment = $('<article><div class="comment" id="comment_' + comment.meta.commentid+'">'
  419. + '<div class="commentmeta"><span class="nickname"></span><span class="commentdate"></span></div><div class="commentdata"></div>'
  420. + '<button class="btn btn-default btn-sm">Reply</button>'
  421. + '</div></article>');
  422. divComment.find('button').click({commentid: comment.meta.commentid}, $.proxy(this.openReply, this));
  423. helper.setElementText(divComment.find('div.commentdata'), cleartext);
  424. // Convert URLs to clickable links in comment.
  425. helper.urls2links(divComment.find('div.commentdata'));
  426. // Try to get optional nickname:
  427. var nick = filter.decipher(key, password, comment.meta.nickname);
  428. if (nick.length > 0)
  429. {
  430. divComment.find('span.nickname').text(nick);
  431. }
  432. else
  433. {
  434. divComment.find('span.nickname').html('<i>(Anonymous)</i>');
  435. }
  436. divComment.find('span.commentdate')
  437. .text(' ('+(new Date(comment.meta.postdate*1000).toString())+')')
  438. .attr('title','CommentID: ' + comment.meta.commentid);
  439. // If an avatar is available, display it.
  440. if (comment.meta.vizhash)
  441. {
  442. divComment.find('span.nickname')
  443. .before('<img src="' + comment.meta.vizhash + '" class="vizhash" title="Anonymous avatar (Vizhash of the IP address)" /> ');
  444. }
  445. place.append(divComment);
  446. }
  447. var divComment = $('<div class="comment"><button class="btn btn-default btn-sm">Add comment</button></div>');
  448. divComment.find('button').click({commentid: this.pasteID()}, $.proxy(this.openReply, this));
  449. this.comments.append(divComment);
  450. this.discussion.removeClass('hidden');
  451. }
  452. },
  453. /**
  454. * Open the comment entry when clicking the "Reply" button of a comment.
  455. *
  456. * @param Event event
  457. */
  458. openReply: function(event)
  459. {
  460. event.preventDefault();
  461. var source = $(event.target),
  462. commentid = event.data.commentid;
  463. // Remove any other reply area.
  464. $('div.reply').remove();
  465. var reply = $('<div class="reply">' +
  466. '<input type="text" id="nickname" class="form-control" title="Optional nickname..." placeholder="Optional nickname..." />' +
  467. '<textarea id="replymessage" class="replymessage form-control" cols="80" rows="7"></textarea>' +
  468. '<br /><button id="replybutton" class="btn btn-default btn-sm">Post comment</button>' +
  469. '<div id="replystatus"> </div>' +
  470. '</div>');
  471. reply.find('button').click({parentid: commentid}, $.proxy(this.sendComment, this));
  472. source.after(reply);
  473. $('#nickname').focus(function() {
  474. if ($(this).val() == $(this).attr('title')) $(this).val('');
  475. });
  476. $('#replymessage').focus();
  477. },
  478. /**
  479. * Send a reply in a discussion.
  480. *
  481. * @param Event event
  482. */
  483. sendComment: function(event)
  484. {
  485. event.preventDefault();
  486. this.errorMessage.addClass('hidden');
  487. // Do not send if no data.
  488. var replyMessage = $('#replymessage');
  489. if (replyMessage.val().length == 0) return;
  490. this.showStatus('Sending comment...', true);
  491. var parentid = event.data.parentid;
  492. var cipherdata = filter.cipher(this.pageKey(), this.passwordInput.val(), replyMessage.val());
  493. var ciphernickname = '';
  494. var nick = $('#nickname').val();
  495. if (nick != '' && nick != 'Optional nickname...')
  496. {
  497. ciphernickname = filter.cipher(this.pageKey(), this.passwordInput.val(), nick);
  498. }
  499. var data_to_send = {
  500. data: cipherdata,
  501. parentid: parentid,
  502. pasteid: this.pasteID(),
  503. nickname: ciphernickname
  504. };
  505. var parent = this;
  506. $.post(this.scriptLocation(), data_to_send, function(data)
  507. {
  508. if (data.status == 0)
  509. {
  510. parent.showStatus('Comment posted.', false);
  511. $.get(parent.scriptLocation() + '?' + parent.pasteID() + '&json', function(data)
  512. {
  513. if (data.status == 0)
  514. {
  515. parent.displayMessages(parent.pageKey(), data.messages);
  516. }
  517. else if (data.status == 1)
  518. {
  519. parent.showError('Could not refresh display: ' + data.message);
  520. }
  521. else
  522. {
  523. parent.showError('Could not refresh display: unknown status');
  524. }
  525. }, 'json')
  526. .fail(function() {
  527. parent.showError('Could not refresh display (server error or not responding).');
  528. });
  529. }
  530. else if (data.status == 1)
  531. {
  532. parent.showError('Could not post comment: ' + data.message);
  533. }
  534. else
  535. {
  536. parent.showError('Could not post comment: unknown status');
  537. }
  538. }, 'json')
  539. .fail(function() {
  540. parent.showError('Comment could not be sent (server error or not responding).');
  541. });
  542. },
  543. /**
  544. * Send a new paste to server
  545. *
  546. * @param Event event
  547. */
  548. sendData: function(event)
  549. {
  550. event.preventDefault();
  551. // Do not send if no data.
  552. if (this.message.val().length == 0) return;
  553. // If sjcl has not collected enough entropy yet, display a message.
  554. if (!sjcl.random.isReady())
  555. {
  556. this.showStatus('Sending paste (Please move your mouse for more entropy)...', true);
  557. sjcl.random.addEventListener('seeded', function() {
  558. this.sendData(event);
  559. });
  560. return;
  561. }
  562. this.showStatus('Sending paste...', true);
  563. var randomkey = sjcl.codec.base64.fromBits(sjcl.random.randomWords(8, 0), 0);
  564. var cipherdata = filter.cipher(randomkey, this.passwordInput.val(), this.message.val());
  565. var data_to_send = {
  566. data: cipherdata,
  567. expire: $('#pasteExpiration').val(),
  568. burnafterreading: this.burnAfterReading.is(':checked') ? 1 : 0,
  569. opendiscussion: this.openDiscussion.is(':checked') ? 1 : 0
  570. };
  571. var parent = this;
  572. $.post(this.scriptLocation(), data_to_send, function(data)
  573. {
  574. if (data.status == 0) {
  575. parent.stateExistingPaste();
  576. var url = parent.scriptLocation() + '?' + data.id + '#' + randomkey;
  577. var deleteUrl = parent.scriptLocation() + '?pasteid=' + data.id + '&deletetoken=' + data.deletetoken;
  578. parent.showStatus('', false);
  579. $('#pastelink').html('Your paste is <a id="pasteurl" href="' + url + '">' + url + '</a> <span id="copyhint">(Hit CTRL+C to copy)</span>');
  580. $('#deletelink').html('<a href="' + deleteUrl + '">Delete data</a>');
  581. parent.pasteResult.removeClass('hidden');
  582. // We pre-select the link so that the user only has to CTRL+C the link.
  583. helper.selectText('pasteurl');
  584. helper.setElementText(parent.clearText, parent.message.val());
  585. helper.setElementText(parent.prettyPrint, parent.message.val());
  586. // Convert URLs to clickable links.
  587. helper.urls2links(parent.clearText);
  588. helper.urls2links(parent.prettyPrint);
  589. parent.showStatus('', false);
  590. if (typeof prettyPrint == 'function') prettyPrint();
  591. }
  592. else if (data.status==1)
  593. {
  594. parent.showError('Could not create paste: ' + data.message);
  595. }
  596. else
  597. {
  598. parent.showError('Could not create paste.');
  599. }
  600. }, 'json')
  601. .fail(function() {
  602. parent.showError('Data could not be sent (server error or not responding).');
  603. });
  604. },
  605. /**
  606. * Put the screen in "New paste" mode.
  607. */
  608. stateNewPaste: function()
  609. {
  610. this.message.text('');
  611. this.cloneButton.addClass('hidden');
  612. this.rawTextButton.addClass('hidden');
  613. this.remainingTime.addClass('hidden');
  614. this.pasteResult.addClass('hidden');
  615. this.clearText.addClass('hidden');
  616. this.discussion.addClass('hidden');
  617. this.prettyMessage.addClass('hidden');
  618. this.sendButton.removeClass('hidden');
  619. this.expiration.removeClass('hidden');
  620. this.burnAfterReadingOption.removeClass('hidden');
  621. this.openDisc.removeClass('hidden');
  622. this.newButton.removeClass('hidden');
  623. this.password.removeClass('hidden');
  624. this.message.removeClass('hidden');
  625. this.message.focus();
  626. },
  627. /**
  628. * Put the screen in "Existing paste" mode.
  629. */
  630. stateExistingPaste: function()
  631. {
  632. this.sendButton.addClass('hidden');
  633. // No "clone" for IE<10.
  634. if ($('#oldienotice').is(":visible"))
  635. {
  636. this.cloneButton.addClass('hidden');
  637. }
  638. else
  639. {
  640. this.cloneButton.removeClass('hidden');
  641. }
  642. this.rawTextButton.removeClass('hidden');
  643. this.expiration.addClass('hidden');
  644. this.burnAfterReadingOption.addClass('hidden');
  645. this.openDisc.addClass('hidden');
  646. this.newButton.removeClass('hidden');
  647. this.pasteResult.addClass('hidden');
  648. this.message.addClass('hidden');
  649. this.clearText.addClass('hidden');
  650. this.prettyMessage.removeClass('hidden');
  651. },
  652. /**
  653. * If "burn after reading" is checked, disable discussion.
  654. */
  655. changeBurnAfterReading: function()
  656. {
  657. if (this.burnAfterReading.is(':checked') )
  658. {
  659. this.openDisc.addClass('buttondisabled');
  660. this.openDiscussion.attr({checked: false, disabled: true});
  661. }
  662. else
  663. {
  664. this.openDisc.removeClass('buttondisabled');
  665. this.openDiscussion.removeAttr('disabled');
  666. }
  667. },
  668. /**
  669. * Reload the page
  670. *
  671. * @param Event event
  672. */
  673. reloadPage: function(event)
  674. {
  675. event.preventDefault();
  676. window.location.href = this.scriptLocation();
  677. },
  678. /**
  679. * Return raw text
  680. *
  681. * @param Event event
  682. */
  683. rawText: function(event)
  684. {
  685. event.preventDefault();
  686. var paste = this.clearText.html();
  687. var newDoc = document.open('text/html', 'replace');
  688. newDoc.write('<pre>' + paste + '</pre>');
  689. newDoc.close();
  690. },
  691. /**
  692. * Clone the current paste.
  693. *
  694. * @param Event event
  695. */
  696. clonePaste: function(event)
  697. {
  698. event.preventDefault();
  699. this.stateNewPaste();
  700. // Erase the id and the key in url
  701. history.replaceState(document.title, document.title, this.scriptLocation());
  702. this.showStatus('', false);
  703. this.message.text(this.clearText.text());
  704. },
  705. /**
  706. * Create a new paste.
  707. */
  708. newPaste: function()
  709. {
  710. this.stateNewPaste();
  711. this.showStatus('', false);
  712. this.message.text('');
  713. },
  714. /**
  715. * Display an error message
  716. * (We use the same function for paste and reply to comments)
  717. *
  718. * @param string message : text to display
  719. */
  720. showError: function(message)
  721. {
  722. if (this.status.length)
  723. {
  724. this.status.addClass('errorMessage').text(message);
  725. }
  726. else
  727. {
  728. this.errorMessage.removeClass('hidden').append(message);
  729. }
  730. this.replyStatus.addClass('errorMessage').text(message);
  731. },
  732. /**
  733. * Display a status message
  734. * (We use the same function for paste and reply to comments)
  735. *
  736. * @param string message : text to display
  737. * @param boolean spin (optional) : tell if the "spinning" animation should be displayed.
  738. */
  739. showStatus: function(message, spin)
  740. {
  741. this.replyStatus.removeClass('errorMessage').text(message);
  742. if (!message)
  743. {
  744. this.status.html(' ');
  745. return;
  746. }
  747. if (message == '')
  748. {
  749. this.status.html(' ');
  750. return;
  751. }
  752. this.status.removeClass('errorMessage').text(message);
  753. if (spin)
  754. {
  755. var img = '<img src="img/busy.gif" style="width:16px;height:9px;margin:0 4px 0 0;" />';
  756. this.status.prepend(img);
  757. this.replyStatus.prepend(img);
  758. }
  759. },
  760. /**
  761. * bind events to DOM elements
  762. */
  763. bindEvents: function()
  764. {
  765. this.burnAfterReading.change($.proxy(this.changeBurnAfterReading, this));
  766. this.sendButton.click($.proxy(this.sendData, this));
  767. this.cloneButton.click($.proxy(this.clonePaste, this));
  768. this.rawTextButton.click($.proxy(this.rawText, this));
  769. $('.reloadlink').click($.proxy(this.reloadPage, this));
  770. },
  771. /**
  772. * main application
  773. */
  774. init: function()
  775. {
  776. // hide "no javascript" message
  777. $('#noscript').hide();
  778. // preload jQuery wrapped DOM elements and bind events
  779. this.burnAfterReading = $('#burnafterreading');
  780. this.burnAfterReadingOption = $('#burnafterreadingoption');
  781. this.cipherData = $('#cipherdata');
  782. this.clearText = $('#cleartext');
  783. this.cloneButton = $('#clonebutton');
  784. this.comments = $('#comments');
  785. this.discussion = $('#discussion');
  786. this.errorMessage = $('#errormessage');
  787. this.expiration = $('#expiration');
  788. this.message = $('#message');
  789. this.newButton = $('#newbutton');
  790. this.openDisc = $('#opendisc');
  791. this.openDiscussion = $('#opendiscussion');
  792. this.password = $('#password');
  793. this.passwordInput = $('#passwordinput');
  794. this.pasteResult = $('#pasteresult');
  795. this.prettyMessage = $('#prettymessage');
  796. this.prettyPrint = $('#prettyprint');
  797. this.rawTextButton = $('#rawtextbutton');
  798. this.remainingTime = $('#remainingtime');
  799. this.replyStatus = $('#replystatus');
  800. this.sendButton = $('#sendbutton');
  801. this.status = $('#status');
  802. this.bindEvents();
  803. // Display status returned by php code if any (eg. Paste was properly deleted.)
  804. if (this.status.text().length > 0)
  805. {
  806. this.showStatus(this.status.text(), false);
  807. return;
  808. }
  809. // Keep line height even if content empty.
  810. this.status.html(' ');
  811. // Display an existing paste
  812. if (this.cipherData.text().length > 1)
  813. {
  814. // Missing decryption key in URL?
  815. if (window.location.hash.length == 0)
  816. {
  817. this.showError('Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL ?)');
  818. return;
  819. }
  820. // List of messages to display.
  821. var messages = $.parseJSON(this.cipherData.text());
  822. // Show proper elements on screen.
  823. this.stateExistingPaste();
  824. this.displayMessages(this.pageKey(), messages);
  825. }
  826. // Display error message from php code.
  827. else if (this.errorMessage.text().length > 1)
  828. {
  829. this.showError(this.errorMessage.text());
  830. }
  831. // Create a new paste.
  832. else
  833. {
  834. this.newPaste();
  835. }
  836. }
  837. }
  838. /**
  839. * main application start, called when DOM is fully loaded
  840. */
  841. zerobin.init();
  842. });