Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: adds large file support to bufferToSha1 method #569

Merged
merged 1 commit into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions lib/analyzer/applications/java.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export async function jarFilesToScannedResults(
continue;
}

const fingerprints = getFingerprints(
const fingerprints = await getFingerprints(
desiredLevelsOfUnpacking,
mappedResult[path],
);
Expand All @@ -68,14 +68,15 @@ export async function jarFilesToScannedResults(
return scanResults;
}

function getFingerprints(
async function getFingerprints(
desiredLevelsOfUnpacking: number,
jarBuffers: JarBuffer[],
): JarFingerprint[] {
const fingerprints = new Set([
...unpackJars(jarBuffers, desiredLevelsOfUnpacking),
]);
return Array.from(fingerprints);
): Promise<JarFingerprint[]> {
const fingerprints: JarFingerprint[] = await unpackJars(
jarBuffers,
desiredLevelsOfUnpacking,
);
return Array.from(new Set(fingerprints));
}

/**
Expand Down Expand Up @@ -188,11 +189,11 @@ function unpackJar({
* @param {number} unpackedLevels
* @returns JarFingerprint[]
*/
function unpackJars(
async function unpackJars(
jarBuffers: JarBuffer[],
desiredLevelsOfUnpacking: number,
unpackedLevels: number = 0,
): JarFingerprint[] {
): Promise<JarFingerprint[]> {
// We have to unpack jars to get the pom.properties manifest which
// we use to support shaded jars and get the package coords (if exists)
// to reduce the dependency on maven search and support private jars.
Expand Down Expand Up @@ -228,7 +229,7 @@ function unpackJars(
// sha so maven-deps can fallback to searching maven central
fingerprints.push({
location: jarInfo.location,
digest: jarInfo.coords ? null : bufferToSha1(jarInfo.buffer),
digest: jarInfo.coords ? null : await bufferToSha1(jarInfo.buffer),
dependencies: jarInfo.dependencies,
...jarInfo.coords,
});
Expand All @@ -237,13 +238,12 @@ function unpackJars(
if (jarInfo.nestedJars.length > 0) {
// this is an uber/fat JAR so we need to unpack the nested JARs to
// analyze them for coords and further nested JARs (depth flag allowing)
fingerprints.push(
...unpackJars(
jarInfo.nestedJars,
desiredLevelsOfUnpacking,
unpackedLevels + 1,
),
const nestedJarFingerprints = await unpackJars(
jarInfo.nestedJars,
desiredLevelsOfUnpacking,
unpackedLevels + 1,
);
fingerprints.push(...nestedJarFingerprints);
}
}

Expand Down
21 changes: 16 additions & 5 deletions lib/buffer-utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import * as crypto from "crypto";
import { Readable } from "stream";
import { HashAlgorithm } from "./types";

const HASH_ENCODING = "hex";

export function bufferToSha1(buffer: Buffer): string {
export async function bufferToSha1(buffer: Buffer): Promise<string> {
const stream = Readable.from(buffer);
const hash = crypto.createHash(HashAlgorithm.Sha1);
hash.setEncoding(HASH_ENCODING);
hash.update(buffer);
hash.end();
return hash.read().toString(HASH_ENCODING);

return new Promise((resolve, reject) => {
stream
.pipe(hash)
.on("finish", () => {
hash.end();
const digest = hash.read().toString(HASH_ENCODING);
resolve(digest);
})
.on("error", (err) => {
reject(err);
});
});
}
21 changes: 19 additions & 2 deletions test/lib/buffer-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Buffer } from "buffer";
import * as crypto from "crypto";

import { bufferToSha1 } from "../../lib/buffer-utils";

describe("buffer-utils", () => {
describe("bufferToSha1", () => {
it("should convert Buffer to sha1", () => {
it("should convert Buffer to sha1", async () => {
// Arrange
const textToConvert = "hello world";
let hashedText = crypto.createHash("sha1");
Expand All @@ -16,10 +17,26 @@ describe("buffer-utils", () => {
const bufferedText = Buffer.from(textToConvert);

// Act
const result = bufferToSha1(bufferedText);
const result = await bufferToSha1(bufferedText);

// Assert
expect(result).toEqual(hashedText);
});

it("should handle large files", async () => {
const megabyte = 1024 * 1024;
const gigabyte = megabyte * 1024;

// create a buffer representing a file over 2GB, which would throw
// a RangeError if using the update method of a Crypto.Hash object,
// instead of the streaming interface which allows us to support
// large files
const buffer = Buffer.concat([
Buffer.alloc(gigabyte * 2),
Buffer.alloc(megabyte),
]);
const digest = await bufferToSha1(buffer);
expect(digest).toEqual(expect.any(String));
});
});
});