download-util.js 3.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  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 */
  24. const STATE_DOWNLOAD_COMPLETE = "complete";
  25. const STATE_DOWNLOAD_INTERRUPTED = "interrupted";
  26. const STATE_ERROR_CANCELED_CHROMIUM = "USER_CANCELED";
  27. const ERROR_DOWNLOAD_CANCELED_GECKO = "canceled";
  28. const ERROR_CONFLICT_ACTION_GECKO = "conflictaction prompt not yet implemented";
  29. const ERROR_INCOGNITO_GECKO = "'incognito'";
  30. const ERROR_INCOGNITO_GECKO_ALT = "\"incognito\"";
  31. const ERROR_INVALID_FILENAME_GECKO = "illegal characters";
  32. const ERROR_INVALID_FILENAME_CHROMIUM = "invalid filename";
  33. export {
  34. download
  35. };
  36. async function download(downloadInfo, replacementCharacter) {
  37. let downloadId;
  38. try {
  39. downloadId = await browser.downloads.download(downloadInfo);
  40. } catch (error) {
  41. if (error.message) {
  42. const errorMessage = error.message.toLowerCase();
  43. const invalidFilename = errorMessage.includes(ERROR_INVALID_FILENAME_GECKO) || errorMessage.includes(ERROR_INVALID_FILENAME_CHROMIUM);
  44. if (invalidFilename && downloadInfo.filename.startsWith(".")) {
  45. downloadInfo.filename = replacementCharacter + downloadInfo.filename;
  46. return download(downloadInfo, replacementCharacter);
  47. } else if (invalidFilename && downloadInfo.filename.includes(",")) {
  48. downloadInfo.filename = downloadInfo.filename.replace(/,/g, replacementCharacter);
  49. return download(downloadInfo, replacementCharacter);
  50. } else if (invalidFilename && !downloadInfo.filename.match(/^[\x00-\x7F]+$/)) { // eslint-disable-line no-control-regex
  51. downloadInfo.filename = downloadInfo.filename.replace(/[^\x00-\x7F]+/g, replacementCharacter); // eslint-disable-line no-control-regex
  52. return download(downloadInfo, replacementCharacter);
  53. } else if ((errorMessage.includes(ERROR_INCOGNITO_GECKO) || errorMessage.includes(ERROR_INCOGNITO_GECKO_ALT)) && downloadInfo.incognito) {
  54. delete downloadInfo.incognito;
  55. return download(downloadInfo, replacementCharacter);
  56. } else if (errorMessage == ERROR_CONFLICT_ACTION_GECKO && downloadInfo.conflictAction) {
  57. delete downloadInfo.conflictAction;
  58. return download(downloadInfo, replacementCharacter);
  59. } else if (errorMessage.includes(ERROR_DOWNLOAD_CANCELED_GECKO)) {
  60. return {};
  61. } else {
  62. throw error;
  63. }
  64. } else {
  65. throw error;
  66. }
  67. }
  68. return new Promise((resolve, reject) => {
  69. browser.downloads.onChanged.addListener(onChanged);
  70. function onChanged(event) {
  71. if (event.id == downloadId && event.state) {
  72. if (event.state.current == STATE_DOWNLOAD_COMPLETE) {
  73. browser.downloads.search({ id: downloadId })
  74. .then(downloadItems => resolve({ filename: downloadItems[0] && downloadItems[0].filename }))
  75. .catch(() => resolve({}));
  76. browser.downloads.onChanged.removeListener(onChanged);
  77. }
  78. if (event.state.current == STATE_DOWNLOAD_INTERRUPTED) {
  79. if (event.error && event.error.current == STATE_ERROR_CANCELED_CHROMIUM) {
  80. resolve({});
  81. } else {
  82. reject(new Error(event.state.current));
  83. }
  84. browser.downloads.onChanged.removeListener(onChanged);
  85. }
  86. }
  87. }
  88. });
  89. }