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

Limiting file size #105

Merged
merged 12 commits into from
Dec 16, 2024
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).

## Version 1.1.8

### Added

- Added limit for file size .

### Changed

- Included test cases for malware scanning within development profile.
Expand Down
61 changes: 42 additions & 19 deletions lib/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const attachmentIDRegex = /\/\w+\(.*ID=([0-9a-fA-F-]{36})/;

cds.on("loaded", function unfoldModel(csn) {
if (!("Attachments" in csn.definitions)) return;
const csnCopy = structuredClone(csn)
const csnCopy = structuredClone(csn);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove all the formatting changes in this file, as these lines are out of scope for this specific task.

cds.linked(csnCopy).forall("Composition", (comp) => {
if (comp._target && comp._target["@_is_media_data"] && comp.parent && comp.is2many) {
const parentDefinition = comp.parent.name
Expand All @@ -31,24 +31,28 @@ cds.once("served", async function registerPluginHandlers() {
for (let srv of cds.services) {
if (srv instanceof cds.ApplicationService) {
Object.values(srv.entities).forEach((entity) => {

for (let elementName in entity.elements) {
if (elementName === "SiblingEntity") continue; // REVISIT: Why do we have this?
const element = entity.elements[elementName], target = element._target;
const element = entity.elements[elementName],
target = element._target;
if (target?.["@_is_media_data"] && target?.drafts) {
DEBUG?.("serving attachments for:", target.name);

srv.before("READ", [target, target.drafts], validateAttachment);

srv.after("READ", [target, target.drafts], readAttachment);

srv.before("PUT", target.drafts, (req) => validateAttachmentSize(req) );


AttachmentsSrv.registerUpdateHandlers(srv, entity, target);
srv.before('NEW', target.drafts, req => {

srv.before("NEW", target.drafts, (req) => {
req.data.url = cds.utils.uuid();
req.data.ID = cds.utils.uuid();
let ext = extname(req.data.filename).toLowerCase().slice(1);
req.data.mimeType = Ext2MimeTyes[ext] || "application/octet-stream";
req.data.mimeType =
Ext2MimeTyes[ext] || "application/octet-stream";
});
}
}
Expand All @@ -57,34 +61,53 @@ cds.once("served", async function registerPluginHandlers() {
}

async function validateAttachment(req) {

/* removing case condition for mediaType annotation as in our case binary value and metadata is stored in different database */

req?.query?.SELECT?.columns?.forEach((element) => {
if(element.as === '[email protected]' && element.xpr){
if (element.as === "[email protected]" && element.xpr) {
delete element.xpr;
element.ref = ['mimeType'];
element.ref = ["mimeType"];
}
});

if(req?.req?.url?.endsWith("/content")) {
if (req?.req?.url?.endsWith("/content")) {
const attachmentID = req.req.url.match(attachmentIDRegex)[1];
const status = await AttachmentsSrv.getStatus(req.target, { ID : attachmentID });
const scanEnabled = cds.env.requires?.attachments?.scan ?? true
if(scanEnabled && status !== 'Clean') {
req.reject(403, 'Unable to download the attachment as scan status is not clean.');
const status = await AttachmentsSrv.getStatus(req.target, {ID: attachmentID,});
const scanEnabled = cds.env.requires?.attachments?.scan ?? true;
if (scanEnabled && status !== "Clean") {
req.reject(
403,
"Unable to download the attachment as scan status is not clean."
);
}
}
}

async function readAttachment([attachment], req) {
if (!req?.req?.url?.endsWith("/content") || !attachment || attachment?.content) return;
let keys = { ID : req.req.url.match(attachmentIDRegex)[1]};
if (!req?.req?.url?.endsWith("/content") ||!attachment ||attachment?.content) return;
let keys = { ID: req.req.url.match(attachmentIDRegex)[1] };
let { target } = req;
attachment.content = await AttachmentsSrv.get(target, keys, req); //Dependency -> sending req object for usage in SDM plugin
}
});

function validateAttachmentSize(req) {
const contentLengthHeader = req.headers["content-length"];
let fileSizeInBytes;

if (contentLengthHeader) {
fileSizeInBytes = Number(contentLengthHeader);
const MAX_FILE_SIZE = 419430400; //400 MB in bytes
if (fileSizeInBytes > MAX_FILE_SIZE) {
return req.reject(403, "File Size limit exceeded beyond 400 MB.");
}
} else {
return req.reject(403, "Invalid Content Size");
}
}

module.exports = { validateAttachmentSize };

const Ext2MimeTyes = {
aac: "audio/aac",
abw: "application/x-abiword",
Expand Down Expand Up @@ -144,5 +167,5 @@ const Ext2MimeTyes = {
xml: "application/xml",
zip: "application/zip",
txt: "application/txt",
lst: "application/txt"
lst: "application/txt",
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
],
"scripts": {
"lint": "npx eslint .",
"test": "npx jest attachments.test.js"
"test": "npx jest"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.400.0",
Expand Down
35 changes: 35 additions & 0 deletions tests/unit/validateAttachmentSize.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const { validateAttachmentSize } = require('../../lib/plugin');

describe('validateAttachmentSize', () => {
let req; // Define a mock request object

beforeEach(() => {
req = {
headers: {},
reject: jest.fn(), // Mocking the reject function
};
});

it('should pass validation for a file size under 400 MB', () => {
req.headers['content-length'] = '51200765';

validateAttachmentSize(req);

expect(req.reject).not.toHaveBeenCalled();
});

it('should reject for a file size over 400 MB', () => {
req.headers['content-length'] = '20480000000';

validateAttachmentSize(req);

expect(req.reject).toHaveBeenCalledWith(403, 'File Size limit exceeded beyond 400 MB.');
});

it('should reject when content-length header is missing', () => {
validateAttachmentSize(req);

expect(req.reject).toHaveBeenCalledWith(403, 'Invalid Content Size');
});
});

Loading