diff --git a/build.gradle b/build.gradle index 44e9a249..832cbc2d 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { } group = 'stirling.software' -version = '0.12.0' +version = '0.12.1' sourceCompatibility = '17' repositories { diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/ShowJavascript.java b/src/main/java/stirling/software/SPDF/controller/api/other/ShowJavascript.java new file mode 100644 index 00000000..49ce28c9 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/other/ShowJavascript.java @@ -0,0 +1,141 @@ +package stirling.software.SPDF.controller.api.other; + +import java.io.IOException; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +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.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.utils.GeneralUtils; +import stirling.software.SPDF.utils.PdfUtils; +import stirling.software.SPDF.utils.WebResponseUtils; +import org.apache.pdfbox.pdmodel.*; +import org.apache.pdfbox.pdmodel.common.*; +import org.apache.pdfbox.pdmodel.PDPageContentStream.*; +import org.springframework.web.bind.annotation.*; +import org.springframework.http.*; +import org.springframework.web.multipart.MultipartFile; +import io.swagger.v3.oas.annotations.*; +import io.swagger.v3.oas.annotations.media.*; +import io.swagger.v3.oas.annotations.parameters.*; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.apache.pdfbox.text.TextPosition; +import org.apache.tomcat.util.http.ResponseUtil; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.ArrayList; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.itextpdf.io.font.constants.StandardFonts; +import com.itextpdf.kernel.font.PdfFont; +import com.itextpdf.kernel.font.PdfFontFactory; +import com.itextpdf.kernel.geom.Rectangle; +import com.itextpdf.kernel.pdf.PdfReader; +import com.itextpdf.kernel.pdf.PdfStream; +import com.itextpdf.kernel.pdf.PdfWriter; +import com.itextpdf.kernel.pdf.PdfArray; +import com.itextpdf.kernel.pdf.PdfDictionary; +import com.itextpdf.kernel.pdf.PdfDocument; +import com.itextpdf.kernel.pdf.PdfName; +import com.itextpdf.kernel.pdf.PdfObject; +import com.itextpdf.kernel.pdf.PdfPage; +import com.itextpdf.kernel.pdf.canvas.PdfCanvas; +import com.itextpdf.layout.Canvas; +import com.itextpdf.layout.element.Paragraph; +import com.itextpdf.layout.properties.TextAlignment; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.io.*; +import org.apache.pdfbox.pdmodel.*; +import org.apache.pdfbox.text.*; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import io.swagger.v3.oas.annotations.*; +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.http.ResponseEntity; +@RestController +@Tag(name = "Other", description = "Other APIs") +public class ShowJavascript { + + private static final Logger logger = LoggerFactory.getLogger(ShowJavascript.class); + @PostMapping(consumes = "multipart/form-data", value = "/show-javascript") + @Operation(summary = "Extract header from PDF file", description = "This endpoint accepts a PDF file and attempts to extract its title or header based on heuristics. Input:PDF Output:PDF Type:SISO") + public ResponseEntity extractHeader( + @RequestPart(value = "fileInput") @Parameter(description = "The input PDF file from which the javascript is to be extracted.", required = true) MultipartFile inputFile) + throws Exception { + + try ( + PdfDocument itextDoc = new PdfDocument(new PdfReader(inputFile.getInputStream())) + ) { + + String name = ""; + String script = ""; + String entryName = "File: "+inputFile.getOriginalFilename() + ", Script: "; + //Javascript + PdfDictionary namesDict = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names); + if (namesDict != null) { + PdfDictionary javascriptDict = namesDict.getAsDictionary(PdfName.JavaScript); + if (javascriptDict != null) { + + PdfArray namesArray = javascriptDict.getAsArray(PdfName.Names); + for (int i = 0; i < namesArray.size(); i += 2) { + if(namesArray.getAsString(i) != null) + name = namesArray.getAsString(i).toString(); + + PdfObject jsCode = namesArray.get(i+1); + if (jsCode instanceof PdfStream) { + byte[] jsCodeBytes = ((PdfStream)jsCode).getBytes(); + String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8); + script = "//" + entryName + name + "\n" +jsCodeStr; + + } else if (jsCode instanceof PdfDictionary) { + // If the JS code is in a dictionary, you'll need to know the key to use. + // Assuming the key is PdfName.JS: + PdfStream jsCodeStream = ((PdfDictionary)jsCode).getAsStream(PdfName.JS); + if (jsCodeStream != null) { + byte[] jsCodeBytes = jsCodeStream.getBytes(); + String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8); + script = "//" + entryName + name + "\n" +jsCodeStr; + } + } + } + + } + } + if(script.equals("")) { + script = "PDF '" +inputFile.getOriginalFilename() + "' does not contain Javascript"; + } + return WebResponseUtils.bytesToWebResponse(script.getBytes(), name + ".js"); + } + + } + + + + +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java b/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java index fea57764..159dd4ab 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java @@ -43,6 +43,7 @@ import com.itextpdf.kernel.pdf.layer.PdfOCProperties; import com.itextpdf.kernel.xmp.XMPException; import com.itextpdf.kernel.xmp.XMPMeta; import com.itextpdf.kernel.xmp.XMPMetaFactory; +import com.itextpdf.kernel.xmp.options.SerializeOptions; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -193,11 +194,13 @@ public class GetInfoOnPDF { if (embeddedFiles != null) { PdfArray namesArray = embeddedFiles.getAsArray(PdfName.Names); - for (int i = 0; i < namesArray.size(); i += 2) { - ObjectNode embeddedFileNode = objectMapper.createObjectNode(); - embeddedFileNode.put("Name", namesArray.getAsString(i).toString()); - // Add other details if required - embeddedFilesArray.add(embeddedFileNode); + if(namesArray != null) { + for (int i = 0; i < namesArray.size(); i += 2) { + ObjectNode embeddedFileNode = objectMapper.createObjectNode(); + embeddedFileNode.put("Name", namesArray.getAsString(i).toString()); + // Add other details if required + embeddedFilesArray.add(embeddedFileNode); + } } } @@ -224,15 +227,33 @@ public class GetInfoOnPDF { if (namesDict != null) { PdfDictionary javascriptDict = namesDict.getAsDictionary(PdfName.JavaScript); if (javascriptDict != null) { - + PdfArray namesArray = javascriptDict.getAsArray(PdfName.Names); for (int i = 0; i < namesArray.size(); i += 2) { ObjectNode jsNode = objectMapper.createObjectNode(); - jsNode.put("JS Name", namesArray.getAsString(i).toString()); - jsNode.put("JS Code", namesArray.getAsString(i + 1).toString()); + if(namesArray.getAsString(i) != null) + jsNode.put("JS Name", namesArray.getAsString(i).toString()); + + // Here we check for a PdfStream object and retrieve the JS code from it + PdfObject jsCode = namesArray.get(i+1); + if (jsCode instanceof PdfStream) { + byte[] jsCodeBytes = ((PdfStream)jsCode).getBytes(); + String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8); + jsNode.put("JS Script Length", jsCodeStr.length()); + } else if (jsCode instanceof PdfDictionary) { + // If the JS code is in a dictionary, you'll need to know the key to use. + // Assuming the key is PdfName.JS: + PdfStream jsCodeStream = ((PdfDictionary)jsCode).getAsStream(PdfName.JS); + if (jsCodeStream != null) { + byte[] jsCodeBytes = jsCodeStream.getBytes(); + String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8); + jsNode.put("JS Script Character Length", jsCodeStr.length()); + } + } + javascriptArray.add(jsNode); } - + } } other.set("JavaScript", javascriptArray); @@ -305,16 +326,15 @@ public class GetInfoOnPDF { } other.set("Bookmarks/Outline/TOC", bookmarksArray); + byte[] xmpBytes = itextDoc.getXmpMetadata(); String xmpString = null; - try { - byte[] xmpBytes = itextDoc.getXmpMetadata(); - if (xmpBytes != null) { + if (xmpBytes != null) { + try { XMPMeta xmpMeta = XMPMetaFactory.parseFromBuffer(xmpBytes); - xmpString = xmpMeta.dumpObject(); - + xmpString = new String(XMPMetaFactory.serializeToBuffer(xmpMeta, new SerializeOptions())); + } catch (XMPException e) { + e.printStackTrace(); } - } catch (XMPException e) { - e.printStackTrace(); } other.put("XMPMetadata", xmpString); @@ -416,8 +436,10 @@ public class GetInfoOnPDF { for (PdfAnnotation annotation : annotations) { if (annotation instanceof PdfLinkAnnotation) { PdfLinkAnnotation linkAnnotation = (PdfLinkAnnotation) annotation; - String uri = linkAnnotation.getAction().toString(); - uniqueURIs.add(uri); // Add to set to ensure uniqueness + if(linkAnnotation != null && linkAnnotation.getAction() != null) { + String uri = linkAnnotation.getAction().toString(); + uniqueURIs.add(uri); // Add to set to ensure uniqueness + } } } 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 ccc19b09..f03dcec8 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,6 +1,7 @@ package stirling.software.SPDF.controller.api.security; 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.PDResources; import org.apache.pdfbox.pdmodel.PDPageTree; @@ -21,7 +22,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema; import stirling.software.SPDF.utils.WebResponseUtils; - +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.cos.COSString; import java.io.IOException; import java.io.InputStream; @@ -75,8 +78,24 @@ public class SanitizeController { return WebResponseUtils.pdfDocToWebResponse(document, inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_sanitized.pdf"); } } - private void sanitizeJavaScript(PDDocument document) throws IOException { - for (PDPage page : document.getPages()) { + 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; @@ -89,13 +108,28 @@ public class SanitizeController { PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); if (acroForm != null) { for (PDField field : acroForm.getFields()) { - if (field.getActions().getF() instanceof PDActionJavaScript) { - field.getActions().setF(null); - } + 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(); diff --git a/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java b/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java index 8fa57d08..157f76e4 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java @@ -31,7 +31,15 @@ public class OtherWebController { modelAndView.addObject("currentPage", "extract-image-scans"); return modelAndView; } - + + @GetMapping("/show-javascript") + @Hidden + public String extractJavascriptForm(Model model) { + model.addAttribute("currentPage", "show-javascript"); + return "other/show-javascript"; + } + + @GetMapping("/add-page-numbers") @Hidden public String addPageNumbersForm(Model model) { diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index bc01fb5f..977260a5 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -255,12 +255,21 @@ home.PdfToSinglePage.title=PDF to Single Large Page home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page + +home.showJS.title=Show Javascript +home.showJS.desc=Searches and displays any JS injected into a PDF +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### - +#showJS +showJS.title=Show Javascript +showJS.header=Show Javascript +showJS.downloadJS=Download Javascript +showJS.submit=Show #pdfToSinglePage diff --git a/src/main/resources/static/css/prism.css b/src/main/resources/static/css/prism.css new file mode 100644 index 00000000..f8de88e3 --- /dev/null +++ b/src/main/resources/static/css/prism.css @@ -0,0 +1,3 @@ +/* PrismJS 1.29.0 +https://prismjs.com/download.html#themes=prism-coy&languages=clike+javascript */ +code[class*=language-],pre[class*=language-]{color:#000;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{position:relative;margin:.5em 0;overflow:visible;padding:1px}pre[class*=language-]>code{position:relative;z-index:1;border-left:10px solid #358ccb;box-shadow:-1px 0 0 0 #358ccb,0 0 0 1px #dfdfdf;background-color:#fdfdfd;background-image:linear-gradient(transparent 50%,rgba(69,142,209,.04) 50%);background-size:3em 3em;background-origin:content-box;background-attachment:local}code[class*=language-]{max-height:inherit;height:inherit;padding:0 1em;display:block;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background-color:#fdfdfd;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin-bottom:1em}:not(pre)>code[class*=language-]{position:relative;padding:.2em;border-radius:.3em;color:#c92c2c;border:1px solid rgba(0,0,0,.1);display:inline;white-space:normal}pre[class*=language-]:after,pre[class*=language-]:before{content:'';display:block;position:absolute;bottom:.75em;left:.18em;width:40%;height:20%;max-height:13em;box-shadow:0 13px 8px #979797;-webkit-transform:rotate(-2deg);-moz-transform:rotate(-2deg);-ms-transform:rotate(-2deg);-o-transform:rotate(-2deg);transform:rotate(-2deg)}pre[class*=language-]:after{right:.75em;left:auto;-webkit-transform:rotate(2deg);-moz-transform:rotate(2deg);-ms-transform:rotate(2deg);-o-transform:rotate(2deg);transform:rotate(2deg)}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#7d8b99}.token.punctuation{color:#5f6364}.token.boolean,.token.constant,.token.deleted,.token.function-name,.token.number,.token.property,.token.symbol,.token.tag{color:#c92c2c}.token.attr-name,.token.builtin,.token.char,.token.function,.token.inserted,.token.selector,.token.string{color:#2f9c0a}.token.entity,.token.operator,.token.url,.token.variable{color:#a67f59;background:rgba(255,255,255,.5)}.token.atrule,.token.attr-value,.token.class-name,.token.keyword{color:#1990b8}.token.important,.token.regex{color:#e90}.language-css .token.string,.style .token.string{color:#a67f59;background:rgba(255,255,255,.5)}.token.important{font-weight:400}.token.bold{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.namespace{opacity:.7}@media screen and (max-width:767px){pre[class*=language-]:after,pre[class*=language-]:before{bottom:14px;box-shadow:none}}pre[class*=language-].line-numbers.line-numbers{padding-left:0}pre[class*=language-].line-numbers.line-numbers code{padding-left:3.8em}pre[class*=language-].line-numbers.line-numbers .line-numbers-rows{left:0}pre[class*=language-][data-line]{padding-top:0;padding-bottom:0;padding-left:0}pre[data-line] code{position:relative;padding-left:4em}pre .line-highlight{margin-top:0} diff --git a/src/main/resources/static/images/js.svg b/src/main/resources/static/images/js.svg new file mode 100644 index 00000000..8b198bfc --- /dev/null +++ b/src/main/resources/static/images/js.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/static/js/fileInput.js b/src/main/resources/static/js/fileInput.js index 9379a7bf..b544304a 100644 --- a/src/main/resources/static/js/fileInput.js +++ b/src/main/resources/static/js/fileInput.js @@ -80,9 +80,13 @@ function setupFileInput(chooser) { document.body.addEventListener('dragleave', dragleaveListener); document.body.addEventListener('drop', dropListener); - $("#" + elementId).on("change", function() { - handleFileInputChange(this); - }); + console.log("Element Id: ", elementId); + + $("#" + elementId).on("change", function(e) { + allFiles = Array.from(e.target.files); + handleFileInputChange(this); + }); + function handleFileInputChange(inputElement) { const files = allFiles; @@ -92,6 +96,7 @@ function setupFileInput(chooser) { fileNames.forEach(fileName => { selectedFilesContainer.append("
" + fileName + "
"); }); + if (fileNames.length === 1) { $(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames[0]); } else if (fileNames.length > 1) { diff --git a/src/main/resources/static/js/thirdParty/prism.js b/src/main/resources/static/js/thirdParty/prism.js new file mode 100644 index 00000000..82b52765 --- /dev/null +++ b/src/main/resources/static/js/thirdParty/prism.js @@ -0,0 +1,5 @@ +/* PrismJS 1.29.0 +https://prismjs.com/download.html#themes=prism-coy&languages=clike+javascript */ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(jg.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a"+i.content+""},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); +Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}; +Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp("(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])"),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r\n,.;:})\\]]|//))"),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript; diff --git a/src/main/resources/templates/fragments/navbar.html b/src/main/resources/templates/fragments/navbar.html index c20a498c..ea253dab 100644 --- a/src/main/resources/templates/fragments/navbar.html +++ b/src/main/resources/templates/fragments/navbar.html @@ -109,7 +109,7 @@ -
+ + +
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/security/get-info-on-pdf.html b/src/main/resources/templates/security/get-info-on-pdf.html index 193056e4..06cc1052 100644 --- a/src/main/resources/templates/security/get-info-on-pdf.html +++ b/src/main/resources/templates/security/get-info-on-pdf.html @@ -73,46 +73,45 @@ function renderJsonSection(key, value, depth = 0) { - // Replace spaces and other non-alphanumeric characters with underscores for valid IDs let safeKey = (typeof key === "string") ? key.replace(/[^a-zA-Z0-9]/g, '_') : key; let output = `
`; - // Check if the value is an object and has children - if (value && typeof value === 'object') { - // For arrays and non-array objects - if (Array.isArray(value) && value.length === 0) { - output += `${key}: Empty array`; - } else if (!Array.isArray(value) && Object.keys(value).length === 0) { - output += `${key}: Empty object`; - } else { - output += ` - `; - } - } else { - // For simple key-value pairs - output += `${key}: ${value}`; - } - + if (key === 'XMPMetadata' && typeof value === "string") { + output += ``; + } else if (value && typeof value === 'object') { + if (Array.isArray(value) && value.length === 0) { + output += `${key}: Empty array`; + } else if (!Array.isArray(value) && Object.keys(value).length === 0) { + output += `${key}: Empty object`; + } else { + output += ``; + } + } else { + output += `${key}: ${value}`; + } output += ` -
-
-
`; + +
+
`; - // Check if the value is a nested object - if (value && typeof value === 'object' && !Array.isArray(value)) { + if (key === 'XMPMetadata' && typeof value === "string") { + output += `
${escapeHTML(value)}
`; + } else if (value && typeof value === 'object' && !Array.isArray(value)) { output += '
'; if (Object.keys(value).length) { for (const subKey in value) { output += renderJsonSection(subKey, value[subKey], depth + 1); } } else { - output += '

Empty object

'; + output += '

Empty

'; } output += '
'; } else if (value && typeof value === 'object' && Array.isArray(value)) { @@ -123,7 +122,7 @@ output += renderJsonSection(arrayKey, val, depth + 1); }); } else { - output += '

Empty array

'; + output += '

Empty

'; } output += '
'; } @@ -132,6 +131,14 @@ return output; } + + + function escapeHTML(s) { + if(s) + return s.replace(/&/g, '&').replace(//g, '>'); + + return null; + }