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 http://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: http://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: http://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, usesPlurals;
  319. if (typeof arguments[0] == 'object')
  320. {
  321. args = arguments[0];
  322. }
  323. if (usesPlurals = $.isArray(args[0]))
  324. {
  325. // use the first plural form as messageId, otherwise the singular
  326. messageId = (args[0].length > 1 ? args[0][1] : args[0][0]);
  327. }
  328. else
  329. {
  330. messageId = args[0];
  331. }
  332. if (messageId.length === 0)
  333. {
  334. return messageId;
  335. }
  336. if (!this.translations.hasOwnProperty(messageId))
  337. {
  338. if (this.language !== 'en')
  339. {
  340. console.debug(
  341. 'Missing ' + this.language + ' translation for: ' + messageId
  342. );
  343. }
  344. this.translations[messageId] = args[0];
  345. }
  346. if (usesPlurals && $.isArray(this.translations[messageId]))
  347. {
  348. var n = parseInt(args[1] || 1, 10),
  349. key = this.getPluralForm(n),
  350. maxKey = this.translations[messageId].length - 1;
  351. if (key > maxKey)
  352. {
  353. key = maxKey;
  354. }
  355. args[0] = this.translations[messageId][key];
  356. args[1] = n;
  357. }
  358. else
  359. {
  360. args[0] = this.translations[messageId];
  361. }
  362. return helper.sprintf(args);
  363. },
  364. /**
  365. * per language functions to use to determine the plural form
  366. * From: http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html
  367. *
  368. * @param int number
  369. * @return int array key
  370. */
  371. getPluralForm: function(n) {
  372. switch (this.language)
  373. {
  374. case 'fr':
  375. case 'zh':
  376. return (n > 1 ? 1 : 0);
  377. case 'pl':
  378. return (n === 1 ? 0 : n%10 >= 2 && n %10 <=4 && (n%100 < 10 || n%100 >= 20) ? 1 : 2);
  379. // en, de
  380. default:
  381. return (n !== 1 ? 1 : 0);
  382. }
  383. },
  384. /**
  385. * load translations into cache, then execute callback function
  386. *
  387. * @param function callback
  388. */
  389. loadTranslations: function(callback)
  390. {
  391. var selectedLang = helper.getCookie('lang');
  392. var language = selectedLang.length > 0 ? selectedLang : (navigator.language || navigator.userLanguage).substring(0, 2);
  393. // note that 'en' is built in, so no translation is necessary
  394. if (this.supportedLanguages.indexOf(language) === -1)
  395. {
  396. callback();
  397. }
  398. else
  399. {
  400. $.getJSON('i18n/' + language + '.json', function(data) {
  401. i18n.language = language;
  402. i18n.translations = data;
  403. callback();
  404. });
  405. }
  406. },
  407. /**
  408. * built in language
  409. */
  410. language: 'en',
  411. /**
  412. * translation cache
  413. */
  414. translations: {}
  415. };
  416. /**
  417. * filter methods
  418. */
  419. var filter = {
  420. /**
  421. * Compress a message (deflate compression). Returns base64 encoded data.
  422. *
  423. * @param string message
  424. * @return base64 string data
  425. */
  426. compress: function(message)
  427. {
  428. return Base64.toBase64( RawDeflate.deflate( Base64.utob(message) ) );
  429. },
  430. /**
  431. * Decompress a message compressed with compress().
  432. *
  433. * @param base64 string data
  434. * @return string message
  435. */
  436. decompress: function(data)
  437. {
  438. return Base64.btou( RawDeflate.inflate( Base64.fromBase64(data) ) );
  439. },
  440. /**
  441. * Compress, then encrypt message with key.
  442. *
  443. * @param string key
  444. * @param string password
  445. * @param string message
  446. * @return encrypted string data
  447. */
  448. cipher: function(key, password, message)
  449. {
  450. if ((password || '').trim().length === 0)
  451. {
  452. return sjcl.encrypt(key, this.compress(message), {mode : 'gcm'});
  453. }
  454. return sjcl.encrypt(key + sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(password)), this.compress(message), {mode : 'gcm'});
  455. },
  456. /**
  457. * Decrypt message with key, then decompress.
  458. *
  459. * @param string key
  460. * @param string password
  461. * @param encrypted string data
  462. * @return string readable message
  463. */
  464. decipher: function(key, password, data)
  465. {
  466. if (data !== undefined)
  467. {
  468. try
  469. {
  470. return this.decompress(sjcl.decrypt(key, data));
  471. }
  472. catch(err)
  473. {
  474. try
  475. {
  476. return this.decompress(sjcl.decrypt(key + sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(password)), data));
  477. }
  478. catch(e)
  479. {}
  480. }
  481. }
  482. return '';
  483. }
  484. };
  485. var privatebin = {
  486. /**
  487. * headers to send in AJAX requests
  488. */
  489. headers: {'X-Requested-With': 'JSONHttpRequest'},
  490. /**
  491. * Get the current script location (without search or hash part of the URL).
  492. * eg. http://server.com/zero/?aaaa#bbbb --> http://server.com/zero/
  493. *
  494. * @return string current script location
  495. */
  496. scriptLocation: function()
  497. {
  498. var scriptLocation = window.location.href.substring(0,window.location.href.length
  499. - window.location.search.length - window.location.hash.length),
  500. hashIndex = scriptLocation.indexOf('#');
  501. if (hashIndex !== -1)
  502. {
  503. scriptLocation = scriptLocation.substring(0, hashIndex);
  504. }
  505. return scriptLocation;
  506. },
  507. /**
  508. * Get the pastes unique identifier from the URL
  509. * eg. http://server.com/zero/?c05354954c49a487#c05354954c49a487 returns c05354954c49a487
  510. *
  511. * @return string unique identifier
  512. */
  513. pasteID: function()
  514. {
  515. return window.location.search.substring(1);
  516. },
  517. /**
  518. * Return the deciphering key stored in anchor part of the URL
  519. *
  520. * @return string key
  521. */
  522. pageKey: function()
  523. {
  524. // Some web 2.0 services and redirectors add data AFTER the anchor
  525. // (such as &utm_source=...). We will strip any additional data.
  526. var key = window.location.hash.substring(1), // Get key
  527. i = key.indexOf('=');
  528. // First, strip everything after the equal sign (=) which signals end of base64 string.
  529. if (i > -1)
  530. {
  531. key = key.substring(0, i + 1);
  532. }
  533. // If the equal sign was not present, some parameters may remain:
  534. i = key.indexOf('&');
  535. if (i > -1)
  536. {
  537. key = key.substring(0, i);
  538. }
  539. // Then add trailing equal sign if it's missing
  540. if (key.charAt(key.length - 1) !== '=')
  541. {
  542. key += '=';
  543. }
  544. return key;
  545. },
  546. /**
  547. * ask the user for the password and return it
  548. *
  549. * @throws error when dialog canceled
  550. * @return string password
  551. */
  552. requestPassword: function()
  553. {
  554. var password = prompt(i18n._('Please enter the password for this paste:'), '');
  555. if (password === null)
  556. {
  557. throw 'password prompt canceled';
  558. }
  559. if (password.length == 0)
  560. {
  561. return this.requestPassword();
  562. }
  563. return password;
  564. },
  565. /**
  566. * use given format on paste, defaults to plain text
  567. *
  568. * @param string format
  569. * @param string text
  570. */
  571. formatPaste: function(format, text)
  572. {
  573. helper.setElementText(this.clearText, text);
  574. helper.setElementText(this.prettyPrint, text);
  575. switch (format || 'plaintext')
  576. {
  577. case 'markdown':
  578. if (typeof showdown === 'object')
  579. {
  580. showdown.setOption('strikethrough', true);
  581. showdown.setOption('tables', true);
  582. showdown.setOption('tablesHeaderId', true);
  583. var converter = new showdown.Converter();
  584. this.clearText.html(
  585. converter.makeHtml(text)
  586. );
  587. this.clearText.removeClass('hidden');
  588. }
  589. this.prettyMessage.addClass('hidden');
  590. break;
  591. case 'syntaxhighlighting':
  592. if (typeof prettyPrintOne === 'function')
  593. {
  594. if (typeof prettyPrint === 'function')
  595. {
  596. prettyPrint();
  597. }
  598. this.prettyPrint.html(
  599. prettyPrintOne(text, null, true)
  600. );
  601. }
  602. // fall through, as the rest is the same
  603. default:
  604. // Convert URLs to clickable links.
  605. helper.urls2links(this.clearText);
  606. helper.urls2links(this.prettyPrint);
  607. this.clearText.addClass('hidden');
  608. if (format === 'plaintext')
  609. {
  610. this.prettyPrint.css('white-space', 'pre-wrap');
  611. this.prettyPrint.css('word-break', 'normal');
  612. this.prettyPrint.removeClass('prettyprint');
  613. }
  614. this.prettyMessage.removeClass('hidden');
  615. }
  616. },
  617. /**
  618. * Show decrypted text in the display area, including discussion (if open)
  619. *
  620. * @param string key : decryption key
  621. * @param object paste : paste object including comments to display (items = array with keys ('data','meta')
  622. */
  623. displayMessages: function(key, paste)
  624. {
  625. // Try to decrypt the paste.
  626. var password = this.passwordInput.val();
  627. if (!this.prettyPrint.hasClass('prettyprinted')) {
  628. try
  629. {
  630. if (paste.attachment)
  631. {
  632. var attachment = filter.decipher(key, password, paste.attachment);
  633. if (attachment.length === 0)
  634. {
  635. if (password.length === 0)
  636. {
  637. password = this.requestPassword();
  638. }
  639. attachment = filter.decipher(key, password, paste.attachment);
  640. }
  641. if (attachment.length === 0)
  642. {
  643. throw 'failed to decipher attachment';
  644. }
  645. if (paste.attachmentname)
  646. {
  647. var attachmentname = filter.decipher(key, password, paste.attachmentname);
  648. if (attachmentname.length > 0)
  649. {
  650. this.attachmentLink.attr('download', attachmentname);
  651. }
  652. }
  653. this.attachmentLink.attr('href', attachment);
  654. this.attachment.removeClass('hidden');
  655. // if the attachment is an image, display it
  656. var imagePrefix = 'data:image/';
  657. if (attachment.substring(0, imagePrefix.length) === imagePrefix)
  658. {
  659. this.image.html(
  660. $(document.createElement('img'))
  661. .attr('src', attachment)
  662. .attr('class', 'img-thumbnail')
  663. );
  664. this.image.removeClass('hidden');
  665. }
  666. }
  667. var cleartext = filter.decipher(key, password, paste.data);
  668. if (cleartext.length === 0 && password.length === 0 && !paste.attachment)
  669. {
  670. password = this.requestPassword();
  671. cleartext = filter.decipher(key, password, paste.data);
  672. }
  673. if (cleartext.length === 0 && !paste.attachment)
  674. {
  675. throw 'failed to decipher message';
  676. }
  677. this.passwordInput.val(password);
  678. if (cleartext.length > 0)
  679. {
  680. this.formatPaste(paste.meta.formatter, cleartext);
  681. }
  682. }
  683. catch(err)
  684. {
  685. this.clearText.addClass('hidden');
  686. this.prettyMessage.addClass('hidden');
  687. this.cloneButton.addClass('hidden');
  688. this.showError(i18n._('Could not decrypt data (Wrong key?)'));
  689. return;
  690. }
  691. }
  692. // Display paste expiration / for your eyes only.
  693. if (paste.meta.expire_date)
  694. {
  695. var expiration = helper.secondsToHuman(paste.meta.remaining_time),
  696. expirationLabel = [
  697. 'This document will expire in %d ' + expiration[1] + '.',
  698. 'This document will expire in %d ' + expiration[1] + 's.'
  699. ];
  700. helper.setMessage(this.remainingTime, i18n._(expirationLabel, expiration[0]));
  701. this.remainingTime.removeClass('foryoureyesonly')
  702. .removeClass('hidden');
  703. }
  704. if (paste.meta.burnafterreading)
  705. {
  706. // unfortunately many web servers don't support DELETE (and PUT) out of the box
  707. $.ajax({
  708. type: 'POST',
  709. url: this.scriptLocation() + '?' + this.pasteID(),
  710. data: {deletetoken: 'burnafterreading'},
  711. dataType: 'json',
  712. headers: this.headers
  713. })
  714. .fail(function() {
  715. privatebin.showError(i18n._('Could not delete the paste, it was not stored in burn after reading mode.'));
  716. });
  717. helper.setMessage(this.remainingTime, i18n._(
  718. 'FOR YOUR EYES ONLY. Don\'t close this window, this message can\'t be displayed again.'
  719. ));
  720. this.remainingTime.addClass('foryoureyesonly')
  721. .removeClass('hidden');
  722. // Discourage cloning (as it can't really be prevented).
  723. this.cloneButton.addClass('hidden');
  724. }
  725. // If the discussion is opened on this paste, display it.
  726. if (paste.meta.opendiscussion)
  727. {
  728. this.comments.html('');
  729. // iterate over comments
  730. for (var i = 0; i < paste.comments.length; ++i)
  731. {
  732. var place = this.comments;
  733. var comment = paste.comments[i];
  734. var commenttext = filter.decipher(key, password, comment.data);
  735. // If parent comment exists, display below (CSS will automatically shift it right.)
  736. var cname = '#comment_' + comment.parentid;
  737. // If the element exists in page
  738. if ($(cname).length)
  739. {
  740. place = $(cname);
  741. }
  742. var divComment = $('<article><div class="comment" id="comment_' + comment.id + '">'
  743. + '<div class="commentmeta"><span class="nickname"></span><span class="commentdate"></span></div><div class="commentdata"></div>'
  744. + '<button class="btn btn-default btn-sm">' + i18n._('Reply') + '</button>'
  745. + '</div></article>');
  746. divComment.find('button').click({commentid: comment.id}, $.proxy(this.openReply, this));
  747. helper.setElementText(divComment.find('div.commentdata'), commenttext);
  748. // Convert URLs to clickable links in comment.
  749. helper.urls2links(divComment.find('div.commentdata'));
  750. // Try to get optional nickname:
  751. var nick = filter.decipher(key, password, comment.meta.nickname);
  752. if (nick.length > 0)
  753. {
  754. divComment.find('span.nickname').text(nick);
  755. }
  756. else
  757. {
  758. divComment.find('span.nickname').html('<i>' + i18n._('Anonymous') + '</i>');
  759. }
  760. divComment.find('span.commentdate')
  761. .text(' (' + (new Date(comment.meta.postdate * 1000).toLocaleString()) + ')')
  762. .attr('title', 'CommentID: ' + comment.id);
  763. // If an avatar is available, display it.
  764. if (comment.meta.vizhash)
  765. {
  766. divComment.find('span.nickname')
  767. .before(
  768. '<img src="' + comment.meta.vizhash + '" class="vizhash" title="' +
  769. i18n._('Anonymous avatar (Vizhash of the IP address)') + '" /> '
  770. );
  771. }
  772. place.append(divComment);
  773. }
  774. var divComment = $(
  775. '<div class="comment"><button class="btn btn-default btn-sm">' +
  776. i18n._('Add comment') + '</button></div>'
  777. );
  778. divComment.find('button').click({commentid: this.pasteID()}, $.proxy(this.openReply, this));
  779. this.comments.append(divComment);
  780. this.discussion.removeClass('hidden');
  781. }
  782. },
  783. /**
  784. * Open the comment entry when clicking the "Reply" button of a comment.
  785. *
  786. * @param Event event
  787. */
  788. openReply: function(event)
  789. {
  790. event.preventDefault();
  791. var source = $(event.target),
  792. commentid = event.data.commentid,
  793. hint = i18n._('Optional nickname...');
  794. // Remove any other reply area.
  795. $('div.reply').remove();
  796. var reply = $(
  797. '<div class="reply">' +
  798. '<input type="text" id="nickname" class="form-control" title="' + hint + '" placeholder="' + hint + '" />' +
  799. '<textarea id="replymessage" class="replymessage form-control" cols="80" rows="7"></textarea>' +
  800. '<br /><button id="replybutton" class="btn btn-default btn-sm">' + i18n._('Post comment') + '</button>' +
  801. '<div id="replystatus"> </div>' +
  802. '</div>'
  803. );
  804. reply.find('button').click({parentid: commentid}, $.proxy(this.sendComment, this));
  805. source.after(reply);
  806. $('#replymessage').focus();
  807. },
  808. /**
  809. * Send a reply in a discussion.
  810. *
  811. * @param Event event
  812. */
  813. sendComment: function(event)
  814. {
  815. event.preventDefault();
  816. this.errorMessage.addClass('hidden');
  817. // Do not send if no data.
  818. var replyMessage = $('#replymessage');
  819. if (replyMessage.val().length === 0)
  820. {
  821. return;
  822. }
  823. this.showStatus(i18n._('Sending comment...'), true);
  824. var parentid = event.data.parentid;
  825. var cipherdata = filter.cipher(this.pageKey(), this.passwordInput.val(), replyMessage.val());
  826. var ciphernickname = '';
  827. var nick = $('#nickname').val();
  828. if (nick !== '')
  829. {
  830. ciphernickname = filter.cipher(this.pageKey(), this.passwordInput.val(), nick);
  831. }
  832. var data_to_send = {
  833. data: cipherdata,
  834. parentid: parentid,
  835. pasteid: this.pasteID(),
  836. nickname: ciphernickname
  837. };
  838. $.ajax({
  839. type: 'POST',
  840. url: this.scriptLocation(),
  841. data: data_to_send,
  842. dataType: 'json',
  843. headers: this.headers,
  844. success: function(data)
  845. {
  846. if (data.status === 0)
  847. {
  848. privatebin.showStatus(i18n._('Comment posted.'), false);
  849. $.ajax({
  850. type: 'GET',
  851. url: privatebin.scriptLocation() + '?' + privatebin.pasteID(),
  852. dataType: 'json',
  853. headers: privatebin.headers,
  854. success: function(data)
  855. {
  856. if (data.status === 0)
  857. {
  858. privatebin.displayMessages(privatebin.pageKey(), data);
  859. }
  860. else if (data.status === 1)
  861. {
  862. privatebin.showError(i18n._('Could not refresh display: %s', data.message));
  863. }
  864. else
  865. {
  866. privatebin.showError(i18n._('Could not refresh display: %s', i18n._('unknown status')));
  867. }
  868. }
  869. })
  870. .fail(function() {
  871. privatebin.showError(i18n._('Could not refresh display: %s', i18n._('server error or not responding')));
  872. });
  873. }
  874. else if (data.status === 1)
  875. {
  876. privatebin.showError(i18n._('Could not post comment: %s', data.message));
  877. }
  878. else
  879. {
  880. privatebin.showError(i18n._('Could not post comment: %s', i18n._('unknown status')));
  881. }
  882. }
  883. })
  884. .fail(function() {
  885. privatebin.showError(i18n._('Could not post comment: %s', i18n._('server error or not responding')));
  886. });
  887. },
  888. /**
  889. * Send a new paste to server
  890. *
  891. * @param Event event
  892. */
  893. sendData: function(event)
  894. {
  895. event.preventDefault();
  896. var file = document.getElementById('file'),
  897. files = (file && file.files) ? file.files : null; // FileList object
  898. // Do not send if no data.
  899. if (this.message.val().length === 0 && !(files && files[0]))
  900. {
  901. return;
  902. }
  903. // If sjcl has not collected enough entropy yet, display a message.
  904. if (!sjcl.random.isReady())
  905. {
  906. this.showStatus(i18n._('Sending paste (Please move your mouse for more entropy)...'), true);
  907. sjcl.random.addEventListener('seeded', function() {
  908. this.sendData(event);
  909. });
  910. return;
  911. }
  912. $('.navbar-toggle').click();
  913. this.password.addClass('hidden');
  914. this.showStatus(i18n._('Sending paste...'), true);
  915. var randomkey = sjcl.codec.base64.fromBits(sjcl.random.randomWords(8, 0), 0);
  916. var cipherdata_attachment;
  917. var password = this.passwordInput.val();
  918. if(files && files[0])
  919. {
  920. if(typeof FileReader === undefined)
  921. {
  922. this.showError(i18n._('Your browser does not support uploading encrypted files. Please use a newer browser.'));
  923. return;
  924. }
  925. var reader = new FileReader();
  926. // Closure to capture the file information.
  927. reader.onload = (function(theFile)
  928. {
  929. return function(e) {
  930. privatebin.sendDataContinue(
  931. randomkey,
  932. filter.cipher(randomkey, password, e.target.result),
  933. filter.cipher(randomkey, password, theFile.name)
  934. );
  935. };
  936. })(files[0]);
  937. reader.readAsDataURL(files[0]);
  938. }
  939. else if(this.attachmentLink.attr('href'))
  940. {
  941. this.sendDataContinue(
  942. randomkey,
  943. filter.cipher(randomkey, password, this.attachmentLink.attr('href')),
  944. this.attachmentLink.attr('download')
  945. );
  946. }
  947. else
  948. {
  949. this.sendDataContinue(randomkey, '', '');
  950. }
  951. },
  952. /**
  953. * Send a new paste to server, step 2
  954. *
  955. * @param string randomkey
  956. * @param encrypted string cipherdata_attachment
  957. * @param encrypted string cipherdata_attachment_name
  958. */
  959. sendDataContinue: function(randomkey, cipherdata_attachment, cipherdata_attachment_name)
  960. {
  961. var cipherdata = filter.cipher(randomkey, this.passwordInput.val(), this.message.val());
  962. var data_to_send = {
  963. data: cipherdata,
  964. expire: $('#pasteExpiration').val(),
  965. formatter: $('#pasteFormatter').val(),
  966. burnafterreading: this.burnAfterReading.is(':checked') ? 1 : 0,
  967. opendiscussion: this.openDiscussion.is(':checked') ? 1 : 0
  968. };
  969. if (cipherdata_attachment.length > 0)
  970. {
  971. data_to_send.attachment = cipherdata_attachment;
  972. if (cipherdata_attachment_name.length > 0)
  973. {
  974. data_to_send.attachmentname = cipherdata_attachment_name;
  975. }
  976. }
  977. $.ajax({
  978. type: 'POST',
  979. url: this.scriptLocation(),
  980. data: data_to_send,
  981. dataType: 'json',
  982. headers: this.headers,
  983. success: function(data)
  984. {
  985. if (data.status === 0) {
  986. privatebin.stateExistingPaste();
  987. var url = privatebin.scriptLocation() + '?' + data.id + '#' + randomkey;
  988. var deleteUrl = privatebin.scriptLocation() + '?pasteid=' + data.id + '&deletetoken=' + data.deletetoken;
  989. privatebin.showStatus('', false);
  990. privatebin.errorMessage.addClass('hidden');
  991. $('#pastelink').html(
  992. i18n._(
  993. 'Your paste is <a id="pasteurl" href="%s">%s</a> <span id="copyhint">(Hit [Ctrl]+[c] to copy)</span>',
  994. url, url
  995. ) + privatebin.shortenUrl(url)
  996. );
  997. $('#deletelink').html('<a href="' + deleteUrl + '">' + i18n._('Delete data') + '</a>');
  998. privatebin.pasteResult.removeClass('hidden');
  999. // We pre-select the link so that the user only has to [Ctrl]+[c] the link.
  1000. helper.selectText('pasteurl');
  1001. privatebin.showStatus('', false);
  1002. privatebin.formatPaste(data_to_send.formatter, privatebin.message.val());
  1003. }
  1004. else if (data.status === 1)
  1005. {
  1006. privatebin.showError(i18n._('Could not create paste: %s', data.message));
  1007. }
  1008. else
  1009. {
  1010. privatebin.showError(i18n._('Could not create paste: %s', i18n._('unknown status')));
  1011. }
  1012. }
  1013. })
  1014. .fail(function()
  1015. {
  1016. privatebin.showError(i18n._('Could not create paste: %s', i18n._('server error or not responding')));
  1017. });
  1018. },
  1019. /**
  1020. * Check if a URL shortener was defined and create HTML containing a link to it.
  1021. *
  1022. * @param string url
  1023. * @return string html
  1024. */
  1025. shortenUrl: function(url)
  1026. {
  1027. var shortenerHtml = $('#shortenbutton');
  1028. if (shortenerHtml) {
  1029. var shortener = shortenerHtml.data('shortener');
  1030. shortenerHtml.attr(
  1031. 'onclick',
  1032. "window.location.href = '" + shortener + encodeURIComponent(url) + "';"
  1033. );
  1034. return ' ' + $('<div />').append(shortenerHtml.clone()).html();
  1035. }
  1036. return '';
  1037. },
  1038. /**
  1039. * Put the screen in "New paste" mode.
  1040. */
  1041. stateNewPaste: function()
  1042. {
  1043. this.message.text('');
  1044. this.attachment.addClass('hidden');
  1045. this.cloneButton.addClass('hidden');
  1046. this.rawTextButton.addClass('hidden');
  1047. this.remainingTime.addClass('hidden');
  1048. this.pasteResult.addClass('hidden');
  1049. this.clearText.addClass('hidden');
  1050. this.discussion.addClass('hidden');
  1051. this.prettyMessage.addClass('hidden');
  1052. this.sendButton.removeClass('hidden');
  1053. this.expiration.removeClass('hidden');
  1054. this.formatter.removeClass('hidden');
  1055. this.burnAfterReadingOption.removeClass('hidden');
  1056. this.openDisc.removeClass('hidden');
  1057. this.newButton.removeClass('hidden');
  1058. this.password.removeClass('hidden');
  1059. this.attach.removeClass('hidden');
  1060. this.message.removeClass('hidden');
  1061. this.preview.removeClass('hidden');
  1062. this.message.focus();
  1063. },
  1064. /**
  1065. * Put the screen in "Existing paste" mode.
  1066. *
  1067. * @param boolean preview (optional) : tell if the preview tabs should be displayed, defaults to false.
  1068. */
  1069. stateExistingPaste: function(preview)
  1070. {
  1071. preview = preview || false;
  1072. if (!preview)
  1073. {
  1074. // No "clone" for IE<10.
  1075. if ($('#oldienotice').is(":visible"))
  1076. {
  1077. this.cloneButton.addClass('hidden');
  1078. }
  1079. else
  1080. {
  1081. this.cloneButton.removeClass('hidden');
  1082. }
  1083. this.rawTextButton.removeClass('hidden');
  1084. this.sendButton.addClass('hidden');
  1085. this.attach.addClass('hidden');
  1086. this.expiration.addClass('hidden');
  1087. this.formatter.addClass('hidden');
  1088. this.burnAfterReadingOption.addClass('hidden');
  1089. this.openDisc.addClass('hidden');
  1090. this.newButton.removeClass('hidden');
  1091. this.preview.addClass('hidden');
  1092. }
  1093. this.pasteResult.addClass('hidden');
  1094. this.message.addClass('hidden');
  1095. this.clearText.addClass('hidden');
  1096. this.prettyMessage.addClass('hidden');
  1097. },
  1098. /**
  1099. * If "burn after reading" is checked, disable discussion.
  1100. */
  1101. changeBurnAfterReading: function()
  1102. {
  1103. if (this.burnAfterReading.is(':checked') )
  1104. {
  1105. this.openDisc.addClass('buttondisabled');
  1106. this.openDiscussion.attr({checked: false, disabled: true});
  1107. }
  1108. else
  1109. {
  1110. this.openDisc.removeClass('buttondisabled');
  1111. this.openDiscussion.removeAttr('disabled');
  1112. }
  1113. },
  1114. /**
  1115. * Reload the page.
  1116. *
  1117. * @param Event event
  1118. */
  1119. reloadPage: function(event)
  1120. {
  1121. event.preventDefault();
  1122. window.location.href = this.scriptLocation();
  1123. },
  1124. /**
  1125. * Return raw text.
  1126. *
  1127. * @param Event event
  1128. */
  1129. rawText: function(event)
  1130. {
  1131. event.preventDefault();
  1132. var paste = this.clearText.html();
  1133. var newDoc = document.open('text/html', 'replace');
  1134. newDoc.write('<pre>' + paste + '</pre>');
  1135. newDoc.close();
  1136. },
  1137. /**
  1138. * Clone the current paste.
  1139. *
  1140. * @param Event event
  1141. */
  1142. clonePaste: function(event)
  1143. {
  1144. event.preventDefault();
  1145. this.stateNewPaste();
  1146. // Erase the id and the key in url
  1147. history.replaceState(document.title, document.title, this.scriptLocation());
  1148. this.showStatus('', false);
  1149. if (this.attachmentLink.attr('href'))
  1150. {
  1151. this.clonedFile.removeClass('hidden');
  1152. this.fileWrap.addClass('hidden');
  1153. }
  1154. this.message.text(this.clearText.text());
  1155. $('.navbar-toggle').click();
  1156. },
  1157. /**
  1158. * Support input of tab character.
  1159. *
  1160. * @param Event event
  1161. */
  1162. supportTabs: function(event)
  1163. {
  1164. var keyCode = event.keyCode || event.which;
  1165. // tab was pressed
  1166. if (keyCode === 9)
  1167. {
  1168. // prevent the textarea to lose focus
  1169. event.preventDefault();
  1170. // get caret position & selection
  1171. var val = this.value,
  1172. start = this.selectionStart,
  1173. end = this.selectionEnd;
  1174. // set textarea value to: text before caret + tab + text after caret
  1175. this.value = val.substring(0, start) + '\t' + val.substring(end);
  1176. // put caret at right position again
  1177. this.selectionStart = this.selectionEnd = start + 1;
  1178. }
  1179. },
  1180. /**
  1181. * View the editor tab.
  1182. *
  1183. * @param Event event
  1184. */
  1185. viewEditor: function(event)
  1186. {
  1187. event.preventDefault();
  1188. this.messagePreview.parent().removeClass('active');
  1189. this.messageEdit.parent().addClass('active');
  1190. this.message.focus();
  1191. this.stateNewPaste();
  1192. },
  1193. /**
  1194. * View the preview tab.
  1195. *
  1196. * @param Event event
  1197. */
  1198. viewPreview: function(event)
  1199. {
  1200. event.preventDefault();
  1201. this.messageEdit.parent().removeClass('active');
  1202. this.messagePreview.parent().addClass('active');
  1203. this.message.focus();
  1204. this.stateExistingPaste(true);
  1205. this.formatPaste($('#pasteFormatter').val(), this.message.val());
  1206. },
  1207. /**
  1208. * Create a new paste.
  1209. */
  1210. newPaste: function()
  1211. {
  1212. this.stateNewPaste();
  1213. this.showStatus('', false);
  1214. this.message.text('');
  1215. },
  1216. /**
  1217. * Removes an attachment.
  1218. */
  1219. removeAttachment: function()
  1220. {
  1221. this.clonedFile.addClass('hidden');
  1222. // removes the saved decrypted file data
  1223. this.attachmentLink.attr('href', '');
  1224. // the only way to deselect the file is to recreate the input
  1225. this.fileWrap.html(this.fileWrap.html());
  1226. this.fileWrap.removeClass('hidden');
  1227. },
  1228. /**
  1229. * Display an error message
  1230. * (We use the same function for paste and reply to comments)
  1231. *
  1232. * @param string message : text to display
  1233. */
  1234. showError: function(message)
  1235. {
  1236. if (this.status.length)
  1237. {
  1238. this.status.addClass('errorMessage').text(message);
  1239. }
  1240. else
  1241. {
  1242. this.errorMessage.removeClass('hidden');
  1243. helper.setMessage(this.errorMessage, message);
  1244. }
  1245. this.replyStatus.addClass('errorMessage').text(message);
  1246. },
  1247. /**
  1248. * Display a status message
  1249. * (We use the same function for paste and reply to comments)
  1250. *
  1251. * @param string message : text to display
  1252. * @param boolean spin (optional) : tell if the "spinning" animation should be displayed.
  1253. */
  1254. showStatus: function(message, spin)
  1255. {
  1256. this.replyStatus.removeClass('errorMessage').text(message);
  1257. if (!message)
  1258. {
  1259. this.status.html(' ');
  1260. return;
  1261. }
  1262. if (message === '')
  1263. {
  1264. this.status.html(' ');
  1265. return;
  1266. }
  1267. this.status.removeClass('errorMessage').text(message);
  1268. if (spin)
  1269. {
  1270. var img = '<img src="img/busy.gif" style="width:16px;height:9px;margin:0 4px 0 0;" />';
  1271. this.status.prepend(img);
  1272. this.replyStatus.prepend(img);
  1273. }
  1274. },
  1275. /**
  1276. * bind events to DOM elements
  1277. */
  1278. bindEvents: function()
  1279. {
  1280. this.burnAfterReading.change($.proxy(this.changeBurnAfterReading, this));
  1281. this.sendButton.click($.proxy(this.sendData, this));
  1282. this.cloneButton.click($.proxy(this.clonePaste, this));
  1283. this.rawTextButton.click($.proxy(this.rawText, this));
  1284. this.fileRemoveButton.click($.proxy(this.removeAttachment, this));
  1285. $('.reloadlink').click($.proxy(this.reloadPage, this));
  1286. this.message.keydown(this.supportTabs);
  1287. this.messageEdit.click($.proxy(this.viewEditor, this));
  1288. this.messagePreview.click($.proxy(this.viewPreview, this));
  1289. },
  1290. /**
  1291. * main application
  1292. */
  1293. init: function()
  1294. {
  1295. // hide "no javascript" message
  1296. $('#noscript').hide();
  1297. // preload jQuery wrapped DOM elements and bind events
  1298. this.attach = $('#attach');
  1299. this.attachment = $('#attachment');
  1300. this.attachmentLink = $('#attachment a');
  1301. this.burnAfterReading = $('#burnafterreading');
  1302. this.burnAfterReadingOption = $('#burnafterreadingoption');
  1303. this.cipherData = $('#cipherdata');
  1304. this.clearText = $('#cleartext');
  1305. this.cloneButton = $('#clonebutton');
  1306. this.clonedFile = $('#clonedfile');
  1307. this.comments = $('#comments');
  1308. this.discussion = $('#discussion');
  1309. this.errorMessage = $('#errormessage');
  1310. this.expiration = $('#expiration');
  1311. this.fileRemoveButton = $('#fileremovebutton');
  1312. this.fileWrap = $('#filewrap');
  1313. this.formatter = $('#formatter');
  1314. this.image = $('#image');
  1315. this.message = $('#message');
  1316. this.messageEdit = $('#messageedit');
  1317. this.messagePreview = $('#messagepreview');
  1318. this.newButton = $('#newbutton');
  1319. this.openDisc = $('#opendisc');
  1320. this.openDiscussion = $('#opendiscussion');
  1321. this.password = $('#password');
  1322. this.passwordInput = $('#passwordinput');
  1323. this.pasteResult = $('#pasteresult');
  1324. this.prettyMessage = $('#prettymessage');
  1325. this.prettyPrint = $('#prettyprint');
  1326. this.preview = $('#preview');
  1327. this.rawTextButton = $('#rawtextbutton');
  1328. this.remainingTime = $('#remainingtime');
  1329. this.replyStatus = $('#replystatus');
  1330. this.sendButton = $('#sendbutton');
  1331. this.status = $('#status');
  1332. this.bindEvents();
  1333. // Display status returned by php code if any (eg. Paste was properly deleted.)
  1334. if (this.status.text().length > 0)
  1335. {
  1336. this.showStatus(this.status.text(), false);
  1337. return;
  1338. }
  1339. // Keep line height even if content empty.
  1340. this.status.html(' ');
  1341. // Display an existing paste
  1342. if (this.cipherData.text().length > 1)
  1343. {
  1344. // Missing decryption key in URL?
  1345. if (window.location.hash.length === 0)
  1346. {
  1347. 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?)'));
  1348. return;
  1349. }
  1350. // List of messages to display.
  1351. var data = $.parseJSON(this.cipherData.text());
  1352. // Show proper elements on screen.
  1353. this.stateExistingPaste();
  1354. this.displayMessages(this.pageKey(), data);
  1355. }
  1356. // Display error message from php code.
  1357. else if (this.errorMessage.text().length > 1)
  1358. {
  1359. this.showError(this.errorMessage.text());
  1360. }
  1361. // Create a new paste.
  1362. else
  1363. {
  1364. this.newPaste();
  1365. }
  1366. }
  1367. }
  1368. /**
  1369. * main application start, called when DOM is fully loaded
  1370. * runs privatebin when translations were loaded
  1371. */
  1372. i18n.loadTranslations($.proxy(privatebin.init, privatebin));
  1373. });