diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java index 19eadf2b..5d2ea90b 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java @@ -1175,6 +1175,32 @@ class DocumentControllerTest { .andExpect(jsonPath("$[0].pdfUrl").value("/api/documents/" + id + "/file")); } + @Test + @WithMockUser(authorities = "WRITE_ALL") + void patchBulk_stripsCarriageReturnsAndNewlinesFromErrorMessages() throws Exception { + // Nora C4 — DocumentController.sanitizeForLog defends against + // CWE-117 (log injection) by replacing CR/LF in any free-form string + // it interpolates. Same helper now sanitises BulkEditError.message + // before it round-trips to the frontend. + when(userService.findByEmail(any())).thenReturn(AppUser.builder().id(UUID.randomUUID()).build()); + UUID badId = UUID.randomUUID(); + when(documentService.applyBulkEditToDocument(eq(badId), any(), any())) + .thenThrow(org.raddatz.familienarchiv.exception.DomainException.notFound( + org.raddatz.familienarchiv.exception.ErrorCode.DOCUMENT_NOT_FOUND, + "evil\r\nFAKE LOG ENTRY: admin logged in")); + + mockMvc.perform(patch("/api/documents/bulk") + .contentType(MediaType.APPLICATION_JSON) + .content(bulkBody(badId.toString()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors[0].message", + org.hamcrest.Matchers.not(org.hamcrest.Matchers.containsString("\n")))) + .andExpect(jsonPath("$.errors[0].message", + org.hamcrest.Matchers.not(org.hamcrest.Matchers.containsString("\r")))) + .andExpect(jsonPath("$.errors[0].message", + org.hamcrest.Matchers.containsString("evil_"))); + } + @Test @WithMockUser(authorities = "WRITE_ALL") void patchBulk_returnsPartialFailureShape_whenServiceThrowsForOneDocument() throws Exception { diff --git a/frontend/messages/de.json b/frontend/messages/de.json index cd9e60d8..0384e776 100644 --- a/frontend/messages/de.json +++ b/frontend/messages/de.json @@ -896,5 +896,7 @@ "bulk_edit_clear_selection": "Auswahl aufheben", "bulk_edit_clear_hint_keyboard": "Esc: Auswahl aufheben", "bulk_edit_loading": "Dokumente werden geladen…", - "bulk_edit_all_x_failed": "Filter konnte nicht abgerufen werden — bitte erneut versuchen." + "bulk_edit_all_x_failed": "Filter konnte nicht abgerufen werden — bitte erneut versuchen.", + "bulk_edit_topbar_title": "Massenbearbeitung", + "bulk_edit_count_pill": "{count} werden bearbeitet" } diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 9c505a67..5e75ef71 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -896,5 +896,7 @@ "bulk_edit_clear_selection": "Clear selection", "bulk_edit_clear_hint_keyboard": "Esc: clear selection", "bulk_edit_loading": "Loading documents…", - "bulk_edit_all_x_failed": "Could not load filter results — please retry." + "bulk_edit_all_x_failed": "Could not load filter results — please retry.", + "bulk_edit_topbar_title": "Bulk edit", + "bulk_edit_count_pill": "{count} will be edited" } diff --git a/frontend/messages/es.json b/frontend/messages/es.json index fdd5dd22..12c2c5c9 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -896,5 +896,7 @@ "bulk_edit_clear_selection": "Limpiar selección", "bulk_edit_clear_hint_keyboard": "Esc: limpiar selección", "bulk_edit_loading": "Cargando documentos…", - "bulk_edit_all_x_failed": "No se pudieron cargar los resultados del filtro; vuelve a intentarlo." + "bulk_edit_all_x_failed": "No se pudieron cargar los resultados del filtro; vuelve a intentarlo.", + "bulk_edit_topbar_title": "Edición masiva", + "bulk_edit_count_pill": "Se editarán {count}" } diff --git a/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte b/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte index 6dd18490..3e062058 100644 --- a/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte +++ b/frontend/src/lib/components/document/BulkDocumentEditLayout.svelte @@ -324,12 +324,20 @@ async function retrySave() { - {isMulti ? m.bulk_title_multi() : m.bulk_title_single()} + {#if mode === 'edit'} + {m.bulk_edit_topbar_title()} + {:else} + {isMulti ? m.bulk_title_multi() : m.bulk_title_single()} + {/if} {#if isMulti} - {m.bulk_count_pill({ count: files.size })} + {#if mode === 'edit'} + {m.bulk_edit_count_pill({ count: files.size })} + {:else} + {m.bulk_count_pill({ count: files.size })} + {/if}