mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-11-01 01:21:18 +01:00 
			
		
		
		
	Upload File Size Limit (#3334)
# Description of Changes The change this PR aims to introduce is a setting for enabling an upload file size limit. The author of the issue mentioned in this PR wanted this feature as they themselves enforced a limit of 50MB file sizes on their NGINX configuration. This was implemented by adding an entry to the [settings.yml.template](e52fc0e478/src/main/resources/settings.yml.template) file. This entry has two sub-configurations in which you declare if the application should enable upload file size limiting and then you declare the limit itself. For this to be available in code, a new field in the [System](e52fc0e478/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java (L280)) class was added, one named `uploadLimit`. After that, inside the [AppConfig](url) class, a new thymeleaf bean was created, one called `uploadLimit`. This bean takes the values available in the `System` class and creates a `long` value representing the limit value. This value is interpreted as non-existent if it is `0`, otherwise it is the value in `bytes` of the upload limit. In order to make this value available in the [common.html](e52fc0e478/src/main/resources/templates/fragments/common.html) file, where the submitFile form is imported from, a new controller [GlobalUploadLimitWebController](e52fc0e478/src/main/java/stirling/software/SPDF/controller/web/GlobalUploadLimitWebController.java) was created. This controller has the tag `ControllerAdvice` so that every controller has the `ModelAttributes` defined within it. I am not sure if this was a good approach but upon first investigations, I couldn't find another method to make these attributes available in every Controller, or template. If there is already a place like this in the code with this specific purpose, please let me know so I can fix it. After making these attributes available, I updated the code in `common.html`to now display the upload limit if it is defined. This was done with localization in mind. Lastly, the [downloader.js](e52fc0e478/src/main/resources/static/js/downloader.js) and [fileInput.js](e52fc0e478/src/main/resources/static/js/fileInput.js) files to include logic to enforce the upload limit if it is defined. The UI updates, when the upload limit is defined, are as so: <img width="708" alt="image" src="https://github.com/user-attachments/assets/4852fa10-2ec3-45cb-83e6-41a102f256d4" /> When the limit is disabled, the page looks exactly as it did before any implementation: <img width="707" alt="image" src="https://github.com/user-attachments/assets/21e5e810-ffdc-4a99-a16d-491aea103709" />\\ Thank you. Closes #2903 --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [x] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [x] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [x] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [x] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									e24e420142
								
							
						
					
					
						commit
						1c655f0ba0
					
				@ -20,6 +20,8 @@ import org.springframework.core.io.Resource;
 | 
				
			|||||||
import org.springframework.core.io.ResourceLoader;
 | 
					import org.springframework.core.io.ResourceLoader;
 | 
				
			||||||
import org.thymeleaf.spring6.SpringTemplateEngine;
 | 
					import org.thymeleaf.spring6.SpringTemplateEngine;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.posthog.java.shaded.kotlin.text.Regex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import stirling.software.SPDF.model.ApplicationProperties;
 | 
					import stirling.software.SPDF.model.ApplicationProperties;
 | 
				
			||||||
@ -107,6 +109,33 @@ public class AppConfig {
 | 
				
			|||||||
        return (rateLimit != null) ? Boolean.valueOf(rateLimit) : false;
 | 
					        return (rateLimit != null) ? Boolean.valueOf(rateLimit) : false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Bean(name = "uploadLimit")
 | 
				
			||||||
 | 
					    public long uploadLimit() {
 | 
				
			||||||
 | 
					        String maxUploadSize =
 | 
				
			||||||
 | 
					                applicationProperties.getSystem().getFileUploadLimit() != null
 | 
				
			||||||
 | 
					                        ? applicationProperties.getSystem().getFileUploadLimit()
 | 
				
			||||||
 | 
					                        : "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (maxUploadSize.isEmpty()) {
 | 
				
			||||||
 | 
					            return 0;
 | 
				
			||||||
 | 
					        } else if (!new Regex("^[1-9][0-9]{0,2}[KMGkmg][Bb]$").matches(maxUploadSize)) {
 | 
				
			||||||
 | 
					            log.error(
 | 
				
			||||||
 | 
					                    "Invalid maxUploadSize format. Expected format: [1-9][0-9]{0,2}[KMGkmg][Bb], but got: {}",
 | 
				
			||||||
 | 
					                    maxUploadSize);
 | 
				
			||||||
 | 
					            return 0;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            String unit = maxUploadSize.replaceAll("[1-9][0-9]{0,2}", "").toUpperCase();
 | 
				
			||||||
 | 
					            String number = maxUploadSize.replaceAll("[KMGkmg][Bb]", "");
 | 
				
			||||||
 | 
					            long size = Long.parseLong(number);
 | 
				
			||||||
 | 
					            return switch (unit) {
 | 
				
			||||||
 | 
					                case "KB" -> size * 1024;
 | 
				
			||||||
 | 
					                case "MB" -> size * 1024 * 1024;
 | 
				
			||||||
 | 
					                case "GB" -> size * 1024 * 1024 * 1024;
 | 
				
			||||||
 | 
					                default -> 0;
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Bean(name = "RunningInDocker")
 | 
					    @Bean(name = "RunningInDocker")
 | 
				
			||||||
    public boolean runningInDocker() {
 | 
					    public boolean runningInDocker() {
 | 
				
			||||||
        return Files.exists(Paths.get("/.dockerenv"));
 | 
					        return Files.exists(Paths.get("/.dockerenv"));
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					package stirling.software.SPDF.controller.web;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Component;
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.ControllerAdvice;
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.ModelAttribute;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component
 | 
				
			||||||
 | 
					@ControllerAdvice
 | 
				
			||||||
 | 
					public class GlobalUploadLimitWebController {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Autowired() private long uploadLimit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @ModelAttribute("uploadLimit")
 | 
				
			||||||
 | 
					    public long populateUploadLimit() {
 | 
				
			||||||
 | 
					        return uploadLimit;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @ModelAttribute("uploadLimitReadable")
 | 
				
			||||||
 | 
					    public String populateReadableLimit() {
 | 
				
			||||||
 | 
					        return humanReadableByteCount(uploadLimit);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private String humanReadableByteCount(long bytes) {
 | 
				
			||||||
 | 
					        if (bytes < 1024) return bytes + " B";
 | 
				
			||||||
 | 
					        int exp = (int) (Math.log(bytes) / Math.log(1024));
 | 
				
			||||||
 | 
					        String pre = "KMGTPE".charAt(exp - 1) + "B";
 | 
				
			||||||
 | 
					        return String.format("%.1f %s", bytes / Math.pow(1024, exp), pre);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -290,6 +290,7 @@ public class ApplicationProperties {
 | 
				
			|||||||
        private Boolean disableSanitize;
 | 
					        private Boolean disableSanitize;
 | 
				
			||||||
        private Boolean enableUrlToPDF;
 | 
					        private Boolean enableUrlToPDF;
 | 
				
			||||||
        private CustomPaths customPaths = new CustomPaths();
 | 
					        private CustomPaths customPaths = new CustomPaths();
 | 
				
			||||||
 | 
					        private String fileUploadLimit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public boolean isAnalyticsEnabled() {
 | 
					        public boolean isAnalyticsEnabled() {
 | 
				
			||||||
            return this.getEnableAnalytics() != null && this.getEnableAnalytics();
 | 
					            return this.getEnableAnalytics() != null && this.getEnableAnalytics();
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,9 @@ multiPdfPrompt=Select PDFs (2+)
 | 
				
			|||||||
multiPdfDropPrompt=Select (or drag & drop) all PDFs you require
 | 
					multiPdfDropPrompt=Select (or drag & drop) all PDFs you require
 | 
				
			||||||
imgPrompt=Select Image(s)
 | 
					imgPrompt=Select Image(s)
 | 
				
			||||||
genericSubmit=Submit
 | 
					genericSubmit=Submit
 | 
				
			||||||
 | 
					uploadLimit=Maximum file size:
 | 
				
			||||||
 | 
					uploadLimitExceededSingular=is too large. Maximum allowed size is
 | 
				
			||||||
 | 
					uploadLimitExceededPlural=are too large. Maximum allowed size is
 | 
				
			||||||
processTimeWarning=Warning: This process can take up to a minute depending on file-size
 | 
					processTimeWarning=Warning: This process can take up to a minute depending on file-size
 | 
				
			||||||
pageOrderPrompt=Custom Page Order (Enter a comma-separated list of page numbers or Functions like 2n+1) :
 | 
					pageOrderPrompt=Custom Page Order (Enter a comma-separated list of page numbers or Functions like 2n+1) :
 | 
				
			||||||
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
 | 
					pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,9 @@ multiPdfPrompt=Select PDFs (2+)
 | 
				
			|||||||
multiPdfDropPrompt=Select (or drag & drop) all PDFs you require
 | 
					multiPdfDropPrompt=Select (or drag & drop) all PDFs you require
 | 
				
			||||||
imgPrompt=Select Image(s)
 | 
					imgPrompt=Select Image(s)
 | 
				
			||||||
genericSubmit=Submit
 | 
					genericSubmit=Submit
 | 
				
			||||||
 | 
					uploadLimit=Maximum file size:
 | 
				
			||||||
 | 
					uploadLimitExceededSingular=is too large. Maximum allowed size is
 | 
				
			||||||
 | 
					uploadLimitExceededPlural=are too large. Maximum allowed size is
 | 
				
			||||||
processTimeWarning=Warning: This process can take up to a minute depending on file-size
 | 
					processTimeWarning=Warning: This process can take up to a minute depending on file-size
 | 
				
			||||||
pageOrderPrompt=Custom Page Order (Enter a comma-separated list of page numbers or Functions like 2n+1) :
 | 
					pageOrderPrompt=Custom Page Order (Enter a comma-separated list of page numbers or Functions like 2n+1) :
 | 
				
			||||||
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
 | 
					pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,9 @@ multiPdfPrompt=Selecione PDFs (2+)
 | 
				
			|||||||
multiPdfDropPrompt=Selecione (ou arraste e solte) todos os PDFs necessários
 | 
					multiPdfDropPrompt=Selecione (ou arraste e solte) todos os PDFs necessários
 | 
				
			||||||
imgPrompt=Selecione Imagem(ns)
 | 
					imgPrompt=Selecione Imagem(ns)
 | 
				
			||||||
genericSubmit=Submeter
 | 
					genericSubmit=Submeter
 | 
				
			||||||
 | 
					uploadLimit=Tamanho máximo de ficheiro:
 | 
				
			||||||
 | 
					uploadLimitExceededSingular=é muito grande. O tamanho máximo permitido é
 | 
				
			||||||
 | 
					uploadLimitExceededPlural=são muito grandes. O tamanho máximo permitido é
 | 
				
			||||||
processTimeWarning=Aviso: Este processo pode demorar até um minuto dependendo do tamanho do ficheiro
 | 
					processTimeWarning=Aviso: Este processo pode demorar até um minuto dependendo do tamanho do ficheiro
 | 
				
			||||||
pageOrderPrompt=Ordem Personalizada de Páginas (Insira uma lista de números de página separados por vírgulas ou Funções como 2n+1):
 | 
					pageOrderPrompt=Ordem Personalizada de Páginas (Insira uma lista de números de página separados por vírgulas ou Funções como 2n+1):
 | 
				
			||||||
pageSelectionPrompt=Seleção Personalizada de Páginas (Insira uma lista de números de página separados por vírgulas 1,5,6 ou Funções como 2n+1):
 | 
					pageSelectionPrompt=Seleção Personalizada de Páginas (Insira uma lista de números de página separados por vírgulas 1,5,6 ou Funções como 2n+1):
 | 
				
			||||||
 | 
				
			|||||||
@ -110,6 +110,7 @@ system:
 | 
				
			|||||||
    operations:
 | 
					    operations:
 | 
				
			||||||
      weasyprint: '' #Defaults to /opt/venv/bin/weasyprint
 | 
					      weasyprint: '' #Defaults to /opt/venv/bin/weasyprint
 | 
				
			||||||
      unoconvert: '' #Defaults to /opt/venv/bin/unoconvert
 | 
					      unoconvert: '' #Defaults to /opt/venv/bin/unoconvert
 | 
				
			||||||
 | 
					  fileUploadLimit: '' # Defaults to "". No limit when string is empty. Set a number, between 0 and 999, followed by one of the following strings to set a limit. "KB", "MB", "GB".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ui:
 | 
					ui:
 | 
				
			||||||
  appName: '' # application's visible name
 | 
					  appName: '' # application's visible name
 | 
				
			||||||
 | 
				
			|||||||
@ -43,6 +43,20 @@
 | 
				
			|||||||
      firstErrorOccurred = false;
 | 
					      firstErrorOccurred = false;
 | 
				
			||||||
      const url = this.action;
 | 
					      const url = this.action;
 | 
				
			||||||
      let files = $('#fileInput-input')[0].files;
 | 
					      let files = $('#fileInput-input')[0].files;
 | 
				
			||||||
 | 
					      const uploadLimit = window.stirlingPDF?.uploadLimit ?? 0;
 | 
				
			||||||
 | 
					      if (uploadLimit > 0) {
 | 
				
			||||||
 | 
					        const oversizedFiles = Array.from(files).filter(f => f.size > uploadLimit);
 | 
				
			||||||
 | 
					        if (oversizedFiles.length > 0) {
 | 
				
			||||||
 | 
					          const names = oversizedFiles.map(f => `"${f.name}"`).join(', ');
 | 
				
			||||||
 | 
					          if (names.length === 1) {
 | 
				
			||||||
 | 
					            alert(`${names} ${window.stirlingPDF.uploadLimitExceededSingular} ${window.stirlingPDF.uploadLimitReadable}.`);
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            alert(`${names} ${window.stirlingPDF.uploadLimitExceededPlural} ${window.stirlingPDF.uploadLimitReadable}.`);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          files = Array.from(files).filter(f => f.size <= uploadLimit);
 | 
				
			||||||
 | 
					          if (files.length === 0) return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      const formData = new FormData(this);
 | 
					      const formData = new FormData(this);
 | 
				
			||||||
      const submitButton = document.getElementById('submitBtn');
 | 
					      const submitButton = document.getElementById('submitBtn');
 | 
				
			||||||
      const showGameBtn = document.getElementById('show-game-btn');
 | 
					      const showGameBtn = document.getElementById('show-game-btn');
 | 
				
			||||||
 | 
				
			|||||||
@ -196,6 +196,28 @@ function setupFileInput(chooser) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    await checkZipFile();
 | 
					    await checkZipFile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const uploadLimit = window.stirlingPDF?.uploadLimit ?? 0;
 | 
				
			||||||
 | 
					    if (uploadLimit > 0) {
 | 
				
			||||||
 | 
					      const oversizedFiles = allFiles.filter(f => f.size > uploadLimit);
 | 
				
			||||||
 | 
					      if (oversizedFiles.length > 0) {
 | 
				
			||||||
 | 
					        const names = oversizedFiles.map(f => `"${f.name}"`).join(', ');
 | 
				
			||||||
 | 
					        if (names.length === 1) {
 | 
				
			||||||
 | 
					          alert(`${names} ${window.stirlingPDF.uploadLimitExceededSingular} ${window.stirlingPDF.uploadLimitReadable}.`);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          alert(`${names} ${window.stirlingPDF.uploadLimitExceededPlural} ${window.stirlingPDF.uploadLimitReadable}.`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        allFiles = allFiles.filter(f => f.size <= uploadLimit);
 | 
				
			||||||
 | 
					        const dataTransfer = new DataTransfer();
 | 
				
			||||||
 | 
					        allFiles.forEach(f => dataTransfer.items.add(f));
 | 
				
			||||||
 | 
					        input.files = dataTransfer.files;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (allFiles.length === 0) {
 | 
				
			||||||
 | 
					          inputContainer.querySelector('#fileInputText').innerHTML = originalText;
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    allFiles = await Promise.all(
 | 
					    allFiles = await Promise.all(
 | 
				
			||||||
      allFiles.map(async (file) => {
 | 
					      allFiles.map(async (file) => {
 | 
				
			||||||
        let decryptedFile = file;
 | 
					        let decryptedFile = file;
 | 
				
			||||||
 | 
				
			|||||||
@ -241,6 +241,10 @@
 | 
				
			|||||||
        window.stirlingPDF.sessionExpired = /*[[#{session.expired}]]*/ '';
 | 
					        window.stirlingPDF.sessionExpired = /*[[#{session.expired}]]*/ '';
 | 
				
			||||||
        window.stirlingPDF.refreshPage = /*[[#{session.refreshPage}]]*/ 'Refresh Page';
 | 
					        window.stirlingPDF.refreshPage = /*[[#{session.refreshPage}]]*/ 'Refresh Page';
 | 
				
			||||||
        window.stirlingPDF.error = /*[[#{error}]]*/ "Error";
 | 
					        window.stirlingPDF.error = /*[[#{error}]]*/ "Error";
 | 
				
			||||||
 | 
					        window.stirlingPDF.uploadLimit = /*[[${uploadLimit}]]*/ 0;
 | 
				
			||||||
 | 
					        window.stirlingPDF.uploadLimitReadable = /*[[${uploadLimitReadable}]]*/ 'Unlimited';
 | 
				
			||||||
 | 
					        window.stirlingPDF.uploadLimitExceededSingular = /*[[#{uploadLimitExceededSingular}]]*/ 'is too large. Maximum allowed size is';
 | 
				
			||||||
 | 
					        window.stirlingPDF.uploadLimitExceededPlural = /*[[#{uploadLimitExceededPlural}]]*/ 'are too large. Maximum allowed size is';
 | 
				
			||||||
      })();
 | 
					      })();
 | 
				
			||||||
  </script>
 | 
					  </script>
 | 
				
			||||||
  <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
					  <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
				
			||||||
@ -289,8 +293,11 @@
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <div class="selected-files flex-wrap"></div>
 | 
					    <div class="selected-files flex-wrap"></div>
 | 
				
			||||||
 | 
					    <div class="text-muted small mt-0 text-end w-100" th:if="${uploadLimit != 0}">
 | 
				
			||||||
 | 
					      <span th:text="#{uploadLimit}">Maximum file size: </span>
 | 
				
			||||||
 | 
					      <span th:text="${uploadLimitReadable}"></span>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
 | 
					 | 
				
			||||||
  <div class="progressBarContainer" style="display: none; position: relative;">
 | 
					  <div class="progressBarContainer" style="display: none; position: relative;">
 | 
				
			||||||
    <div class="progress" style="height: 1rem;">
 | 
					    <div class="progress" style="height: 1rem;">
 | 
				
			||||||
      <div class="progressBar progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar"
 | 
					      <div class="progressBar progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar"
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user