fix(core): Normalize binary data handling across registries and add buffer helpers

This commit is contained in:
2025-11-25 22:35:31 +00:00
parent e81fa41b18
commit a78934836e
10 changed files with 1496 additions and 9 deletions

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartregistry',
version: '2.2.0',
version: '2.2.1',
description: 'A composable TypeScript library implementing OCI, NPM, Maven, Cargo, Composer, PyPI, and RubyGems registries for building unified container and package registries'
}

View File

@@ -7,6 +7,7 @@ import { BaseRegistry } from '../core/classes.baseregistry.js';
import type { RegistryStorage } from '../core/classes.registrystorage.js';
import type { AuthManager } from '../core/classes.authmanager.js';
import type { IRequestContext, IResponse, IAuthToken } from '../core/interfaces.core.js';
import { isBinaryData, toBuffer } from '../core/helpers.buffer.js';
import type {
IComposerPackage,
IComposerPackageMetadata,
@@ -255,7 +256,7 @@ export class ComposerRegistry extends BaseRegistry {
};
}
if (!body || !Buffer.isBuffer(body)) {
if (!body || !isBinaryData(body)) {
return {
status: 400,
headers: {},
@@ -263,8 +264,11 @@ export class ComposerRegistry extends BaseRegistry {
};
}
// Convert to Buffer for ZIP processing
const zipData = toBuffer(body);
// Extract and validate composer.json from ZIP
const composerJson = await extractComposerJsonFromZip(body);
const composerJson = await extractComposerJsonFromZip(zipData);
if (!composerJson || !validateComposerJson(composerJson)) {
return {
status: 400,
@@ -292,13 +296,13 @@ export class ComposerRegistry extends BaseRegistry {
}
// Calculate SHA-1 hash
const shasum = await calculateSha1(body);
const shasum = await calculateSha1(zipData);
// Generate reference (use version or commit hash)
const reference = composerJson.source?.reference || version.replace(/[^a-zA-Z0-9.-]/g, '-');
// Store ZIP file
await this.storage.putComposerPackageZip(vendorPackage, reference, body);
await this.storage.putComposerPackageZip(vendorPackage, reference, zipData);
// Get or create metadata
let metadata = await this.storage.getComposerPackageMetadata(vendorPackage);

34
ts/core/helpers.buffer.ts Normal file
View File

@@ -0,0 +1,34 @@
/**
* Shared buffer utilities for consistent binary data handling across all registry types.
*
* This module addresses the common issue where `Buffer.isBuffer(Uint8Array)` returns `false`,
* which can cause data handling bugs when binary data arrives as Uint8Array instead of Buffer.
*/
/**
* Check if value is binary data (Buffer or Uint8Array)
*/
export function isBinaryData(value: unknown): value is Buffer | Uint8Array {
return Buffer.isBuffer(value) || value instanceof Uint8Array;
}
/**
* Convert any binary-like data to Buffer.
* Handles Buffer, Uint8Array, string, and objects.
*
* @param data - The data to convert to Buffer
* @returns A Buffer containing the data
*/
export function toBuffer(data: unknown): Buffer {
if (Buffer.isBuffer(data)) {
return data;
}
if (data instanceof Uint8Array) {
return Buffer.from(data);
}
if (typeof data === 'string') {
return Buffer.from(data, 'utf-8');
}
// Fallback: serialize object to JSON
return Buffer.from(JSON.stringify(data));
}

View File

@@ -7,6 +7,7 @@ import { BaseRegistry } from '../core/classes.baseregistry.js';
import type { RegistryStorage } from '../core/classes.registrystorage.js';
import type { AuthManager } from '../core/classes.authmanager.js';
import type { IRequestContext, IResponse, IAuthToken } from '../core/interfaces.core.js';
import { toBuffer } from '../core/helpers.buffer.js';
import type { IMavenCoordinate, IMavenMetadata, IChecksums } from './interfaces.maven.js';
import {
pathToGAV,
@@ -296,7 +297,7 @@ export class MavenRegistry extends BaseRegistry {
coordinate: IMavenCoordinate,
body: Buffer | any
): Promise<IResponse> {
const data = Buffer.isBuffer(body) ? body : Buffer.from(JSON.stringify(body));
const data = toBuffer(body);
// Validate POM if uploading .pom file
if (coordinate.extension === 'pom') {

View File

@@ -738,7 +738,7 @@ export class OciRegistry extends BaseRegistry {
}
private generateUploadId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
}
private async calculateDigest(data: Buffer): Promise<string> {

View File

@@ -3,6 +3,7 @@ import { BaseRegistry } from '../core/classes.baseregistry.js';
import { RegistryStorage } from '../core/classes.registrystorage.js';
import { AuthManager } from '../core/classes.authmanager.js';
import type { IRequestContext, IResponse, IAuthToken } from '../core/interfaces.core.js';
import { isBinaryData, toBuffer } from '../core/helpers.buffer.js';
import type {
IPypiPackageMetadata,
IPypiFile,
@@ -328,8 +329,9 @@ export class PypiRegistry extends BaseRegistry {
const version = formData.version;
// Support both: formData.content.filename (multipart parsed) and formData.filename (flat)
const filename = formData.content?.filename || formData.filename;
// Support both: formData.content.data (multipart parsed) and formData.content (Buffer directly)
const fileData = (formData.content?.data || (Buffer.isBuffer(formData.content) ? formData.content : null)) as Buffer;
// Support both: formData.content.data (multipart parsed) and formData.content (Buffer/Uint8Array directly)
const rawContent = formData.content?.data || (isBinaryData(formData.content) ? formData.content : null);
const fileData = rawContent ? toBuffer(rawContent) : null;
const filetype = formData.filetype; // 'bdist_wheel' or 'sdist'
const pyversion = formData.pyversion;