mirror of
https://github.com/PaperCutSoftware/GhostTrap.git
synced 2026-02-04 20:52:35 -06:00
Compare commits
28 Commits
v1.4.10.03
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6c4390ab3 | ||
|
|
68db4201fa | ||
|
|
480e7fbb57 | ||
|
|
4c0b5da0ba | ||
|
|
704eca53c9 | ||
|
|
ca60f89103 | ||
|
|
b809a3b273 | ||
|
|
53d3421b05 | ||
|
|
6136391ba4 | ||
|
|
ff41890552 | ||
|
|
55818f6cdc | ||
|
|
72c00b07a4 | ||
|
|
11ca7033d5 | ||
|
|
26facc5d5d | ||
|
|
0e1d749525 | ||
|
|
32db122829 | ||
|
|
be828762cc | ||
|
|
e1b5fe2d3e | ||
|
|
deb51f3203 | ||
|
|
30cad13c29 | ||
|
|
4887c5b1d5 | ||
|
|
1441d20fe6 | ||
|
|
af21c97863 | ||
|
|
8384a17b8c | ||
|
|
e2c0e3d2d8 | ||
|
|
c54e330c91 | ||
|
|
fe0b7b92da | ||
|
|
6a376c3002 |
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -20,3 +20,5 @@
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
|
||||
*.bat text eol=crlf
|
||||
|
||||
18
README.md
18
README.md
@ -16,7 +16,7 @@ securely holds Ghostscripts in a laser containment field :-)
|
||||
|
||||
## Download
|
||||
|
||||
*Windows:* [ghost-trap-installer.exe](https://cdn1.papercut.com/files/open-source/ghost-trap/ghost-trap-installer-1.4.10.03.exe) (version 1.4.10.03)
|
||||
*Windows:* [ghost-trap-installer.exe](https://cdn1.papercut.com/files/open-source/ghost-trap/ghost-trap-installer-1.6.10.04.exe) (version 1.6.10.04)
|
||||
|
||||
|
||||
## Motivation
|
||||
@ -65,7 +65,7 @@ To convert a multi-page PDF file into a JPEG images *WITH* sandboxing:
|
||||
"C:\Program Files (x86)\GhostTrap\examples\annots.pdf"
|
||||
|
||||
`gsc-trapped.exe` is the sandboxed version of `gsc.exe`. It should behave the same
|
||||
as the standard Ghostscript console command as [documented](https://ghostscript.readthedocs.io/en/gs10.03.1/Use.html),
|
||||
as the standard Ghostscript console command as [documented](https://ghostscript.readthedocs.io/en/gs10.04.0/Use.html),
|
||||
with the following known exceptions:
|
||||
|
||||
* The input and output files must be on a local disk (no network shares).
|
||||
@ -94,6 +94,13 @@ escape vectors.
|
||||
|
||||
## Release History
|
||||
|
||||
### [1.6.10.04.0] - 2025-04-10
|
||||
* Updated to GhostScript 10.04.0 (2024-09-18).
|
||||
* Installer now silently installs the same Visual Studio C++ runtime (Build 14.29.30153, x64) as GhostScript installers would, no longer breaking `/VERYSILENT` installation flags.
|
||||
|
||||
### [1.5.10.03.1] - 2024-08-09
|
||||
* Installer compatible with ARM64 Windows.
|
||||
|
||||
### [1.4.10.03.1] - 2024-07-17
|
||||
* Updated to Ghostscript 10.03.1 (2024-05-02).
|
||||
* Updated to the latest Chromium Sandbox (as of [2024-07-16](https://chromium.googlesource.com/chromium/src/+/c067d47d154d8a6cf56ee8ac7e7b9a9a8b6f9a6f)).
|
||||
@ -155,7 +162,7 @@ The following future refinements are planned:
|
||||
### Requirements
|
||||
|
||||
* Visual Studio 2019 or 2017
|
||||
* [Inno Setup 6](http://www.jrsoftware.org/isinfo.php)
|
||||
* [Inno Setup 6.3](http://www.jrsoftware.org/isinfo.php)
|
||||
|
||||
|
||||
## Building
|
||||
@ -187,7 +194,7 @@ The following future refinements are planned:
|
||||
|
||||
*Ghost Trap* is open source software licensed under the Affero GPL:
|
||||
|
||||
Copyright (c) 2012-2024 PaperCut Software Pty Ltd http://www.papercut.com/
|
||||
Copyright (c) 2012-2025 PaperCut Software Pty Ltd http://www.papercut.com/
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
@ -202,7 +209,8 @@ The following future refinements are planned:
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
[1.6.10.04.0]: TBA
|
||||
[1.5.10.03.1]: https://github.com/PaperCutSoftware/GhostTrap/compare/v1.4.10.03.1...v1.5.10.03.1
|
||||
[1.4.10.03.1]: https://github.com/PaperCutSoftware/GhostTrap/compare/v1.4.10.02.1...v1.4.10.03.1
|
||||
[1.4.10.02.1]: https://github.com/PaperCutSoftware/GhostTrap/compare/v1.4.10.00...v1.4.10.02.1
|
||||
[1.4.10.00]: https://github.com/PaperCutSoftware/GhostTrap/compare/v1.3.9.27...v1.4.10.00
|
||||
|
||||
13
build.bat
Executable file → Normal file
13
build.bat
Executable file → Normal file
@ -1,10 +1,10 @@
|
||||
@echo off
|
||||
set GHOST_TRAP_VERSION=1.4
|
||||
set GHOST_TRAP_VERSION=1.6
|
||||
set INNO_COMPILER=%programfiles(x86)%\Inno Setup 6\ISCC.exe
|
||||
SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
set starttime=%time%
|
||||
set startdir=%cd%
|
||||
set gsversion=10.03.1
|
||||
set gsversion=10.04.0
|
||||
|
||||
echo .-. ___ _ _ _____
|
||||
echo (o o) / _ \ ^|__ ___ ___^| ^|/__ \_ __ __ _ _ __
|
||||
@ -107,20 +107,28 @@ REM #
|
||||
|
||||
call cd "%~dp0third-party\chromium\src\" > NUL
|
||||
if %errorlevel% NEQ 0 goto builderror
|
||||
echo Checked out into Chromium's repository.
|
||||
|
||||
call gn gen out\Default --args="is_debug=false" > NUL
|
||||
if %errorlevel% NEQ 0 goto builderror
|
||||
echo GN generation successful.
|
||||
|
||||
call autoninja -C out\Default sandbox/win:gsc-trapped > NUL
|
||||
if %errorlevel% NEQ 0 goto builderror
|
||||
echo autoninja build successful.
|
||||
|
||||
REM #
|
||||
REM # Test Ghost Trap
|
||||
REM #
|
||||
echo Testing Ghost Trap...
|
||||
copy "%~dp0third-party\ghostpdl\bin\gsdll64.dll" "%~dp0third-party\chromium\src\out\Default\" /Y > NUL
|
||||
REM #
|
||||
REM # !!! Do not add multi-lined conditional blocks after calling gsc-trapped. Keep the "if" line strictly single-lined.
|
||||
REM # CMD gets tripped up for no particular good reason and complains about unexpected symbol "."
|
||||
REM #
|
||||
call "%~dp0third-party\chromium\src\out\Default\gsc-trapped.exe" --test-sandbox -sOutputFile="C:\output\outputtest.txt" "C:\input\inputtest.txt"
|
||||
if %errorlevel% NEQ 0 goto builderror
|
||||
echo gsc-trapped executed for testing successfully.
|
||||
|
||||
REM #
|
||||
REM # Create target dir mirroring Ghostscript standard install.
|
||||
@ -142,6 +150,7 @@ copy "%~dp0LICENSE*" "%~dp0target\installfiles" /Y > NUL
|
||||
copy "%~dp0README*" "%~dp0target\installfiles" /Y > NUL
|
||||
|
||||
REM # Ghostscript files (mirroring standard install structure)
|
||||
echo copying files from GhostPDL project into GhostTrap paths for bundling installer etc...
|
||||
copy "%~dp0third-party\ghostpdl\bin\gswin64.exe" "%~dp0target\installfiles\bin\gs.exe" /Y > NUL
|
||||
copy "%~dp0third-party\ghostpdl\bin\gswin64c.exe" "%~dp0target\installfiles\bin\gsc.exe" /Y > NUL
|
||||
copy "%~dp0third-party\ghostpdl\bin\gsdll64.dll" "%~dp0target\installfiles\bin\" /Y > NUL
|
||||
|
||||
BIN
installer/redist/VC_redist.x64.exe
Normal file
BIN
installer/redist/VC_redist.x64.exe
Normal file
Binary file not shown.
@ -1,4 +1,4 @@
|
||||
; Copyright (c) 2012-2024 PaperCut Software Pty Ltd
|
||||
; Copyright (c) 2012-2025 PaperCut Software Pty Ltd
|
||||
; Author: Chris Dance <chris.dance@papercut.com>
|
||||
;
|
||||
; License: GNU Affero GPL v3 - See project LICENSE file.
|
||||
@ -17,7 +17,7 @@
|
||||
#define gs_name "GPL Ghostscript"
|
||||
|
||||
#ifndef gs_version
|
||||
#define gs_version "10.03.1"
|
||||
#define gs_version "10.04.0"
|
||||
#endif
|
||||
|
||||
#define gs_c_exe "gsc.exe"
|
||||
@ -30,7 +30,7 @@ AppPublisherURL=https://github.com/PaperCutSoftware/GhostTrap
|
||||
AppSupportURL=https://github.com/PaperCutSoftware/GhostTrap/issues
|
||||
AppUpdatesURL=https://github.com/PaperCutSoftware/GhostTrap
|
||||
DefaultDirName={commonpf}\{#app_name_no_space}
|
||||
ArchitecturesAllowed=x64
|
||||
ArchitecturesAllowed=x64compatible
|
||||
ArchitecturesInstallIn64BitMode=x64
|
||||
|
||||
LicenseFile=..\..\LICENSE.rtf
|
||||
@ -61,6 +61,10 @@ Source: *; DestDir: {app}; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: bin\gsc-trapped.exe; DestDir: {app}\bin\; DestName: gswin32c-trapped.exe;
|
||||
Source: bin\gs.exe; DestDir: {app}\bin\; DestName: gswin32.exe;
|
||||
Source: bin\gsc.exe; DestDir: {app}\bin\; DestName: gswin32c.exe;
|
||||
; This is the Microsoft's redistributable binaries for VC++ dependency, we should provide the exact same build as it is
|
||||
; is used by GhostScript. As of 25/2/25, the build number is 30153, with a full version number of 14.29.30153. Manually
|
||||
; update this file whenever necessary.
|
||||
Source: ..\..\installer\redist\VC_redist.x64.exe; DestDir: {app}; DestName: VC_redist.x64.exe; AfterInstall: InstallVCRedist
|
||||
|
||||
|
||||
[Registry]
|
||||
@ -82,27 +86,9 @@ Filename: {app}\bin\{#gs_c_exe}; Parameters: "-q -dBATCH ""-sFONTDIR={code:Fonts
|
||||
|
||||
[UninstallDelete]
|
||||
Type: filesandordirs; Name: {app}\lib\cidfmap;
|
||||
Type: filesandordirs; Name: {app}\redist;
|
||||
|
||||
[Code]
|
||||
|
||||
const
|
||||
{ Expected minimum version is 14.38.33130.00, for Visual C++ 2015 redistributable packs
|
||||
Update the values here if our requirements change over time }
|
||||
MinMajor = 14;
|
||||
{ only applies if actual major version is 14, do not apply if actual major version is higher }
|
||||
MinMinor = 38;
|
||||
{ only applies if actual major is 14 and actual minor 38, do not apply if previous versions are higher }
|
||||
MinBld = 33130;
|
||||
|
||||
{ Used to store value of whether we have acceptable Visual C++ Runtime components }
|
||||
var
|
||||
HasRequiredVCRuntimeVersion : Boolean;
|
||||
VCRuntimeMissingOptionsPage : TInputOptionWizardPage;
|
||||
ReadMoreLink : TLabel;
|
||||
|
||||
procedure ExitProcess(ExitCode : Integer);
|
||||
external 'ExitProcess@kernel32.dll stdcall';
|
||||
|
||||
function FontsDirWithForwardSlashes(Param: String): String;
|
||||
begin
|
||||
{ The system's Fonts directory in forward slash format }
|
||||
@ -136,126 +122,56 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
function IsRequiredVCRuntimeVersionInstalled() : Boolean;
|
||||
var
|
||||
cMajor: Cardinal;
|
||||
cMinor: Cardinal;
|
||||
cBld: Cardinal;
|
||||
cRbld: Cardinal;
|
||||
sKey: String;
|
||||
{ To get the version string of a given file. Needed for logging the VC redistributable file version. }
|
||||
function GetFileVersion(const FileName: String): String;
|
||||
begin
|
||||
Result := False;
|
||||
{ for more info see https://learn.microsoft.com/en-us/cpp/windows/redistributing-visual-cpp-files?view=msvc-170#install-the-redistributable-packages }
|
||||
sKey := 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64';
|
||||
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, sKey, 'Major', cMajor) then begin
|
||||
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, sKey, 'Minor', cMinor) then begin
|
||||
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, sKey, 'Bld', cBld) then begin
|
||||
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, sKey, 'RBld', cRbld) then begin
|
||||
if cMajor > MinMajor then begin
|
||||
Result := True;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if cMajor < MinMajor then begin
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if cMinor > MinMinor then begin
|
||||
Result := True;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if cMinor < MinMinor then begin
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if (cBld >= MinBld) and (cRbld >= 0) then begin
|
||||
Result := True;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
Result := '';
|
||||
if FileExists(FileName) then
|
||||
begin
|
||||
if not GetVersionNumbersString(FileName, Result) then
|
||||
begin
|
||||
Result := 'Unspecified Version';
|
||||
end;
|
||||
end;
|
||||
end
|
||||
else
|
||||
Result := 'File not found';
|
||||
end;
|
||||
|
||||
procedure OpenBrowser(Url: string);
|
||||
{ The same version of VC++ dependency that is used by GhostScript will be automatically installed by GhostTrap }
|
||||
{ Requires manual updating if the version used by GhostScript changes in order to mirror the dependency updates }
|
||||
procedure InstallVCRedist();
|
||||
var
|
||||
ErrorCode: Integer;
|
||||
VC_Redist: String;
|
||||
VC_Redist_Relocated: String;
|
||||
Relocated_Dir: String;
|
||||
VC_Redist_Version: String;
|
||||
begin
|
||||
ShellExec('open', Url, '', '', SW_SHOWNORMAL, ewNoWait, ErrorCode);
|
||||
end;
|
||||
VC_Redist := ExpandConstant('{app}\VC_redist.x64.exe');
|
||||
VC_Redist_Version := GetFileVersion(VC_Redist);
|
||||
Log(Format('The VC++ dependency version to be installed is: %s', [VC_Redist_Version]));
|
||||
if Exec(VC_Redist, '/norestart /install /quiet', ExpandConstant('{app}'), SW_HIDE, ewWaitUntilTerminated, ErrorCode) then
|
||||
Log('VC dependencies successfully installed.')
|
||||
else
|
||||
Log(Format('Failed to launch VC++ Redistributable installer. Error Code: %d', [ErrorCode]));
|
||||
|
||||
procedure ReadMoreLinkClick(Sender : TObject);
|
||||
begin
|
||||
OpenBrowser('https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#visual-studio-2015-2017-2019-and-2022');
|
||||
end;
|
||||
|
||||
procedure CreateLink();
|
||||
begin
|
||||
ReadMoreLink := TLabel.Create(WizardForm);
|
||||
ReadMoreLink.Parent := WizardForm;
|
||||
ReadMoreLink.Left := 8;
|
||||
ReadMoreLink.Top := WizardForm.ClientHeight - ReadMoreLink.ClientHeight - 15;
|
||||
ReadMoreLink.Cursor := crHand;
|
||||
ReadMoreLink.Font.Color := clBlue;
|
||||
ReadMoreLink.Caption := 'Read more about Visual C++ dependencies';
|
||||
ReadMoreLink.OnCLick := @ReadMoreLinkClick;
|
||||
end;
|
||||
|
||||
{ Only display Read More link on added custom page, also set up custom page }
|
||||
procedure CurPageChanged(CurPageID: Integer);
|
||||
begin
|
||||
ReadMoreLink.Visible := CurPageID = VCRuntimeMissingOptionsPage.ID;
|
||||
if CurPageID = VCRuntimeMissingOptionsPage.ID then begin
|
||||
WizardForm.BackButton.Visible := False;
|
||||
WizardForm.CancelButton.Visible := False;
|
||||
WizardForm.NextButton.Caption := 'Finish';
|
||||
if ErrorCode <> 0 then
|
||||
begin
|
||||
Log(Format('VC++ Redistributable installation failed with exit code: %d', [ErrorCode]));
|
||||
end;
|
||||
end;
|
||||
|
||||
function NextButtonClick(CurPageID: Integer): Boolean;
|
||||
var
|
||||
ErrorCode : Integer;
|
||||
InstallNow : Boolean;
|
||||
begin
|
||||
{ Handle the "Finish" button being clicked on the custom page }
|
||||
if CurPageID = VCRuntimeMissingOptionsPage.ID then begin
|
||||
InstallNow := VCRuntimeMissingOptionsPage.Values[0];
|
||||
if InstallNow then begin
|
||||
if MsgBox('Would you like to install required dependencies now?', mbInformation, MB_YESNO or MB_DEFBUTTON1) = IDYES then begin
|
||||
ShellExec('', 'https://aka.ms/vs/17/release/vc_redist.x64.exe', '', '', SW_SHOWNORMAL, ewNoWait, ErrorCode);
|
||||
ExitProcess(9); { Custom exit code }
|
||||
end;
|
||||
end else begin
|
||||
MsgBox('Please install required VC++ dependencies first and try again.', mbInformation, MB_OK);
|
||||
ExitProcess(9); { Custom exit code }
|
||||
VC_Redist_Relocated := ExpandConstant('{app}\redist\VC_redist.x64.exe');
|
||||
Relocated_Dir := ExtractFilePath(VC_Redist_Relocated);
|
||||
|
||||
if not DirExists(Relocated_Dir) then
|
||||
begin
|
||||
if not CreateDir(Relocated_Dir) then
|
||||
begin
|
||||
Log(Format('Failed to create directory at: %s for redistributable file relocation', [Relocated_Dir]));
|
||||
end;
|
||||
Result := False;
|
||||
end else begin
|
||||
Result := True;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ While the custom page is always created, it should be skipped if dependencies are already installed }
|
||||
function ShouldSkipPage(PageID: Integer): Boolean;
|
||||
begin
|
||||
{ For potentially different custom page in the future, compare page ID one at a time }
|
||||
Result := (PageID = VCRuntimeMissingOptionsPage.ID) and HasRequiredVCRuntimeVersion;
|
||||
end;
|
||||
|
||||
procedure InitializeWizard();
|
||||
begin
|
||||
{ If the OS is missing required dependencies, the installation process will not go the full length and will be exited early }
|
||||
HasRequiredVCRuntimeVersion := IsRequiredVCRuntimeVersionInstalled();
|
||||
{ Create the custom page to handle the scenario where dependencies don't exist }
|
||||
VCRuntimeMissingOptionsPage := CreateInputOptionPage(wpLicense,
|
||||
'Visual C++ Runtime Required',
|
||||
'You are seeing this page because required dependencies for GhostTrap are missing on your operating system.',
|
||||
'GhostTrap of version 1.4.10.02 and above requires reasonably up-to-date Visual C++ Runtimes to function properly. You must install required Microsoft Redistributables before installing GhostTrap.' + #13#10 + #13#10 + 'Either option will terminate the current installation process.',
|
||||
True, False);
|
||||
|
||||
VCRuntimeMissingOptionsPage.Add('&Install the dependencies from Microsoft now. (Recommended)');
|
||||
VCRuntimeMissingOptionsPage.Add('I will find the required dependencies myself later.');
|
||||
VCRuntimeMissingOptionsPage.Values[0] := True;
|
||||
CreateLink();
|
||||
end;
|
||||
if FileCopy(VC_Redist, VC_Redist_Relocated, True) then
|
||||
Log('Moved VC++ redistributable file to the redist folder under installation.');
|
||||
DeleteFile(VC_Redist);
|
||||
end;
|
||||
@ -20,7 +20,7 @@
|
||||
For more information about Google Chromium sandbox, visit:\uc0\u8232 {\field{\*\fldinst{HYPERLINK "http://dev.chromium.org/developers/design-documents/sandbox"}}{\fldrslt \cf2 \ul \ulc2 http://dev.chromium.org/developers/design-documents/sandbox}}\
|
||||
\
|
||||
\pard\pardeftab720\sl276\slmult1\sa200
|
||||
\cf0 Ghost Trap is distributed under the GNU Affero General Public License Version 3 and Copyright (c) 2012-2024 PaperCut Software Pty Ltd. Ghost Trap source code distribution, and the Ghost Trap executable code, include the following libraries obtained from other parties:\
|
||||
\cf0 Ghost Trap is distributed under the GNU Affero General Public License Version 3 and Copyright (c) 2012-2025 PaperCut Software Pty Ltd. Ghost Trap source code distribution, and the Ghost Trap executable code, include the following libraries obtained from other parties:\
|
||||
\pard\pardeftab720
|
||||
|
||||
\fs16 \cf0 Ghostscript created by Aladdin Enterprises. For more information, please see {\field{\*\fldinst{HYPERLINK "http://www.ghostscript.com/"}}{\fldrslt \cf2 \ul \ulc2 http://www.ghostscript.com/}} and the LICENSE file in the GhostPDL project.\
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2024 PaperCut Software Pty Ltd
|
||||
* Copyright (c) 2012-2025 PaperCut Software Pty Ltd
|
||||
* http://www.papercut.com/
|
||||
*
|
||||
* Author: Chris Dance <chris.dance@papercut.com>
|
||||
@ -43,8 +43,8 @@
|
||||
* Ghost Trap version number starts at 1 and suffixes the Ghostscript version we've
|
||||
* tested/written against.
|
||||
*/
|
||||
#define GHOST_TRAP_VERSION "1.4.10.03.1"
|
||||
#define GHOST_TRAP_COPYRIGHT "Copyright (c) 2012-2024 PaperCut Software Pty Ltd"
|
||||
#define GHOST_TRAP_VERSION "1.6.10.04.0"
|
||||
#define GHOST_TRAP_COPYRIGHT "Copyright (c) 2012-2025 PaperCut Software Pty Ltd"
|
||||
|
||||
const wchar_t* PARAM_OUTPUT_FILE = L"OutputFile=";
|
||||
const wchar_t* PARAM_FAIL_TEST = L"--fail-test=";
|
||||
@ -702,4 +702,4 @@ error:
|
||||
*/
|
||||
int wmain(int argc, wchar_t* argv[]) {
|
||||
return RunConsoleAppInSandbox(ApplyPolicy, PreSandboxedInit, SandboxedMain, argc, argv);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2023 PaperCut Software Pty Ltd
|
||||
* Copyright (c) 2012-2024 PaperCut Software Pty Ltd
|
||||
* http://www.papercut.com/
|
||||
*
|
||||
* Author: Chris Dance <chris.dance@papercut.com>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user