1
0

PasteStatus.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. 'use strict';
  2. const common = require('../common');
  3. const fc = require('fast-check');
  4. function urlStrings(schema, longUrl, shortUrl) {
  5. longUrl.schema = schema;
  6. shortUrl.schema = schema;
  7. let longUrlString = common.urlToString(longUrl),
  8. shortUrlString = common.urlToString(shortUrl);
  9. // ensure the two random URLs actually are sorted as expected
  10. if (longUrlString.length <= shortUrlString.length) {
  11. if (longUrlString.length === shortUrlString.length) {
  12. longUrl.address.unshift('a');
  13. longUrlString = common.urlToString(longUrl);
  14. } else {
  15. [longUrlString, shortUrlString] = [shortUrlString, longUrlString];
  16. }
  17. }
  18. return [longUrlString, shortUrlString];
  19. }
  20. describe('PasteStatus', function () {
  21. describe('createPasteNotification', function () {
  22. this.timeout(30000);
  23. it('creates a notification after a successful document upload', function () {
  24. cleanup();
  25. document.body.innerHTML = '<a href="#" id="deletelink"><span></span></a><div id="pastelink"></div><div id="pastesuccess"></div>';
  26. PrivateBin.PasteStatus.init();
  27. const expected1 = 'https://example.com/long';
  28. const expected2 = 'https://example.com/short';
  29. PrivateBin.PasteStatus.createPasteNotification(expected1, expected2);
  30. assert.strictEqual(document.getElementById('pasteurl').href, expected1);
  31. assert.strictEqual(document.getElementById('deletelink').href, expected2);
  32. assert.ok(!document.getElementById('pastesuccess').classList.contains('hidden'));
  33. });
  34. it('creates a notification after a successful document upload (jsc)', () => {
  35. fc.assert(fc.asyncProperty(
  36. common.fcUrl(),
  37. common.fcUrl(false),
  38. async function (url1, url2) {
  39. // sometimes the generator returns incomplete objects, bail out
  40. if (!url1 || !url1.address || !url2 || !url2.address) {
  41. return true;
  42. }
  43. const expected1 = common.urlToString(url1).replace(/&(gt|lt)$/, '&$1a'),
  44. expected2 = common.urlToString(url2).replace(/&(gt|lt)$/, '&$1a');
  45. globalThis.cleanup();
  46. document.body.innerHTML = '<a href="#" id="deletelink"><span></span></a><div id="pastelink"></div><div id="pastesuccess"></div>';
  47. PrivateBin.PasteStatus.init();
  48. PrivateBin.PasteStatus.createPasteNotification(expected1, expected2);
  49. assert.ok(!document.getElementById('pastesuccess').classList.contains('hidden'));
  50. const result2 = document.getElementById('deletelink').href;
  51. return document.getElementById('pasteurl').href === expected1 && result2 === expected2;
  52. }
  53. ));
  54. });
  55. it(
  56. 'shows Cmd hotkey hint on macOS',
  57. function () {
  58. cleanup();
  59. Object.defineProperty(navigator, 'userAgent', {
  60. value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
  61. configurable: true
  62. });
  63. document.body.innerHTML = '<a href="#" id="deletelink"><span></span></a><div id="pastelink"></div>';
  64. PrivateBin.PasteStatus.init();
  65. PrivateBin.PasteStatus.createPasteNotification('https://example.com/', 'https://example.com/delete');
  66. const hotkey = document.querySelector('#copyhint kbd').textContent;
  67. assert.strictEqual(hotkey, 'Cmd');
  68. }
  69. );
  70. it(
  71. 'shows Ctrl hotkey hint on non-Mac platforms',
  72. function () {
  73. cleanup();
  74. Object.defineProperty(navigator, 'userAgent', {
  75. value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
  76. configurable: true
  77. });
  78. document.body.innerHTML = '<a href="#" id="deletelink"><span></span></a><div id="pastelink"></div>';
  79. PrivateBin.PasteStatus.init();
  80. PrivateBin.PasteStatus.createPasteNotification('https://example.com/', 'https://example.com/delete');
  81. const hotkey = document.querySelector('#copyhint kbd').textContent;
  82. assert.strictEqual(hotkey, 'Ctrl');
  83. }
  84. );
  85. });
  86. describe('extractUrl', function () {
  87. this.timeout(30000);
  88. it('extracts and updates IDN URLs found in given response', () => {
  89. fc.assert(fc.property(
  90. common.fcSchemas(false),
  91. fc.string({minLength: 1}),
  92. common.fcUrl(),
  93. function (schema, domain, url) {
  94. domain = domain.replace(/\P{Letter}|[\u{AA}-\u{BA}]/gu, '').toLowerCase();
  95. if (domain.length === 0) {
  96. domain = 'a';
  97. }
  98. url.schema = schema;
  99. url.address.unshift('.');
  100. url.address = domain.split('').concat(url.address);
  101. const urlString = common.urlToString(url),
  102. expected = urlString.substring((schema + '://' + domain).length),
  103. clean = globalThis.cleanup();
  104. document.body.innerHTML = '<div><div id="pastelink"></div></div>';
  105. PrivateBin.PasteStatus.init();
  106. PrivateBin.PasteStatus.createPasteNotification('', '');
  107. PrivateBin.PasteStatus.extractUrl(urlString);
  108. const result = document.getElementById('pasteurl').href;
  109. clean();
  110. return result.endsWith(expected) && (
  111. result.startsWith(schema + '://xn--') ||
  112. result.startsWith(schema + '://' + domain)
  113. );
  114. }
  115. ));
  116. });
  117. // YOURLS API samples from: https://yourls.org/readme.html#API;apireturn
  118. it('extracts and updates URLs found in YOURLS API JSON response', () => {
  119. fc.assert(fc.property(
  120. common.fcSchemas(false),
  121. common.fcUrl(),
  122. common.fcUrl(false),
  123. function (schema, longUrl, shortUrl) {
  124. const [longUrlString, shortUrlString] = urlStrings(schema, longUrl, shortUrl),
  125. yourlsResponse = {
  126. url: {
  127. keyword: longUrl.address.join(''),
  128. url: longUrlString,
  129. title: 'example title',
  130. date: '2014-10-24 16:01:39',
  131. ip: '127.0.0.1'
  132. },
  133. status: 'success',
  134. message: longUrlString + ' added to database',
  135. title: 'example title',
  136. shorturl: shortUrlString,
  137. statusCode: 200
  138. },
  139. clean = globalThis.cleanup();
  140. document.body.innerHTML = '<div><div id="pastelink"></div></div>';
  141. PrivateBin.PasteStatus.init();
  142. PrivateBin.PasteStatus.createPasteNotification('', '');
  143. PrivateBin.PasteStatus.extractUrl(JSON.stringify(yourlsResponse, undefined, 4));
  144. const result = document.getElementById('pasteurl').href;
  145. clean();
  146. return result === shortUrlString;
  147. }
  148. ));
  149. });
  150. it('extracts and updates URLs found in YOURLS API XML response', () => {
  151. fc.assert(fc.property(
  152. common.fcSchemas(false),
  153. common.fcUrl(),
  154. common.fcUrl(false),
  155. function (schema, longUrl, shortUrl) {
  156. const [longUrlString, shortUrlString] = urlStrings(schema, longUrl, shortUrl),
  157. yourlsResponse = '<result>\n' +
  158. ' <keyword>' + longUrl.address.join('') + '</keyword>\n' +
  159. ' <shorturl>' + shortUrlString + '</shorturl>\n' +
  160. ' <longurl>' + longUrlString + '</longurl>\n' +
  161. ' <message>success</message>\n' +
  162. ' <statusCode>200</statusCode>\n' +
  163. '</result>',
  164. clean = globalThis.cleanup();
  165. document.body.innerHTML = '<div><div id="pastelink"></div></div>';
  166. PrivateBin.PasteStatus.init();
  167. PrivateBin.PasteStatus.createPasteNotification('', '');
  168. PrivateBin.PasteStatus.extractUrl(yourlsResponse);
  169. const result = document.getElementById('pasteurl').href;
  170. clean();
  171. return result === shortUrlString;
  172. }
  173. ));
  174. });
  175. it('extracts and updates URLs found in YOURLS proxy HTML response', () => {
  176. fc.assert(fc.property(
  177. common.fcSchemas(false),
  178. common.fcUrl(),
  179. common.fcUrl(false),
  180. function (schema, longUrl, shortUrl) {
  181. const [_, shortUrlString] = urlStrings(schema, longUrl, shortUrl),
  182. yourlsResponse = '<!DOCTYPE html>\n' +
  183. '<html lang="en">\n' +
  184. '\t<head>\n' +
  185. '\t\t<meta charset="utf-8" />\n' +
  186. '\t\t<meta name="robots" content="noindex" />\n' +
  187. '\t\t<meta name="google" content="notranslate">\n' +
  188. '\t\t<title>PrivateBin</title>\n' +
  189. '\t</head>\n' +
  190. '\t<body>\n' +
  191. '\t\t<p>Your document is <a id="pasteurl" href="' + shortUrlString + '">' + shortUrlString + '</a> <span id="copyhint">(Hit <kbd>Ctrl</kbd>+<kbd>c</kbd> to copy)</span></p>\n' +
  192. '\t</body>\n' +
  193. '</html>',
  194. clean = globalThis.cleanup();
  195. document.body.innerHTML = '<div><div id="pastelink"></div></div>';
  196. PrivateBin.PasteStatus.init();
  197. PrivateBin.PasteStatus.createPasteNotification('', '');
  198. PrivateBin.PasteStatus.extractUrl(yourlsResponse);
  199. const result = document.getElementById('pasteurl').href;
  200. clean();
  201. return result === shortUrlString;
  202. }
  203. ));
  204. });
  205. });
  206. describe('showRemainingTime', function () {
  207. this.timeout(30000);
  208. it('shows burn after reading message or remaining time', () => {
  209. fc.assert(fc.property(
  210. fc.boolean(),
  211. fc.nat(),
  212. common.fcUrl(),
  213. function (burnafterreading, remainingTime, url) {
  214. let clean = globalThis.cleanup('', {url: common.urlToString(url)}),
  215. result;
  216. document.body.innerHTML = '<div id="remainingtime" class="hidden"></div>';
  217. PrivateBin.Alert.init();
  218. PrivateBin.PasteStatus.init();
  219. PrivateBin.PasteStatus.showRemainingTime(PrivateBin.Helper.PasteFactory({
  220. 'adata': [null, null, null, burnafterreading],
  221. 'v': 2,
  222. 'meta': {
  223. 'time_to_live': remainingTime
  224. }
  225. }));
  226. if (burnafterreading) {
  227. result = document.getElementById('remainingtime').classList.contains('foryoureyesonly') &&
  228. !document.getElementById('remainingtime').classList.contains('hidden');
  229. } else if (remainingTime) {
  230. result =!document.getElementById('remainingtime').classList.contains('foryoureyesonly') &&
  231. !document.getElementById('remainingtime').classList.contains('hidden');
  232. } else {
  233. result = document.getElementById('remainingtime').classList.contains('hidden') &&
  234. !document.getElementById('remainingtime').classList.contains('foryoureyesonly');
  235. }
  236. clean();
  237. return result;
  238. }
  239. ));
  240. });
  241. });
  242. describe('hideMessages', function () {
  243. it(
  244. 'hides all messages',
  245. function() {
  246. document.body.innerHTML = (
  247. '<div id="remainingtime"></div><div id="pastesuccess"></div>'
  248. );
  249. PrivateBin.PasteStatus.init();
  250. PrivateBin.PasteStatus.hideMessages();
  251. assert.ok(document.getElementById('remainingtime').classList.contains('hidden'));
  252. assert.ok(document.getElementById('pastesuccess').classList.contains('hidden'));
  253. }
  254. );
  255. });
  256. });