diff --git a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java index 76e4c758..53f27595 100644 --- a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java +++ b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java @@ -123,11 +123,9 @@ public void eventHandlers(CdsRuntimeConfigurer configurer) { // build malware scanner client, could be null if no service binding is available MalwareScanClient scanClient = buildMalwareScanClient(runtime.getEnvironment()); - // determine default max size based on malware scanner binding availability - String defaultMaxSize = - scanClient != null - ? ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER - : ModifyApplicationHandlerHelper.UNLIMITED_SIZE; + // determine default max size; independent of the scanner binding it stays finite so a missing + // scanner does not allow unbounded uploads. An explicit configuration can raise the limit. + String defaultMaxSize = resolveDefaultMaxSize(runtime.getEnvironment()); AttachmentMalwareScanner malwareScanner = new DefaultAttachmentMalwareScanner(persistenceService, attachmentService, scanClient); @@ -192,6 +190,26 @@ public void eventHandlers(CdsRuntimeConfigurer configurer) { } } + /** + * Resolves the default maximum upload size for attachment content. + * + *

An explicit {@code cds.attachments.maxUploadSize} configuration takes precedence. Otherwise + * a finite default is used regardless of the malware scanner binding, so that a missing scanner + * does not silently allow unbounded uploads. Consuming apps can still override the limit per + * entity via {@code @Validation.Maximum}. + * + * @param environment the {@link CdsEnvironment} to read configuration from + * @return the default maximum upload size as a size string (e.g. {@code "400MB"}) + */ + static String resolveDefaultMaxSize(CdsEnvironment environment) { + String configured = + environment.getProperty("cds.attachments.maxUploadSize", String.class, null); + if (configured != null && !configured.isBlank()) { + return configured; + } + return ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE; + } + /** * Builds the {@link MalwareScanClient malware scanner client} based on the service binding. * diff --git a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelper.java b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelper.java index a73569bf..f23ba28c 100644 --- a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelper.java +++ b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelper.java @@ -26,11 +26,14 @@ public final class ModifyApplicationHandlerHelper { - /** Default max size when malware scanner binding is present (400 MB). */ - public static final String DEFAULT_SIZE_WITH_SCANNER = "400MB"; - - /** Effectively unlimited max size when no malware scanner binding is present. */ - public static final String UNLIMITED_SIZE = String.valueOf(Long.MAX_VALUE); + /** + * Default maximum upload size for attachment content (400 MB). A finite default is used + * regardless of the malware scanner binding so that a missing scanner does not silently allow + * unbounded uploads. It can be raised via the {@code cds.attachments.maxUploadSize} property or a + * per-entity {@code @Validation.Maximum} annotation, and matches the SAP Malware Scanning Service + * limit when a scanner is present. + */ + public static final String DEFAULT_MAX_UPLOAD_SIZE = "400MB"; /** * Handles attachments for entities. diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/configuration/RegistrationTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/configuration/RegistrationTest.java index 3a4726a5..7edfecd9 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/configuration/RegistrationTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/configuration/RegistrationTest.java @@ -15,6 +15,7 @@ import com.sap.cds.feature.attachments.handler.applicationservice.DeleteAttachmentsHandler; import com.sap.cds.feature.attachments.handler.applicationservice.ReadAttachmentsHandler; import com.sap.cds.feature.attachments.handler.applicationservice.UpdateAttachmentsHandler; +import com.sap.cds.feature.attachments.handler.applicationservice.helper.ModifyApplicationHandlerHelper; import com.sap.cds.feature.attachments.handler.draftservice.DraftActiveAttachmentsHandler; import com.sap.cds.feature.attachments.handler.draftservice.DraftCancelAttachmentsHandler; import com.sap.cds.feature.attachments.handler.draftservice.DraftPatchAttachmentsHandler; @@ -253,6 +254,35 @@ void environmentHandlesNullExistingPaths() { "../target/cds/com.sap.cds/cds-feature-attachments/**"); } + @Test + void resolveDefaultMaxSize_returnsFiniteDefaultWhenNoConfig() { + CdsEnvironment environment = mock(CdsEnvironment.class); + when(environment.getProperty("cds.attachments.maxUploadSize", String.class, null)) + .thenReturn(null); + + assertThat(Registration.resolveDefaultMaxSize(environment)) + .isEqualTo(ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE); + } + + @Test + void resolveDefaultMaxSize_ignoresBlankConfig() { + CdsEnvironment environment = mock(CdsEnvironment.class); + when(environment.getProperty("cds.attachments.maxUploadSize", String.class, null)) + .thenReturn(" "); + + assertThat(Registration.resolveDefaultMaxSize(environment)) + .isEqualTo(ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE); + } + + @Test + void resolveDefaultMaxSize_honorsConfiguredOverride() { + CdsEnvironment environment = mock(CdsEnvironment.class); + when(environment.getProperty("cds.attachments.maxUploadSize", String.class, null)) + .thenReturn("1GB"); + + assertThat(Registration.resolveDefaultMaxSize(environment)).isEqualTo("1GB"); + } + private void isHandlerForClassIncluded( List handlers, Class includedClass) { var isHandlerIncluded = diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/CreateAttachmentsHandlerTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/CreateAttachmentsHandlerTest.java index 746b07ab..fe3402a3 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/CreateAttachmentsHandlerTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/CreateAttachmentsHandlerTest.java @@ -77,7 +77,7 @@ void setup() { new CreateAttachmentsHandler( eventFactory, storageReader, - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER, + ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE, runtime); createContext = mock(CdsCreateEventContext.class); diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandlerTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandlerTest.java index d1f32eb4..79a50eb2 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandlerTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandlerTest.java @@ -85,7 +85,7 @@ void setup() { attachmentsReader, attachmentService, storageReader, - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER); + ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE); event = mock(ModifyAttachmentEvent.class); updateContext = mock(CdsUpdateEventContext.class); diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelperTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelperTest.java index 92267c80..2c54d5db 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelperTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelperTest.java @@ -93,7 +93,7 @@ void serviceExceptionDueToContentLength() { eventContext, path, attachment.getContent(), - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER, + ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE, new FieldAccessor.Composition())); assertThat(exception.getErrorStatus()).isEqualTo(ExtendedErrorStatuses.CONTENT_TOO_LARGE); @@ -149,7 +149,7 @@ void serviceExceptionDueToLimitExceeded() { eventContext, path, content, - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER, + ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE, new FieldAccessor.Composition())); assertThat(exception.getErrorStatus()).isEqualTo(ExtendedErrorStatuses.CONTENT_TOO_LARGE); @@ -182,7 +182,7 @@ void defaultValMaxValueUsed() { eventContext, path, content, - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER, + ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE, new FieldAccessor.Composition())); } @@ -215,7 +215,7 @@ void malformedContentLengthHeader() { eventContext, path, content, - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER, + ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE, new FieldAccessor.Composition())); assertThat(exception.getErrorStatus()).isEqualTo(ErrorStatuses.BAD_REQUEST); @@ -249,7 +249,7 @@ void inlineAttachmentSizeLimitFromAnnotation() { eventContext, path, (InputStream) data.get("avatar_content"), - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER, + ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE, new FieldAccessor.Inline("avatar"))); assertThat(exception.getErrorStatus()).isEqualTo(ExtendedErrorStatuses.CONTENT_TOO_LARGE); @@ -282,7 +282,7 @@ void inlineAttachmentWithinSizeLimitSucceeds() { eventContext, path, content, - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER, + ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE, new FieldAccessor.Inline("avatar"))); } @@ -329,7 +329,7 @@ void streamingLimitExceededOnInlineAttachment() { eventContext, path, content, - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER, + ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE, new FieldAccessor.Inline("avatar"))); assertThat(exception.getErrorStatus()).isEqualTo(ExtendedErrorStatuses.CONTENT_TOO_LARGE); diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandlerTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandlerTest.java index fade3f78..bc403247 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandlerTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandlerTest.java @@ -70,7 +70,7 @@ void setup() { eventFactory = mock(ModifyAttachmentEventFactory.class); cut = new DraftPatchAttachmentsHandler( - persistence, eventFactory, ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER); + persistence, eventFactory, ModifyApplicationHandlerHelper.DEFAULT_MAX_UPLOAD_SIZE); eventContext = mock(DraftPatchEventContext.class); event = mock(ModifyAttachmentEvent.class); when(eventFactory.getEvent(any(), any(), any())).thenReturn(event);