diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js index 5356821b29..4131d75256 100644 --- a/chrome/content/zotero/xpcom/utilities_internal.js +++ b/chrome/content/zotero/xpcom/utilities_internal.js @@ -139,14 +139,28 @@ Zotero.Utilities.Internal = { /** * @param {nsIFile|String} file File or file path - * @param {Boolean} [base64=FALSE] Return as base-64-encoded string - * rather than hex string */ - md5Async: async function (file, base64) { + md5Async: async function (file) { function toHexString(charCode) { return ("0" + charCode.toString(16)).slice(-2); } + var file = Zotero.File.pathToFile(file); + try { + let { size } = await IOUtils.stat(file.path); + if (size === 0) { + // MD5 for empty string + return "d41d8cd98f00b204e9800998ecf8427e"; + } + } + catch (e) { + // Return false for missing files + if (e.name == "NotFoundError") { + return false; + } + throw e; + } + var ch = Components.classes["@mozilla.org/security/hash;1"] .createInstance(Components.interfaces.nsICryptoHash); ch.init(ch.MD5); @@ -156,12 +170,8 @@ Zotero.Utilities.Internal = { .createInstance(Ci.nsIFileInputStream); is.init(Zotero.File.pathToFile(file), -1, -1, Ci.nsIFileInputStream.CLOSE_ON_EOF); ch.updateFromStream(is, -1); - let hash = ch.finish(base64); - // Base64 - if (base64) { - return hash; - } - // Hex string + // Get binary string and convert to hex string + let hash = ch.finish(false); let hexStr = ""; for (let i = 0; i < hash.length; i++) { hexStr += toHexString(hash.charCodeAt(i)); diff --git a/test/tests/utilities_internalTest.js b/test/tests/utilities_internalTest.js index f7f0c4d2cd..d2f209cb1e 100644 --- a/test/tests/utilities_internalTest.js +++ b/test/tests/utilities_internalTest.js @@ -44,6 +44,24 @@ describe("Zotero.Utilities.Internal", function () { yield OS.File.remove(file); }); + + it("should return false for a nonexistent file", async function () { + var tmpDir = Zotero.getTempDirectory().path; + var file = OS.Path.join(tmpDir, 'nonexistent-asawefaweoihafa'); + await assert.eventually.isFalse(ZUI.md5Async(file)); + }); + + it("should return hash for an empty file", async function () { + const emptyHash = 'd41d8cd98f00b204e9800998ecf8427e'; + + var tmpDir = Zotero.getTempDirectory().path; + var file = OS.Path.join(tmpDir, 'empty-file'); + await IOUtils.write(file, new Uint8Array()); + + await assert.eventually.equal(ZUI.md5Async(file), emptyHash); + + await IOUtils.remove(file); + }); })