downloads.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. /*
  2. * Copyright 2010-2020 Gildas Lormeau
  3. * contact : gildas.lormeau <at> gmail.com
  4. *
  5. * This file is part of SingleFile.
  6. *
  7. * The code in this file is free software: you can redistribute it and/or
  8. * modify it under the terms of the GNU Affero General Public License
  9. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  10. * of the License, or (at your option) any later version.
  11. *
  12. * The code in this file is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  15. * General Public License for more details.
  16. *
  17. * As additional permission under GNU AGPL version 3 section 7, you may
  18. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  19. * AGPL normally required by section 4, provided you include this license
  20. * notice and a URL through which recipients can access the Corresponding
  21. * Source.
  22. */
  23. /* global browser, singlefile, URL, fetch, document, Blob */
  24. import * as config from "./config.js";
  25. import * as bookmarks from "./bookmarks.js";
  26. import * as companion from "./companion.js";
  27. import * as business from "./business.js";
  28. import * as editor from "./editor.js";
  29. import { launchWebAuthFlow, extractAuthCode } from "./tabs-util.js";
  30. import * as ui from "./../../ui/bg/index.js";
  31. import * as woleet from "./../../lib/woleet/woleet.js";
  32. import { GDrive } from "./../../lib/gdrive/gdrive.js";
  33. import { Dropbox } from "./../../lib/dropbox/dropbox.js";
  34. import { WebDAV } from "./../../lib/webdav/webdav.js";
  35. import { GitHub } from "./../../lib/github/github.js";
  36. import { S3 } from "./../../lib/s3/s3.js";
  37. import { download } from "./download-util.js";
  38. import * as yabson from "./../../lib/yabson/yabson.js";
  39. import { RestFormApi } from "../../lib/../lib/rest-form-api/index.js";
  40. const partialContents = new Map();
  41. const tabData = new Map();
  42. const SCOPES = ["https://www.googleapis.com/auth/drive.file"];
  43. const CONFLICT_ACTION_SKIP = "skip";
  44. const CONFLICT_ACTION_UNIQUIFY = "uniquify";
  45. const REGEXP_ESCAPE = /([{}()^$&.*?/+|[\\\\]|\]|-)/g;
  46. let GDRIVE_CLIENT_ID = "207618107333-h1220p1oasj3050kr5r416661adm091a.apps.googleusercontent.com";
  47. let GDRIVE_CLIENT_KEY = "VQJ8Gq8Vxx72QyxPyeLtWvUt";
  48. const DROPBOX_CLIENT_ID = "s50p6litdvuzrtb";
  49. const DROPBOX_CLIENT_KEY = "i1vzwllesr14fzd";
  50. const gDriveOauth2 = browser.runtime.getManifest().oauth2;
  51. if (gDriveOauth2) {
  52. GDRIVE_CLIENT_ID = gDriveOauth2.client_id;
  53. GDRIVE_CLIENT_KEY = gDriveOauth2.client_secret;
  54. }
  55. const gDrive = new GDrive(GDRIVE_CLIENT_ID, GDRIVE_CLIENT_KEY, SCOPES);
  56. const dropbox = new Dropbox(DROPBOX_CLIENT_ID, DROPBOX_CLIENT_KEY);
  57. export {
  58. onMessage,
  59. downloadPage,
  60. testSkipSave,
  61. saveToGDrive,
  62. saveToGitHub,
  63. saveToDropbox,
  64. saveWithWebDAV,
  65. saveToRestFormApi,
  66. saveToS3,
  67. encodeSharpCharacter
  68. };
  69. async function onMessage(message, sender) {
  70. if (message.method.endsWith(".download")) {
  71. return downloadTabPage(message, sender.tab);
  72. }
  73. if (message.method.endsWith(".disableGDrive")) {
  74. const authInfo = await config.getAuthInfo();
  75. config.removeAuthInfo();
  76. await gDrive.revokeAuthToken(authInfo && (authInfo.accessToken || authInfo.revokableAccessToken));
  77. return {};
  78. }
  79. if (message.method.endsWith(".disableDropbox")) {
  80. const authInfo = await config.getDropboxAuthInfo();
  81. config.removeDropboxAuthInfo();
  82. await dropbox.revokeAuthToken(authInfo && (authInfo.accessToken || authInfo.revokableAccessToken));
  83. return {};
  84. }
  85. if (message.method.endsWith(".end")) {
  86. if (message.hash) {
  87. try {
  88. await woleet.anchor(message.hash, message.woleetKey);
  89. } catch (error) {
  90. ui.onError(sender.tab.id, error.message, error.link);
  91. }
  92. }
  93. business.onSaveEnd(message.taskId);
  94. return {};
  95. }
  96. if (message.method.endsWith(".getInfo")) {
  97. return business.getTasksInfo();
  98. }
  99. if (message.method.endsWith(".cancel")) {
  100. if (message.taskId) {
  101. business.cancelTask(message.taskId);
  102. } else {
  103. business.cancel(sender.tab.id);
  104. }
  105. return {};
  106. }
  107. if (message.method.endsWith(".cancelAll")) {
  108. business.cancelAllTasks();
  109. return {};
  110. }
  111. if (message.method.endsWith(".saveUrls")) {
  112. business.saveUrls(message.urls);
  113. return {};
  114. }
  115. }
  116. async function downloadTabPage(message, tab) {
  117. const tabId = tab.id;
  118. let contents;
  119. if (message.blobURL) {
  120. try {
  121. if (message.compressContent) {
  122. message.pageData = await yabson.parse(new Uint8Array(await (await fetch(message.blobURL)).arrayBuffer()));
  123. await downloadCompressedContent(message, tab);
  124. } else {
  125. message.content = await (await fetch(message.blobURL)).text();
  126. await downloadContent([message.content], tab, tab.incognito, message);
  127. }
  128. // eslint-disable-next-line no-unused-vars
  129. } catch (error) {
  130. return { error: true };
  131. }
  132. } else if (message.compressContent) {
  133. let parser = tabData.get(tabId);
  134. if (!parser) {
  135. parser = yabson.getParser();
  136. tabData.set(tabId, parser);
  137. }
  138. if (message.data) {
  139. await parser.next(new Uint8Array(message.data));
  140. } else {
  141. tabData.delete(tabId);
  142. const result = await parser.next();
  143. const message = result.value;
  144. await downloadCompressedContent(message, tab);
  145. }
  146. } else {
  147. if (message.truncated) {
  148. contents = partialContents.get(tabId);
  149. if (!contents) {
  150. contents = [];
  151. partialContents.set(tabId, contents);
  152. }
  153. contents.push(message.content);
  154. if (message.finished) {
  155. partialContents.delete(tabId);
  156. }
  157. } else if (message.content) {
  158. contents = [message.content];
  159. }
  160. if (!message.truncated || message.finished) {
  161. await downloadContent(contents, tab, tab.incognito, message);
  162. }
  163. }
  164. return {};
  165. }
  166. async function downloadContent(contents, tab, incognito, message) {
  167. const tabId = tab.id;
  168. try {
  169. let skipped;
  170. if (message.backgroundSave && !message.saveToGDrive && !message.saveToDropbox && !message.saveWithWebDAV && !message.saveToGitHub && !message.saveToRestFormApi && !message.saveToS3) {
  171. const testSkip = await testSkipSave(message.filename, message);
  172. message.filenameConflictAction = testSkip.filenameConflictAction;
  173. skipped = testSkip.skipped;
  174. }
  175. if (skipped) {
  176. ui.onEnd(tabId);
  177. } else {
  178. const prompt = filename => promptFilename(tabId, filename);
  179. let response;
  180. if (message.openEditor) {
  181. ui.onEdit(tabId);
  182. await editor.open({ tabIndex: tab.index + 1, filename: message.filename, content: contents.join(""), url: message.originalUrl });
  183. } else if (message.saveToClipboard) {
  184. message.content = contents.join("");
  185. saveToClipboard(message);
  186. } else if (message.saveWithWebDAV) {
  187. response = await saveWithWebDAV(message.taskId, encodeSharpCharacter(message.filename), contents.join(""), message.webDAVURL, message.webDAVUser, message.webDAVPassword, { filenameConflictAction: message.filenameConflictAction, prompt });
  188. } else if (message.saveToGDrive) {
  189. await saveToGDrive(message.taskId, encodeSharpCharacter(message.filename), new Blob(contents, { type: message.mimeType }), {
  190. forceWebAuthFlow: message.forceWebAuthFlow
  191. }, {
  192. onProgress: (offset, size) => ui.onUploadProgress(tabId, offset, size),
  193. filenameConflictAction: message.filenameConflictAction,
  194. prompt
  195. });
  196. } else if (message.saveToDropbox) {
  197. await saveToDropbox(message.taskId, encodeSharpCharacter(message.filename), new Blob(contents, { type: message.mimeType }), {
  198. onProgress: (offset, size) => ui.onUploadProgress(tabId, offset, size),
  199. filenameConflictAction: message.filenameConflictAction,
  200. prompt
  201. });
  202. } else if (message.saveToGitHub) {
  203. response = await saveToGitHub(message.taskId, encodeSharpCharacter(message.filename), contents.join(""), message.githubToken, message.githubUser, message.githubRepository, message.githubBranch, {
  204. filenameConflictAction: message.filenameConflictAction,
  205. prompt
  206. });
  207. await response.pushPromise;
  208. } else if (message.saveWithCompanion) {
  209. await companion.save({
  210. filename: message.filename,
  211. content: message.content,
  212. filenameConflictAction: message.filenameConflictAction
  213. });
  214. } else if (message.saveToRestFormApi) {
  215. response = await saveToRestFormApi(
  216. message.taskId,
  217. message.filename,
  218. contents.join(""),
  219. tab.url,
  220. message.saveToRestFormApiToken,
  221. message.saveToRestFormApiUrl,
  222. message.saveToRestFormApiFileFieldName,
  223. message.saveToRestFormApiUrlFieldName
  224. );
  225. } else if (message.saveToS3) {
  226. response = await saveToS3(message.taskId, encodeSharpCharacter(message.filename), new Blob(contents, { type: message.mimeType }), message.S3Domain, message.S3Region, message.S3Bucket, message.S3AccessKey, message.S3SecretKey, {
  227. filenameConflictAction: message.filenameConflictAction,
  228. prompt
  229. });
  230. } else {
  231. message.url = URL.createObjectURL(new Blob(contents, { type: message.mimeType }));
  232. response = await downloadPage(message, {
  233. confirmFilename: message.confirmFilename,
  234. incognito,
  235. filenameConflictAction: message.filenameConflictAction,
  236. filenameReplacementCharacter: message.filenameReplacementCharacter,
  237. bookmarkId: message.bookmarkId,
  238. replaceBookmarkURL: message.replaceBookmarkURL,
  239. includeInfobar: message.includeInfobar,
  240. openInfobar: message.openInfobar,
  241. infobarPositionAbsolute: message.infobarPositionAbsolute,
  242. infobarPositionTop: message.infobarPositionTop,
  243. infobarPositionBottom: message.infobarPositionBottom,
  244. infobarPositionLeft: message.infobarPositionLeft,
  245. infobarPositionRight: message.infobarPositionRight
  246. });
  247. if (!response) {
  248. throw new Error("upload_cancelled");
  249. }
  250. }
  251. if (message.bookmarkId && message.replaceBookmarkURL && response && response.url) {
  252. await bookmarks.update(message.bookmarkId, { url: response.url });
  253. }
  254. ui.onEnd(tabId);
  255. if (message.openSavedPage && !message.openEditor) {
  256. const createTabProperties = { active: true, url: "/src/ui/pages/viewer.html?blobURI=" + URL.createObjectURL(new Blob(contents, { type: message.mimeType })), windowId: tab.windowId };
  257. if (tab.index != null) {
  258. createTabProperties.index = tab.index + 1;
  259. }
  260. browser.tabs.create(createTabProperties);
  261. }
  262. }
  263. } catch (error) {
  264. if (!error.message || error.message != "upload_cancelled") {
  265. console.error(error); // eslint-disable-line no-console
  266. ui.onError(tabId, error.message, error.link);
  267. }
  268. } finally {
  269. if (message.url) {
  270. URL.revokeObjectURL(message.url);
  271. }
  272. }
  273. }
  274. async function downloadCompressedContent(message, tab) {
  275. const tabId = tab.id;
  276. try {
  277. let skipped;
  278. if (message.backgroundSave && !message.saveToGDrive && !message.saveToDropbox && !message.saveWithWebDAV && !message.saveToGitHub && !message.saveToRestFormApi && !message.sharePage) {
  279. const testSkip = await testSkipSave(message.filename, message);
  280. message.filenameConflictAction = testSkip.filenameConflictAction;
  281. skipped = testSkip.skipped;
  282. }
  283. if (skipped) {
  284. ui.onEnd(tabId);
  285. } else {
  286. const pageData = message.pageData;
  287. const prompt = filename => promptFilename(tabId, filename);
  288. const blob = await singlefile.processors.compression.process(pageData, {
  289. insertTextBody: message.insertTextBody,
  290. url: pageData.url || tab.url,
  291. createRootDirectory: message.createRootDirectory,
  292. tabId,
  293. selfExtractingArchive: message.selfExtractingArchive,
  294. extractDataFromPage: message.extractDataFromPage,
  295. preventAppendedData: message.preventAppendedData,
  296. insertCanonicalLink: message.insertCanonicalLink,
  297. insertMetaNoIndex: message.insertMetaNoIndex,
  298. insertMetaCSP: message.insertMetaCSP,
  299. password: message.password,
  300. embeddedImage: message.embeddedImage
  301. });
  302. let response;
  303. if (message.openEditor) {
  304. ui.onEdit(tabId);
  305. await editor.open({
  306. tabIndex: tab.index + 1,
  307. filename: message.filename,
  308. content: Array.from(new Uint8Array(await blob.arrayBuffer())),
  309. compressContent: message.compressContent,
  310. selfExtractingArchive: message.selfExtractingArchive,
  311. extractDataFromPage: message.extractDataFromPage,
  312. insertTextBody: message.insertTextBody,
  313. insertMetaCSP: message.insertMetaCSP,
  314. embeddedImage: message.embeddedImage,
  315. url: message.originalUrl
  316. });
  317. } else if (message.foregroundSave || !message.backgroundSave || message.sharePage) {
  318. const response = await downloadPageForeground(message.taskId, message.filename, blob, pageData.mimeType, tabId, {
  319. foregroundSave: true,
  320. sharePage: message.sharePage
  321. });
  322. if (response.error) {
  323. throw new Error(response.error);
  324. }
  325. } else if (message.saveWithWebDAV) {
  326. response = await saveWithWebDAV(message.taskId, encodeSharpCharacter(message.filename), blob, message.webDAVURL, message.webDAVUser, message.webDAVPassword, { filenameConflictAction: message.filenameConflictAction, prompt });
  327. } else if (message.saveToGDrive) {
  328. await saveToGDrive(message.taskId, encodeSharpCharacter(message.filename), blob, {
  329. forceWebAuthFlow: message.forceWebAuthFlow
  330. }, {
  331. onProgress: (offset, size) => ui.onUploadProgress(tabId, offset, size),
  332. filenameConflictAction: message.filenameConflictAction,
  333. prompt
  334. });
  335. } else if (message.saveToDropbox) {
  336. await saveToDropbox(message.taskId, encodeSharpCharacter(message.filename), blob, {
  337. onProgress: (offset, size) => ui.onUploadProgress(tabId, offset, size),
  338. filenameConflictAction: message.filenameConflictAction,
  339. prompt
  340. });
  341. } else if (message.saveToGitHub) {
  342. response = await saveToGitHub(message.taskId, encodeSharpCharacter(message.filename), blob, message.githubToken, message.githubUser, message.githubRepository, message.githubBranch, {
  343. filenameConflictAction: message.filenameConflictAction,
  344. prompt
  345. });
  346. await response.pushPromise;
  347. } else if (message.saveToRestFormApi) {
  348. response = await saveToRestFormApi(
  349. message.taskId,
  350. message.filename,
  351. blob,
  352. tab.url,
  353. message.saveToRestFormApiToken,
  354. message.saveToRestFormApiUrl,
  355. message.saveToRestFormApiFileFieldName,
  356. message.saveToRestFormApiUrlFieldName
  357. );
  358. } else if (message.saveToS3) {
  359. response = await saveToS3(message.taskId, encodeSharpCharacter(message.filename), blob, message.S3Domain, message.S3Region, message.S3Bucket, message.S3AccessKey, message.S3SecretKey, {
  360. filenameConflictAction: message.filenameConflictAction,
  361. prompt
  362. });
  363. } else {
  364. message.url = URL.createObjectURL(blob);
  365. response = await downloadPage(message, {
  366. confirmFilename: message.confirmFilename,
  367. incognito: tab.incognito,
  368. filenameConflictAction: message.filenameConflictAction,
  369. filenameReplacementCharacter: message.filenameReplacementCharacter,
  370. bookmarkId: message.bookmarkId,
  371. replaceBookmarkURL: message.replaceBookmarkURL,
  372. includeInfobar: message.includeInfobar,
  373. openInfobar: message.openInfobar,
  374. infobarPositionAbsolute: message.infobarPositionAbsolute,
  375. infobarPositionTop: message.infobarPositionTop,
  376. infobarPositionBottom: message.infobarPositionBottom,
  377. infobarPositionLeft: message.infobarPositionLeft,
  378. infobarPositionRight: message.infobarPositionRight
  379. });
  380. }
  381. if (message.bookmarkId && message.replaceBookmarkURL && response && response.url) {
  382. await bookmarks.update(message.bookmarkId, { url: response.url });
  383. }
  384. ui.onEnd(tabId);
  385. if (message.openSavedPage && !message.openEditor) {
  386. const createTabProperties = { active: true, url: "/src/ui/pages/viewer.html?compressed&blobURI=" + URL.createObjectURL(blob), windowId: tab.windowId };
  387. if (tab.index != null) {
  388. createTabProperties.index = tab.index + 1;
  389. }
  390. browser.tabs.create(createTabProperties);
  391. }
  392. }
  393. } catch (error) {
  394. if (!error.message || error.message != "upload_cancelled") {
  395. console.error(error); // eslint-disable-line no-console
  396. ui.onError(tabId, error.message, error.link);
  397. }
  398. } finally {
  399. if (message.url) {
  400. URL.revokeObjectURL(message.url);
  401. }
  402. }
  403. }
  404. function encodeSharpCharacter(path) {
  405. return path.replace(/#/g, "%23");
  406. }
  407. function getRegExp(string) {
  408. return string.replace(REGEXP_ESCAPE, "\\$1");
  409. }
  410. async function getAuthInfo(authOptions, force) {
  411. let authInfo = await config.getAuthInfo();
  412. const options = {
  413. interactive: true,
  414. forceWebAuthFlow: authOptions.forceWebAuthFlow,
  415. launchWebAuthFlow: options => launchWebAuthFlow(options),
  416. extractAuthCode: authURL => extractAuthCode(authURL)
  417. };
  418. gDrive.setAuthInfo(authInfo, options);
  419. if (!authInfo || !authInfo.accessToken || force) {
  420. authInfo = await gDrive.auth(options);
  421. if (authInfo) {
  422. await config.setAuthInfo(authInfo);
  423. } else {
  424. await config.removeAuthInfo();
  425. }
  426. }
  427. return authInfo;
  428. }
  429. async function getDropboxAuthInfo(force) {
  430. let authInfo = await config.getDropboxAuthInfo();
  431. const options = {
  432. launchWebAuthFlow: options => launchWebAuthFlow(options),
  433. extractAuthCode: authURL => extractAuthCode(authURL)
  434. };
  435. dropbox.setAuthInfo(authInfo);
  436. if (!authInfo || !authInfo.accessToken || force) {
  437. authInfo = await dropbox.auth(options);
  438. if (authInfo) {
  439. await config.setDropboxAuthInfo(authInfo);
  440. } else {
  441. await config.removeDropboxAuthInfo();
  442. }
  443. }
  444. return authInfo;
  445. }
  446. async function saveToGitHub(taskId, filename, content, githubToken, githubUser, githubRepository, githubBranch, { filenameConflictAction, prompt }) {
  447. try {
  448. const taskInfo = business.getTaskInfo(taskId);
  449. if (!taskInfo || !taskInfo.cancelled) {
  450. const client = new GitHub(githubToken, githubUser, githubRepository, githubBranch);
  451. business.setCancelCallback(taskId, () => client.abort());
  452. return await client.upload(filename, content, { filenameConflictAction, prompt });
  453. }
  454. } catch (error) {
  455. throw new Error(error.message + " (GitHub)");
  456. }
  457. }
  458. async function saveToS3(taskId, filename, blob, domain, region, bucket, accessKey, secretKey, { filenameConflictAction, prompt }) {
  459. try {
  460. const taskInfo = business.getTaskInfo(taskId);
  461. if (!taskInfo || !taskInfo.cancelled) {
  462. const client = new S3(region, bucket, accessKey, secretKey, domain);
  463. business.setCancelCallback(taskId, () => client.abort());
  464. return await client.upload(filename, blob, { filenameConflictAction, prompt });
  465. }
  466. } catch (error) {
  467. throw new Error(error.message + " (S3)");
  468. }
  469. }
  470. async function saveWithWebDAV(taskId, filename, content, url, username, password, { filenameConflictAction, prompt }) {
  471. try {
  472. const taskInfo = business.getTaskInfo(taskId);
  473. if (!taskInfo || !taskInfo.cancelled) {
  474. const client = new WebDAV(url, username, password);
  475. business.setCancelCallback(taskId, () => client.abort());
  476. return await client.upload(filename, content, { filenameConflictAction, prompt });
  477. }
  478. } catch (error) {
  479. throw new Error(error.message + " (WebDAV)");
  480. }
  481. }
  482. async function saveToGDrive(taskId, filename, blob, authOptions, uploadOptions) {
  483. try {
  484. await getAuthInfo(authOptions);
  485. const taskInfo = business.getTaskInfo(taskId);
  486. if (!taskInfo || !taskInfo.cancelled) {
  487. return await gDrive.upload(filename, blob, uploadOptions, callback => business.setCancelCallback(taskId, callback));
  488. }
  489. }
  490. catch (error) {
  491. if (error.message == "invalid_token") {
  492. let authInfo;
  493. try {
  494. authInfo = await gDrive.refreshAuthToken();
  495. } catch (error) {
  496. if (error.message == "unknown_token") {
  497. authInfo = await getAuthInfo(authOptions, true);
  498. } else {
  499. throw new Error(error.message + " (Google Drive)");
  500. }
  501. }
  502. if (authInfo) {
  503. await config.setAuthInfo(authInfo);
  504. } else {
  505. await config.removeAuthInfo();
  506. }
  507. return await saveToGDrive(taskId, filename, blob, authOptions, uploadOptions);
  508. } else {
  509. throw new Error(error.message + " (Google Drive)");
  510. }
  511. }
  512. }
  513. async function saveToDropbox(taskId, filename, blob, uploadOptions) {
  514. try {
  515. await getDropboxAuthInfo();
  516. const taskInfo = business.getTaskInfo(taskId);
  517. if (!taskInfo || !taskInfo.cancelled) {
  518. return await dropbox.upload(filename, blob, uploadOptions, callback => business.setCancelCallback(taskId, callback));
  519. }
  520. }
  521. catch (error) {
  522. if (error.message == "invalid_token") {
  523. let authInfo;
  524. try {
  525. authInfo = await dropbox.refreshAuthToken();
  526. } catch (error) {
  527. if (error.message == "unknown_token") {
  528. authInfo = await getDropboxAuthInfo(true);
  529. } else {
  530. throw new Error(error.message + " (Dropbox)");
  531. }
  532. }
  533. if (authInfo) {
  534. await config.setDropboxAuthInfo(authInfo);
  535. } else {
  536. await config.removeDropboxAuthInfo();
  537. }
  538. return await saveToDropbox(taskId, filename, blob, uploadOptions);
  539. } else {
  540. throw new Error(error.message + " (Dropbox)");
  541. }
  542. }
  543. }
  544. async function testSkipSave(filename, options) {
  545. let skipped, filenameConflictAction = options.filenameConflictAction;
  546. if (filenameConflictAction == CONFLICT_ACTION_SKIP) {
  547. const downloadItems = await browser.downloads.search({
  548. filenameRegex: "(\\\\|/)" + getRegExp(filename) + "$",
  549. exists: true
  550. });
  551. if (downloadItems.length) {
  552. skipped = true;
  553. } else {
  554. filenameConflictAction = CONFLICT_ACTION_UNIQUIFY;
  555. }
  556. }
  557. return { skipped, filenameConflictAction };
  558. }
  559. function promptFilename(tabId, filename) {
  560. return browser.tabs.sendMessage(tabId, { method: "content.prompt", message: "Filename conflict, please enter a new filename", value: filename });
  561. }
  562. async function downloadPage(pageData, options) {
  563. const downloadInfo = {
  564. url: pageData.url,
  565. saveAs: options.confirmFilename,
  566. filename: pageData.filename,
  567. conflictAction: options.filenameConflictAction
  568. };
  569. if (options.incognito) {
  570. downloadInfo.incognito = true;
  571. }
  572. const downloadData = await download(downloadInfo, options.filenameReplacementCharacter);
  573. if (downloadData.filename) {
  574. let url = downloadData.filename;
  575. if (!url.startsWith("file:")) {
  576. if (url.startsWith("/")) {
  577. url = url.substring(1);
  578. }
  579. url = "file:///" + encodeSharpCharacter(url);
  580. }
  581. return { url };
  582. }
  583. if (downloadData.cancelled) {
  584. business.cancelTask(pageData.taskId);
  585. }
  586. }
  587. function saveToClipboard(pageData) {
  588. const command = "copy";
  589. document.addEventListener(command, listener);
  590. document.execCommand(command);
  591. document.removeEventListener(command, listener);
  592. function listener(event) {
  593. event.clipboardData.setData(pageData.mimeType, pageData.content);
  594. event.clipboardData.setData("text/plain", pageData.content);
  595. event.preventDefault();
  596. }
  597. }
  598. async function saveToRestFormApi(taskId, filename, content, url, token, restApiUrl, fileFieldName, urlFieldName) {
  599. try {
  600. const taskInfo = business.getTaskInfo(taskId);
  601. if (!taskInfo || !taskInfo.cancelled) {
  602. const client = new RestFormApi(token, restApiUrl, fileFieldName, urlFieldName);
  603. business.setCancelCallback(taskId, () => client.abort());
  604. return await client.upload(filename, content, url);
  605. }
  606. } catch (error) {
  607. throw new Error(error.message + " (RestFormApi)");
  608. }
  609. }
  610. async function downloadPageForeground(taskId, filename, content, mimeType, tabId, { foregroundSave, sharePage } = {}) {
  611. const serializer = yabson.getSerializer({
  612. filename,
  613. taskId,
  614. foregroundSave,
  615. sharePage,
  616. content: await content.arrayBuffer(),
  617. mimeType
  618. });
  619. for await (const data of serializer) {
  620. await browser.tabs.sendMessage(tabId, {
  621. method: "content.download",
  622. data: Array.from(data)
  623. });
  624. }
  625. return browser.tabs.sendMessage(tabId, { method: "content.download" });
  626. }