privatebin.js 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465
  1. /**
  2. * PrivateBin
  3. *
  4. * a zero-knowledge paste bin
  5. *
  6. * @link https://github.com/PrivateBin/PrivateBin
  7. * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  8. * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
  9. * @version 0.22
  10. */
  11. 'use strict';
  12. /** global: Base64 */
  13. /** global: FileReader */
  14. /** global: RawDeflate */
  15. /** global: history */
  16. /** global: navigator */
  17. /** global: prettyPrint */
  18. /** global: prettyPrintOne */
  19. /** global: showdown */
  20. /** global: sjcl */
  21. // Immediately start random number generator collector.
  22. sjcl.random.startCollectors();
  23. $(function() {
  24. /**
  25. * static helper methods
  26. */
  27. var helper = {
  28. /**
  29. * Converts a duration (in seconds) into human friendly approximation.
  30. *
  31. * @param int seconds
  32. * @return array
  33. */
  34. secondsToHuman: function(seconds)
  35. {
  36. var v;
  37. if (seconds < 60)
  38. {
  39. v = Math.floor(seconds);
  40. return [v, 'second'];
  41. }
  42. if (seconds < 60 * 60)
  43. {
  44. v = Math.floor(seconds / 60);
  45. return [v, 'minute'];
  46. }
  47. if (seconds < 60 * 60 * 24)
  48. {
  49. v = Math.floor(seconds / (60 * 60));
  50. return [v, 'hour'];
  51. }
  52. // If less than 2 months, display in days:
  53. if (seconds < 60 * 60 * 24 * 60)
  54. {
  55. v = Math.floor(seconds / (60 * 60 * 24));
  56. return [v, 'day'];
  57. }
  58. v = Math.floor(seconds / (60 * 60 * 24 * 30));
  59. return [v, 'month'];
  60. },
  61. /**
  62. * Converts an associative array to an encoded string
  63. * for appending to the anchor.
  64. *
  65. * @param object associative_array Object to be serialized
  66. * @return string
  67. */
  68. hashToParameterString: function(associativeArray)
  69. {
  70. var parameterString = '';
  71. for (key in associativeArray)
  72. {
  73. if(parameterString === '')
  74. {
  75. parameterString = encodeURIComponent(key);
  76. parameterString += '=' + encodeURIComponent(associativeArray[key]);
  77. }
  78. else
  79. {
  80. parameterString += '&' + encodeURIComponent(key);
  81. parameterString += '=' + encodeURIComponent(associativeArray[key]);
  82. }
  83. }
  84. // padding for URL shorteners
  85. parameterString += '&p=p';
  86. return parameterString;
  87. },
  88. /**
  89. * Converts a string to an associative array.
  90. *
  91. * @param string parameter_string String containing parameters
  92. * @return object
  93. */
  94. parameterStringToHash: function(parameterString)
  95. {
  96. var parameterHash = {};
  97. var parameterArray = parameterString.split('&');
  98. for (var i = 0; i < parameterArray.length; i++)
  99. {
  100. var pair = parameterArray[i].split('=');
  101. var key = decodeURIComponent(pair[0]);
  102. var value = decodeURIComponent(pair[1]);
  103. parameterHash[key] = value;
  104. }
  105. return parameterHash;
  106. },
  107. /**
  108. * Get an associative array of the parameters found in the anchor
  109. *
  110. * @return object
  111. */
  112. getParameterHash: function()
  113. {
  114. var hashIndex = window.location.href.indexOf('#');
  115. if (hashIndex >= 0)
  116. {
  117. return this.parameterStringToHash(window.location.href.substring(hashIndex + 1));
  118. }
  119. else
  120. {
  121. return {};
  122. }
  123. },
  124. /**
  125. * Convert all applicable characters to HTML entities
  126. *
  127. * @param string str
  128. * @return string encoded string
  129. */
  130. htmlEntities: function(str)
  131. {
  132. return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
  133. },
  134. /**
  135. * Text range selection.
  136. * From: https://stackoverflow.com/questions/985272/jquery-selecting-text-in-an-element-akin-to-highlighting-with-your-mouse
  137. *
  138. * @param string element : Indentifier of the element to select (id="").
  139. */
  140. selectText: function(element)
  141. {
  142. var doc = document,
  143. text = doc.getElementById(element),
  144. range,
  145. selection;
  146. // MS
  147. if (doc.body.createTextRange)
  148. {
  149. range = doc.body.createTextRange();
  150. range.moveToElementText(text);
  151. range.select();
  152. }
  153. // all others
  154. else if (window.getSelection)
  155. {
  156. selection = window.getSelection();
  157. range = doc.createRange();
  158. range.selectNodeContents(text);
  159. selection.removeAllRanges();
  160. selection.addRange(range);
  161. }
  162. },
  163. /**
  164. * Set text of a DOM element (required for IE)
  165. * This is equivalent to element.text(text)
  166. *
  167. * @param object element : a DOM element.
  168. * @param string text : the text to enter.
  169. */
  170. setElementText: function(element, text)
  171. {
  172. // For IE<10: Doesn't support white-space:pre-wrap; so we have to do this...
  173. if ($('#oldienotice').is(':visible')) {
  174. var html = this.htmlEntities(text).replace(/\n/ig,'\r\n<br>');
  175. element.html('<pre>'+html+'</pre>');
  176. }
  177. // for other (sane) browsers:
  178. else
  179. {
  180. element.text(text);
  181. }
  182. },
  183. /**
  184. * replace last child of element with message
  185. *
  186. * @param object element : a jQuery wrapped DOM element.
  187. * @param string message : the message to append.
  188. */
  189. setMessage: function(element, message)
  190. {
  191. var content = element.contents();
  192. if (content.length > 0)
  193. {
  194. content[content.length - 1].nodeValue = ' ' + message;
  195. }
  196. else
  197. {
  198. this.setElementText(element, message);
  199. }
  200. },
  201. /**
  202. * Convert URLs to clickable links.
  203. * URLs to handle:
  204. * <code>
  205. * magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7
  206. * http://localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
  207. * http://user:password@localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
  208. * </code>
  209. *
  210. * @param object element : a jQuery DOM element.
  211. */
  212. urls2links: function(element)
  213. {
  214. var markup = '<a href="$1" rel="nofollow">$1</a>';
  215. element.html(
  216. element.html().replace(
  217. /((http|https|ftp):\/\/[\w?=&.\/-;#@~%+-]+(?![\w\s?&.\/;#~%"=-]*>))/ig,
  218. markup
  219. )
  220. );
  221. element.html(
  222. element.html().replace(
  223. /((magnet):[\w?=&.\/-;#@~%+-]+)/ig,
  224. markup
  225. )
  226. );
  227. },
  228. /**
  229. * minimal sprintf emulation for %s and %d formats
  230. * From: https://stackoverflow.com/questions/610406/javascript-equivalent-to-printf-string-format#4795914
  231. *
  232. * @param string format
  233. * @param mixed args one or multiple parameters injected into format string
  234. * @return string
  235. */
  236. sprintf: function()
  237. {
  238. var args = arguments;
  239. if (typeof arguments[0] === 'object')
  240. {
  241. args = arguments[0];
  242. }
  243. var string = args[0],
  244. i = 1;
  245. return string.replace(/%((%)|s|d)/g, function (m) {
  246. // m is the matched format, e.g. %s, %d
  247. var val;
  248. if (m[2]) {
  249. val = m[2];
  250. } else {
  251. val = args[i];
  252. // A switch statement so that the formatter can be extended.
  253. switch (m)
  254. {
  255. case '%d':
  256. val = parseFloat(val);
  257. if (isNaN(val)) {
  258. val = 0;
  259. }
  260. break;
  261. default:
  262. // Default is %s
  263. }
  264. ++i;
  265. }
  266. return val;
  267. });
  268. },
  269. /**
  270. * get value of cookie, if it was set, empty string otherwise
  271. * From: http://www.w3schools.com/js/js_cookies.asp
  272. *
  273. * @param string cname
  274. * @return string
  275. */
  276. getCookie: function(cname) {
  277. var name = cname + '=';
  278. var ca = document.cookie.split(';');
  279. for(var i = 0; i < ca.length; ++i) {
  280. var c = ca[i];
  281. while (c.charAt(0) === ' ') c = c.substring(1);
  282. if (c.indexOf(name) === 0)
  283. {
  284. return c.substring(name.length, c.length);
  285. }
  286. }
  287. return '';
  288. }
  289. };
  290. /**
  291. * internationalization methods
  292. */
  293. var i18n = {
  294. /**
  295. * supported languages, minus the built in 'en'
  296. */
  297. supportedLanguages: ['de', 'fr', 'pl', 'sl', 'zh'],
  298. /**
  299. * translate a string, alias for translate()
  300. *
  301. * @param string $messageId
  302. * @param mixed args one or multiple parameters injected into placeholders
  303. * @return string
  304. */
  305. _: function()
  306. {
  307. return this.translate(arguments);
  308. },
  309. /**
  310. * translate a string
  311. *
  312. * @param string $messageId
  313. * @param mixed args one or multiple parameters injected into placeholders
  314. * @return string
  315. */
  316. translate: function()
  317. {
  318. var args = arguments, messageId;
  319. if (typeof arguments[0] === 'object')
  320. {
  321. args = arguments[0];
  322. }
  323. var usesPlurals = $.isArray(args[0]);
  324. if (usesPlurals)
  325. {
  326. // use the first plural form as messageId, otherwise the singular
  327. messageId = (args[0].length > 1 ? args[0][1] : args[0][0]);
  328. }
  329. else
  330. {
  331. messageId = args[0];
  332. }
  333. if (messageId.length === 0)
  334. {
  335. return messageId;
  336. }
  337. if (!this.translations.hasOwnProperty(messageId))
  338. {
  339. if (this.language !== 'en')
  340. {
  341. console.debug(
  342. 'Missing ' + this.language + ' translation for: ' + messageId
  343. );
  344. }
  345. this.translations[messageId] = args[0];
  346. }
  347. if (usesPlurals && $.isArray(this.translations[messageId]))
  348. {
  349. var n = parseInt(args[1] || 1, 10),
  350. key = this.getPluralForm(n),
  351. maxKey = this.translations[messageId].length - 1;
  352. if (key > maxKey)
  353. {
  354. key = maxKey;
  355. }
  356. args[0] = this.translations[messageId][key];
  357. args[1] = n;
  358. }
  359. else
  360. {
  361. args[0] = this.translations[messageId];
  362. }
  363. return helper.sprintf(args);
  364. },
  365. /**
  366. * per language functions to use to determine the plural form
  367. * From: http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html
  368. *
  369. * @param int number
  370. * @return int array key
  371. */
  372. getPluralForm: function(n) {
  373. switch (this.language)
  374. {
  375. case 'fr':
  376. case 'zh':
  377. return (n > 1 ? 1 : 0);
  378. case 'pl':
  379. return (n === 1 ? 0 : n%10 >= 2 && n %10 <=4 && (n%100 < 10 || n%100 >= 20) ? 1 : 2);
  380. // en, de
  381. default:
  382. return (n !== 1 ? 1 : 0);
  383. }
  384. },
  385. /**
  386. * load translations into cache, then execute callback function
  387. *
  388. * @param function callback
  389. */
  390. loadTranslations: function(callback)
  391. {
  392. var selectedLang = helper.getCookie('lang');
  393. var language = selectedLang.length > 0 ? selectedLang : (navigator.language || navigator.userLanguage).substring(0, 2);
  394. // note that 'en' is built in, so no translation is necessary
  395. if (this.supportedLanguages.indexOf(language) === -1)
  396. {
  397. callback();
  398. }
  399. else
  400. {
  401. $.getJSON('i18n/' + language + '.json', function(data) {
  402. i18n.language = language;
  403. i18n.translations = data;
  404. callback();
  405. });
  406. }
  407. },
  408. /**
  409. * built in language
  410. */
  411. language: 'en',
  412. /**
  413. * translation cache
  414. */
  415. translations: {}
  416. };
  417. /**
  418. * filter methods
  419. */
  420. var filter = {
  421. /**
  422. * Compress a message (deflate compression). Returns base64 encoded data.
  423. *
  424. * @param string message
  425. * @return base64 string data
  426. */
  427. compress: function(message)
  428. {
  429. return Base64.toBase64( RawDeflate.deflate( Base64.utob(message) ) );
  430. },
  431. /**
  432. * Decompress a message compressed with compress().
  433. *
  434. * @param base64 string data
  435. * @return string message
  436. */
  437. decompress: function(data)
  438. {
  439. return Base64.btou( RawDeflate.inflate( Base64.fromBase64(data) ) );
  440. },
  441. /**
  442. * Compress, then encrypt message with key.
  443. *
  444. * @param string key
  445. * @param string password
  446. * @param string message
  447. * @return encrypted string data
  448. */
  449. cipher: function(key, password, message)
  450. {
  451. if ((password || '').trim().length === 0)
  452. {
  453. return sjcl.encrypt(key, this.compress(message), {mode : 'gcm'});
  454. }
  455. return sjcl.encrypt(key + sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(password)), this.compress(message), {mode : 'gcm'});
  456. },
  457. /**
  458. * Decrypt message with key, then decompress.
  459. *
  460. * @param string key
  461. * @param string password
  462. * @param encrypted string data
  463. * @return string readable message
  464. */
  465. decipher: function(key, password, data)
  466. {
  467. if (data !== undefined)
  468. {
  469. try
  470. {
  471. return this.decompress(sjcl.decrypt(key, data));
  472. }
  473. catch(err)
  474. {
  475. try
  476. {
  477. return this.decompress(sjcl.decrypt(key + sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(password)), data));
  478. }
  479. catch(e)
  480. {}
  481. }
  482. }
  483. return '';
  484. }
  485. };
  486. var privatebin = {
  487. /**
  488. * headers to send in AJAX requests
  489. */
  490. headers: {'X-Requested-With': 'JSONHttpRequest'},
  491. /**
  492. * Get the current script location (without search or hash part of the URL).
  493. * eg. http://server.com/zero/?aaaa#bbbb --> http://server.com/zero/
  494. *
  495. * @return string current script location
  496. */
  497. scriptLocation: function()
  498. {
  499. var scriptLocation = window.location.href.substring(0,window.location.href.length
  500. - window.location.search.length - window.location.hash.length),
  501. hashIndex = scriptLocation.indexOf('#');
  502. if (hashIndex !== -1)
  503. {
  504. scriptLocation = scriptLocation.substring(0, hashIndex);
  505. }
  506. return scriptLocation;
  507. },
  508. /**
  509. * Get the pastes unique identifier from the URL
  510. * eg. http://server.com/zero/?c05354954c49a487#c05354954c49a487 returns c05354954c49a487
  511. *
  512. * @return string unique identifier
  513. */
  514. pasteID: function()
  515. {
  516. return window.location.search.substring(1);
  517. },
  518. /**
  519. * Return the deciphering key stored in anchor part of the URL
  520. *
  521. * @return string key
  522. */
  523. pageKey: function()
  524. {
  525. // Some web 2.0 services and redirectors add data AFTER the anchor
  526. // (such as &utm_source=...). We will strip any additional data.
  527. var key = window.location.hash.substring(1), // Get key
  528. i = key.indexOf('=');
  529. // First, strip everything after the equal sign (=) which signals end of base64 string.
  530. if (i > -1)
  531. {
  532. key = key.substring(0, i + 1);
  533. }
  534. // If the equal sign was not present, some parameters may remain:
  535. i = key.indexOf('&');
  536. if (i > -1)
  537. {
  538. key = key.substring(0, i);
  539. }
  540. // Then add trailing equal sign if it's missing
  541. if (key.charAt(key.length - 1) !== '=')
  542. {
  543. key += '=';
  544. }
  545. return key;
  546. },
  547. /**
  548. * ask the user for the password and return it
  549. *
  550. * @throws error when dialog canceled
  551. * @return string password
  552. */
  553. requestPassword: function()
  554. {
  555. var password = prompt(i18n._('Please enter the password for this paste:'), '');
  556. if (password === null)
  557. {
  558. throw 'password prompt canceled';
  559. }
  560. if (password.length === 0)
  561. {
  562. return this.requestPassword();
  563. }
  564. return password;
  565. },
  566. /**
  567. * use given format on paste, defaults to plain text
  568. *
  569. * @param string format
  570. * @param string text
  571. */
  572. formatPaste: function(format, text)
  573. {
  574. helper.setElementText(this.clearText, text);
  575. helper.setElementText(this.prettyPrint, text);
  576. switch (format || 'plaintext')
  577. {
  578. case 'markdown':
  579. if (typeof showdown === 'object')
  580. {
  581. showdown.setOption('strikethrough', true);
  582. showdown.setOption('tables', true);
  583. showdown.setOption('tablesHeaderId', true);
  584. var converter = new showdown.Converter();
  585. this.clearText.html(
  586. converter.makeHtml(text)
  587. );
  588. this.clearText.removeClass('hidden');
  589. }
  590. this.prettyMessage.addClass('hidden');
  591. break;
  592. case 'syntaxhighlighting':
  593. if (typeof prettyPrintOne === 'function')
  594. {
  595. if (typeof prettyPrint === 'function')
  596. {
  597. prettyPrint();
  598. }
  599. this.prettyPrint.html(
  600. prettyPrintOne(text, null, true)
  601. );
  602. }
  603. // fall through, as the rest is the same
  604. default:
  605. // Convert URLs to clickable links.
  606. helper.urls2links(this.clearText);
  607. helper.urls2links(this.prettyPrint);
  608. this.clearText.addClass('hidden');
  609. if (format === 'plaintext')
  610. {
  611. this.prettyPrint.css('white-space', 'pre-wrap');
  612. this.prettyPrint.css('word-break', 'normal');
  613. this.prettyPrint.removeClass('prettyprint');
  614. }
  615. this.prettyMessage.removeClass('hidden');
  616. }
  617. },
  618. /**
  619. * Show decrypted text in the display area, including discussion (if open)
  620. *
  621. * @param string key : decryption key
  622. * @param object paste : paste object including comments to display (items = array with keys ('data','meta')
  623. */
  624. displayMessages: function(key, paste)
  625. {
  626. // Try to decrypt the paste.
  627. var password = this.passwordInput.val();
  628. if (!this.prettyPrint.hasClass('prettyprinted')) {
  629. try
  630. {
  631. if (paste.attachment)
  632. {
  633. var attachment = filter.decipher(key, password, paste.attachment);
  634. if (attachment.length === 0)
  635. {
  636. if (password.length === 0)
  637. {
  638. password = this.requestPassword();
  639. }
  640. attachment = filter.decipher(key, password, paste.attachment);
  641. }
  642. if (attachment.length === 0)
  643. {
  644. throw 'failed to decipher attachment';
  645. }
  646. if (paste.attachmentname)
  647. {
  648. var attachmentname = filter.decipher(key, password, paste.attachmentname);
  649. if (attachmentname.length > 0)
  650. {
  651. this.attachmentLink.attr('download', attachmentname);
  652. }
  653. }
  654. this.attachmentLink.attr('href', attachment);
  655. this.attachment.removeClass('hidden');
  656. // if the attachment is an image, display it
  657. var imagePrefix = 'data:image/';
  658. if (attachment.substring(0, imagePrefix.length) === imagePrefix)
  659. {
  660. this.image.html(
  661. $(document.createElement('img'))
  662. .attr('src', attachment)
  663. .attr('class', 'img-thumbnail')
  664. );
  665. this.image.removeClass('hidden');
  666. }
  667. }
  668. var cleartext = filter.decipher(key, password, paste.data);
  669. if (cleartext.length === 0 && password.length === 0 && !paste.attachment)
  670. {
  671. password = this.requestPassword();
  672. cleartext = filter.decipher(key, password, paste.data);
  673. }
  674. if (cleartext.length === 0 && !paste.attachment)
  675. {
  676. throw 'failed to decipher message';
  677. }
  678. this.passwordInput.val(password);
  679. if (cleartext.length > 0)
  680. {
  681. this.formatPaste(paste.meta.formatter, cleartext);
  682. }
  683. }
  684. catch(err)
  685. {
  686. this.clearText.addClass('hidden');
  687. this.prettyMessage.addClass('hidden');
  688. this.cloneButton.addClass('hidden');
  689. this.showError(i18n._('Could not decrypt data (Wrong key?)'));
  690. return;
  691. }
  692. }
  693. // Display paste expiration / for your eyes only.
  694. if (paste.meta.expire_date)
  695. {
  696. var expiration = helper.secondsToHuman(paste.meta.remaining_time),
  697. expirationLabel = [
  698. 'This document will expire in %d ' + expiration[1] + '.',
  699. 'This document will expire in %d ' + expiration[1] + 's.'
  700. ];
  701. helper.setMessage(this.remainingTime, i18n._(expirationLabel, expiration[0]));
  702. this.remainingTime.removeClass('foryoureyesonly')
  703. .removeClass('hidden');
  704. }
  705. if (paste.meta.burnafterreading)
  706. {
  707. // unfortunately many web servers don't support DELETE (and PUT) out of the box
  708. $.ajax({
  709. type: 'POST',
  710. url: this.scriptLocation() + '?' + this.pasteID(),
  711. data: {deletetoken: 'burnafterreading'},
  712. dataType: 'json',
  713. headers: this.headers
  714. })
  715. .fail(function() {
  716. privatebin.showError(i18n._('Could not delete the paste, it was not stored in burn after reading mode.'));
  717. });
  718. helper.setMessage(this.remainingTime, i18n._(
  719. 'FOR YOUR EYES ONLY. Don\'t close this window, this message can\'t be displayed again.'
  720. ));
  721. this.remainingTime.addClass('foryoureyesonly')
  722. .removeClass('hidden');
  723. // Discourage cloning (as it can't really be prevented).
  724. this.cloneButton.addClass('hidden');
  725. }
  726. // If the discussion is opened on this paste, display it.
  727. if (paste.meta.opendiscussion)
  728. {
  729. this.comments.html('');
  730. // iterate over comments
  731. for (var i = 0; i < paste.comments.length; ++i)
  732. {
  733. var place = this.comments;
  734. var comment = paste.comments[i];
  735. var commenttext = filter.decipher(key, password, comment.data);
  736. // If parent comment exists, display below (CSS will automatically shift it right.)
  737. var cname = '#comment_' + comment.parentid;
  738. // If the element exists in page
  739. if ($(cname).length)
  740. {
  741. place = $(cname);
  742. }
  743. var divComment = $('<article><div class="comment" id="comment_' + comment.id + '">'
  744. + '<div class="commentmeta"><span class="nickname"></span><span class="commentdate"></span></div><div class="commentdata"></div>'
  745. + '<button class="btn btn-default btn-sm">' + i18n._('Reply') + '</button>'
  746. + '</div></article>');
  747. divComment.find('button').click({commentid: comment.id}, $.proxy(this.openReply, this));
  748. helper.setElementText(divComment.find('div.commentdata'), commenttext);
  749. // Convert URLs to clickable links in comment.
  750. helper.urls2links(divComment.find('div.commentdata'));
  751. // Try to get optional nickname:
  752. var nick = filter.decipher(key, password, comment.meta.nickname);
  753. if (nick.length > 0)
  754. {
  755. divComment.find('span.nickname').text(nick);
  756. }
  757. else
  758. {
  759. divComment.find('span.nickname').html('<i>' + i18n._('Anonymous') + '</i>');
  760. }
  761. divComment.find('span.commentdate')
  762. .text(' (' + (new Date(comment.meta.postdate * 1000).toLocaleString()) + ')')
  763. .attr('title', 'CommentID: ' + comment.id);
  764. // If an avatar is available, display it.
  765. if (comment.meta.vizhash)
  766. {
  767. divComment.find('span.nickname')
  768. .before(
  769. '<img src="' + comment.meta.vizhash + '" class="vizhash" title="' +
  770. i18n._('Anonymous avatar (Vizhash of the IP address)') + '" /> '
  771. );
  772. }
  773. place.append(divComment);
  774. }
  775. var divComment = $(
  776. '<div class="comment"><button class="btn btn-default btn-sm">' +
  777. i18n._('Add comment') + '</button></div>'
  778. );
  779. divComment.find('button').click({commentid: this.pasteID()}, $.proxy(this.openReply, this));
  780. this.comments.append(divComment);
  781. this.discussion.removeClass('hidden');
  782. }
  783. },
  784. /**
  785. * Open the comment entry when clicking the "Reply" button of a comment.
  786. *
  787. * @param Event event
  788. */
  789. openReply: function(event)
  790. {
  791. event.preventDefault();
  792. var source = $(event.target),
  793. commentid = event.data.commentid,
  794. hint = i18n._('Optional nickname...');
  795. // Remove any other reply area.
  796. $('div.reply').remove();
  797. var reply = $(
  798. '<div class="reply">' +
  799. '<input type="text" id="nickname" class="form-control" title="' + hint + '" placeholder="' + hint + '" />' +
  800. '<textarea id="replymessage" class="replymessage form-control" cols="80" rows="7"></textarea>' +
  801. '<br /><button id="replybutton" class="btn btn-default btn-sm">' + i18n._('Post comment') + '</button>' +
  802. '<div id="replystatus"> </div>' +
  803. '</div>'
  804. );
  805. reply.find('button').click({parentid: commentid}, $.proxy(this.sendComment, this));
  806. source.after(reply);
  807. $('#replymessage').focus();
  808. },
  809. /**
  810. * Send a reply in a discussion.
  811. *
  812. * @param Event event
  813. */
  814. sendComment: function(event)
  815. {
  816. event.preventDefault();
  817. this.errorMessage.addClass('hidden');
  818. // Do not send if no data.
  819. var replyMessage = $('#replymessage');
  820. if (replyMessage.val().length === 0)
  821. {
  822. return;
  823. }
  824. this.showStatus(i18n._('Sending comment...'), true);
  825. var parentid = event.data.parentid;
  826. var cipherdata = filter.cipher(this.pageKey(), this.passwordInput.val(), replyMessage.val());
  827. var ciphernickname = '';
  828. var nick = $('#nickname').val();
  829. if (nick !== '')
  830. {
  831. ciphernickname = filter.cipher(this.pageKey(), this.passwordInput.val(), nick);
  832. }
  833. var data_to_send = {
  834. data: cipherdata,
  835. parentid: parentid,
  836. pasteid: this.pasteID(),
  837. nickname: ciphernickname
  838. };
  839. $.ajax({
  840. type: 'POST',
  841. url: this.scriptLocation(),
  842. data: data_to_send,
  843. dataType: 'json',
  844. headers: this.headers,
  845. success: function(data)
  846. {
  847. if (data.status === 0)
  848. {
  849. privatebin.showStatus(i18n._('Comment posted.'), false);
  850. $.ajax({
  851. type: 'GET',
  852. url: privatebin.scriptLocation() + '?' + privatebin.pasteID(),
  853. dataType: 'json',
  854. headers: privatebin.headers,
  855. success: function(data)
  856. {
  857. if (data.status === 0)
  858. {
  859. privatebin.displayMessages(privatebin.pageKey(), data);
  860. }
  861. else if (data.status === 1)
  862. {
  863. privatebin.showError(i18n._('Could not refresh display: %s', data.message));
  864. }
  865. else
  866. {
  867. privatebin.showError(i18n._('Could not refresh display: %s', i18n._('unknown status')));
  868. }
  869. }
  870. })
  871. .fail(function() {
  872. privatebin.showError(i18n._('Could not refresh display: %s', i18n._('server error or not responding')));
  873. });
  874. }
  875. else if (data.status === 1)
  876. {
  877. privatebin.showError(i18n._('Could not post comment: %s', data.message));
  878. }
  879. else
  880. {
  881. privatebin.showError(i18n._('Could not post comment: %s', i18n._('unknown status')));
  882. }
  883. }
  884. })
  885. .fail(function() {
  886. privatebin.showError(i18n._('Could not post comment: %s', i18n._('server error or not responding')));
  887. });
  888. },
  889. /**
  890. * Send a new paste to server
  891. *
  892. * @param Event event
  893. */
  894. sendData: function(event)
  895. {
  896. event.preventDefault();
  897. var file = document.getElementById('file'),
  898. files = (file && file.files) ? file.files : null; // FileList object
  899. // Do not send if no data.
  900. if (this.message.val().length === 0 && !(files && files[0]))
  901. {
  902. return;
  903. }
  904. // If sjcl has not collected enough entropy yet, display a message.
  905. if (!sjcl.random.isReady())
  906. {
  907. this.showStatus(i18n._('Sending paste (Please move your mouse for more entropy)...'), true);
  908. sjcl.random.addEventListener('seeded', function() {
  909. this.sendData(event);
  910. });
  911. return;
  912. }
  913. $('.navbar-toggle').click();
  914. this.password.addClass('hidden');
  915. this.showStatus(i18n._('Sending paste...'), true);
  916. var randomkey = sjcl.codec.base64.fromBits(sjcl.random.randomWords(8, 0), 0);
  917. var cipherdata_attachment;
  918. var password = this.passwordInput.val();
  919. if(files && files[0])
  920. {
  921. if(typeof FileReader === undefined)
  922. {
  923. this.showError(i18n._('Your browser does not support uploading encrypted files. Please use a newer browser.'));
  924. return;
  925. }
  926. var reader = new FileReader();
  927. // Closure to capture the file information.
  928. reader.onload = (function(theFile)
  929. {
  930. return function(e) {
  931. privatebin.sendDataContinue(
  932. randomkey,
  933. filter.cipher(randomkey, password, e.target.result),
  934. filter.cipher(randomkey, password, theFile.name)
  935. );
  936. };
  937. })(files[0]);
  938. reader.readAsDataURL(files[0]);
  939. }
  940. else if(this.attachmentLink.attr('href'))
  941. {
  942. this.sendDataContinue(
  943. randomkey,
  944. filter.cipher(randomkey, password, this.attachmentLink.attr('href')),
  945. this.attachmentLink.attr('download')
  946. );
  947. }
  948. else
  949. {
  950. this.sendDataContinue(randomkey, '', '');
  951. }
  952. },
  953. /**
  954. * Send a new paste to server, step 2
  955. *
  956. * @param string randomkey
  957. * @param encrypted string cipherdata_attachment
  958. * @param encrypted string cipherdata_attachment_name
  959. */
  960. sendDataContinue: function(randomkey, cipherdata_attachment, cipherdata_attachment_name)
  961. {
  962. var cipherdata = filter.cipher(randomkey, this.passwordInput.val(), this.message.val());
  963. var data_to_send = {
  964. data: cipherdata,
  965. expire: $('#pasteExpiration').val(),
  966. formatter: $('#pasteFormatter').val(),
  967. burnafterreading: this.burnAfterReading.is(':checked') ? 1 : 0,
  968. opendiscussion: this.openDiscussion.is(':checked') ? 1 : 0
  969. };
  970. if (cipherdata_attachment.length > 0)
  971. {
  972. data_to_send.attachment = cipherdata_attachment;
  973. if (cipherdata_attachment_name.length > 0)
  974. {
  975. data_to_send.attachmentname = cipherdata_attachment_name;
  976. }
  977. }
  978. $.ajax({
  979. type: 'POST',
  980. url: this.scriptLocation(),
  981. data: data_to_send,
  982. dataType: 'json',
  983. headers: this.headers,
  984. success: function(data)
  985. {
  986. if (data.status === 0) {
  987. privatebin.stateExistingPaste();
  988. var url = privatebin.scriptLocation() + '?' + data.id + '#' + randomkey;
  989. var deleteUrl = privatebin.scriptLocation() + '?pasteid=' + data.id + '&deletetoken=' + data.deletetoken;
  990. privatebin.showStatus('', false);
  991. privatebin.errorMessage.addClass('hidden');
  992. $('#pastelink').html(
  993. i18n._(
  994. 'Your paste is <a id="pasteurl" href="%s">%s</a> <span id="copyhint">(Hit [Ctrl]+[c] to copy)</span>',
  995. url, url
  996. ) + privatebin.shortenUrl(url)
  997. );
  998. $('#deletelink').html('<a href="' + deleteUrl + '">' + i18n._('Delete data') + '</a>');
  999. privatebin.pasteResult.removeClass('hidden');
  1000. // We pre-select the link so that the user only has to [Ctrl]+[c] the link.
  1001. helper.selectText('pasteurl');
  1002. privatebin.showStatus('', false);
  1003. privatebin.formatPaste(data_to_send.formatter, privatebin.message.val());
  1004. }
  1005. else if (data.status === 1)
  1006. {
  1007. privatebin.showError(i18n._('Could not create paste: %s', data.message));
  1008. }
  1009. else
  1010. {
  1011. privatebin.showError(i18n._('Could not create paste: %s', i18n._('unknown status')));
  1012. }
  1013. }
  1014. })
  1015. .fail(function()
  1016. {
  1017. privatebin.showError(i18n._('Could not create paste: %s', i18n._('server error or not responding')));
  1018. });
  1019. },
  1020. /**
  1021. * Check if a URL shortener was defined and create HTML containing a link to it.
  1022. *
  1023. * @param string url
  1024. * @return string html
  1025. */
  1026. shortenUrl: function(url)
  1027. {
  1028. var shortenerHtml = $('#shortenbutton');
  1029. if (shortenerHtml) {
  1030. var shortener = shortenerHtml.data('shortener');
  1031. shortenerHtml.attr(
  1032. 'onclick',
  1033. "window.location.href = '" + shortener + encodeURIComponent(url) + "';"
  1034. );
  1035. return ' ' + $('<div />').append(shortenerHtml.clone()).html();
  1036. }
  1037. return '';
  1038. },
  1039. /**
  1040. * Put the screen in "New paste" mode.
  1041. */
  1042. stateNewPaste: function()
  1043. {
  1044. this.message.text('');
  1045. this.attachment.addClass('hidden');
  1046. this.cloneButton.addClass('hidden');
  1047. this.rawTextButton.addClass('hidden');
  1048. this.remainingTime.addClass('hidden');
  1049. this.pasteResult.addClass('hidden');
  1050. this.clearText.addClass('hidden');
  1051. this.discussion.addClass('hidden');
  1052. this.prettyMessage.addClass('hidden');
  1053. this.sendButton.removeClass('hidden');
  1054. this.expiration.removeClass('hidden');
  1055. this.formatter.removeClass('hidden');
  1056. this.burnAfterReadingOption.removeClass('hidden');
  1057. this.openDisc.removeClass('hidden');
  1058. this.newButton.removeClass('hidden');
  1059. this.password.removeClass('hidden');
  1060. this.attach.removeClass('hidden');
  1061. this.message.removeClass('hidden');
  1062. this.preview.removeClass('hidden');
  1063. this.message.focus();
  1064. },
  1065. /**
  1066. * Put the screen in "Existing paste" mode.
  1067. *
  1068. * @param boolean preview (optional) : tell if the preview tabs should be displayed, defaults to false.
  1069. */
  1070. stateExistingPaste: function(preview)
  1071. {
  1072. preview = preview || false;
  1073. if (!preview)
  1074. {
  1075. // No "clone" for IE<10.
  1076. if ($('#oldienotice').is(":visible"))
  1077. {
  1078. this.cloneButton.addClass('hidden');
  1079. }
  1080. else
  1081. {
  1082. this.cloneButton.removeClass('hidden');
  1083. }
  1084. this.rawTextButton.removeClass('hidden');
  1085. this.sendButton.addClass('hidden');
  1086. this.attach.addClass('hidden');
  1087. this.expiration.addClass('hidden');
  1088. this.formatter.addClass('hidden');
  1089. this.burnAfterReadingOption.addClass('hidden');
  1090. this.openDisc.addClass('hidden');
  1091. this.newButton.removeClass('hidden');
  1092. this.preview.addClass('hidden');
  1093. }
  1094. this.pasteResult.addClass('hidden');
  1095. this.message.addClass('hidden');
  1096. this.clearText.addClass('hidden');
  1097. this.prettyMessage.addClass('hidden');
  1098. },
  1099. /**
  1100. * If "burn after reading" is checked, disable discussion.
  1101. */
  1102. changeBurnAfterReading: function()
  1103. {
  1104. if (this.burnAfterReading.is(':checked') )
  1105. {
  1106. this.openDisc.addClass('buttondisabled');
  1107. this.openDiscussion.attr({checked: false, disabled: true});
  1108. }
  1109. else
  1110. {
  1111. this.openDisc.removeClass('buttondisabled');
  1112. this.openDiscussion.removeAttr('disabled');
  1113. }
  1114. },
  1115. /**
  1116. * Reload the page.
  1117. *
  1118. * @param Event event
  1119. */
  1120. reloadPage: function(event)
  1121. {
  1122. event.preventDefault();
  1123. window.location.href = this.scriptLocation();
  1124. },
  1125. /**
  1126. * Return raw text.
  1127. *
  1128. * @param Event event
  1129. */
  1130. rawText: function(event)
  1131. {
  1132. event.preventDefault();
  1133. var paste = this.clearText.html();
  1134. var newDoc = document.open('text/html', 'replace');
  1135. newDoc.write('<pre>' + paste + '</pre>');
  1136. newDoc.close();
  1137. },
  1138. /**
  1139. * Clone the current paste.
  1140. *
  1141. * @param Event event
  1142. */
  1143. clonePaste: function(event)
  1144. {
  1145. event.preventDefault();
  1146. this.stateNewPaste();
  1147. // Erase the id and the key in url
  1148. history.replaceState(document.title, document.title, this.scriptLocation());
  1149. this.showStatus('', false);
  1150. if (this.attachmentLink.attr('href'))
  1151. {
  1152. this.clonedFile.removeClass('hidden');
  1153. this.fileWrap.addClass('hidden');
  1154. }
  1155. this.message.text(this.clearText.text());
  1156. $('.navbar-toggle').click();
  1157. },
  1158. /**
  1159. * Support input of tab character.
  1160. *
  1161. * @param Event event
  1162. */
  1163. supportTabs: function(event)
  1164. {
  1165. var keyCode = event.keyCode || event.which;
  1166. // tab was pressed
  1167. if (keyCode === 9)
  1168. {
  1169. // prevent the textarea to lose focus
  1170. event.preventDefault();
  1171. // get caret position & selection
  1172. var val = this.value,
  1173. start = this.selectionStart,
  1174. end = this.selectionEnd;
  1175. // set textarea value to: text before caret + tab + text after caret
  1176. this.value = val.substring(0, start) + '\t' + val.substring(end);
  1177. // put caret at right position again
  1178. this.selectionStart = this.selectionEnd = start + 1;
  1179. }
  1180. },
  1181. /**
  1182. * View the editor tab.
  1183. *
  1184. * @param Event event
  1185. */
  1186. viewEditor: function(event)
  1187. {
  1188. event.preventDefault();
  1189. this.messagePreview.parent().removeClass('active');
  1190. this.messageEdit.parent().addClass('active');
  1191. this.message.focus();
  1192. this.stateNewPaste();
  1193. },
  1194. /**
  1195. * View the preview tab.
  1196. *
  1197. * @param Event event
  1198. */
  1199. viewPreview: function(event)
  1200. {
  1201. event.preventDefault();
  1202. this.messageEdit.parent().removeClass('active');
  1203. this.messagePreview.parent().addClass('active');
  1204. this.message.focus();
  1205. this.stateExistingPaste(true);
  1206. this.formatPaste($('#pasteFormatter').val(), this.message.val());
  1207. },
  1208. /**
  1209. * Create a new paste.
  1210. */
  1211. newPaste: function()
  1212. {
  1213. this.stateNewPaste();
  1214. this.showStatus('', false);
  1215. this.message.text('');
  1216. },
  1217. /**
  1218. * Removes an attachment.
  1219. */
  1220. removeAttachment: function()
  1221. {
  1222. this.clonedFile.addClass('hidden');
  1223. // removes the saved decrypted file data
  1224. this.attachmentLink.attr('href', '');
  1225. // the only way to deselect the file is to recreate the input
  1226. this.fileWrap.html(this.fileWrap.html());
  1227. this.fileWrap.removeClass('hidden');
  1228. },
  1229. /**
  1230. * Display an error message
  1231. * (We use the same function for paste and reply to comments)
  1232. *
  1233. * @param string message : text to display
  1234. */
  1235. showError: function(message)
  1236. {
  1237. if (this.status.length)
  1238. {
  1239. this.status.addClass('errorMessage').text(message);
  1240. }
  1241. else
  1242. {
  1243. this.errorMessage.removeClass('hidden');
  1244. helper.setMessage(this.errorMessage, message);
  1245. }
  1246. this.replyStatus.addClass('errorMessage').text(message);
  1247. },
  1248. /**
  1249. * Display a status message
  1250. * (We use the same function for paste and reply to comments)
  1251. *
  1252. * @param string message : text to display
  1253. * @param boolean spin (optional) : tell if the "spinning" animation should be displayed.
  1254. */
  1255. showStatus: function(message, spin)
  1256. {
  1257. this.replyStatus.removeClass('errorMessage').text(message);
  1258. if (!message)
  1259. {
  1260. this.status.html(' ');
  1261. return;
  1262. }
  1263. if (message === '')
  1264. {
  1265. this.status.html(' ');
  1266. return;
  1267. }
  1268. this.status.removeClass('errorMessage').text(message);
  1269. if (spin)
  1270. {
  1271. var img = '<img src="img/busy.gif" style="width:16px;height:9px;margin:0 4px 0 0;" />';
  1272. this.status.prepend(img);
  1273. this.replyStatus.prepend(img);
  1274. }
  1275. },
  1276. /**
  1277. * bind events to DOM elements
  1278. */
  1279. bindEvents: function()
  1280. {
  1281. this.burnAfterReading.change($.proxy(this.changeBurnAfterReading, this));
  1282. this.sendButton.click($.proxy(this.sendData, this));
  1283. this.cloneButton.click($.proxy(this.clonePaste, this));
  1284. this.rawTextButton.click($.proxy(this.rawText, this));
  1285. this.fileRemoveButton.click($.proxy(this.removeAttachment, this));
  1286. $('.reloadlink').click($.proxy(this.reloadPage, this));
  1287. this.message.keydown(this.supportTabs);
  1288. this.messageEdit.click($.proxy(this.viewEditor, this));
  1289. this.messagePreview.click($.proxy(this.viewPreview, this));
  1290. },
  1291. /**
  1292. * main application
  1293. */
  1294. init: function()
  1295. {
  1296. // hide "no javascript" message
  1297. $('#noscript').hide();
  1298. // preload jQuery wrapped DOM elements and bind events
  1299. this.attach = $('#attach');
  1300. this.attachment = $('#attachment');
  1301. this.attachmentLink = $('#attachment a');
  1302. this.burnAfterReading = $('#burnafterreading');
  1303. this.burnAfterReadingOption = $('#burnafterreadingoption');
  1304. this.cipherData = $('#cipherdata');
  1305. this.clearText = $('#cleartext');
  1306. this.cloneButton = $('#clonebutton');
  1307. this.clonedFile = $('#clonedfile');
  1308. this.comments = $('#comments');
  1309. this.discussion = $('#discussion');
  1310. this.errorMessage = $('#errormessage');
  1311. this.expiration = $('#expiration');
  1312. this.fileRemoveButton = $('#fileremovebutton');
  1313. this.fileWrap = $('#filewrap');
  1314. this.formatter = $('#formatter');
  1315. this.image = $('#image');
  1316. this.message = $('#message');
  1317. this.messageEdit = $('#messageedit');
  1318. this.messagePreview = $('#messagepreview');
  1319. this.newButton = $('#newbutton');
  1320. this.openDisc = $('#opendisc');
  1321. this.openDiscussion = $('#opendiscussion');
  1322. this.password = $('#password');
  1323. this.passwordInput = $('#passwordinput');
  1324. this.pasteResult = $('#pasteresult');
  1325. this.prettyMessage = $('#prettymessage');
  1326. this.prettyPrint = $('#prettyprint');
  1327. this.preview = $('#preview');
  1328. this.rawTextButton = $('#rawtextbutton');
  1329. this.remainingTime = $('#remainingtime');
  1330. this.replyStatus = $('#replystatus');
  1331. this.sendButton = $('#sendbutton');
  1332. this.status = $('#status');
  1333. this.bindEvents();
  1334. // Display status returned by php code if any (eg. Paste was properly deleted.)
  1335. if (this.status.text().length > 0)
  1336. {
  1337. this.showStatus(this.status.text(), false);
  1338. return;
  1339. }
  1340. // Keep line height even if content empty.
  1341. this.status.html(' ');
  1342. // Display an existing paste
  1343. if (this.cipherData.text().length > 1)
  1344. {
  1345. // Missing decryption key in URL?
  1346. if (window.location.hash.length === 0)
  1347. {
  1348. this.showError(i18n._('Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)'));
  1349. return;
  1350. }
  1351. // List of messages to display.
  1352. var data = $.parseJSON(this.cipherData.text());
  1353. // Show proper elements on screen.
  1354. this.stateExistingPaste();
  1355. this.displayMessages(this.pageKey(), data);
  1356. }
  1357. // Display error message from php code.
  1358. else if (this.errorMessage.text().length > 1)
  1359. {
  1360. this.showError(this.errorMessage.text());
  1361. }
  1362. // Create a new paste.
  1363. else
  1364. {
  1365. this.newPaste();
  1366. }
  1367. }
  1368. }
  1369. /**
  1370. * main application start, called when DOM is fully loaded
  1371. * runs privatebin when translations were loaded
  1372. */
  1373. i18n.loadTranslations($.proxy(privatebin.init, privatebin));
  1374. });