You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1620 lines
72 KiB

3 years ago
11 months ago
3 years ago
3 years ago
11 months ago
11 months ago
3 years ago
3 years ago
2 years ago
2 years ago
2 years ago
11 months ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
9 months ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. // Load #1, load as little as possible because we are in a race to get the loading screen up.
  2. define([
  3. '/bower_components/nthen/index.js',
  4. '/api/config',
  5. 'jquery',
  6. ], function (nThen, ApiConfig, $) {
  7. var common = {};
  8. common.start = function (cfg) {
  9. cfg = cfg || {};
  10. var realtime = !cfg.noRealtime;
  11. var secret;
  12. var hashes;
  13. var isNewFile;
  14. var CpNfOuter;
  15. var Cryptpad;
  16. var Crypto;
  17. var Cryptget;
  18. var SFrameChannel;
  19. var sframeChan;
  20. var SecureIframe;
  21. var Messaging;
  22. var Notifier;
  23. var Utils = {
  24. nThen: nThen
  25. };
  26. var AppConfig;
  27. var Test;
  28. var password;
  29. var initialPathInDrive;
  30. var currentPad = window.CryptPad_location = {
  31. app: '',
  32. href: cfg.href || window.location.href,
  33. hash: cfg.hash || window.location.hash
  34. };
  35. nThen(function (waitFor) {
  36. // Load #2, the loading screen is up so grab whatever you need...
  37. require([
  38. '/common/sframe-chainpad-netflux-outer.js',
  39. '/common/cryptpad-common.js',
  40. '/bower_components/chainpad-crypto/crypto.js',
  41. '/common/cryptget.js',
  42. '/common/outer/worker-channel.js',
  43. '/secureiframe/main.js',
  44. '/common/common-messaging.js',
  45. '/common/common-notifier.js',
  46. '/common/common-hash.js',
  47. '/common/common-util.js',
  48. '/common/common-realtime.js',
  49. '/common/common-constants.js',
  50. '/common/common-feedback.js',
  51. '/common/outer/local-store.js',
  52. '/customize/application_config.js',
  53. '/common/test.js',
  54. '/common/userObject.js',
  55. ], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel,
  56. _SecureIframe, _Messaging, _Notifier, _Hash, _Util, _Realtime,
  57. _Constants, _Feedback, _LocalStore, _AppConfig, _Test, _UserObject) {
  58. CpNfOuter = _CpNfOuter;
  59. Cryptpad = _Cryptpad;
  60. Crypto = Utils.Crypto = _Crypto;
  61. Cryptget = _Cryptget;
  62. SFrameChannel = _SFrameChannel;
  63. SecureIframe = _SecureIframe;
  64. Messaging = _Messaging;
  65. Notifier = _Notifier;
  66. Utils.Hash = _Hash;
  67. Utils.Util = _Util;
  68. Utils.Realtime = _Realtime;
  69. Utils.Constants = _Constants;
  70. Utils.Feedback = _Feedback;
  71. Utils.LocalStore = _LocalStore;
  72. Utils.UserObject = _UserObject;
  73. AppConfig = _AppConfig;
  74. Test = _Test;
  75. if (localStorage.CRYPTPAD_URLARGS !== ApiConfig.requireConf.urlArgs) {
  76. console.log("New version, flushing cache");
  77. Object.keys(localStorage).forEach(function (k) {
  78. if (k.indexOf('CRYPTPAD_CACHE|') !== 0) { return; }
  79. delete localStorage[k];
  80. });
  81. localStorage.CRYPTPAD_URLARGS = ApiConfig.requireConf.urlArgs;
  82. }
  83. var cache = window.cpCache = {};
  84. var localStore = window.localStore = {};
  85. Object.keys(localStorage).forEach(function (k) {
  86. if (k.indexOf('CRYPTPAD_CACHE|') === 0) {
  87. cache[k.slice(('CRYPTPAD_CACHE|').length)] = localStorage[k];
  88. return;
  89. }
  90. if (k.indexOf('CRYPTPAD_STORE|') === 0) {
  91. localStore[k.slice(('CRYPTPAD_STORE|').length)] = localStorage[k];
  92. return;
  93. }
  94. });
  95. // The inner iframe tries to get some data from us every ms (cache, store...).
  96. // It will send a "READY" message and wait for our answer with the correct txid.
  97. // First, we have to answer to this message, otherwise we're going to block
  98. // sframe-boot.js. Then we can start the channel.
  99. var msgEv = _Util.mkEvent();
  100. var iframe = $('#sbox-iframe')[0].contentWindow;
  101. var postMsg = function (data) {
  102. iframe.postMessage(data, '*');
  103. };
  104. var whenReady = waitFor(function (msg) {
  105. if (msg.source !== iframe) { return; }
  106. var data = JSON.parse(msg.data);
  107. if (!data.txid) { return; }
  108. // Remove the listener once we've received the READY message
  109. window.removeEventListener('message', whenReady);
  110. // Answer with the requested data
  111. postMsg(JSON.stringify({ txid: data.txid, cache: cache, localStore: localStore, language: Cryptpad.getLanguage() }));
  112. // Then start the channel
  113. window.addEventListener('message', function (msg) {
  114. if (msg.source !== iframe) { return; }
  115. msgEv.fire(msg);
  116. });
  117. SFrameChannel.create(msgEv, postMsg, waitFor(function (sfc) {
  118. Utils.sframeChan = sframeChan = sfc;
  119. }));
  120. });
  121. window.addEventListener('message', whenReady);
  122. Cryptpad.loading.onDriveEvent.reg(function (data) {
  123. if (sframeChan) { sframeChan.event('EV_LOADING_INFO', data); }
  124. });
  125. Cryptpad.ready(waitFor(function () {
  126. if (sframeChan) {
  127. sframeChan.event('EV_LOADING_INFO', {
  128. state: -1
  129. });
  130. }
  131. }), {
  132. driveEvents: cfg.driveEvents,
  133. currentPad: currentPad
  134. });
  135. if (window.history && window.history.replaceState && currentPad.hash) {
  136. var nHash = currentPad.hash;
  137. if (!/^#/.test(nHash)) { nHash = '#' + nHash; }
  138. window.history.replaceState({}, window.document.title, nHash);
  139. }
  140. }));
  141. }).nThen(function (waitFor) {
  142. if (!Utils.Hash.isValidHref(window.location.href)) {
  143. waitFor.abort();
  144. return void sframeChan.event('EV_LOADING_ERROR', 'INVALID_HASH');
  145. }
  146. $('#sbox-iframe').focus();
  147. sframeChan.on('EV_CACHE_PUT', function (x) {
  148. Object.keys(x).forEach(function (k) {
  149. localStorage['CRYPTPAD_CACHE|' + k] = x[k];
  150. });
  151. });
  152. sframeChan.on('EV_LOCALSTORE_PUT', function (x) {
  153. Object.keys(x).forEach(function (k) {
  154. if (typeof(x[k]) === "undefined") {
  155. delete localStorage['CRYPTPAD_STORE|' + k];
  156. return;
  157. }
  158. localStorage['CRYPTPAD_STORE|' + k] = x[k];
  159. });
  160. });
  161. var parsed = Utils.Hash.parsePadUrl(currentPad.href);
  162. currentPad.app = parsed.type;
  163. if (cfg.getSecrets) {
  164. var w = waitFor();
  165. // No password for drive, profile and todo
  166. cfg.getSecrets(Cryptpad, Utils, waitFor(function (err, s) {
  167. secret = Utils.secret = s;
  168. Cryptpad.getShareHashes(secret, function (err, h) {
  169. hashes = h;
  170. w();
  171. });
  172. }));
  173. } else {
  174. var todo = function () {
  175. secret = Utils.secret = Utils.Hash.getSecrets(parsed.type, parsed.hash, password);
  176. Cryptpad.getShareHashes(secret, waitFor(function (err, h) {
  177. hashes = h;
  178. // Update the rendered hash and the full hash with the "password" settings
  179. if (password && !parsed.hashData.password) {
  180. var opts = parsed.getOptions();
  181. opts.password = true;
  182. // Full hash
  183. currentPad.href = parsed.getUrl(opts);
  184. if (parsed.hashData) {
  185. currentPad.hash = parsed.hashData.getHash(opts);
  186. }
  187. // Rendered (maybe hidden) hash
  188. var renderedParsed = Utils.Hash.parsePadUrl(window.location.href);
  189. var ohc = window.onhashchange;
  190. window.onhashchange = function () {};
  191. window.location.href = renderedParsed.getUrl(opts);
  192. window.onhashchange = ohc;
  193. ohc({reset: true});
  194. }
  195. }));
  196. };
  197. if (!parsed.hashData) { // No hash, no need to check for a password
  198. return void todo();
  199. }
  200. // We now need to check if there is a password and if we know the correct password.
  201. // We'll use getFileSize and isNewChannel to detect incorrect passwords.
  202. // First we'll get the password value from our drive (getPadAttribute), and we'll check
  203. // if the channel is valid. If the pad is not stored in our drive, we'll test with an
  204. // empty password instead.
  205. // If this initial check returns a valid channel, open the pad.
  206. // If the channel is invalid:
  207. // Option 1: this is a password-protected pad not stored in our drive --> password prompt
  208. // Option 2: this is a pad stored in our drive
  209. // 2a: 'edit' pad or file --> password-prompt
  210. // 2b: 'view' pad no '/p/' --> the seed is incorrect
  211. // 2c: 'view' pad and '/p/' and a wrong password stored --> the seed is incorrect
  212. // 2d: 'view' pad and '/p/' and password never stored (security feature) --> password-prompt
  213. var askPassword = function (wrongPasswordStored, cfg) {
  214. // Ask for the password and check if the pad exists
  215. // If the pad doesn't exist, it means the password isn't correct
  216. // or the pad has been deleted
  217. var correctPassword = waitFor();
  218. sframeChan.on('Q_PAD_PASSWORD_VALUE', function (data, cb) {
  219. password = data;
  220. var next = function (e, isNew) {
  221. if (Boolean(isNew)) {
  222. // Ask again in the inner iframe
  223. // We should receive a new Q_PAD_PASSWORD_VALUE
  224. cb(false);
  225. } else {
  226. todo();
  227. if (wrongPasswordStored) {
  228. // Store the correct password
  229. nThen(function (w) {
  230. Cryptpad.setPadAttribute('password', password, w(), parsed.getUrl());
  231. Cryptpad.setPadAttribute('channel', secret.channel, w(), parsed.getUrl());
  232. if (parsed.hashData.mode === 'edit') {
  233. var href = window.location.pathname + '#' + Utils.Hash.getEditHashFromKeys(secret);
  234. Cryptpad.setPadAttribute('href', href, w(), parsed.getUrl());
  235. var roHref = window.location.pathname + '#' + Utils.Hash.getViewHashFromKeys(secret);
  236. Cryptpad.setPadAttribute('roHref', roHref, w(), parsed.getUrl());
  237. }
  238. }).nThen(correctPassword);
  239. } else {
  240. correctPassword();
  241. }
  242. cb(true);
  243. }
  244. };
  245. if (parsed.type === "file") {
  246. // `isNewChannel` doesn't work for files (not a channel)
  247. // `getFileSize` is not adapted to channels because of metadata
  248. Cryptpad.getFileSize(currentPad.href, password, function (e, size) {
  249. next(e, size === 0);
  250. });
  251. return;
  252. }
  253. // Not a file, so we can use `isNewChannel`
  254. Cryptpad.isNewChannel(currentPad.href, password, next);
  255. });
  256. sframeChan.event("EV_PAD_PASSWORD", cfg);
  257. };
  258. var done = waitFor();
  259. var stored = false;
  260. var passwordCfg = {
  261. value: ''
  262. };
  263. // Hidden hash: can't find the channel in our drives: abort
  264. var noPadData = function (err) {
  265. sframeChan.event("EV_PAD_NODATA", err);
  266. };
  267. var newHref;
  268. nThen(function (w) {
  269. if (parsed.hashData.key || !parsed.hashData.channel) { return; }
  270. var edit = parsed.hashData.mode === 'edit';
  271. Cryptpad.getPadDataFromChannel({
  272. channel: parsed.hashData.channel,
  273. edit: edit,
  274. file: parsed.hashData.type === 'file'
  275. }, w(function (err, res) {
  276. // Error while getting data? abort
  277. if (err || !res || res.error) {
  278. w.abort();
  279. return void noPadData(err || (!res ? 'EINVAL' : res.error));
  280. }
  281. // No data found? abort
  282. if (!Object.keys(res).length) {
  283. w.abort();
  284. return void noPadData('NO_RESULT');
  285. }
  286. // Data found but weaker? warn
  287. if (edit && !res.href) {
  288. newHref = res.roHref;
  289. }
  290. // We have good data, keep the hash in memory
  291. newHref = edit ? res.href : (res.roHref || res.href);
  292. }));
  293. }).nThen(function (w) {
  294. if (newHref) {
  295. // Get the options (embed, present, etc.) of the hidden hash
  296. // Use the same options in the full hash
  297. var opts = parsed.getOptions();
  298. parsed = Utils.Hash.parsePadUrl(newHref);
  299. currentPad.href = parsed.getUrl(opts);
  300. currentPad.hash = parsed.hashData && parsed.hashData.getHash(opts);
  301. }
  302. Cryptpad.getPadAttribute('title', w(function (err, data) {
  303. stored = (!err && typeof (data) === "string");
  304. }));
  305. Cryptpad.getPadAttribute('password', w(function (err, val) {
  306. password = val;
  307. }), parsed.getUrl());
  308. }).nThen(function (w) {
  309. if (!password && !stored && sessionStorage.newPadPassword) {
  310. passwordCfg.value = sessionStorage.newPadPassword;
  311. delete sessionStorage.newPadPassword;
  312. }
  313. if (parsed.type === "file") {
  314. // `isNewChannel` doesn't work for files (not a channel)
  315. // `getFileSize` is not adapted to channels because of metadata
  316. Cryptpad.getFileSize(currentPad.href, password, w(function (e, size) {
  317. if (size !== 0) { return void todo(); }
  318. // Wrong password or deleted file?
  319. askPassword(true, passwordCfg);
  320. }));
  321. return;
  322. }
  323. // Not a file, so we can use `isNewChannel`
  324. Cryptpad.isNewChannel(currentPad.href, password, w(function(e, isNew) {
  325. if (!isNew) { return void todo(); }
  326. if (parsed.hashData.mode === 'view' && (password || !parsed.hashData.password)) {
  327. // Error, wrong password stored, the view seed has changed with the password
  328. // password will never work
  329. sframeChan.event("EV_PAD_PASSWORD_ERROR");
  330. waitFor.abort();
  331. return;
  332. }
  333. if (!stored && !parsed.hashData.password) {
  334. // We've received a link without /p/ and it doesn't work without a password: abort
  335. return void todo();
  336. }
  337. // Wrong password or deleted file?
  338. askPassword(true, passwordCfg);
  339. }));
  340. }).nThen(done);
  341. }
  342. }).nThen(function (waitFor) {
  343. if (cfg.afterSecrets) {
  344. cfg.afterSecrets(Cryptpad, Utils, secret, waitFor());
  345. }
  346. }).nThen(function (waitFor) {
  347. // Check if the pad exists on server
  348. if (!currentPad.hash) { isNewFile = true; return; }
  349. if (realtime) {
  350. // TODO we probably don't need to check again for password-protected pads
  351. // (we use isNewChannel to test the password...)
  352. Cryptpad.isNewChannel(currentPad.href, password, waitFor(function (e, isNew) {
  353. if (e) { return console.error(e); }
  354. isNewFile = Boolean(isNew);
  355. }));
  356. }
  357. }).nThen(function () {
  358. var readOnly = secret.keys && !secret.keys.editKeyStr;
  359. var isNewHash = true;
  360. if (!secret.keys) {
  361. isNewHash = false;
  362. secret.keys = secret.key;
  363. readOnly = false;
  364. }
  365. Utils.crypto = Utils.Crypto.createEncryptor(Utils.secret.keys);
  366. var parsed = Utils.Hash.parsePadUrl(currentPad.href);
  367. var burnAfterReading = parsed && parsed.hashData && parsed.hashData.ownerKey;
  368. if (!parsed.type) { throw new Error(); }
  369. var defaultTitle = Utils.UserObject.getDefaultName(parsed);
  370. var edPublic, curvePublic, notifications, isTemplate;
  371. var settings = {};
  372. var forceCreationScreen = cfg.useCreationScreen &&
  373. sessionStorage[Utils.Constants.displayPadCreationScreen];
  374. delete sessionStorage[Utils.Constants.displayPadCreationScreen];
  375. var isSafe = ['debug', 'profile', 'drive'].indexOf(currentPad.app) !== -1;
  376. var updateMeta = function () {
  377. //console.log('EV_METADATA_UPDATE');
  378. var metaObj;
  379. nThen(function (waitFor) {
  380. Cryptpad.getMetadata(waitFor(function (err, m) {
  381. if (err) { console.log(err); }
  382. metaObj = m;
  383. edPublic = metaObj.priv.edPublic; // needed to create an owned pad
  384. curvePublic = metaObj.user.curvePublic;
  385. notifications = metaObj.user.notifications;
  386. settings = metaObj.priv.settings;
  387. }));
  388. if (typeof(isTemplate) === "undefined") {
  389. Cryptpad.isTemplate(currentPad.href, waitFor(function (err, t) {
  390. if (err) { console.log(err); }
  391. isTemplate = t;
  392. }));
  393. }
  394. }).nThen(function (/*waitFor*/) {
  395. metaObj.doc = {
  396. defaultTitle: defaultTitle,
  397. type: cfg.type || parsed.type
  398. };
  399. var additionalPriv = {
  400. app: parsed.type,
  401. loggedIn: Utils.LocalStore.isLoggedIn(),
  402. origin: window.location.origin,
  403. pathname: window.location.pathname,
  404. fileHost: ApiConfig.fileHost,
  405. readOnly: readOnly,
  406. isTemplate: isTemplate,
  407. feedbackAllowed: Utils.Feedback.state,
  408. isPresent: parsed.hashData && parsed.hashData.present,
  409. isEmbed: parsed.hashData && parsed.hashData.embed,
  410. accounts: {
  411. donateURL: Cryptpad.donateURL,
  412. upgradeURL: Cryptpad.upgradeURL
  413. },
  414. isNewFile: isNewFile,
  415. isDeleted: isNewFile && currentPad.hash.length > 0,
  416. forceCreationScreen: forceCreationScreen,
  417. password: password,
  418. channel: secret.channel,
  419. enableSF: localStorage.CryptPad_SF === "1", // TODO to remove when enabled by default
  420. devMode: localStorage.CryptPad_dev === "1",
  421. fromFileData: Cryptpad.fromFileData ? {
  422. title: Cryptpad.fromFileData.title
  423. } : undefined,
  424. burnAfterReading: burnAfterReading,
  425. storeInTeam: Cryptpad.initialTeam || (Cryptpad.initialPath ? -1 : undefined)
  426. };
  427. if (window.CryptPad_newSharedFolder) {
  428. additionalPriv.newSharedFolder = window.CryptPad_newSharedFolder;
  429. }
  430. if (Utils.Constants.criticalApps.indexOf(parsed.type) === -1 &&
  431. AppConfig.availablePadTypes.indexOf(parsed.type) === -1) {
  432. additionalPriv.disabledApp = true;
  433. }
  434. if (!Utils.LocalStore.isLoggedIn() &&
  435. AppConfig.registeredOnlyTypes.indexOf(parsed.type) !== -1 &&
  436. parsed.type !== "file") {
  437. additionalPriv.registeredOnly = true;
  438. }
  439. if (isSafe) {
  440. additionalPriv.hashes = hashes;
  441. }
  442. for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; }
  443. if (cfg.addData) {
  444. cfg.addData(metaObj.priv, Cryptpad, metaObj.user);
  445. }
  446. sframeChan.event('EV_METADATA_UPDATE', metaObj);
  447. });
  448. };
  449. Cryptpad.onMetadataChanged(updateMeta);
  450. sframeChan.onReg('EV_METADATA_UPDATE', updateMeta);
  451. Utils.LocalStore.onLogout(function () {
  452. sframeChan.event('EV_LOGOUT');
  453. });
  454. Test.registerOuter(sframeChan);
  455. Cryptpad.onNewVersionReconnect.reg(function () {
  456. sframeChan.event("EV_NEW_VERSION");
  457. });
  458. // Put in the following function the RPC queries that should also work in filepicker
  459. var addCommonRpc = function (sframeChan, safe) {
  460. sframeChan.on('Q_ANON_RPC_MESSAGE', function (data, cb) {
  461. Cryptpad.anonRpcMsg(data.msg, data.content, function (err, response) {
  462. cb({error: err, response: response});
  463. });
  464. });
  465. sframeChan.on('Q_GET_PINNED_USAGE', function (data, cb) {
  466. Cryptpad.getPinnedUsage({}, function (e, used) {
  467. cb({
  468. error: e,
  469. quota: used
  470. });
  471. });
  472. });
  473. sframeChan.on('Q_GET_PIN_LIMIT_STATUS', function (data, cb) {
  474. Cryptpad.isOverPinLimit(null, function (e, overLimit, limits) {
  475. cb({
  476. error: e,
  477. overLimit: overLimit,
  478. limits: limits
  479. });
  480. });
  481. });
  482. sframeChan.on('Q_THUMBNAIL_GET', function (data, cb) {
  483. Utils.LocalStore.getThumbnail(data.key, function (e, data) {
  484. cb({
  485. error: e,
  486. data: data
  487. });
  488. });
  489. });
  490. sframeChan.on('Q_THUMBNAIL_SET', function (data, cb) {
  491. Utils.LocalStore.setThumbnail(data.key, data.value, function (e) {
  492. cb({error:e});
  493. });
  494. });
  495. sframeChan.on('Q_GET_ATTRIBUTE', function (data, cb) {
  496. Cryptpad.getAttribute(data.key, function (e, data) {
  497. cb({
  498. error: e,
  499. data: data
  500. });
  501. });
  502. });
  503. sframeChan.on('Q_SET_ATTRIBUTE', function (data, cb) {
  504. Cryptpad.setAttribute(data.key, data.value, function (e) {
  505. cb({error:e});
  506. });
  507. });
  508. Cryptpad.mailbox.onEvent.reg(function (data, cb) {
  509. sframeChan.query('EV_MAILBOX_EVENT', data, function (err, obj) {
  510. if (!cb) { return; }
  511. if (err) { return void cb({error: err}); }
  512. cb(obj);
  513. });
  514. });
  515. sframeChan.on('Q_MAILBOX_COMMAND', function (data, cb) {
  516. Cryptpad.mailbox.execCommand(data, cb);
  517. });
  518. sframeChan.on('Q_SET_LOGIN_REDIRECT', function (data, cb) {
  519. sessionStorage.redirectTo = currentPad.href;
  520. cb();
  521. });
  522. sframeChan.on('Q_STORE_IN_TEAM', function (data, cb) {
  523. Cryptpad.storeInTeam(data, cb);
  524. });
  525. sframeChan.on('EV_GOTO_URL', function (url) {
  526. if (url) {
  527. window.location.href = url;
  528. } else {
  529. window.location.reload();
  530. }
  531. });
  532. sframeChan.on('EV_OPEN_URL', function (url) {
  533. if (url) {
  534. window.open(url);
  535. }
  536. });
  537. sframeChan.on('Q_GET_PAD_METADATA', function (data, cb) {
  538. if (!data || !data.channel) {
  539. data = {
  540. channel: secret.channel
  541. };
  542. }
  543. Cryptpad.getPadMetadata(data, cb);
  544. });
  545. sframeChan.on('Q_SET_PAD_METADATA', function (data, cb) {
  546. Cryptpad.setPadMetadata(data, cb);
  547. });
  548. sframeChan.on('Q_GET_PAD_ATTRIBUTE', function (data, cb) {
  549. var href;
  550. if (readOnly && hashes.editHash) {
  551. // If we have a stronger hash, use it for pad attributes
  552. href = window.location.pathname + '#' + hashes.editHash;
  553. }
  554. if (data.href) { href = data.href; }
  555. Cryptpad.getPadAttribute(data.key, function (e, data) {
  556. if (!safe && data) {
  557. // Remove unsafe data for the unsafe iframe
  558. delete data.href;
  559. delete data.roHref;
  560. delete data.password;
  561. }
  562. cb({
  563. error: e,
  564. data: data
  565. });
  566. }, href);
  567. });
  568. sframeChan.on('Q_SET_PAD_ATTRIBUTE', function (data, cb) {
  569. var href;
  570. if (readOnly && hashes.editHash) {
  571. // If we have a stronger hash, use it for pad attributes
  572. href = window.location.pathname + '#' + hashes.editHash;
  573. }
  574. if (data.href) { href = data.href; }
  575. Cryptpad.setPadAttribute(data.key, data.value, function (e) {
  576. cb({error:e});
  577. }, href);
  578. });
  579. };
  580. addCommonRpc(sframeChan, isSafe);
  581. var currentTitle;
  582. var currentTabTitle;
  583. var setDocumentTitle = function () {
  584. if (!currentTabTitle) {
  585. document.title = currentTitle || 'CryptPad';
  586. return;
  587. }
  588. var title = currentTabTitle.replace(/\{title\}/g, currentTitle || 'CryptPad');
  589. document.title = title;
  590. };
  591. sframeChan.on('Q_SET_PAD_TITLE_IN_DRIVE', function (newData, cb) {
  592. var newTitle = newData.title || newData.defaultTitle;
  593. currentTitle = newTitle;
  594. setDocumentTitle();
  595. var data = {
  596. password: password,
  597. title: newTitle,
  598. channel: secret.channel,
  599. path: initialPathInDrive // Where to store the pad if we don't have it in our drive
  600. };
  601. Cryptpad.setPadTitle(data, function (err, obj) {
  602. if (!err && !(obj && obj.notStored)) {
  603. // No error and the pad was correctly stored
  604. // hide the hash
  605. var opts = parsed.getOptions();
  606. var hash = Utils.Hash.getHiddenHashFromKeys(parsed.type, secret, opts);
  607. var useUnsafe = Utils.Util.find(settings, ['security', 'unsafeLinks']);
  608. if (useUnsafe === false && window.history && window.history.replaceState) {
  609. if (!/^#/.test(hash)) { hash = '#' + hash; }
  610. window.history.replaceState({}, window.document.title, hash);
  611. }
  612. }
  613. cb({error: err});
  614. });
  615. });
  616. sframeChan.on('EV_SET_TAB_TITLE', function (newTabTitle) {
  617. currentTabTitle = newTabTitle;
  618. setDocumentTitle();
  619. });
  620. sframeChan.on('EV_SET_HASH', function (hash) {
  621. // In this case, we want to set the hash for the next page reload
  622. // This hash is a category for the sidebar layout apps
  623. // No need to store it in memory
  624. window.location.hash = hash;
  625. });
  626. Cryptpad.autoStore.onStoreRequest.reg(function (data) {
  627. sframeChan.event("EV_AUTOSTORE_DISPLAY_POPUP", data);
  628. });
  629. sframeChan.on('Q_AUTOSTORE_STORE', function (obj, cb) {
  630. var data = {
  631. password: password,
  632. title: currentTitle,
  633. channel: secret.channel,
  634. path: initialPathInDrive, // Where to store the pad if we don't have it in our drive
  635. forceSave: true
  636. };
  637. Cryptpad.setPadTitle(data, function (err) {
  638. if (!err && !(obj && obj.notStored)) {
  639. // No error and the pad was correctly stored
  640. // hide the hash
  641. var opts = parsed.getOptions();
  642. var hash = Utils.Hash.getHiddenHashFromKeys(parsed.type, secret, opts);
  643. var useUnsafe = Utils.Util.find(settings, ['security', 'unsafeLinks']);
  644. if (useUnsafe === false && window.history && window.history.replaceState) {
  645. if (!/^#/.test(hash)) { hash = '#' + hash; }
  646. window.history.replaceState({}, window.document.title, hash);
  647. }
  648. }
  649. cb({error: err});
  650. });
  651. });
  652. sframeChan.on('Q_IS_PAD_STORED', function (data, cb) {
  653. Cryptpad.getPadAttribute('title', function (err, data) {
  654. cb (!err && typeof (data) === "string");
  655. });
  656. });
  657. sframeChan.on('Q_ACCEPT_OWNERSHIP', function (data, cb) {
  658. var parsed = Utils.Hash.parsePadUrl(data.href);
  659. if (parsed.type === 'drive') {
  660. // Shared folder
  661. var secret = Utils.Hash.getSecrets(parsed.type, parsed.hash, data.password);
  662. Cryptpad.addSharedFolder(null, secret, cb);
  663. } else {
  664. var _data = {
  665. password: data.password,
  666. href: data.href,
  667. channel: data.channel,
  668. title: data.title,
  669. owners: data.metadata.owners,
  670. expire: data.metadata.expire,
  671. forceSave: true
  672. };
  673. Cryptpad.setPadTitle(_data, function (err) {
  674. cb({error: err});
  675. });
  676. }
  677. // Also add your mailbox to the metadata object
  678. var padParsed = Utils.Hash.parsePadUrl(data.href);
  679. var padSecret = Utils.Hash.getSecrets(padParsed.type, padParsed.hash, data.password);
  680. var padCrypto = Utils.Crypto.createEncryptor(padSecret.keys);
  681. try {
  682. var value = {};
  683. value[edPublic] = padCrypto.encrypt(JSON.stringify({
  684. notifications: notifications,
  685. curvePublic: curvePublic
  686. }));
  687. var msg = {
  688. channel: data.channel,
  689. command: 'ADD_MAILBOX',
  690. value: value
  691. };
  692. Cryptpad.setPadMetadata(msg, function (res) {
  693. if (res.error) { console.error(res.error); }
  694. });
  695. } catch (err) {
  696. return void console.error(err);
  697. }
  698. });
  699. sframeChan.on('Q_IMPORT_MEDIATAG', function (obj, cb) {
  700. var key = obj.key;
  701. var channel = obj.channel;
  702. var hash = Utils.Hash.getFileHashFromKeys({
  703. version: 1,
  704. channel: channel,
  705. keys: {
  706. fileKeyStr: key
  707. }
  708. });
  709. var href = '/file/#' + hash;
  710. var data = {
  711. title: obj.name,
  712. href: href,
  713. channel: channel,
  714. owners: obj.owners,
  715. forceSave: true,
  716. };
  717. Cryptpad.setPadTitle(data, function (err) {
  718. Cryptpad.setPadAttribute('fileType', obj.type, null, href);
  719. cb(err);
  720. });
  721. });
  722. sframeChan.on('Q_SETTINGS_SET_DISPLAY_NAME', function (newName, cb) {
  723. Cryptpad.setDisplayName(newName, function (err) {
  724. if (err) {
  725. console.log("Couldn't set username");
  726. console.error(err);
  727. cb('ERROR');
  728. return;
  729. }
  730. Cryptpad.changeMetadata();
  731. cb();
  732. });
  733. });
  734. sframeChan.on('Q_LOGOUT', function (data, cb) {
  735. Utils.LocalStore.logout(cb);
  736. });
  737. sframeChan.on('Q_LOGOUT_EVERYWHERE', function (data, cb) {
  738. Cryptpad.logoutFromAll(Utils.Util.bake(Utils.LocalStore.logout, cb));
  739. });
  740. sframeChan.on('EV_NOTIFY', function (data) {
  741. Notifier.notify(data);
  742. });
  743. sframeChan.on('Q_MOVE_TO_TRASH', function (data, cb) {
  744. cb = cb || $.noop;
  745. if (readOnly && hashes.editHash) {
  746. var appPath = window.location.pathname;
  747. Cryptpad.moveToTrash(cb, appPath + '#' + hashes.editHash);
  748. return;
  749. }
  750. Cryptpad.moveToTrash(cb);
  751. });
  752. sframeChan.on('Q_SAVE_AS_TEMPLATE', function (data, cb) {
  753. Cryptpad.saveAsTemplate(Cryptget.put, data, cb);
  754. });
  755. sframeChan.on('EV_MAKE_A_COPY', function () {
  756. var data = {
  757. channel: secret.channel,
  758. href: currentPad.href,
  759. password: password,
  760. title: currentTitle
  761. };
  762. sessionStorage[Utils.Constants.newPadFileData] = JSON.stringify(data);
  763. window.open(window.location.pathname);
  764. setTimeout(function () {
  765. delete sessionStorage[Utils.Constants.newPadFileData];
  766. }, 100);
  767. });
  768. // Messaging
  769. sframeChan.on('Q_SEND_FRIEND_REQUEST', function (data, cb) {
  770. Cryptpad.messaging.sendFriendRequest(data, cb);
  771. });
  772. sframeChan.on('Q_ANSWER_FRIEND_REQUEST', function (data, cb) {
  773. Cryptpad.messaging.answerFriendRequest(data, cb);
  774. });
  775. sframeChan.on('Q_ANON_GET_PREVIEW_CONTENT', function (data, cb) {
  776. Cryptpad.anonGetPreviewContent(data, cb);
  777. });
  778. // History
  779. sframeChan.on('Q_GET_FULL_HISTORY', function (data, cb) {
  780. var crypto = Crypto.createEncryptor(secret.keys);
  781. Cryptpad.getFullHistory({
  782. debug: data && data.debug,
  783. channel: secret.channel,
  784. validateKey: secret.keys.validateKey
  785. }, function (encryptedMsgs) {
  786. var nt = nThen;
  787. var decryptedMsgs = [];
  788. var total = encryptedMsgs.length;
  789. encryptedMsgs.forEach(function (_msg, i) {
  790. nt = nt(function (waitFor) {
  791. // The 3rd parameter "true" means we're going to skip signature validation.
  792. // We don't need it since the message is already validated serverside by hk
  793. if (typeof(_msg) === "object") {
  794. decryptedMsgs.push({
  795. author: _msg.author,
  796. time: _msg.time,
  797. msg: crypto.decrypt(_msg.msg, true, true)
  798. });
  799. } else {
  800. decryptedMsgs.push(crypto.decrypt(_msg, true, true));
  801. }
  802. setTimeout(waitFor(function () {
  803. sframeChan.event('EV_FULL_HISTORY_STATUS', (i+1)/total);
  804. }));
  805. }).nThen;
  806. });
  807. nt(function () {
  808. cb(decryptedMsgs);
  809. });
  810. });
  811. });
  812. sframeChan.on('Q_GET_HISTORY_RANGE', function (data, cb) {
  813. var nSecret = secret;
  814. if (cfg.isDrive) {
  815. // Shared folder or user hash or fs hash
  816. var hash = Utils.LocalStore.getUserHash() || Utils.LocalStore.getFSHash();
  817. if (data.sharedFolder) { hash = data.sharedFolder.hash; }
  818. if (hash) {
  819. var password = (data.sharedFolder && data.sharedFolder.password) || undefined;
  820. nSecret = Utils.Hash.getSecrets('drive', hash, password);
  821. }
  822. }
  823. var channel = nSecret.channel;
  824. var validate = nSecret.keys.validateKey;
  825. var crypto = Crypto.createEncryptor(nSecret.keys);
  826. Cryptpad.getHistoryRange({
  827. channel: channel,
  828. validateKey: validate,
  829. lastKnownHash: data.lastKnownHash
  830. }, function (data) {
  831. cb({
  832. isFull: data.isFull,
  833. messages: data.messages.map(function (obj) {
  834. // The 3rd parameter "true" means we're going to skip signature validation.
  835. // We don't need it since the message is already validated serverside by hk
  836. return {
  837. msg: crypto.decrypt(obj.msg, true, true),
  838. author: obj.author,
  839. time: obj.time
  840. };
  841. }),
  842. lastKnownHash: data.lastKnownHash
  843. });
  844. });
  845. });
  846. // Store
  847. sframeChan.on('Q_DRIVE_GETDELETED', function (data, cb) {
  848. Cryptpad.getDeletedPads(data, function (err, obj) {
  849. if (err) { return void console.error(err); }
  850. cb(obj);
  851. });
  852. });
  853. sframeChan.on('Q_SESSIONSTORAGE_PUT', function (data, cb) {
  854. if (typeof (data.value) === "undefined") {
  855. delete sessionStorage[data.key];
  856. } else {
  857. sessionStorage[data.key] = data.value;
  858. }
  859. cb();
  860. });
  861. sframeChan.on('Q_IS_ONLY_IN_SHARED_FOLDER', function (data, cb) {
  862. Cryptpad.isOnlyInSharedFolder(secret.channel, function (err, t) {
  863. if (err) { return void cb({error: err}); }
  864. cb(t);
  865. });
  866. });
  867. // Present mode URL
  868. sframeChan.on('Q_PRESENT_URL_GET_VALUE', function (data, cb) {
  869. var parsed = Utils.Hash.parsePadUrl(currentPad.href);
  870. cb(parsed.hashData && parsed.hashData.present);
  871. });
  872. sframeChan.on('EV_PRESENT_URL_SET_VALUE', function (data) {
  873. // Update the rendered hash and the full hash with the "present" settings
  874. var opts = parsed.getOptions();
  875. opts.present = data;
  876. // Full hash
  877. currentPad.href = parsed.getUrl(opts);
  878. if (parsed.hashData) { currentPad.hash = parsed.hashData.getHash(opts); }
  879. // Rendered (maybe hidden) hash
  880. var hiddenParsed = Utils.Hash.parsePadUrl(window.location.href);
  881. window.location.href = hiddenParsed.getUrl(opts);
  882. });
  883. // File upload
  884. var onFileUpload = function (sframeChan, data, cb) {
  885. require(['/common/outer/upload.js'], function (Files) {
  886. var sendEvent = function (data) {
  887. sframeChan.event("EV_FILE_UPLOAD_STATE", data);
  888. };
  889. var updateProgress = function (progressValue) {
  890. sendEvent({
  891. uid: data.uid,
  892. progress: progressValue
  893. });
  894. };
  895. var onComplete = function (href) {
  896. sendEvent({
  897. complete: true,
  898. uid: data.uid,
  899. href: href
  900. });
  901. };
  902. var onError = function (e) {
  903. sendEvent({
  904. uid: data.uid,
  905. error: e
  906. });
  907. };
  908. var onPending = function (cb) {
  909. sframeChan.query('Q_CANCEL_PENDING_FILE_UPLOAD', {
  910. uid: data.uid
  911. }, function (err, data) {
  912. if (data) {
  913. cb();
  914. }
  915. });
  916. };
  917. data.blob = Crypto.Nacl.util.decodeBase64(data.blob);
  918. Files.upload(data, data.noStore, Cryptpad, updateProgress, onComplete, onError, onPending);
  919. cb();
  920. });
  921. };
  922. sframeChan.on('Q_UPLOAD_FILE', function (data, cb) {
  923. onFileUpload(sframeChan, data, cb);
  924. });
  925. // Secure modal
  926. var SecureModal = {};
  927. // Create or display the iframe and modal
  928. var initSecureModal = function (type, cfg, cb) {
  929. cfg.modal = type;
  930. SecureModal.cb = cb;
  931. // cfg.hidden means pre-loading the iframe while keeping it hidden.
  932. // if cfg.hidden is true and the iframe already exists, do nothing
  933. if (!SecureModal.$iframe) {
  934. var config = {};
  935. config.onAction = function (data) {
  936. if (typeof(SecureModal.cb) !== "function") { return; }
  937. SecureModal.cb(data);
  938. };
  939. config.onFileUpload = onFileUpload;
  940. config.onClose = function () {
  941. SecureModal.$iframe.hide();
  942. };
  943. config.data = {
  944. app: parsed.type,
  945. hashes: hashes,
  946. password: password,
  947. isTemplate: isTemplate
  948. };
  949. config.addCommonRpc = addCommonRpc;
  950. config.modules = {
  951. Cryptpad: Cryptpad,
  952. SFrameChannel: SFrameChannel,
  953. Utils: Utils
  954. };
  955. SecureModal.$iframe = $('<iframe>', {id: 'sbox-secure-iframe'}).appendTo($('body'));
  956. SecureModal.modal = SecureIframe.create(config);
  957. }
  958. if (!cfg.hidden) {
  959. SecureModal.modal.refresh(cfg, function () {
  960. SecureModal.$iframe.show();
  961. });
  962. } else {
  963. SecureModal.$iframe.hide();
  964. return;
  965. }
  966. SecureModal.$iframe.focus();
  967. };
  968. sframeChan.on('Q_FILE_PICKER_OPEN', function (data, cb) {
  969. initSecureModal('filepicker', data || {}, cb);
  970. });
  971. sframeChan.on('EV_PROPERTIES_OPEN', function (data) {
  972. initSecureModal('properties', data || {});
  973. });
  974. sframeChan.on('EV_ACCESS_OPEN', function (data) {
  975. initSecureModal('access', data || {});
  976. });
  977. sframeChan.on('EV_SHARE_OPEN', function (data) {
  978. initSecureModal('share', data || {});
  979. });
  980. sframeChan.on('Q_TEMPLATE_USE', function (data, cb) {
  981. Cryptpad.useTemplate(data, Cryptget, cb);
  982. });
  983. sframeChan.on('Q_TEMPLATE_EXIST', function (type, cb) {
  984. Cryptpad.listTemplates(type, function (err, templates) {
  985. cb(templates.length > 0);
  986. });
  987. });
  988. var getKey = function (href, channel) {
  989. var parsed = Utils.Hash.parsePadUrl(href);
  990. return 'thumbnail-' + parsed.type + '-' + channel;
  991. };
  992. sframeChan.on('Q_CREATE_TEMPLATES', function (type, cb) {
  993. Cryptpad.getSecureFilesList({
  994. types: [type],
  995. where: ['template']
  996. }, function (err, data) {
  997. // NOTE: Never return data directly!
  998. if (err) { return void cb({error: err}); }
  999. var res = [];
  1000. nThen(function (waitFor) {
  1001. Object.keys(data).map(function (el) {
  1002. var k = getKey(data[el].href, data[el].channel);
  1003. Utils.LocalStore.getThumbnail(k, waitFor(function (e, thumb) {
  1004. res.push({
  1005. id: el,
  1006. name: data[el].filename || data[el].title || '?',
  1007. thumbnail: thumb,
  1008. used: data[el].used || 0
  1009. });
  1010. }));
  1011. });
  1012. }).nThen(function () {
  1013. cb({data: res});
  1014. });
  1015. });
  1016. });
  1017. sframeChan.on('Q_GET_FILE_THUMBNAIL', function (data, cb) {
  1018. if (!Cryptpad.fromFileData || !Cryptpad.fromFileData.href) {
  1019. return void cb({
  1020. error: "EINVAL",
  1021. });
  1022. }
  1023. var key = getKey(Cryptpad.fromFileData.href, Cryptpad.fromFileData.channel);
  1024. Utils.LocalStore.getThumbnail(key, function (e, data) {
  1025. if (data === "EMPTY") { data = null; }
  1026. cb({
  1027. error: e,
  1028. data: data
  1029. });
  1030. });
  1031. });
  1032. sframeChan.on('Q_PIN_GET_USAGE', function (teamId, cb) {
  1033. Cryptpad.isOverPinLimit(teamId, function (err, overLimit, data) {
  1034. cb({
  1035. error: err,
  1036. data: data
  1037. });
  1038. });
  1039. });
  1040. sframeChan.on('Q_LANGUAGE_SET', function (data, cb) {
  1041. Cryptpad.setLanguage(data, cb);
  1042. });
  1043. sframeChan.on('Q_GET_ALL_TAGS', function (data, cb) {
  1044. Cryptpad.listAllTags(function (err, tags) {
  1045. cb({
  1046. error: err,
  1047. tags: tags
  1048. });
  1049. });
  1050. });
  1051. sframeChan.on('Q_BLOB_PASSWORD_CHANGE', function (data, cb) {
  1052. data.href = data.href || currentPad.href;
  1053. var onPending = function (cb) {
  1054. sframeChan.query('Q_BLOB_PASSWORD_CHANGE_PENDING', null, function (err, obj) {
  1055. if (obj && obj.cancel) { cb(); }
  1056. });
  1057. };
  1058. var updateProgress = function (p) {
  1059. sframeChan.event('EV_BLOB_PASSWORD_CHANGE_PROGRESS', p);
  1060. };
  1061. Cryptpad.changeBlobPassword(data, {
  1062. onPending: onPending,
  1063. updateProgress: updateProgress
  1064. }, cb);
  1065. });
  1066. sframeChan.on('Q_OO_PASSWORD_CHANGE', function (data, cb) {
  1067. data.href = data.href;
  1068. Cryptpad.changeOOPassword(data, cb);
  1069. });
  1070. sframeChan.on('Q_PAD_PASSWORD_CHANGE', function (data, cb) {
  1071. data.href = data.href;
  1072. Cryptpad.changePadPassword(Cryptget, Crypto, data, cb);
  1073. });
  1074. sframeChan.on('Q_CHANGE_USER_PASSWORD', function (data, cb) {
  1075. Cryptpad.changeUserPassword(Cryptget, edPublic, data, cb);
  1076. });
  1077. sframeChan.on('Q_WRITE_LOGIN_BLOCK', function (data, cb) {
  1078. Cryptpad.writeLoginBlock(data, cb);
  1079. });
  1080. sframeChan.on('Q_REMOVE_LOGIN_BLOCK', function (data, cb) {
  1081. Cryptpad.removeLoginBlock(data, cb);
  1082. });
  1083. // It seems we have performance issues when we open and close a lot of channels over
  1084. // the same network, maybe a memory leak. To fix this, we kill and create a new
  1085. // network every 30 cryptget calls (1 call = 1 channel)
  1086. var cgNetwork;
  1087. var whenCGReady = function (cb) {
  1088. if (cgNetwork && cgNetwork !== true) { console.log(cgNetwork); return void cb(); }
  1089. setTimeout(function () {
  1090. whenCGReady(cb);
  1091. }, 500);
  1092. };
  1093. var i = 0;
  1094. sframeChan.on('Q_CRYPTGET', function (data, cb) {
  1095. var todo = function () {
  1096. data.opts.network = cgNetwork;
  1097. Cryptget.get(data.hash, function (err, val) {
  1098. cb({
  1099. error: err,
  1100. data: val
  1101. });
  1102. }, data.opts, function (progress) {
  1103. sframeChan.event("EV_CRYPTGET_PROGRESS", {
  1104. hash: data.hash,
  1105. progress: progress,
  1106. });
  1107. });
  1108. };
  1109. //return void todo();
  1110. if (i > 30) {
  1111. i = 0;
  1112. cgNetwork = undefined;
  1113. }
  1114. i++;
  1115. if (!cgNetwork) {
  1116. cgNetwork = true;
  1117. return void Cryptpad.makeNetwork(function (err, nw) {
  1118. console.log(nw);
  1119. cgNetwork = nw;
  1120. todo();
  1121. });
  1122. } else if (cgNetwork === true) {
  1123. return void whenCGReady(todo);
  1124. }
  1125. todo();
  1126. });
  1127. sframeChan.on('EV_CRYPTGET_DISCONNECT', function () {
  1128. if (!cgNetwork) { return; }
  1129. cgNetwork.disconnect();
  1130. cgNetwork = undefined;
  1131. });
  1132. if (cfg.addRpc) {
  1133. cfg.addRpc(sframeChan, Cryptpad, Utils);
  1134. }
  1135. sframeChan.on('Q_CURSOR_OPENCHANNEL', function (data, cb) {
  1136. Cryptpad.cursor.execCommand({
  1137. cmd: 'INIT_CURSOR',
  1138. data: {
  1139. channel: data,
  1140. secret: secret
  1141. }
  1142. }, cb);
  1143. });
  1144. Cryptpad.cursor.onEvent.reg(function (data) {
  1145. sframeChan.event('EV_CURSOR_EVENT', data);
  1146. });
  1147. sframeChan.on('Q_CURSOR_COMMAND', function (data, cb) {
  1148. Cryptpad.cursor.execCommand(data, cb);
  1149. });
  1150. Cryptpad.universal.onEvent.reg(function (data) {
  1151. sframeChan.event('EV_UNIVERSAL_EVENT', data);
  1152. });
  1153. sframeChan.on('Q_UNIVERSAL_COMMAND', function (data, cb) {
  1154. Cryptpad.universal.execCommand(data, cb);
  1155. });
  1156. Cryptpad.onTimeoutEvent.reg(function () {
  1157. sframeChan.event('EV_WORKER_TIMEOUT');
  1158. });
  1159. sframeChan.on('EV_GIVE_ACCESS', function (data, cb) {
  1160. Cryptpad.padRpc.giveAccess(data, cb);
  1161. });
  1162. // REQUEST_ACCESS is used both to check IF we can contact an owner (send === false)
  1163. // AND also to send the request if we want (send === true)
  1164. sframeChan.on('Q_REQUEST_ACCESS', function (data, cb) {
  1165. if (readOnly && hashes.editHash) {
  1166. return void cb({error: 'ALREADYKNOWN'});
  1167. }
  1168. var send = data.send;
  1169. var metadata = data.metadata;
  1170. var owner, owners;
  1171. var _secret = secret;
  1172. if (metadata && metadata.roHref) {
  1173. var _parsed = Utils.Hash.parsePadUrl(metadata.roHref);
  1174. _secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password);
  1175. }
  1176. if (_secret.channel.length !== 32) {
  1177. return void cb({error: 'EINVAL'});
  1178. }
  1179. var crypto = Crypto.createEncryptor(_secret.keys);
  1180. nThen(function (waitFor) {
  1181. // Try to get the owner's mailbox from the pad metadata first.
  1182. // If it's is an older owned pad, check if the owner is a friend
  1183. // or an acquaintance (from async-store directly in requestAccess)
  1184. var todo = function (obj) {
  1185. owners = obj.owners;
  1186. var mailbox;
  1187. // Get the first available mailbox (the field can be an string or an object)
  1188. // TODO maybe we should send the request to all the owners?
  1189. if (typeof (obj.mailbox) === "string") {
  1190. mailbox = obj.mailbox;
  1191. } else if (obj.mailbox && obj.owners && obj.owners.length) {
  1192. mailbox = obj.mailbox[obj.owners[0]];
  1193. }
  1194. if (mailbox) {
  1195. try {
  1196. var dataStr = crypto.decrypt(mailbox, true, true);
  1197. var data = JSON.parse(dataStr);
  1198. if (!data.notifications || !data.curvePublic) { return; }
  1199. owner = data;
  1200. } catch (e) { console.error(e); }
  1201. }
  1202. };
  1203. // If we already have metadata, use it, otherwise, try to get it
  1204. if (metadata) { return void todo(metadata); }
  1205. Cryptpad.getPadMetadata({
  1206. channel: _secret.channel
  1207. }, waitFor(function (obj) {
  1208. obj = obj || {};
  1209. if (obj.error) { return; }
  1210. todo(obj);
  1211. }));
  1212. }).nThen(function () {
  1213. // If we are just checking (send === false) and there is a mailbox field, cb state true
  1214. // If there is no mailbox, we'll have to check if an owner is a friend in the worker
  1215. if (!send) { return void cb({state: Boolean(owner)}); }
  1216. Cryptpad.padRpc.requestAccess({
  1217. send: send,
  1218. channel: _secret.channel,
  1219. owner: owner,
  1220. owners: owners
  1221. }, cb);
  1222. });
  1223. });
  1224. // Add or remove our mailbox from the list if we're an owner
  1225. sframeChan.on('Q_UPDATE_MAILBOX', function (data, cb) {
  1226. var metadata = data.metadata;
  1227. var add = data.add;
  1228. var _secret = secret;
  1229. if (metadata && (metadata.href || metadata.roHref)) {
  1230. var _parsed = Utils.Hash.parsePadUrl(metadata.href || metadata.roHref);
  1231. _secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password);
  1232. }
  1233. if (_secret.channel.length !== 32) {
  1234. return void cb({error: 'EINVAL'});
  1235. }
  1236. var crypto = Crypto.createEncryptor(_secret.keys);
  1237. nThen(function (waitFor) {
  1238. // If we already have metadata, use it, otherwise, try to get it
  1239. if (metadata) { return; }
  1240. Cryptpad.getPadMetadata({
  1241. channel: secret.channel
  1242. }, waitFor(function (obj) {
  1243. obj = obj || {};
  1244. if (obj.error) {
  1245. waitFor.abort();
  1246. return void cb(obj);
  1247. }
  1248. metadata = obj;
  1249. }));
  1250. }).nThen(function () {
  1251. // Get and maybe migrate the existing mailbox object
  1252. var owners = metadata.owners;
  1253. if (!Array.isArray(owners) || owners.indexOf(edPublic) === -1) {
  1254. return void cb({ error: 'INSUFFICIENT_PERMISSIONS' });
  1255. }
  1256. // Remove a mailbox
  1257. if (!add) {
  1258. // Old format: this is the mailbox of the first owner
  1259. if (typeof (metadata.mailbox) === "string" && metadata.mailbox) {
  1260. // Not our mailbox? abort
  1261. if (owners[0] !== edPublic) {
  1262. return void cb({ error: 'INSUFFICIENT_PERMISSIONS' });
  1263. }
  1264. // Remove it
  1265. return void Cryptpad.setPadMetadata({
  1266. channel: _secret.channel,
  1267. command: 'RM_MAILBOX',
  1268. value: []
  1269. }, cb);
  1270. } else if (metadata.mailbox) { // New format
  1271. return void Cryptpad.setPadMetadata({
  1272. channel: _secret.channel,
  1273. command: 'RM_MAILBOX',
  1274. value: [edPublic]
  1275. }, cb);
  1276. }
  1277. return void cb({
  1278. error: 'NO_MAILBOX'
  1279. });
  1280. }
  1281. // Add a mailbox
  1282. var toAdd = {};
  1283. toAdd[edPublic] = crypto.encrypt(JSON.stringify({
  1284. notifications: notifications,
  1285. curvePublic: curvePublic
  1286. }));
  1287. Cryptpad.setPadMetadata({
  1288. channel: _secret.channel,
  1289. command: 'ADD_MAILBOX',
  1290. value: toAdd
  1291. }, cb);
  1292. });
  1293. });
  1294. sframeChan.on('EV_BURN_PAD', function (channel) {
  1295. if (!burnAfterReading) { return; }
  1296. Cryptpad.burnPad({
  1297. channel: channel,
  1298. ownerKey: burnAfterReading
  1299. });
  1300. });
  1301. if (cfg.messaging) {
  1302. Notifier.getPermission();
  1303. sframeChan.on('Q_CHAT_OPENPADCHAT', function (data, cb) {
  1304. Cryptpad.universal.execCommand({
  1305. type: 'messenger',
  1306. data: {
  1307. cmd: 'OPEN_PAD_CHAT',
  1308. data: {
  1309. channel: data,
  1310. secret: secret
  1311. }
  1312. }
  1313. }, cb);
  1314. });
  1315. }
  1316. // Chrome 68 on Mac contains a bug resulting in the page turning white after a few seconds
  1317. try {
  1318. if (navigator.platform.toUpperCase().indexOf('MAC') >= 0 &&
  1319. !localStorage.CryptPad_chrome68) {
  1320. var isChrome = !!window.chrome && !!window.chrome.webstore;
  1321. var getChromeVersion = function () {
  1322. var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
  1323. return raw ? parseInt(raw[2], 10) : false;
  1324. };
  1325. if (isChrome && getChromeVersion() === 68) {
  1326. sframeChan.whenReg('EV_CHROME_68', function () {
  1327. sframeChan.event("EV_CHROME_68");
  1328. localStorage.CryptPad_chrome68 = "1";
  1329. });
  1330. }
  1331. }
  1332. } catch (e) {}
  1333. // If our channel was deleted from all of our drives, sitch back to full hash
  1334. // in the address bar
  1335. Cryptpad.padRpc.onChannelDeleted.reg(function (channel) {
  1336. if (channel !== secret.channel) { return; }
  1337. var ohc = window.onhashchange;
  1338. window.onhashchange = function () {};
  1339. window.location.href = currentPad.href;
  1340. window.onhashchange = ohc;
  1341. ohc({reset: true});
  1342. });
  1343. // Join the netflux channel
  1344. var rtStarted = false;
  1345. var startRealtime = function (rtConfig) {
  1346. rtConfig = rtConfig || {};
  1347. rtStarted = true;
  1348. var replaceHash = function (hash) {
  1349. // The pad has just been created but is not stored yet. We'll switch
  1350. // to hidden hash once the pad is stored
  1351. if (window.history && window.history.replaceState) {
  1352. if (!/^#/.test(hash)) { hash = '#' + hash; }
  1353. window.history.replaceState({}, window.document.title, hash);
  1354. if (typeof(window.onhashchange) === 'function') {
  1355. window.onhashchange();
  1356. }
  1357. return;
  1358. }
  1359. window.location.hash = hash;
  1360. };
  1361. if (burnAfterReading) {
  1362. Cryptpad.padRpc.onReadyEvent.reg(function () {
  1363. Cryptpad.burnPad({
  1364. password: password,
  1365. href: currentPad.href,
  1366. channel: secret.channel,
  1367. ownerKey: burnAfterReading
  1368. });
  1369. });
  1370. }
  1371. var cpNfCfg = {
  1372. sframeChan: sframeChan,
  1373. channel: secret.channel,
  1374. padRpc: Cryptpad.padRpc,
  1375. validateKey: secret.keys.validateKey || undefined,
  1376. isNewHash: isNewHash,
  1377. readOnly: readOnly,
  1378. crypto: Crypto.createEncryptor(secret.keys),
  1379. onConnect: function () {
  1380. if (currentPad.hash && currentPad.hash !== '#') {
  1381. /*window.location = parsed.getUrl({
  1382. present: parsed.hashData.present,
  1383. embed: parsed.hashData.embed
  1384. });*/
  1385. return;
  1386. }
  1387. if (readOnly || cfg.noHash) { return; }
  1388. replaceHash(Utils.Hash.getEditHashFromKeys(secret));
  1389. }
  1390. };
  1391. nThen(function (waitFor) {
  1392. if (isNewFile && cfg.owned && !currentPad.hash) {
  1393. Cryptpad.getMetadata(waitFor(function (err, m) {
  1394. cpNfCfg.owners = [m.priv.edPublic];
  1395. }));
  1396. } else if (isNewFile && !cfg.useCreationScreen && currentPad.hash) {
  1397. console.log("new file with hash in the address bar in an app without pcs and which requires owners");
  1398. sframeChan.onReady(function () {
  1399. sframeChan.query("EV_LOADING_ERROR", "DELETED");
  1400. });
  1401. waitFor.abort();
  1402. }
  1403. }).nThen(function () {
  1404. Object.keys(rtConfig).forEach(function (k) {
  1405. cpNfCfg[k] = rtConfig[k];
  1406. });
  1407. CpNfOuter.start(cpNfCfg);
  1408. });
  1409. };
  1410. sframeChan.on('Q_CREATE_PAD', function (data, cb) {
  1411. if (!isNewFile || rtStarted) { return; }
  1412. // Create a new hash
  1413. password = data.password;
  1414. var newHash = Utils.Hash.createRandomHash(parsed.type, password);
  1415. secret = Utils.secret = Utils.Hash.getSecrets(parsed.type, newHash, password);
  1416. Utils.crypto = Utils.Crypto.createEncryptor(Utils.secret.keys);
  1417. // Update the hash in the address bar
  1418. var ohc = window.onhashchange;
  1419. window.onhashchange = function () {};
  1420. window.location.hash = newHash;
  1421. currentPad.hash = newHash;
  1422. currentPad.href = '/' + parsed.type + '/#' + newHash;
  1423. window.onhashchange = ohc;
  1424. ohc({reset: true});
  1425. // Update metadata values and send new metadata inside
  1426. parsed = Utils.Hash.parsePadUrl(currentPad.href);
  1427. defaultTitle = Utils.UserObject.getDefaultName(parsed);
  1428. hashes = Utils.Hash.getHashes(secret);
  1429. readOnly = false;
  1430. updateMeta();
  1431. var rtConfig = {
  1432. metadata: {}
  1433. };
  1434. if (data.team) {
  1435. Cryptpad.initialTeam = data.team.id;
  1436. }
  1437. if (data.owned && data.team && data.team.edPublic) {
  1438. rtConfig.metadata.owners = [data.team.edPublic];
  1439. } else if (data.owned) {
  1440. rtConfig.metadata.owners = [edPublic];
  1441. rtConfig.metadata.mailbox = {};
  1442. rtConfig.metadata.mailbox[edPublic] = Utils.crypto.encrypt(JSON.stringify({
  1443. notifications: notifications,
  1444. curvePublic: curvePublic
  1445. }));
  1446. }
  1447. if (data.expire) {
  1448. rtConfig.metadata.expire = data.expire;
  1449. }
  1450. rtConfig.metadata.validateKey = (secret.keys && secret.keys.validateKey) || undefined;
  1451. Utils.rtConfig = rtConfig;
  1452. nThen(function(waitFor) {
  1453. if (data.templateId) {
  1454. if (data.templateId === -1) {
  1455. initialPathInDrive = ['template'];
  1456. return;
  1457. }
  1458. Cryptpad.getPadData(data.templateId, waitFor(function (err, d) {
  1459. data.template = d.href;
  1460. }));
  1461. }
  1462. }).nThen(function () {
  1463. var cryptputCfg = $.extend(true, {}, rtConfig, {password: password});
  1464. if (data.template) {
  1465. // Pass rtConfig to useTemplate because Cryptput will create the file and
  1466. // we need to have the owners and expiration time in the first line on the
  1467. // server
  1468. Cryptpad.useTemplate({
  1469. href: data.template
  1470. }, Cryptget, function () {
  1471. startRealtime();
  1472. cb();
  1473. }, cryptputCfg);
  1474. return;
  1475. }
  1476. // if we open a new code from a file
  1477. if (Cryptpad.fromFileData) {
  1478. Cryptpad.useFile(Cryptget, function () {
  1479. startRealtime();
  1480. cb();
  1481. }, cryptputCfg);
  1482. return;
  1483. }
  1484. // Start realtime outside the iframe and callback
  1485. startRealtime(rtConfig);
  1486. cb();
  1487. });
  1488. });
  1489. sframeChan.on('EV_BURN_AFTER_READING', function () {
  1490. startRealtime();
  1491. });
  1492. sframeChan.ready();
  1493. Utils.Feedback.reportAppUsage();
  1494. if (!realtime && !Test.testing) { return; }
  1495. if (isNewFile && cfg.useCreationScreen && !Test.testing) { return; }
  1496. if (burnAfterReading) { return; }
  1497. //if (isNewFile && Utils.LocalStore.isLoggedIn()
  1498. // && AppConfig.displayCreationScreen && cfg.useCreationScreen) { return; }
  1499. startRealtime();
  1500. });
  1501. };
  1502. return common;
  1503. });