Compare commits

...

3 Commits
v1.3.2 ... main

Author SHA1 Message Date
Balázs Szücs
a438a15105
Update hungarian translation for editTableOfContents strings (#4165) 2025-09-06 20:27:19 +01:00
Balázs Szücs
8192b1a44f
performance: Use StringBuilder instead of string concatenation for building strings (#4193) 2025-09-06 20:27:11 +01:00
Balázs Szücs
47bce86ae2
fix: try-with-resources for Streams interacting with Files to ensure proper resource management (#4404)
# Description of Changes

The Javadoc recommends wrapping Files.list(), Files.walk(),
Files.find(), and Files.lines() in try-with-resources so the stream’s
close() is called as soon as the terminal operation completes.

This is because when Stream interact with files, Java can ONLY close the
Stream during garbage-collection finalization, which is not guaranteed
to run promptly or at all before the JVM exits, creating a memory leak.

Direct quote:

> Streams have a
[BaseStream.close()](https://docs.oracle.com/javase/8/docs/api/java/util/stream/BaseStream.html#close--)
method and implement
[AutoCloseable](https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html),
but nearly all stream instances do not actually need to be closed after
use. Generally, only streams whose source is an IO channel (such as
those returned by [Files.lines(Path,
Charset)](https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#lines-java.nio.file.Path-java.nio.charset.Charset-))
will require closing. Most streams are backed by collections, arrays, or
generating functions, which require no special resource management. (If
a stream does require closing, it can be declared as a resource in a
try-with-resources statement.)

> A DirectoryStream is opened upon creation and is closed by invoking
the close method. Closing a directory stream releases any resources
associated with the stream. Failure to close the stream may result in a
resource leak. The try-with-resources statement provides a useful
construct to ensure that the stream is closed:

Sources:
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html

https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/file/DirectoryStream.html

https://stackoverflow.com/questions/79078272/using-try-with-resources-for-a-java-files-walk-stream-created-in-a-separate-meth

https://stackoverflow.com/questions/36990053/resource-leak-in-files-listpath-dir-when-stream-is-not-explicitly-closed

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Signed-off-by: Balázs Szücs <bszucs1209@gmail.com>
2025-09-06 09:00:17 +01:00
9 changed files with 76 additions and 67 deletions

View File

@ -124,20 +124,21 @@ public class FileToPdf {
private static void zipDirectory(Path sourceDir, Path zipFilePath) throws IOException { private static void zipDirectory(Path sourceDir, Path zipFilePath) throws IOException {
try (ZipOutputStream zos = try (ZipOutputStream zos =
new ZipOutputStream(new FileOutputStream(zipFilePath.toFile()))) { new ZipOutputStream(new FileOutputStream(zipFilePath.toFile()))) {
Files.walk(sourceDir) try (Stream<Path> walk = Files.walk(sourceDir)) {
.filter(path -> !Files.isDirectory(path)) walk.filter(path -> !Files.isDirectory(path))
.forEach( .forEach(
path -> { path -> {
ZipEntry zipEntry = ZipEntry zipEntry =
new ZipEntry(sourceDir.relativize(path).toString()); new ZipEntry(sourceDir.relativize(path).toString());
try { try {
zos.putNextEntry(zipEntry); zos.putNextEntry(zipEntry);
Files.copy(path, zos); Files.copy(path, zos);
zos.closeEntry(); zos.closeEntry();
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} }
}); });
}
} }
} }

View File

@ -552,10 +552,10 @@ public class PdfUtils {
public boolean containsTextInFile(PDDocument pdfDocument, String text, String pagesToCheck) public boolean containsTextInFile(PDDocument pdfDocument, String text, String pagesToCheck)
throws IOException { throws IOException {
PDFTextStripper textStripper = new PDFTextStripper(); PDFTextStripper textStripper = new PDFTextStripper();
String pdfText = ""; StringBuilder pdfText = new StringBuilder();
if (pagesToCheck == null || "all".equals(pagesToCheck)) { if (pagesToCheck == null || "all".equals(pagesToCheck)) {
pdfText = textStripper.getText(pdfDocument); pdfText = new StringBuilder(textStripper.getText(pdfDocument));
} else { } else {
// remove whitespaces // remove whitespaces
pagesToCheck = pagesToCheck.replaceAll("\\s+", ""); pagesToCheck = pagesToCheck.replaceAll("\\s+", "");
@ -571,21 +571,21 @@ public class PdfUtils {
for (int i = startPage; i <= endPage; i++) { for (int i = startPage; i <= endPage; i++) {
textStripper.setStartPage(i); textStripper.setStartPage(i);
textStripper.setEndPage(i); textStripper.setEndPage(i);
pdfText += textStripper.getText(pdfDocument); pdfText.append(textStripper.getText(pdfDocument));
} }
} else { } else {
// Handle individual page // Handle individual page
int page = Integer.parseInt(splitPoint); int page = Integer.parseInt(splitPoint);
textStripper.setStartPage(page); textStripper.setStartPage(page);
textStripper.setEndPage(page); textStripper.setEndPage(page);
pdfText += textStripper.getText(pdfDocument); pdfText.append(textStripper.getText(pdfDocument));
} }
} }
} }
pdfDocument.close(); pdfDocument.close();
return pdfText.contains(text); return pdfText.toString().contains(text);
} }
public boolean pageCount(PDDocument pdfDocument, int pageCount, String comparator) public boolean pageCount(PDDocument pdfDocument, int pageCount, String comparator)

View File

@ -9,6 +9,7 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Stream;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
@ -150,10 +151,11 @@ public class ConvertImgPDFController {
.runCommandWithOutputHandling(command); .runCommandWithOutputHandling(command);
// Find all WebP files in the output directory // Find all WebP files in the output directory
List<Path> webpFiles = List<Path> webpFiles;
Files.walk(tempOutputDir) try (Stream<Path> walkStream = Files.walk(tempOutputDir)) {
.filter(path -> path.toString().endsWith(".webp")) webpFiles =
.toList(); walkStream.filter(path -> path.toString().endsWith(".webp")).toList();
}
if (webpFiles.isEmpty()) { if (webpFiles.isEmpty()) {
log.error("No WebP files were created in: {}", tempOutputDir.toString()); log.error("No WebP files were created in: {}", tempOutputDir.toString());

View File

@ -48,10 +48,12 @@ public class FilterController {
String text = request.getText(); String text = request.getText();
String pageNumber = request.getPageNumbers(); String pageNumber = request.getPageNumbers();
PDDocument pdfDocument = pdfDocumentFactory.load(inputFile); try (PDDocument pdfDocument = pdfDocumentFactory.load(inputFile)) {
if (PdfUtils.hasText(pdfDocument, pageNumber, text)) if (PdfUtils.hasText(pdfDocument, pageNumber, text)) {
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
pdfDocument, Filenames.toSimpleFileName(inputFile.getOriginalFilename())); pdfDocument, Filenames.toSimpleFileName(inputFile.getOriginalFilename()));
}
}
return null; return null;
} }

View File

@ -94,14 +94,14 @@ public class AutoRenameController {
// Merge lines with same font size // Merge lines with same font size
List<LineInfo> mergedLineInfos = new ArrayList<>(); List<LineInfo> mergedLineInfos = new ArrayList<>();
for (int i = 0; i < lineInfos.size(); i++) { for (int i = 0; i < lineInfos.size(); i++) {
String mergedText = lineInfos.get(i).text; StringBuilder mergedText = new StringBuilder(lineInfos.get(i).text);
float fontSize = lineInfos.get(i).fontSize; float fontSize = lineInfos.get(i).fontSize;
while (i + 1 < lineInfos.size() while (i + 1 < lineInfos.size()
&& lineInfos.get(i + 1).fontSize == fontSize) { && lineInfos.get(i + 1).fontSize == fontSize) {
mergedText += " " + lineInfos.get(i + 1).text; mergedText.append(" ").append(lineInfos.get(i + 1).text);
i++; i++;
} }
mergedLineInfos.add(new LineInfo(mergedText, fontSize)); mergedLineInfos.add(new LineInfo(mergedText.toString(), fontSize));
} }
// Sort lines by font size in descending order and get the first one // Sort lines by font size in descending order and get the first one

View File

@ -8,6 +8,7 @@ import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Stream;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
@ -142,7 +143,10 @@ public class ExtractImageScansController {
.runCommandWithOutputHandling(command); .runCommandWithOutputHandling(command);
// Read the output photos in temp directory // Read the output photos in temp directory
List<Path> tempOutputFiles = Files.list(tempDir).sorted().toList(); List<Path> tempOutputFiles;
try (Stream<Path> listStream = Files.list(tempDir)) {
tempOutputFiles = listStream.sorted().toList();
}
for (Path tempOutputFile : tempOutputFiles) { for (Path tempOutputFile : tempOutputFiles) {
byte[] imageBytes = Files.readAllBytes(tempOutputFile); byte[] imageBytes = Files.readAllBytes(tempOutputFile);
processedImageBytes.add(imageBytes); processedImageBytes.add(imageBytes);

View File

@ -1,8 +1,9 @@
package stirling.software.SPDF.controller.api.misc; package stirling.software.SPDF.controller.api.misc;
import java.nio.charset.StandardCharsets; import io.github.pixee.security.Filenames;
import java.util.Map; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode; import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript; import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
@ -13,17 +14,13 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.common.model.api.PDFFile; import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils; import stirling.software.common.util.WebResponseUtils;
import java.nio.charset.StandardCharsets;
import java.util.Map;
@RestController @RestController
@RequestMapping("/api/v1/misc") @RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs") @Tag(name = "Misc", description = "Miscellaneous APIs")
@ -38,7 +35,8 @@ public class ShowJavascript {
description = "desc. Input:PDF Output:JS Type:SISO") description = "desc. Input:PDF Output:JS Type:SISO")
public ResponseEntity<byte[]> extractHeader(@ModelAttribute PDFFile file) throws Exception { public ResponseEntity<byte[]> extractHeader(@ModelAttribute PDFFile file) throws Exception {
MultipartFile inputFile = file.getFileInput(); MultipartFile inputFile = file.getFileInput();
String script = ""; StringBuilder script = new StringBuilder();
boolean foundScript = false;
try (PDDocument document = pdfDocumentFactory.load(inputFile)) { try (PDDocument document = pdfDocumentFactory.load(inputFile)) {
@ -55,28 +53,28 @@ public class ShowJavascript {
PDActionJavaScript jsAction = entry.getValue(); PDActionJavaScript jsAction = entry.getValue();
String jsCodeStr = jsAction.getAction(); String jsCodeStr = jsAction.getAction();
script += if (jsCodeStr != null && !jsCodeStr.trim().isEmpty()) {
"// File: " script.append("// File: ")
+ Filenames.toSimpleFileName( .append(Filenames.toSimpleFileName(inputFile.getOriginalFilename()))
inputFile.getOriginalFilename()) .append(", Script: ")
+ ", Script: " .append(name)
+ name .append("\n")
+ "\n" .append(jsCodeStr)
+ jsCodeStr .append("\n");
+ "\n"; foundScript = true;
}
} }
} }
} }
if (script.isEmpty()) { if (!foundScript) {
script = script = new StringBuilder("PDF '")
"PDF '" .append(Filenames.toSimpleFileName(inputFile.getOriginalFilename()))
+ Filenames.toSimpleFileName(inputFile.getOriginalFilename()) .append("' does not contain Javascript");
+ "' does not contain Javascript";
} }
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
script.getBytes(StandardCharsets.UTF_8), script.toString().getBytes(StandardCharsets.UTF_8),
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + ".js", Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + ".js",
MediaType.TEXT_PLAIN); MediaType.TEXT_PLAIN);
} }

View File

@ -7,6 +7,7 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Stream;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thymeleaf.util.StringUtils; import org.thymeleaf.util.StringUtils;
@ -66,10 +67,11 @@ public class SignatureService {
private List<SignatureFile> getSignaturesFromFolder(Path folder, String category) private List<SignatureFile> getSignaturesFromFolder(Path folder, String category)
throws IOException { throws IOException {
return Files.list(folder) try (Stream<Path> stream = Files.list(folder)) {
.filter(path -> isImageFile(path)) return stream.filter(this::isImageFile)
.map(path -> new SignatureFile(path.getFileName().toString(), category)) .map(path -> new SignatureFile(path.getFileName().toString(), category))
.toList(); .toList();
}
} }
public byte[] getSignatureBytes(String username, String fileName) throws IOException { public byte[] getSignatureBytes(String username, String fileName) throws IOException {

View File

@ -1896,12 +1896,12 @@ editTableOfContents.replaceExisting=Meglévő könyvjelzők cseréje (törölje
editTableOfContents.editorTitle=Könyvjelző szerkesztő editTableOfContents.editorTitle=Könyvjelző szerkesztő
editTableOfContents.editorDesc=Könyvjelzők hozzáadása és rendezése lent. Kattintson a + gombra gyermek könyvjelzők hozzáadásához. editTableOfContents.editorDesc=Könyvjelzők hozzáadása és rendezése lent. Kattintson a + gombra gyermek könyvjelzők hozzáadásához.
editTableOfContents.addBookmark=Új könyvjelző hozzáadása editTableOfContents.addBookmark=Új könyvjelző hozzáadása
editTableOfContents.importBookmarksDefault=Import editTableOfContents.importBookmarksDefault=Importálás
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file editTableOfContents.importBookmarksFromJsonFile=JSON fájl feltöltése
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard editTableOfContents.importBookmarksFromClipboard=Beillesztés vágólapról
editTableOfContents.exportBookmarksDefault=Export editTableOfContents.exportBookmarksDefault=Exportálás
editTableOfContents.exportBookmarksAsJson=Download as JSON editTableOfContents.exportBookmarksAsJson=Letöltés JSON formátumban
editTableOfContents.exportBookmarksAsText=Copy as text editTableOfContents.exportBookmarksAsText=Másolás szövegként
editTableOfContents.desc.1=Ez az eszköz lehetővé teszi a tartalomjegyzék (könyvjelzők) hozzáadását vagy szerkesztését egy PDF dokumentumban. editTableOfContents.desc.1=Ez az eszköz lehetővé teszi a tartalomjegyzék (könyvjelzők) hozzáadását vagy szerkesztését egy PDF dokumentumban.
editTableOfContents.desc.2=Hierarchikus struktúrákat hozhat létre, ha gyermek könyvjelzőket ad a szülő könyvjelzőkhöz. editTableOfContents.desc.2=Hierarchikus struktúrákat hozhat létre, ha gyermek könyvjelzőket ad a szülő könyvjelzőkhöz.
editTableOfContents.desc.3=Minden könyvjelzőhöz szükséges egy cím és egy céloldalszám. editTableOfContents.desc.3=Minden könyvjelzőhöz szükséges egy cím és egy céloldalszám.