privatebin.js 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464
  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 (var 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. });