Bug 1773186 - Add IOUtils::ComputeHexDigest r=nika,keeler

Differential Revision: https://phabricator.services.mozilla.com/D148966
This commit is contained in:
Barret Rennie 2022-06-28 19:42:34 +00:00
parent 681cf4b1f5
commit 5703e6ec6b
5 changed files with 198 additions and 0 deletions

View File

@ -253,6 +253,17 @@ namespace IOUtils {
[NewObject]
Promise<DOMString> createUniqueDirectory(DOMString parent, DOMString prefix, optional unsigned long permissions = 0755);
/**
* Compute the hash of a file as a hex digest.
*
* @param path The absolute path of the file to hash.
* @param method The hashing method to use.
*
* @return A promise that resolves to the hex digest of the file's hash in lowercase.
*/
[NewObject]
Promise<UTF8String> computeHexDigest(DOMString path, HashAlgorithm method);
#if defined(XP_WIN)
/**
* Return the Windows-specific file attributes of the file at the given path.
@ -579,6 +590,11 @@ dictionary FileInfo {
unsigned long permissions;
};
/**
* The supported hash algorithms for |IOUtils.hashFile|.
*/
enum HashAlgorithm { "sha1", "sha256", "sha384", "sha512" };
#ifdef XP_WIN
/**
* Windows-specific file attributes.

View File

@ -41,8 +41,11 @@
#include "nsIDirectoryEnumerator.h"
#include "nsIFile.h"
#include "nsIGlobalObject.h"
#include "nsIInputStream.h"
#include "nsISupports.h"
#include "nsLocalFile.h"
#include "nsNetUtil.h"
#include "nsNSSComponent.h"
#include "nsPrintfCString.h"
#include "nsReadableUtils.h"
#include "nsString.h"
@ -54,6 +57,8 @@
#include "prio.h"
#include "prtime.h"
#include "prtypes.h"
#include "ScopedNSSTypes.h"
#include "secoidt.h"
#if defined(XP_UNIX) && !defined(ANDROID)
# include "nsSystemInfo.h"
@ -807,6 +812,32 @@ already_AddRefed<Promise> IOUtils::CreateUnique(GlobalObject& aGlobal,
});
}
/* static */
already_AddRefed<Promise> IOUtils::ComputeHexDigest(
GlobalObject& aGlobal, const nsAString& aPath,
const HashAlgorithm aAlgorithm, ErrorResult& aError) {
const bool nssInitialized = EnsureNSSInitializedChromeOrContent();
return WithPromiseAndState(
aGlobal, aError, [&](Promise* promise, auto& state) {
if (!nssInitialized) {
RejectJSPromise(promise,
IOError(NS_ERROR_UNEXPECTED)
.WithMessage("Could not initialize NSS"));
return;
}
nsCOMPtr<nsIFile> file = new nsLocalFile();
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
DispatchAndResolve<nsCString>(state->mEventQueue, promise,
[file = std::move(file), aAlgorithm]() {
return ComputeHexDigestSync(file,
aAlgorithm);
});
});
}
#if defined(XP_WIN)
/* static */
@ -1719,6 +1750,86 @@ Result<nsString, IOUtils::IOError> IOUtils::CreateUniqueSync(
return path;
}
/* static */
Result<nsCString, IOUtils::IOError> IOUtils::ComputeHexDigestSync(
nsIFile* aFile, const HashAlgorithm aAlgorithm) {
static constexpr size_t BUFFER_SIZE = 8192;
SECOidTag alg;
switch (aAlgorithm) {
case HashAlgorithm::Sha1:
alg = SEC_OID_SHA1;
break;
case HashAlgorithm::Sha256:
alg = SEC_OID_SHA256;
break;
case HashAlgorithm::Sha384:
alg = SEC_OID_SHA384;
break;
case HashAlgorithm::Sha512:
alg = SEC_OID_SHA512;
break;
default:
MOZ_RELEASE_ASSERT(false, "Unexpected HashAlgorithm");
}
Digest digest;
if (nsresult rv = digest.Begin(alg); NS_FAILED(rv)) {
return Err(IOError(rv).WithMessage("Could not hash file at %s",
aFile->HumanReadablePath().get()));
}
RefPtr<nsIInputStream> stream;
if (nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), aFile);
NS_FAILED(rv)) {
return Err(IOError(rv).WithMessage("Could not open the file at %s",
aFile->HumanReadablePath().get()));
}
char buffer[BUFFER_SIZE];
uint32_t read = 0;
for (;;) {
if (nsresult rv = stream->Read(buffer, BUFFER_SIZE, &read); NS_FAILED(rv)) {
return Err(IOError(rv).WithMessage(
"Encountered an unexpected error while reading file(%s)",
aFile->HumanReadablePath().get()));
}
if (read == 0) {
break;
}
if (nsresult rv =
digest.Update(reinterpret_cast<unsigned char*>(buffer), read);
NS_FAILED(rv)) {
return Err(IOError(rv).WithMessage("Could not hash file at %s",
aFile->HumanReadablePath().get()));
}
}
AutoTArray<uint8_t, SHA512_LENGTH> rawDigest;
if (nsresult rv = digest.End(rawDigest); NS_FAILED(rv)) {
return Err(IOError(rv).WithMessage("Could not hash file at %s",
aFile->HumanReadablePath().get()));
}
nsCString hexDigest;
if (!hexDigest.SetCapacity(2 * rawDigest.Length(), fallible)) {
return Err(IOError(NS_ERROR_OUT_OF_MEMORY));
}
const char HEX[] = "0123456789abcdef";
for (uint8_t b : rawDigest) {
hexDigest.Append(HEX[(b >> 4) & 0xF]);
hexDigest.Append(HEX[b & 0xF]);
}
return hexDigest;
}
#if defined(XP_WIN)
Result<uint32_t, IOUtils::IOError> IOUtils::GetWindowsAttributesSync(

View File

@ -157,6 +157,10 @@ class IOUtils final {
ErrorResult& aError);
public:
static already_AddRefed<Promise> ComputeHexDigest(
GlobalObject& aGlobal, const nsAString& aPath,
const HashAlgorithm aAlgorithm, ErrorResult& aError);
#if defined(XP_WIN)
static already_AddRefed<Promise> GetWindowsAttributes(GlobalObject& aGlobal,
const nsAString& aPath,
@ -451,6 +455,17 @@ class IOUtils final {
static Result<nsString, IOError> CreateUniqueSync(
nsIFile* aFile, const uint32_t aFileType, const uint32_t aPermissions);
/**
* Compute the hash of a file.
*
* @param aFile The file to hash.
* @param aAlgorithm The hashing algorithm to use.
*
* @return The hash of the file, as a hex digest.
*/
static Result<nsCString, IOError> ComputeHexDigestSync(
nsIFile* aFile, const HashAlgorithm aAlgorithm);
#if defined(XP_WIN)
/**
* Return the Windows-specific attributes of the file.

View File

@ -4,6 +4,7 @@ support-files =
file_ioutils_worker.js
[test_ioutils.html]
[test_ioutils_compute_hex_digest.html]
[test_ioutils_copy_move.html]
[test_ioutils_create_unique.html]
[test_ioutils_dir_iteration.html]

View File

@ -0,0 +1,55 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test the IOUtils file I/O API</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script src="file_ioutils_test_fixtures.js"></script>
<script>
"use strict";
const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
add_task(async function test_computeHexDigest() {
const tempDir = PathUtils.join(PathUtils.tempDir, "ioutils-test-compute-hex-digest.tmp.d");
await createDir(tempDir);
const path = PathUtils.join(tempDir, "file");
await IOUtils.writeUTF8(path, "hello world\n");
const DIGESTS = [
"22596363b3de40b06f981fb85d82312e8c0ed511",
"a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447",
"6b3b69ff0a404f28d75e98a066d3fc64fffd9940870cc68bece28545b9a75086b343d7a1366838083e4b8f3ca6fd3c80",
"db3974a97f2407b7cae1ae637c0030687a11913274d578492558e39c16c017de84eacdc8c62fe34ee4e12b4b1428817f09b6a2760c3f8a664ceae94d2434a593",
];
const ALGORITHMS = ["sha1", "sha256", "sha384", "sha512"];
for (let i = 0; i < ALGORITHMS.length; i++) {
const alg = ALGORITHMS[i];
const expected = DIGESTS[i];
Assert.equal(
await IOUtils.computeHexDigest(path, alg),
expected,
`IOUtils.hashFile() has expected value for ${alg}`);
}
await cleanup(tempDir);
});
</script>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
</body>
</html>