Merge branch 'main' into bug/csrf-decryption-API
This commit is contained in:
@@ -28,7 +28,7 @@ public class LicenseKeyChecker {
|
||||
this.checkLicense();
|
||||
}
|
||||
|
||||
@Scheduled(initialDelay = 604800000,fixedRate = 604800000) // 7 days in milliseconds
|
||||
@Scheduled(initialDelay = 604800000, fixedRate = 604800000) // 7 days in milliseconds
|
||||
public void checkLicensePeriodically() {
|
||||
checkLicense();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package stirling.software.SPDF;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.nio.file.Files;
|
||||
@@ -8,6 +9,9 @@ import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -19,13 +23,16 @@ import org.springframework.core.env.Environment;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
import io.github.pixee.security.SystemCommand;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.UI.WebBrowser;
|
||||
import stirling.software.SPDF.config.ConfigInitializer;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableScheduling
|
||||
@Slf4j
|
||||
public class SPdfApplication {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class);
|
||||
@@ -67,36 +74,19 @@ public class SPdfApplication {
|
||||
}
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
baseUrlStatic = this.baseUrl;
|
||||
// Check if the BROWSER_OPEN environment variable is set to true
|
||||
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
|
||||
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
||||
if (browserOpen) {
|
||||
try {
|
||||
String url = baseUrl + ":" + getStaticPort();
|
||||
|
||||
String os = System.getProperty("os.name").toLowerCase();
|
||||
Runtime rt = Runtime.getRuntime();
|
||||
if (os.contains("win")) {
|
||||
// For Windows
|
||||
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
|
||||
} else if (os.contains("mac")) {
|
||||
SystemCommand.runCommand(rt, "open " + url);
|
||||
} else if (os.contains("nix") || os.contains("nux")) {
|
||||
SystemCommand.runCommand(rt, "xdg-open " + url);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error opening browser: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
logger.info("Running configs {}", applicationProperties.toString());
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException, InterruptedException {
|
||||
|
||||
SpringApplication app = new SpringApplication(SPdfApplication.class);
|
||||
|
||||
Properties props = new Properties();
|
||||
|
||||
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
||||
System.setProperty("java.awt.headless", "false");
|
||||
app.setHeadless(false);
|
||||
props.put("java.awt.headless", "false");
|
||||
props.put("spring.main.web-application-type", "servlet");
|
||||
}
|
||||
|
||||
app.setAdditionalProfiles("default");
|
||||
app.addInitializers(new ConfigInitializer());
|
||||
Map<String, String> propertyFiles = new HashMap<>();
|
||||
@@ -120,14 +110,20 @@ public class SPdfApplication {
|
||||
} else {
|
||||
logger.warn("Custom configuration file 'configs/custom_settings.yml' does not exist.");
|
||||
}
|
||||
Properties finalProps = new Properties();
|
||||
|
||||
if (!propertyFiles.isEmpty()) {
|
||||
app.setDefaultProperties(
|
||||
finalProps.putAll(
|
||||
Collections.singletonMap(
|
||||
"spring.config.additional-location",
|
||||
propertyFiles.get("spring.config.additional-location")));
|
||||
}
|
||||
|
||||
if (!props.isEmpty()) {
|
||||
finalProps.putAll(props);
|
||||
}
|
||||
app.setDefaultProperties(finalProps);
|
||||
|
||||
app.run(args);
|
||||
|
||||
// Ensure directories are created
|
||||
@@ -147,6 +143,46 @@ public class SPdfApplication {
|
||||
logger.info("Navigate to {}", url);
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
private WebBrowser webBrowser;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
baseUrlStatic = this.baseUrl;
|
||||
String url = baseUrl + ":" + getStaticPort();
|
||||
if (webBrowser != null
|
||||
&& Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
||||
webBrowser.initWebUI(url);
|
||||
} else {
|
||||
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
|
||||
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
||||
if (browserOpen) {
|
||||
try {
|
||||
String os = System.getProperty("os.name").toLowerCase();
|
||||
Runtime rt = Runtime.getRuntime();
|
||||
if (os.contains("win")) {
|
||||
// For Windows
|
||||
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
|
||||
} else if (os.contains("mac")) {
|
||||
SystemCommand.runCommand(rt, "open " + url);
|
||||
} else if (os.contains("nix") || os.contains("nux")) {
|
||||
SystemCommand.runCommand(rt, "xdg-open " + url);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error opening browser: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info("Running configs {}", applicationProperties.toString());
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void cleanup() {
|
||||
if (webBrowser != null) {
|
||||
webBrowser.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getStaticBaseUrl() {
|
||||
return baseUrlStatic;
|
||||
}
|
||||
|
||||
7
src/main/java/stirling/software/SPDF/UI/WebBrowser.java
Normal file
7
src/main/java/stirling/software/SPDF/UI/WebBrowser.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package stirling.software.SPDF.UI;
|
||||
|
||||
public interface WebBrowser {
|
||||
void initWebUI(String url);
|
||||
|
||||
void cleanup();
|
||||
}
|
||||
354
src/main/java/stirling/software/SPDF/UI/impl/DesktopBrowser.java
Normal file
354
src/main/java/stirling/software/SPDF/UI/impl/DesktopBrowser.java
Normal file
@@ -0,0 +1,354 @@
|
||||
package stirling.software.SPDF.UI.impl;
|
||||
|
||||
import java.awt.AWTException;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Frame;
|
||||
import java.awt.Image;
|
||||
import java.awt.MenuItem;
|
||||
import java.awt.PopupMenu;
|
||||
import java.awt.SystemTray;
|
||||
import java.awt.TrayIcon;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.event.WindowStateListener;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.Timer;
|
||||
|
||||
import org.cef.CefApp;
|
||||
import org.cef.CefClient;
|
||||
import org.cef.CefSettings;
|
||||
import org.cef.browser.CefBrowser;
|
||||
import org.cef.callback.CefBeforeDownloadCallback;
|
||||
import org.cef.callback.CefDownloadItem;
|
||||
import org.cef.callback.CefDownloadItemCallback;
|
||||
import org.cef.handler.CefDownloadHandlerAdapter;
|
||||
import org.cef.handler.CefLoadHandlerAdapter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.friwi.jcefmaven.CefAppBuilder;
|
||||
import me.friwi.jcefmaven.EnumProgress;
|
||||
import me.friwi.jcefmaven.MavenCefAppHandlerAdapter;
|
||||
import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler;
|
||||
import stirling.software.SPDF.UI.WebBrowser;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
@ConditionalOnProperty(
|
||||
name = "STIRLING_PDF_DESKTOP_UI",
|
||||
havingValue = "true",
|
||||
matchIfMissing = false)
|
||||
public class DesktopBrowser implements WebBrowser {
|
||||
private static CefApp cefApp;
|
||||
private static CefClient client;
|
||||
private static CefBrowser browser;
|
||||
private static JFrame frame;
|
||||
private static LoadingWindow loadingWindow;
|
||||
private static volatile boolean browserInitialized = false;
|
||||
private static TrayIcon trayIcon;
|
||||
private static SystemTray systemTray;
|
||||
|
||||
public DesktopBrowser() {
|
||||
SwingUtilities.invokeLater(
|
||||
() -> {
|
||||
loadingWindow = new LoadingWindow(null, "Initializing...");
|
||||
loadingWindow.setVisible(true);
|
||||
});
|
||||
}
|
||||
|
||||
public void initWebUI(String url) {
|
||||
CompletableFuture.runAsync(
|
||||
() -> {
|
||||
try {
|
||||
CefAppBuilder builder = new CefAppBuilder();
|
||||
configureCefSettings(builder);
|
||||
builder.setProgressHandler(createProgressHandler());
|
||||
|
||||
// Build and initialize CEF
|
||||
cefApp = builder.build();
|
||||
client = cefApp.createClient();
|
||||
|
||||
// Set up download handler
|
||||
setupDownloadHandler();
|
||||
|
||||
// Create browser and frame on EDT
|
||||
SwingUtilities.invokeAndWait(
|
||||
() -> {
|
||||
browser = client.createBrowser(url, false, false);
|
||||
setupMainFrame();
|
||||
setupLoadHandler();
|
||||
|
||||
// Show the frame immediately but transparent
|
||||
frame.setVisible(true);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("Error initializing JCEF browser: ", e);
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void configureCefSettings(CefAppBuilder builder) {
|
||||
CefSettings settings = builder.getCefSettings();
|
||||
settings.cache_path = new File("jcef-bundle").getAbsolutePath();
|
||||
settings.root_cache_path = new File("jcef-bundle").getAbsolutePath();
|
||||
settings.persist_session_cookies = true;
|
||||
settings.windowless_rendering_enabled = false;
|
||||
settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_INFO;
|
||||
|
||||
builder.setAppHandler(
|
||||
new MavenCefAppHandlerAdapter() {
|
||||
@Override
|
||||
public void stateHasChanged(org.cef.CefApp.CefAppState state) {
|
||||
log.info("CEF state changed: " + state);
|
||||
if (state == CefApp.CefAppState.TERMINATED) {
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupDownloadHandler() {
|
||||
client.addDownloadHandler(
|
||||
new CefDownloadHandlerAdapter() {
|
||||
@Override
|
||||
public boolean onBeforeDownload(
|
||||
CefBrowser browser,
|
||||
CefDownloadItem downloadItem,
|
||||
String suggestedName,
|
||||
CefBeforeDownloadCallback callback) {
|
||||
callback.Continue("", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadUpdated(
|
||||
CefBrowser browser,
|
||||
CefDownloadItem downloadItem,
|
||||
CefDownloadItemCallback callback) {
|
||||
if (downloadItem.isComplete()) {
|
||||
log.info("Download completed: " + downloadItem.getFullPath());
|
||||
} else if (downloadItem.isCanceled()) {
|
||||
log.info("Download canceled: " + downloadItem.getFullPath());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ConsoleProgressHandler createProgressHandler() {
|
||||
return new ConsoleProgressHandler() {
|
||||
@Override
|
||||
public void handleProgress(EnumProgress state, float percent) {
|
||||
Objects.requireNonNull(state, "state cannot be null");
|
||||
SwingUtilities.invokeLater(
|
||||
() -> {
|
||||
if (loadingWindow != null) {
|
||||
switch (state) {
|
||||
case LOCATING:
|
||||
loadingWindow.setStatus("Locating Files...");
|
||||
loadingWindow.setProgress(0);
|
||||
break;
|
||||
case DOWNLOADING:
|
||||
if (percent >= 0) {
|
||||
loadingWindow.setStatus(
|
||||
String.format(
|
||||
"Downloading additional files: %.0f%%",
|
||||
percent));
|
||||
loadingWindow.setProgress((int) percent);
|
||||
}
|
||||
break;
|
||||
case EXTRACTING:
|
||||
loadingWindow.setStatus("Extracting files...");
|
||||
loadingWindow.setProgress(60);
|
||||
break;
|
||||
case INITIALIZING:
|
||||
loadingWindow.setStatus("Initializing UI...");
|
||||
loadingWindow.setProgress(80);
|
||||
break;
|
||||
case INITIALIZED:
|
||||
loadingWindow.setStatus("Finalising startup...");
|
||||
loadingWindow.setProgress(90);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void setupMainFrame() {
|
||||
frame = new JFrame("Stirling-PDF");
|
||||
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
|
||||
frame.setUndecorated(true);
|
||||
frame.setOpacity(0.0f);
|
||||
|
||||
JPanel contentPane = new JPanel(new BorderLayout());
|
||||
contentPane.setDoubleBuffered(true);
|
||||
contentPane.add(browser.getUIComponent(), BorderLayout.CENTER);
|
||||
frame.setContentPane(contentPane);
|
||||
|
||||
frame.addWindowListener(
|
||||
new java.awt.event.WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(java.awt.event.WindowEvent windowEvent) {
|
||||
cleanup();
|
||||
System.exit(0);
|
||||
}
|
||||
});
|
||||
|
||||
frame.setSize(1280, 768);
|
||||
frame.setLocationRelativeTo(null);
|
||||
|
||||
loadIcon();
|
||||
}
|
||||
|
||||
private void setupLoadHandler() {
|
||||
client.addLoadHandler(
|
||||
new CefLoadHandlerAdapter() {
|
||||
@Override
|
||||
public void onLoadingStateChange(
|
||||
CefBrowser browser,
|
||||
boolean isLoading,
|
||||
boolean canGoBack,
|
||||
boolean canGoForward) {
|
||||
if (!isLoading && !browserInitialized) {
|
||||
browserInitialized = true;
|
||||
SwingUtilities.invokeLater(
|
||||
() -> {
|
||||
if (loadingWindow != null) {
|
||||
Timer timer =
|
||||
new Timer(
|
||||
500,
|
||||
e -> {
|
||||
loadingWindow.dispose();
|
||||
loadingWindow = null;
|
||||
|
||||
frame.dispose();
|
||||
frame.setOpacity(1.0f);
|
||||
frame.setUndecorated(false);
|
||||
frame.pack();
|
||||
frame.setSize(1280, 800);
|
||||
frame.setLocationRelativeTo(null);
|
||||
frame.setVisible(true);
|
||||
frame.requestFocus();
|
||||
frame.toFront();
|
||||
browser.getUIComponent()
|
||||
.requestFocus();
|
||||
});
|
||||
timer.setRepeats(false);
|
||||
timer.start();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupTrayIcon(Image icon) {
|
||||
if (!SystemTray.isSupported()) {
|
||||
log.warn("System tray is not supported");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
systemTray = SystemTray.getSystemTray();
|
||||
|
||||
// Create popup menu
|
||||
PopupMenu popup = new PopupMenu();
|
||||
|
||||
// Create menu items
|
||||
MenuItem showItem = new MenuItem("Show");
|
||||
showItem.addActionListener(
|
||||
e -> {
|
||||
frame.setVisible(true);
|
||||
frame.setState(Frame.NORMAL);
|
||||
});
|
||||
|
||||
MenuItem exitItem = new MenuItem("Exit");
|
||||
exitItem.addActionListener(
|
||||
e -> {
|
||||
cleanup();
|
||||
System.exit(0);
|
||||
});
|
||||
|
||||
// Add menu items to popup menu
|
||||
popup.add(showItem);
|
||||
popup.addSeparator();
|
||||
popup.add(exitItem);
|
||||
|
||||
// Create tray icon
|
||||
trayIcon = new TrayIcon(icon, "Stirling-PDF", popup);
|
||||
trayIcon.setImageAutoSize(true);
|
||||
|
||||
// Add double-click behavior
|
||||
trayIcon.addActionListener(
|
||||
e -> {
|
||||
frame.setVisible(true);
|
||||
frame.setState(Frame.NORMAL);
|
||||
});
|
||||
|
||||
// Add tray icon to system tray
|
||||
systemTray.add(trayIcon);
|
||||
|
||||
// Modify frame behavior to minimize to tray
|
||||
frame.addWindowStateListener(
|
||||
new WindowStateListener() {
|
||||
public void windowStateChanged(WindowEvent e) {
|
||||
if (e.getNewState() == Frame.ICONIFIED) {
|
||||
frame.setVisible(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (AWTException e) {
|
||||
log.error("Error setting up system tray icon", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadIcon() {
|
||||
try {
|
||||
Image icon = null;
|
||||
String[] iconPaths = {"/static/favicon.ico"};
|
||||
|
||||
for (String path : iconPaths) {
|
||||
if (icon != null) break;
|
||||
try {
|
||||
try (InputStream is = getClass().getResourceAsStream(path)) {
|
||||
if (is != null) {
|
||||
icon = ImageIO.read(is);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not load icon from " + path, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (icon != null) {
|
||||
frame.setIconImage(icon);
|
||||
setupTrayIcon(icon);
|
||||
} else {
|
||||
log.warn("Could not load icon from any source");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error loading icon", e);
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void cleanup() {
|
||||
if (browser != null) browser.close(true);
|
||||
if (client != null) client.dispose();
|
||||
if (cefApp != null) cefApp.dispose();
|
||||
if (loadingWindow != null) loadingWindow.dispose();
|
||||
}
|
||||
}
|
||||
114
src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java
Normal file
114
src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java
Normal file
@@ -0,0 +1,114 @@
|
||||
package stirling.software.SPDF.UI.impl;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.*;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class LoadingWindow extends JDialog {
|
||||
private final JProgressBar progressBar;
|
||||
private final JLabel statusLabel;
|
||||
private final JPanel mainPanel;
|
||||
private final JLabel brandLabel;
|
||||
|
||||
public LoadingWindow(Frame parent, String initialUrl) {
|
||||
super(parent, "Initializing Stirling-PDF", true);
|
||||
|
||||
// Initialize components
|
||||
mainPanel = new JPanel();
|
||||
mainPanel.setBackground(Color.WHITE);
|
||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 30, 20, 30));
|
||||
mainPanel.setLayout(new GridBagLayout());
|
||||
GridBagConstraints gbc = new GridBagConstraints();
|
||||
|
||||
// Configure GridBagConstraints
|
||||
gbc.gridwidth = GridBagConstraints.REMAINDER;
|
||||
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||
gbc.insets = new Insets(5, 5, 5, 5);
|
||||
gbc.weightx = 1.0; // Add horizontal weight
|
||||
gbc.weighty = 0.0; // Add vertical weight
|
||||
|
||||
// Add icon
|
||||
try {
|
||||
try (InputStream is = getClass().getResourceAsStream("/static/favicon.ico")) {
|
||||
if (is != null) {
|
||||
Image img = ImageIO.read(is);
|
||||
if (img != null) {
|
||||
Image scaledImg = img.getScaledInstance(48, 48, Image.SCALE_SMOOTH);
|
||||
JLabel iconLabel = new JLabel(new ImageIcon(scaledImg));
|
||||
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
gbc.gridy = 0;
|
||||
mainPanel.add(iconLabel, gbc);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to load icon", e);
|
||||
}
|
||||
// URL Label with explicit size
|
||||
brandLabel = new JLabel(initialUrl);
|
||||
brandLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
brandLabel.setPreferredSize(new Dimension(300, 25));
|
||||
brandLabel.setText("Stirling-PDF");
|
||||
gbc.gridy = 1;
|
||||
mainPanel.add(brandLabel, gbc);
|
||||
|
||||
// Status label with explicit size
|
||||
statusLabel = new JLabel("Initializing...");
|
||||
statusLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
statusLabel.setPreferredSize(new Dimension(300, 25));
|
||||
gbc.gridy = 2;
|
||||
mainPanel.add(statusLabel, gbc);
|
||||
// Progress bar with explicit size
|
||||
progressBar = new JProgressBar(0, 100);
|
||||
progressBar.setStringPainted(true);
|
||||
progressBar.setPreferredSize(new Dimension(300, 25));
|
||||
gbc.gridy = 3;
|
||||
mainPanel.add(progressBar, gbc);
|
||||
|
||||
// Set dialog properties
|
||||
setContentPane(mainPanel);
|
||||
setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
|
||||
setResizable(false);
|
||||
setUndecorated(false);
|
||||
|
||||
// Set size and position
|
||||
setSize(400, 200);
|
||||
setLocationRelativeTo(parent);
|
||||
setAlwaysOnTop(true);
|
||||
setProgress(0);
|
||||
setStatus("Starting...");
|
||||
}
|
||||
|
||||
public void setProgress(final int progress) {
|
||||
SwingUtilities.invokeLater(
|
||||
() -> {
|
||||
try {
|
||||
progressBar.setValue(Math.min(Math.max(progress, 0), 100));
|
||||
progressBar.setString(progress + "%");
|
||||
mainPanel.revalidate();
|
||||
mainPanel.repaint();
|
||||
} catch (Exception e) {
|
||||
log.error("Error updating progress", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setStatus(final String status) {
|
||||
log.info(status);
|
||||
SwingUtilities.invokeLater(
|
||||
() -> {
|
||||
try {
|
||||
statusLabel.setText(status != null ? status : "");
|
||||
mainPanel.revalidate();
|
||||
mainPanel.repaint();
|
||||
} catch (Exception e) {
|
||||
log.error("Error updating status", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -260,6 +260,9 @@ public class EndpointConfiguration {
|
||||
|
||||
// Pdftohtml dependent endpoints
|
||||
addEndpointToGroup("Pdftohtml", "pdf-to-html");
|
||||
|
||||
// disabled for now while we resolve issues
|
||||
disableEndpoint("pdf-to-pdfa");
|
||||
}
|
||||
|
||||
private void processEnvironmentConfigs() {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import io.micrometer.common.util.StringUtils;
|
||||
@@ -23,6 +26,18 @@ public class InitialSetup {
|
||||
@Autowired private ApplicationProperties applicationProperties;
|
||||
|
||||
@PostConstruct
|
||||
public void init() throws IOException {
|
||||
initUUIDKey();
|
||||
|
||||
initSecretKey();
|
||||
|
||||
initEnableCSRFSecurity();
|
||||
|
||||
initLegalUrls();
|
||||
|
||||
initSetAppVersion();
|
||||
}
|
||||
|
||||
public void initUUIDKey() throws IOException {
|
||||
String uuid = applicationProperties.getAutomaticallyGenerated().getUUID();
|
||||
if (!GeneralUtils.isValidUUID(uuid)) {
|
||||
@@ -32,7 +47,6 @@ public class InitialSetup {
|
||||
}
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void initSecretKey() throws IOException {
|
||||
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
|
||||
if (!GeneralUtils.isValidUUID(secretKey)) {
|
||||
@@ -42,13 +56,24 @@ public class InitialSetup {
|
||||
}
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void initEnableCSRFSecurity() throws IOException {
|
||||
if (GeneralUtils.isVersionHigher(
|
||||
"0.36.0", applicationProperties.getAutomaticallyGenerated().getAppVersion())) {
|
||||
Boolean csrf = applicationProperties.getSecurity().getCsrfDisabled();
|
||||
if (!csrf) {
|
||||
GeneralUtils.saveKeyToConfig("security.csrfDisabled", false, false);
|
||||
GeneralUtils.saveKeyToConfig("system.enableAnalytics", "true", false);
|
||||
applicationProperties.getSecurity().setCsrfDisabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void initLegalUrls() throws IOException {
|
||||
// Initialize Terms and Conditions
|
||||
String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
|
||||
if (StringUtils.isEmpty(termsUrl)) {
|
||||
String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions";
|
||||
GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl);
|
||||
GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl, false);
|
||||
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
|
||||
}
|
||||
|
||||
@@ -56,8 +81,23 @@ public class InitialSetup {
|
||||
String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy();
|
||||
if (StringUtils.isEmpty(privacyUrl)) {
|
||||
String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy";
|
||||
GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl);
|
||||
GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl, false);
|
||||
applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl);
|
||||
}
|
||||
}
|
||||
|
||||
public void initSetAppVersion() throws IOException {
|
||||
|
||||
String appVersion = "0.0.0";
|
||||
Resource resource = new ClassPathResource("version.properties");
|
||||
Properties props = new Properties();
|
||||
try {
|
||||
props.load(resource.getInputStream());
|
||||
appVersion = props.getProperty("version");
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
applicationProperties.getAutomaticallyGenerated().setAppVersion(appVersion);
|
||||
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.appVersion", appVersion, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,5 +75,6 @@ public class InitialSecuritySetup {
|
||||
userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
|
||||
log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
|
||||
}
|
||||
userService.syncCustomApiUser(applicationProperties.getSecurity().getCustomGlobalAPIKey());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ public class SecurityConfiguration {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
if (applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||
if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) {
|
||||
http.csrf(csrf -> csrf.disable());
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ public class SecurityConfiguration {
|
||||
csrf ->
|
||||
csrf.ignoringRequestMatchers(
|
||||
request -> {
|
||||
String apiKey = request.getHeader("X-API-Key");
|
||||
String apiKey = request.getHeader("X-API-KEY");
|
||||
|
||||
// If there's no API key, don't ignore CSRF
|
||||
// (return false)
|
||||
@@ -289,17 +289,17 @@ public class SecurityConfiguration {
|
||||
}
|
||||
|
||||
} else {
|
||||
if (!applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||
CookieCsrfTokenRepository cookieRepo =
|
||||
CookieCsrfTokenRepository.withHttpOnlyFalse();
|
||||
CsrfTokenRequestAttributeHandler requestHandler =
|
||||
new CsrfTokenRequestAttributeHandler();
|
||||
requestHandler.setCsrfRequestAttributeName(null);
|
||||
http.csrf(
|
||||
csrf ->
|
||||
csrf.csrfTokenRepository(cookieRepo)
|
||||
.csrfTokenRequestHandler(requestHandler));
|
||||
}
|
||||
// if (!applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||
// CookieCsrfTokenRepository cookieRepo =
|
||||
// CookieCsrfTokenRepository.withHttpOnlyFalse();
|
||||
// CsrfTokenRequestAttributeHandler requestHandler =
|
||||
// new CsrfTokenRequestAttributeHandler();
|
||||
// requestHandler.setCsrfRequestAttributeName(null);
|
||||
// http.csrf(
|
||||
// csrf ->
|
||||
// csrf.csrfTokenRepository(cookieRepo)
|
||||
// .csrfTokenRequestHandler(requestHandler));
|
||||
// }
|
||||
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
// Check for API key in the request headers if no authentication exists
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
String apiKey = request.getHeader("X-API-Key");
|
||||
String apiKey = request.getHeader("X-API-KEY");
|
||||
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
||||
try {
|
||||
// Use API key to authenticate. This requires you to have an authentication
|
||||
|
||||
@@ -59,7 +59,7 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
||||
String identifier = null;
|
||||
|
||||
// Check for API key in the request headers
|
||||
String apiKey = request.getHeader("X-API-Key");
|
||||
String apiKey = request.getHeader("X-API-KEY");
|
||||
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
||||
identifier =
|
||||
"API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
|
||||
@@ -79,7 +79,7 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
||||
Role userRole =
|
||||
getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
|
||||
|
||||
if (request.getHeader("X-API-Key") != null) {
|
||||
if (request.getHeader("X-API-KEY") != null) {
|
||||
// It's an API call
|
||||
processRequest(
|
||||
userRole.getApiCallsPerDay(),
|
||||
|
||||
@@ -390,6 +390,37 @@ public class UserService implements UserServiceInterface {
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void syncCustomApiUser(String customApiKey) throws IOException {
|
||||
if (customApiKey == null || customApiKey.trim().length() == 0) {
|
||||
return;
|
||||
}
|
||||
String username = "CUSTOM_API_USER";
|
||||
Optional<User> existingUser = findByUsernameIgnoreCase(username);
|
||||
|
||||
if (!existingUser.isPresent()) {
|
||||
// Create new user with API role
|
||||
User user = new User();
|
||||
user.setUsername(username);
|
||||
user.setPassword(UUID.randomUUID().toString());
|
||||
user.setEnabled(true);
|
||||
user.setFirstLogin(false);
|
||||
user.setAuthenticationType(AuthenticationType.WEB);
|
||||
user.setApiKey(customApiKey);
|
||||
user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user));
|
||||
userRepository.save(user);
|
||||
databaseBackupHelper.exportDatabase();
|
||||
} else {
|
||||
// Update API key if it has changed
|
||||
User user = existingUser.get();
|
||||
if (!customApiKey.equals(user.getApiKey())) {
|
||||
user.setApiKey(customApiKey);
|
||||
userRepository.save(user);
|
||||
databaseBackupHelper.exportDatabase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalUsersCount() {
|
||||
return userRepository.count();
|
||||
|
||||
@@ -52,84 +52,115 @@ public class SplitPDFController {
|
||||
"This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO")
|
||||
public ResponseEntity<byte[]> splitPdf(@ModelAttribute PDFWithPageNums request)
|
||||
throws IOException {
|
||||
MultipartFile file = request.getFileInput();
|
||||
String pages = request.getPageNumbers();
|
||||
// open the pdf document
|
||||
|
||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
|
||||
int totalPages = document.getNumberOfPages();
|
||||
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
|
||||
if (!pageNumbers.contains(totalPages - 1)) {
|
||||
// Create a mutable ArrayList so we can add to it
|
||||
pageNumbers = new ArrayList<>(pageNumbers);
|
||||
pageNumbers.add(totalPages - 1);
|
||||
}
|
||||
|
||||
logger.info(
|
||||
"Splitting PDF into pages: {}",
|
||||
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||
|
||||
// split the document
|
||||
PDDocument document = null;
|
||||
Path zipFile = null;
|
||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||
int previousPageNumber = 0;
|
||||
for (int splitPoint : pageNumbers) {
|
||||
try (PDDocument splitDocument =
|
||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) {
|
||||
for (int i = previousPageNumber; i <= splitPoint; i++) {
|
||||
PDPage page = document.getPage(i);
|
||||
splitDocument.addPage(page);
|
||||
logger.info("Adding page {} to split document", i);
|
||||
|
||||
try {
|
||||
|
||||
MultipartFile file = request.getFileInput();
|
||||
String pages = request.getPageNumbers();
|
||||
// open the pdf document
|
||||
|
||||
document = Loader.loadPDF(file.getBytes());
|
||||
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
|
||||
int totalPages = document.getNumberOfPages();
|
||||
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
|
||||
if (!pageNumbers.contains(totalPages - 1)) {
|
||||
// Create a mutable ArrayList so we can add to it
|
||||
pageNumbers = new ArrayList<>(pageNumbers);
|
||||
pageNumbers.add(totalPages - 1);
|
||||
}
|
||||
|
||||
logger.info(
|
||||
"Splitting PDF into pages: {}",
|
||||
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||
|
||||
// split the document
|
||||
splitDocumentsBoas = new ArrayList<>();
|
||||
int previousPageNumber = 0;
|
||||
for (int splitPoint : pageNumbers) {
|
||||
try (PDDocument splitDocument =
|
||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) {
|
||||
for (int i = previousPageNumber; i <= splitPoint; i++) {
|
||||
PDPage page = document.getPage(i);
|
||||
splitDocument.addPage(page);
|
||||
logger.info("Adding page {} to split document", i);
|
||||
}
|
||||
previousPageNumber = splitPoint + 1;
|
||||
|
||||
// Transfer metadata to split pdf
|
||||
// PdfMetadataService.setMetadataToPdf(splitDocument, metadata);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
splitDocument.save(baos);
|
||||
|
||||
splitDocumentsBoas.add(baos);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed splitting documents and saving them", e);
|
||||
throw e;
|
||||
}
|
||||
previousPageNumber = splitPoint + 1;
|
||||
}
|
||||
|
||||
// Transfer metadata to split pdf
|
||||
// PdfMetadataService.setMetadataToPdf(splitDocument, metadata);
|
||||
// closing the original document
|
||||
document.close();
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
splitDocument.save(baos);
|
||||
zipFile = Files.createTempFile("split_documents", ".zip");
|
||||
|
||||
splitDocumentsBoas.add(baos);
|
||||
String filename =
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "");
|
||||
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
|
||||
// loop through the split documents and write them to the zip file
|
||||
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
|
||||
String fileName = filename + "_" + (i + 1) + ".pdf";
|
||||
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
||||
byte[] pdf = baos.toByteArray();
|
||||
|
||||
// Add PDF file to the zip
|
||||
ZipEntry pdfEntry = new ZipEntry(fileName);
|
||||
zipOut.putNextEntry(pdfEntry);
|
||||
zipOut.write(pdf);
|
||||
zipOut.closeEntry();
|
||||
|
||||
logger.info("Wrote split document {} to zip file", fileName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed splitting documents and saving them", e);
|
||||
logger.error("Failed writing to zip", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// closing the original document
|
||||
document.close();
|
||||
logger.info(
|
||||
"Successfully created zip file with split documents: {}", zipFile.toString());
|
||||
byte[] data = Files.readAllBytes(zipFile);
|
||||
Files.deleteIfExists(zipFile);
|
||||
|
||||
Path zipFile = Files.createTempFile("split_documents", ".zip");
|
||||
// return the Resource in the response
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
|
||||
String filename =
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "");
|
||||
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
|
||||
// loop through the split documents and write them to the zip file
|
||||
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
|
||||
String fileName = filename + "_" + (i + 1) + ".pdf";
|
||||
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
||||
byte[] pdf = baos.toByteArray();
|
||||
} finally {
|
||||
try {
|
||||
// Close the main document
|
||||
if (document != null) {
|
||||
document.close();
|
||||
}
|
||||
|
||||
// Add PDF file to the zip
|
||||
ZipEntry pdfEntry = new ZipEntry(fileName);
|
||||
zipOut.putNextEntry(pdfEntry);
|
||||
zipOut.write(pdf);
|
||||
zipOut.closeEntry();
|
||||
// Close all ByteArrayOutputStreams
|
||||
for (ByteArrayOutputStream baos : splitDocumentsBoas) {
|
||||
if (baos != null) {
|
||||
baos.close();
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Wrote split document {} to zip file", fileName);
|
||||
// Delete temporary zip file
|
||||
if (zipFile != null) {
|
||||
Files.deleteIfExists(zipFile);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error while cleaning up resources", e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed writing to zip", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
|
||||
byte[] data = Files.readAllBytes(zipFile);
|
||||
Files.deleteIfExists(zipFile);
|
||||
|
||||
// return the Resource in the response
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,70 +59,86 @@ public class SplitPdfByChaptersController {
|
||||
public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfByChaptersRequest request)
|
||||
throws Exception {
|
||||
MultipartFile file = request.getFileInput();
|
||||
boolean includeMetadata = request.getIncludeMetadata();
|
||||
Integer bookmarkLevel =
|
||||
request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
|
||||
if (bookmarkLevel < 0) {
|
||||
return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes());
|
||||
}
|
||||
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
||||
PDDocument sourceDocument = null;
|
||||
Path zipFile = null;
|
||||
|
||||
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
|
||||
|
||||
if (outline == null) {
|
||||
logger.warn("No outline found for {}", file.getOriginalFilename());
|
||||
return ResponseEntity.badRequest().body("No outline found".getBytes());
|
||||
}
|
||||
List<Bookmark> bookmarks = new ArrayList<>();
|
||||
try {
|
||||
bookmarks =
|
||||
extractOutlineItems(
|
||||
sourceDocument,
|
||||
outline.getFirstChild(),
|
||||
bookmarks,
|
||||
outline.getFirstChild().getNextSibling(),
|
||||
0,
|
||||
bookmarkLevel);
|
||||
// to handle last page edge case
|
||||
bookmarks.get(bookmarks.size() - 1).setEndPage(sourceDocument.getNumberOfPages());
|
||||
Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1);
|
||||
boolean includeMetadata = request.getIncludeMetadata();
|
||||
Integer bookmarkLevel =
|
||||
request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
|
||||
if (bookmarkLevel < 0) {
|
||||
return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes());
|
||||
}
|
||||
sourceDocument = Loader.loadPDF(file.getBytes());
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to extract outline items", e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body("Unable to extract outline items".getBytes());
|
||||
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
|
||||
|
||||
if (outline == null) {
|
||||
logger.warn("No outline found for {}", file.getOriginalFilename());
|
||||
return ResponseEntity.badRequest().body("No outline found".getBytes());
|
||||
}
|
||||
List<Bookmark> bookmarks = new ArrayList<>();
|
||||
try {
|
||||
bookmarks =
|
||||
extractOutlineItems(
|
||||
sourceDocument,
|
||||
outline.getFirstChild(),
|
||||
bookmarks,
|
||||
outline.getFirstChild().getNextSibling(),
|
||||
0,
|
||||
bookmarkLevel);
|
||||
// to handle last page edge case
|
||||
bookmarks.get(bookmarks.size() - 1).setEndPage(sourceDocument.getNumberOfPages());
|
||||
Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to extract outline items", e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body("Unable to extract outline items".getBytes());
|
||||
}
|
||||
|
||||
boolean allowDuplicates = request.getAllowDuplicates();
|
||||
if (!allowDuplicates) {
|
||||
/*
|
||||
duplicates are generated when multiple bookmarks correspond to the same page,
|
||||
if the user doesn't want duplicates mergeBookmarksThatCorrespondToSamePage() method will merge the titles of all
|
||||
the bookmarks that correspond to the same page, and treat them as a single bookmark
|
||||
*/
|
||||
bookmarks = mergeBookmarksThatCorrespondToSamePage(bookmarks);
|
||||
}
|
||||
for (Bookmark bookmark : bookmarks) {
|
||||
logger.info(
|
||||
"{}::::{} to {}",
|
||||
bookmark.getTitle(),
|
||||
bookmark.getStartPage(),
|
||||
bookmark.getEndPage());
|
||||
}
|
||||
List<ByteArrayOutputStream> splitDocumentsBoas =
|
||||
getSplitDocumentsBoas(sourceDocument, bookmarks, includeMetadata);
|
||||
|
||||
zipFile = createZipFile(bookmarks, splitDocumentsBoas);
|
||||
|
||||
byte[] data = Files.readAllBytes(zipFile);
|
||||
Files.deleteIfExists(zipFile);
|
||||
|
||||
String filename =
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "");
|
||||
sourceDocument.close();
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
} finally {
|
||||
try {
|
||||
if (sourceDocument != null) {
|
||||
sourceDocument.close();
|
||||
}
|
||||
if (zipFile != null) {
|
||||
Files.deleteIfExists(zipFile);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error while cleaning up resources", e);
|
||||
}
|
||||
}
|
||||
|
||||
boolean allowDuplicates = request.getAllowDuplicates();
|
||||
if (!allowDuplicates) {
|
||||
/*
|
||||
duplicates are generated when multiple bookmarks correspond to the same page,
|
||||
if the user doesn't want duplicates mergeBookmarksThatCorrespondToSamePage() method will merge the titles of all
|
||||
the bookmarks that correspond to the same page, and treat them as a single bookmark
|
||||
*/
|
||||
bookmarks = mergeBookmarksThatCorrespondToSamePage(bookmarks);
|
||||
}
|
||||
for (Bookmark bookmark : bookmarks) {
|
||||
logger.info(
|
||||
"{}::::{} to {}",
|
||||
bookmark.getTitle(),
|
||||
bookmark.getStartPage(),
|
||||
bookmark.getEndPage());
|
||||
}
|
||||
List<ByteArrayOutputStream> splitDocumentsBoas =
|
||||
getSplitDocumentsBoas(sourceDocument, bookmarks, includeMetadata);
|
||||
|
||||
Path zipFile = createZipFile(bookmarks, splitDocumentsBoas);
|
||||
|
||||
byte[] data = Files.readAllBytes(zipFile);
|
||||
Files.deleteIfExists(zipFile);
|
||||
|
||||
String filename =
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "");
|
||||
sourceDocument.close();
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
private List<Bookmark> mergeBookmarksThatCorrespondToSamePage(List<Bookmark> bookmarks) {
|
||||
|
||||
@@ -105,15 +105,13 @@ public class SplitPdfBySectionsController {
|
||||
|
||||
if (sectionNum == horiz * verti) pageNum++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("exception", e);
|
||||
} finally {
|
||||
data = Files.readAllBytes(zipFile);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
|
||||
} finally {
|
||||
Files.deleteIfExists(zipFile);
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
public List<PDDocument> splitPdfPages(
|
||||
|
||||
@@ -65,112 +65,137 @@ public class ConvertImgPDFController {
|
||||
String colorType = request.getColorType();
|
||||
String dpi = request.getDpi();
|
||||
|
||||
byte[] pdfBytes = file.getBytes();
|
||||
ImageType colorTypeResult = ImageType.RGB;
|
||||
if ("greyscale".equals(colorType)) {
|
||||
colorTypeResult = ImageType.GRAY;
|
||||
} else if ("blackwhite".equals(colorType)) {
|
||||
colorTypeResult = ImageType.BINARY;
|
||||
}
|
||||
// returns bytes for image
|
||||
boolean singleImage = "single".equals(singleOrMultiple);
|
||||
Path tempFile = null;
|
||||
Path tempOutputDir = null;
|
||||
Path tempPdfPath = null;
|
||||
byte[] result = null;
|
||||
String filename =
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "");
|
||||
|
||||
result =
|
||||
PdfUtils.convertFromPdf(
|
||||
pdfBytes,
|
||||
"webp".equalsIgnoreCase(imageFormat) ? "png" : imageFormat.toUpperCase(),
|
||||
colorTypeResult,
|
||||
singleImage,
|
||||
Integer.valueOf(dpi),
|
||||
filename);
|
||||
if (result == null || result.length == 0) {
|
||||
logger.error("resultant bytes for {} is null, error converting ", filename);
|
||||
}
|
||||
if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) {
|
||||
throw new IOException("Python is not installed. Required for WebP conversion.");
|
||||
} else if ("webp".equalsIgnoreCase(imageFormat)
|
||||
&& CheckProgramInstall.isPythonAvailable()) {
|
||||
// Write the output stream to a temp file
|
||||
Path tempFile = Files.createTempFile("temp_png", ".png");
|
||||
try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {
|
||||
fos.write(result);
|
||||
fos.flush();
|
||||
try {
|
||||
byte[] pdfBytes = file.getBytes();
|
||||
ImageType colorTypeResult = ImageType.RGB;
|
||||
if ("greyscale".equals(colorType)) {
|
||||
colorTypeResult = ImageType.GRAY;
|
||||
} else if ("blackwhite".equals(colorType)) {
|
||||
colorTypeResult = ImageType.BINARY;
|
||||
}
|
||||
// returns bytes for image
|
||||
boolean singleImage = "single".equals(singleOrMultiple);
|
||||
String filename =
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "");
|
||||
|
||||
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(pythonVersion);
|
||||
command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion
|
||||
|
||||
// Create a temporary directory for the output WebP files
|
||||
Path tempOutputDir = Files.createTempDirectory("webp_output");
|
||||
if (singleImage) {
|
||||
// Run the Python script to convert PNG to WebP
|
||||
command.add(tempFile.toString());
|
||||
command.add(tempOutputDir.toString());
|
||||
command.add("--single");
|
||||
} else {
|
||||
// Save the uploaded PDF to a temporary file
|
||||
Path tempPdfPath = Files.createTempFile("temp_pdf", ".pdf");
|
||||
file.transferTo(tempPdfPath.toFile());
|
||||
// Run the Python script to convert PDF to WebP
|
||||
command.add(tempPdfPath.toString());
|
||||
command.add(tempOutputDir.toString());
|
||||
result =
|
||||
PdfUtils.convertFromPdf(
|
||||
pdfBytes,
|
||||
"webp".equalsIgnoreCase(imageFormat)
|
||||
? "png"
|
||||
: imageFormat.toUpperCase(),
|
||||
colorTypeResult,
|
||||
singleImage,
|
||||
Integer.valueOf(dpi),
|
||||
filename);
|
||||
if (result == null || result.length == 0) {
|
||||
logger.error("resultant bytes for {} is null, error converting ", filename);
|
||||
}
|
||||
command.add("--dpi");
|
||||
command.add(dpi);
|
||||
ProcessExecutorResult resultProcess =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
|
||||
.runCommandWithOutputHandling(command);
|
||||
|
||||
// Find all WebP files in the output directory
|
||||
List<Path> webpFiles =
|
||||
Files.walk(tempOutputDir)
|
||||
.filter(path -> path.toString().endsWith(".webp"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (webpFiles.isEmpty()) {
|
||||
logger.error("No WebP files were created in: {}", tempOutputDir.toString());
|
||||
throw new IOException("No WebP files were created. " + resultProcess.getMessages());
|
||||
}
|
||||
|
||||
byte[] bodyBytes = new byte[0];
|
||||
|
||||
if (webpFiles.size() == 1) {
|
||||
// Return the single WebP file directly
|
||||
Path webpFilePath = webpFiles.get(0);
|
||||
bodyBytes = Files.readAllBytes(webpFilePath);
|
||||
} else {
|
||||
// Create a ZIP file containing all WebP images
|
||||
ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream();
|
||||
try (ZipOutputStream zos = new ZipOutputStream(zipOutputStream)) {
|
||||
for (Path webpFile : webpFiles) {
|
||||
zos.putNextEntry(new ZipEntry(webpFile.getFileName().toString()));
|
||||
Files.copy(webpFile, zos);
|
||||
zos.closeEntry();
|
||||
}
|
||||
if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) {
|
||||
throw new IOException("Python is not installed. Required for WebP conversion.");
|
||||
} else if ("webp".equalsIgnoreCase(imageFormat)
|
||||
&& CheckProgramInstall.isPythonAvailable()) {
|
||||
// Write the output stream to a temp file
|
||||
tempFile = Files.createTempFile("temp_png", ".png");
|
||||
try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {
|
||||
fos.write(result);
|
||||
fos.flush();
|
||||
}
|
||||
bodyBytes = zipOutputStream.toByteArray();
|
||||
}
|
||||
// Clean up the temporary files
|
||||
Files.deleteIfExists(tempFile);
|
||||
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||
result = bodyBytes;
|
||||
}
|
||||
|
||||
if (singleImage) {
|
||||
String docName = filename + "." + imageFormat;
|
||||
MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat));
|
||||
return WebResponseUtils.bytesToWebResponse(result, docName, mediaType);
|
||||
} else {
|
||||
String zipFilename = filename + "_convertedToImages.zip";
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
result, zipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(pythonVersion);
|
||||
command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion
|
||||
|
||||
// Create a temporary directory for the output WebP files
|
||||
tempOutputDir = Files.createTempDirectory("webp_output");
|
||||
if (singleImage) {
|
||||
// Run the Python script to convert PNG to WebP
|
||||
command.add(tempFile.toString());
|
||||
command.add(tempOutputDir.toString());
|
||||
command.add("--single");
|
||||
} else {
|
||||
// Save the uploaded PDF to a temporary file
|
||||
tempPdfPath = Files.createTempFile("temp_pdf", ".pdf");
|
||||
file.transferTo(tempPdfPath.toFile());
|
||||
// Run the Python script to convert PDF to WebP
|
||||
command.add(tempPdfPath.toString());
|
||||
command.add(tempOutputDir.toString());
|
||||
}
|
||||
command.add("--dpi");
|
||||
command.add(dpi);
|
||||
ProcessExecutorResult resultProcess =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
|
||||
.runCommandWithOutputHandling(command);
|
||||
|
||||
// Find all WebP files in the output directory
|
||||
List<Path> webpFiles =
|
||||
Files.walk(tempOutputDir)
|
||||
.filter(path -> path.toString().endsWith(".webp"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (webpFiles.isEmpty()) {
|
||||
logger.error("No WebP files were created in: {}", tempOutputDir.toString());
|
||||
throw new IOException(
|
||||
"No WebP files were created. " + resultProcess.getMessages());
|
||||
}
|
||||
|
||||
byte[] bodyBytes = new byte[0];
|
||||
|
||||
if (webpFiles.size() == 1) {
|
||||
// Return the single WebP file directly
|
||||
Path webpFilePath = webpFiles.get(0);
|
||||
bodyBytes = Files.readAllBytes(webpFilePath);
|
||||
} else {
|
||||
// Create a ZIP file containing all WebP images
|
||||
ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream();
|
||||
try (ZipOutputStream zos = new ZipOutputStream(zipOutputStream)) {
|
||||
for (Path webpFile : webpFiles) {
|
||||
zos.putNextEntry(new ZipEntry(webpFile.getFileName().toString()));
|
||||
Files.copy(webpFile, zos);
|
||||
zos.closeEntry();
|
||||
}
|
||||
}
|
||||
bodyBytes = zipOutputStream.toByteArray();
|
||||
}
|
||||
// Clean up the temporary files
|
||||
Files.deleteIfExists(tempFile);
|
||||
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||
result = bodyBytes;
|
||||
}
|
||||
|
||||
if (singleImage) {
|
||||
String docName = filename + "." + imageFormat;
|
||||
MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat));
|
||||
return WebResponseUtils.bytesToWebResponse(result, docName, mediaType);
|
||||
} else {
|
||||
String zipFilename = filename + "_convertedToImages.zip";
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
result, zipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
} finally {
|
||||
try {
|
||||
// Clean up temporary files
|
||||
if (tempFile != null) {
|
||||
Files.deleteIfExists(tempFile);
|
||||
}
|
||||
if (tempPdfPath != null) {
|
||||
Files.deleteIfExists(tempPdfPath);
|
||||
}
|
||||
if (tempOutputDir != null) {
|
||||
FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error cleaning up temporary files", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ public class OCRController {
|
||||
|
||||
Files.createDirectories(tempOutputDir);
|
||||
Files.createDirectories(tempImagesDir);
|
||||
|
||||
Process process = null;
|
||||
try {
|
||||
// Save input file
|
||||
inputFile.transferTo(tempInputFile.toFile());
|
||||
@@ -139,7 +139,7 @@ public class OCRController {
|
||||
command.add("pdf"); // Always output PDF
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(command);
|
||||
Process process = pb.start();
|
||||
process = pb.start();
|
||||
|
||||
// Capture any error output
|
||||
try (BufferedReader reader =
|
||||
@@ -188,6 +188,10 @@ public class OCRController {
|
||||
.body(pdfContent);
|
||||
|
||||
} finally {
|
||||
if (process != null) {
|
||||
process.destroy();
|
||||
}
|
||||
|
||||
// Clean up temporary files
|
||||
deleteDirectory(tempDir);
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ public class PipelineProcessor {
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
String apiKey = getApiKeyForUser();
|
||||
headers.add("X-API-Key", apiKey);
|
||||
headers.add("X-API-KEY", apiKey);
|
||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||
|
||||
// Create HttpEntity with the body and headers
|
||||
|
||||
@@ -595,7 +595,9 @@ public class GetInfoOnPDF {
|
||||
|
||||
permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument()));
|
||||
permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent()));
|
||||
permissionsNode.put("Extracting for accessibility", getPermissionState(ap.canExtractForAccessibility()));
|
||||
permissionsNode.put(
|
||||
"Extracting for accessibility",
|
||||
getPermissionState(ap.canExtractForAccessibility()));
|
||||
permissionsNode.put("Form Filling", getPermissionState(ap.canFillInForm()));
|
||||
permissionsNode.put("Modifying", getPermissionState(ap.canModify()));
|
||||
permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations()));
|
||||
|
||||
@@ -92,20 +92,29 @@ public class ValidateSignatureController {
|
||||
SignerInformationStore signerStore = signedData.getSignerInfos();
|
||||
|
||||
for (SignerInformation signer : signerStore.getSigners()) {
|
||||
X509CertificateHolder certHolder = (X509CertificateHolder) certStore.getMatches(signer.getSID()).iterator().next();
|
||||
X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certHolder);
|
||||
X509CertificateHolder certHolder =
|
||||
(X509CertificateHolder)
|
||||
certStore.getMatches(signer.getSID()).iterator().next();
|
||||
X509Certificate cert =
|
||||
new JcaX509CertificateConverter().getCertificate(certHolder);
|
||||
|
||||
boolean isValid = signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
|
||||
boolean isValid =
|
||||
signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
|
||||
result.setValid(isValid);
|
||||
|
||||
// Additional validations
|
||||
result.setChainValid(customCert != null
|
||||
? certValidationService.validateCertificateChainWithCustomCert(cert, customCert)
|
||||
: certValidationService.validateCertificateChain(cert));
|
||||
result.setChainValid(
|
||||
customCert != null
|
||||
? certValidationService
|
||||
.validateCertificateChainWithCustomCert(
|
||||
cert, customCert)
|
||||
: certValidationService.validateCertificateChain(cert));
|
||||
|
||||
result.setTrustValid(customCert != null
|
||||
? certValidationService.validateTrustWithCustomCert(cert, customCert)
|
||||
: certValidationService.validateTrustStore(cert));
|
||||
result.setTrustValid(
|
||||
customCert != null
|
||||
? certValidationService.validateTrustWithCustomCert(
|
||||
cert, customCert)
|
||||
: certValidationService.validateTrustStore(cert));
|
||||
|
||||
result.setNotRevoked(!certValidationService.isRevoked(cert));
|
||||
result.setNotExpired(!cert.getNotAfter().before(new Date()));
|
||||
@@ -123,17 +132,18 @@ public class ValidateSignatureController {
|
||||
result.setValidFrom(cert.getNotBefore().toString());
|
||||
result.setValidUntil(cert.getNotAfter().toString());
|
||||
result.setSignatureAlgorithm(cert.getSigAlgName());
|
||||
|
||||
|
||||
// Get key size (if possible)
|
||||
try {
|
||||
result.setKeySize(((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength());
|
||||
result.setKeySize(
|
||||
((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength());
|
||||
} catch (Exception e) {
|
||||
// If not RSA or error, set to 0
|
||||
result.setKeySize(0);
|
||||
}
|
||||
|
||||
result.setVersion(String.valueOf(cert.getVersion()));
|
||||
|
||||
|
||||
// Set key usage
|
||||
List<String> keyUsages = new ArrayList<>();
|
||||
boolean[] keyUsageFlags = cert.getKeyUsage();
|
||||
@@ -150,9 +160,11 @@ public class ValidateSignatureController {
|
||||
}
|
||||
}
|
||||
result.setKeyUsages(keyUsages);
|
||||
|
||||
|
||||
// Check if self-signed
|
||||
result.setSelfSigned(cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal()));
|
||||
result.setSelfSigned(
|
||||
cert.getSubjectX500Principal()
|
||||
.equals(cert.getIssuerX500Principal()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.setValid(false);
|
||||
|
||||
@@ -73,6 +73,7 @@ public class ApplicationProperties {
|
||||
private int loginAttemptCount;
|
||||
private long loginResetTimeMinutes;
|
||||
private String loginMethod = "all";
|
||||
private String customGlobalAPIKey;
|
||||
|
||||
public Boolean isAltLogin() {
|
||||
return saml2.getEnabled() || oauth2.getEnabled();
|
||||
@@ -285,6 +286,7 @@ public class ApplicationProperties {
|
||||
public static class AutomaticallyGenerated {
|
||||
@ToString.Exclude private String key;
|
||||
private String UUID;
|
||||
private String appVersion;
|
||||
}
|
||||
|
||||
@Data
|
||||
|
||||
@@ -16,16 +16,15 @@ public class SignatureValidationResult {
|
||||
private boolean trustValid;
|
||||
private boolean notExpired;
|
||||
private boolean notRevoked;
|
||||
|
||||
private String issuerDN; // Certificate issuer's Distinguished Name
|
||||
private String subjectDN; // Certificate subject's Distinguished Name
|
||||
private String serialNumber; // Certificate serial number
|
||||
private String validFrom; // Certificate validity start date
|
||||
private String validUntil; // Certificate validity end date
|
||||
private String signatureAlgorithm;// Algorithm used for signing
|
||||
private int keySize; // Key size in bits
|
||||
private String version; // Certificate version
|
||||
private List<String> keyUsages; // List of key usage purposes
|
||||
private boolean isSelfSigned; // Whether the certificate is self-signed
|
||||
|
||||
|
||||
private String issuerDN; // Certificate issuer's Distinguished Name
|
||||
private String subjectDN; // Certificate subject's Distinguished Name
|
||||
private String serialNumber; // Certificate serial number
|
||||
private String validFrom; // Certificate validity start date
|
||||
private String validUntil; // Certificate validity end date
|
||||
private String signatureAlgorithm; // Algorithm used for signing
|
||||
private int keySize; // Key size in bits
|
||||
private String version; // Certificate version
|
||||
private List<String> keyUsages; // List of key usage purposes
|
||||
private boolean isSelfSigned; // Whether the certificate is self-signed
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package stirling.software.SPDF.service;
|
||||
|
||||
import io.github.pixee.security.BoundedLineReader;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -24,6 +23,8 @@ import java.util.Set;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import io.github.pixee.security.BoundedLineReader;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
@Service
|
||||
|
||||
@@ -88,15 +88,45 @@ public class GeneralUtils {
|
||||
|
||||
public static boolean isURLReachable(String urlStr) {
|
||||
try {
|
||||
// Parse the URL
|
||||
URL url = URI.create(urlStr).toURL();
|
||||
|
||||
// Allow only http and https protocols
|
||||
String protocol = url.getProtocol();
|
||||
if (!protocol.equals("http") && !protocol.equals("https")) {
|
||||
return false; // Disallow other protocols
|
||||
}
|
||||
|
||||
// Check if the host is a local address
|
||||
String host = url.getHost();
|
||||
if (isLocalAddress(host)) {
|
||||
return false; // Exclude local addresses
|
||||
}
|
||||
|
||||
// Check if the URL is reachable
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("HEAD");
|
||||
// connection.setConnectTimeout(5000); // Set connection timeout
|
||||
// connection.setReadTimeout(5000); // Set read timeout
|
||||
int responseCode = connection.getResponseCode();
|
||||
return (200 <= responseCode && responseCode <= 399);
|
||||
} catch (MalformedURLException e) {
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
return false; // Return false in case of any exception
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isLocalAddress(String host) {
|
||||
try {
|
||||
// Resolve DNS to IP address
|
||||
InetAddress address = InetAddress.getByName(host);
|
||||
|
||||
// Check for local addresses
|
||||
return address.isAnyLocalAddress() || // Matches 0.0.0.0 or similar
|
||||
address.isLoopbackAddress() || // Matches 127.0.0.1 or ::1
|
||||
address.isSiteLocalAddress() || // Matches private IPv4 ranges: 192.168.x.x, 10.x.x.x, 172.16.x.x to 172.31.x.x
|
||||
address.getHostAddress().startsWith("fe80:"); // Matches link-local IPv6 addresses
|
||||
} catch (Exception e) {
|
||||
return false; // Return false for invalid or unresolved addresses
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,6 +319,10 @@ public class GeneralUtils {
|
||||
saveKeyToConfig(id, key, true);
|
||||
}
|
||||
|
||||
public static void saveKeyToConfig(String id, boolean key) throws IOException {
|
||||
saveKeyToConfig(id, key, true);
|
||||
}
|
||||
|
||||
public static void saveKeyToConfig(String id, String key, boolean autoGenerated)
|
||||
throws IOException {
|
||||
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
|
||||
@@ -307,6 +341,24 @@ public class GeneralUtils {
|
||||
settingsYml.save();
|
||||
}
|
||||
|
||||
public static void saveKeyToConfig(String id, boolean key, boolean autoGenerated)
|
||||
throws IOException {
|
||||
Path path = Paths.get("configs", "settings.yml");
|
||||
|
||||
final YamlFile settingsYml = new YamlFile(path.toFile());
|
||||
DumperOptions yamlOptionssettingsYml =
|
||||
((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions();
|
||||
yamlOptionssettingsYml.setSplitLines(false);
|
||||
|
||||
settingsYml.loadWithComments();
|
||||
|
||||
YamlFileWrapper writer = settingsYml.path(id).set(key);
|
||||
if (autoGenerated) {
|
||||
writer.comment("# Automatically Generated Settings (Do Not Edit Directly)");
|
||||
}
|
||||
settingsYml.save();
|
||||
}
|
||||
|
||||
public static String generateMachineFingerprint() {
|
||||
try {
|
||||
// Get the MAC address
|
||||
@@ -349,4 +401,33 @@ public class GeneralUtils {
|
||||
return "GenericID";
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isVersionHigher(String currentVersion, String compareVersion) {
|
||||
if (currentVersion == null || compareVersion == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Split versions into components
|
||||
String[] current = currentVersion.split("\\.");
|
||||
String[] compare = compareVersion.split("\\.");
|
||||
|
||||
// Get the length of the shorter version array
|
||||
int length = Math.min(current.length, compare.length);
|
||||
|
||||
// Compare each component
|
||||
for (int i = 0; i < length; i++) {
|
||||
int currentPart = Integer.parseInt(current[i]);
|
||||
int comparePart = Integer.parseInt(compare[i]);
|
||||
|
||||
if (currentPart > comparePart) {
|
||||
return true;
|
||||
}
|
||||
if (currentPart < comparePart) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If all components so far are equal, the longer version is considered higher
|
||||
return current.length > compare.length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=الصفحات المحددة
|
||||
multiTool.undo=تراجع
|
||||
multiTool.redo=إعادة إجراء
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=هذه الميزة متوفرة في <a href="{0}">صفحة الأدوات المتعددة</a> لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Seçilmiş Səhifə(lər)
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=Bu xüsusiyyət bizim <a href="{0}">multi-alət səhifə</a>mizdə də mövcuddur. Əlavə xüsusiyyətlər və səhifə-səhifə interfeys üçün sınaqdan keçirin!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Ausgewählte Seite(n)
|
||||
multiTool.undo=Rückgängig machen
|
||||
multiTool.redo=Wiederherstellen
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=Diese Funktion ist auch auf unserer <a href="{0}">PDF-Multitool-Seite</a> verfügbar. Probieren Sie sie aus, denn sie bietet eine verbesserte Benutzeroberfläche und zusätzliche Funktionen!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -122,6 +122,7 @@ enterpriseEdition.warning=این ویژگی فقط برای کاربران حر
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro از فایلهای پیکربندی YAML و دیگر ویژگیهای SSO پشتیبانی میکند.
|
||||
enterpriseEdition.ssoAdvert=به دنبال ویژگیهای بیشتر برای مدیریت کاربران هستید؟ Stirling PDF Pro را بررسی کنید
|
||||
|
||||
|
||||
#################
|
||||
# Analytics #
|
||||
#################
|
||||
@@ -515,6 +516,7 @@ home.validateSignature.title=اعتبارسنجی امضای PDF
|
||||
home.validateSignature.desc=تأیید امضاها و گواهیهای دیجیتال در اسناد PDF
|
||||
validateSignature.tags=امضا، تأیید، اعتبارسنجی، PDF، گواهینامه، امضای دیجیتال
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=جایگزینی/معکوس کردن رنگ
|
||||
replace-color.header=جایگزینی/معکوس کردن رنگ PDF
|
||||
home.replaceColorPdf.title=جایگزینی و معکوس کردن رنگ
|
||||
@@ -535,7 +537,6 @@ replace-color.submit=جایگزینی
|
||||
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
@@ -611,6 +612,7 @@ MarkdownToPDF.help=در حال پیشرفت
|
||||
MarkdownToPDF.credit=از WeasyPrint استفاده میکند
|
||||
|
||||
|
||||
|
||||
#url-to-pdf
|
||||
URLToPDF.title=URL به PDF
|
||||
URLToPDF.header=URL به PDF
|
||||
@@ -903,7 +905,7 @@ compress.selectText.5=اندازه PDF مورد انتظار (مثلاً ۲۵MB
|
||||
compress.submit=فشردهسازی
|
||||
|
||||
|
||||
# Add image
|
||||
#Add image
|
||||
addImage.title=افزودن تصویر
|
||||
addImage.header=افزودن تصویر به PDF
|
||||
addImage.everyPage=هر صفحه؟
|
||||
@@ -911,7 +913,7 @@ addImage.upload=افزودن تصویر
|
||||
addImage.submit=افزودن تصویر
|
||||
|
||||
|
||||
# Merge
|
||||
#merge
|
||||
merge.title=ادغام
|
||||
merge.header=ادغام چندین PDF (۲+)
|
||||
merge.sortByName=مرتبسازی بر اساس نام
|
||||
@@ -920,7 +922,7 @@ merge.removeCertSign=حذف امضای دیجیتال در فایل ادغام
|
||||
merge.submit=ادغام
|
||||
|
||||
|
||||
# PDF Organizer
|
||||
#pdfOrganiser
|
||||
pdfOrganiser.title=سازماندهی صفحات
|
||||
pdfOrganiser.header=سازماندهی صفحات PDF
|
||||
pdfOrganiser.submit=بازآرایی صفحات
|
||||
@@ -938,7 +940,7 @@ pdfOrganiser.mode.10=ادغام فرد-زوج
|
||||
pdfOrganiser.placeholder=(مثال: ۱,۳,۲ یا ۴-۸,۲,۱۰-۱۲ یا 2n-1)
|
||||
|
||||
|
||||
# Multi Tool
|
||||
#multiTool
|
||||
multiTool.title=ابزار چندگانه PDF
|
||||
multiTool.header=ابزار چندگانه PDF
|
||||
multiTool.uploadPrompts=نام فایل
|
||||
@@ -963,14 +965,24 @@ multiTool.dragDropMessage=صفحه(ها) انتخاب شدهاند
|
||||
multiTool.undo=واگرد
|
||||
multiTool.redo=بازگرداندن
|
||||
|
||||
# Multi Tool Advert
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=این ویژگی همچنین در <a href="{0}">صفحه ابزار چندگانه ما</a> موجود است. برای رابط کاربری صفحه به صفحه پیشرفته و ویژگیهای اضافی بررسی کنید!
|
||||
|
||||
# View PDF
|
||||
#view pdf
|
||||
viewPdf.title=مشاهده PDF
|
||||
viewPdf.header=مشاهده PDF
|
||||
|
||||
# Page Remover
|
||||
#pageRemover
|
||||
pageRemover.title=حذف صفحات
|
||||
pageRemover.header=حذف صفحات PDF
|
||||
pageRemover.pagesToDelete=صفحات برای حذف (یک لیست از اعداد صفحه جدا شده با کاما وارد کنید):
|
||||
@@ -978,14 +990,14 @@ pageRemover.submit=حذف صفحات
|
||||
pageRemover.placeholder=(مثال: ۱,۲,۶ یا ۱-۱۰,۱۵-۳۰)
|
||||
|
||||
|
||||
# Rotate
|
||||
#rotate
|
||||
rotate.title=چرخش PDF
|
||||
rotate.header=چرخش PDF
|
||||
rotate.selectAngle=زاویه چرخش را انتخاب کنید (به مضربهای ۹۰ درجه):
|
||||
rotate.submit=چرخش
|
||||
|
||||
|
||||
# Split PDFs
|
||||
#split-pdfs
|
||||
split.title=تقسیم PDF
|
||||
split.header=تقسیم PDF
|
||||
split.desc.1=اعدادی که انتخاب میکنید شماره صفحههایی هستند که میخواهید بر روی آنها تقسیم انجام دهید
|
||||
@@ -1014,7 +1026,7 @@ imageToPDF.selectText.4=ادغام در یک PDF واحد
|
||||
imageToPDF.selectText.5=تبدیل به PDF های جداگانه
|
||||
|
||||
|
||||
# PDF to Image
|
||||
#pdfToImage
|
||||
pdfToImage.title=PDF به تصویر
|
||||
pdfToImage.header=PDF به تصویر
|
||||
pdfToImage.selectText=فرمت تصویر
|
||||
@@ -1029,7 +1041,7 @@ pdfToImage.submit=تبدیل
|
||||
pdfToImage.info=پایتون نصب نشده است. برای تبدیل WebP لازم است.
|
||||
|
||||
|
||||
# Add Password
|
||||
#addPassword
|
||||
addPassword.title=افزودن گذرواژه
|
||||
addPassword.header=افزودن گذرواژه (رمزنگاری)
|
||||
addPassword.selectText.1=انتخاب PDF برای رمزنگاری
|
||||
@@ -1051,7 +1063,7 @@ addPassword.selectText.16=محدودیتهای باز شدن خود سند
|
||||
addPassword.submit=رمزنگاری
|
||||
|
||||
|
||||
# Watermark
|
||||
#watermark
|
||||
watermark.title=افزودن واترمارک
|
||||
watermark.header=افزودن واترمارک
|
||||
watermark.customColor=رنگ متن سفارشی
|
||||
@@ -1070,7 +1082,7 @@ watermark.type.1=متن
|
||||
watermark.type.2=تصویر
|
||||
|
||||
|
||||
# Change Permissions
|
||||
#Change permissions
|
||||
permissions.title=تغییر مجوزها
|
||||
permissions.header=تغییر مجوزها
|
||||
permissions.warning=برای اینکه این مجوزها غیرقابل تغییر باشند، توصیه میشود آنها را با گذرواژه از طریق صفحه افزودن گذرواژه تنظیم کنید
|
||||
@@ -1087,7 +1099,7 @@ permissions.selectText.10=جلوگیری از چاپ فرمتهای مختل
|
||||
permissions.submit=تغییر
|
||||
|
||||
|
||||
# Remove Password
|
||||
#remove password
|
||||
removePassword.title=حذف گذرواژه
|
||||
removePassword.header=حذف گذرواژه (رمزگشایی)
|
||||
removePassword.selectText.1=PDFی را برای رمزگشایی انتخاب کنید
|
||||
@@ -1095,7 +1107,7 @@ removePassword.selectText.2=گذرواژه
|
||||
removePassword.submit=حذف
|
||||
|
||||
|
||||
# Change Metadata
|
||||
#changeMetadata
|
||||
changeMetadata.title=عنوان:
|
||||
changeMetadata.header=تغییر متادادهها
|
||||
changeMetadata.selectText.1=لطفاً متغیرهایی که مایل به تغییر آنها هستید را ویرایش کنید
|
||||
@@ -1114,7 +1126,7 @@ changeMetadata.selectText.5=افزودن ورودی متاداده سفارشی
|
||||
changeMetadata.submit=تغییر
|
||||
|
||||
|
||||
# PDF to PDF/A
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF به PDF/A
|
||||
pdfToPDFA.header=PDF به PDF/A
|
||||
pdfToPDFA.credit=این سرویس از qpdf برای تبدیل PDF/A استفاده میکند
|
||||
@@ -1124,7 +1136,7 @@ pdfToPDFA.outputFormat=فرمت خروجی
|
||||
pdfToPDFA.pdfWithDigitalSignature=PDF حاوی یک امضای دیجیتال است. این در مرحله بعد حذف خواهد شد.
|
||||
|
||||
|
||||
# PDF to Word
|
||||
#PDFToWord
|
||||
PDFToWord.title=PDF به ورد
|
||||
PDFToWord.header=PDF به ورد
|
||||
PDFToWord.selectText.1=فرمت فایل خروجی
|
||||
@@ -1132,7 +1144,7 @@ PDFToWord.credit=این سرویس از LibreOffice برای تبدیل فایل
|
||||
PDFToWord.submit=تبدیل
|
||||
|
||||
|
||||
# PDF to Presentation
|
||||
#PDFToPresentation
|
||||
PDFToPresentation.title=PDF به ارائه
|
||||
PDFToPresentation.header=PDF به ارائه
|
||||
PDFToPresentation.selectText.1=فرمت فایل خروجی
|
||||
@@ -1140,7 +1152,7 @@ PDFToPresentation.credit=این سرویس از LibreOffice برای تبدیل
|
||||
PDFToPresentation.submit=تبدیل
|
||||
|
||||
|
||||
# PDF to Text
|
||||
#PDFToText
|
||||
PDFToText.title=PDF به RTF (متن)
|
||||
PDFToText.header=PDF به RTF (متن)
|
||||
PDFToText.selectText.1=فرمت فایل خروجی
|
||||
@@ -1148,27 +1160,26 @@ PDFToText.credit=این سرویس از LibreOffice برای تبدیل فایل
|
||||
PDFToText.submit=تبدیل
|
||||
|
||||
|
||||
# PDF to HTML
|
||||
#PDFToHTML
|
||||
PDFToHTML.title=PDF به HTML
|
||||
PDFToHTML.header=PDF به HTML
|
||||
PDFToHTML.credit=این سرویس از pdftohtml برای تبدیل فایل استفاده میکند.
|
||||
PDFToHTML.submit=تبدیل
|
||||
|
||||
|
||||
# PDF to XML
|
||||
#PDFToXML
|
||||
PDFToXML.title=PDF به XML
|
||||
PDFToXML.header=PDF به XML
|
||||
PDFToXML.credit=این سرویس از LibreOffice برای تبدیل فایل استفاده میکند.
|
||||
PDFToXML.submit=تبدیل
|
||||
|
||||
# PDF to CSV
|
||||
#PDFToCSV
|
||||
PDFToCSV.title=PDF به CSV
|
||||
PDFToCSV.header=PDF به CSV
|
||||
PDFToCSV.prompt=صفحهای که میخواهید جدول استخراج شود را انتخاب کنید
|
||||
PDFToCSV.submit=استخراج
|
||||
|
||||
|
||||
# Split by Size or Count
|
||||
#split-by-size-or-count
|
||||
split-by-size-or-count.title=تقسیم PDF بر اساس اندازه یا تعداد
|
||||
split-by-size-or-count.header=تقسیم PDF بر اساس اندازه یا تعداد
|
||||
split-by-size-or-count.type.label=انتخاب نوع تقسیم
|
||||
@@ -1180,7 +1191,7 @@ split-by-size-or-count.value.placeholder=اندازه را وارد کنید (م
|
||||
split-by-size-or-count.submit=ارسال
|
||||
|
||||
|
||||
# Overlay PDFs
|
||||
#overlay-pdfs
|
||||
overlay-pdfs.header=ترکیب فایلهای PDF
|
||||
overlay-pdfs.baseFile.label=انتخاب فایل پایه PDF
|
||||
overlay-pdfs.overlayFiles.label=انتخاب فایلهای ترکیبی PDF
|
||||
@@ -1196,7 +1207,7 @@ overlay-pdfs.position.background=پسزمینه
|
||||
overlay-pdfs.submit=ارسال
|
||||
|
||||
|
||||
# Split by Sections
|
||||
#split-by-sections
|
||||
split-by-sections.title=تقسیم PDF به بخشها
|
||||
split-by-sections.header=تقسیم PDF به بخشها
|
||||
split-by-sections.horizontal.label=تقسیمات افقی
|
||||
@@ -1207,7 +1218,7 @@ split-by-sections.submit=تقسیم PDF
|
||||
split-by-sections.merge=ادغام به یک PDF
|
||||
|
||||
|
||||
# Print File
|
||||
#printFile
|
||||
printFile.title=چاپ فایل
|
||||
printFile.header=چاپ فایل به چاپگر
|
||||
printFile.selectText.1=انتخاب فایل برای چاپ
|
||||
@@ -1215,7 +1226,7 @@ printFile.selectText.2=نام چاپگر را وارد کنید
|
||||
printFile.submit=چاپ
|
||||
|
||||
|
||||
# Licenses
|
||||
#licenses
|
||||
licenses.nav=مجوزها
|
||||
licenses.title=مجوزهای شخص ثالث
|
||||
licenses.header=مجوزهای شخص ثالث
|
||||
@@ -1223,7 +1234,7 @@ licenses.module=ماژول
|
||||
licenses.version=نسخه
|
||||
licenses.license=مجوز
|
||||
|
||||
# Survey
|
||||
#survey
|
||||
survey.nav=نظرسنجی
|
||||
survey.title=نظرسنجی Stirling-PDF
|
||||
survey.description=Stirling-PDF هیچ ردیابی ندارد، بنابراین ما میخواهیم از کاربران خود بشنویم تا Stirling-PDF را بهبود دهیم!
|
||||
@@ -1235,7 +1246,7 @@ survey.button=شرکت در نظرسنجی
|
||||
survey.dontShowAgain=دیگر نشان نده
|
||||
|
||||
|
||||
# Error
|
||||
#error
|
||||
error.sorry=متأسفیم برای مشکل موجود!
|
||||
error.needHelp=نیاز به کمک / یافتن مشکلی؟
|
||||
error.contactTip=اگر هنوز مشکلی دارید، دریغ نکنید که با ما تماس بگیرید. میتوانید یک تیکت در صفحه GitHub ما ارسال کنید یا از طریق Discord با ما تماس بگیرید:
|
||||
@@ -1249,14 +1260,13 @@ error.githubSubmit=GitHub - ارسال تیکت
|
||||
error.discordSubmit=Discord - ارسال پست پشتیبانی
|
||||
|
||||
|
||||
# Remove Image
|
||||
#remove-image
|
||||
removeImage.title=حذف تصویر
|
||||
removeImage.header=حذف تصویر
|
||||
removeImage.removeImage=حذف تصویر
|
||||
removeImage.submit=حذف تصویر
|
||||
|
||||
|
||||
# Split by Chapters
|
||||
splitByChapters.title=تقسیم PDF بر اساس فصلها
|
||||
splitByChapters.header=تقسیم PDF بر اساس فصلها
|
||||
splitByChapters.bookmarkLevel=سطح نشانک
|
||||
@@ -1268,20 +1278,20 @@ splitByChapters.desc.3=شامل متادیتا: اگر انتخاب شده، م
|
||||
splitByChapters.desc.4=اجازهی تکرار: اگر انتخاب شده باشد، اجازه میدهد نشانکهای متعدد در یک صفحه، فایلهای PDF جداگانه ایجاد کنند.
|
||||
splitByChapters.submit=تقسیم PDF
|
||||
|
||||
# File Chooser
|
||||
#File Chooser
|
||||
fileChooser.click=کلیک کنید
|
||||
fileChooser.or=یا
|
||||
fileChooser.dragAndDrop=بکشید و رها کنید
|
||||
fileChooser.hoveredDragAndDrop=فایل(های) خود را اینجا بکشید و رها کنید
|
||||
|
||||
# Release Notes
|
||||
#release notes
|
||||
releases.footer=نسخهها
|
||||
releases.title=یادداشتهای نسخه
|
||||
releases.header=یادداشتهای نسخه
|
||||
releases.current.version=نسخه فعلی
|
||||
releases.note=یادداشتهای نسخه فقط به زبان انگلیسی موجود است
|
||||
|
||||
# Validate Signature
|
||||
#Validate Signature
|
||||
validateSignature.title=اعتبارسنجی امضاهای PDF
|
||||
validateSignature.header=اعتبارسنجی امضای دیجیتال
|
||||
validateSignature.selectPDF=فایل PDF امضاشده را انتخاب کنید
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) sélectionnées
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=Cette fonctionnalité est aussi disponible dans la <a href="{0}">page de l'outil multifonction</a>. Allez-y pour une interface page par page améliorée et des fonctionnalités additionnelles !
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Pagina(e) selezionata(e)
|
||||
multiTool.undo=Annulla
|
||||
multiTool.redo=Rifai
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=Questo file è protetto da password. Inserisci la password:
|
||||
decrypt.cancelled=Operazione annullata per il PDF: {0}
|
||||
decrypt.noPassword=Nessuna password fornita per il PDF crittografato: {0}
|
||||
decrypt.invalidPassword=Riprova con la password corretta.
|
||||
decrypt.invalidPasswordHeader=Password errata o crittografia non supportata per il PDF: {0}
|
||||
decrypt.unexpectedError=Si è verificato un errore durante l'elaborazione del file. Riprova..
|
||||
decrypt.serverError=Errore del server durante la decrittazione: {0}
|
||||
decrypt.success=File decrittografato con successo.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=Questa funzione è disponibile anche nella nostra <a href="{0}">pagina multi-strumento</a>. Scoprila per un'interfaccia utente pagina per pagina migliorata e funzionalità aggiuntive!
|
||||
|
||||
|
||||
@@ -56,12 +56,12 @@ userNotFoundMessage=ユーザーが見つかりません。
|
||||
incorrectPasswordMessage=現在のパスワードが正しくありません。
|
||||
usernameExistsMessage=新しいユーザー名はすでに存在します。
|
||||
invalidUsernameMessage=ユーザー名が無効です。ユーザー名には文字、数字、およびそれに続く特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
|
||||
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
|
||||
invalidPasswordMessage=パスワードは空にすることはできません。また、先頭・末尾にスペースを含めることもできません。
|
||||
confirmPasswordErrorMessage=新しいパスワードと新しいパスワードの確認は一致する必要があります。
|
||||
deleteCurrentUserMessage=現在ログインしているユーザーは削除できません。
|
||||
deleteUsernameExistsMessage=そのユーザー名は存在しないため削除できません。
|
||||
downgradeCurrentUserMessage=現在のユーザーの役割をダウングレードできません
|
||||
disabledCurrentUserMessage=The current user cannot be disabled
|
||||
disabledCurrentUserMessage=現在のユーザーを無効にすることはできません
|
||||
downgradeCurrentUserLongMessage=現在のユーザーの役割をダウングレードできません。したがって、現在のユーザーは表示されません。
|
||||
userAlreadyExistsOAuthMessage=ユーザーは既にOAuth2ユーザーとして存在します。
|
||||
userAlreadyExistsWebMessage=ユーザーは既にWebユーザーとして存在します。
|
||||
@@ -76,12 +76,12 @@ donate=寄付する
|
||||
color=色
|
||||
sponsor=スポンサー
|
||||
info=Info
|
||||
pro=Pro
|
||||
page=Page
|
||||
pages=Pages
|
||||
loading=Loading...
|
||||
addToDoc=Add to Document
|
||||
reset=Reset
|
||||
pro=pro
|
||||
page=ページ
|
||||
pages=ページ
|
||||
loading=読込中...
|
||||
addToDoc=ドキュメントに追加
|
||||
reset=リセット
|
||||
|
||||
legal.privacy=プライバシーポリシー
|
||||
legal.terms=利用規約
|
||||
@@ -92,7 +92,7 @@ legal.impressum=著作権利者情報
|
||||
###############
|
||||
# Pipeline #
|
||||
###############
|
||||
pipeline.header=パイプラインメニュー (Alpha)
|
||||
pipeline.header=パイプラインメニュー (Beta)
|
||||
pipeline.uploadButton=カスタムのアップロード
|
||||
pipeline.configureButton=設定
|
||||
pipeline.defaultOption=カスタム
|
||||
@@ -117,21 +117,21 @@ pipelineOptions.validateButton=検証
|
||||
########################
|
||||
# ENTERPRISE EDITION #
|
||||
########################
|
||||
enterpriseEdition.button=Upgrade to Pro
|
||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||
enterpriseEdition.button=Proにアップグレード
|
||||
enterpriseEdition.warning=この機能はProユーザーのみが利用できます。
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Proは、YAML構成ファイルやその他のSSO機能をサポートしています。
|
||||
enterpriseEdition.ssoAdvert=より多くのユーザー管理機能をお探しですか? Stirling PDF Proをご覧ください
|
||||
|
||||
|
||||
#################
|
||||
# Analytics #
|
||||
#################
|
||||
analytics.title=Do you want make Stirling PDF better?
|
||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||
analytics.enable=Enable analytics
|
||||
analytics.disable=Disable analytics
|
||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||
analytics.title=Stirling PDFをもっと良くしたいですか?
|
||||
analytics.paragraph1=Stirling PDFでは、製品の改善に役立つ分析機能をオプトインしています。個人情報やファイルの内容を追跡することはありません。
|
||||
analytics.paragraph2=Stirling-PDFの成長を支援しユーザーをより深く理解できるように分析を有効にすることを検討してください。
|
||||
analytics.enable=分析を有効にする
|
||||
analytics.disable=分析を無効にする
|
||||
analytics.settings=config/settings.ymlファイルでアナリティクスの設定を変更できます。
|
||||
|
||||
#############
|
||||
# NAVBAR #
|
||||
@@ -142,14 +142,14 @@ navbar.language=言語
|
||||
navbar.settings=設定
|
||||
navbar.allTools=ツール
|
||||
navbar.multiTool=マルチツール
|
||||
navbar.search=Search
|
||||
navbar.search=検索
|
||||
navbar.sections.organize=整理
|
||||
navbar.sections.convertTo=PDFへ変換
|
||||
navbar.sections.convertFrom=PDFから変換
|
||||
navbar.sections.security=署名とセキュリティ
|
||||
navbar.sections.advance=アドバンスド
|
||||
navbar.sections.edit=閲覧と編集
|
||||
navbar.sections.popular=Popular
|
||||
navbar.sections.popular=人気
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -208,7 +208,7 @@ adminUserSettings.user=ユーザー
|
||||
adminUserSettings.addUser=新しいユーザを追加
|
||||
adminUserSettings.deleteUser=ユーザの削除
|
||||
adminUserSettings.confirmDeleteUser=ユーザを本当に削除しますか?
|
||||
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
|
||||
adminUserSettings.confirmChangeUserStatus=ユーザーを無効/有効にする必要がありますか?
|
||||
adminUserSettings.usernameInfo=ユーザー名には、文字、数字、および次の特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
|
||||
adminUserSettings.roles=役割
|
||||
adminUserSettings.role=役割
|
||||
@@ -247,8 +247,8 @@ database.fileNotFound=ファイルが見つかりません
|
||||
database.fileNullOrEmpty=ファイルは null または空であってはなりません
|
||||
database.failedImportFile=ファイルのインポートに失敗
|
||||
|
||||
session.expired=Your session has expired. Please refresh the page and try again.
|
||||
session.refreshPage=Refresh Page
|
||||
session.expired=セッションが期限切れです。ページを更新してもう一度お試しください。
|
||||
session.refreshPage=ページを更新
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
@@ -488,52 +488,52 @@ overlay-pdfs.tags=Overlay
|
||||
|
||||
home.split-by-sections.title=PDFをセクションで分割
|
||||
home.split-by-sections.desc=PDFの各ページを縦横に分割します。
|
||||
split-by-sections.tags=Section Split, Divide, Customize
|
||||
split-by-sections.tags=Section Split, Divide, Customize,Customise
|
||||
|
||||
home.AddStampRequest.title=PDFにスタンプを追加
|
||||
home.AddStampRequest.desc=設定した位置にテキストや画像のスタンプを追加できます
|
||||
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize
|
||||
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize,Customise
|
||||
|
||||
|
||||
home.PDFToBook.title=PDFを書籍に変換
|
||||
home.PDFToBook.desc=calibreを使用してPDFを書籍/コミック形式に変換します
|
||||
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
|
||||
|
||||
home.BookToPDF.title=PDFを書籍に変換
|
||||
home.BookToPDF.desc=calibreを使用してPDFを書籍/コミック形式に変換します
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
|
||||
|
||||
home.removeImagePdf.title=画像の削除
|
||||
home.removeImagePdf.desc=PDFから画像を削除してファイルサイズを小さくします
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||
home.splitPdfByChapters.title=PDFをチャプターごとに分割
|
||||
home.splitPdfByChapters.desc=チャプターの構造に基づいてPDFを複数のファイルに分割します
|
||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||
|
||||
home.validateSignature.title=Validate PDF Signature
|
||||
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||
home.validateSignature.title=PDF署名の検証
|
||||
home.validateSignature.desc=PDF文書のデジタル署名と証明書を検証します
|
||||
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=Replace-Invert-Color
|
||||
replace-color.header=Replace-Invert Color PDF
|
||||
home.replaceColorPdf.title=Replace and Invert Color
|
||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||
replace-color.title=色の置換・反転
|
||||
replace-color.header=PDFの色の置換・反転
|
||||
home.replaceColorPdf.title=色の置換と反転
|
||||
home.replaceColorPdf.desc=PDF内のテキストと背景の色を置き換え、PDFのフルカラーを反転してファイルサイズを縮小します。
|
||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||
replace-color.selectText.1=Replace or Invert color Options
|
||||
replace-color.selectText.2=Default(Default high contrast colors)
|
||||
replace-color.selectText.3=Custom(Customized colors)
|
||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||
replace-color.selectText.5=High contrast color options
|
||||
replace-color.selectText.6=white text on black background
|
||||
replace-color.selectText.7=Black text on white background
|
||||
replace-color.selectText.8=Yellow text on black background
|
||||
replace-color.selectText.9=Green text on black background
|
||||
replace-color.selectText.10=Choose text Color
|
||||
replace-color.selectText.11=Choose background Color
|
||||
replace-color.submit=Replace
|
||||
replace-color.selectText.1=色の置換または反転オプション
|
||||
replace-color.selectText.2=デフォルト(デフォルトの高コントラスト色)
|
||||
replace-color.selectText.3=カスタム(カスタマイズされた色)
|
||||
replace-color.selectText.4=フル反転(すべての色を反転)
|
||||
replace-color.selectText.5=高コントラストカラーオプション
|
||||
replace-color.selectText.6=黒背景に白文字
|
||||
replace-color.selectText.7=白背景に黒文字
|
||||
replace-color.selectText.8=黒背景に黄色文字
|
||||
replace-color.selectText.9=黒背景に緑文字
|
||||
replace-color.selectText.10=テキストの色を選択
|
||||
replace-color.selectText.11=背景色を選択
|
||||
replace-color.submit=置換
|
||||
|
||||
|
||||
|
||||
@@ -560,9 +560,9 @@ login.oauth2AccessDenied=アクセス拒否
|
||||
login.oauth2InvalidTokenResponse=無効なトークン応答
|
||||
login.oauth2InvalidIdToken=無効なIDトークン
|
||||
login.userIsDisabled=ユーザーは非アクティブ化されており、現在このユーザー名でのログインはブロックされています。管理者に連絡してください。
|
||||
login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.alreadyLoggedIn=すでにログインしています
|
||||
login.alreadyLoggedIn2=デバイスからログアウトしてもう一度お試しください。
|
||||
login.toManySessions=アクティブなセッションが多すぎます
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=自動塗りつぶし
|
||||
@@ -578,8 +578,8 @@ autoRedact.submitButton=送信
|
||||
|
||||
|
||||
#showJS
|
||||
showJS.title=JavaScriptを表示
|
||||
showJS.header=JavaScriptを表示
|
||||
showJS.title=Javascriptを表示
|
||||
showJS.header=Javascriptを表示
|
||||
showJS.downloadJS=Javascriptをダウンロード
|
||||
showJS.submit=表示
|
||||
|
||||
@@ -757,7 +757,7 @@ certSign.showSig=署名を表示
|
||||
certSign.reason=理由
|
||||
certSign.location=場所
|
||||
certSign.name=名前
|
||||
certSign.showLogo=Show Logo
|
||||
certSign.showLogo=ロゴを表示
|
||||
certSign.submit=PDFに署名
|
||||
|
||||
|
||||
@@ -792,9 +792,9 @@ compare.highlightColor.2=ハイライトカラー 2:
|
||||
compare.document.1=ドキュメント 1
|
||||
compare.document.2=ドキュメント 2
|
||||
compare.submit=比較
|
||||
compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced
|
||||
compare.large.file.message=One or Both of the provided documents are too large to process
|
||||
compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison.
|
||||
compare.complex.message=提供された文書の一方または両方が大きなファイルであるため、比較の精度が低下する可能性があります。
|
||||
compare.large.file.message=提供された文書の1つまたは両方が大きすぎて処理できません
|
||||
compare.no.text.message=選択したPDFの1つまたは両方にテキストコンテンツがありません。比較するには、テキストを含むPDFを選択してください。
|
||||
|
||||
#BookToPDF
|
||||
BookToPDF.title=書籍やコミックをPDFに変換
|
||||
@@ -803,8 +803,8 @@ BookToPDF.credit=calibreを使用
|
||||
BookToPDF.submit=変換
|
||||
|
||||
#PDFToBook
|
||||
PDFToBook.title=書籍をPDFに変換
|
||||
PDFToBook.header=書籍をPDFに変換
|
||||
PDFToBook.title=PDFを書籍に変換
|
||||
PDFToBook.header=PDFを書籍に変換
|
||||
PDFToBook.selectText.1=フォーマット
|
||||
PDFToBook.credit=calibreを使用
|
||||
PDFToBook.submit=変換
|
||||
@@ -817,17 +817,17 @@ sign.draw=署名を書く
|
||||
sign.text=テキスト入力
|
||||
sign.clear=クリア
|
||||
sign.add=追加
|
||||
sign.saved=Saved Signatures
|
||||
sign.save=Save Signature
|
||||
sign.personalSigs=Personal Signatures
|
||||
sign.sharedSigs=Shared Signatures
|
||||
sign.noSavedSigs=No saved signatures found
|
||||
sign.addToAll=Add to all pages
|
||||
sign.delete=Delete
|
||||
sign.first=First page
|
||||
sign.last=Last page
|
||||
sign.next=Next page
|
||||
sign.previous=Previous page
|
||||
sign.saved=保存された署名
|
||||
sign.save=署名を保存
|
||||
sign.personalSigs=個人署名
|
||||
sign.sharedSigs=共有署名
|
||||
sign.noSavedSigs=保存された署名が見つかりません
|
||||
sign.addToAll=すべてのページに追加
|
||||
sign.delete=削除
|
||||
sign.first=最初のページ
|
||||
sign.last=最後のページ
|
||||
sign.next=次のページ
|
||||
sign.previous=前のページ
|
||||
|
||||
#repair
|
||||
repair.title=修復
|
||||
@@ -944,29 +944,39 @@ pdfOrganiser.placeholder=(例:1,3,2または4-8,2,10-12または2n-1)
|
||||
multiTool.title=PDFマルチツール
|
||||
multiTool.header=PDFマルチツール
|
||||
multiTool.uploadPrompts=ファイル名
|
||||
multiTool.selectAll=Select All
|
||||
multiTool.deselectAll=Deselect All
|
||||
multiTool.selectPages=Page Select
|
||||
multiTool.selectedPages=Selected Pages
|
||||
multiTool.page=Page
|
||||
multiTool.deleteSelected=Delete Selected
|
||||
multiTool.downloadAll=Export
|
||||
multiTool.downloadSelected=Export Selected
|
||||
multiTool.selectAll=すべて選択
|
||||
multiTool.deselectAll=選択を解除
|
||||
multiTool.selectPages=ページ選択
|
||||
multiTool.selectedPages=選択したページ
|
||||
multiTool.page=ページ
|
||||
multiTool.deleteSelected=選択項目を削除
|
||||
multiTool.downloadAll=エクスポート
|
||||
multiTool.downloadSelected=選択項目をエクスポート
|
||||
|
||||
multiTool.insertPageBreak=Insert Page Break
|
||||
multiTool.addFile=Add File
|
||||
multiTool.rotateLeft=Rotate Left
|
||||
multiTool.rotateRight=Rotate Right
|
||||
multiTool.split=Split
|
||||
multiTool.moveLeft=Move Left
|
||||
multiTool.moveRight=Move Right
|
||||
multiTool.delete=Delete
|
||||
multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
multiTool.insertPageBreak=改ページを挿入
|
||||
multiTool.addFile=ファイルを追加
|
||||
multiTool.rotateLeft=左回転
|
||||
multiTool.rotateRight=右回転
|
||||
multiTool.split=分割
|
||||
multiTool.moveLeft=左に移動
|
||||
multiTool.moveRight=右に移動
|
||||
multiTool.delete=削除
|
||||
multiTool.dragDropMessage=選択されたページ
|
||||
multiTool.undo=元に戻す
|
||||
multiTool.redo=やり直す
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=このファイルはパスワードで保護されています。パスワードを入力してください:
|
||||
decrypt.cancelled=PDFの操作がキャンセルされました: {0}
|
||||
decrypt.noPassword=暗号化されたPDFにパスワードが指定されていません: {0}
|
||||
decrypt.invalidPassword=正しいパスワードでもう一度お試しください。
|
||||
decrypt.invalidPasswordHeader=PDFのパスワードが正しくないか、暗号化がサポートされていません: {0}
|
||||
decrypt.unexpectedError=ファイルの処理中にエラーが発生しました。もう一度お試しください。
|
||||
decrypt.serverError=復号化中にサーバーエラーが発生しました: {0}
|
||||
decrypt.success=ファイルの暗号化が正常に完了しました。
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
multiTool-advert.message=この機能は、<a href="{0}">マルチツール</a>でもご利用いただけます。強化されたページごとのUIと追加機能についてはこちらをご覧ください。
|
||||
|
||||
#view pdf
|
||||
viewPdf.title=PDFを表示
|
||||
@@ -1122,8 +1132,8 @@ pdfToPDFA.header=PDFをPDF/Aに変換
|
||||
pdfToPDFA.credit=本サービスはPDF/Aの変換にqpdfを使用しています。
|
||||
pdfToPDFA.submit=変換
|
||||
pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=PDF にはデジタル署名が含まれています。これは次の手順で削除されます。
|
||||
pdfToPDFA.outputFormat=出力形式
|
||||
pdfToPDFA.pdfWithDigitalSignature=PDFにはデジタル署名が含まれています。これは次の手順で削除されます。
|
||||
|
||||
|
||||
#PDFToWord
|
||||
@@ -1228,8 +1238,8 @@ licenses.license=ライセンス
|
||||
survey.nav=アンケート
|
||||
survey.title=Stirling-PDFのアンケート
|
||||
survey.description=Stirling-PDFには追跡機能がないため、Stirling-PDFをより良くするために皆様の意見を聞かせてください!
|
||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||
survey.changes2=With these changes we are getting paid business support and funding
|
||||
survey.changes=Stirling-PDFは前回の調査から変更されました。詳細についてはこちらのブログ投稿をご覧ください。
|
||||
survey.changes2=これらの変更により私たちは有償のビジネスサポートと資金援助を受けています
|
||||
survey.please=アンケートにご協力ください!
|
||||
survey.disabled=(アンケートのポップアップは、次の更新では無効になりますが、ページの下部に表示されます。)
|
||||
survey.button=アンケートに答える
|
||||
@@ -1257,61 +1267,61 @@ removeImage.removeImage=画像の削除
|
||||
removeImage.submit=画像を削除
|
||||
|
||||
|
||||
splitByChapters.title=Split PDF by Chapters
|
||||
splitByChapters.header=Split PDF by Chapters
|
||||
splitByChapters.bookmarkLevel=Bookmark Level
|
||||
splitByChapters.includeMetadata=Include Metadata
|
||||
splitByChapters.allowDuplicates=Allow Duplicates
|
||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||
splitByChapters.submit=Split PDF
|
||||
splitByChapters.title=PDFをチャプターごとに分割
|
||||
splitByChapters.header=PDFをチャプターごとに分割
|
||||
splitByChapters.bookmarkLevel=ブックマークレベル
|
||||
splitByChapters.includeMetadata=メタデータを含める
|
||||
splitByChapters.allowDuplicates=重複を許可する
|
||||
splitByChapters.desc.1=このツールは、チャプター構造に基づいてPDFファイルを複数のPDFに分割します。
|
||||
splitByChapters.desc.2=ブックマークレベル:分割に使用するブックマークのレベルを選択します(最上位レベルの場合は0、第2レベルの場合は1など)。
|
||||
splitByChapters.desc.3=メタデータを含める:チェックすると、元のPDFのメタデータが各分割PDFに含まれます。
|
||||
splitByChapters.desc.4=重複を許可:チェックすると同じページ上の複数のブックマークから個別のPDFを作成できます。
|
||||
splitByChapters.submit=PDFを分割
|
||||
|
||||
#File Chooser
|
||||
fileChooser.click=Click
|
||||
fileChooser.or=or
|
||||
fileChooser.dragAndDrop=Drag & Drop
|
||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||
fileChooser.click=クリック
|
||||
fileChooser.or=または
|
||||
fileChooser.dragAndDrop=ドラッグ&ドロップ
|
||||
fileChooser.hoveredDragAndDrop=ファイルをここにドラッグ&ドロップ
|
||||
|
||||
#release notes
|
||||
releases.footer=Releases
|
||||
releases.title=Release Notes
|
||||
releases.header=Release Notes
|
||||
releases.current.version=Current Release
|
||||
releases.note=Release notes are only available in English
|
||||
releases.footer=リリース
|
||||
releases.title=リリースノート
|
||||
releases.header=リリースノート
|
||||
releases.current.version=現在のリリース
|
||||
releases.note=リリースノートは英語でのみで提供されています
|
||||
|
||||
#Validate Signature
|
||||
validateSignature.title=Validate PDF Signatures
|
||||
validateSignature.header=Validate Digital Signatures
|
||||
validateSignature.selectPDF=Select signed PDF file
|
||||
validateSignature.submit=Validate Signatures
|
||||
validateSignature.results=Validation Results
|
||||
validateSignature.status=Status
|
||||
validateSignature.signer=Signer
|
||||
validateSignature.date=Date
|
||||
validateSignature.reason=Reason
|
||||
validateSignature.location=Location
|
||||
validateSignature.noSignatures=No digital signatures found in this document
|
||||
validateSignature.status.valid=Valid
|
||||
validateSignature.status.invalid=Invalid
|
||||
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||
validateSignature.cert.expired=Certificate has expired
|
||||
validateSignature.cert.revoked=Certificate has been revoked
|
||||
validateSignature.signature.info=Signature Information
|
||||
validateSignature.signature=Signature
|
||||
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||
validateSignature.cert.info=Certificate Details
|
||||
validateSignature.cert.issuer=Issuer
|
||||
validateSignature.cert.subject=Subject
|
||||
validateSignature.cert.serialNumber=Serial Number
|
||||
validateSignature.cert.validFrom=Valid From
|
||||
validateSignature.cert.validUntil=Valid Until
|
||||
validateSignature.cert.algorithm=Algorithm
|
||||
validateSignature.cert.keySize=Key Size
|
||||
validateSignature.cert.version=Version
|
||||
validateSignature.cert.keyUsage=Key Usage
|
||||
validateSignature.cert.selfSigned=Self-Signed
|
||||
validateSignature.cert.bits=bits
|
||||
validateSignature.title=PDF署名の検証
|
||||
validateSignature.header=デジタル署名の検証
|
||||
validateSignature.selectPDF=署名済みPDFファイルを選択
|
||||
validateSignature.submit=署名の検証
|
||||
validateSignature.results=検証結果
|
||||
validateSignature.status=状態
|
||||
validateSignature.signer=署名者
|
||||
validateSignature.date=日付
|
||||
validateSignature.reason=理由
|
||||
validateSignature.location=場所
|
||||
validateSignature.noSignatures=この文書にはデジタル署名が見つかりません
|
||||
validateSignature.status.valid=有効
|
||||
validateSignature.status.invalid=無効
|
||||
validateSignature.chain.invalid=証明書チェーンの検証に失敗しました - 署名者の身元を確認できません
|
||||
validateSignature.trust.invalid=証明書が信頼ストアにありません - ソースを検証できません
|
||||
validateSignature.cert.expired=証明書の有効期限が切れています
|
||||
validateSignature.cert.revoked=証明書は取り消されました
|
||||
validateSignature.signature.info=署名情報
|
||||
validateSignature.signature=署名
|
||||
validateSignature.signature.mathValid=署名は数学的には有効ですが:
|
||||
validateSignature.selectCustomCert=カスタム証明書ファイル X.509 (オプション)
|
||||
validateSignature.cert.info=証明書の詳細
|
||||
validateSignature.cert.issuer=発行者
|
||||
validateSignature.cert.subject=主題
|
||||
validateSignature.cert.serialNumber=シリアルナンバー
|
||||
validateSignature.cert.validFrom=有効開始日
|
||||
validateSignature.cert.validUntil=有効期限
|
||||
validateSignature.cert.algorithm=アルゴリズム
|
||||
validateSignature.cert.keySize=キーサイズ
|
||||
validateSignature.cert.version=バージョン
|
||||
validateSignature.cert.keyUsage=キーの使用法
|
||||
validateSignature.cert.selfSigned=自己署名
|
||||
validateSignature.cert.bits=ビット
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Página(s) Selecionadas
|
||||
multiTool.undo=Desfazer
|
||||
multiTool.redo=Refazer
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=Esta função também está disponível em <a href="{0}">Multiferramentas de PDF</a>. Com uma interface mais completa e funções adicionais.
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
||||
multiTool.undo=Undo
|
||||
multiTool.redo=Redo
|
||||
|
||||
#decrypt
|
||||
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||
decrypt.invalidPassword=Please try again with the correct password.
|
||||
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||
decrypt.serverError=Server error while decrypting: {0}
|
||||
decrypt.success=File decrypted successfully.
|
||||
|
||||
#multiTool-advert
|
||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
security:
|
||||
enableLogin: false # set to 'true' to enable login
|
||||
csrfDisabled: true # set to 'true' to disable CSRF protection (not recommended for production)
|
||||
csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production)
|
||||
loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1
|
||||
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
|
||||
loginMethod: all # Accepts values like 'all' and 'normal'(only Login with Username/Password), 'oauth2'(only Login with OAuth2) or 'saml2'(only Login with SAML2)
|
||||
@@ -102,7 +102,8 @@ metrics:
|
||||
AutomaticallyGenerated:
|
||||
key: example
|
||||
UUID: example
|
||||
|
||||
appVersion: 0.35.0
|
||||
|
||||
processExecutor:
|
||||
sessionLimit: # Process executor instances limits
|
||||
libreOfficeSessionLimit: 1
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
overflow: hidden;
|
||||
margin: -20px;
|
||||
padding: 20px;
|
||||
box-sizing:content-box;
|
||||
}
|
||||
|
||||
.feature-group-container.animated-group {
|
||||
|
||||
BIN
src/main/resources/static/favicon.icns
Normal file
BIN
src/main/resources/static/favicon.icns
Normal file
Binary file not shown.
@@ -268,7 +268,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
const parent = header.parentNode;
|
||||
const container = header.parentNode.querySelector(".feature-group-container");
|
||||
if (parent.id !== "groupFavorites") {
|
||||
container.style.maxHeight = container.clientHeight + "px";
|
||||
container.style.maxHeight = container.scrollHeight + "px";
|
||||
}
|
||||
header.onclick = () => {
|
||||
expandCollapseToggle(parent);
|
||||
|
||||
Reference in New Issue
Block a user