security and apple icons
|
@ -8,7 +8,7 @@ plugins {
|
|||
}
|
||||
|
||||
group = 'stirling.software'
|
||||
version = '0.12.2'
|
||||
version = '0.12.3'
|
||||
sourceCompatibility = '17'
|
||||
|
||||
repositories {
|
||||
|
@ -45,8 +45,9 @@ launch4j {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.yaml:snakeyaml:2.1'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web:3.1.2'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.1'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.2'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.2'
|
||||
// https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio
|
||||
implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4'
|
||||
|
|
|
@ -30,7 +30,7 @@ public class MetricsFilter extends OncePerRequestFilter {
|
|||
|
||||
//System.out.println("uri="+uri + ", method=" + request.getMethod() );
|
||||
// Ignore static resources
|
||||
if (!(uri.startsWith("/js") || uri.startsWith("/images") || uri.endsWith(".ico") || uri.endsWith(".css") || uri.endsWith(".svg")|| uri.endsWith(".js") || uri.contains("swagger") || uri.startsWith("/api"))) {
|
||||
if (!(uri.startsWith("/js") || uri.startsWith("api-docs") || uri.startsWith("/images") || uri.endsWith(".png") || uri.endsWith(".ico") || uri.endsWith(".css") || uri.endsWith(".svg")|| uri.endsWith(".js") || uri.contains("swagger") || uri.startsWith("/api"))) {
|
||||
Counter counter = Counter.builder("http.requests")
|
||||
.tag("uri", uri)
|
||||
.tag("method", request.getMethod())
|
||||
|
|
BIN
src/main/resources/static/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
src/main/resources/static/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
src/main/resources/static/apple-touch-icon-114x114.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
src/main/resources/static/apple-touch-icon-120x120.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
src/main/resources/static/apple-touch-icon-144x144.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
src/main/resources/static/apple-touch-icon-152x152.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
src/main/resources/static/apple-touch-icon-180x180.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
src/main/resources/static/apple-touch-icon-57x57.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
src/main/resources/static/apple-touch-icon-60x60.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
src/main/resources/static/apple-touch-icon-72x72.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
src/main/resources/static/apple-touch-icon-76x76.png
Normal file
After Width: | Height: | Size: 5 KiB |
BIN
src/main/resources/static/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 14 KiB |
9
src/main/resources/static/browserconfig.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#2d89ef</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
BIN
src/main/resources/static/favicon-16x16.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/main/resources/static/favicon-32x32.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 15 KiB |
20
src/main/resources/static/manifest.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "Stirling-PDF",
|
||||
"short_name": "Stirling-PDF",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#000000"
|
||||
}
|
BIN
src/main/resources/static/mstile-150x150.png
Normal file
After Width: | Height: | Size: 13 KiB |
41
src/main/resources/static/safari-pinned-tab.svg
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="752.000000pt" height="752.000000pt" viewBox="0 0 752.000000 752.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,752.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M5025 7049 c-87 -21 -1141 -257 -2770 -620 -566 -126 -1057 -239
|
||||
-1090 -250 -153 -56 -253 -125 -297 -206 -62 -113 -58 67 -58 -2629 l0 -2462
|
||||
22 -5 c73 -19 1640 -347 1655 -347 9 0 58 39 107 88 596 575 1106 1069 1611
|
||||
1556 336 324 725 700 865 836 140 135 383 369 540 520 157 151 390 377 518
|
||||
502 l233 228 -23 33 c-12 17 -89 122 -170 231 l-148 200 0 944 c0 894 -1 943
|
||||
-17 938 -40 -11 -853 -186 -868 -186 -13 0 -15 38 -15 325 0 179 -3 325 -7
|
||||
324 -5 -1 -44 -10 -88 -20z m-2205 -1513 c167 -23 343 -73 504 -145 l66 -30 0
|
||||
-536 0 -535 -40 0 c-40 0 -46 6 -263 224 -211 212 -226 226 -307 265 -142 68
|
||||
-274 75 -427 21 -141 -49 -213 -161 -213 -329 0 -89 16 -124 82 -172 91 -66
|
||||
206 -115 499 -212 228 -76 340 -126 458 -204 157 -105 282 -253 366 -436 59
|
||||
-128 70 -203 69 -452 -1 -213 -2 -224 -31 -337 -33 -123 -89 -255 -144 -335
|
||||
-43 -63 -169 -194 -240 -248 -172 -134 -388 -199 -705 -212 -260 -11 -473 34
|
||||
-858 179 l-246 93 3 540 2 540 43 3 c34 3 43 0 48 -15 48 -159 263 -392 514
|
||||
-560 159 -107 273 -152 401 -160 154 -10 269 42 353 160 74 104 102 196 90
|
||||
295 -18 145 -192 275 -511 381 -375 125 -541 216 -706 386 -198 203 -273 411
|
||||
-271 760 l0 170 38 110 c77 223 147 338 285 468 158 148 283 219 478 268 252
|
||||
64 465 81 663 55z"/>
|
||||
<path d="M5633 2297 c-267 -688 -370 -952 -393 -1002 l-27 -60 -114 -44 c-63
|
||||
-24 -127 -50 -144 -57 -16 -7 -104 -43 -195 -79 -91 -36 -174 -70 -185 -75
|
||||
-11 -5 -46 -19 -77 -30 -32 -12 -58 -26 -58 -31 0 -6 -4 -8 -10 -4 -5 3 -17 1
|
||||
-27 -4 -25 -14 -169 -44 -1248 -256 -324 -64 -599 -119 -610 -122 -17 -5 -15
|
||||
-8 12 -15 63 -17 633 -117 803 -142 293 -42 276 -42 471 -3 96 20 226 45 289
|
||||
57 63 12 282 55 485 95 204 40 422 83 485 95 63 12 237 46 385 75 231 46 390
|
||||
77 765 149 l65 13 -50 11 c-27 6 -192 34 -365 62 -173 28 -331 54 -350 59 -41
|
||||
9 -41 -12 1 226 30 175 61 355 124 720 20 116 42 235 49 266 12 53 11 61 -12
|
||||
118 -13 33 -27 61 -31 61 -3 0 -20 -37 -38 -83z"/>
|
||||
<path d="M6404 928 c4 -23 8 -44 10 -45 2 -2 44 4 94 14 51 9 89 20 84 24 -8
|
||||
9 -158 49 -180 49 -11 0 -13 -9 -8 -42z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
19
src/main/resources/static/site.webmanifest
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "Stirling PDF",
|
||||
"short_name": "Stirling PDF",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
|
@ -5,6 +5,24 @@
|
|||
|
||||
<title th:text="${@appName} + (${title} != null and ${title} != '' ? ' - ' + ${title} : '')"></title>
|
||||
<link rel="shortcut icon" href="favicon.svg">
|
||||
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="apple-touch-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="apple-touch-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="apple-touch-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="apple-touch-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="apple-touch-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="apple-touch-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="apple-touch-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon-180x180.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png">
|
||||
<link rel="manifest" href="site.webmanifest">
|
||||
<link rel="mask-icon" href="safari-pinned-tab.svg" color="#ca2b2a">
|
||||
<meta name="msapplication-TileColor" content="#2d89ef">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<!-- jQuery -->
|
||||
|
|
|
@ -54,11 +54,22 @@
|
|||
body: formData
|
||||
}).then(response => response.text())
|
||||
.then(data => {
|
||||
// Escape < and > characters
|
||||
// Escape < and > characters
|
||||
let escapedData = data.replace(/</g, '<').replace(/>/g, '>');
|
||||
|
||||
// Wrap the JavaScript content in a pre and code tag and add it to the div
|
||||
document.querySelector('#script-content').innerHTML = '<pre><code class="language-javascript">' + escapedData + '</code></pre>';
|
||||
// Create the elements manually
|
||||
let preElement = document.createElement('pre');
|
||||
let codeElement = document.createElement('code');
|
||||
codeElement.classList.add('language-javascript');
|
||||
codeElement.textContent = escapedData; // Use textContent instead of innerHTML
|
||||
preElement.appendChild(codeElement);
|
||||
|
||||
let scriptContent = document.querySelector('#script-content');
|
||||
// Clear existing content, if any
|
||||
while (scriptContent.firstChild) {
|
||||
scriptContent.removeChild(scriptContent.firstChild);
|
||||
}
|
||||
scriptContent.appendChild(preElement);
|
||||
|
||||
// Highlight the code using Prism.js
|
||||
Prism.highlightAll();
|
||||
|
|
|
@ -35,110 +35,120 @@
|
|||
</div>
|
||||
<script>
|
||||
|
||||
document.getElementById("pdfInfoForm").addEventListener("submit", function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Prevent the form from submitting the traditional way
|
||||
document.getElementById("pdfInfoForm").addEventListener("submit", function(event) {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(event.target);
|
||||
|
||||
fetch('get-info-on-pdf', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
displayJsonData(data);
|
||||
setDownloadLink(data);
|
||||
document.getElementById("downloadJson").style.display = "block";
|
||||
})
|
||||
.catch(error => console.error('Error:', error));
|
||||
});
|
||||
|
||||
// Fetch the formData
|
||||
const formData = new FormData(event.target);
|
||||
|
||||
fetch('get-info-on-pdf', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
displayJsonData(data); // Display the data
|
||||
setDownloadLink(data); // Set download link
|
||||
document.getElementById("downloadJson").style.display = "block";
|
||||
})
|
||||
.catch(error => console.error('Error:', error));
|
||||
});
|
||||
function displayJsonData(jsonData) {
|
||||
const jsonContent = document.getElementById('json-content');
|
||||
|
||||
while (jsonContent.firstChild) {
|
||||
jsonContent.removeChild(jsonContent.firstChild);
|
||||
}
|
||||
|
||||
function displayJsonData(jsonData) {
|
||||
let content = '';
|
||||
for (const key in jsonData) {
|
||||
content += renderJsonSection(key, jsonData[key]);
|
||||
}
|
||||
document.getElementById('json-content').innerHTML = content;
|
||||
}
|
||||
for (const key in jsonData) {
|
||||
const sectionElem = createJsonSection(key, jsonData[key]);
|
||||
jsonContent.appendChild(sectionElem);
|
||||
}
|
||||
}
|
||||
|
||||
function setDownloadLink(jsonData) {
|
||||
const downloadLink = document.getElementById('downloadJson');
|
||||
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(jsonData, null, 4));
|
||||
downloadLink.setAttribute("href", dataStr);
|
||||
downloadLink.setAttribute("download", "data.json");
|
||||
}
|
||||
function setDownloadLink(jsonData) {
|
||||
const downloadLink = document.getElementById('downloadJson');
|
||||
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(jsonData, null, 4));
|
||||
downloadLink.setAttribute("href", dataStr);
|
||||
downloadLink.setAttribute("download", "data.json");
|
||||
}
|
||||
|
||||
function createJsonSection(key, value, depth = 0) {
|
||||
let safeKey = (typeof key === "string") ? key.replace(/[^a-zA-Z0-9]/g, '_') : key;
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card mb-3';
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.className = 'card-header';
|
||||
header.id = `${safeKey}-heading-${depth}`;
|
||||
const h5Elem = document.createElement('h5');
|
||||
h5Elem.className = 'mb-0';
|
||||
|
||||
if (key === 'XMPMetadata' && typeof value === "string") {
|
||||
const buttonElem = createButtonElement(key, safeKey, depth);
|
||||
h5Elem.appendChild(buttonElem);
|
||||
} else if (value && typeof value === 'object') {
|
||||
if (Array.isArray(value) && value.length === 0) {
|
||||
h5Elem.textContent = `${key}: Empty array`;
|
||||
} else if (!Array.isArray(value) && Object.keys(value).length === 0) {
|
||||
h5Elem.textContent = `${key}: Empty object`;
|
||||
} else {
|
||||
const buttonElem = createButtonElement(key, safeKey, depth);
|
||||
h5Elem.appendChild(buttonElem);
|
||||
}
|
||||
} else {
|
||||
h5Elem.textContent = `${key}: ${String(value)}`;
|
||||
}
|
||||
|
||||
header.appendChild(h5Elem);
|
||||
card.appendChild(header);
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.id = `${safeKey}-content-${depth}`;
|
||||
content.className = 'collapse';
|
||||
content.setAttribute('aria-labelledby', `${safeKey}-heading-${depth}`);
|
||||
|
||||
if (key === 'XMPMetadata' && typeof value === "string") {
|
||||
const body = document.createElement('div');
|
||||
body.className = 'card-body';
|
||||
const preElem = document.createElement('pre');
|
||||
preElem.textContent = value; // Not escaping since we're using textContent
|
||||
body.appendChild(preElem);
|
||||
content.appendChild(body);
|
||||
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
const body = document.createElement('div');
|
||||
body.className = 'card-body';
|
||||
for (const subKey in value) {
|
||||
const subElem = createJsonSection(subKey, value[subKey], depth + 1);
|
||||
body.appendChild(subElem);
|
||||
}
|
||||
content.appendChild(body);
|
||||
} else if (value && typeof value === 'object' && Array.isArray(value)) {
|
||||
const body = document.createElement('div');
|
||||
body.className = 'card-body';
|
||||
value.forEach((val, index) => {
|
||||
const subElem = createJsonSection(`${key}[${index}]`, val, depth + 1);
|
||||
body.appendChild(subElem);
|
||||
});
|
||||
content.appendChild(body);
|
||||
}
|
||||
|
||||
card.appendChild(content);
|
||||
return card;
|
||||
}
|
||||
|
||||
|
||||
function renderJsonSection(key, value, depth = 0) {
|
||||
let safeKey = (typeof key === "string") ? key.replace(/[^a-zA-Z0-9]/g, '_') : key;
|
||||
|
||||
let output = `<div class="card mb-3">
|
||||
<div class="card-header" id="${safeKey}-heading-${depth}">
|
||||
<h5 class="mb-0">`;
|
||||
|
||||
if (key === 'XMPMetadata' && typeof value === "string") {
|
||||
output += `<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#${safeKey}-content-${depth}" aria-expanded="true" aria-controls="${safeKey}-content-${depth}">
|
||||
${key}
|
||||
</button>`;
|
||||
} 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 += `<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#${safeKey}-content-${depth}" aria-expanded="true" aria-controls="${safeKey}-content-${depth}">
|
||||
${key}
|
||||
</button>`;
|
||||
}
|
||||
} else {
|
||||
output += `${key}: ${value}`;
|
||||
}
|
||||
|
||||
output += `
|
||||
</h5>
|
||||
</div>
|
||||
<div id="${safeKey}-content-${depth}" class="collapse" aria-labelledby="${safeKey}-heading-${depth}">`;
|
||||
|
||||
if (key === 'XMPMetadata' && typeof value === "string") {
|
||||
output += `<div class="card-body"><pre>${escapeHTML(value)}</pre></div>`;
|
||||
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
output += '<div class="card-body">';
|
||||
if (Object.keys(value).length) {
|
||||
for (const subKey in value) {
|
||||
output += renderJsonSection(subKey, value[subKey], depth + 1);
|
||||
}
|
||||
} else {
|
||||
output += '<p class="text-muted">Empty</p>';
|
||||
}
|
||||
output += '</div>';
|
||||
} else if (value && typeof value === 'object' && Array.isArray(value)) {
|
||||
output += '<div class="card-body">';
|
||||
if (value.length) {
|
||||
value.forEach((val, index) => {
|
||||
const arrayKey = `${key}[${index}]`;
|
||||
output += renderJsonSection(arrayKey, val, depth + 1);
|
||||
});
|
||||
} else {
|
||||
output += '<p class="text-muted">Empty</p>';
|
||||
}
|
||||
output += '</div>';
|
||||
}
|
||||
|
||||
output += '</div></div>';
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
function escapeHTML(s) {
|
||||
if(s)
|
||||
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
|
||||
return null;
|
||||
}
|
||||
function createButtonElement(key, safeKey, depth) {
|
||||
const buttonElem = document.createElement('button');
|
||||
buttonElem.className = 'btn btn-link';
|
||||
buttonElem.type = 'button';
|
||||
buttonElem.dataset.toggle = "collapse";
|
||||
buttonElem.dataset.target = `#${safeKey}-content-${depth}`;
|
||||
buttonElem.setAttribute('aria-expanded', 'true');
|
||||
buttonElem.setAttribute('aria-controls', `${safeKey}-content-${depth}`);
|
||||
buttonElem.textContent = key;
|
||||
return buttonElem;
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
|