From 6baf1f94c14cc4f948e6875da863323bb5502b72 Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Sat, 23 Dec 2023 17:41:48 +0800 Subject: [PATCH 1/4] Update supported languages in README to include all 24 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adjusted the README to correctly enumerate all 24 languages supported by the project. This change includes the addition of Hungarian (Magyar) and Bulgarian (Български) to the list, based on the properties files located in 'src/main/resources'. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f9b42a42..bd2c2c60 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ Note: Podman is CLI-compatible with Docker, so simply replace "docker" with "pod Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md ## Want to add your own language? -Stirling PDF currently supports 21! +Stirling PDF currently supports 24! - English (English) (en_GB) - English (US) (en_US) - Arabic (العربية) (ar_AR) @@ -170,6 +170,8 @@ Stirling PDF currently supports 21! - Turkish (Türkçe) (tr_TR) - Indonesia (Bahasa Indonesia) (id_ID) - Hindi (हिंदी) (hi_IN) +- Hungarian (Magyar) (hu_HU) +- Bulgarian (Български) (bg_BG) If you want to add your own language to Stirling-PDF please refer https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md From 2a744473f90f70e72f473ad7fc709a68b445512e Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Mon, 25 Dec 2023 00:57:55 +0800 Subject: [PATCH 2/4] Add a basic Traditional Chinese translation The svg flag file came from: Flag of the Republic of China - Taiwan - Wikipedia https://en.wikipedia.org/wiki/Taiwan#/media/File:Flag_of_the_Republic_of_China.svg --- README.md | 5 +- src/main/resources/messages_zh_TW.properties | 896 ++++++++++++++++++ src/main/resources/static/images/flags/tw.svg | 1 + .../templates/fragments/languages.html | 3 + 4 files changed, 903 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/messages_zh_TW.properties create mode 100644 src/main/resources/static/images/flags/tw.svg diff --git a/README.md b/README.md index bd2c2c60..8c50fdc6 100644 --- a/README.md +++ b/README.md @@ -147,14 +147,15 @@ Note: Podman is CLI-compatible with Docker, so simply replace "docker" with "pod Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md ## Want to add your own language? -Stirling PDF currently supports 24! +Stirling PDF currently supports 25! - English (English) (en_GB) - English (US) (en_US) - Arabic (العربية) (ar_AR) - German (Deutsch) (de_DE) - French (Français) (fr_FR) - Spanish (Español) (es_ES) -- Chinese (简体中文) (zh_CN) +- Simplified Chinese (简体中文) (zh_CN) +- Traditional Chinese (繁體中文) (zh_TW) - Catalan (Català) (ca_CA) - Italian (Italiano) (it_IT) - Swedish (Svenska) (sv_SE) diff --git a/src/main/resources/messages_zh_TW.properties b/src/main/resources/messages_zh_TW.properties new file mode 100644 index 00000000..3787f813 --- /dev/null +++ b/src/main/resources/messages_zh_TW.properties @@ -0,0 +1,896 @@ +########### +# Generic # +########### +# the direction that the language is written (ltr = left to right, rtl = right to left) +language.direction=ltr + +pdfPrompt=選擇 PDF 檔案 +multiPdfPrompt=選擇多個 PDF 檔案 +multiPdfDropPrompt=選擇(或拖放)所有需要的 PDF 檔案 +imgPrompt=選擇圖片 +genericSubmit=送出 +processTimeWarning=警告:此過程可能需要長達一分鐘,具體取決於檔案大小 +pageOrderPrompt=自訂頁面順序(輸入以逗號分隔的頁碼或函式,如 2n+1): +goToPage=前往 +true=是 +false=否 +unknown=未知 +save=儲存 +close=關閉 +filesSelected=已選擇的檔案 +noFavourites=未新增收藏 +bored=等待時覺得無聊? +alphabet=字母表 +downloadPdf=下載 PDF +text=文字 +font=字型 +selectFillter=-- 選擇 -- +pageNum=頁碼 +sizes.small=小 +sizes.medium=中 +sizes.large=大 +sizes.x-large=特大 +error.pdfPassword=PDF 檔案已加密,但未提供密碼或密碼不正確 +delete=刪除 +username=使用者名稱 +password=密碼 +welcome=歡迎 +property=屬性 +black=黑色 +white=白色 +red=紅色 +green=綠色 +blue=藍色 +custom=自訂... + +changedCredsMessage=憑證已變更! +notAuthenticatedMessage=使用者未認證。 +userNotFoundMessage=找不到使用者。 +incorrectPasswordMessage=目前密碼不正確。 +usernameExistsMessage=新使用者名稱已存在。 + + + +############# +# NAVBAR # +############# +navbar.convert=轉換 +navbar.security=安全 +navbar.other=其他 +navbar.darkmode=暗黑模式 +navbar.pageOps=頁面操作 +navbar.settings=設定 + +############# +# SETTINGS # +############# +settings.title=設定 +settings.update=有更新可用 +settings.appVersion=應用版本: +settings.downloadOption.title=選擇下載選項(對於單一檔案非壓縮下載): +settings.downloadOption.1=在同一視窗中開啟 +settings.downloadOption.2=在新視窗中開啟 +settings.downloadOption.3=下載檔案 +settings.zipThreshold=當下載的檔案數量超過時,壓縮檔案 +settings.signOut=登出 +settings.accountSettings=帳戶設定 + + + +changeCreds.title=變更憑證 +changeCreds.header=更新您的帳戶詳細資訊 +changeCreds.changeUserAndPassword=您正在使用預設的登入憑證。請輸入新的密碼(如果需要,也可以輸入使用者名稱) +changeCreds.newUsername=新使用者名稱 +changeCreds.oldPassword=目前密碼 +changeCreds.newPassword=新密碼 +changeCreds.confirmNewPassword=確認新密碼 +changeCreds.submit=送出變更 + + + +account.title=帳戶設定 +account.accountSettings=帳戶設定 +account.adminSettings=管理設定 - 檢視和新增使用者 +account.userControlSettings=使用者控制設定 +account.changeUsername=新使用者名稱 +account.changeUsername=修改使用者名稱 +account.password=確認密碼 +account.oldPassword=舊密碼 +account.newPassword=新密碼 +account.changePassword=修改密碼 +account.confirmNewPassword=確認新密碼 +account.signOut=登出 +account.yourApiKey=您的 API 金鑰 +account.syncTitle=將瀏覽器設定與帳戶同步 +account.settingsCompare=設定比較: +account.property=屬性 +account.webBrowserSettings=網頁瀏覽器設定 +account.syncToBrowser=同步帳戶 -> 瀏覽器 +account.syncToAccount=同步帳戶 <- 瀏覽器 + + +adminUserSettings.title=使用者控制設定 +adminUserSettings.header=管理使用者控制設定 +adminUserSettings.admin=管理員 +adminUserSettings.user=使用者 +adminUserSettings.addUser=新增使用者 +adminUserSettings.roles=角色 +adminUserSettings.role=角色 +adminUserSettings.actions=操作 +adminUserSettings.apiUser=受限制的 API 使用者 +adminUserSettings.webOnlyUser=僅使用網頁的使用者 +adminUserSettings.demoUser=示範用途的使用者(無自訂設定) +adminUserSettings.forceChange = 強制使用者在登入時修改使用者名稱/密碼 +adminUserSettings.submit=儲存 + +############# +# HOME-PAGE # +############# +home.desc=您的本地主機一站式 PDF 需求解決方案。 +home.searchBar=搜尋功能... + + +home.viewPdf.title=檢視 PDF +home.viewPdf.desc=檢視、註釋、新增文字或圖片 +viewPdf.tags=檢視,閱讀,註釋,文字,圖片 + +home.multiTool.title=PDF 多工具 +home.multiTool.desc=合併、旋轉、重新排列和移除頁面 +multiTool.tags=多工具,多操作,UI,點選拖動,前端,客戶端,互動,可互動,移動 + +home.merge.title=合併 +home.merge.desc=輕鬆將多個 PDF 合併為一個。 +merge.tags=合併,頁面操作,後端,伺服器端 + +home.split.title=分割 +home.split.desc=將 PDF 分割為多個文件 +split.tags=頁面操作,劃分,多頁,剪下,伺服器端 + +home.rotate.title=旋轉 +home.rotate.desc=輕鬆旋轉您的 PDF。 +rotate.tags=伺服器端 + + +home.imageToPdf.title=圖片轉 PDF +home.imageToPdf.desc=將圖片(PNG、JPEG、GIF)轉換為 PDF。 +imageToPdf.tags=轉換,img,jpg,圖片,照片 + +home.pdfToImage.title=PDF 轉圖片 +home.pdfToImage.desc=將 PDF 轉換為圖片。(PNG、JPEG、GIF) +pdfToImage.tags=轉換,img,jpg,圖片,照片 + +home.pdfOrganiser.title=整理 +home.pdfOrganiser.desc=以任何順序移除/重新排列頁面 +pdfOrganiser.tags=雙面,偶數,奇數,排序,移動 + + +home.addImage.title=新增圖片 +home.addImage.desc=在 PDF 的指定位置新增圖片 +addImage.tags=img,jpg,圖片,照片 + +home.watermark.title=新增浮水印 +home.watermark.desc=在您的 PDF 檔案中新增自訂浮水印。 +watermark.tags=文字,重複,標籤,自有,版權,商標,img,jpg,圖片,照片 + +home.permissions.title=修改權限 +home.permissions.desc=修改您的 PDF 檔案權限 +permissions.tags=讀取,寫入,編輯,列印 + + +home.removePages.title=移除 +home.removePages.desc=從您的 PDF 檔案中刪除不需要的頁面。 +removePages.tags=移除頁面,刪除頁面 + +home.addPassword.title=新增密碼 +home.addPassword.desc=用密碼加密您的 PDF 檔案。 +addPassword.tags=安全,安全性 + +home.removePassword.title=移除密碼 +home.removePassword.desc=從您的 PDF 檔案中移除密碼保護。 +removePassword.tags=安全,解密,安全性,取消密碼,刪除密碼 + +home.compressPdfs.title=壓縮 +home.compressPdfs.desc=壓縮 PDF 以減少其檔案大小。 +compressPdfs.tags=壓縮,小,微小 + + +home.changeMetadata.title=變更中繼資料 +home.changeMetadata.desc=從 PDF 檔案中變更/移除/新增中繼資料 +changeMetadata.tags==標題,作者,日期,建立,時間,出版商,製作人,統計 + +home.fileToPDF.title=檔案轉 PDF +home.fileToPDF.desc=將幾乎所有格式轉換為 PDF(DOCX、PNG、XLS、PPT、TXT 等等) +fileToPDF.tags=轉換,格式,文件,圖片,幻燈片,文字,轉換,辦公室,文件,Word,Excel,PowerPoint + +home.ocr.title=OCR / 清理掃描 +home.ocr.desc=清理掃描並從 PDF 中的影像中偵測文字並重新新增為文字。 +ocr.tags=識別,文字,影像,掃描,讀取,識別,偵測,可編輯 + + +home.extractImages.title=提取圖片 +home.extractImages.desc=從 PDF 中提取所有圖片並將它們儲存到壓縮檔中 +extractImages.tags=圖片,照片,儲存,存檔,壓縮檔,捕獲,抓取 + +home.pdfToPDFA.title=PDF 轉 PDF/A +home.pdfToPDFA.desc=將 PDF 轉換為長期儲存的 PDF/A +pdfToPDFA.tags=存檔,長期,標準,轉換,儲存,儲存 + +home.PDFToWord.title=PDF 轉 Word +home.PDFToWord.desc=將 PDF 轉換為 Word 格式(DOC、DOCX 和 ODT) +PDFToWord.tags=doc,docx,odt,word,轉換,格式,轉換,辦公室,微軟,docfile + +home.PDFToPresentation.title=PDF 轉簡報 +home.PDFToPresentation.desc=將 PDF 轉換為簡報格式(PPT、PPTX 和 ODP) +PDFToPresentation.tags=幻燈片,展示,辦公室,微軟 + +home.PDFToText.title=PDF 轉 RTF(文字) +home.PDFToText.desc=將 PDF 轉換為文字或 RTF 格式 +PDFToText.tags=豐富格式,豐富文字格式,豐富文字格式 + +home.PDFToHTML.title=PDF 轉 HTML +home.PDFToHTML.desc=將 PDF 轉換為 HTML 格式 +PDFToHTML.tags=網頁內容,瀏覽器友善 + + +home.PDFToXML.title=PDF 轉 XML +home.PDFToXML.desc=將 PDF 轉換為 XML 格式 +PDFToXML.tags=資料提取,結構化內容,互操作,轉換,轉換 + +home.ScannerImageSplit.title=偵測/分割掃描照片 +home.ScannerImageSplit.desc=從照片/PDF 中分割多張照片 +ScannerImageSplit.tags=分離,自動偵測,掃描,多照片,組織 + +home.sign.title=簽章 +home.sign.desc=透過繪圖、文字或影像新增簽章到 PDF +sign.tags=授權,縮寫,繪製簽章,文字,影像簽章 + +home.flatten.title=平坦化 +home.flatten.desc=從 PDF 中移除所有互動元素和表單 +flatten.tags=靜態,停用,非互動,簡化 + +home.repair.title=修復 +home.repair.desc=嘗試修復損壞/破損的 PDF +repair.tags=修復,恢復,修正,恢復 + +home.removeBlanks.title=移除空白頁面 +home.removeBlanks.desc=偵測並從文件中移除空白頁面 +removeBlanks.tags=清理,簡化,非內容,組織 + +home.removeAnnotations.title=移除註釋 +home.removeAnnotations.desc=從 PDF 中移除所有註釋/註解 +removeAnnotations.tags=註釋,突出,註解,標記,移除 + +home.compare.title=比較 +home.compare.desc=比較並顯示 2 個 PDF 檔案的差異 +compare.tags=區分,對比,變化,分析 + +home.certSign.title=使用憑證簽章 +home.certSign.desc=使用憑證/金鑰(PEM/P12)簽章 PDF +certSign.tags=驗證,PEM,P12,官方,加密 + +home.pageLayout.title=多頁面版面配置 +home.pageLayout.desc=將 PDF 檔案的多個頁面合併到單一頁面 +pageLayout.tags=合併,複合,單一檢視,組織 + +home.scalePages.title=調整頁面大小/比例 +home.scalePages.desc=修改頁面及其內容的大小/比例。 +scalePages.tags=調整大小,修改,尺寸,適應 + +home.pipeline.title=管道(進階) +home.pipeline.desc=透過定義管道指令碼在 PDF 上執行多個操作 +pipeline.tags=自動化,序列,指令碼,批次處理 + +home.add-page-numbers.title=新增頁碼 +home.add-page-numbers.desc=在文件的設定位置新增頁碼 +add-page-numbers.tags=分頁,標籤,組織,索引 + +home.auto-rename.title=自動重新命名 PDF 檔案 +home.auto-rename.desc=根據其偵測到的標頭自動重新命名 PDF 檔案 +auto-rename.tags=自動偵測,基於標頭,組織,重新標籤 + +home.adjust-contrast.title=調整顏色/對比度 +home.adjust-contrast.desc=調整 PDF 的對比度、飽和度和亮度 +adjust-contrast.tags=色彩校正,調整,修改,增強 + +home.crop.title=裁剪 PDF +home.crop.desc=裁剪 PDF 以減少其大小(保持文字!) +crop.tags=修剪,縮小,編輯,形狀 + +home.autoSplitPDF.title=自動分割頁面 +home.autoSplitPDF.desc=自動分割掃描的 PDF,使用實體掃描頁面分割器 QR Code +autoSplitPDF.tags=基於 QR Code,分離,掃描區段,組織 + +home.sanitizePdf.title=清理 +home.sanitizePdf.desc=從 PDF 檔案中移除指令碼和其他元素 +sanitizePdf.tags=清理,安全,安全,移除威脅 + +home.URLToPDF.title=網址/網站轉 PDF +home.URLToPDF.desc=將任何 http(s) 網址轉換為 PDF +URLToPDF.tags=網頁捕獲,儲存頁面,網頁轉文件,存檔 + +home.HTMLToPDF.title=HTML 轉 PDF +home.HTMLToPDF.desc=將任何 HTML 檔案或壓縮檔轉換為 PDF +HTMLToPDF.tags=標記,網頁內容,轉換,轉換 + + +home.MarkdownToPDF.title=Markdown 轉 PDF +home.MarkdownToPDF.desc=將任何 Markdown 檔案轉換為 PDF +MarkdownToPDF.tags=標記,網頁內容,轉換,轉換 + + +home.getPdfInfo.title=取得 PDF 的所有資訊 +home.getPdfInfo.desc=取得 PDF 的所有可能資訊 +getPdfInfo.tags=資訊,資料,統計,統計資料 + + +home.extractPage.title=提取多個頁面 +home.extractPage.desc=從 PDF 中提取選定的頁面 +extractPage.tags=提取 + + +home.PdfToSinglePage.title=PDF 轉單一大頁面 +home.PdfToSinglePage.desc=將所有 PDF 頁面合併為一個大的單一頁面 +PdfToSinglePage.tags=單一頁面 + + +home.showJS.title=顯示 JavaScript +home.showJS.desc=搜尋並顯示嵌入 PDF 中的任何 JS(JavaScript) +showJS.tags=JS + +home.autoRedact.title=自動塗黑 +home.autoRedact.desc=根據輸入的文字自動塗黑 PDF 中的文字 +showJS.tags=塗黑,隱藏,塗黑,黑色,標記,隱藏 + +home.tableExtraxt.title=PDF 轉 CSV +home.tableExtraxt.desc=從 PDF 中提取表格並將其轉換為 CSV +tableExtraxt.tags=CSV,表格提取,提取,轉換 + + +home.autoSizeSplitPDF.title=根據大小/數量自動分割 +home.autoSizeSplitPDF.desc=根據大小、頁數或文件數將單一 PDF 分割為多個文件 +autoSizeSplitPDF.tags=pdf,分割,文件,組織 + + +home.overlay-pdfs.title=覆蓋 PDF +home.overlay-pdfs.desc=將 PDF 覆蓋在另一個 PDF 上 +overlay-pdfs.tags=覆蓋 + +home.split-by-sections.title=依區段分割 PDF +home.split-by-sections.desc=將 PDF 的每一頁分割為較小的水平和垂直區段 +split-by-sections.tags=區段分割, 劃分, 自訂 + +########################### +# # +# WEB PAGES # +# # +########################### +#login +login.title=登入 +login.signin=登入 +login.rememberme=記住我 +login.invalid=使用者名稱或密碼無效。 +login.locked=您的帳戶已被鎖定。 +login.signinTitle=請登入 + + +#auto-redact +autoRedact.title=自動塗黑 +autoRedact.header=自動塗黑 +autoRedact.colorLabel=顏色 +autoRedact.textsToRedactLabel=要塗黑的文字(以行分隔) +autoRedact.textsToRedactPlaceholder=例如 \n機密 \n最高機密 +autoRedact.useRegexLabel=使用正則表達式 +autoRedact.wholeWordSearchLabel=整個單詞搜尋 +autoRedact.customPaddingLabel=自訂額外填充 +autoRedact.convertPDFToImageLabel=將 PDF 轉換為 PDF-影像(用於移除方框後面的文字) +autoRedact.submitButton=送出 + + +#showJS +showJS.title=顯示 JavaScript +showJS.header=顯示 JavaScript +showJS.downloadJS=下載 JavaScript +showJS.submit=顯示 + + +#pdfToSinglePage +pdfToSinglePage.title=PDF 轉為單一頁面 +pdfToSinglePage.header=PDF 轉為單一頁面 +pdfToSinglePage.submit=轉換為單一頁面 + + +#pageExtracter +pageExtracter.title=提取頁面 +pageExtracter.header=提取頁面 +pageExtracter.submit=提取 + + +#getPdfInfo +getPdfInfo.title=取得 PDF 資訊 +getPdfInfo.header=取得 PDF 資訊 +getPdfInfo.submit=取得資訊 +getPdfInfo.downloadJson=下載 JSON + + +#markdown-to-pdf +MarkdownToPDF.title=Markdown 轉 PDF +MarkdownToPDF.header=Markdown 轉 PDF +MarkdownToPDF.submit=轉換 +MarkdownToPDF.help=正在進行中 +MarkdownToPDF.credit=使用 WeasyPrint + + + +#url-to-pdf +URLToPDF.title=URL 轉 PDF +URLToPDF.header=URL 轉 PDF +URLToPDF.submit=轉換 +URLToPDF.credit=使用 WeasyPrint + + +#html-to-pdf +HTMLToPDF.title=HTML 轉 PDF +HTMLToPDF.header=HTML 轉 PDF +HTMLToPDF.help=接受 HTML 文件和包含所需 html/css/images 等的 ZIP +HTMLToPDF.submit=轉換 +HTMLToPDF.credit=使用 WeasyPrint + + +#sanitizePDF +sanitizePDF.title=清理 PDF +sanitizePDF.header=清理 PDF 檔案 +sanitizePDF.selectText.1=移除 JavaScript 操作 +sanitizePDF.selectText.2=移除內嵌文件 +sanitizePDF.selectText.3=移除中繼資料 +sanitizePDF.selectText.4=移除連結 +sanitizePDF.selectText.5=移除字型 +sanitizePDF.submit=清理 PDF + + +#addPageNumbers +addPageNumbers.title=新增頁碼 +addPageNumbers.header=新增頁碼 +addPageNumbers.selectText.1=選擇 PDF 檔案: +addPageNumbers.selectText.2=邊距大小 +addPageNumbers.selectText.3=位置 +addPageNumbers.selectText.4=起始號碼 +addPageNumbers.selectText.5=要編號的頁面 +addPageNumbers.selectText.6=自訂文字 +addPageNumbers.customTextDesc=自訂文字 +addPageNumbers.numberPagesDesc=要編號的頁面,預設為 '全部',也可使用 1-5 或 2,5,9 等格式 +addPageNumbers.customNumberDesc=預設為 {n},也接受 '頁面 {n} 共 {total}','文字-{n}','{filename}-{n} +addPageNumbers.submit=新增頁碼 + + +#auto-rename +auto-rename.title=自動重新命名 +auto-rename.header=自動重新命名 PDF +auto-rename.submit=自動重新命名 + + +#adjustContrast +adjustContrast.title=調整對比度 +adjustContrast.header=調整對比度 +adjustContrast.contrast=對比度: +adjustContrast.brightness=亮度: +adjustContrast.saturation=飽和度: +adjustContrast.download=下載 + + +#crop +crop.title=裁剪 +crop.header=裁剪影像 +crop.submit=送出 + + +#autoSplitPDF +autoSplitPDF.title=自動分割 PDF +autoSplitPDF.header=自動分割 PDF +autoSplitPDF.description=列印,插入,掃描,上傳,讓 Stirling PDF 處理其餘的工作。不需要手動工作排序。 +autoSplitPDF.selectText.1=從下面列印一些分隔紙張(黑白即可)。 +autoSplitPDF.selectText.2=透過在它們之間插入分隔紙張一次掃描所有文件。 +autoSplitPDF.selectText.3=上傳單一大的掃描 PDF 檔案,讓 Stirling PDF 處理其餘的工作。 +autoSplitPDF.selectText.4=自動偵測並移除分隔頁面,確保最終文件整潔。 +autoSplitPDF.formPrompt=送出包含 Stirling-PDF 頁面分隔器的 PDF: +autoSplitPDF.duplexMode=雙面模式(正反面掃描) +autoSplitPDF.dividerDownload1=下載 '自動分割器分隔器(最小化).pdf' +autoSplitPDF.dividerDownload2=下載 '自動分割器分隔器(帶說明).pdf' +autoSplitPDF.submit=送出 + + +#pipeline +pipeline.title=管道 + + +#pageLayout +pageLayout.title=多頁面版面配置 +pageLayout.header=多頁面版面配置 +pageLayout.pagesPerSheet=每張紙的頁面數: +pageLayout.addBorder=新增邊框 +pageLayout.submit=送出 + + +#scalePages +scalePages.title=調整頁面大小/比例 +scalePages.header=調整頁面大小/比例 +scalePages.pageSize=文件的頁面大小。 +scalePages.scaleFactor=頁面的縮放級別(裁剪)。 +scalePages.submit=送出 + + +#certSign +certSign.title=憑證簽章 +certSign.header=使用您的憑證簽章(進行中) +certSign.selectPDF=選擇要簽章的 PDF 檔案: +certSign.selectKey=選擇您的私鑰文件(PKCS#8 格式,可能是 .pem 或 .der): +certSign.selectCert=選擇您的憑證文件(X.509 格式,可能是 .pem 或 .der): +certSign.selectP12=選擇您的 PKCS#12 金鑰庫文件(.p12 或 .pfx)(可選,如果提供,它應包含您的私鑰和憑證): +certSign.certType=憑證類型 +certSign.password=輸入您的金鑰庫或私鑰密碼(如果有): +certSign.showSig=顯示簽章 +certSign.reason=原因 +certSign.location=位置 +certSign.name=名稱 +certSign.submit=簽章 PDF + + +#removeBlanks +removeBlanks.title=移除空白頁面 +removeBlanks.header=移除空白頁面 +removeBlanks.threshold=畫素白度閾值: +removeBlanks.thresholdDesc=確定一個白色畫素必須多麼白才能被分類為 '白色'。0 = 黑色,255 純白。 +removeBlanks.whitePercent=白色百分比(%): +removeBlanks.whitePercentDesc=頁面必須是 '白色' 畫素的百分比才能被移除 +removeBlanks.submit=移除空白 + + +#removeAnnotations +removeAnnotations.title=移除註釋 +removeAnnotations.header=移除註釋 +removeAnnotations.submit=移除 + + +#compare +compare.title=比較 +compare.header=比較 PDF +compare.document.1=文件 1 +compare.document.2=文件 2 +compare.submit=比較 + + +#sign +sign.title=簽章 +sign.header=簽章 PDF +sign.upload=上傳影像 +sign.draw=繪製簽章 +sign.text=文字輸入 +sign.clear=清除 +sign.add=新增 + + +#repair +repair.title=修復 +repair.header=修復 PDF +repair.submit=修復 + + +#flatten +flatten.title=平坦化 +flatten.header=PDF 平坦化 +flatten.submit=平坦化 + + +#ScannerImageSplit +ScannerImageSplit.selectText.1=角度閾值: +ScannerImageSplit.selectText.2=設定影像旋轉所需的最小絕對角度(預設:10)。 +ScannerImageSplit.selectText.3=容忍度: +ScannerImageSplit.selectText.4=確定圍繞估計的背景顏色的顏色變化範圍(預設:30)。 +ScannerImageSplit.selectText.5=最小區域: +ScannerImageSplit.selectText.6=設定照片的最小區域閾值(預設:10000)。 +ScannerImageSplit.selectText.7=最小輪廓區域: +ScannerImageSplit.selectText.8=設定照片的最小輪廓區域閾值 +ScannerImageSplit.selectText.9=邊框大小: +ScannerImageSplit.selectText.10=設定新增和移除的邊框大小,以防止輸出中的白色邊框(預設:1)。 + + +#OCR +ocr.title=OCR / 掃描清理 +ocr.header=清理掃描 / OCR(光學字元識別) +ocr.selectText.1=選擇要在 PDF 中偵測的語言(列出的是目前可以偵測的語言): +ocr.selectText.2=產生包含 OCR 文字的文字文件,並與 OCR 的 PDF 一起 +ocr.selectText.3=修正掃描的頁面傾斜角度,將它們旋轉回原位 +ocr.selectText.4=清理頁面,使 OCR 不太可能在背景噪音中找到文字。(無輸出變化) +ocr.selectText.5=清理頁面,使 OCR 不太可能在背景噪音中找到文字,保持清理的輸出。 +ocr.selectText.6=忽略具有互動文字的頁面,只對影像頁面進行 OCR +ocr.selectText.7=強制 OCR,將對每一頁進行 OCR,移除所有原始文字元素 +ocr.selectText.8=正常(如果 PDF 包含文字將出錯) +ocr.selectText.9=額外設定 +ocr.selectText.10=OCR 模式 +ocr.selectText.11=移除 OCR 後的影像(移除所有影像,只有在轉換步驟中才有用) +ocr.selectText.12=渲染類型(進階) +ocr.help=請閱讀此文件,了解如何使用其他語言和/或在 Docker 中使用 +ocr.credit=此服務使用 OCRmyPDF 和 Tesseract 進行 OCR。 +ocr.submit=使用 OCR 處理 PDF + + +#extractImages +extractImages.title=提取圖片 +extractImages.header=提取圖片 +extractImages.selectText=選擇要轉換提取影像的影像格式 +extractImages.submit=提取 + + +#File to PDF +fileToPDF.title=檔案轉 PDF +fileToPDF.header=將任何檔案轉換為 PDF +fileToPDF.credit=此服務使用 LibreOffice 和 Unoconv 進行檔案轉換。 +fileToPDF.supportedFileTypes=支援的檔案類型應包括以下內容,但要獲得完整的更新支援格式列表,請參閱 LibreOffice 的文件 +fileToPDF.submit=轉換為 PDF + + +#compress +compress.title=壓縮 +compress.header=壓縮 PDF +compress.credit=此服務使用 Ghostscript 進行 PDF 壓縮/最佳化。 +compress.selectText.1=手動模式 - 從 1 到 4 +compress.selectText.2=最佳化級別: +compress.selectText.3=4(對於文字影像非常糟糕) +compress.selectText.4=自動模式 - 自動調整品質以使 PDF 達到確定大小 +compress.selectText.5=預期的 PDF 大小(例如 25MB, 10.8MB, 25KB) +compress.submit=壓縮 + + +#Add image +addImage.title=新增圖片 +addImage.header=新增圖片到 PDF +addImage.everyPage=每一頁? +addImage.upload=新增圖片 +addImage.submit=新增圖片 + + +#merge +merge.title=合併 +merge.header=合併多個 PDF +merge.sortByName=依名稱排序 +merge.sortByDate=依日期排序 +merge.submit=合併 + + +#pdfOrganiser +pdfOrganiser.title=頁面整理 +pdfOrganiser.header=PDF 頁面整理 +pdfOrganiser.submit=重新排列頁面 + + +#multiTool +multiTool.title=PDF 多工具 +multiTool.header=PDF 多工具 + +#view pdf +viewPdf.title=檢視 PDF +viewPdf.header=檢視 PDF + +#pageRemover +pageRemover.title=頁面移除 +pageRemover.header=PDF 頁面移除 +pageRemover.pagesToDelete=要刪除的頁面(輸入以逗號分隔的頁碼): +pageRemover.submit=刪除頁面 + + +#rotate +rotate.title=旋轉 PDF +rotate.header=旋轉 PDF +rotate.selectAngle=選擇旋轉角度(以 90 度的倍數): +rotate.submit=旋轉 + + +#merge +split.title=分割 PDF +split.header=分割 PDF +split.desc.1=您選擇的數字是您希望進行分割的頁碼 +split.desc.2=因此,選擇 1,3,7-8 將會將一個 10 頁的文件分割為 6 個單獨的 PDF,包括: +split.desc.3=文件 #1:頁面 1 +split.desc.4=文件 #2:頁面 2 和 3 +split.desc.5=文件 #3:頁面 4、5 和 6 +split.desc.6=文件 #4:頁面 7 +split.desc.7=文件 #5:頁面 8 +split.desc.8=文件 #6:頁面 9 和 10 +split.splitPages=輸入要分割的頁面: +split.submit=分割 + + +#merge +imageToPDF.title=圖片轉 PDF +imageToPDF.header=圖片轉 PDF +imageToPDF.submit=轉換 +imageToPDF.selectLabel=影像適應選項 +imageToPDF.fillPage=填充頁面 +imageToPDF.fitDocumentToImage=適應影像到頁面 +imageToPDF.maintainAspectRatio=保持長寬比 +imageToPDF.selectText.2=自動旋轉 PDF +imageToPDF.selectText.3=多文件邏輯(僅在處理多個影像時啟用) +imageToPDF.selectText.4=合併為單一 PDF +imageToPDF.selectText.5=轉換為單獨的 PDF + + +#pdfToImage +pdfToImage.title=PDF 轉圖片 +pdfToImage.header=PDF 轉圖片 +pdfToImage.selectText=影像格式 +pdfToImage.singleOrMultiple=頁面到影像的結果類型 +pdfToImage.single=單一大影像結合所有頁面 +pdfToImage.multi=多個影像,每頁一個影像 +pdfToImage.colorType=顏色類型 +pdfToImage.color=顏色 +pdfToImage.grey=灰度 +pdfToImage.blackwhite=黑白(可能會遺失資料!) +pdfToImage.submit=轉換 + + +#addPassword +addPassword.title=新增密碼 +addPassword.header=新增密碼(加密) +addPassword.selectText.1=選擇要加密的 PDF +addPassword.selectText.2=使用者密碼 +addPassword.selectText.3=加密金鑰長度 +addPassword.selectText.4=較高的值更強,但較低的值具有更好的相容性。 +addPassword.selectText.5=要設定的權限(建議與擁有者密碼一起使用) +addPassword.selectText.6=防止文件組裝 +addPassword.selectText.7=防止內容提取 +addPassword.selectText.8=防止為了無障礙使用而提取資料 +addPassword.selectText.9=防止填寫表單 +addPassword.selectText.10=防止修改 +addPassword.selectText.11=防止註釋修改 +addPassword.selectText.12=防止列印 +addPassword.selectText.13=防止列印不同格式 +addPassword.selectText.14=擁有者密碼 +addPassword.selectText.15=限制一旦開啟文件可以做什麼(並非所有軟體都支援) +addPassword.selectText.16=限制開啟文件本身 +addPassword.submit=加密 + + +#watermark +watermark.title=新增浮水印 +watermark.header=新增浮水印 +watermark.selectText.1=選擇要新增浮水印的 PDF: +watermark.selectText.2=浮水印文字: +watermark.selectText.3=字型大小: +watermark.selectText.4=旋轉(0-360): +watermark.selectText.5=widthSpacer(每個浮水印之間的水平間距): +watermark.selectText.6=heightSpacer(每個浮水印之間的垂直間距): +watermark.selectText.7=不透明度(0% - 100%): +watermark.selectText.8=浮水印類型: +watermark.selectText.9=浮水印影像: +watermark.submit=新增浮水印 + + +#Change permissions +permissions.title=變更權限 +permissions.header=變更權限 +permissions.warning=警告,要使這些權限不可變更,建議透過新增密碼頁面使用密碼設定這些權限 +permissions.selectText.1=選擇要變更權限的 PDF +permissions.selectText.2=要設定的權限 +permissions.selectText.3=防止文件組裝 +permissions.selectText.4=防止內容提取 +permissions.selectText.5=防止為了無障礙使用而提取資料 +permissions.selectText.6=防止填寫表單 +permissions.selectText.7=防止修改 +permissions.selectText.8=防止註釋修改 +permissions.selectText.9=防止列印 +permissions.selectText.10=防止列印不同格式 +permissions.submit=變更 + + +#remove password +removePassword.title=移除密碼 +removePassword.header=移除密碼(解密) +removePassword.selectText.1=選擇要解密的 PDF +removePassword.selectText.2=密碼 +removePassword.submit=移除 + + +#changeMetadata +changeMetadata.title=變更中繼資料 +changeMetadata.header=變更中繼資料 +changeMetadata.selectText.1=請編輯您希望變更的變數 +changeMetadata.selectText.2=刪除所有中繼資料 +changeMetadata.selectText.3=顯示自訂中繼資料: +changeMetadata.author=作者: +changeMetadata.creationDate=建立日期(yyyy/MM/dd HH:mm:ss): +changeMetadata.creator=建立者: +changeMetadata.keywords=關鍵字: +changeMetadata.modDate=修改日期(yyyy/MM/dd HH:mm:ss): +changeMetadata.producer=製作人: +changeMetadata.subject=主題: +changeMetadata.title=標題: +changeMetadata.trapped=陷阱: +changeMetadata.selectText.4=其他中繼資料: +changeMetadata.selectText.5=新增自訂中繼資料項目 +changeMetadata.submit=變更 + + +#pdfToPDFA +pdfToPDFA.title=PDF 轉 PDF/A +pdfToPDFA.header=PDF 轉 PDF/A +pdfToPDFA.credit=此服務使用 OCRmyPDF 進行 PDF/A 轉換 +pdfToPDFA.submit=轉換 + + +#PDFToWord +PDFToWord.title=PDF 轉 Word +PDFToWord.header=PDF 轉 Word +PDFToWord.selectText.1=輸出文件格式 +PDFToWord.credit=此服務使用 LibreOffice 進行檔案轉換。 +PDFToWord.submit=轉換 + + +#PDFToPresentation +PDFToPresentation.title=PDF 轉簡報 +PDFToPresentation.header=PDF 轉簡報 +PDFToPresentation.selectText.1=輸出文件格式 +PDFToPresentation.credit=此服務使用 LibreOffice 進行檔案轉換。 +PDFToPresentation.submit=轉換 + + +#PDFToText +PDFToText.title=PDF 轉 RTF(文字) +PDFToText.header=PDF 轉 RTF(文字) +PDFToText.selectText.1=輸出文件格式 +PDFToText.credit=此服務使用 LibreOffice 進行檔案轉換。 +PDFToText.submit=轉換 + + +#PDFToHTML +PDFToHTML.title=PDF 轉 HTML +PDFToHTML.header=PDF 轉 HTML +PDFToHTML.credit=此服務使用 LibreOffice 進行檔案轉換。 +PDFToHTML.submit=轉換 + + +#PDFToXML +PDFToXML.title=PDF 轉換為 XML +PDFToXML.header=PDF 轉換為 XML +PDFToXML.credit=此服務使用 LibreOffice 進行檔案轉換。 +PDFToXML.submit=轉換 + +#PDFToCSV +PDFToCSV.title=PDF 轉換為 CSV +PDFToCSV.header=PDF 轉換為 CSV +PDFToCSV.prompt=選擇要提取表格的頁面 +PDFToCSV.submit=提取 + +#split-by-size-or-count +split-by-size-or-count.header=依大小或數量分割 PDF +split-by-size-or-count.type.label=選擇分割類型 +split-by-size-or-count.type.size=依大小 +split-by-size-or-count.type.pageCount=依頁數 +split-by-size-or-count.type.docCount=依文件數量 +split-by-size-or-count.value.label=輸入值 +split-by-size-or-count.value.placeholder=輸入大小(例如:2MB 或 3KB)或數量(例如:5) +split-by-size-or-count.submit=送出 + + +#overlay-pdfs +overlay-pdfs.header=覆蓋 PDF 檔案 +overlay-pdfs.baseFile.label=選擇基礎 PDF 檔案 +overlay-pdfs.overlayFiles.label=選擇覆蓋 PDF 檔案 +overlay-pdfs.mode.label=選擇覆蓋模式 +overlay-pdfs.mode.sequential=序列覆蓋 +overlay-pdfs.mode.interleaved=交錯覆蓋 +overlay-pdfs.mode.fixedRepeat=固定重複覆蓋 +overlay-pdfs.counts.label=覆蓋次數(適用於固定重複模式) +overlay-pdfs.counts.placeholder=輸入逗號分隔的次數(例如:2,3,1) +overlay-pdfs.position.label=選擇覆蓋位置 +overlay-pdfs.position.foreground=前景 +overlay-pdfs.position.background=背景 +overlay-pdfs.submit=送出 + + +#split-by-sections +split-by-sections.title=依區段分割 PDF +split-by-sections.header=將 PDF 分割成區段 +split-by-sections.horizontal.label=水平劃分 +split-by-sections.vertical.label=垂直劃分 +split-by-sections.horizontal.placeholder=輸入水平劃分的數量 +split-by-sections.vertical.placeholder=輸入垂直劃分的數量 +split-by-sections.submit=分割 PDF diff --git a/src/main/resources/static/images/flags/tw.svg b/src/main/resources/static/images/flags/tw.svg new file mode 100644 index 00000000..86084de2 --- /dev/null +++ b/src/main/resources/static/images/flags/tw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/templates/fragments/languages.html b/src/main/resources/templates/fragments/languages.html index 2b4c5838..f7340252 100644 --- a/src/main/resources/templates/fragments/languages.html +++ b/src/main/resources/templates/fragments/languages.html @@ -11,6 +11,9 @@ icon 简体中文 + + icon 正體中文 + icon Deutsch From 0c2b05eabf2b6ae154362b8d04632ef4f022c7cd Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 3 Jan 2024 17:58:02 +0000 Subject: [PATCH 3/4] Update .gitattributes --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index 77083f51..6413e57c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,5 @@ +* text=auto eol=lf + # Ignore all JavaScript files in a directory src/main/resources/static/pdfjs/* linguist-vendored src/main/resources/static/pdfjs/** linguist-vendored From 6fe268adcb6fc6db1916dab2b183a622a8c5bcc1 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 3 Jan 2024 17:59:04 +0000 Subject: [PATCH 4/4] eol --- .../software/SPDF/SPdfApplication.java | 162 ++-- .../software/SPDF/config/AppConfig.java | 120 +-- .../stirling/software/SPDF/config/Beans.java | 128 +-- .../SPDF/config/CleanUrlInterceptor.java | 148 ++-- .../SPDF/config/EndpointConfiguration.java | 462 +++++----- .../SPDF/config/EndpointInterceptor.java | 52 +- .../software/SPDF/config/MetricsConfig.java | 50 +- .../software/SPDF/config/MetricsFilter.java | 128 +-- .../software/SPDF/config/OpenApiConfig.java | 106 +-- .../config/StartupApplicationListener.java | 36 +- .../software/SPDF/config/WebMvcConfig.java | 52 +- .../CustomAuthenticationFailureHandler.java | 98 +-- .../security/CustomUserDetailsService.java | 114 +-- .../security/SecurityConfiguration.java | 280 +++--- .../security/UserAuthenticationFilter.java | 236 +++--- .../security/UserBasedRateLimitingFilter.java | 286 +++---- .../SPDF/config/security/UserService.java | 394 ++++----- .../api/SplitPdfBySectionsController.java | 9 +- .../api/converters/ConvertWebsiteToPDF.java | 154 ++-- .../api/filters/FilterController.java | 420 ++++----- .../api/misc/PageNumbersController.java | 300 +++---- .../api/pipeline/PipelineController.java | 266 +++--- .../api/security/SanitizeController.java | 342 ++++---- .../web/ConverterWebController.java | 226 ++--- .../controller/web/SecurityWebController.java | 138 +-- .../software/SPDF/utils/GeneralUtils.java | 372 ++++---- .../software/SPDF/utils/PdfUtils.java | 800 +++++++++--------- .../software/SPDF/utils/WebResponseUtils.java | 134 +-- 28 files changed, 3006 insertions(+), 3007 deletions(-) diff --git a/src/main/java/stirling/software/SPDF/SPdfApplication.java b/src/main/java/stirling/software/SPDF/SPdfApplication.java index ef3733c6..7388b5e4 100644 --- a/src/main/java/stirling/software/SPDF/SPdfApplication.java +++ b/src/main/java/stirling/software/SPDF/SPdfApplication.java @@ -1,81 +1,81 @@ -package stirling.software.SPDF; - -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.Collections; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.core.env.Environment; -import org.springframework.scheduling.annotation.EnableScheduling; - -import jakarta.annotation.PostConstruct; -import stirling.software.SPDF.config.ConfigInitializer; -import stirling.software.SPDF.utils.GeneralUtils; - -@SpringBootApplication -@EnableScheduling -public class SPdfApplication { - - @Autowired private Environment env; - - @PostConstruct - public void init() { - // Check if the BROWSER_OPEN environment variable is set to true - String browserOpenEnv = env.getProperty("BROWSER_OPEN"); - boolean browserOpen = browserOpenEnv != null && browserOpenEnv.equalsIgnoreCase("true"); - - if (browserOpen) { - try { - String url = "http://localhost:" + getPort(); - - String os = System.getProperty("os.name").toLowerCase(); - Runtime rt = Runtime.getRuntime(); - if (os.contains("win")) { - // For Windows - rt.exec("rundll32 url.dll,FileProtocolHandler " + url); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - public static void main(String[] args) { - SpringApplication app = new SpringApplication(SPdfApplication.class); - app.addInitializers(new ConfigInitializer()); - if (Files.exists(Paths.get("configs/settings.yml"))) { - app.setDefaultProperties( - Collections.singletonMap( - "spring.config.additional-location", "file:configs/settings.yml")); - } else { - System.out.println( - "External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead."); - } - app.run(args); - - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - GeneralUtils.createDir("customFiles/static/"); - GeneralUtils.createDir("customFiles/templates/"); - - System.out.println("Stirling-PDF Started."); - - String url = "http://localhost:" + getPort(); - System.out.println("Navigate to " + url); - } - - public static String getPort() { - String port = System.getProperty("local.server.port"); - if (port == null || port.isEmpty()) { - port = "8080"; - } - return port; - } -} +package stirling.software.SPDF; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Collections; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.core.env.Environment; +import org.springframework.scheduling.annotation.EnableScheduling; + +import jakarta.annotation.PostConstruct; +import stirling.software.SPDF.config.ConfigInitializer; +import stirling.software.SPDF.utils.GeneralUtils; + +@SpringBootApplication +@EnableScheduling +public class SPdfApplication { + + @Autowired private Environment env; + + @PostConstruct + public void init() { + // Check if the BROWSER_OPEN environment variable is set to true + String browserOpenEnv = env.getProperty("BROWSER_OPEN"); + boolean browserOpen = browserOpenEnv != null && browserOpenEnv.equalsIgnoreCase("true"); + + if (browserOpen) { + try { + String url = "http://localhost:" + getPort(); + + String os = System.getProperty("os.name").toLowerCase(); + Runtime rt = Runtime.getRuntime(); + if (os.contains("win")) { + // For Windows + rt.exec("rundll32 url.dll,FileProtocolHandler " + url); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public static void main(String[] args) { + SpringApplication app = new SpringApplication(SPdfApplication.class); + app.addInitializers(new ConfigInitializer()); + if (Files.exists(Paths.get("configs/settings.yml"))) { + app.setDefaultProperties( + Collections.singletonMap( + "spring.config.additional-location", "file:configs/settings.yml")); + } else { + System.out.println( + "External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead."); + } + app.run(args); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + GeneralUtils.createDir("customFiles/static/"); + GeneralUtils.createDir("customFiles/templates/"); + + System.out.println("Stirling-PDF Started."); + + String url = "http://localhost:" + getPort(); + System.out.println("Navigate to " + url); + } + + public static String getPort() { + String port = System.getProperty("local.server.port"); + if (port == null || port.isEmpty()) { + port = "8080"; + } + return port; + } +} diff --git a/src/main/java/stirling/software/SPDF/config/AppConfig.java b/src/main/java/stirling/software/SPDF/config/AppConfig.java index 273de957..0411715f 100644 --- a/src/main/java/stirling/software/SPDF/config/AppConfig.java +++ b/src/main/java/stirling/software/SPDF/config/AppConfig.java @@ -1,60 +1,60 @@ -package stirling.software.SPDF.config; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import stirling.software.SPDF.model.ApplicationProperties; - -@Configuration -public class AppConfig { - - @Autowired ApplicationProperties applicationProperties; - - @Bean(name = "loginEnabled") - public boolean loginEnabled() { - return applicationProperties.getSecurity().getEnableLogin(); - } - - @Bean(name = "appName") - public String appName() { - String homeTitle = applicationProperties.getUi().getAppName(); - return (homeTitle != null) ? homeTitle : "Stirling PDF"; - } - - @Bean(name = "appVersion") - public String appVersion() { - String version = getClass().getPackage().getImplementationVersion(); - return (version != null) ? version : "0.0.0"; - } - - @Bean(name = "homeText") - public String homeText() { - return (applicationProperties.getUi().getHomeDescription() != null) - ? applicationProperties.getUi().getHomeDescription() - : "null"; - } - - @Bean(name = "navBarText") - public String navBarText() { - String defaultNavBar = - applicationProperties.getUi().getAppNameNavbar() != null - ? applicationProperties.getUi().getAppNameNavbar() - : applicationProperties.getUi().getAppName(); - return (defaultNavBar != null) ? defaultNavBar : "Stirling PDF"; - } - - @Bean(name = "enableAlphaFunctionality") - public boolean enableAlphaFunctionality() { - return applicationProperties.getSystem().getEnableAlphaFunctionality() != null - ? applicationProperties.getSystem().getEnableAlphaFunctionality() - : false; - } - - @Bean(name = "rateLimit") - public boolean rateLimit() { - String appName = System.getProperty("rateLimit"); - if (appName == null) appName = System.getenv("rateLimit"); - return (appName != null) ? Boolean.valueOf(appName) : false; - } -} +package stirling.software.SPDF.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import stirling.software.SPDF.model.ApplicationProperties; + +@Configuration +public class AppConfig { + + @Autowired ApplicationProperties applicationProperties; + + @Bean(name = "loginEnabled") + public boolean loginEnabled() { + return applicationProperties.getSecurity().getEnableLogin(); + } + + @Bean(name = "appName") + public String appName() { + String homeTitle = applicationProperties.getUi().getAppName(); + return (homeTitle != null) ? homeTitle : "Stirling PDF"; + } + + @Bean(name = "appVersion") + public String appVersion() { + String version = getClass().getPackage().getImplementationVersion(); + return (version != null) ? version : "0.0.0"; + } + + @Bean(name = "homeText") + public String homeText() { + return (applicationProperties.getUi().getHomeDescription() != null) + ? applicationProperties.getUi().getHomeDescription() + : "null"; + } + + @Bean(name = "navBarText") + public String navBarText() { + String defaultNavBar = + applicationProperties.getUi().getAppNameNavbar() != null + ? applicationProperties.getUi().getAppNameNavbar() + : applicationProperties.getUi().getAppName(); + return (defaultNavBar != null) ? defaultNavBar : "Stirling PDF"; + } + + @Bean(name = "enableAlphaFunctionality") + public boolean enableAlphaFunctionality() { + return applicationProperties.getSystem().getEnableAlphaFunctionality() != null + ? applicationProperties.getSystem().getEnableAlphaFunctionality() + : false; + } + + @Bean(name = "rateLimit") + public boolean rateLimit() { + String appName = System.getProperty("rateLimit"); + if (appName == null) appName = System.getenv("rateLimit"); + return (appName != null) ? Boolean.valueOf(appName) : false; + } +} diff --git a/src/main/java/stirling/software/SPDF/config/Beans.java b/src/main/java/stirling/software/SPDF/config/Beans.java index 9230a0a0..03084b24 100644 --- a/src/main/java/stirling/software/SPDF/config/Beans.java +++ b/src/main/java/stirling/software/SPDF/config/Beans.java @@ -1,64 +1,64 @@ -package stirling.software.SPDF.config; - -import java.util.Locale; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.LocaleResolver; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; -import org.springframework.web.servlet.i18n.SessionLocaleResolver; - -import stirling.software.SPDF.model.ApplicationProperties; - -@Configuration -public class Beans implements WebMvcConfigurer { - - @Autowired ApplicationProperties applicationProperties; - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(localeChangeInterceptor()); - registry.addInterceptor(new CleanUrlInterceptor()); - } - - @Bean - public LocaleChangeInterceptor localeChangeInterceptor() { - LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); - lci.setParamName("lang"); - return lci; - } - - @Bean - public LocaleResolver localeResolver() { - SessionLocaleResolver slr = new SessionLocaleResolver(); - - String appLocaleEnv = applicationProperties.getSystem().getDefaultLocale(); - Locale defaultLocale = - Locale.UK; // Fallback to UK locale if environment variable is not set - - if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) { - Locale tempLocale = Locale.forLanguageTag(appLocaleEnv); - String tempLanguageTag = tempLocale.toLanguageTag(); - - if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) { - defaultLocale = tempLocale; - } else { - tempLocale = Locale.forLanguageTag(appLocaleEnv.replace("_", "-")); - tempLanguageTag = tempLocale.toLanguageTag(); - - if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) { - defaultLocale = tempLocale; - } else { - System.err.println( - "Invalid APP_LOCALE environment variable value. Falling back to default Locale.UK."); - } - } - } - - slr.setDefaultLocale(defaultLocale); - return slr; - } -} +package stirling.software.SPDF.config; + +import java.util.Locale; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; +import org.springframework.web.servlet.i18n.SessionLocaleResolver; + +import stirling.software.SPDF.model.ApplicationProperties; + +@Configuration +public class Beans implements WebMvcConfigurer { + + @Autowired ApplicationProperties applicationProperties; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(localeChangeInterceptor()); + registry.addInterceptor(new CleanUrlInterceptor()); + } + + @Bean + public LocaleChangeInterceptor localeChangeInterceptor() { + LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); + lci.setParamName("lang"); + return lci; + } + + @Bean + public LocaleResolver localeResolver() { + SessionLocaleResolver slr = new SessionLocaleResolver(); + + String appLocaleEnv = applicationProperties.getSystem().getDefaultLocale(); + Locale defaultLocale = + Locale.UK; // Fallback to UK locale if environment variable is not set + + if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) { + Locale tempLocale = Locale.forLanguageTag(appLocaleEnv); + String tempLanguageTag = tempLocale.toLanguageTag(); + + if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) { + defaultLocale = tempLocale; + } else { + tempLocale = Locale.forLanguageTag(appLocaleEnv.replace("_", "-")); + tempLanguageTag = tempLocale.toLanguageTag(); + + if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) { + defaultLocale = tempLocale; + } else { + System.err.println( + "Invalid APP_LOCALE environment variable value. Falling back to default Locale.UK."); + } + } + } + + slr.setDefaultLocale(defaultLocale); + return slr; + } +} diff --git a/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java b/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java index 472fb951..18393581 100644 --- a/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java +++ b/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java @@ -1,74 +1,74 @@ -package stirling.software.SPDF.config; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.servlet.ModelAndView; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -public class CleanUrlInterceptor implements HandlerInterceptor { - - private static final List ALLOWED_PARAMS = - Arrays.asList( - "lang", "endpoint", "endpoints", "logout", "error", "file", "messageType"); - - @Override - public boolean preHandle( - HttpServletRequest request, HttpServletResponse response, Object handler) - throws Exception { - String queryString = request.getQueryString(); - if (queryString != null && !queryString.isEmpty()) { - String requestURI = request.getRequestURI(); - Map parameters = new HashMap<>(); - - // Keep only the allowed parameters - String[] queryParameters = queryString.split("&"); - for (String param : queryParameters) { - String[] keyValue = param.split("="); - if (keyValue.length != 2) { - continue; - } - if (ALLOWED_PARAMS.contains(keyValue[0])) { - parameters.put(keyValue[0], keyValue[1]); - } - } - - // If there are any parameters that are not allowed - if (parameters.size() != queryParameters.length) { - // Construct new query string - StringBuilder newQueryString = new StringBuilder(); - for (Map.Entry entry : parameters.entrySet()) { - if (newQueryString.length() > 0) { - newQueryString.append("&"); - } - newQueryString.append(entry.getKey()).append("=").append(entry.getValue()); - } - - // Redirect to the URL with only allowed query parameters - String redirectUrl = requestURI + "?" + newQueryString; - response.sendRedirect(redirectUrl); - return false; - } - } - return true; - } - - @Override - public void postHandle( - HttpServletRequest request, - HttpServletResponse response, - Object handler, - ModelAndView modelAndView) {} - - @Override - public void afterCompletion( - HttpServletRequest request, - HttpServletResponse response, - Object handler, - Exception ex) {} -} +package stirling.software.SPDF.config; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class CleanUrlInterceptor implements HandlerInterceptor { + + private static final List ALLOWED_PARAMS = + Arrays.asList( + "lang", "endpoint", "endpoints", "logout", "error", "file", "messageType"); + + @Override + public boolean preHandle( + HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + String queryString = request.getQueryString(); + if (queryString != null && !queryString.isEmpty()) { + String requestURI = request.getRequestURI(); + Map parameters = new HashMap<>(); + + // Keep only the allowed parameters + String[] queryParameters = queryString.split("&"); + for (String param : queryParameters) { + String[] keyValue = param.split("="); + if (keyValue.length != 2) { + continue; + } + if (ALLOWED_PARAMS.contains(keyValue[0])) { + parameters.put(keyValue[0], keyValue[1]); + } + } + + // If there are any parameters that are not allowed + if (parameters.size() != queryParameters.length) { + // Construct new query string + StringBuilder newQueryString = new StringBuilder(); + for (Map.Entry entry : parameters.entrySet()) { + if (newQueryString.length() > 0) { + newQueryString.append("&"); + } + newQueryString.append(entry.getKey()).append("=").append(entry.getValue()); + } + + // Redirect to the URL with only allowed query parameters + String redirectUrl = requestURI + "?" + newQueryString; + response.sendRedirect(redirectUrl); + return false; + } + } + return true; + } + + @Override + public void postHandle( + HttpServletRequest request, + HttpServletResponse response, + Object handler, + ModelAndView modelAndView) {} + + @Override + public void afterCompletion( + HttpServletRequest request, + HttpServletResponse response, + Object handler, + Exception ex) {} +} diff --git a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java index ddba4623..593b70b4 100644 --- a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java @@ -1,231 +1,231 @@ -package stirling.software.SPDF.config; - -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import stirling.software.SPDF.model.ApplicationProperties; - -@Service -public class EndpointConfiguration { - private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class); - private Map endpointStatuses = new ConcurrentHashMap<>(); - private Map> endpointGroups = new ConcurrentHashMap<>(); - - private final ApplicationProperties applicationProperties; - - @Autowired - public EndpointConfiguration(ApplicationProperties applicationProperties) { - this.applicationProperties = applicationProperties; - init(); - processEnvironmentConfigs(); - } - - public void enableEndpoint(String endpoint) { - endpointStatuses.put(endpoint, true); - } - - public void disableEndpoint(String endpoint) { - if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) { - logger.info("Disabling {}", endpoint); - endpointStatuses.put(endpoint, false); - } - } - - public boolean isEndpointEnabled(String endpoint) { - if (endpoint.startsWith("/")) { - endpoint = endpoint.substring(1); - } - return endpointStatuses.getOrDefault(endpoint, true); - } - - public void addEndpointToGroup(String group, String endpoint) { - endpointGroups.computeIfAbsent(group, k -> new HashSet<>()).add(endpoint); - } - - public void enableGroup(String group) { - Set endpoints = endpointGroups.get(group); - if (endpoints != null) { - for (String endpoint : endpoints) { - enableEndpoint(endpoint); - } - } - } - - public void disableGroup(String group) { - Set endpoints = endpointGroups.get(group); - if (endpoints != null) { - for (String endpoint : endpoints) { - disableEndpoint(endpoint); - } - } - } - - public void init() { - // Adding endpoints to "PageOps" group - addEndpointToGroup("PageOps", "remove-pages"); - addEndpointToGroup("PageOps", "merge-pdfs"); - addEndpointToGroup("PageOps", "split-pdfs"); - addEndpointToGroup("PageOps", "pdf-organizer"); - addEndpointToGroup("PageOps", "rotate-pdf"); - addEndpointToGroup("PageOps", "multi-page-layout"); - addEndpointToGroup("PageOps", "scale-pages"); - addEndpointToGroup("PageOps", "adjust-contrast"); - addEndpointToGroup("PageOps", "crop"); - addEndpointToGroup("PageOps", "auto-split-pdf"); - addEndpointToGroup("PageOps", "extract-page"); - addEndpointToGroup("PageOps", "pdf-to-single-page"); - addEndpointToGroup("PageOps", "split-by-size-or-count"); - addEndpointToGroup("PageOps", "overlay-pdf"); - addEndpointToGroup("PageOps", "split-pdf-by-sections"); - - // Adding endpoints to "Convert" group - addEndpointToGroup("Convert", "pdf-to-img"); - addEndpointToGroup("Convert", "img-to-pdf"); - addEndpointToGroup("Convert", "pdf-to-pdfa"); - addEndpointToGroup("Convert", "file-to-pdf"); - addEndpointToGroup("Convert", "xlsx-to-pdf"); - addEndpointToGroup("Convert", "pdf-to-word"); - addEndpointToGroup("Convert", "pdf-to-presentation"); - addEndpointToGroup("Convert", "pdf-to-text"); - addEndpointToGroup("Convert", "pdf-to-html"); - addEndpointToGroup("Convert", "pdf-to-xml"); - addEndpointToGroup("Convert", "html-to-pdf"); - addEndpointToGroup("Convert", "url-to-pdf"); - addEndpointToGroup("Convert", "markdown-to-pdf"); - addEndpointToGroup("Convert", "pdf-to-csv"); - - // Adding endpoints to "Security" group - addEndpointToGroup("Security", "add-password"); - addEndpointToGroup("Security", "remove-password"); - addEndpointToGroup("Security", "change-permissions"); - addEndpointToGroup("Security", "add-watermark"); - addEndpointToGroup("Security", "cert-sign"); - addEndpointToGroup("Security", "sanitize-pdf"); - addEndpointToGroup("Security", "auto-redact"); - - // Adding endpoints to "Other" group - addEndpointToGroup("Other", "ocr-pdf"); - addEndpointToGroup("Other", "add-image"); - addEndpointToGroup("Other", "compress-pdf"); - addEndpointToGroup("Other", "extract-images"); - addEndpointToGroup("Other", "change-metadata"); - addEndpointToGroup("Other", "extract-image-scans"); - addEndpointToGroup("Other", "sign"); - addEndpointToGroup("Other", "flatten"); - addEndpointToGroup("Other", "repair"); - addEndpointToGroup("Other", "remove-blanks"); - addEndpointToGroup("Other", "remove-annotations"); - addEndpointToGroup("Other", "compare"); - addEndpointToGroup("Other", "add-page-numbers"); - addEndpointToGroup("Other", "auto-rename"); - addEndpointToGroup("Other", "get-info-on-pdf"); - addEndpointToGroup("Other", "show-javascript"); - - // CLI - addEndpointToGroup("CLI", "compress-pdf"); - addEndpointToGroup("CLI", "extract-image-scans"); - addEndpointToGroup("CLI", "remove-blanks"); - addEndpointToGroup("CLI", "repair"); - addEndpointToGroup("CLI", "pdf-to-pdfa"); - addEndpointToGroup("CLI", "file-to-pdf"); - addEndpointToGroup("CLI", "xlsx-to-pdf"); - addEndpointToGroup("CLI", "pdf-to-word"); - addEndpointToGroup("CLI", "pdf-to-presentation"); - addEndpointToGroup("CLI", "pdf-to-text"); - addEndpointToGroup("CLI", "pdf-to-html"); - addEndpointToGroup("CLI", "pdf-to-xml"); - addEndpointToGroup("CLI", "ocr-pdf"); - addEndpointToGroup("CLI", "html-to-pdf"); - addEndpointToGroup("CLI", "url-to-pdf"); - - // python - addEndpointToGroup("Python", "extract-image-scans"); - addEndpointToGroup("Python", "remove-blanks"); - addEndpointToGroup("Python", "html-to-pdf"); - addEndpointToGroup("Python", "url-to-pdf"); - - // openCV - addEndpointToGroup("OpenCV", "extract-image-scans"); - addEndpointToGroup("OpenCV", "remove-blanks"); - - // LibreOffice - addEndpointToGroup("LibreOffice", "repair"); - addEndpointToGroup("LibreOffice", "file-to-pdf"); - addEndpointToGroup("LibreOffice", "xlsx-to-pdf"); - addEndpointToGroup("LibreOffice", "pdf-to-word"); - addEndpointToGroup("LibreOffice", "pdf-to-presentation"); - addEndpointToGroup("LibreOffice", "pdf-to-text"); - addEndpointToGroup("LibreOffice", "pdf-to-html"); - addEndpointToGroup("LibreOffice", "pdf-to-xml"); - - // OCRmyPDF - addEndpointToGroup("OCRmyPDF", "compress-pdf"); - addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa"); - addEndpointToGroup("OCRmyPDF", "ocr-pdf"); - - // Java - addEndpointToGroup("Java", "merge-pdfs"); - addEndpointToGroup("Java", "remove-pages"); - addEndpointToGroup("Java", "split-pdfs"); - addEndpointToGroup("Java", "pdf-organizer"); - addEndpointToGroup("Java", "rotate-pdf"); - addEndpointToGroup("Java", "pdf-to-img"); - addEndpointToGroup("Java", "img-to-pdf"); - addEndpointToGroup("Java", "add-password"); - addEndpointToGroup("Java", "remove-password"); - addEndpointToGroup("Java", "change-permissions"); - addEndpointToGroup("Java", "add-watermark"); - addEndpointToGroup("Java", "add-image"); - addEndpointToGroup("Java", "extract-images"); - addEndpointToGroup("Java", "change-metadata"); - addEndpointToGroup("Java", "cert-sign"); - addEndpointToGroup("Java", "multi-page-layout"); - addEndpointToGroup("Java", "scale-pages"); - addEndpointToGroup("Java", "add-page-numbers"); - addEndpointToGroup("Java", "auto-rename"); - addEndpointToGroup("Java", "auto-split-pdf"); - addEndpointToGroup("Java", "sanitize-pdf"); - addEndpointToGroup("Java", "crop"); - addEndpointToGroup("Java", "get-info-on-pdf"); - addEndpointToGroup("Java", "extract-page"); - addEndpointToGroup("Java", "pdf-to-single-page"); - addEndpointToGroup("Java", "markdown-to-pdf"); - addEndpointToGroup("Java", "show-javascript"); - addEndpointToGroup("Java", "auto-redact"); - addEndpointToGroup("Java", "pdf-to-csv"); - addEndpointToGroup("Java", "split-by-size-or-count"); - addEndpointToGroup("Java", "overlay-pdf"); - addEndpointToGroup("Java", "split-pdf-by-sections"); - - // Javascript - addEndpointToGroup("Javascript", "pdf-organizer"); - addEndpointToGroup("Javascript", "sign"); - addEndpointToGroup("Javascript", "compare"); - addEndpointToGroup("Javascript", "adjust-contrast"); - } - - private void processEnvironmentConfigs() { - List endpointsToRemove = applicationProperties.getEndpoints().getToRemove(); - List groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove(); - - if (endpointsToRemove != null) { - for (String endpoint : endpointsToRemove) { - disableEndpoint(endpoint.trim()); - } - } - - if (groupsToRemove != null) { - for (String group : groupsToRemove) { - disableGroup(group.trim()); - } - } - } -} +package stirling.software.SPDF.config; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import stirling.software.SPDF.model.ApplicationProperties; + +@Service +public class EndpointConfiguration { + private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class); + private Map endpointStatuses = new ConcurrentHashMap<>(); + private Map> endpointGroups = new ConcurrentHashMap<>(); + + private final ApplicationProperties applicationProperties; + + @Autowired + public EndpointConfiguration(ApplicationProperties applicationProperties) { + this.applicationProperties = applicationProperties; + init(); + processEnvironmentConfigs(); + } + + public void enableEndpoint(String endpoint) { + endpointStatuses.put(endpoint, true); + } + + public void disableEndpoint(String endpoint) { + if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) { + logger.info("Disabling {}", endpoint); + endpointStatuses.put(endpoint, false); + } + } + + public boolean isEndpointEnabled(String endpoint) { + if (endpoint.startsWith("/")) { + endpoint = endpoint.substring(1); + } + return endpointStatuses.getOrDefault(endpoint, true); + } + + public void addEndpointToGroup(String group, String endpoint) { + endpointGroups.computeIfAbsent(group, k -> new HashSet<>()).add(endpoint); + } + + public void enableGroup(String group) { + Set endpoints = endpointGroups.get(group); + if (endpoints != null) { + for (String endpoint : endpoints) { + enableEndpoint(endpoint); + } + } + } + + public void disableGroup(String group) { + Set endpoints = endpointGroups.get(group); + if (endpoints != null) { + for (String endpoint : endpoints) { + disableEndpoint(endpoint); + } + } + } + + public void init() { + // Adding endpoints to "PageOps" group + addEndpointToGroup("PageOps", "remove-pages"); + addEndpointToGroup("PageOps", "merge-pdfs"); + addEndpointToGroup("PageOps", "split-pdfs"); + addEndpointToGroup("PageOps", "pdf-organizer"); + addEndpointToGroup("PageOps", "rotate-pdf"); + addEndpointToGroup("PageOps", "multi-page-layout"); + addEndpointToGroup("PageOps", "scale-pages"); + addEndpointToGroup("PageOps", "adjust-contrast"); + addEndpointToGroup("PageOps", "crop"); + addEndpointToGroup("PageOps", "auto-split-pdf"); + addEndpointToGroup("PageOps", "extract-page"); + addEndpointToGroup("PageOps", "pdf-to-single-page"); + addEndpointToGroup("PageOps", "split-by-size-or-count"); + addEndpointToGroup("PageOps", "overlay-pdf"); + addEndpointToGroup("PageOps", "split-pdf-by-sections"); + + // Adding endpoints to "Convert" group + addEndpointToGroup("Convert", "pdf-to-img"); + addEndpointToGroup("Convert", "img-to-pdf"); + addEndpointToGroup("Convert", "pdf-to-pdfa"); + addEndpointToGroup("Convert", "file-to-pdf"); + addEndpointToGroup("Convert", "xlsx-to-pdf"); + addEndpointToGroup("Convert", "pdf-to-word"); + addEndpointToGroup("Convert", "pdf-to-presentation"); + addEndpointToGroup("Convert", "pdf-to-text"); + addEndpointToGroup("Convert", "pdf-to-html"); + addEndpointToGroup("Convert", "pdf-to-xml"); + addEndpointToGroup("Convert", "html-to-pdf"); + addEndpointToGroup("Convert", "url-to-pdf"); + addEndpointToGroup("Convert", "markdown-to-pdf"); + addEndpointToGroup("Convert", "pdf-to-csv"); + + // Adding endpoints to "Security" group + addEndpointToGroup("Security", "add-password"); + addEndpointToGroup("Security", "remove-password"); + addEndpointToGroup("Security", "change-permissions"); + addEndpointToGroup("Security", "add-watermark"); + addEndpointToGroup("Security", "cert-sign"); + addEndpointToGroup("Security", "sanitize-pdf"); + addEndpointToGroup("Security", "auto-redact"); + + // Adding endpoints to "Other" group + addEndpointToGroup("Other", "ocr-pdf"); + addEndpointToGroup("Other", "add-image"); + addEndpointToGroup("Other", "compress-pdf"); + addEndpointToGroup("Other", "extract-images"); + addEndpointToGroup("Other", "change-metadata"); + addEndpointToGroup("Other", "extract-image-scans"); + addEndpointToGroup("Other", "sign"); + addEndpointToGroup("Other", "flatten"); + addEndpointToGroup("Other", "repair"); + addEndpointToGroup("Other", "remove-blanks"); + addEndpointToGroup("Other", "remove-annotations"); + addEndpointToGroup("Other", "compare"); + addEndpointToGroup("Other", "add-page-numbers"); + addEndpointToGroup("Other", "auto-rename"); + addEndpointToGroup("Other", "get-info-on-pdf"); + addEndpointToGroup("Other", "show-javascript"); + + // CLI + addEndpointToGroup("CLI", "compress-pdf"); + addEndpointToGroup("CLI", "extract-image-scans"); + addEndpointToGroup("CLI", "remove-blanks"); + addEndpointToGroup("CLI", "repair"); + addEndpointToGroup("CLI", "pdf-to-pdfa"); + addEndpointToGroup("CLI", "file-to-pdf"); + addEndpointToGroup("CLI", "xlsx-to-pdf"); + addEndpointToGroup("CLI", "pdf-to-word"); + addEndpointToGroup("CLI", "pdf-to-presentation"); + addEndpointToGroup("CLI", "pdf-to-text"); + addEndpointToGroup("CLI", "pdf-to-html"); + addEndpointToGroup("CLI", "pdf-to-xml"); + addEndpointToGroup("CLI", "ocr-pdf"); + addEndpointToGroup("CLI", "html-to-pdf"); + addEndpointToGroup("CLI", "url-to-pdf"); + + // python + addEndpointToGroup("Python", "extract-image-scans"); + addEndpointToGroup("Python", "remove-blanks"); + addEndpointToGroup("Python", "html-to-pdf"); + addEndpointToGroup("Python", "url-to-pdf"); + + // openCV + addEndpointToGroup("OpenCV", "extract-image-scans"); + addEndpointToGroup("OpenCV", "remove-blanks"); + + // LibreOffice + addEndpointToGroup("LibreOffice", "repair"); + addEndpointToGroup("LibreOffice", "file-to-pdf"); + addEndpointToGroup("LibreOffice", "xlsx-to-pdf"); + addEndpointToGroup("LibreOffice", "pdf-to-word"); + addEndpointToGroup("LibreOffice", "pdf-to-presentation"); + addEndpointToGroup("LibreOffice", "pdf-to-text"); + addEndpointToGroup("LibreOffice", "pdf-to-html"); + addEndpointToGroup("LibreOffice", "pdf-to-xml"); + + // OCRmyPDF + addEndpointToGroup("OCRmyPDF", "compress-pdf"); + addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa"); + addEndpointToGroup("OCRmyPDF", "ocr-pdf"); + + // Java + addEndpointToGroup("Java", "merge-pdfs"); + addEndpointToGroup("Java", "remove-pages"); + addEndpointToGroup("Java", "split-pdfs"); + addEndpointToGroup("Java", "pdf-organizer"); + addEndpointToGroup("Java", "rotate-pdf"); + addEndpointToGroup("Java", "pdf-to-img"); + addEndpointToGroup("Java", "img-to-pdf"); + addEndpointToGroup("Java", "add-password"); + addEndpointToGroup("Java", "remove-password"); + addEndpointToGroup("Java", "change-permissions"); + addEndpointToGroup("Java", "add-watermark"); + addEndpointToGroup("Java", "add-image"); + addEndpointToGroup("Java", "extract-images"); + addEndpointToGroup("Java", "change-metadata"); + addEndpointToGroup("Java", "cert-sign"); + addEndpointToGroup("Java", "multi-page-layout"); + addEndpointToGroup("Java", "scale-pages"); + addEndpointToGroup("Java", "add-page-numbers"); + addEndpointToGroup("Java", "auto-rename"); + addEndpointToGroup("Java", "auto-split-pdf"); + addEndpointToGroup("Java", "sanitize-pdf"); + addEndpointToGroup("Java", "crop"); + addEndpointToGroup("Java", "get-info-on-pdf"); + addEndpointToGroup("Java", "extract-page"); + addEndpointToGroup("Java", "pdf-to-single-page"); + addEndpointToGroup("Java", "markdown-to-pdf"); + addEndpointToGroup("Java", "show-javascript"); + addEndpointToGroup("Java", "auto-redact"); + addEndpointToGroup("Java", "pdf-to-csv"); + addEndpointToGroup("Java", "split-by-size-or-count"); + addEndpointToGroup("Java", "overlay-pdf"); + addEndpointToGroup("Java", "split-pdf-by-sections"); + + // Javascript + addEndpointToGroup("Javascript", "pdf-organizer"); + addEndpointToGroup("Javascript", "sign"); + addEndpointToGroup("Javascript", "compare"); + addEndpointToGroup("Javascript", "adjust-contrast"); + } + + private void processEnvironmentConfigs() { + List endpointsToRemove = applicationProperties.getEndpoints().getToRemove(); + List groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove(); + + if (endpointsToRemove != null) { + for (String endpoint : endpointsToRemove) { + disableEndpoint(endpoint.trim()); + } + } + + if (groupsToRemove != null) { + for (String group : groupsToRemove) { + disableGroup(group.trim()); + } + } + } +} diff --git a/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java b/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java index d408b9ea..81b50b84 100644 --- a/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java +++ b/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java @@ -1,26 +1,26 @@ -package stirling.software.SPDF.config; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.HandlerInterceptor; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -@Component -public class EndpointInterceptor implements HandlerInterceptor { - - @Autowired private EndpointConfiguration endpointConfiguration; - - @Override - public boolean preHandle( - HttpServletRequest request, HttpServletResponse response, Object handler) - throws Exception { - String requestURI = request.getRequestURI(); - if (!endpointConfiguration.isEndpointEnabled(requestURI)) { - response.sendError(HttpServletResponse.SC_FORBIDDEN, "This endpoint is disabled"); - return false; - } - return true; - } -} +package stirling.software.SPDF.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Component +public class EndpointInterceptor implements HandlerInterceptor { + + @Autowired private EndpointConfiguration endpointConfiguration; + + @Override + public boolean preHandle( + HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + String requestURI = request.getRequestURI(); + if (!endpointConfiguration.isEndpointEnabled(requestURI)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "This endpoint is disabled"); + return false; + } + return true; + } +} diff --git a/src/main/java/stirling/software/SPDF/config/MetricsConfig.java b/src/main/java/stirling/software/SPDF/config/MetricsConfig.java index 3877c566..ba216be7 100644 --- a/src/main/java/stirling/software/SPDF/config/MetricsConfig.java +++ b/src/main/java/stirling/software/SPDF/config/MetricsConfig.java @@ -1,25 +1,25 @@ -package stirling.software.SPDF.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import io.micrometer.core.instrument.Meter; -import io.micrometer.core.instrument.config.MeterFilter; -import io.micrometer.core.instrument.config.MeterFilterReply; - -@Configuration -public class MetricsConfig { - - @Bean - public MeterFilter meterFilter() { - return new MeterFilter() { - @Override - public MeterFilterReply accept(Meter.Id id) { - if (id.getName().equals("http.requests")) { - return MeterFilterReply.NEUTRAL; - } - return MeterFilterReply.DENY; - } - }; - } -} +package stirling.software.SPDF.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.config.MeterFilter; +import io.micrometer.core.instrument.config.MeterFilterReply; + +@Configuration +public class MetricsConfig { + + @Bean + public MeterFilter meterFilter() { + return new MeterFilter() { + @Override + public MeterFilterReply accept(Meter.Id id) { + if (id.getName().equals("http.requests")) { + return MeterFilterReply.NEUTRAL; + } + return MeterFilterReply.DENY; + } + }; + } +} diff --git a/src/main/java/stirling/software/SPDF/config/MetricsFilter.java b/src/main/java/stirling/software/SPDF/config/MetricsFilter.java index 9207fd07..6f2751a9 100644 --- a/src/main/java/stirling/software/SPDF/config/MetricsFilter.java +++ b/src/main/java/stirling/software/SPDF/config/MetricsFilter.java @@ -1,64 +1,64 @@ -package stirling.software.SPDF.config; - -import java.io.IOException; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.MeterRegistry; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -@Component -public class MetricsFilter extends OncePerRequestFilter { - - private final MeterRegistry meterRegistry; - - @Autowired - public MetricsFilter(MeterRegistry meterRegistry) { - this.meterRegistry = meterRegistry; - } - - @Override - protected void doFilterInternal( - HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - String uri = request.getRequestURI(); - - // System.out.println("uri="+uri + ", method=" + request.getMethod() ); - // Ignore static resources - if (!(uri.startsWith("/js") - || uri.startsWith("/v1/api-docs") - || uri.endsWith("robots.txt") - || uri.startsWith("/images") - || uri.startsWith("/images") - || uri.endsWith(".png") - || uri.endsWith(".ico") - || uri.endsWith(".css") - || uri.endsWith(".map") - || uri.endsWith(".svg") - || uri.endsWith(".js") - || uri.contains("swagger") - || uri.startsWith("/api/v1/info") - || uri.startsWith("/site.webmanifest") - || uri.startsWith("/fonts") - || uri.startsWith("/pdfjs"))) { - - Counter counter = - Counter.builder("http.requests") - .tag("uri", uri) - .tag("method", request.getMethod()) - .register(meterRegistry); - - counter.increment(); - // System.out.println("Counted"); - } - - filterChain.doFilter(request, response); - } -} +package stirling.software.SPDF.config; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Component +public class MetricsFilter extends OncePerRequestFilter { + + private final MeterRegistry meterRegistry; + + @Autowired + public MetricsFilter(MeterRegistry meterRegistry) { + this.meterRegistry = meterRegistry; + } + + @Override + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + String uri = request.getRequestURI(); + + // System.out.println("uri="+uri + ", method=" + request.getMethod() ); + // Ignore static resources + if (!(uri.startsWith("/js") + || uri.startsWith("/v1/api-docs") + || uri.endsWith("robots.txt") + || uri.startsWith("/images") + || uri.startsWith("/images") + || uri.endsWith(".png") + || uri.endsWith(".ico") + || uri.endsWith(".css") + || uri.endsWith(".map") + || uri.endsWith(".svg") + || uri.endsWith(".js") + || uri.contains("swagger") + || uri.startsWith("/api/v1/info") + || uri.startsWith("/site.webmanifest") + || uri.startsWith("/fonts") + || uri.startsWith("/pdfjs"))) { + + Counter counter = + Counter.builder("http.requests") + .tag("uri", uri) + .tag("method", request.getMethod()) + .register(meterRegistry); + + counter.increment(); + // System.out.println("Counted"); + } + + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java b/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java index a852bb1a..7194f4a2 100644 --- a/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java +++ b/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java @@ -1,53 +1,53 @@ -package stirling.software.SPDF.config; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import io.swagger.v3.oas.models.Components; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.models.security.SecurityRequirement; -import io.swagger.v3.oas.models.security.SecurityScheme; - -import stirling.software.SPDF.model.ApplicationProperties; - -@Configuration -public class OpenApiConfig { - - @Autowired ApplicationProperties applicationProperties; - - @Bean - public OpenAPI customOpenAPI() { - String version = getClass().getPackage().getImplementationVersion(); - if (version == null) { - version = "1.0.0"; // default version if all else fails - } - - SecurityScheme apiKeyScheme = - new SecurityScheme() - .type(SecurityScheme.Type.APIKEY) - .in(SecurityScheme.In.HEADER) - .name("X-API-KEY"); - if (!applicationProperties.getSecurity().getEnableLogin()) { - return new OpenAPI() - .components(new Components()) - .info( - new Info() - .title("Stirling PDF API") - .version(version) - .description( - "API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")); - } else { - return new OpenAPI() - .components(new Components().addSecuritySchemes("apiKey", apiKeyScheme)) - .info( - new Info() - .title("Stirling PDF API") - .version(version) - .description( - "API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")) - .addSecurityItem(new SecurityRequirement().addList("apiKey")); - } - } -} +package stirling.software.SPDF.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; + +import stirling.software.SPDF.model.ApplicationProperties; + +@Configuration +public class OpenApiConfig { + + @Autowired ApplicationProperties applicationProperties; + + @Bean + public OpenAPI customOpenAPI() { + String version = getClass().getPackage().getImplementationVersion(); + if (version == null) { + version = "1.0.0"; // default version if all else fails + } + + SecurityScheme apiKeyScheme = + new SecurityScheme() + .type(SecurityScheme.Type.APIKEY) + .in(SecurityScheme.In.HEADER) + .name("X-API-KEY"); + if (!applicationProperties.getSecurity().getEnableLogin()) { + return new OpenAPI() + .components(new Components()) + .info( + new Info() + .title("Stirling PDF API") + .version(version) + .description( + "API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")); + } else { + return new OpenAPI() + .components(new Components().addSecuritySchemes("apiKey", apiKeyScheme)) + .info( + new Info() + .title("Stirling PDF API") + .version(version) + .description( + "API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")) + .addSecurityItem(new SecurityRequirement().addList("apiKey")); + } + } +} diff --git a/src/main/java/stirling/software/SPDF/config/StartupApplicationListener.java b/src/main/java/stirling/software/SPDF/config/StartupApplicationListener.java index 07644b30..737b47d5 100644 --- a/src/main/java/stirling/software/SPDF/config/StartupApplicationListener.java +++ b/src/main/java/stirling/software/SPDF/config/StartupApplicationListener.java @@ -1,18 +1,18 @@ -package stirling.software.SPDF.config; - -import java.time.LocalDateTime; - -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.stereotype.Component; - -@Component -public class StartupApplicationListener implements ApplicationListener { - - public static LocalDateTime startTime; - - @Override - public void onApplicationEvent(ContextRefreshedEvent event) { - startTime = LocalDateTime.now(); - } -} +package stirling.software.SPDF.config; + +import java.time.LocalDateTime; + +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; + +@Component +public class StartupApplicationListener implements ApplicationListener { + + public static LocalDateTime startTime; + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + startTime = LocalDateTime.now(); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java b/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java index 850f334b..eaadd251 100644 --- a/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java +++ b/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java @@ -1,26 +1,26 @@ -package stirling.software.SPDF.config; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class WebMvcConfig implements WebMvcConfigurer { - - @Autowired private EndpointInterceptor endpointInterceptor; - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(endpointInterceptor); - } - - @Override - public void addResourceHandlers(ResourceHandlerRegistry registry) { - // Handler for external static resources - registry.addResourceHandler("/**") - .addResourceLocations("file:customFiles/static/", "classpath:/static/"); - // .setCachePeriod(0); // Optional: disable caching - } -} +package stirling.software.SPDF.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + @Autowired private EndpointInterceptor endpointInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(endpointInterceptor); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + // Handler for external static resources + registry.addResourceHandler("/**") + .addResourceLocations("file:customFiles/static/", "classpath:/static/"); + // .setCachePeriod(0); // Optional: disable caching + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java b/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java index 6c2a05d3..cbdf7d26 100644 --- a/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java @@ -1,49 +1,49 @@ -package stirling.software.SPDF.config.security; - -import java.io.IOException; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.LockedException; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; -import org.springframework.stereotype.Component; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -@Component -public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { - - @Autowired private final LoginAttemptService loginAttemptService; - - @Autowired - public CustomAuthenticationFailureHandler(LoginAttemptService loginAttemptService) { - this.loginAttemptService = loginAttemptService; - } - - @Override - public void onAuthenticationFailure( - HttpServletRequest request, - HttpServletResponse response, - AuthenticationException exception) - throws IOException, ServletException { - String ip = request.getRemoteAddr(); - logger.error("Failed login attempt from IP: " + ip); - - String username = request.getParameter("username"); - if (loginAttemptService.loginAttemptCheck(username)) { - setDefaultFailureUrl("/login?error=locked"); - - } else { - if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) { - setDefaultFailureUrl("/login?error=badcredentials"); - } else if (exception.getClass().isAssignableFrom(LockedException.class)) { - setDefaultFailureUrl("/login?error=locked"); - } - } - - super.onAuthenticationFailure(request, response, exception); - } -} +package stirling.software.SPDF.config.security; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.LockedException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Component +public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { + + @Autowired private final LoginAttemptService loginAttemptService; + + @Autowired + public CustomAuthenticationFailureHandler(LoginAttemptService loginAttemptService) { + this.loginAttemptService = loginAttemptService; + } + + @Override + public void onAuthenticationFailure( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException exception) + throws IOException, ServletException { + String ip = request.getRemoteAddr(); + logger.error("Failed login attempt from IP: " + ip); + + String username = request.getParameter("username"); + if (loginAttemptService.loginAttemptCheck(username)) { + setDefaultFailureUrl("/login?error=locked"); + + } else { + if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) { + setDefaultFailureUrl("/login?error=badcredentials"); + } else if (exception.getClass().isAssignableFrom(LockedException.class)) { + setDefaultFailureUrl("/login?error=locked"); + } + } + + super.onAuthenticationFailure(request, response, exception); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/CustomUserDetailsService.java b/src/main/java/stirling/software/SPDF/config/security/CustomUserDetailsService.java index 021cdc31..cd048eb9 100644 --- a/src/main/java/stirling/software/SPDF/config/security/CustomUserDetailsService.java +++ b/src/main/java/stirling/software/SPDF/config/security/CustomUserDetailsService.java @@ -1,57 +1,57 @@ -package stirling.software.SPDF.config.security; - -import java.util.Collection; -import java.util.Set; -import java.util.stream.Collectors; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.LockedException; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; - -import stirling.software.SPDF.model.Authority; -import stirling.software.SPDF.model.User; -import stirling.software.SPDF.repository.UserRepository; - -@Service -public class CustomUserDetailsService implements UserDetailsService { - - @Autowired private UserRepository userRepository; - - @Autowired private LoginAttemptService loginAttemptService; - - @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - User user = - userRepository - .findByUsername(username) - .orElseThrow( - () -> - new UsernameNotFoundException( - "No user found with username: " + username)); - - if (loginAttemptService.isBlocked(username)) { - throw new LockedException( - "Your account has been locked due to too many failed login attempts."); - } - - return new org.springframework.security.core.userdetails.User( - user.getUsername(), - user.getPassword(), - user.isEnabled(), - true, - true, - true, - getAuthorities(user.getAuthorities())); - } - - private Collection getAuthorities(Set authorities) { - return authorities.stream() - .map(authority -> new SimpleGrantedAuthority(authority.getAuthority())) - .collect(Collectors.toList()); - } -} +package stirling.software.SPDF.config.security; + +import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.LockedException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import stirling.software.SPDF.model.Authority; +import stirling.software.SPDF.model.User; +import stirling.software.SPDF.repository.UserRepository; + +@Service +public class CustomUserDetailsService implements UserDetailsService { + + @Autowired private UserRepository userRepository; + + @Autowired private LoginAttemptService loginAttemptService; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = + userRepository + .findByUsername(username) + .orElseThrow( + () -> + new UsernameNotFoundException( + "No user found with username: " + username)); + + if (loginAttemptService.isBlocked(username)) { + throw new LockedException( + "Your account has been locked due to too many failed login attempts."); + } + + return new org.springframework.security.core.userdetails.User( + user.getUsername(), + user.getPassword(), + user.isEnabled(), + true, + true, + true, + getAuthorities(user.getAuthorities())); + } + + private Collection getAuthorities(Set authorities) { + return authorities.stream() + .map(authority -> new SimpleGrantedAuthority(authority.getAuthority())) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java index bf1e3661..ca88dcb9 100644 --- a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java @@ -1,140 +1,140 @@ -package stirling.software.SPDF.config.security; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Lazy; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; -import org.springframework.security.web.savedrequest.NullRequestCache; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; - -import stirling.software.SPDF.repository.JPATokenRepositoryImpl; - -@Configuration -@EnableWebSecurity() -@EnableMethodSecurity -public class SecurityConfiguration { - - @Autowired private UserDetailsService userDetailsService; - - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - - @Autowired @Lazy private UserService userService; - - @Autowired - @Qualifier("loginEnabled") - public boolean loginEnabledValue; - - @Autowired private UserAuthenticationFilter userAuthenticationFilter; - - @Autowired private LoginAttemptService loginAttemptService; - - @Autowired private FirstLoginFilter firstLoginFilter; - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - - if (loginEnabledValue) { - - http.csrf(csrf -> csrf.disable()); - http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class); - http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class); - http.formLogin( - formLogin -> - formLogin - .loginPage("/login") - .successHandler( - new CustomAuthenticationSuccessHandler()) - .defaultSuccessUrl("/") - .failureHandler( - new CustomAuthenticationFailureHandler( - loginAttemptService)) - .permitAll()) - .requestCache(requestCache -> requestCache.requestCache(new NullRequestCache())) - .logout( - logout -> - logout.logoutRequestMatcher( - new AntPathRequestMatcher("/logout")) - .logoutSuccessUrl("/login?logout=true") - .invalidateHttpSession(true) // Invalidate session - .deleteCookies("JSESSIONID", "remember-me")) - .rememberMe( - rememberMeConfigurer -> - rememberMeConfigurer // Use the configurator directly - .key("uniqueAndSecret") - .tokenRepository(persistentTokenRepository()) - .tokenValiditySeconds(1209600) // 2 weeks - ) - .authorizeHttpRequests( - authz -> - authz.requestMatchers( - req -> { - String uri = req.getRequestURI(); - String contextPath = req.getContextPath(); - - // Remove the context path from the URI - String trimmedUri = - uri.startsWith(contextPath) - ? uri.substring( - contextPath - .length()) - : uri; - - return trimmedUri.startsWith("/login") - || trimmedUri.endsWith(".svg") - || trimmedUri.startsWith( - "/register") - || trimmedUri.startsWith("/error") - || trimmedUri.startsWith("/images/") - || trimmedUri.startsWith("/public/") - || trimmedUri.startsWith("/css/") - || trimmedUri.startsWith("/js/") - || trimmedUri.startsWith( - "/api/v1/info/status"); - }) - .permitAll() - .anyRequest() - .authenticated()) - .userDetailsService(userDetailsService) - .authenticationProvider(authenticationProvider()); - } else { - http.csrf(csrf -> csrf.disable()) - .authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); - } - - return http.build(); - } - - @Bean - public IPRateLimitingFilter rateLimitingFilter() { - int maxRequestsPerIp = 1000000; // Example limit TODO add config level - return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp); - } - - @Bean - public DaoAuthenticationProvider authenticationProvider() { - DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); - authProvider.setUserDetailsService(userDetailsService); - authProvider.setPasswordEncoder(passwordEncoder()); - return authProvider; - } - - @Bean - public PersistentTokenRepository persistentTokenRepository() { - return new JPATokenRepositoryImpl(); - } -} +package stirling.software.SPDF.config.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; +import org.springframework.security.web.savedrequest.NullRequestCache; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +import stirling.software.SPDF.repository.JPATokenRepositoryImpl; + +@Configuration +@EnableWebSecurity() +@EnableMethodSecurity +public class SecurityConfiguration { + + @Autowired private UserDetailsService userDetailsService; + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Autowired @Lazy private UserService userService; + + @Autowired + @Qualifier("loginEnabled") + public boolean loginEnabledValue; + + @Autowired private UserAuthenticationFilter userAuthenticationFilter; + + @Autowired private LoginAttemptService loginAttemptService; + + @Autowired private FirstLoginFilter firstLoginFilter; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + + if (loginEnabledValue) { + + http.csrf(csrf -> csrf.disable()); + http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class); + http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class); + http.formLogin( + formLogin -> + formLogin + .loginPage("/login") + .successHandler( + new CustomAuthenticationSuccessHandler()) + .defaultSuccessUrl("/") + .failureHandler( + new CustomAuthenticationFailureHandler( + loginAttemptService)) + .permitAll()) + .requestCache(requestCache -> requestCache.requestCache(new NullRequestCache())) + .logout( + logout -> + logout.logoutRequestMatcher( + new AntPathRequestMatcher("/logout")) + .logoutSuccessUrl("/login?logout=true") + .invalidateHttpSession(true) // Invalidate session + .deleteCookies("JSESSIONID", "remember-me")) + .rememberMe( + rememberMeConfigurer -> + rememberMeConfigurer // Use the configurator directly + .key("uniqueAndSecret") + .tokenRepository(persistentTokenRepository()) + .tokenValiditySeconds(1209600) // 2 weeks + ) + .authorizeHttpRequests( + authz -> + authz.requestMatchers( + req -> { + String uri = req.getRequestURI(); + String contextPath = req.getContextPath(); + + // Remove the context path from the URI + String trimmedUri = + uri.startsWith(contextPath) + ? uri.substring( + contextPath + .length()) + : uri; + + return trimmedUri.startsWith("/login") + || trimmedUri.endsWith(".svg") + || trimmedUri.startsWith( + "/register") + || trimmedUri.startsWith("/error") + || trimmedUri.startsWith("/images/") + || trimmedUri.startsWith("/public/") + || trimmedUri.startsWith("/css/") + || trimmedUri.startsWith("/js/") + || trimmedUri.startsWith( + "/api/v1/info/status"); + }) + .permitAll() + .anyRequest() + .authenticated()) + .userDetailsService(userDetailsService) + .authenticationProvider(authenticationProvider()); + } else { + http.csrf(csrf -> csrf.disable()) + .authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); + } + + return http.build(); + } + + @Bean + public IPRateLimitingFilter rateLimitingFilter() { + int maxRequestsPerIp = 1000000; // Example limit TODO add config level + return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp); + } + + @Bean + public DaoAuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(userDetailsService); + authProvider.setPasswordEncoder(passwordEncoder()); + return authProvider; + } + + @Bean + public PersistentTokenRepository persistentTokenRepository() { + return new JPATokenRepositoryImpl(); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java index d12fc72b..47423eb6 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java @@ -1,118 +1,118 @@ -package stirling.software.SPDF.config.security; - -import java.io.IOException; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Lazy; -import org.springframework.http.HttpStatus; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import stirling.software.SPDF.model.ApiKeyAuthenticationToken; - -@Component -public class UserAuthenticationFilter extends OncePerRequestFilter { - - @Autowired private UserDetailsService userDetailsService; - - @Autowired @Lazy private UserService userService; - - @Autowired - @Qualifier("loginEnabled") - public boolean loginEnabledValue; - - @Override - protected void doFilterInternal( - HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - if (!loginEnabledValue) { - // If login is not enabled, just pass all requests without authentication - filterChain.doFilter(request, response); - return; - } - String requestURI = request.getRequestURI(); - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - - // Check for API key in the request headers if no authentication exists - if (authentication == null || !authentication.isAuthenticated()) { - 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 - // provider for API keys. - UserDetails userDetails = userService.loadUserByApiKey(apiKey); - if (userDetails == null) { - response.setStatus(HttpStatus.UNAUTHORIZED.value()); - response.getWriter().write("Invalid API Key."); - return; - } - authentication = - new ApiKeyAuthenticationToken( - userDetails, apiKey, userDetails.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authentication); - } catch (AuthenticationException e) { - // If API key authentication fails, deny the request - response.setStatus(HttpStatus.UNAUTHORIZED.value()); - response.getWriter().write("Invalid API Key."); - return; - } - } - } - - // If we still don't have any authentication, deny the request - if (authentication == null || !authentication.isAuthenticated()) { - String method = request.getMethod(); - String contextPath = request.getContextPath(); - - if ("GET".equalsIgnoreCase(method) && !(contextPath + "/login").equals(requestURI)) { - response.sendRedirect(contextPath + "/login"); // redirect to the login page - return; - } else { - response.setStatus(HttpStatus.UNAUTHORIZED.value()); - response.getWriter() - .write( - "Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternativly you can disable authentication if this is unexpected"); - return; - } - } - - filterChain.doFilter(request, response); - } - - @Override - protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { - String uri = request.getRequestURI(); - String contextPath = request.getContextPath(); - String[] permitAllPatterns = { - contextPath + "/login", - contextPath + "/register", - contextPath + "/error", - contextPath + "/images/", - contextPath + "/public/", - contextPath + "/css/", - contextPath + "/js/", - contextPath + "/pdfjs/", - contextPath + "/api/v1/info/status", - contextPath + "/site.webmanifest" - }; - - for (String pattern : permitAllPatterns) { - if (uri.startsWith(pattern) || uri.endsWith(".svg")) { - return true; - } - } - - return false; - } -} +package stirling.software.SPDF.config.security; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import stirling.software.SPDF.model.ApiKeyAuthenticationToken; + +@Component +public class UserAuthenticationFilter extends OncePerRequestFilter { + + @Autowired private UserDetailsService userDetailsService; + + @Autowired @Lazy private UserService userService; + + @Autowired + @Qualifier("loginEnabled") + public boolean loginEnabledValue; + + @Override + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + if (!loginEnabledValue) { + // If login is not enabled, just pass all requests without authentication + filterChain.doFilter(request, response); + return; + } + String requestURI = request.getRequestURI(); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + // Check for API key in the request headers if no authentication exists + if (authentication == null || !authentication.isAuthenticated()) { + 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 + // provider for API keys. + UserDetails userDetails = userService.loadUserByApiKey(apiKey); + if (userDetails == null) { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.getWriter().write("Invalid API Key."); + return; + } + authentication = + new ApiKeyAuthenticationToken( + userDetails, apiKey, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } catch (AuthenticationException e) { + // If API key authentication fails, deny the request + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.getWriter().write("Invalid API Key."); + return; + } + } + } + + // If we still don't have any authentication, deny the request + if (authentication == null || !authentication.isAuthenticated()) { + String method = request.getMethod(); + String contextPath = request.getContextPath(); + + if ("GET".equalsIgnoreCase(method) && !(contextPath + "/login").equals(requestURI)) { + response.sendRedirect(contextPath + "/login"); // redirect to the login page + return; + } else { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.getWriter() + .write( + "Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternativly you can disable authentication if this is unexpected"); + return; + } + } + + filterChain.doFilter(request, response); + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + String uri = request.getRequestURI(); + String contextPath = request.getContextPath(); + String[] permitAllPatterns = { + contextPath + "/login", + contextPath + "/register", + contextPath + "/error", + contextPath + "/images/", + contextPath + "/public/", + contextPath + "/css/", + contextPath + "/js/", + contextPath + "/pdfjs/", + contextPath + "/api/v1/info/status", + contextPath + "/site.webmanifest" + }; + + for (String pattern : permitAllPatterns) { + if (uri.startsWith(pattern) || uri.endsWith(".svg")) { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java b/src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java index 6c315971..7b3b9b4e 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java @@ -1,143 +1,143 @@ -package stirling.software.SPDF.config.security; - -import java.io.IOException; -import java.time.Duration; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.http.HttpStatus; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import io.github.bucket4j.Bandwidth; -import io.github.bucket4j.Bucket; -import io.github.bucket4j.ConsumptionProbe; -import io.github.bucket4j.Refill; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import stirling.software.SPDF.model.Role; - -@Component -public class UserBasedRateLimitingFilter extends OncePerRequestFilter { - - private final Map apiBuckets = new ConcurrentHashMap<>(); - private final Map webBuckets = new ConcurrentHashMap<>(); - - @Autowired private UserDetailsService userDetailsService; - - @Autowired - @Qualifier("rateLimit") - public boolean rateLimit; - - @Override - protected void doFilterInternal( - HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - if (!rateLimit) { - // If rateLimit is not enabled, just pass all requests without rate limiting - filterChain.doFilter(request, response); - return; - } - - String method = request.getMethod(); - if (!"POST".equalsIgnoreCase(method)) { - // If the request is not a POST, just pass it through without rate limiting - filterChain.doFilter(request, response); - return; - } - - String identifier = null; - - // Check for API key in the request headers - 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 - } else { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null && authentication.isAuthenticated()) { - UserDetails userDetails = (UserDetails) authentication.getPrincipal(); - identifier = userDetails.getUsername(); - } - } - - // If neither API key nor an authenticated user is present, use IP address - if (identifier == null) { - identifier = request.getRemoteAddr(); - } - - Role userRole = - getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication()); - - if (request.getHeader("X-API-Key") != null) { - // It's an API call - processRequest( - userRole.getApiCallsPerDay(), - identifier, - apiBuckets, - request, - response, - filterChain); - } else { - // It's a Web UI call - processRequest( - userRole.getWebCallsPerDay(), - identifier, - webBuckets, - request, - response, - filterChain); - } - } - - private Role getRoleFromAuthentication(Authentication authentication) { - if (authentication != null && authentication.isAuthenticated()) { - for (GrantedAuthority authority : authentication.getAuthorities()) { - try { - return Role.fromString(authority.getAuthority()); - } catch (IllegalArgumentException ex) { - // Ignore and continue to next authority. - } - } - } - throw new IllegalStateException("User does not have a valid role."); - } - - private void processRequest( - int limitPerDay, - String identifier, - Map buckets, - HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain) - throws IOException, ServletException { - Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket(limitPerDay)); - ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1); - - if (probe.isConsumed()) { - response.setHeader("X-Rate-Limit-Remaining", Long.toString(probe.getRemainingTokens())); - filterChain.doFilter(request, response); - } else { - long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000; - response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); - response.setHeader("X-Rate-Limit-Retry-After-Seconds", String.valueOf(waitForRefill)); - response.getWriter().write("Rate limit exceeded for POST requests."); - } - } - - private Bucket createUserBucket(int limitPerDay) { - Bandwidth limit = - Bandwidth.classic(limitPerDay, Refill.intervally(limitPerDay, Duration.ofDays(1))); - return Bucket.builder().addLimit(limit).build(); - } -} +package stirling.software.SPDF.config.security; + +import java.io.IOException; +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import io.github.bucket4j.Bandwidth; +import io.github.bucket4j.Bucket; +import io.github.bucket4j.ConsumptionProbe; +import io.github.bucket4j.Refill; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import stirling.software.SPDF.model.Role; + +@Component +public class UserBasedRateLimitingFilter extends OncePerRequestFilter { + + private final Map apiBuckets = new ConcurrentHashMap<>(); + private final Map webBuckets = new ConcurrentHashMap<>(); + + @Autowired private UserDetailsService userDetailsService; + + @Autowired + @Qualifier("rateLimit") + public boolean rateLimit; + + @Override + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + if (!rateLimit) { + // If rateLimit is not enabled, just pass all requests without rate limiting + filterChain.doFilter(request, response); + return; + } + + String method = request.getMethod(); + if (!"POST".equalsIgnoreCase(method)) { + // If the request is not a POST, just pass it through without rate limiting + filterChain.doFilter(request, response); + return; + } + + String identifier = null; + + // Check for API key in the request headers + 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 + } else { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication.isAuthenticated()) { + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + identifier = userDetails.getUsername(); + } + } + + // If neither API key nor an authenticated user is present, use IP address + if (identifier == null) { + identifier = request.getRemoteAddr(); + } + + Role userRole = + getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication()); + + if (request.getHeader("X-API-Key") != null) { + // It's an API call + processRequest( + userRole.getApiCallsPerDay(), + identifier, + apiBuckets, + request, + response, + filterChain); + } else { + // It's a Web UI call + processRequest( + userRole.getWebCallsPerDay(), + identifier, + webBuckets, + request, + response, + filterChain); + } + } + + private Role getRoleFromAuthentication(Authentication authentication) { + if (authentication != null && authentication.isAuthenticated()) { + for (GrantedAuthority authority : authentication.getAuthorities()) { + try { + return Role.fromString(authority.getAuthority()); + } catch (IllegalArgumentException ex) { + // Ignore and continue to next authority. + } + } + } + throw new IllegalStateException("User does not have a valid role."); + } + + private void processRequest( + int limitPerDay, + String identifier, + Map buckets, + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) + throws IOException, ServletException { + Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket(limitPerDay)); + ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1); + + if (probe.isConsumed()) { + response.setHeader("X-Rate-Limit-Remaining", Long.toString(probe.getRemainingTokens())); + filterChain.doFilter(request, response); + } else { + long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000; + response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); + response.setHeader("X-Rate-Limit-Retry-After-Seconds", String.valueOf(waitForRefill)); + response.getWriter().write("Rate limit exceeded for POST requests."); + } + } + + private Bucket createUserBucket(int limitPerDay) { + Bandwidth limit = + Bandwidth.classic(limitPerDay, Refill.intervally(limitPerDay, Duration.ofDays(1))); + return Bucket.builder().addLimit(limit).build(); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/UserService.java b/src/main/java/stirling/software/SPDF/config/security/UserService.java index 986bb16f..60b3ebef 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserService.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserService.java @@ -1,197 +1,197 @@ -package stirling.software.SPDF.config.security; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.stream.Collectors; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; - -import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface; -import stirling.software.SPDF.model.Authority; -import stirling.software.SPDF.model.Role; -import stirling.software.SPDF.model.User; -import stirling.software.SPDF.repository.UserRepository; - -@Service -public class UserService implements UserServiceInterface { - - @Autowired private UserRepository userRepository; - - @Autowired private PasswordEncoder passwordEncoder; - - public Authentication getAuthentication(String apiKey) { - User user = getUserByApiKey(apiKey); - if (user == null) { - throw new UsernameNotFoundException("API key is not valid"); - } - - // Convert the user into an Authentication object - return new UsernamePasswordAuthenticationToken( - user, // principal (typically the user) - null, // credentials (we don't expose the password or API key here) - getAuthorities(user) // user's authorities (roles/permissions) - ); - } - - private Collection getAuthorities(User user) { - // Convert each Authority object into a SimpleGrantedAuthority object. - return user.getAuthorities().stream() - .map((Authority authority) -> new SimpleGrantedAuthority(authority.getAuthority())) - .collect(Collectors.toList()); - } - - private String generateApiKey() { - String apiKey; - do { - apiKey = UUID.randomUUID().toString(); - } while (userRepository.findByApiKey(apiKey) != null); // Ensure uniqueness - return apiKey; - } - - public User addApiKeyToUser(String username) { - User user = - userRepository - .findByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found")); - - user.setApiKey(generateApiKey()); - return userRepository.save(user); - } - - public User refreshApiKeyForUser(String username) { - return addApiKeyToUser(username); // reuse the add API key method for refreshing - } - - public String getApiKeyForUser(String username) { - User user = - userRepository - .findByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found")); - return user.getApiKey(); - } - - public boolean isValidApiKey(String apiKey) { - return userRepository.findByApiKey(apiKey) != null; - } - - public User getUserByApiKey(String apiKey) { - return userRepository.findByApiKey(apiKey); - } - - public UserDetails loadUserByApiKey(String apiKey) { - User userOptional = userRepository.findByApiKey(apiKey); - if (userOptional != null) { - User user = userOptional; - // Convert your User entity to a UserDetails object with authorities - return new org.springframework.security.core.userdetails.User( - user.getUsername(), - user.getPassword(), // you might not need this for API key auth - getAuthorities(user)); - } - return null; // or throw an exception - } - - public boolean validateApiKeyForUser(String username, String apiKey) { - Optional userOpt = userRepository.findByUsername(username); - return userOpt.isPresent() && userOpt.get().getApiKey().equals(apiKey); - } - - public void saveUser(String username, String password) { - User user = new User(); - user.setUsername(username); - user.setPassword(passwordEncoder.encode(password)); - user.setEnabled(true); - userRepository.save(user); - } - - public void saveUser(String username, String password, String role, boolean firstLogin) { - User user = new User(); - user.setUsername(username); - user.setPassword(passwordEncoder.encode(password)); - user.addAuthority(new Authority(role, user)); - user.setEnabled(true); - user.setFirstLogin(firstLogin); - userRepository.save(user); - } - - public void saveUser(String username, String password, String role) { - User user = new User(); - user.setUsername(username); - user.setPassword(passwordEncoder.encode(password)); - user.addAuthority(new Authority(role, user)); - user.setEnabled(true); - user.setFirstLogin(false); - userRepository.save(user); - } - - public void deleteUser(String username) { - Optional userOpt = userRepository.findByUsername(username); - if (userOpt.isPresent()) { - for (Authority authority : userOpt.get().getAuthorities()) { - if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) { - return; - } - } - userRepository.delete(userOpt.get()); - } - } - - public boolean usernameExists(String username) { - return userRepository.findByUsername(username).isPresent(); - } - - public boolean hasUsers() { - return userRepository.count() > 0; - } - - public void updateUserSettings(String username, Map updates) { - Optional userOpt = userRepository.findByUsername(username); - if (userOpt.isPresent()) { - User user = userOpt.get(); - Map settingsMap = user.getSettings(); - - if (settingsMap == null) { - settingsMap = new HashMap(); - } - settingsMap.clear(); - settingsMap.putAll(updates); - user.setSettings(settingsMap); - - userRepository.save(user); - } - } - - public Optional findByUsername(String username) { - return userRepository.findByUsername(username); - } - - public void changeUsername(User user, String newUsername) { - user.setUsername(newUsername); - userRepository.save(user); - } - - public void changePassword(User user, String newPassword) { - user.setPassword(passwordEncoder.encode(newPassword)); - userRepository.save(user); - } - - public void changeFirstUse(User user, boolean firstUse) { - user.setFirstLogin(firstUse); - userRepository.save(user); - } - - public boolean isPasswordCorrect(User user, String currentPassword) { - return passwordEncoder.matches(currentPassword, user.getPassword()); - } -} +package stirling.software.SPDF.config.security; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface; +import stirling.software.SPDF.model.Authority; +import stirling.software.SPDF.model.Role; +import stirling.software.SPDF.model.User; +import stirling.software.SPDF.repository.UserRepository; + +@Service +public class UserService implements UserServiceInterface { + + @Autowired private UserRepository userRepository; + + @Autowired private PasswordEncoder passwordEncoder; + + public Authentication getAuthentication(String apiKey) { + User user = getUserByApiKey(apiKey); + if (user == null) { + throw new UsernameNotFoundException("API key is not valid"); + } + + // Convert the user into an Authentication object + return new UsernamePasswordAuthenticationToken( + user, // principal (typically the user) + null, // credentials (we don't expose the password or API key here) + getAuthorities(user) // user's authorities (roles/permissions) + ); + } + + private Collection getAuthorities(User user) { + // Convert each Authority object into a SimpleGrantedAuthority object. + return user.getAuthorities().stream() + .map((Authority authority) -> new SimpleGrantedAuthority(authority.getAuthority())) + .collect(Collectors.toList()); + } + + private String generateApiKey() { + String apiKey; + do { + apiKey = UUID.randomUUID().toString(); + } while (userRepository.findByApiKey(apiKey) != null); // Ensure uniqueness + return apiKey; + } + + public User addApiKeyToUser(String username) { + User user = + userRepository + .findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + + user.setApiKey(generateApiKey()); + return userRepository.save(user); + } + + public User refreshApiKeyForUser(String username) { + return addApiKeyToUser(username); // reuse the add API key method for refreshing + } + + public String getApiKeyForUser(String username) { + User user = + userRepository + .findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + return user.getApiKey(); + } + + public boolean isValidApiKey(String apiKey) { + return userRepository.findByApiKey(apiKey) != null; + } + + public User getUserByApiKey(String apiKey) { + return userRepository.findByApiKey(apiKey); + } + + public UserDetails loadUserByApiKey(String apiKey) { + User userOptional = userRepository.findByApiKey(apiKey); + if (userOptional != null) { + User user = userOptional; + // Convert your User entity to a UserDetails object with authorities + return new org.springframework.security.core.userdetails.User( + user.getUsername(), + user.getPassword(), // you might not need this for API key auth + getAuthorities(user)); + } + return null; // or throw an exception + } + + public boolean validateApiKeyForUser(String username, String apiKey) { + Optional userOpt = userRepository.findByUsername(username); + return userOpt.isPresent() && userOpt.get().getApiKey().equals(apiKey); + } + + public void saveUser(String username, String password) { + User user = new User(); + user.setUsername(username); + user.setPassword(passwordEncoder.encode(password)); + user.setEnabled(true); + userRepository.save(user); + } + + public void saveUser(String username, String password, String role, boolean firstLogin) { + User user = new User(); + user.setUsername(username); + user.setPassword(passwordEncoder.encode(password)); + user.addAuthority(new Authority(role, user)); + user.setEnabled(true); + user.setFirstLogin(firstLogin); + userRepository.save(user); + } + + public void saveUser(String username, String password, String role) { + User user = new User(); + user.setUsername(username); + user.setPassword(passwordEncoder.encode(password)); + user.addAuthority(new Authority(role, user)); + user.setEnabled(true); + user.setFirstLogin(false); + userRepository.save(user); + } + + public void deleteUser(String username) { + Optional userOpt = userRepository.findByUsername(username); + if (userOpt.isPresent()) { + for (Authority authority : userOpt.get().getAuthorities()) { + if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) { + return; + } + } + userRepository.delete(userOpt.get()); + } + } + + public boolean usernameExists(String username) { + return userRepository.findByUsername(username).isPresent(); + } + + public boolean hasUsers() { + return userRepository.count() > 0; + } + + public void updateUserSettings(String username, Map updates) { + Optional userOpt = userRepository.findByUsername(username); + if (userOpt.isPresent()) { + User user = userOpt.get(); + Map settingsMap = user.getSettings(); + + if (settingsMap == null) { + settingsMap = new HashMap(); + } + settingsMap.clear(); + settingsMap.putAll(updates); + user.setSettings(settingsMap); + + userRepository.save(user); + } + } + + public Optional findByUsername(String username) { + return userRepository.findByUsername(username); + } + + public void changeUsername(User user, String newUsername) { + user.setUsername(newUsername); + userRepository.save(user); + } + + public void changePassword(User user, String newPassword) { + user.setPassword(passwordEncoder.encode(newPassword)); + userRepository.save(user); + } + + public void changeFirstUse(User user, boolean firstUse) { + user.setFirstLogin(firstUse); + userRepository.save(user); + } + + public boolean isPasswordCorrect(User user, String currentPassword) { + return passwordEncoder.matches(currentPassword, user.getPassword()); + } +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java index bc4e2cce..71dca371 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java @@ -119,11 +119,10 @@ public class SplitPdfBySectionsController { // Set clipping area and position float translateX = -subPageWidth * i; float translateY = height - subPageHeight * (verticalDivisions - j); - - - //Code for google Docs pdfs.. - //float translateY = -subPageHeight * (verticalDivisions - 1 - j); - + + // Code for google Docs pdfs.. + // float translateY = -subPageHeight * (verticalDivisions - 1 - j); + contentStream.saveGraphicsState(); contentStream.addRect(0, 0, subPageWidth, subPageHeight); contentStream.clip(); diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java index bf631c87..815018e8 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java @@ -1,77 +1,77 @@ -package stirling.software.SPDF.controller.api.converters; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; - -import stirling.software.SPDF.model.api.converters.UrlToPdfRequest; -import stirling.software.SPDF.utils.GeneralUtils; -import stirling.software.SPDF.utils.ProcessExecutor; -import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; -import stirling.software.SPDF.utils.WebResponseUtils; - -@RestController -@Tag(name = "Convert", description = "Convert APIs") -@RequestMapping("/api/v1/convert") -public class ConvertWebsiteToPDF { - - @PostMapping(consumes = "multipart/form-data", value = "/url/pdf") - @Operation( - summary = "Convert a URL to a PDF", - description = - "This endpoint fetches content from a URL and converts it to a PDF format. Input:N/A Output:PDF Type:SISO") - public ResponseEntity urlToPdf(@ModelAttribute UrlToPdfRequest request) - throws IOException, InterruptedException { - String URL = request.getUrlInput(); - - // Validate the URL format - if (!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) { - throw new IllegalArgumentException("Invalid URL format provided."); - } - Path tempOutputFile = null; - byte[] pdfBytes; - try { - // Prepare the output file path - tempOutputFile = Files.createTempFile("output_", ".pdf"); - - // Prepare the OCRmyPDF command - List command = new ArrayList<>(); - command.add("weasyprint"); - command.add(URL); - command.add(tempOutputFile.toString()); - - ProcessExecutorResult returnCode = - ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) - .runCommandWithOutputHandling(command); - - // Read the optimized PDF file - pdfBytes = Files.readAllBytes(tempOutputFile); - } finally { - // Clean up the temporary files - Files.delete(tempOutputFile); - } - // Convert URL to a safe filename - String outputFilename = convertURLToFileName(URL); - - return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); - } - - private String convertURLToFileName(String url) { - String safeName = url.replaceAll("[^a-zA-Z0-9]", "_"); - if (safeName.length() > 50) { - safeName = safeName.substring(0, 50); // restrict to 50 characters - } - return safeName + ".pdf"; - } -} +package stirling.software.SPDF.controller.api.converters; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import stirling.software.SPDF.model.api.converters.UrlToPdfRequest; +import stirling.software.SPDF.utils.GeneralUtils; +import stirling.software.SPDF.utils.ProcessExecutor; +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; +import stirling.software.SPDF.utils.WebResponseUtils; + +@RestController +@Tag(name = "Convert", description = "Convert APIs") +@RequestMapping("/api/v1/convert") +public class ConvertWebsiteToPDF { + + @PostMapping(consumes = "multipart/form-data", value = "/url/pdf") + @Operation( + summary = "Convert a URL to a PDF", + description = + "This endpoint fetches content from a URL and converts it to a PDF format. Input:N/A Output:PDF Type:SISO") + public ResponseEntity urlToPdf(@ModelAttribute UrlToPdfRequest request) + throws IOException, InterruptedException { + String URL = request.getUrlInput(); + + // Validate the URL format + if (!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) { + throw new IllegalArgumentException("Invalid URL format provided."); + } + Path tempOutputFile = null; + byte[] pdfBytes; + try { + // Prepare the output file path + tempOutputFile = Files.createTempFile("output_", ".pdf"); + + // Prepare the OCRmyPDF command + List command = new ArrayList<>(); + command.add("weasyprint"); + command.add(URL); + command.add(tempOutputFile.toString()); + + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) + .runCommandWithOutputHandling(command); + + // Read the optimized PDF file + pdfBytes = Files.readAllBytes(tempOutputFile); + } finally { + // Clean up the temporary files + Files.delete(tempOutputFile); + } + // Convert URL to a safe filename + String outputFilename = convertURLToFileName(URL); + + return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); + } + + private String convertURLToFileName(String url) { + String safeName = url.replaceAll("[^a-zA-Z0-9]", "_"); + if (safeName.length() > 50) { + safeName = safeName.substring(0, 50); // restrict to 50 characters + } + return safeName + ".pdf"; + } +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/filters/FilterController.java b/src/main/java/stirling/software/SPDF/controller/api/filters/FilterController.java index d159f974..967978a7 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/filters/FilterController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/filters/FilterController.java @@ -1,210 +1,210 @@ -package stirling.software.SPDF.controller.api.filters; - -import java.io.IOException; - -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.PDPage; -import org.apache.pdfbox.pdmodel.common.PDRectangle; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; - -import stirling.software.SPDF.model.api.PDFComparisonAndCount; -import stirling.software.SPDF.model.api.PDFWithPageNums; -import stirling.software.SPDF.model.api.filter.ContainsTextRequest; -import stirling.software.SPDF.model.api.filter.FileSizeRequest; -import stirling.software.SPDF.model.api.filter.PageRotationRequest; -import stirling.software.SPDF.model.api.filter.PageSizeRequest; -import stirling.software.SPDF.utils.PdfUtils; -import stirling.software.SPDF.utils.WebResponseUtils; - -@RestController -@RequestMapping("/api/v1/filter") -@Tag(name = "Filter", description = "Filter APIs") -public class FilterController { - - @PostMapping(consumes = "multipart/form-data", value = "/filter-contains-text") - @Operation( - summary = "Checks if a PDF contains set text, returns true if does", - description = "Input:PDF Output:Boolean Type:SISO") - public ResponseEntity containsText(@ModelAttribute ContainsTextRequest request) - throws IOException, InterruptedException { - MultipartFile inputFile = request.getFileInput(); - String text = request.getText(); - String pageNumber = request.getPageNumbers(); - - PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); - if (PdfUtils.hasText(pdfDocument, pageNumber, text)) - return WebResponseUtils.pdfDocToWebResponse( - pdfDocument, inputFile.getOriginalFilename()); - return null; - } - - // TODO - @PostMapping(consumes = "multipart/form-data", value = "/filter-contains-image") - @Operation( - summary = "Checks if a PDF contains an image", - description = "Input:PDF Output:Boolean Type:SISO") - public ResponseEntity containsImage(@ModelAttribute PDFWithPageNums request) - throws IOException, InterruptedException { - MultipartFile inputFile = request.getFileInput(); - String pageNumber = request.getPageNumbers(); - - PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); - if (PdfUtils.hasImages(pdfDocument, pageNumber)) - return WebResponseUtils.pdfDocToWebResponse( - pdfDocument, inputFile.getOriginalFilename()); - return null; - } - - @PostMapping(consumes = "multipart/form-data", value = "/filter-page-count") - @Operation( - summary = "Checks if a PDF is greater, less or equal to a setPageCount", - description = "Input:PDF Output:Boolean Type:SISO") - public ResponseEntity pageCount(@ModelAttribute PDFComparisonAndCount request) - throws IOException, InterruptedException { - MultipartFile inputFile = request.getFileInput(); - String pageCount = request.getPageCount(); - String comparator = request.getComparator(); - // Load the PDF - PDDocument document = PDDocument.load(inputFile.getInputStream()); - int actualPageCount = document.getNumberOfPages(); - - boolean valid = false; - // Perform the comparison - switch (comparator) { - case "Greater": - valid = actualPageCount > Integer.parseInt(pageCount); - break; - case "Equal": - valid = actualPageCount == Integer.parseInt(pageCount); - break; - case "Less": - valid = actualPageCount < Integer.parseInt(pageCount); - break; - default: - throw new IllegalArgumentException("Invalid comparator: " + comparator); - } - - if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); - return null; - } - - @PostMapping(consumes = "multipart/form-data", value = "/filter-page-size") - @Operation( - summary = "Checks if a PDF is of a certain size", - description = "Input:PDF Output:Boolean Type:SISO") - public ResponseEntity pageSize(@ModelAttribute PageSizeRequest request) - throws IOException, InterruptedException { - MultipartFile inputFile = request.getFileInput(); - String standardPageSize = request.getStandardPageSize(); - String comparator = request.getComparator(); - - // Load the PDF - PDDocument document = PDDocument.load(inputFile.getInputStream()); - - PDPage firstPage = document.getPage(0); - PDRectangle actualPageSize = firstPage.getMediaBox(); - - // Calculate the area of the actual page size - float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight(); - - // Get the standard size and calculate its area - PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize); - float standardArea = standardSize.getWidth() * standardSize.getHeight(); - - boolean valid = false; - // Perform the comparison - switch (comparator) { - case "Greater": - valid = actualArea > standardArea; - break; - case "Equal": - valid = actualArea == standardArea; - break; - case "Less": - valid = actualArea < standardArea; - break; - default: - throw new IllegalArgumentException("Invalid comparator: " + comparator); - } - - if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); - return null; - } - - @PostMapping(consumes = "multipart/form-data", value = "/filter-file-size") - @Operation( - summary = "Checks if a PDF is a set file size", - description = "Input:PDF Output:Boolean Type:SISO") - public ResponseEntity fileSize(@ModelAttribute FileSizeRequest request) - throws IOException, InterruptedException { - MultipartFile inputFile = request.getFileInput(); - String fileSize = request.getFileSize(); - String comparator = request.getComparator(); - - // Get the file size - long actualFileSize = inputFile.getSize(); - - boolean valid = false; - // Perform the comparison - switch (comparator) { - case "Greater": - valid = actualFileSize > Long.parseLong(fileSize); - break; - case "Equal": - valid = actualFileSize == Long.parseLong(fileSize); - break; - case "Less": - valid = actualFileSize < Long.parseLong(fileSize); - break; - default: - throw new IllegalArgumentException("Invalid comparator: " + comparator); - } - - if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); - return null; - } - - @PostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation") - @Operation( - summary = "Checks if a PDF is of a certain rotation", - description = "Input:PDF Output:Boolean Type:SISO") - public ResponseEntity pageRotation(@ModelAttribute PageRotationRequest request) - throws IOException, InterruptedException { - MultipartFile inputFile = request.getFileInput(); - int rotation = request.getRotation(); - String comparator = request.getComparator(); - - // Load the PDF - PDDocument document = PDDocument.load(inputFile.getInputStream()); - - // Get the rotation of the first page - PDPage firstPage = document.getPage(0); - int actualRotation = firstPage.getRotation(); - boolean valid = false; - // Perform the comparison - switch (comparator) { - case "Greater": - valid = actualRotation > rotation; - break; - case "Equal": - valid = actualRotation == rotation; - break; - case "Less": - valid = actualRotation < rotation; - break; - default: - throw new IllegalArgumentException("Invalid comparator: " + comparator); - } - - if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); - return null; - } -} +package stirling.software.SPDF.controller.api.filters; + +import java.io.IOException; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import stirling.software.SPDF.model.api.PDFComparisonAndCount; +import stirling.software.SPDF.model.api.PDFWithPageNums; +import stirling.software.SPDF.model.api.filter.ContainsTextRequest; +import stirling.software.SPDF.model.api.filter.FileSizeRequest; +import stirling.software.SPDF.model.api.filter.PageRotationRequest; +import stirling.software.SPDF.model.api.filter.PageSizeRequest; +import stirling.software.SPDF.utils.PdfUtils; +import stirling.software.SPDF.utils.WebResponseUtils; + +@RestController +@RequestMapping("/api/v1/filter") +@Tag(name = "Filter", description = "Filter APIs") +public class FilterController { + + @PostMapping(consumes = "multipart/form-data", value = "/filter-contains-text") + @Operation( + summary = "Checks if a PDF contains set text, returns true if does", + description = "Input:PDF Output:Boolean Type:SISO") + public ResponseEntity containsText(@ModelAttribute ContainsTextRequest request) + throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String text = request.getText(); + String pageNumber = request.getPageNumbers(); + + PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); + if (PdfUtils.hasText(pdfDocument, pageNumber, text)) + return WebResponseUtils.pdfDocToWebResponse( + pdfDocument, inputFile.getOriginalFilename()); + return null; + } + + // TODO + @PostMapping(consumes = "multipart/form-data", value = "/filter-contains-image") + @Operation( + summary = "Checks if a PDF contains an image", + description = "Input:PDF Output:Boolean Type:SISO") + public ResponseEntity containsImage(@ModelAttribute PDFWithPageNums request) + throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String pageNumber = request.getPageNumbers(); + + PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); + if (PdfUtils.hasImages(pdfDocument, pageNumber)) + return WebResponseUtils.pdfDocToWebResponse( + pdfDocument, inputFile.getOriginalFilename()); + return null; + } + + @PostMapping(consumes = "multipart/form-data", value = "/filter-page-count") + @Operation( + summary = "Checks if a PDF is greater, less or equal to a setPageCount", + description = "Input:PDF Output:Boolean Type:SISO") + public ResponseEntity pageCount(@ModelAttribute PDFComparisonAndCount request) + throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String pageCount = request.getPageCount(); + String comparator = request.getComparator(); + // Load the PDF + PDDocument document = PDDocument.load(inputFile.getInputStream()); + int actualPageCount = document.getNumberOfPages(); + + boolean valid = false; + // Perform the comparison + switch (comparator) { + case "Greater": + valid = actualPageCount > Integer.parseInt(pageCount); + break; + case "Equal": + valid = actualPageCount == Integer.parseInt(pageCount); + break; + case "Less": + valid = actualPageCount < Integer.parseInt(pageCount); + break; + default: + throw new IllegalArgumentException("Invalid comparator: " + comparator); + } + + if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); + return null; + } + + @PostMapping(consumes = "multipart/form-data", value = "/filter-page-size") + @Operation( + summary = "Checks if a PDF is of a certain size", + description = "Input:PDF Output:Boolean Type:SISO") + public ResponseEntity pageSize(@ModelAttribute PageSizeRequest request) + throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String standardPageSize = request.getStandardPageSize(); + String comparator = request.getComparator(); + + // Load the PDF + PDDocument document = PDDocument.load(inputFile.getInputStream()); + + PDPage firstPage = document.getPage(0); + PDRectangle actualPageSize = firstPage.getMediaBox(); + + // Calculate the area of the actual page size + float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight(); + + // Get the standard size and calculate its area + PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize); + float standardArea = standardSize.getWidth() * standardSize.getHeight(); + + boolean valid = false; + // Perform the comparison + switch (comparator) { + case "Greater": + valid = actualArea > standardArea; + break; + case "Equal": + valid = actualArea == standardArea; + break; + case "Less": + valid = actualArea < standardArea; + break; + default: + throw new IllegalArgumentException("Invalid comparator: " + comparator); + } + + if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); + return null; + } + + @PostMapping(consumes = "multipart/form-data", value = "/filter-file-size") + @Operation( + summary = "Checks if a PDF is a set file size", + description = "Input:PDF Output:Boolean Type:SISO") + public ResponseEntity fileSize(@ModelAttribute FileSizeRequest request) + throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + String fileSize = request.getFileSize(); + String comparator = request.getComparator(); + + // Get the file size + long actualFileSize = inputFile.getSize(); + + boolean valid = false; + // Perform the comparison + switch (comparator) { + case "Greater": + valid = actualFileSize > Long.parseLong(fileSize); + break; + case "Equal": + valid = actualFileSize == Long.parseLong(fileSize); + break; + case "Less": + valid = actualFileSize < Long.parseLong(fileSize); + break; + default: + throw new IllegalArgumentException("Invalid comparator: " + comparator); + } + + if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); + return null; + } + + @PostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation") + @Operation( + summary = "Checks if a PDF is of a certain rotation", + description = "Input:PDF Output:Boolean Type:SISO") + public ResponseEntity pageRotation(@ModelAttribute PageRotationRequest request) + throws IOException, InterruptedException { + MultipartFile inputFile = request.getFileInput(); + int rotation = request.getRotation(); + String comparator = request.getComparator(); + + // Load the PDF + PDDocument document = PDDocument.load(inputFile.getInputStream()); + + // Get the rotation of the first page + PDPage firstPage = document.getPage(0); + int actualRotation = firstPage.getRotation(); + boolean valid = false; + // Perform the comparison + switch (comparator) { + case "Greater": + valid = actualRotation > rotation; + break; + case "Equal": + valid = actualRotation == rotation; + break; + case "Less": + valid = actualRotation < rotation; + break; + default: + throw new IllegalArgumentException("Invalid comparator: " + comparator); + } + + if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); + return null; + } +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/PageNumbersController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/PageNumbersController.java index 6c302524..0ae9a49e 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/PageNumbersController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/PageNumbersController.java @@ -1,150 +1,150 @@ -package stirling.software.SPDF.controller.api.misc; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.List; - -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.PDPage; -import org.apache.pdfbox.pdmodel.PDPageContentStream; -import org.apache.pdfbox.pdmodel.common.PDRectangle; -import org.apache.pdfbox.pdmodel.font.PDType1Font; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; - -import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest; -import stirling.software.SPDF.utils.GeneralUtils; -import stirling.software.SPDF.utils.WebResponseUtils; - -@RestController -@RequestMapping("/api/v1/misc") -@Tag(name = "Misc", description = "Miscellaneous APIs") -public class PageNumbersController { - - private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class); - - @PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data") - @Operation( - summary = "Add page numbers to a PDF document", - description = - "This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO") - public ResponseEntity addPageNumbers(@ModelAttribute AddPageNumbersRequest request) - throws IOException { - MultipartFile file = request.getFileInput(); - String customMargin = request.getCustomMargin(); - int position = request.getPosition(); - int startingNumber = request.getStartingNumber(); - String pagesToNumber = request.getPagesToNumber(); - String customText = request.getCustomText(); - int pageNumber = startingNumber; - byte[] fileBytes = file.getBytes(); - PDDocument document = PDDocument.load(fileBytes); - - float marginFactor; - switch (customMargin.toLowerCase()) { - case "small": - marginFactor = 0.02f; - break; - case "medium": - marginFactor = 0.035f; - break; - case "large": - marginFactor = 0.05f; - break; - case "x-large": - marginFactor = 0.075f; - break; - - default: - marginFactor = 0.035f; - break; - } - - float fontSize = 12.0f; - PDType1Font font = PDType1Font.HELVETICA; - if (pagesToNumber == null || pagesToNumber.length() == 0) { - pagesToNumber = "all"; - } - if (customText == null || customText.length() == 0) { - customText = "{n}"; - } - List pagesToNumberList = - GeneralUtils.parsePageList(pagesToNumber.split(","), document.getNumberOfPages()); - - for (int i : pagesToNumberList) { - PDPage page = document.getPage(i); - PDRectangle pageSize = page.getMediaBox(); - - String text = - customText != null - ? customText - .replace("{n}", String.valueOf(pageNumber)) - .replace("{total}", String.valueOf(document.getNumberOfPages())) - .replace( - "{filename}", - file.getOriginalFilename() - .replaceFirst("[.][^.]+$", "")) - : String.valueOf(pageNumber); - - float x, y; - - int xGroup = (position - 1) % 3; - int yGroup = 2 - (position - 1) / 3; - - switch (xGroup) { - case 0: // left - x = pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth(); - break; - case 1: // center - x = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2); - break; - default: // right - x = pageSize.getUpperRightX() - marginFactor * pageSize.getWidth(); - break; - } - - switch (yGroup) { - case 0: // bottom - y = pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight(); - break; - case 1: // middle - y = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2); - break; - default: // top - y = pageSize.getUpperRightY() - marginFactor * pageSize.getHeight(); - break; - } - - PDPageContentStream contentStream = - new PDPageContentStream( - document, page, PDPageContentStream.AppendMode.APPEND, true); - contentStream.beginText(); - contentStream.setFont(font, fontSize); - contentStream.newLineAtOffset(x, y); - contentStream.showText(text); - contentStream.endText(); - contentStream.close(); - - pageNumber++; - } - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - document.save(baos); - document.close(); - - return WebResponseUtils.bytesToWebResponse( - baos.toByteArray(), - file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf", - MediaType.APPLICATION_PDF); - } -} +package stirling.software.SPDF.controller.api.misc; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest; +import stirling.software.SPDF.utils.GeneralUtils; +import stirling.software.SPDF.utils.WebResponseUtils; + +@RestController +@RequestMapping("/api/v1/misc") +@Tag(name = "Misc", description = "Miscellaneous APIs") +public class PageNumbersController { + + private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class); + + @PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data") + @Operation( + summary = "Add page numbers to a PDF document", + description = + "This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO") + public ResponseEntity addPageNumbers(@ModelAttribute AddPageNumbersRequest request) + throws IOException { + MultipartFile file = request.getFileInput(); + String customMargin = request.getCustomMargin(); + int position = request.getPosition(); + int startingNumber = request.getStartingNumber(); + String pagesToNumber = request.getPagesToNumber(); + String customText = request.getCustomText(); + int pageNumber = startingNumber; + byte[] fileBytes = file.getBytes(); + PDDocument document = PDDocument.load(fileBytes); + + float marginFactor; + switch (customMargin.toLowerCase()) { + case "small": + marginFactor = 0.02f; + break; + case "medium": + marginFactor = 0.035f; + break; + case "large": + marginFactor = 0.05f; + break; + case "x-large": + marginFactor = 0.075f; + break; + + default: + marginFactor = 0.035f; + break; + } + + float fontSize = 12.0f; + PDType1Font font = PDType1Font.HELVETICA; + if (pagesToNumber == null || pagesToNumber.length() == 0) { + pagesToNumber = "all"; + } + if (customText == null || customText.length() == 0) { + customText = "{n}"; + } + List pagesToNumberList = + GeneralUtils.parsePageList(pagesToNumber.split(","), document.getNumberOfPages()); + + for (int i : pagesToNumberList) { + PDPage page = document.getPage(i); + PDRectangle pageSize = page.getMediaBox(); + + String text = + customText != null + ? customText + .replace("{n}", String.valueOf(pageNumber)) + .replace("{total}", String.valueOf(document.getNumberOfPages())) + .replace( + "{filename}", + file.getOriginalFilename() + .replaceFirst("[.][^.]+$", "")) + : String.valueOf(pageNumber); + + float x, y; + + int xGroup = (position - 1) % 3; + int yGroup = 2 - (position - 1) / 3; + + switch (xGroup) { + case 0: // left + x = pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth(); + break; + case 1: // center + x = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2); + break; + default: // right + x = pageSize.getUpperRightX() - marginFactor * pageSize.getWidth(); + break; + } + + switch (yGroup) { + case 0: // bottom + y = pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight(); + break; + case 1: // middle + y = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2); + break; + default: // top + y = pageSize.getUpperRightY() - marginFactor * pageSize.getHeight(); + break; + } + + PDPageContentStream contentStream = + new PDPageContentStream( + document, page, PDPageContentStream.AppendMode.APPEND, true); + contentStream.beginText(); + contentStream.setFont(font, fontSize); + contentStream.newLineAtOffset(x, y); + contentStream.showText(text); + contentStream.endText(); + contentStream.close(); + + pageNumber++; + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + document.save(baos); + document.close(); + + return WebResponseUtils.bytesToWebResponse( + baos.toByteArray(), + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf", + MediaType.APPLICATION_PDF); + } +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java b/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java index 31652e18..0a53daf0 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineController.java @@ -1,133 +1,133 @@ -package stirling.software.SPDF.controller.api.pipeline; - -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.Resource; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import io.swagger.v3.oas.annotations.tags.Tag; - -import stirling.software.SPDF.model.ApplicationProperties; -import stirling.software.SPDF.model.PipelineConfig; -import stirling.software.SPDF.model.api.HandleDataRequest; -import stirling.software.SPDF.utils.WebResponseUtils; - -@RestController -@RequestMapping("/api/v1/pipeline") -@Tag(name = "Pipeline", description = "Pipeline APIs") -public class PipelineController { - - private static final Logger logger = LoggerFactory.getLogger(PipelineController.class); - - final String watchedFoldersDir = "./pipeline/watchedFolders/"; - final String finishedFoldersDir = "./pipeline/finishedFolders/"; - @Autowired PipelineProcessor processor; - - @Autowired ApplicationProperties applicationProperties; - - @Autowired private ObjectMapper objectMapper; - - @PostMapping("/handleData") - public ResponseEntity handleData(@ModelAttribute HandleDataRequest request) - throws JsonMappingException, JsonProcessingException { - if (!Boolean.TRUE.equals(applicationProperties.getSystem().getEnableAlphaFunctionality())) { - return new ResponseEntity<>(HttpStatus.BAD_REQUEST); - } - - MultipartFile[] files = request.getFileInput(); - String jsonString = request.getJson(); - if (files == null) { - return null; - } - PipelineConfig config = objectMapper.readValue(jsonString, PipelineConfig.class); - logger.info("Received POST request to /handleData with {} files", files.length); - try { - List inputFiles = processor.generateInputFiles(files); - if (inputFiles == null || inputFiles.size() == 0) { - return null; - } - List outputFiles = processor.runPipelineAgainstFiles(inputFiles, config); - if (outputFiles != null && outputFiles.size() == 1) { - // If there is only one file, return it directly - Resource singleFile = outputFiles.get(0); - InputStream is = singleFile.getInputStream(); - byte[] bytes = new byte[(int) singleFile.contentLength()]; - is.read(bytes); - is.close(); - - logger.info("Returning single file response..."); - return WebResponseUtils.bytesToWebResponse( - bytes, singleFile.getFilename(), MediaType.APPLICATION_OCTET_STREAM); - } else if (outputFiles == null) { - return null; - } - - // Create a ByteArrayOutputStream to hold the zip - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ZipOutputStream zipOut = new ZipOutputStream(baos); - - // A map to keep track of filenames and their counts - Map filenameCount = new HashMap<>(); - - // Loop through each file and add it to the zip - for (Resource file : outputFiles) { - String originalFilename = file.getFilename(); - String filename = originalFilename; - - // Check if the filename already exists, and modify it if necessary - if (filenameCount.containsKey(originalFilename)) { - int count = filenameCount.get(originalFilename); - String baseName = originalFilename.replaceAll("\\.[^.]*$", ""); - String extension = originalFilename.replaceAll("^.*\\.", ""); - filename = baseName + "(" + count + ")." + extension; - filenameCount.put(originalFilename, count + 1); - } else { - filenameCount.put(originalFilename, 1); - } - - ZipEntry zipEntry = new ZipEntry(filename); - zipOut.putNextEntry(zipEntry); - - // Read the file into a byte array - InputStream is = file.getInputStream(); - byte[] bytes = new byte[(int) file.contentLength()]; - is.read(bytes); - - // Write the bytes of the file to the zip - zipOut.write(bytes, 0, bytes.length); - zipOut.closeEntry(); - - is.close(); - } - - zipOut.close(); - - logger.info("Returning zipped file response..."); - return WebResponseUtils.boasToWebResponse( - baos, "output.zip", MediaType.APPLICATION_OCTET_STREAM); - } catch (Exception e) { - logger.error("Error handling data: ", e); - return null; - } - } -} +package stirling.software.SPDF.controller.api.pipeline; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.swagger.v3.oas.annotations.tags.Tag; + +import stirling.software.SPDF.model.ApplicationProperties; +import stirling.software.SPDF.model.PipelineConfig; +import stirling.software.SPDF.model.api.HandleDataRequest; +import stirling.software.SPDF.utils.WebResponseUtils; + +@RestController +@RequestMapping("/api/v1/pipeline") +@Tag(name = "Pipeline", description = "Pipeline APIs") +public class PipelineController { + + private static final Logger logger = LoggerFactory.getLogger(PipelineController.class); + + final String watchedFoldersDir = "./pipeline/watchedFolders/"; + final String finishedFoldersDir = "./pipeline/finishedFolders/"; + @Autowired PipelineProcessor processor; + + @Autowired ApplicationProperties applicationProperties; + + @Autowired private ObjectMapper objectMapper; + + @PostMapping("/handleData") + public ResponseEntity handleData(@ModelAttribute HandleDataRequest request) + throws JsonMappingException, JsonProcessingException { + if (!Boolean.TRUE.equals(applicationProperties.getSystem().getEnableAlphaFunctionality())) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + MultipartFile[] files = request.getFileInput(); + String jsonString = request.getJson(); + if (files == null) { + return null; + } + PipelineConfig config = objectMapper.readValue(jsonString, PipelineConfig.class); + logger.info("Received POST request to /handleData with {} files", files.length); + try { + List inputFiles = processor.generateInputFiles(files); + if (inputFiles == null || inputFiles.size() == 0) { + return null; + } + List outputFiles = processor.runPipelineAgainstFiles(inputFiles, config); + if (outputFiles != null && outputFiles.size() == 1) { + // If there is only one file, return it directly + Resource singleFile = outputFiles.get(0); + InputStream is = singleFile.getInputStream(); + byte[] bytes = new byte[(int) singleFile.contentLength()]; + is.read(bytes); + is.close(); + + logger.info("Returning single file response..."); + return WebResponseUtils.bytesToWebResponse( + bytes, singleFile.getFilename(), MediaType.APPLICATION_OCTET_STREAM); + } else if (outputFiles == null) { + return null; + } + + // Create a ByteArrayOutputStream to hold the zip + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ZipOutputStream zipOut = new ZipOutputStream(baos); + + // A map to keep track of filenames and their counts + Map filenameCount = new HashMap<>(); + + // Loop through each file and add it to the zip + for (Resource file : outputFiles) { + String originalFilename = file.getFilename(); + String filename = originalFilename; + + // Check if the filename already exists, and modify it if necessary + if (filenameCount.containsKey(originalFilename)) { + int count = filenameCount.get(originalFilename); + String baseName = originalFilename.replaceAll("\\.[^.]*$", ""); + String extension = originalFilename.replaceAll("^.*\\.", ""); + filename = baseName + "(" + count + ")." + extension; + filenameCount.put(originalFilename, count + 1); + } else { + filenameCount.put(originalFilename, 1); + } + + ZipEntry zipEntry = new ZipEntry(filename); + zipOut.putNextEntry(zipEntry); + + // Read the file into a byte array + InputStream is = file.getInputStream(); + byte[] bytes = new byte[(int) file.contentLength()]; + is.read(bytes); + + // Write the bytes of the file to the zip + zipOut.write(bytes, 0, bytes.length); + zipOut.closeEntry(); + + is.close(); + } + + zipOut.close(); + + logger.info("Returning zipped file response..."); + return WebResponseUtils.boasToWebResponse( + baos, "output.zip", MediaType.APPLICATION_OCTET_STREAM); + } catch (Exception e) { + logger.error("Error handling data: ", e); + return null; + } + } +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/SanitizeController.java b/src/main/java/stirling/software/SPDF/controller/api/security/SanitizeController.java index 21a33529..30ce1466 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/SanitizeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/SanitizeController.java @@ -1,171 +1,171 @@ -package stirling.software.SPDF.controller.api.security; - -import java.io.IOException; - -import org.apache.pdfbox.cos.COSDictionary; -import org.apache.pdfbox.cos.COSName; -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.PDDocumentCatalog; -import org.apache.pdfbox.pdmodel.PDPage; -import org.apache.pdfbox.pdmodel.PDPageTree; -import org.apache.pdfbox.pdmodel.PDResources; -import org.apache.pdfbox.pdmodel.common.PDMetadata; -import org.apache.pdfbox.pdmodel.interactive.action.PDAction; -import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript; -import org.apache.pdfbox.pdmodel.interactive.action.PDActionLaunch; -import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; -import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions; -import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; -import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; -import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; -import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; -import org.apache.pdfbox.pdmodel.interactive.form.PDField; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; - -import stirling.software.SPDF.model.api.security.SanitizePdfRequest; -import stirling.software.SPDF.utils.WebResponseUtils; - -@RestController -@RequestMapping("/api/v1/security") -@Tag(name = "Security", description = "Security APIs") -public class SanitizeController { - - @PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf") - @Operation( - summary = "Sanitize a PDF file", - description = - "This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO") - public ResponseEntity sanitizePDF(@ModelAttribute SanitizePdfRequest request) - throws IOException { - MultipartFile inputFile = request.getFileInput(); - boolean removeJavaScript = request.isRemoveJavaScript(); - boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles(); - boolean removeMetadata = request.isRemoveMetadata(); - boolean removeLinks = request.isRemoveLinks(); - boolean removeFonts = request.isRemoveFonts(); - - try (PDDocument document = PDDocument.load(inputFile.getInputStream())) { - if (removeJavaScript) { - sanitizeJavaScript(document); - } - - if (removeEmbeddedFiles) { - sanitizeEmbeddedFiles(document); - } - - if (removeMetadata) { - sanitizeMetadata(document); - } - - if (removeLinks) { - sanitizeLinks(document); - } - - if (removeFonts) { - sanitizeFonts(document); - } - - return WebResponseUtils.pdfDocToWebResponse( - document, - inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") - + "_sanitized.pdf"); - } - } - - private void sanitizeJavaScript(PDDocument document) throws IOException { - // Get the root dictionary (catalog) of the PDF - PDDocumentCatalog catalog = document.getDocumentCatalog(); - - // Get the Names dictionary - COSDictionary namesDict = - (COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.NAMES); - - if (namesDict != null) { - // Get the JavaScript dictionary - COSDictionary javaScriptDict = - (COSDictionary) namesDict.getDictionaryObject(COSName.getPDFName("JavaScript")); - - if (javaScriptDict != null) { - // Remove the JavaScript dictionary - namesDict.removeItem(COSName.getPDFName("JavaScript")); - } - } - - for (PDPage page : document.getPages()) { - for (PDAnnotation annotation : page.getAnnotations()) { - if (annotation instanceof PDAnnotationWidget) { - PDAnnotationWidget widget = (PDAnnotationWidget) annotation; - PDAction action = widget.getAction(); - if (action instanceof PDActionJavaScript) { - widget.setAction(null); - } - } - } - PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); - if (acroForm != null) { - for (PDField field : acroForm.getFields()) { - PDFormFieldAdditionalActions actions = field.getActions(); - if (actions != null) { - if (actions.getC() instanceof PDActionJavaScript) { - actions.setC(null); - } - if (actions.getF() instanceof PDActionJavaScript) { - actions.setF(null); - } - if (actions.getK() instanceof PDActionJavaScript) { - actions.setK(null); - } - if (actions.getV() instanceof PDActionJavaScript) { - actions.setV(null); - } - } - } - } - } - } - - private void sanitizeEmbeddedFiles(PDDocument document) { - PDPageTree allPages = document.getPages(); - - for (PDPage page : allPages) { - PDResources res = page.getResources(); - - // Remove embedded files from the PDF - res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles")); - } - } - - private void sanitizeMetadata(PDDocument document) { - PDMetadata metadata = document.getDocumentCatalog().getMetadata(); - if (metadata != null) { - document.getDocumentCatalog().setMetadata(null); - } - } - - private void sanitizeLinks(PDDocument document) throws IOException { - for (PDPage page : document.getPages()) { - for (PDAnnotation annotation : page.getAnnotations()) { - if (annotation instanceof PDAnnotationLink) { - PDAction action = ((PDAnnotationLink) annotation).getAction(); - if (action instanceof PDActionLaunch || action instanceof PDActionURI) { - ((PDAnnotationLink) annotation).setAction(null); - } - } - } - } - } - - private void sanitizeFonts(PDDocument document) { - for (PDPage page : document.getPages()) { - page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font")); - } - } -} +package stirling.software.SPDF.controller.api.security; + +import java.io.IOException; + +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDDocumentCatalog; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageTree; +import org.apache.pdfbox.pdmodel.PDResources; +import org.apache.pdfbox.pdmodel.common.PDMetadata; +import org.apache.pdfbox.pdmodel.interactive.action.PDAction; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionLaunch; +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; +import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDField; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import stirling.software.SPDF.model.api.security.SanitizePdfRequest; +import stirling.software.SPDF.utils.WebResponseUtils; + +@RestController +@RequestMapping("/api/v1/security") +@Tag(name = "Security", description = "Security APIs") +public class SanitizeController { + + @PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf") + @Operation( + summary = "Sanitize a PDF file", + description = + "This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO") + public ResponseEntity sanitizePDF(@ModelAttribute SanitizePdfRequest request) + throws IOException { + MultipartFile inputFile = request.getFileInput(); + boolean removeJavaScript = request.isRemoveJavaScript(); + boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles(); + boolean removeMetadata = request.isRemoveMetadata(); + boolean removeLinks = request.isRemoveLinks(); + boolean removeFonts = request.isRemoveFonts(); + + try (PDDocument document = PDDocument.load(inputFile.getInputStream())) { + if (removeJavaScript) { + sanitizeJavaScript(document); + } + + if (removeEmbeddedFiles) { + sanitizeEmbeddedFiles(document); + } + + if (removeMetadata) { + sanitizeMetadata(document); + } + + if (removeLinks) { + sanitizeLinks(document); + } + + if (removeFonts) { + sanitizeFonts(document); + } + + return WebResponseUtils.pdfDocToWebResponse( + document, + inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + + "_sanitized.pdf"); + } + } + + private void sanitizeJavaScript(PDDocument document) throws IOException { + // Get the root dictionary (catalog) of the PDF + PDDocumentCatalog catalog = document.getDocumentCatalog(); + + // Get the Names dictionary + COSDictionary namesDict = + (COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.NAMES); + + if (namesDict != null) { + // Get the JavaScript dictionary + COSDictionary javaScriptDict = + (COSDictionary) namesDict.getDictionaryObject(COSName.getPDFName("JavaScript")); + + if (javaScriptDict != null) { + // Remove the JavaScript dictionary + namesDict.removeItem(COSName.getPDFName("JavaScript")); + } + } + + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationWidget) { + PDAnnotationWidget widget = (PDAnnotationWidget) annotation; + PDAction action = widget.getAction(); + if (action instanceof PDActionJavaScript) { + widget.setAction(null); + } + } + } + PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); + if (acroForm != null) { + for (PDField field : acroForm.getFields()) { + PDFormFieldAdditionalActions actions = field.getActions(); + if (actions != null) { + if (actions.getC() instanceof PDActionJavaScript) { + actions.setC(null); + } + if (actions.getF() instanceof PDActionJavaScript) { + actions.setF(null); + } + if (actions.getK() instanceof PDActionJavaScript) { + actions.setK(null); + } + if (actions.getV() instanceof PDActionJavaScript) { + actions.setV(null); + } + } + } + } + } + } + + private void sanitizeEmbeddedFiles(PDDocument document) { + PDPageTree allPages = document.getPages(); + + for (PDPage page : allPages) { + PDResources res = page.getResources(); + + // Remove embedded files from the PDF + res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles")); + } + } + + private void sanitizeMetadata(PDDocument document) { + PDMetadata metadata = document.getDocumentCatalog().getMetadata(); + if (metadata != null) { + document.getDocumentCatalog().setMetadata(null); + } + } + + private void sanitizeLinks(PDDocument document) throws IOException { + for (PDPage page : document.getPages()) { + for (PDAnnotation annotation : page.getAnnotations()) { + if (annotation instanceof PDAnnotationLink) { + PDAction action = ((PDAnnotationLink) annotation).getAction(); + if (action instanceof PDActionLaunch || action instanceof PDActionURI) { + ((PDAnnotationLink) annotation).setAction(null); + } + } + } + } + } + + private void sanitizeFonts(PDDocument document) { + for (PDPage page : document.getPages()) { + page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font")); + } + } +} diff --git a/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java b/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java index b34bac3c..16e42dec 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java @@ -1,113 +1,113 @@ -package stirling.software.SPDF.controller.web; - -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.servlet.ModelAndView; - -import io.swagger.v3.oas.annotations.Hidden; -import io.swagger.v3.oas.annotations.tags.Tag; - -@Controller -@Tag(name = "Convert", description = "Convert APIs") -public class ConverterWebController { - - @GetMapping("/img-to-pdf") - @Hidden - public String convertImgToPdfForm(Model model) { - model.addAttribute("currentPage", "img-to-pdf"); - return "convert/img-to-pdf"; - } - - @GetMapping("/html-to-pdf") - @Hidden - public String convertHTMLToPdfForm(Model model) { - model.addAttribute("currentPage", "html-to-pdf"); - return "convert/html-to-pdf"; - } - - @GetMapping("/markdown-to-pdf") - @Hidden - public String convertMarkdownToPdfForm(Model model) { - model.addAttribute("currentPage", "markdown-to-pdf"); - return "convert/markdown-to-pdf"; - } - - @GetMapping("/url-to-pdf") - @Hidden - public String convertURLToPdfForm(Model model) { - model.addAttribute("currentPage", "url-to-pdf"); - return "convert/url-to-pdf"; - } - - @GetMapping("/pdf-to-img") - @Hidden - public String pdfToimgForm(Model model) { - model.addAttribute("currentPage", "pdf-to-img"); - return "convert/pdf-to-img"; - } - - @GetMapping("/file-to-pdf") - @Hidden - public String convertToPdfForm(Model model) { - model.addAttribute("currentPage", "file-to-pdf"); - return "convert/file-to-pdf"; - } - - // PDF TO...... - - @GetMapping("/pdf-to-html") - @Hidden - public ModelAndView pdfToHTML() { - ModelAndView modelAndView = new ModelAndView("convert/pdf-to-html"); - modelAndView.addObject("currentPage", "pdf-to-html"); - return modelAndView; - } - - @GetMapping("/pdf-to-presentation") - @Hidden - public ModelAndView pdfToPresentation() { - ModelAndView modelAndView = new ModelAndView("convert/pdf-to-presentation"); - modelAndView.addObject("currentPage", "pdf-to-presentation"); - return modelAndView; - } - - @GetMapping("/pdf-to-text") - @Hidden - public ModelAndView pdfToText() { - ModelAndView modelAndView = new ModelAndView("convert/pdf-to-text"); - modelAndView.addObject("currentPage", "pdf-to-text"); - return modelAndView; - } - - @GetMapping("/pdf-to-word") - @Hidden - public ModelAndView pdfToWord() { - ModelAndView modelAndView = new ModelAndView("convert/pdf-to-word"); - modelAndView.addObject("currentPage", "pdf-to-word"); - return modelAndView; - } - - @GetMapping("/pdf-to-xml") - @Hidden - public ModelAndView pdfToXML() { - ModelAndView modelAndView = new ModelAndView("convert/pdf-to-xml"); - modelAndView.addObject("currentPage", "pdf-to-xml"); - return modelAndView; - } - - @GetMapping("/pdf-to-csv") - @Hidden - public ModelAndView pdfToCSV() { - ModelAndView modelAndView = new ModelAndView("convert/pdf-to-csv"); - modelAndView.addObject("currentPage", "pdf-to-csv"); - return modelAndView; - } - - @GetMapping("/pdf-to-pdfa") - @Hidden - public String pdfToPdfAForm(Model model) { - model.addAttribute("currentPage", "pdf-to-pdfa"); - return "convert/pdf-to-pdfa"; - } -} +package stirling.software.SPDF.controller.web; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.servlet.ModelAndView; + +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Controller +@Tag(name = "Convert", description = "Convert APIs") +public class ConverterWebController { + + @GetMapping("/img-to-pdf") + @Hidden + public String convertImgToPdfForm(Model model) { + model.addAttribute("currentPage", "img-to-pdf"); + return "convert/img-to-pdf"; + } + + @GetMapping("/html-to-pdf") + @Hidden + public String convertHTMLToPdfForm(Model model) { + model.addAttribute("currentPage", "html-to-pdf"); + return "convert/html-to-pdf"; + } + + @GetMapping("/markdown-to-pdf") + @Hidden + public String convertMarkdownToPdfForm(Model model) { + model.addAttribute("currentPage", "markdown-to-pdf"); + return "convert/markdown-to-pdf"; + } + + @GetMapping("/url-to-pdf") + @Hidden + public String convertURLToPdfForm(Model model) { + model.addAttribute("currentPage", "url-to-pdf"); + return "convert/url-to-pdf"; + } + + @GetMapping("/pdf-to-img") + @Hidden + public String pdfToimgForm(Model model) { + model.addAttribute("currentPage", "pdf-to-img"); + return "convert/pdf-to-img"; + } + + @GetMapping("/file-to-pdf") + @Hidden + public String convertToPdfForm(Model model) { + model.addAttribute("currentPage", "file-to-pdf"); + return "convert/file-to-pdf"; + } + + // PDF TO...... + + @GetMapping("/pdf-to-html") + @Hidden + public ModelAndView pdfToHTML() { + ModelAndView modelAndView = new ModelAndView("convert/pdf-to-html"); + modelAndView.addObject("currentPage", "pdf-to-html"); + return modelAndView; + } + + @GetMapping("/pdf-to-presentation") + @Hidden + public ModelAndView pdfToPresentation() { + ModelAndView modelAndView = new ModelAndView("convert/pdf-to-presentation"); + modelAndView.addObject("currentPage", "pdf-to-presentation"); + return modelAndView; + } + + @GetMapping("/pdf-to-text") + @Hidden + public ModelAndView pdfToText() { + ModelAndView modelAndView = new ModelAndView("convert/pdf-to-text"); + modelAndView.addObject("currentPage", "pdf-to-text"); + return modelAndView; + } + + @GetMapping("/pdf-to-word") + @Hidden + public ModelAndView pdfToWord() { + ModelAndView modelAndView = new ModelAndView("convert/pdf-to-word"); + modelAndView.addObject("currentPage", "pdf-to-word"); + return modelAndView; + } + + @GetMapping("/pdf-to-xml") + @Hidden + public ModelAndView pdfToXML() { + ModelAndView modelAndView = new ModelAndView("convert/pdf-to-xml"); + modelAndView.addObject("currentPage", "pdf-to-xml"); + return modelAndView; + } + + @GetMapping("/pdf-to-csv") + @Hidden + public ModelAndView pdfToCSV() { + ModelAndView modelAndView = new ModelAndView("convert/pdf-to-csv"); + modelAndView.addObject("currentPage", "pdf-to-csv"); + return modelAndView; + } + + @GetMapping("/pdf-to-pdfa") + @Hidden + public String pdfToPdfAForm(Model model) { + model.addAttribute("currentPage", "pdf-to-pdfa"); + return "convert/pdf-to-pdfa"; + } +} diff --git a/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java b/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java index ba67e1d7..68b16d89 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java @@ -1,69 +1,69 @@ -package stirling.software.SPDF.controller.web; - -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; - -import io.swagger.v3.oas.annotations.Hidden; -import io.swagger.v3.oas.annotations.tags.Tag; - -@Controller -@Tag(name = "Security", description = "Security APIs") -public class SecurityWebController { - - @GetMapping("/auto-redact") - @Hidden - public String autoRedactForm(Model model) { - model.addAttribute("currentPage", "auto-redact"); - return "security/auto-redact"; - } - - @GetMapping("/add-password") - @Hidden - public String addPasswordForm(Model model) { - model.addAttribute("currentPage", "add-password"); - return "security/add-password"; - } - - @GetMapping("/change-permissions") - @Hidden - public String permissionsForm(Model model) { - model.addAttribute("currentPage", "change-permissions"); - return "security/change-permissions"; - } - - @GetMapping("/remove-password") - @Hidden - public String removePasswordForm(Model model) { - model.addAttribute("currentPage", "remove-password"); - return "security/remove-password"; - } - - @GetMapping("/add-watermark") - @Hidden - public String addWatermarkForm(Model model) { - model.addAttribute("currentPage", "add-watermark"); - return "security/add-watermark"; - } - - @GetMapping("/cert-sign") - @Hidden - public String certSignForm(Model model) { - model.addAttribute("currentPage", "cert-sign"); - return "security/cert-sign"; - } - - @GetMapping("/sanitize-pdf") - @Hidden - public String sanitizeForm(Model model) { - model.addAttribute("currentPage", "sanitize-pdf"); - return "security/sanitize-pdf"; - } - - @GetMapping("/get-info-on-pdf") - @Hidden - public String getInfo(Model model) { - model.addAttribute("currentPage", "get-info-on-pdf"); - return "security/get-info-on-pdf"; - } -} +package stirling.software.SPDF.controller.web; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Controller +@Tag(name = "Security", description = "Security APIs") +public class SecurityWebController { + + @GetMapping("/auto-redact") + @Hidden + public String autoRedactForm(Model model) { + model.addAttribute("currentPage", "auto-redact"); + return "security/auto-redact"; + } + + @GetMapping("/add-password") + @Hidden + public String addPasswordForm(Model model) { + model.addAttribute("currentPage", "add-password"); + return "security/add-password"; + } + + @GetMapping("/change-permissions") + @Hidden + public String permissionsForm(Model model) { + model.addAttribute("currentPage", "change-permissions"); + return "security/change-permissions"; + } + + @GetMapping("/remove-password") + @Hidden + public String removePasswordForm(Model model) { + model.addAttribute("currentPage", "remove-password"); + return "security/remove-password"; + } + + @GetMapping("/add-watermark") + @Hidden + public String addWatermarkForm(Model model) { + model.addAttribute("currentPage", "add-watermark"); + return "security/add-watermark"; + } + + @GetMapping("/cert-sign") + @Hidden + public String certSignForm(Model model) { + model.addAttribute("currentPage", "cert-sign"); + return "security/cert-sign"; + } + + @GetMapping("/sanitize-pdf") + @Hidden + public String sanitizeForm(Model model) { + model.addAttribute("currentPage", "sanitize-pdf"); + return "security/sanitize-pdf"; + } + + @GetMapping("/get-info-on-pdf") + @Hidden + public String getInfo(Model model) { + model.addAttribute("currentPage", "get-info-on-pdf"); + return "security/get-info-on-pdf"; + } +} diff --git a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java index a1e177e4..cdf00bf0 100644 --- a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java @@ -1,186 +1,186 @@ -package stirling.software.SPDF.utils; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; -import java.util.List; - -import org.springframework.web.multipart.MultipartFile; - -public class GeneralUtils { - - public static void deleteDirectory(Path path) throws IOException { - Files.walkFileTree( - path, - new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) - throws IOException { - Files.delete(file); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) - throws IOException { - Files.delete(dir); - return FileVisitResult.CONTINUE; - } - }); - } - - public static String convertToFileName(String name) { - String safeName = name.replaceAll("[^a-zA-Z0-9]", "_"); - if (safeName.length() > 50) { - safeName = safeName.substring(0, 50); - } - return safeName; - } - - public static boolean isValidURL(String urlStr) { - try { - new URL(urlStr); - return true; - } catch (MalformedURLException e) { - return false; - } - } - - public static File multipartToFile(MultipartFile multipart) throws IOException { - Path tempFile = Files.createTempFile("overlay-", ".pdf"); - try (InputStream in = multipart.getInputStream(); - FileOutputStream out = new FileOutputStream(tempFile.toFile())) { - byte[] buffer = new byte[1024]; - int bytesRead; - while ((bytesRead = in.read(buffer)) != -1) { - out.write(buffer, 0, bytesRead); - } - } - return tempFile.toFile(); - } - - public static Long convertSizeToBytes(String sizeStr) { - if (sizeStr == null) { - return null; - } - - sizeStr = sizeStr.trim().toUpperCase(); - try { - if (sizeStr.endsWith("KB")) { - return (long) - (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024); - } else if (sizeStr.endsWith("MB")) { - return (long) - (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) - * 1024 - * 1024); - } else if (sizeStr.endsWith("GB")) { - return (long) - (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) - * 1024 - * 1024 - * 1024); - } else if (sizeStr.endsWith("B")) { - return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1)); - } else { - // Assume MB if no unit is specified - return (long) (Double.parseDouble(sizeStr) * 1024 * 1024); - } - } catch (NumberFormatException e) { - // The numeric part of the input string cannot be parsed, handle this case - } - - return null; - } - - public static List parsePageString(String pageOrder, int totalPages) { - return parsePageList(pageOrder.split(","), totalPages); - } - - public static List parsePageList(String[] pageOrderArr, int totalPages) { - List newPageOrder = new ArrayList<>(); - - // loop through the page order array - for (String element : pageOrderArr) { - if (element.equalsIgnoreCase("all")) { - for (int i = 0; i < totalPages; i++) { - newPageOrder.add(i); - } - // As all pages are already added, no need to check further - break; - } else if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) { - // Handle page order as a function - int coefficient = 0; - int constant = 0; - boolean coefficientExists = false; - boolean constantExists = false; - - if (element.contains("n")) { - String[] parts = element.split("n"); - if (!parts[0].equals("") && parts[0] != null) { - coefficient = Integer.parseInt(parts[0]); - coefficientExists = true; - } - if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) { - constant = Integer.parseInt(parts[1]); - constantExists = true; - } - } else if (element.contains("+")) { - constant = Integer.parseInt(element.replace("+", "")); - constantExists = true; - } - - for (int i = 1; i <= totalPages; i++) { - int pageNum = coefficientExists ? coefficient * i : i; - pageNum += constantExists ? constant : 0; - - if (pageNum <= totalPages && pageNum > 0) { - newPageOrder.add(pageNum - 1); - } - } - } else if (element.contains("-")) { - // split the range into start and end page - String[] range = element.split("-"); - int start = Integer.parseInt(range[0]); - int end = Integer.parseInt(range[1]); - // check if the end page is greater than total pages - if (end > totalPages) { - end = totalPages; - } - // loop through the range of pages - for (int j = start; j <= end; j++) { - // print the current index - newPageOrder.add(j - 1); - } - } else { - // if the element is a single page - newPageOrder.add(Integer.parseInt(element) - 1); - } - } - - return newPageOrder; - } - - public static boolean createDir(String path) { - Path folder = Paths.get(path); - if (!Files.exists(folder)) { - try { - Files.createDirectories(folder); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - } - return true; - } -} +package stirling.software.SPDF.utils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.web.multipart.MultipartFile; + +public class GeneralUtils { + + public static void deleteDirectory(Path path) throws IOException { + Files.walkFileTree( + path, + new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) + throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + } + + public static String convertToFileName(String name) { + String safeName = name.replaceAll("[^a-zA-Z0-9]", "_"); + if (safeName.length() > 50) { + safeName = safeName.substring(0, 50); + } + return safeName; + } + + public static boolean isValidURL(String urlStr) { + try { + new URL(urlStr); + return true; + } catch (MalformedURLException e) { + return false; + } + } + + public static File multipartToFile(MultipartFile multipart) throws IOException { + Path tempFile = Files.createTempFile("overlay-", ".pdf"); + try (InputStream in = multipart.getInputStream(); + FileOutputStream out = new FileOutputStream(tempFile.toFile())) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + } + } + return tempFile.toFile(); + } + + public static Long convertSizeToBytes(String sizeStr) { + if (sizeStr == null) { + return null; + } + + sizeStr = sizeStr.trim().toUpperCase(); + try { + if (sizeStr.endsWith("KB")) { + return (long) + (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024); + } else if (sizeStr.endsWith("MB")) { + return (long) + (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) + * 1024 + * 1024); + } else if (sizeStr.endsWith("GB")) { + return (long) + (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) + * 1024 + * 1024 + * 1024); + } else if (sizeStr.endsWith("B")) { + return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1)); + } else { + // Assume MB if no unit is specified + return (long) (Double.parseDouble(sizeStr) * 1024 * 1024); + } + } catch (NumberFormatException e) { + // The numeric part of the input string cannot be parsed, handle this case + } + + return null; + } + + public static List parsePageString(String pageOrder, int totalPages) { + return parsePageList(pageOrder.split(","), totalPages); + } + + public static List parsePageList(String[] pageOrderArr, int totalPages) { + List newPageOrder = new ArrayList<>(); + + // loop through the page order array + for (String element : pageOrderArr) { + if (element.equalsIgnoreCase("all")) { + for (int i = 0; i < totalPages; i++) { + newPageOrder.add(i); + } + // As all pages are already added, no need to check further + break; + } else if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) { + // Handle page order as a function + int coefficient = 0; + int constant = 0; + boolean coefficientExists = false; + boolean constantExists = false; + + if (element.contains("n")) { + String[] parts = element.split("n"); + if (!parts[0].equals("") && parts[0] != null) { + coefficient = Integer.parseInt(parts[0]); + coefficientExists = true; + } + if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) { + constant = Integer.parseInt(parts[1]); + constantExists = true; + } + } else if (element.contains("+")) { + constant = Integer.parseInt(element.replace("+", "")); + constantExists = true; + } + + for (int i = 1; i <= totalPages; i++) { + int pageNum = coefficientExists ? coefficient * i : i; + pageNum += constantExists ? constant : 0; + + if (pageNum <= totalPages && pageNum > 0) { + newPageOrder.add(pageNum - 1); + } + } + } else if (element.contains("-")) { + // split the range into start and end page + String[] range = element.split("-"); + int start = Integer.parseInt(range[0]); + int end = Integer.parseInt(range[1]); + // check if the end page is greater than total pages + if (end > totalPages) { + end = totalPages; + } + // loop through the range of pages + for (int j = start; j <= end; j++) { + // print the current index + newPageOrder.add(j - 1); + } + } else { + // if the element is a single page + newPageOrder.add(Integer.parseInt(element) - 1); + } + } + + return newPageOrder; + } + + public static boolean createDir(String path) { + Path folder = Paths.get(path); + if (!Files.exists(folder)) { + try { + Files.createDirectories(folder); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + return true; + } +} diff --git a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index 677bafd1..79a47864 100644 --- a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java @@ -1,400 +1,400 @@ -package stirling.software.SPDF.utils; - -import java.awt.Graphics; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -import javax.imageio.IIOImage; -import javax.imageio.ImageIO; -import javax.imageio.ImageReader; -import javax.imageio.ImageWriteParam; -import javax.imageio.ImageWriter; -import javax.imageio.stream.ImageOutputStream; - -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.PDPage; -import org.apache.pdfbox.pdmodel.PDPageContentStream; -import org.apache.pdfbox.pdmodel.common.PDRectangle; -import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory; -import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; -import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; -import org.apache.pdfbox.rendering.ImageType; -import org.apache.pdfbox.rendering.PDFRenderer; -import org.apache.pdfbox.text.PDFTextStripper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.web.multipart.MultipartFile; - -import stirling.software.SPDF.pdf.ImageFinder; - -public class PdfUtils { - - private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class); - - public static PDRectangle textToPageSize(String size) { - switch (size.toUpperCase()) { - case "A0": - return PDRectangle.A0; - case "A1": - return PDRectangle.A1; - case "A2": - return PDRectangle.A2; - case "A3": - return PDRectangle.A3; - case "A4": - return PDRectangle.A4; - case "A5": - return PDRectangle.A5; - case "A6": - return PDRectangle.A6; - case "LETTER": - return PDRectangle.LETTER; - case "LEGAL": - return PDRectangle.LEGAL; - default: - throw new IllegalArgumentException("Invalid standard page size: " + size); - } - } - - public static boolean hasImages(PDDocument document, String pagesToCheck) throws IOException { - String[] pageOrderArr = pagesToCheck.split(","); - List pageList = - GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); - - for (int pageNumber : pageList) { - PDPage page = document.getPage(pageNumber); - if (hasImagesOnPage(page)) { - return true; - } - } - - return false; - } - - public static boolean hasText(PDDocument document, String pageNumbersToCheck, String phrase) - throws IOException { - String[] pageOrderArr = pageNumbersToCheck.split(","); - List pageList = - GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); - - for (int pageNumber : pageList) { - PDPage page = document.getPage(pageNumber); - if (hasTextOnPage(page, phrase)) { - return true; - } - } - - return false; - } - - public static boolean hasImagesOnPage(PDPage page) throws IOException { - ImageFinder imageFinder = new ImageFinder(page); - imageFinder.processPage(page); - return imageFinder.hasImages(); - } - - public static boolean hasTextOnPage(PDPage page, String phrase) throws IOException { - PDFTextStripper textStripper = new PDFTextStripper(); - PDDocument tempDoc = new PDDocument(); - tempDoc.addPage(page); - String pageText = textStripper.getText(tempDoc); - tempDoc.close(); - return pageText.contains(phrase); - } - - public boolean containsTextInFile(PDDocument pdfDocument, String text, String pagesToCheck) - throws IOException { - PDFTextStripper textStripper = new PDFTextStripper(); - String pdfText = ""; - - if (pagesToCheck == null || pagesToCheck.equals("all")) { - pdfText = textStripper.getText(pdfDocument); - } else { - // remove whitespaces - pagesToCheck = pagesToCheck.replaceAll("\\s+", ""); - - String[] splitPoints = pagesToCheck.split(","); - for (String splitPoint : splitPoints) { - if (splitPoint.contains("-")) { - // Handle page ranges - String[] range = splitPoint.split("-"); - int startPage = Integer.parseInt(range[0]); - int endPage = Integer.parseInt(range[1]); - - for (int i = startPage; i <= endPage; i++) { - textStripper.setStartPage(i); - textStripper.setEndPage(i); - pdfText += textStripper.getText(pdfDocument); - } - } else { - // Handle individual page - int page = Integer.parseInt(splitPoint); - textStripper.setStartPage(page); - textStripper.setEndPage(page); - pdfText += textStripper.getText(pdfDocument); - } - } - } - - pdfDocument.close(); - - return pdfText.contains(text); - } - - public boolean pageCount(PDDocument pdfDocument, int pageCount, String comparator) - throws IOException { - int actualPageCount = pdfDocument.getNumberOfPages(); - pdfDocument.close(); - - switch (comparator.toLowerCase()) { - case "greater": - return actualPageCount > pageCount; - case "equal": - return actualPageCount == pageCount; - case "less": - return actualPageCount < pageCount; - default: - throw new IllegalArgumentException( - "Invalid comparator. Only 'greater', 'equal', and 'less' are supported."); - } - } - - public boolean pageSize(PDDocument pdfDocument, String expectedPageSize) throws IOException { - PDPage firstPage = pdfDocument.getPage(0); - PDRectangle mediaBox = firstPage.getMediaBox(); - - float actualPageWidth = mediaBox.getWidth(); - float actualPageHeight = mediaBox.getHeight(); - - pdfDocument.close(); - - // Assumes the expectedPageSize is in the format "widthxheight", e.g. "595x842" for A4 - String[] dimensions = expectedPageSize.split("x"); - float expectedPageWidth = Float.parseFloat(dimensions[0]); - float expectedPageHeight = Float.parseFloat(dimensions[1]); - - // Checks if the actual page size matches the expected page size - return actualPageWidth == expectedPageWidth && actualPageHeight == expectedPageHeight; - } - - public static byte[] convertFromPdf( - byte[] inputStream, - String imageType, - ImageType colorType, - boolean singleImage, - int DPI, - String filename) - throws IOException, Exception { - try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) { - PDFRenderer pdfRenderer = new PDFRenderer(document); - int pageCount = document.getNumberOfPages(); - - // Create a ByteArrayOutputStream to save the image(s) to - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - if (singleImage) { - if (imageType.toLowerCase().equals("tiff") - || imageType.toLowerCase().equals("tif")) { - // Write the images to the output stream as a TIFF with multiple frames - ImageWriter writer = ImageIO.getImageWritersByFormatName("tiff").next(); - ImageWriteParam param = writer.getDefaultWriteParam(); - param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); - param.setCompressionType("ZLib"); - param.setCompressionQuality(1.0f); - - try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) { - writer.setOutput(ios); - writer.prepareWriteSequence(null); - - for (int i = 0; i < pageCount; ++i) { - BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); - writer.writeToSequence(new IIOImage(image, null, null), param); - } - - writer.endWriteSequence(); - } - - writer.dispose(); - } else { - // Combine all images into a single big image - BufferedImage image = pdfRenderer.renderImageWithDPI(0, DPI, colorType); - BufferedImage combined = - new BufferedImage( - image.getWidth(), - image.getHeight() * pageCount, - BufferedImage.TYPE_INT_RGB); - Graphics g = combined.getGraphics(); - - for (int i = 0; i < pageCount; ++i) { - if (i != 0) { - image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); - } - g.drawImage(image, 0, i * image.getHeight(), null); - } - - // Write the image to the output stream - ImageIO.write(combined, imageType, baos); - } - - // Log that the image was successfully written to the byte array - logger.info("Image successfully written to byte array"); - } else { - // Zip the images and return as byte array - try (ZipOutputStream zos = new ZipOutputStream(baos)) { - for (int i = 0; i < pageCount; ++i) { - BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); - try (ByteArrayOutputStream baosImage = new ByteArrayOutputStream()) { - ImageIO.write(image, imageType, baosImage); - - // Add the image to the zip file - zos.putNextEntry( - new ZipEntry( - String.format( - filename + "_%d.%s", - i + 1, - imageType.toLowerCase()))); - zos.write(baosImage.toByteArray()); - } - } - // Log that the images were successfully written to the byte array - logger.info("Images successfully written to byte array as a zip"); - } - } - return baos.toByteArray(); - } catch (IOException e) { - // Log an error message if there is an issue converting the PDF to an image - logger.error("Error converting PDF to image", e); - throw e; - } - } - - public static byte[] imageToPdf( - MultipartFile[] files, String fitOption, boolean autoRotate, String colorType) - throws IOException { - try (PDDocument doc = new PDDocument()) { - for (MultipartFile file : files) { - String contentType = file.getContentType(); - String originalFilename = file.getOriginalFilename(); - if (originalFilename != null - && (originalFilename.toLowerCase().endsWith(".tiff") - || originalFilename.toLowerCase().endsWith(".tif"))) { - ImageReader reader = ImageIO.getImageReadersByFormatName("tiff").next(); - reader.setInput(ImageIO.createImageInputStream(file.getInputStream())); - int numPages = reader.getNumImages(true); - for (int i = 0; i < numPages; i++) { - BufferedImage pageImage = reader.read(i); - BufferedImage convertedImage = - ImageProcessingUtils.convertColorType(pageImage, colorType); - PDImageXObject pdImage = - LosslessFactory.createFromImage(doc, convertedImage); - addImageToDocument(doc, pdImage, fitOption, autoRotate); - } - } else { - BufferedImage image = ImageIO.read(file.getInputStream()); - BufferedImage convertedImage = - ImageProcessingUtils.convertColorType(image, colorType); - // Use JPEGFactory if it's JPEG since JPEG is lossy - PDImageXObject pdImage = - (contentType != null && contentType.equals("image/jpeg")) - ? JPEGFactory.createFromImage(doc, convertedImage) - : LosslessFactory.createFromImage(doc, convertedImage); - addImageToDocument(doc, pdImage, fitOption, autoRotate); - } - } - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - doc.save(byteArrayOutputStream); - logger.info("PDF successfully saved to byte array"); - return byteArrayOutputStream.toByteArray(); - } - } - - private static void addImageToDocument( - PDDocument doc, PDImageXObject image, String fitOption, boolean autoRotate) - throws IOException { - boolean imageIsLandscape = image.getWidth() > image.getHeight(); - PDRectangle pageSize = PDRectangle.A4; - - System.out.println(fitOption); - - if (autoRotate && imageIsLandscape) { - pageSize = new PDRectangle(pageSize.getHeight(), pageSize.getWidth()); - } - - if ("fitDocumentToImage".equals(fitOption)) { - pageSize = new PDRectangle(image.getWidth(), image.getHeight()); - } - - PDPage page = new PDPage(pageSize); - doc.addPage(page); - - float pageWidth = page.getMediaBox().getWidth(); - float pageHeight = page.getMediaBox().getHeight(); - - try (PDPageContentStream contentStream = new PDPageContentStream(doc, page)) { - if ("fillPage".equals(fitOption) || "fitDocumentToImage".equals(fitOption)) { - contentStream.drawImage(image, 0, 0, pageWidth, pageHeight); - } else if ("maintainAspectRatio".equals(fitOption)) { - float imageAspectRatio = (float) image.getWidth() / (float) image.getHeight(); - float pageAspectRatio = pageWidth / pageHeight; - - float scaleFactor = 1.0f; - if (imageAspectRatio > pageAspectRatio) { - scaleFactor = pageWidth / image.getWidth(); - } else { - scaleFactor = pageHeight / image.getHeight(); - } - - float xPos = (pageWidth - (image.getWidth() * scaleFactor)) / 2; - float yPos = (pageHeight - (image.getHeight() * scaleFactor)) / 2; - contentStream.drawImage( - image, - xPos, - yPos, - image.getWidth() * scaleFactor, - image.getHeight() * scaleFactor); - } - } catch (IOException e) { - logger.error("Error adding image to PDF", e); - throw e; - } - } - - public static byte[] overlayImage( - byte[] pdfBytes, byte[] imageBytes, float x, float y, boolean everyPage) - throws IOException { - - PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes)); - - // Get the first page of the PDF - int pages = document.getNumberOfPages(); - for (int i = 0; i < pages; i++) { - PDPage page = document.getPage(i); - try (PDPageContentStream contentStream = - new PDPageContentStream( - document, page, PDPageContentStream.AppendMode.APPEND, true)) { - // Create an image object from the image bytes - PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, ""); - // Draw the image onto the page at the specified x and y coordinates - contentStream.drawImage(image, x, y); - logger.info("Image successfully overlayed onto PDF"); - if (!everyPage && i == 0) { - break; - } - } catch (IOException e) { - // Log an error message if there is an issue overlaying the image onto the PDF - logger.error("Error overlaying image onto PDF", e); - throw e; - } - } - // Create a ByteArrayOutputStream to save the PDF to - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - document.save(baos); - logger.info("PDF successfully saved to byte array"); - return baos.toByteArray(); - } -} +package stirling.software.SPDF.utils; + +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.stream.ImageOutputStream; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory; +import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.apache.pdfbox.rendering.ImageType; +import org.apache.pdfbox.rendering.PDFRenderer; +import org.apache.pdfbox.text.PDFTextStripper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.multipart.MultipartFile; + +import stirling.software.SPDF.pdf.ImageFinder; + +public class PdfUtils { + + private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class); + + public static PDRectangle textToPageSize(String size) { + switch (size.toUpperCase()) { + case "A0": + return PDRectangle.A0; + case "A1": + return PDRectangle.A1; + case "A2": + return PDRectangle.A2; + case "A3": + return PDRectangle.A3; + case "A4": + return PDRectangle.A4; + case "A5": + return PDRectangle.A5; + case "A6": + return PDRectangle.A6; + case "LETTER": + return PDRectangle.LETTER; + case "LEGAL": + return PDRectangle.LEGAL; + default: + throw new IllegalArgumentException("Invalid standard page size: " + size); + } + } + + public static boolean hasImages(PDDocument document, String pagesToCheck) throws IOException { + String[] pageOrderArr = pagesToCheck.split(","); + List pageList = + GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); + + for (int pageNumber : pageList) { + PDPage page = document.getPage(pageNumber); + if (hasImagesOnPage(page)) { + return true; + } + } + + return false; + } + + public static boolean hasText(PDDocument document, String pageNumbersToCheck, String phrase) + throws IOException { + String[] pageOrderArr = pageNumbersToCheck.split(","); + List pageList = + GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); + + for (int pageNumber : pageList) { + PDPage page = document.getPage(pageNumber); + if (hasTextOnPage(page, phrase)) { + return true; + } + } + + return false; + } + + public static boolean hasImagesOnPage(PDPage page) throws IOException { + ImageFinder imageFinder = new ImageFinder(page); + imageFinder.processPage(page); + return imageFinder.hasImages(); + } + + public static boolean hasTextOnPage(PDPage page, String phrase) throws IOException { + PDFTextStripper textStripper = new PDFTextStripper(); + PDDocument tempDoc = new PDDocument(); + tempDoc.addPage(page); + String pageText = textStripper.getText(tempDoc); + tempDoc.close(); + return pageText.contains(phrase); + } + + public boolean containsTextInFile(PDDocument pdfDocument, String text, String pagesToCheck) + throws IOException { + PDFTextStripper textStripper = new PDFTextStripper(); + String pdfText = ""; + + if (pagesToCheck == null || pagesToCheck.equals("all")) { + pdfText = textStripper.getText(pdfDocument); + } else { + // remove whitespaces + pagesToCheck = pagesToCheck.replaceAll("\\s+", ""); + + String[] splitPoints = pagesToCheck.split(","); + for (String splitPoint : splitPoints) { + if (splitPoint.contains("-")) { + // Handle page ranges + String[] range = splitPoint.split("-"); + int startPage = Integer.parseInt(range[0]); + int endPage = Integer.parseInt(range[1]); + + for (int i = startPage; i <= endPage; i++) { + textStripper.setStartPage(i); + textStripper.setEndPage(i); + pdfText += textStripper.getText(pdfDocument); + } + } else { + // Handle individual page + int page = Integer.parseInt(splitPoint); + textStripper.setStartPage(page); + textStripper.setEndPage(page); + pdfText += textStripper.getText(pdfDocument); + } + } + } + + pdfDocument.close(); + + return pdfText.contains(text); + } + + public boolean pageCount(PDDocument pdfDocument, int pageCount, String comparator) + throws IOException { + int actualPageCount = pdfDocument.getNumberOfPages(); + pdfDocument.close(); + + switch (comparator.toLowerCase()) { + case "greater": + return actualPageCount > pageCount; + case "equal": + return actualPageCount == pageCount; + case "less": + return actualPageCount < pageCount; + default: + throw new IllegalArgumentException( + "Invalid comparator. Only 'greater', 'equal', and 'less' are supported."); + } + } + + public boolean pageSize(PDDocument pdfDocument, String expectedPageSize) throws IOException { + PDPage firstPage = pdfDocument.getPage(0); + PDRectangle mediaBox = firstPage.getMediaBox(); + + float actualPageWidth = mediaBox.getWidth(); + float actualPageHeight = mediaBox.getHeight(); + + pdfDocument.close(); + + // Assumes the expectedPageSize is in the format "widthxheight", e.g. "595x842" for A4 + String[] dimensions = expectedPageSize.split("x"); + float expectedPageWidth = Float.parseFloat(dimensions[0]); + float expectedPageHeight = Float.parseFloat(dimensions[1]); + + // Checks if the actual page size matches the expected page size + return actualPageWidth == expectedPageWidth && actualPageHeight == expectedPageHeight; + } + + public static byte[] convertFromPdf( + byte[] inputStream, + String imageType, + ImageType colorType, + boolean singleImage, + int DPI, + String filename) + throws IOException, Exception { + try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) { + PDFRenderer pdfRenderer = new PDFRenderer(document); + int pageCount = document.getNumberOfPages(); + + // Create a ByteArrayOutputStream to save the image(s) to + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + if (singleImage) { + if (imageType.toLowerCase().equals("tiff") + || imageType.toLowerCase().equals("tif")) { + // Write the images to the output stream as a TIFF with multiple frames + ImageWriter writer = ImageIO.getImageWritersByFormatName("tiff").next(); + ImageWriteParam param = writer.getDefaultWriteParam(); + param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + param.setCompressionType("ZLib"); + param.setCompressionQuality(1.0f); + + try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) { + writer.setOutput(ios); + writer.prepareWriteSequence(null); + + for (int i = 0; i < pageCount; ++i) { + BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); + writer.writeToSequence(new IIOImage(image, null, null), param); + } + + writer.endWriteSequence(); + } + + writer.dispose(); + } else { + // Combine all images into a single big image + BufferedImage image = pdfRenderer.renderImageWithDPI(0, DPI, colorType); + BufferedImage combined = + new BufferedImage( + image.getWidth(), + image.getHeight() * pageCount, + BufferedImage.TYPE_INT_RGB); + Graphics g = combined.getGraphics(); + + for (int i = 0; i < pageCount; ++i) { + if (i != 0) { + image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); + } + g.drawImage(image, 0, i * image.getHeight(), null); + } + + // Write the image to the output stream + ImageIO.write(combined, imageType, baos); + } + + // Log that the image was successfully written to the byte array + logger.info("Image successfully written to byte array"); + } else { + // Zip the images and return as byte array + try (ZipOutputStream zos = new ZipOutputStream(baos)) { + for (int i = 0; i < pageCount; ++i) { + BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); + try (ByteArrayOutputStream baosImage = new ByteArrayOutputStream()) { + ImageIO.write(image, imageType, baosImage); + + // Add the image to the zip file + zos.putNextEntry( + new ZipEntry( + String.format( + filename + "_%d.%s", + i + 1, + imageType.toLowerCase()))); + zos.write(baosImage.toByteArray()); + } + } + // Log that the images were successfully written to the byte array + logger.info("Images successfully written to byte array as a zip"); + } + } + return baos.toByteArray(); + } catch (IOException e) { + // Log an error message if there is an issue converting the PDF to an image + logger.error("Error converting PDF to image", e); + throw e; + } + } + + public static byte[] imageToPdf( + MultipartFile[] files, String fitOption, boolean autoRotate, String colorType) + throws IOException { + try (PDDocument doc = new PDDocument()) { + for (MultipartFile file : files) { + String contentType = file.getContentType(); + String originalFilename = file.getOriginalFilename(); + if (originalFilename != null + && (originalFilename.toLowerCase().endsWith(".tiff") + || originalFilename.toLowerCase().endsWith(".tif"))) { + ImageReader reader = ImageIO.getImageReadersByFormatName("tiff").next(); + reader.setInput(ImageIO.createImageInputStream(file.getInputStream())); + int numPages = reader.getNumImages(true); + for (int i = 0; i < numPages; i++) { + BufferedImage pageImage = reader.read(i); + BufferedImage convertedImage = + ImageProcessingUtils.convertColorType(pageImage, colorType); + PDImageXObject pdImage = + LosslessFactory.createFromImage(doc, convertedImage); + addImageToDocument(doc, pdImage, fitOption, autoRotate); + } + } else { + BufferedImage image = ImageIO.read(file.getInputStream()); + BufferedImage convertedImage = + ImageProcessingUtils.convertColorType(image, colorType); + // Use JPEGFactory if it's JPEG since JPEG is lossy + PDImageXObject pdImage = + (contentType != null && contentType.equals("image/jpeg")) + ? JPEGFactory.createFromImage(doc, convertedImage) + : LosslessFactory.createFromImage(doc, convertedImage); + addImageToDocument(doc, pdImage, fitOption, autoRotate); + } + } + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + doc.save(byteArrayOutputStream); + logger.info("PDF successfully saved to byte array"); + return byteArrayOutputStream.toByteArray(); + } + } + + private static void addImageToDocument( + PDDocument doc, PDImageXObject image, String fitOption, boolean autoRotate) + throws IOException { + boolean imageIsLandscape = image.getWidth() > image.getHeight(); + PDRectangle pageSize = PDRectangle.A4; + + System.out.println(fitOption); + + if (autoRotate && imageIsLandscape) { + pageSize = new PDRectangle(pageSize.getHeight(), pageSize.getWidth()); + } + + if ("fitDocumentToImage".equals(fitOption)) { + pageSize = new PDRectangle(image.getWidth(), image.getHeight()); + } + + PDPage page = new PDPage(pageSize); + doc.addPage(page); + + float pageWidth = page.getMediaBox().getWidth(); + float pageHeight = page.getMediaBox().getHeight(); + + try (PDPageContentStream contentStream = new PDPageContentStream(doc, page)) { + if ("fillPage".equals(fitOption) || "fitDocumentToImage".equals(fitOption)) { + contentStream.drawImage(image, 0, 0, pageWidth, pageHeight); + } else if ("maintainAspectRatio".equals(fitOption)) { + float imageAspectRatio = (float) image.getWidth() / (float) image.getHeight(); + float pageAspectRatio = pageWidth / pageHeight; + + float scaleFactor = 1.0f; + if (imageAspectRatio > pageAspectRatio) { + scaleFactor = pageWidth / image.getWidth(); + } else { + scaleFactor = pageHeight / image.getHeight(); + } + + float xPos = (pageWidth - (image.getWidth() * scaleFactor)) / 2; + float yPos = (pageHeight - (image.getHeight() * scaleFactor)) / 2; + contentStream.drawImage( + image, + xPos, + yPos, + image.getWidth() * scaleFactor, + image.getHeight() * scaleFactor); + } + } catch (IOException e) { + logger.error("Error adding image to PDF", e); + throw e; + } + } + + public static byte[] overlayImage( + byte[] pdfBytes, byte[] imageBytes, float x, float y, boolean everyPage) + throws IOException { + + PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes)); + + // Get the first page of the PDF + int pages = document.getNumberOfPages(); + for (int i = 0; i < pages; i++) { + PDPage page = document.getPage(i); + try (PDPageContentStream contentStream = + new PDPageContentStream( + document, page, PDPageContentStream.AppendMode.APPEND, true)) { + // Create an image object from the image bytes + PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, ""); + // Draw the image onto the page at the specified x and y coordinates + contentStream.drawImage(image, x, y); + logger.info("Image successfully overlayed onto PDF"); + if (!everyPage && i == 0) { + break; + } + } catch (IOException e) { + // Log an error message if there is an issue overlaying the image onto the PDF + logger.error("Error overlaying image onto PDF", e); + throw e; + } + } + // Create a ByteArrayOutputStream to save the PDF to + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + document.save(baos); + logger.info("PDF successfully saved to byte array"); + return baos.toByteArray(); + } +} diff --git a/src/main/java/stirling/software/SPDF/utils/WebResponseUtils.java b/src/main/java/stirling/software/SPDF/utils/WebResponseUtils.java index 4958a00d..1114de64 100644 --- a/src/main/java/stirling/software/SPDF/utils/WebResponseUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/WebResponseUtils.java @@ -1,67 +1,67 @@ -package stirling.software.SPDF.utils; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; - -import org.apache.pdfbox.pdmodel.PDDocument; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.multipart.MultipartFile; - -public class WebResponseUtils { - - public static ResponseEntity boasToWebResponse( - ByteArrayOutputStream baos, String docName) throws IOException { - return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName); - } - - public static ResponseEntity boasToWebResponse( - ByteArrayOutputStream baos, String docName, MediaType mediaType) throws IOException { - return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName, mediaType); - } - - public static ResponseEntity multiPartFileToWebResponse(MultipartFile file) - throws IOException { - String fileName = file.getOriginalFilename(); - MediaType mediaType = MediaType.parseMediaType(file.getContentType()); - - byte[] bytes = file.getBytes(); - - return bytesToWebResponse(bytes, fileName, mediaType); - } - - public static ResponseEntity bytesToWebResponse( - byte[] bytes, String docName, MediaType mediaType) throws IOException { - - // Return the PDF as a response - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(mediaType); - headers.setContentLength(bytes.length); - String encodedDocName = - URLEncoder.encode(docName, StandardCharsets.UTF_8.toString()) - .replaceAll("\\+", "%20"); - headers.setContentDispositionFormData("attachment", encodedDocName); - return new ResponseEntity<>(bytes, headers, HttpStatus.OK); - } - - public static ResponseEntity bytesToWebResponse(byte[] bytes, String docName) - throws IOException { - return bytesToWebResponse(bytes, docName, MediaType.APPLICATION_PDF); - } - - public static ResponseEntity pdfDocToWebResponse(PDDocument document, String docName) - throws IOException { - - // Open Byte Array and save document to it - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - document.save(baos); - // Close the document - document.close(); - - return boasToWebResponse(baos, docName); - } -} +package stirling.software.SPDF.utils; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.multipart.MultipartFile; + +public class WebResponseUtils { + + public static ResponseEntity boasToWebResponse( + ByteArrayOutputStream baos, String docName) throws IOException { + return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName); + } + + public static ResponseEntity boasToWebResponse( + ByteArrayOutputStream baos, String docName, MediaType mediaType) throws IOException { + return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName, mediaType); + } + + public static ResponseEntity multiPartFileToWebResponse(MultipartFile file) + throws IOException { + String fileName = file.getOriginalFilename(); + MediaType mediaType = MediaType.parseMediaType(file.getContentType()); + + byte[] bytes = file.getBytes(); + + return bytesToWebResponse(bytes, fileName, mediaType); + } + + public static ResponseEntity bytesToWebResponse( + byte[] bytes, String docName, MediaType mediaType) throws IOException { + + // Return the PDF as a response + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(mediaType); + headers.setContentLength(bytes.length); + String encodedDocName = + URLEncoder.encode(docName, StandardCharsets.UTF_8.toString()) + .replaceAll("\\+", "%20"); + headers.setContentDispositionFormData("attachment", encodedDocName); + return new ResponseEntity<>(bytes, headers, HttpStatus.OK); + } + + public static ResponseEntity bytesToWebResponse(byte[] bytes, String docName) + throws IOException { + return bytesToWebResponse(bytes, docName, MediaType.APPLICATION_PDF); + } + + public static ResponseEntity pdfDocToWebResponse(PDDocument document, String docName) + throws IOException { + + // Open Byte Array and save document to it + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + document.save(baos); + // Close the document + document.close(); + + return boasToWebResponse(baos, docName); + } +}