fix(oci): Prefer raw request body for content-addressable OCI operations and expose rawBody on request context
This commit is contained in:
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-11-25 - 2.1.2 - fix(oci)
|
||||||
|
Prefer raw request body for content-addressable OCI operations and expose rawBody on request context
|
||||||
|
|
||||||
|
- Add rawBody?: Buffer to IRequestContext to allow callers to provide the exact raw request bytes for digest calculation (falls back to body if absent).
|
||||||
|
- OCI registry handlers now prefer context.rawBody over context.body for content-addressable operations (manifests, blobs, and blob uploads) to preserve exact bytes and ensure digest calculation matches client expectations.
|
||||||
|
- Upload flow updates: upload init, PATCH (upload chunk) and PUT (complete upload) now pass rawBody when available.
|
||||||
|
|
||||||
## 2025-11-25 - 2.1.1 - fix(oci)
|
## 2025-11-25 - 2.1.1 - fix(oci)
|
||||||
Preserve raw manifest bytes for digest calculation and handle string/JSON manifest bodies in OCI registry
|
Preserve raw manifest bytes for digest calculation and handle string/JSON manifest bodies in OCI registry
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartregistry',
|
name: '@push.rocks/smartregistry',
|
||||||
version: '2.1.1',
|
version: '2.1.2',
|
||||||
description: 'A composable TypeScript library implementing OCI, NPM, Maven, Cargo, Composer, PyPI, and RubyGems registries for building unified container and package registries'
|
description: 'A composable TypeScript library implementing OCI, NPM, Maven, Cargo, Composer, PyPI, and RubyGems registries for building unified container and package registries'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,6 +158,12 @@ export interface IRequestContext {
|
|||||||
headers: Record<string, string>;
|
headers: Record<string, string>;
|
||||||
query: Record<string, string>;
|
query: Record<string, string>;
|
||||||
body?: any;
|
body?: any;
|
||||||
|
/**
|
||||||
|
* Raw request body as bytes. MUST be provided for content-addressable operations
|
||||||
|
* (OCI manifests, blobs) to ensure digest calculation matches client expectations.
|
||||||
|
* If not provided, falls back to 'body' field.
|
||||||
|
*/
|
||||||
|
rawBody?: Buffer;
|
||||||
token?: string;
|
token?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,9 @@ export class OciRegistry extends BaseRegistry {
|
|||||||
const manifestMatch = path.match(/^\/v2\/([^\/]+(?:\/[^\/]+)*)\/manifests\/([^\/]+)$/);
|
const manifestMatch = path.match(/^\/v2\/([^\/]+(?:\/[^\/]+)*)\/manifests\/([^\/]+)$/);
|
||||||
if (manifestMatch) {
|
if (manifestMatch) {
|
||||||
const [, name, reference] = manifestMatch;
|
const [, name, reference] = manifestMatch;
|
||||||
return this.handleManifestRequest(context.method, name, reference, token, context.body, context.headers);
|
// Prefer rawBody for content-addressable operations to preserve exact bytes
|
||||||
|
const bodyData = context.rawBody || context.body;
|
||||||
|
return this.handleManifestRequest(context.method, name, reference, token, bodyData, context.headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blob operations: /v2/{name}/blobs/{digest}
|
// Blob operations: /v2/{name}/blobs/{digest}
|
||||||
@@ -76,7 +78,9 @@ export class OciRegistry extends BaseRegistry {
|
|||||||
const uploadInitMatch = path.match(/^\/v2\/([^\/]+(?:\/[^\/]+)*)\/blobs\/uploads\/?$/);
|
const uploadInitMatch = path.match(/^\/v2\/([^\/]+(?:\/[^\/]+)*)\/blobs\/uploads\/?$/);
|
||||||
if (uploadInitMatch && context.method === 'POST') {
|
if (uploadInitMatch && context.method === 'POST') {
|
||||||
const [, name] = uploadInitMatch;
|
const [, name] = uploadInitMatch;
|
||||||
return this.handleUploadInit(name, token, context.query, context.body);
|
// Prefer rawBody for content-addressable operations to preserve exact bytes
|
||||||
|
const bodyData = context.rawBody || context.body;
|
||||||
|
return this.handleUploadInit(name, token, context.query, bodyData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blob upload operations: /v2/{name}/blobs/uploads/{uuid}
|
// Blob upload operations: /v2/{name}/blobs/uploads/{uuid}
|
||||||
@@ -261,11 +265,14 @@ export class OciRegistry extends BaseRegistry {
|
|||||||
return this.createUnauthorizedResponse(session.repository, 'push');
|
return this.createUnauthorizedResponse(session.repository, 'push');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prefer rawBody for content-addressable operations to preserve exact bytes
|
||||||
|
const bodyData = context.rawBody || context.body;
|
||||||
|
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'PATCH':
|
case 'PATCH':
|
||||||
return this.uploadChunk(uploadId, context.body, context.headers['content-range']);
|
return this.uploadChunk(uploadId, bodyData, context.headers['content-range']);
|
||||||
case 'PUT':
|
case 'PUT':
|
||||||
return this.completeUpload(uploadId, context.query['digest'], context.body);
|
return this.completeUpload(uploadId, context.query['digest'], bodyData);
|
||||||
case 'GET':
|
case 'GET':
|
||||||
return this.getUploadStatus(uploadId);
|
return this.getUploadStatus(uploadId);
|
||||||
default:
|
default:
|
||||||
|
|||||||
Reference in New Issue
Block a user