Compare commits
5 Commits
bug/rememb
...
v0.33.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03529567ba | ||
|
|
781a52c759 | ||
|
|
be2c103065 | ||
|
|
80fd2eff5f | ||
|
|
65abfd9c7a |
26
README.md
26
README.md
@@ -190,29 +190,29 @@ Stirling-PDF currently supports 36 languages!
|
||||
|
||||
| Language | Progress |
|
||||
| -------------------------------------------- | -------------------------------------- |
|
||||
| Arabic (العربية) (ar_AR) |  |
|
||||
| Arabic (العربية) (ar_AR) |  |
|
||||
| Basque (Euskara) (eu_ES) |  |
|
||||
| Bulgarian (Български) (bg_BG) |  |
|
||||
| Bulgarian (Български) (bg_BG) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Croatian (Hrvatski) (hr_HR) |  |
|
||||
| Czech (Česky) (cs_CZ) |  |
|
||||
| Danish (Dansk) (da_DK) |  |
|
||||
| Czech (Česky) (cs_CZ) |  |
|
||||
| Danish (Dansk) (da_DK) |  |
|
||||
| Dutch (Nederlands) (nl_NL) |  |
|
||||
| English (English) (en_GB) |  |
|
||||
| English (US) (en_US) |  |
|
||||
| French (Français) (fr_FR) |  |
|
||||
| German (Deutsch) (de_DE) |  |
|
||||
| Greek (Ελληνικά) (el_GR) |  |
|
||||
| German (Deutsch) (de_DE) |  |
|
||||
| Greek (Ελληνικά) (el_GR) |  |
|
||||
| Hindi (हिंदी) (hi_IN) |  |
|
||||
| Hungarian (Magyar) (hu_HU) |  |
|
||||
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
||||
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
||||
| Irish (Gaeilge) (ga_IE) |  |
|
||||
| Italian (Italiano) (it_IT) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Norwegian (Norsk) (no_NB) |  |
|
||||
| Italian (Italiano) (it_IT) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Norwegian (Norsk) (no_NB) |  |
|
||||
| Polish (Polski) (pl_PL) |  |
|
||||
| Portuguese (Português) (pt_PT) |  |
|
||||
| Portuguese (Português) (pt_PT) |  |
|
||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||
| Romanian (Română) (ro_RO) |  |
|
||||
| Russian (Русский) (ru_RU) |  |
|
||||
@@ -225,7 +225,7 @@ Stirling-PDF currently supports 36 languages!
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||
|
||||
## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ ext {
|
||||
}
|
||||
|
||||
group = "stirling.software"
|
||||
version = "0.32.0"
|
||||
version = "0.33.0"
|
||||
|
||||
java {
|
||||
// 17 is lowest but we support and recommend 21
|
||||
|
||||
@@ -32,12 +32,12 @@ ignore = [
|
||||
ignore = [
|
||||
'AddStampRequest.alphabet',
|
||||
'AddStampRequest.position',
|
||||
'home.pipeline.title'
|
||||
'PDFToBook.selectText.1',
|
||||
'PDFToText.tags',
|
||||
'addPageNumbers.selectText.3',
|
||||
'alphabet',
|
||||
'certSign.name',
|
||||
'home.pipeline.title',
|
||||
'language.direction',
|
||||
'licenses.version',
|
||||
'pipeline.title',
|
||||
@@ -46,7 +46,6 @@ ignore = [
|
||||
'sponsor',
|
||||
'text',
|
||||
'watermark.type.1',
|
||||
'certSign.name',
|
||||
]
|
||||
|
||||
[el_GR]
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
package stirling.software.SPDF.config.security.saml2;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.Scanner;
|
||||
|
||||
import org.bouncycastle.util.io.pem.PemObject;
|
||||
import org.bouncycastle.util.io.pem.PemReader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class CertificateUtils {
|
||||
|
||||
public static X509Certificate readCertificate(Resource certificateResource) throws Exception {
|
||||
@@ -46,84 +39,4 @@ public class CertificateUtils {
|
||||
.generatePrivate(new PKCS8EncodedKeySpec(decodedKey));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static X509Certificate getIdPCertificate(Resource certificateResource) throws Exception {
|
||||
|
||||
if (certificateResource instanceof UrlResource) {
|
||||
return extractCertificateFromMetadata(certificateResource);
|
||||
} else {
|
||||
// Treat as file resource
|
||||
return readCertificate(certificateResource);
|
||||
}
|
||||
}
|
||||
|
||||
private static X509Certificate extractCertificateFromMetadata(Resource metadataResource) throws Exception {
|
||||
log.info("Attempting to extract certificate from metadata resource: {}", metadataResource.getDescription());
|
||||
|
||||
try (InputStream is = metadataResource.getInputStream()) {
|
||||
String content = new String(is.readAllBytes(), StandardCharsets.UTF_8);
|
||||
log.info("Retrieved metadata content, length: {}", content.length());
|
||||
|
||||
// Find the certificate data
|
||||
int startIndex = content.indexOf("<ds:X509Certificate>");
|
||||
int endIndex = content.indexOf("</ds:X509Certificate>");
|
||||
|
||||
if (startIndex == -1 || endIndex == -1) {
|
||||
log.error("Certificate tags not found in metadata");
|
||||
throw new Exception("Certificate tags not found in metadata");
|
||||
}
|
||||
|
||||
// Extract certificate data
|
||||
String certData = content.substring(
|
||||
startIndex + "<ds:X509Certificate>".length(),
|
||||
endIndex
|
||||
).trim();
|
||||
|
||||
log.info("Found certificate data, length: {}", certData.length());
|
||||
|
||||
// Remove any whitespace and newlines from cert data
|
||||
certData = certData.replaceAll("\\s+", "");
|
||||
|
||||
// Reconstruct PEM format with proper line breaks
|
||||
StringBuilder pemBuilder = new StringBuilder();
|
||||
pemBuilder.append("-----BEGIN CERTIFICATE-----\n");
|
||||
|
||||
// Insert line breaks every 64 characters
|
||||
int lineLength = 64;
|
||||
for (int i = 0; i < certData.length(); i += lineLength) {
|
||||
int end = Math.min(i + lineLength, certData.length());
|
||||
pemBuilder.append(certData, i, end).append('\n');
|
||||
}
|
||||
|
||||
pemBuilder.append("-----END CERTIFICATE-----");
|
||||
String pemCert = pemBuilder.toString();
|
||||
|
||||
log.debug("Reconstructed PEM certificate:\n{}", pemCert);
|
||||
|
||||
try {
|
||||
ByteArrayInputStream pemStream = new ByteArrayInputStream(pemCert.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
X509Certificate cert = (X509Certificate) cf.generateCertificate(pemStream);
|
||||
|
||||
log.info("Successfully parsed certificate. Subject: {}", cert.getSubjectX500Principal());
|
||||
|
||||
// Optional: check validity dates
|
||||
cert.checkValidity(); // Throws CertificateExpiredException if expired
|
||||
log.info("Certificate is valid (not expired)");
|
||||
|
||||
return cert;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to parse certificate", e);
|
||||
throw new Exception("Failed to parse X509 certificate from metadata", e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error processing metadata resource", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -16,35 +16,23 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@Slf4j
|
||||
public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailure(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
AuthenticationException exception)
|
||||
throws IOException, ServletException {
|
||||
|
||||
if (exception instanceof Saml2AuthenticationException saml2Exception) {
|
||||
Saml2Error error = saml2Exception.getSaml2Error();
|
||||
|
||||
// Log detailed information about the SAML error
|
||||
log.error("SAML Authentication failed with error code: {}", error.getErrorCode());
|
||||
log.error("Error description: {}", error.getDescription());
|
||||
|
||||
// Redirect to login with specific error code
|
||||
getRedirectStrategy()
|
||||
.sendRedirect(request, response, "/login?erroroauth=" + error.getErrorCode());
|
||||
} else if (exception instanceof ProviderNotFoundException) {
|
||||
log.error("Authentication failed: No authentication provider found");
|
||||
|
||||
getRedirectStrategy()
|
||||
.sendRedirect(
|
||||
request,
|
||||
response,
|
||||
"/login?erroroauth=not_authentication_provider_found");
|
||||
} else {
|
||||
log.error("Unknown AuthenticationException: {}", exception.getMessage());
|
||||
getRedirectStrategy().sendRedirect(request, response, "/login?erroroauth=unknown_error");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailure(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
AuthenticationException exception)
|
||||
throws IOException, ServletException {
|
||||
if (exception instanceof Saml2AuthenticationException) {
|
||||
Saml2Error error = ((Saml2AuthenticationException) exception).getSaml2Error();
|
||||
getRedirectStrategy()
|
||||
.sendRedirect(request, response, "/login?erroroauth=" + error.getErrorCode());
|
||||
} else if (exception instanceof ProviderNotFoundException) {
|
||||
getRedirectStrategy()
|
||||
.sendRedirect(
|
||||
request,
|
||||
response,
|
||||
"/login?erroroauth=not_authentication_provider_found");
|
||||
}
|
||||
log.error("AuthenticationException: " + exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
package stirling.software.SPDF.config.security.saml2;
|
||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.saml2.core.Saml2ErrorCodes;
|
||||
|
||||
public class LoggingSamlAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LoggingSamlAuthenticationProvider.class);
|
||||
private final OpenSaml4AuthenticationProvider delegate;
|
||||
|
||||
public LoggingSamlAuthenticationProvider(OpenSaml4AuthenticationProvider delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
if (authentication instanceof Saml2AuthenticationToken token) {
|
||||
String samlResponse = token.getSaml2Response();
|
||||
|
||||
// Log the raw SAML response
|
||||
log.info("Raw SAML Response (Base64): {}", samlResponse);
|
||||
|
||||
// Decode and log the SAML response XML
|
||||
try {
|
||||
String decodedResponse = new String(Base64.getDecoder().decode(samlResponse), StandardCharsets.UTF_8);
|
||||
log.info("Decoded SAML Response XML:\n{}", decodedResponse);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// If decoding fails, it’s likely already plain XML
|
||||
log.warn("SAML Response appears to be different format, not Base64-encoded.");
|
||||
log.debug("SAML Response XML:\n{}", samlResponse);
|
||||
}
|
||||
// Delegate the actual authentication to the wrapped OpenSaml4AuthenticationProvider
|
||||
try {
|
||||
return delegate.authenticate(authentication);
|
||||
} catch (Saml2AuthenticationException e) {
|
||||
log.error("SAML authentication failed: {}");
|
||||
log.error("Detailed error message: {}", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
// Only support Saml2AuthenticationToken
|
||||
return Saml2AuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
@@ -31,7 +30,6 @@ import stirling.software.SPDF.model.provider.GithubProvider;
|
||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "")
|
||||
@@ -136,20 +134,44 @@ public class ApplicationProperties {
|
||||
private String privateKey;
|
||||
private String spCert;
|
||||
|
||||
public Resource getIdpMetadataUri() throws IOException {
|
||||
return GeneralUtils.filePathToResource(idpMetadataUri);
|
||||
public InputStream getIdpMetadataUri() throws IOException {
|
||||
if (idpMetadataUri.startsWith("classpath:")) {
|
||||
return new ClassPathResource(idpMetadataUri.substring("classpath".length()))
|
||||
.getInputStream();
|
||||
}
|
||||
try {
|
||||
URI uri = new URI(idpMetadataUri);
|
||||
URL url = uri.toURL();
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
return connection.getInputStream();
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IOException("Invalid URI format: " + idpMetadataUri, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Resource getSpCert() {
|
||||
return GeneralUtils.filePathToResource(spCert);
|
||||
if (spCert.startsWith("classpath:")) {
|
||||
return new ClassPathResource(spCert.substring("classpath:".length()));
|
||||
} else {
|
||||
return new FileSystemResource(spCert);
|
||||
}
|
||||
}
|
||||
|
||||
public Resource getidpCert() {
|
||||
return GeneralUtils.filePathToResource(idpCert);
|
||||
if (idpCert.startsWith("classpath:")) {
|
||||
return new ClassPathResource(idpCert.substring("classpath:".length()));
|
||||
} else {
|
||||
return new FileSystemResource(idpCert);
|
||||
}
|
||||
}
|
||||
|
||||
public Resource getPrivateKey() {
|
||||
return GeneralUtils.filePathToResource(privateKey);
|
||||
if (privateKey.startsWith("classpath:")) {
|
||||
return new ClassPathResource(privateKey.substring("classpath:".length()));
|
||||
} else {
|
||||
return new FileSystemResource(privateKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,10 +29,6 @@ import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
|
||||
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.fathzer.soft.javaluator.DoubleEvaluator;
|
||||
@@ -353,23 +349,4 @@ public class GeneralUtils {
|
||||
return "GenericID";
|
||||
}
|
||||
}
|
||||
|
||||
public static Resource filePathToResource(String resourceFile) {
|
||||
if (resourceFile == null) {
|
||||
throw new IllegalStateException("file is not configured");
|
||||
}
|
||||
|
||||
if (resourceFile.startsWith("classpath:")) {
|
||||
return new ClassPathResource(resourceFile.substring("classpath:".length()));
|
||||
} else if (resourceFile.startsWith("http://") || resourceFile.startsWith("https://")) {
|
||||
try {
|
||||
return new UrlResource(resourceFile);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create URL resource: " + resourceFile, e);
|
||||
}
|
||||
} else {
|
||||
return new FileSystemResource(resourceFile);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ page=صفحة
|
||||
pages=صفحات
|
||||
loading=جارٍ التحميل...
|
||||
addToDoc=إضافة إلى المستند
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=سياسة الخصوصية
|
||||
legal.terms=شروط الاستخدام
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Страница
|
||||
pages=Страници
|
||||
loading=Loading...
|
||||
addToDoc=Add to Document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Политика за поверителност
|
||||
legal.terms=Правила и условия
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Pàgina
|
||||
pages=Pàgines
|
||||
loading=Carregant...
|
||||
addToDoc=Afegeix al document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Política de Privacitat
|
||||
legal.terms=Termes i condicions
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Strana
|
||||
pages=Strany
|
||||
loading=Načítání...
|
||||
addToDoc=Přidat do dokumentu
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Politika soukromí
|
||||
legal.terms=Podmínky použití
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Sidenummer
|
||||
pages=Sideantal
|
||||
loading=Laster...
|
||||
addToDoc=Tilføj til Dokument
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Vilkår og betingelser
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Seite
|
||||
pages=Seiten
|
||||
loading=Laden...
|
||||
addToDoc=In Dokument hinzufügen
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Datenschutz
|
||||
legal.terms=AGB
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Σελίδα
|
||||
pages=Σελίδες
|
||||
loading=Φόρτωση...
|
||||
addToDoc=Πρόσθεση στο Εκπομπώματο
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Πολιτική Προνομίους
|
||||
legal.terms=Φράσεις Υποχρεωτικότητας
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Page
|
||||
pages=Pages
|
||||
loading=Loading...
|
||||
addToDoc=Add to Document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Página
|
||||
pages=Páginas
|
||||
loading=Cargando...
|
||||
addToDoc=Agregar al Documento
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Política de Privacidad
|
||||
legal.terms=Términos y Condiciones
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Page
|
||||
pages=Pages
|
||||
loading=Loading...
|
||||
addToDoc=Add to Document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Page
|
||||
pages=Pages
|
||||
loading=Chargement...
|
||||
addToDoc=Ajouter au Document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Politique de Confidentialité
|
||||
legal.terms=Conditions Générales
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Page
|
||||
pages=Pages
|
||||
loading=Loading...
|
||||
addToDoc=Add to Document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
|
||||
@@ -81,6 +81,7 @@ page=पृष्ठ
|
||||
pages=पृष्ठों
|
||||
loading=डालिंग...
|
||||
addToDoc=Add to Document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=गुप्तता सूचना
|
||||
legal.terms=शर्तें और प्रवाह
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Stranica
|
||||
pages=Stranice
|
||||
loading=Učitavanje...
|
||||
addToDoc=Dodaj u dokument
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Politika privatnosti
|
||||
legal.terms=Uspe sodržine
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Oldal
|
||||
pages=Oldalak
|
||||
loading=Betöltés...
|
||||
addToDoc=Hozzáadás dokumentumba
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Adatvédelmi nyilatkozat
|
||||
legal.terms=Feltételek és feltételek
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Halaman
|
||||
pages=Halaman-halaman
|
||||
loading=Mengambil data...
|
||||
addToDoc=Tambahkan ke Dokumen
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Kebijakan Privasi
|
||||
legal.terms=Syarat dan Ketentuan
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Pagina
|
||||
pages=Pagine
|
||||
loading=Caricamento...
|
||||
addToDoc=Aggiungi al documento
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Informativa sulla privacy
|
||||
legal.terms=Termini e Condizioni
|
||||
@@ -141,7 +142,7 @@ navbar.language=Lingue
|
||||
navbar.settings=Impostazioni
|
||||
navbar.allTools=Strumenti
|
||||
navbar.multiTool=Strumenti multipli
|
||||
navbar.search=Search
|
||||
navbar.search=Cerca
|
||||
navbar.sections.organize=Organizza
|
||||
navbar.sections.convertTo=Converti in PDF
|
||||
navbar.sections.convertFrom=Converti da PDF
|
||||
@@ -944,7 +945,7 @@ multiTool.downloadAll=Esporta
|
||||
multiTool.downloadSelected=Esporta selezionata
|
||||
|
||||
#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=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!
|
||||
|
||||
#view pdf
|
||||
viewPdf.title=Visualizza PDF
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Page
|
||||
pages=Pages
|
||||
loading=Loading...
|
||||
addToDoc=Add to Document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=プライバシーポリシー
|
||||
legal.terms=利用規約
|
||||
|
||||
@@ -81,6 +81,7 @@ page=페이지
|
||||
pages=페이지
|
||||
loading=로딩 중...
|
||||
addToDoc=문서에 추가
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=개인 정보 정책
|
||||
legal.terms=이용 약관
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Pagina
|
||||
pages=Pagen
|
||||
loading=Laden...
|
||||
addToDoc=Toevoegen aan document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Privacybeleid
|
||||
legal.terms=Voorwaarden van gebruik
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Page
|
||||
pages=Pages
|
||||
loading=Loading...
|
||||
addToDoc=Add to Document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Strona
|
||||
pages=Strony
|
||||
loading=Loading...
|
||||
addToDoc=Add to Document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Polityka Prywatności
|
||||
legal.terms=Zasady i Postanowienia
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Página
|
||||
pages=Páginas
|
||||
loading=Carregando...
|
||||
addToDoc=Adicionar ao Documento
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Política de Privacidade
|
||||
legal.terms=Termos e Condições
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Página
|
||||
pages=Páginas
|
||||
loading=A carregar...
|
||||
addToDoc=Adicionar ao Documento
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Política de Privacidade
|
||||
legal.terms=Termos e Condições
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Page
|
||||
pages=Pages
|
||||
loading=Loading...
|
||||
addToDoc=Add to Document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Страница
|
||||
pages=Страницы
|
||||
loading=Загрузка...
|
||||
addToDoc=Добавить в документ
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Политика конфиденциальности
|
||||
legal.terms=Условия использования
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Page
|
||||
pages=Pages
|
||||
loading=Loading...
|
||||
addToDoc=Add to Document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Page
|
||||
pages=Pages
|
||||
loading=Loading...
|
||||
addToDoc=Add to Document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Sidan
|
||||
pages=Sidor
|
||||
loading=Laddar...
|
||||
addToDoc=Lägg till i dokument
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Dataprotektionspolicy
|
||||
legal.terms=Villkor och betingelser
|
||||
|
||||
@@ -81,6 +81,7 @@ page=หน้า
|
||||
pages=หน้า
|
||||
loading=กำลังโหลด...
|
||||
addToDoc=เพิ่มเข้าสู่เอกสาร
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=นโยบายความเป็นส่วนตัว
|
||||
legal.terms=ข้อกำหนดการใช้งาน
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Page
|
||||
pages=Pages
|
||||
loading=Loading...
|
||||
addToDoc=Add to Document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Gizlilik Politikası
|
||||
legal.terms=Şartlar ve koşullar
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Page
|
||||
pages=Pages
|
||||
loading=Loading...
|
||||
addToDoc=Add to Document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Page
|
||||
pages=Pages
|
||||
loading=Loading...
|
||||
addToDoc=Add to Document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
|
||||
@@ -81,6 +81,7 @@ page=Page
|
||||
pages=Pages
|
||||
loading=Loading...
|
||||
addToDoc=Add to Document
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
|
||||
@@ -81,6 +81,7 @@ page=頁面
|
||||
pages=頁面
|
||||
loading=載入中...
|
||||
addToDoc=新增至文件
|
||||
reset=Reset
|
||||
|
||||
legal.privacy=隱私權政策
|
||||
legal.terms=使用條款
|
||||
|
||||
@@ -96,71 +96,20 @@
|
||||
});
|
||||
});
|
||||
|
||||
function getPDFPageCount(file) {
|
||||
try {
|
||||
if (file.type !== 'application/pdf') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create a URL for the file
|
||||
const url = URL.createObjectURL(file);
|
||||
|
||||
try {
|
||||
// Ensure the worker is properly set
|
||||
if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
|
||||
}
|
||||
|
||||
// Load the PDF document
|
||||
const loadingTask = pdfjsLib.getDocument(url);
|
||||
const pdf = await loadingTask.promise;
|
||||
|
||||
// Get the page count
|
||||
const pageCount = pdf.numPages;
|
||||
return pageCount;
|
||||
} finally {
|
||||
// Clean up the URL
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error counting PDF pages:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function trackFileProcessing(file, startTime, success, errorMessage = null) {
|
||||
const endTime = performance.now();
|
||||
const processingTime = endTime - startTime;
|
||||
|
||||
posthog.capture('file_processing', {
|
||||
success: success,
|
||||
file_type: file.type || 'unknown',
|
||||
file_size: file.size,
|
||||
processing_time: processingTime,
|
||||
error_message: errorMessage,
|
||||
pdf_pages: file.type === 'application/pdf' ? getPDFPageCount(file) : null
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSingleDownload(url, formData, isMulti = false, isZip = false) {
|
||||
const startTime = performance.now();
|
||||
const file = formData.get('fileInput');
|
||||
|
||||
try {
|
||||
const response = await fetch(url, { method: "POST", body: formData });
|
||||
const contentType = response.headers.get("content-type");
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
// Handle 401 Unauthorized error
|
||||
showSessionExpiredPrompt();
|
||||
trackFileProcessing(file, startTime, false, 'unauthorized');
|
||||
return;
|
||||
}
|
||||
if (contentType && contentType.includes("application/json")) {
|
||||
console.error("Throwing error banner, response was not okay");
|
||||
const jsonResponse = await handleJsonResponse(response);
|
||||
trackFileProcessing(file, startTime, false, jsonResponse?.error || 'unknown_error');
|
||||
return jsonResponse;
|
||||
return handleJsonResponse(response);
|
||||
}
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
@@ -169,8 +118,6 @@
|
||||
let filename = getFilenameFromContentDisposition(contentDisposition);
|
||||
|
||||
const blob = await response.blob();
|
||||
trackFileProcessing(file, startTime, true, null);
|
||||
|
||||
if (contentType.includes("application/pdf") || contentType.includes("image/")) {
|
||||
clearFileInput();
|
||||
return handleResponse(blob, filename, !isMulti, isZip);
|
||||
@@ -179,11 +126,9 @@
|
||||
return handleResponse(blob, filename, false, isZip);
|
||||
}
|
||||
|
||||
|
||||
} catch (error) {
|
||||
clearFileInput();
|
||||
console.error("Error in handleSingleDownload:", error);
|
||||
trackFileProcessing(file, startTime, false, error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -224,7 +169,8 @@
|
||||
if (considerViewOptions) {
|
||||
if (downloadOption === "sameWindow") {
|
||||
const url = URL.createObjectURL(blob);
|
||||
return { filename, blob, url };
|
||||
window.location.href = url;
|
||||
return;
|
||||
} else if (downloadOption === "newWindow") {
|
||||
const url = URL.createObjectURL(blob);
|
||||
window.open(url, "_blank");
|
||||
|
||||
@@ -76,7 +76,7 @@ async function checkForUpdate() {
|
||||
document.getElementById("update-btn").style.display = "block";
|
||||
}
|
||||
if (updateLink !== null) {
|
||||
document.getElementById("app-update").innerText = updateAvailable.replace("{0}", '<b>' + currentVersion + '</b>').replace("{1}", '<b>' + latestVersion + '</b>');
|
||||
document.getElementById("app-update").innerHTML = updateAvailable.replace("{0}", '<b>' + currentVersion + '</b>').replace("{1}", '<b>' + latestVersion + '</b>');
|
||||
if (updateLink.classList.contains("visually-hidden")) {
|
||||
updateLink.classList.remove("visually-hidden");
|
||||
}
|
||||
|
||||
@@ -201,7 +201,6 @@
|
||||
window.stirlingPDF.error = /*[[#{error}]]*/ "Error";
|
||||
})();
|
||||
</script>
|
||||
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
||||
<script th:src="@{'/js/downloader.js'}"></script>
|
||||
|
||||
<div class="custom-file-chooser" th:attr="data-bs-unique-id=${name}, data-bs-element-id=${name+'-input'}, data-bs-files-selected=#{filesSelected}, data-bs-pdf-prompt=#{pdfPrompt}">
|
||||
|
||||
Reference in New Issue
Block a user