From ab5ecdbabf7679adc73bef8cd74ef7914756e688 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 4 Aug 2025 17:27:03 -0300 Subject: [PATCH 01/18] feat: add java fallback Usage: Will be necessary identify the Java Version to configure the fallback and also add the URL to download the .tar.gz file. ```php $jsignParam = new JSignParam(); $jsignParam->setJavaVersion('21.0.0'); $jsignParam->setJavaDownloadUrl(''); $jsignParam->setJavaPath('/the/path/of/bin/java'); ``` Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- src/Runtime/JavaRuntimeService.php | 97 ++++++++++ src/Sign/JSignParam.php | 34 ++++ src/Sign/JSignService.php | 15 +- tests/JSignPDFTest.php | 1 + tests/Runtime/JavaRuntimeServiceTest.php | 215 +++++++++++++++++++++++ vendor-bin/phpunit/composer.json | 4 +- vendor-bin/phpunit/composer.lock | 174 +++++++++++++++++- 7 files changed, 527 insertions(+), 13 deletions(-) create mode 100644 src/Runtime/JavaRuntimeService.php create mode 100644 tests/Runtime/JavaRuntimeServiceTest.php diff --git a/src/Runtime/JavaRuntimeService.php b/src/Runtime/JavaRuntimeService.php new file mode 100644 index 0000000..a58866b --- /dev/null +++ b/src/Runtime/JavaRuntimeService.php @@ -0,0 +1,97 @@ +isUseJavaInstalled()) { + return 'java'; + } + + $javaPath = $params->getJavaPath(); + $downloadUrl = $params->getJavaDownloadUrl(); + + if ($javaPath && !$downloadUrl) { + if (is_file($javaPath) && is_executable($javaPath)) { + return $javaPath; + } + throw new InvalidArgumentException('Java path defined is not executable: ' . $javaPath); + } + + if ($downloadUrl && $javaPath) { + $baseDir = rtrim($javaPath, '/bin/java'); + if (!is_dir($baseDir)) { + throw new InvalidArgumentException('The java base dir is not a real directory: '. $baseDir); + } + try { + self::validateVersion($params); + } catch (RuntimeException) { + self::downloadAndExtract($downloadUrl, $javaPath); + } + return $javaPath; + } + + throw new InvalidArgumentException('Java not found.'); + } + + private function validateVersion(JSignParam $params): bool + { + $version = $params->getJavaVersion(); + if (!$version) { + throw new InvalidArgumentException('Java version required'); + } + $javaPath = $params->getJavaPath(); + \exec($javaPath . ' -version 2>&1', $javaVersion, $resultCode); + if (empty($javaVersion)) { + throw new RuntimeException('Failed to execute Java. Sounds that your operational system is blocking the JVM.'); + } + if ($resultCode !== 0) { + throw new RuntimeException('Failure to check Java version.'); + } + $javaVersion = current($javaVersion); + return $javaVersion === $version; + } + + private function downloadAndExtract(string $url, string $baseDir): void + { + $baseDir = rtrim($baseDir, '/bin/java'); + + if (!is_dir($baseDir)) { + $ok = mkdir($baseDir, 0755, true); + if (!$ok) { + throw new RuntimeException('Failure to create the folder: ' . $baseDir); + } + } + if (!filter_var($url, FILTER_VALIDATE_URL)) { + throw new InvalidArgumentException('The url to download Java is invalid: ' . $url); + } + $content = @file_get_contents($url); + if ($content === false) { + throw new InvalidArgumentException("Failute to download file using the url $url, error: " . print_r(error_get_last(), true)); + } + if (!$content) { + throw new InvalidArgumentException('The url returned empty content: ' . $url); + } + $decompressedContent = @gzdecode($content); + if ($decompressedContent === false) { + throw new InvalidArgumentException('The file downloaded from follow URL cannot be gzdecoded: ' . $url); + } + file_put_contents($baseDir . '/java.tar.gz', $decompressedContent); + $tar = new PharData($baseDir . '/java.tar.gz'); + $tar->extractTo($baseDir, null, true); + unlink($baseDir . '/java.tar.gz'); + if (!file_exists($baseDir . '/bin/java')) { + throw new RuntimeException('Java binary not found at: ' . $baseDir . '/bin/java'); + } + chmod($baseDir . '/bin/java', 0700); + } +} diff --git a/src/Sign/JSignParam.php b/src/Sign/JSignParam.php index 576791f..fdba12d 100644 --- a/src/Sign/JSignParam.php +++ b/src/Sign/JSignParam.php @@ -18,6 +18,9 @@ class JSignParam private $tempName; private $isOutputTypeBase64 = false; private $jSignPdfJarPath; + private ?string $javaVersion = null; + private ?string $javaDownloadUrl = null; + private ?string $jSignPdfDownloadUrl = null; public function __construct() { @@ -158,4 +161,35 @@ public function getTempCertificatePath() return $this->getTempPath() . $this->getTempName('.pfx'); } + public function setJavaDownloadUrl(string $url): self + { + $this->javaDownloadUrl = $url; + return $this; + } + + public function getJavaDownloadUrl(): ?string + { + return $this->javaDownloadUrl; + } + + public function setJSignPdfDownloadUrl(string $url): self + { + $this->jSignPdfDownloadUrl = $url; + return $this; + } + + public function getJSignPdfDownloadUrl(): ?string + { + return $this->jSignPdfDownloadUrl; + } + + public function setJavaVersion(string $javaVersion): self + { + $this->javaVersion = $javaVersion; + return $this; + } + + public function getJavaVersion(): ?string { + return $this->javaVersion; + } } diff --git a/src/Sign/JSignService.php b/src/Sign/JSignService.php index fae57ce..35f330c 100644 --- a/src/Sign/JSignService.php +++ b/src/Sign/JSignService.php @@ -4,6 +4,7 @@ use Exception; use Jeidison\JSignPDF\JSignFileService; +use Jeidison\JSignPDF\Runtime\JavaRuntimeService; use Throwable; /** @@ -130,18 +131,10 @@ private function commandSign(JSignParam $params) return "$java -Duser.language=en -jar $jSignPdf $pdf -ksf $certificate -ksp {$password} {$params->getJSignParameters()} -d {$params->getPathPdfSigned()} 2>&1"; } - private function javaCommand(JSignParam $params) + private function javaCommand(JSignParam $params): string { - if ($params->isUseJavaInstalled()) { - return 'java'; - } - if ($params->getJavaPath()) { - return $params->getJavaPath(); - } - if (!class_exists('JSignPDF\JSignPDFBin\JavaCommandService')) { - throw new Exception("JSignPDF not found, install manually or run composer require jsignpdf/jsignpdf-bin", 1); - } - return \JSignPDF\JSignPDFBin\JavaCommandService::instance()->command($params->isUseJavaInstalled()); + $javaRuntimeService = new JavaRuntimeService(); + return $javaRuntimeService->getPath($params); } private function throwIf($condition, $message) diff --git a/tests/JSignPDFTest.php b/tests/JSignPDFTest.php index d0298dc..a12d0e1 100644 --- a/tests/JSignPDFTest.php +++ b/tests/JSignPDFTest.php @@ -173,6 +173,7 @@ public function testJSignPDFNotFound() $this->expectExceptionMessageMatches('/JSignPDF not found/'); $params = JSignParamBuilder::instance()->withDefault()->setjSignPdfJarPath('invalid_path'); $params->setCertificate($this->getNewCert($params->getPassword())); + $params->setIsUseJavaInstalled(true); $this->service->getVersion($params); } diff --git a/tests/Runtime/JavaRuntimeServiceTest.php b/tests/Runtime/JavaRuntimeServiceTest.php new file mode 100644 index 0000000..ea34ffc --- /dev/null +++ b/tests/Runtime/JavaRuntimeServiceTest.php @@ -0,0 +1,215 @@ +testTmpDir = sys_get_temp_dir() . '/jsignpdf_temp_dir_' . uniqid(); + } + + public function testGetPathWhenJavaIsInstalled(): void { + $jsignParam = new JSignParam(); + $service = new JavaRuntimeService(); + $jsignParam->setIsUseJavaInstalled(true); + $path = $service->getPath($jsignParam); + $this->assertEquals('java', $path); + } + + public function testGetPathWithCustomAndValidJavaPath(): void { + $jsignParam = new JSignParam(); + $service = new JavaRuntimeService(); + $jsignParam->setJavaPath(PHP_BINARY); + $path = $service->getPath($jsignParam); + $this->assertEquals(PHP_BINARY, $path); + } + + public function testGetPathWithCustomAndInvalidJavaPath(): void { + $jsignParam = new JSignParam(); + $service = new JavaRuntimeService(); + $jsignParam->setJavaPath(__FILE__); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/not executable/'); + $service->getPath($jsignParam); + } + + public function testGetPathWithDownloadUrlAndNotRealDirectory(): void { + $jsignParam = new JSignParam(); + $service = new JavaRuntimeService(); + $jsignParam->setJavaPath(__FILE__); + $jsignParam->setJavaDownloadUrl('https://fake.url'); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/not a real directory/'); + $service->getPath($jsignParam); + } + + public function testGetPathWithDownloadUrlAndEmptyJavaVersion(): void { + $jsignParam = new JSignParam(); + $service = new JavaRuntimeService(); + $jsignParam->setJavaPath(realpath(__DIR__ . '/../../tmp/')); + $jsignParam->setJavaDownloadUrl('https://fake.url'); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/Java version required/'); + $service->getPath($jsignParam); + } + + public function testGetPathWithDownloadUrlWithInvalidUrl(): void { + vfsStream::setup('download'); + mkdir('vfs://download/bin'); + touch('vfs://download/bin/java'); + + $jsignParam = new JSignParam(); + $service = new JavaRuntimeService(); + $jsignParam->setJavaPath('vfs://download/bin/java'); + $jsignParam->setJavaDownloadUrl('invalid_url'); + $jsignParam->setJavaVersion('21.0.0'); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/url.*invalid/'); + $service->getPath($jsignParam); + } + + public function testGetPathWithDownloadUrlWith4xxError(): void { + vfsStream::setup('download'); + mkdir('vfs://download/bin'); + touch('vfs://download/bin/java'); + + $jsignParam = new JSignParam(); + $service = new JavaRuntimeService(); + $jsignParam->setJavaPath('vfs://download/bin/java'); + $jsignParam->setJavaDownloadUrl('https://404.domain'); + $jsignParam->setJavaVersion('21.0.0'); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/Failute to download/'); + $service->getPath($jsignParam); + } + + public function testGetPathWithDownloadUrlWithInvalidGzipedFile(): void { + vfsStream::setup('download'); + mkdir('vfs://download/bin'); + touch('vfs://download/bin/java'); + + + $jsignParam = new JSignParam(); + $service = new JavaRuntimeService(); + $jsignParam->setJavaPath('vfs://download/bin/java'); + + $server = new MockWebServer(); + $server->start(); + $server->setResponseOfPath( + '/', + new Response( + 'invalid body response', + ) + ); + $url = $server->getServerRoot(); + $jsignParam->setJavaDownloadUrl($url); + + $jsignParam->setJavaVersion('21.0.0'); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/cannot be gzdecoded/'); + $service->getPath($jsignParam); + } + + public function testGetPathWithDownloadUrlWithCorruptFile(): void { + mkdir($this->testTmpDir . '/bin', 0755, true); + touch($this->testTmpDir . '/bin/java'); + + $jsignParam = new JSignParam(); + $service = new JavaRuntimeService(); + $jsignParam->setJavaPath($this->testTmpDir . '/bin/java'); + + $server = new MockWebServer(); + $server->start(); + $server->setResponseOfPath( + '/', + new Response( + gzencode('invalid tar content'), + ) + ); + $url = $server->getServerRoot(); + $jsignParam->setJavaDownloadUrl($url); + + $jsignParam->setJavaVersion('21.0.0'); + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessageMatches('/internal corruption/'); + $service->getPath($jsignParam); + } + + public function testGetPathWithDownloadUrlWithInvalidJavaPackage(): void { + mkdir($this->testTmpDir . '/bin', 0755, true); + + $jsignParam = new JSignParam(); + $service = new JavaRuntimeService(); + $jsignParam->setJavaPath($this->testTmpDir . '/bin/java'); + + $tar = new PharData($this->testTmpDir . '/temp.tar.gz'); + $tar->addFromString('file.txt', 'invalid file'); + + $server = new MockWebServer(); + $server->start(); + $server->setResponseOfPath( + '/', + new Response( + gzencode(file_get_contents($this->testTmpDir . '/temp.tar.gz')), + ) + ); + unlink($this->testTmpDir . '/temp.tar.gz'); + $url = $server->getServerRoot(); + $jsignParam->setJavaDownloadUrl($url); + + $jsignParam->setJavaVersion('21.0.0'); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessageMatches('/Java binary not found/'); + $service->getPath($jsignParam); + } + + public function testGetPathWithoutJavaFallback(): void { + mkdir($this->testTmpDir . '/bin', 0755, true); + + $jsignParam = new JSignParam(); + $service = new JavaRuntimeService(); + + $jsignParam->setJavaVersion('21.0.0'); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/Java not found/'); + $service->getPath($jsignParam); + } + + protected function tearDown(): void + { + $dirs = glob(sys_get_temp_dir() . '/jsignpdf_temp_*', GLOB_ONLYDIR); + + foreach ($dirs as $dir) { + if (is_dir($dir)) { + $this->removeDirectoryContents($dir); + rmdir($dir); + } + } + } + + private function removeDirectoryContents($dir): void + { + $it = new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS); + $files = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($files as $file) { + if ($file->isDir()) { + rmdir($file->getRealPath()); + } else { + unlink($file->getRealPath()); + } + } + } +} diff --git a/vendor-bin/phpunit/composer.json b/vendor-bin/phpunit/composer.json index b5f041e..394a71f 100644 --- a/vendor-bin/phpunit/composer.json +++ b/vendor-bin/phpunit/composer.json @@ -5,6 +5,8 @@ } }, "require-dev": { - "phpunit/phpunit": "^10.5" + "phpunit/phpunit": "^10.5", + "mikey179/vfsstream": "^1.6", + "donatj/mock-webserver": "^2.8" } } diff --git a/vendor-bin/phpunit/composer.lock b/vendor-bin/phpunit/composer.lock index d7598c1..1bb837c 100644 --- a/vendor-bin/phpunit/composer.lock +++ b/vendor-bin/phpunit/composer.lock @@ -4,9 +4,137 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fb78960ff7e774a72d424270c4bd3d90", + "content-hash": "a599f9439564eae0c324bd8f816485da", "packages": [], "packages-dev": [ + { + "name": "donatj/mock-webserver", + "version": "v2.8.0", + "source": { + "type": "git", + "url": "https://github.com/donatj/mock-webserver.git", + "reference": "73b5d53a8f1285674ff36b2474a4cb9132d81177" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/donatj/mock-webserver/zipball/73b5d53a8f1285674ff36b2474a4cb9132d81177", + "reference": "73b5d53a8f1285674ff36b2474a4cb9132d81177", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-sockets": "*", + "php": ">=7.1", + "ralouphie/getallheaders": "~2.0 || ~3.0" + }, + "require-dev": { + "corpus/coding-standard": "^0.6.0 || ^0.9.0", + "donatj/drop": "^1.0", + "ext-curl": "*", + "friendsofphp/php-cs-fixer": "^3.1", + "phpunit/phpunit": "~7|~9", + "squizlabs/php_codesniffer": "^3.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "donatj\\MockWebServer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jesse G. Donat", + "email": "donatj@gmail.com", + "homepage": "https://donatstudios.com", + "role": "Lead" + } + ], + "description": "Simple mock web server for unit testing", + "keywords": [ + "dev", + "http", + "mock", + "phpunit", + "testing", + "unit testing", + "webserver" + ], + "support": { + "issues": "https://github.com/donatj/mock-webserver/issues", + "source": "https://github.com/donatj/mock-webserver/tree/v2.8.0" + }, + "funding": [ + { + "url": "https://www.paypal.me/donatj/15", + "type": "custom" + }, + { + "url": "https://github.com/donatj", + "type": "github" + }, + { + "url": "https://ko-fi.com/donatj", + "type": "ko_fi" + } + ], + "time": "2025-06-26T22:24:11+00:00" + }, + { + "name": "mikey179/vfsstream", + "version": "v1.6.12", + "source": { + "type": "git", + "url": "https://github.com/bovigo/vfsStream.git", + "reference": "fe695ec993e0a55c3abdda10a9364eb31c6f1bf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/fe695ec993e0a55c3abdda10a9364eb31c6f1bf0", + "reference": "fe695ec993e0a55c3abdda10a9364eb31c6f1bf0", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5||^8.5||^9.6", + "yoast/phpunit-polyfills": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "org\\bovigo\\vfs\\": "src/main/php" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Frank Kleine", + "homepage": "http://frankkleine.de/", + "role": "Developer" + } + ], + "description": "Virtual file system to mock the real file system in unit tests.", + "homepage": "http://vfs.bovigo.org/", + "support": { + "issues": "https://github.com/bovigo/vfsStream/issues", + "source": "https://github.com/bovigo/vfsStream/tree/master", + "wiki": "https://github.com/bovigo/vfsStream/wiki" + }, + "time": "2024-08-29T18:43:31+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.13.4", @@ -673,6 +801,50 @@ ], "time": "2025-07-11T04:07:17+00:00" }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, { "name": "sebastian/cli-parser", "version": "2.0.1", From f3d759704d624645525614f89c954d9a2ca2413a Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 4 Aug 2025 23:11:37 -0300 Subject: [PATCH 02/18] fix: update index.php with real example and fix the code Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- example/index.php | 32 +++++++++++-- src/Runtime/JavaRuntimeService.php | 59 +++++++++++++++++++----- tests/Runtime/JavaRuntimeServiceTest.php | 33 ++----------- 3 files changed, 78 insertions(+), 46 deletions(-) diff --git a/example/index.php b/example/index.php index 3cbc4a8..53f0044 100644 --- a/example/index.php +++ b/example/index.php @@ -3,16 +3,38 @@ /** * @author Jeidison Farias */ -require_once "../src/JSignPDF.php"; +require_once __DIR__ . '/../vendor/autoload.php'; use Jeidison\JSignPDF\JSignPDF; use Jeidison\JSignPDF\Sign\JSignParam; +$password = '123'; + +$privateKey = openssl_pkey_new([ + 'private_key_bits' => 2048, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, +]); + +$csr = openssl_csr_new(['commonName' => 'John Doe'], $privateKey, ['digest_alg' => 'sha256']); +$x509 = openssl_csr_sign($csr, null, $privateKey, 365); + +openssl_pkcs12_export( + $x509, + $pfxCertificateContent, + $privateKey, + $password, +); + $param = JSignParam::instance(); -$param->setCertificate(file_get_contents('../tests/resources/certificado.pfx')); -$param->setPdf(file_get_contents('../tests/resources/pdf-test.pdf')); -$param->setPassword('123'); + +$param->setJavaVersion('openjdk version "21.0.7" 2025-04-15 LTS'); +$param->setJavaDownloadUrl('https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/OpenJDK21U-jre_x64_linux_hotspot_21.0.8_9.tar.gz'); +$param->setJavaPath(__DIR__ . '/../tmp/java'); + +$param->setCertificate($pfxCertificateContent); +$param->setPdf(file_get_contents(__DIR__ . '/../tests/resources/pdf-test.pdf')); +$param->setPassword($password); $jSignPdf = new JSignPDF($param); $fileSigned = $jSignPdf->sign(); -file_put_contents('../tmp/file_signed.pdf', $fileSigned); \ No newline at end of file +file_put_contents(__DIR__ . '/../tmp/file_signed.pdf', $fileSigned); diff --git a/src/Runtime/JavaRuntimeService.php b/src/Runtime/JavaRuntimeService.php index a58866b..e99a29a 100644 --- a/src/Runtime/JavaRuntimeService.php +++ b/src/Runtime/JavaRuntimeService.php @@ -7,7 +7,9 @@ use InvalidArgumentException; use Jeidison\JSignPDF\Sign\JSignParam; use PharData; +use PharException; use RuntimeException; +use UnexpectedValueException; class JavaRuntimeService { @@ -74,24 +76,57 @@ private function downloadAndExtract(string $url, string $baseDir): void if (!filter_var($url, FILTER_VALIDATE_URL)) { throw new InvalidArgumentException('The url to download Java is invalid: ' . $url); } - $content = @file_get_contents($url); - if ($content === false) { - throw new InvalidArgumentException("Failute to download file using the url $url, error: " . print_r(error_get_last(), true)); + $this->chunkDownload($url, $baseDir . '/java.tar.gz'); + try { + $tar = new PharData($baseDir . '/java.tar.gz'); + } catch (PharException|UnexpectedValueException $e) { + throw new InvalidArgumentException('The file ' . $baseDir . '/java.tar.gz cannot be extracted'); } - if (!$content) { - throw new InvalidArgumentException('The url returned empty content: ' . $url); + $rootDirInsideTar = $this->findRootDir($tar, $baseDir . '/java.tar.gz'); + if (!$rootDirInsideTar) { + throw new InvalidArgumentException('Invalid tar content.'); } - $decompressedContent = @gzdecode($content); - if ($decompressedContent === false) { - throw new InvalidArgumentException('The file downloaded from follow URL cannot be gzdecoded: ' . $url); - } - file_put_contents($baseDir . '/java.tar.gz', $decompressedContent); - $tar = new PharData($baseDir . '/java.tar.gz'); - $tar->extractTo($baseDir, null, true); + $tar->extractTo(directory: $baseDir, overwrite: true); + @exec('mv ' . escapeshellarg($baseDir . '/'. $rootDirInsideTar) . '/* ' . escapeshellarg($baseDir)); + @exec('rm -rf ' . escapeshellarg($baseDir . '/'. $rootDirInsideTar)); unlink($baseDir . '/java.tar.gz'); if (!file_exists($baseDir . '/bin/java')) { throw new RuntimeException('Java binary not found at: ' . $baseDir . '/bin/java'); } chmod($baseDir . '/bin/java', 0700); } + + private function findRootDir(PharData $phar, $rootDir) { + $files = new \RecursiveIteratorIterator($phar, \RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($files as $file) { + $pathName = $file->getPathname(); + if (str_contains($pathName, '/bin/') || str_contains($pathName, '/bin/')) { + $parts = explode($rootDir, $pathName); + $internalFullPath = end($parts); + $parts = explode('/bin/', $internalFullPath); + return trim($parts[0], '/'); + } + } + } + + private function chunkDownload(string $url, string $destination): void + { + $fp = fopen($destination, 'w'); + + if ($fp) { + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + $response = curl_exec($ch); + if ($response === false) { + throw new InvalidArgumentException('Failure to download file using the url ' . $url); + } + curl_close($ch); + fclose($fp); + } else { + throw new InvalidArgumentException("Failute to download file using the url $url"); + } + } } diff --git a/tests/Runtime/JavaRuntimeServiceTest.php b/tests/Runtime/JavaRuntimeServiceTest.php index ea34ffc..7c06500 100644 --- a/tests/Runtime/JavaRuntimeServiceTest.php +++ b/tests/Runtime/JavaRuntimeServiceTest.php @@ -91,7 +91,7 @@ public function testGetPathWithDownloadUrlWith4xxError(): void { $jsignParam->setJavaDownloadUrl('https://404.domain'); $jsignParam->setJavaVersion('21.0.0'); $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageMatches('/Failute to download/'); + $this->expectExceptionMessageMatches('/Failure to download/'); $service->getPath($jsignParam); } @@ -118,32 +118,7 @@ public function testGetPathWithDownloadUrlWithInvalidGzipedFile(): void { $jsignParam->setJavaVersion('21.0.0'); $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageMatches('/cannot be gzdecoded/'); - $service->getPath($jsignParam); - } - - public function testGetPathWithDownloadUrlWithCorruptFile(): void { - mkdir($this->testTmpDir . '/bin', 0755, true); - touch($this->testTmpDir . '/bin/java'); - - $jsignParam = new JSignParam(); - $service = new JavaRuntimeService(); - $jsignParam->setJavaPath($this->testTmpDir . '/bin/java'); - - $server = new MockWebServer(); - $server->start(); - $server->setResponseOfPath( - '/', - new Response( - gzencode('invalid tar content'), - ) - ); - $url = $server->getServerRoot(); - $jsignParam->setJavaDownloadUrl($url); - - $jsignParam->setJavaVersion('21.0.0'); - $this->expectException(UnexpectedValueException::class); - $this->expectExceptionMessageMatches('/internal corruption/'); + $this->expectExceptionMessageMatches('/cannot be extracted/'); $service->getPath($jsignParam); } @@ -170,8 +145,8 @@ public function testGetPathWithDownloadUrlWithInvalidJavaPackage(): void { $jsignParam->setJavaDownloadUrl($url); $jsignParam->setJavaVersion('21.0.0'); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessageMatches('/Java binary not found/'); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/Invalid tar content/'); $service->getPath($jsignParam); } From 43f34c5832cc70c2d431ead1fc197c119ee3fbef Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 4 Aug 2025 23:24:52 -0300 Subject: [PATCH 03/18] fix: create temp folder Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/Runtime/JavaRuntimeServiceTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Runtime/JavaRuntimeServiceTest.php b/tests/Runtime/JavaRuntimeServiceTest.php index 7c06500..bed2b01 100644 --- a/tests/Runtime/JavaRuntimeServiceTest.php +++ b/tests/Runtime/JavaRuntimeServiceTest.php @@ -18,6 +18,7 @@ class JavaRuntimeServiceTest extends TestCase public string $testTmpDir = ''; protected function setUp(): void { $this->testTmpDir = sys_get_temp_dir() . '/jsignpdf_temp_dir_' . uniqid(); + mkdir(directory: $this->testTmpDir, recursive: true); } public function testGetPathWhenJavaIsInstalled(): void { From 411275e9f936b04923af203ae2a53825ec466e6b Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:04:39 -0300 Subject: [PATCH 04/18] feat: fallback to automatic download JSignPdf Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- example/index.php | 8 ++- src/Runtime/JSignPdfRuntimeService.php | 94 ++++++++++++++++++++++++++ src/Runtime/JavaRuntimeService.php | 11 +-- src/Sign/JSignParam.php | 12 ++++ src/Sign/JSignService.php | 17 ++--- 5 files changed, 128 insertions(+), 14 deletions(-) create mode 100644 src/Runtime/JSignPdfRuntimeService.php diff --git a/example/index.php b/example/index.php index 53f0044..14a3f7d 100644 --- a/example/index.php +++ b/example/index.php @@ -27,9 +27,13 @@ $param = JSignParam::instance(); -$param->setJavaVersion('openjdk version "21.0.7" 2025-04-15 LTS'); +$param->setJavaVersion('openjdk version "21.0.8" 2025-07-15 LTS'); $param->setJavaDownloadUrl('https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/OpenJDK21U-jre_x64_linux_hotspot_21.0.8_9.tar.gz'); -$param->setJavaPath(__DIR__ . '/../tmp/java'); +$param->setJavaPath(__DIR__ . '/../tmp/java/bin/java'); + +$param->setJsignPdfVersion('asdfasd'); +$param->setJSignPdfDownloadUrl('https://github.com/intoolswetrust/jsignpdf/releases/download/JSignPdf_2_3_0/jsignpdf-2.3.0.zip'); +$param->setjSignPdfJarPath(__DIR__ . '/../tmp/jsignpdf/JSignPdf.jar'); $param->setCertificate($pfxCertificateContent); $param->setPdf(file_get_contents(__DIR__ . '/../tests/resources/pdf-test.pdf')); diff --git a/src/Runtime/JSignPdfRuntimeService.php b/src/Runtime/JSignPdfRuntimeService.php new file mode 100644 index 0000000..72412d9 --- /dev/null +++ b/src/Runtime/JSignPdfRuntimeService.php @@ -0,0 +1,94 @@ +getjSignPdfJarPath(); + $downloadUrl = $params->getJSignPdfDownloadUrl(); + + if ($jsignPdfPath && !$downloadUrl) { + if (file_exists($jsignPdfPath)) { + return $jsignPdfPath; + } + throw new InvalidArgumentException('Jar of JSignPDF not found on path: '. $jsignPdfPath); + } + + if ($downloadUrl && $jsignPdfPath) { + $baseDir = preg_replace('/\/JSignPdf.jar$/', '', $jsignPdfPath); + if (!is_dir($baseDir)) { + throw new InvalidArgumentException('The JSignPdf base dir is not a real directory: '. $baseDir); + } + self::downloadAndExtract($params); + return $jsignPdfPath; + } + + throw new InvalidArgumentException('Java not found.'); + } + + private function downloadAndExtract(JSignParam $params): void + { + $jsignPdfPath = $params->getjSignPdfJarPath(); + $url = $params->getJSignPdfDownloadUrl(); + + $baseDir = preg_replace('/\/JSignPdf.jar$/', '', $jsignPdfPath); + + if (!is_dir($baseDir)) { + $ok = mkdir($baseDir, 0755, true); + if (!$ok) { + throw new RuntimeException('Failure to create the folder: ' . $baseDir); + } + } + if (!filter_var($url, FILTER_VALIDATE_URL)) { + throw new InvalidArgumentException('The url to download Java is invalid: ' . $url); + } + $this->chunkDownload($url, $baseDir . '/jsignpdf.zip'); + $z = new ZipArchive(); + $ok = $z->open($baseDir . '/jsignpdf.zip'); + if ($ok !== true) { + throw new InvalidArgumentException('The file ' . $baseDir . '/jsignpdf.zip cannot be extracted'); + } + $ok = $z->extractTo(pathto: $baseDir, files: [$z->getNameIndex(0) . 'JSignPdf.jar']); + if ($ok !== true) { + throw new InvalidArgumentException('JSignPdf.jar not found inside path: ' . $z->getNameIndex(0) . 'JSignPdf.jar'); + } + @exec('mv ' . escapeshellarg($baseDir . '/'. $z->getNameIndex(0)) . '/JSignPdf.jar ' . escapeshellarg($baseDir)); + @exec('rm -rf ' . escapeshellarg($baseDir . '/'. $z->getNameIndex(0))); + unlink($baseDir . '/jsignpdf.zip'); + if (!file_exists($baseDir . '/JSignPdf.jar')) { + throw new RuntimeException('Java binary not found at: ' . $baseDir . '/bin/java'); + } + } + + private function chunkDownload(string $url, string $destination): void + { + $fp = fopen($destination, 'w'); + + if ($fp) { + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + $response = curl_exec($ch); + if ($response === false) { + throw new InvalidArgumentException('Failure to download file using the url ' . $url); + } + curl_close($ch); + fclose($fp); + } else { + throw new InvalidArgumentException("Failute to download file using the url $url"); + } + } +} diff --git a/src/Runtime/JavaRuntimeService.php b/src/Runtime/JavaRuntimeService.php index e99a29a..fa1f525 100644 --- a/src/Runtime/JavaRuntimeService.php +++ b/src/Runtime/JavaRuntimeService.php @@ -30,15 +30,17 @@ public function getPath(JSignParam $params): string } if ($downloadUrl && $javaPath) { - $baseDir = rtrim($javaPath, '/bin/java'); + $baseDir = preg_replace('/\/bin\/java$/', '', $javaPath); if (!is_dir($baseDir)) { - throw new InvalidArgumentException('The java base dir is not a real directory: '. $baseDir); + throw new InvalidArgumentException('The java base dir is not a real directory. Create this directory first: '. $baseDir); } try { self::validateVersion($params); } catch (RuntimeException) { self::downloadAndExtract($downloadUrl, $javaPath); } + $params->setJavaDownloadUrl(''); + $params->setJavaPath($javaPath); return $javaPath; } @@ -53,7 +55,7 @@ private function validateVersion(JSignParam $params): bool } $javaPath = $params->getJavaPath(); \exec($javaPath . ' -version 2>&1', $javaVersion, $resultCode); - if (empty($javaVersion)) { + if (count($javaVersion) <= 1) { throw new RuntimeException('Failed to execute Java. Sounds that your operational system is blocking the JVM.'); } if ($resultCode !== 0) { @@ -65,7 +67,7 @@ private function validateVersion(JSignParam $params): bool private function downloadAndExtract(string $url, string $baseDir): void { - $baseDir = rtrim($baseDir, '/bin/java'); + $baseDir = preg_replace('/\/bin\/java$/', '', $baseDir); if (!is_dir($baseDir)) { $ok = mkdir($baseDir, 0755, true); @@ -98,6 +100,7 @@ private function downloadAndExtract(string $url, string $baseDir): void private function findRootDir(PharData $phar, $rootDir) { $files = new \RecursiveIteratorIterator($phar, \RecursiveIteratorIterator::CHILD_FIRST); + $rootDir = realpath($rootDir); foreach ($files as $file) { $pathName = $file->getPathname(); diff --git a/src/Sign/JSignParam.php b/src/Sign/JSignParam.php index fdba12d..2bda0e5 100644 --- a/src/Sign/JSignParam.php +++ b/src/Sign/JSignParam.php @@ -21,6 +21,7 @@ class JSignParam private ?string $javaVersion = null; private ?string $javaDownloadUrl = null; private ?string $jSignPdfDownloadUrl = null; + private ?string $jsignPdfVersion = null; public function __construct() { @@ -135,6 +136,17 @@ public function getjSignPdfJarPath() return $this->jSignPdfJarPath; } + public function setJsignPdfVersion(string $version): self + { + $this->jsignPdfVersion = $version; + return $this; + } + + public function getJsignPdfVersion(): ?string + { + return $this->jsignPdfVersion; + } + public function isOutputTypeBase64(): bool { return $this->isOutputTypeBase64; diff --git a/src/Sign/JSignService.php b/src/Sign/JSignService.php index 35f330c..35b7537 100644 --- a/src/Sign/JSignService.php +++ b/src/Sign/JSignService.php @@ -5,6 +5,7 @@ use Exception; use Jeidison\JSignPDF\JSignFileService; use Jeidison\JSignPDF\Runtime\JavaRuntimeService; +use Jeidison\JSignPDF\Runtime\JSignPdfRuntimeService; use Throwable; /** @@ -70,11 +71,8 @@ private function repackCertificateIfPasswordIsUnicode(JSignParam $params, $cert, public function getVersion(JSignParam $params) { $java = $this->javaCommand($params); + $jSignPdf = $this->getjSignPdfJarPath($params); $jSignPdf = $params->getjSignPdfJarPath(); - if (!$jSignPdf && class_exists('JSignPDF\JSignPDFBin\JSignPdfPathService')) { - $jSignPdf = \JSignPDF\JSignPDFBin\JSignPdfPathService::jSignPdfJarPath(); - } - $this->throwIf(!file_exists($jSignPdf), 'Jar of JSignPDF not found on path: '. $jSignPdf); $command = "$java -jar $jSignPdf --version 2>&1"; \exec($command, $output); @@ -121,10 +119,7 @@ private function commandSign(JSignParam $params) { list ($pdf, $certificate) = $this->storeTempFiles($params); $java = $this->javaCommand($params); - $jSignPdf = $params->getjSignPdfJarPath(); - if (!$jSignPdf && class_exists('JSignPDF\JSignPDFBin\JSignPdfPathService')) { - $jSignPdf = \JSignPDF\JSignPDFBin\JSignPdfPathService::jSignPdfJarPath(); - } + $jSignPdf = $this->getjSignPdfJarPath($params); $this->throwIf(!file_exists($jSignPdf), 'Jar of JSignPDF not found on path: '. $jSignPdf); $password = escapeshellarg($params->getPassword()); @@ -137,6 +132,12 @@ private function javaCommand(JSignParam $params): string return $javaRuntimeService->getPath($params); } + private function getjSignPdfJarPath(JSignParam $params): string + { + $JsignPdfRuntimeService = new JSignPdfRuntimeService(); + return $JsignPdfRuntimeService->getPath($params); + } + private function throwIf($condition, $message) { if ($condition) From 56c4e0b00fbececff8d6d89032493f9dd29c5f85 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:18:44 -0300 Subject: [PATCH 05/18] chore: ignore .vscode folder Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1b3a0fd..d96eb0d 100755 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea +.vscode .phpunit.result.cache vendor /vendor-bin/**/vendor/ From 88bd7809554978f5acd1dd4ed94d72832091f403 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:19:22 -0300 Subject: [PATCH 06/18] chore: set default values Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- example/index.php | 8 -------- src/Runtime/JSignPdfRuntimeService.php | 12 +++++++----- src/Runtime/JavaRuntimeService.php | 5 ++++- src/Sign/JSignParam.php | 16 +++++++++------- tests/JSignPDFTest.php | 6 ++++-- 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/example/index.php b/example/index.php index 14a3f7d..dca3118 100644 --- a/example/index.php +++ b/example/index.php @@ -27,14 +27,6 @@ $param = JSignParam::instance(); -$param->setJavaVersion('openjdk version "21.0.8" 2025-07-15 LTS'); -$param->setJavaDownloadUrl('https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/OpenJDK21U-jre_x64_linux_hotspot_21.0.8_9.tar.gz'); -$param->setJavaPath(__DIR__ . '/../tmp/java/bin/java'); - -$param->setJsignPdfVersion('asdfasd'); -$param->setJSignPdfDownloadUrl('https://github.com/intoolswetrust/jsignpdf/releases/download/JSignPdf_2_3_0/jsignpdf-2.3.0.zip'); -$param->setjSignPdfJarPath(__DIR__ . '/../tmp/jsignpdf/JSignPdf.jar'); - $param->setCertificate($pfxCertificateContent); $param->setPdf(file_get_contents(__DIR__ . '/../tests/resources/pdf-test.pdf')); $param->setPassword($password); diff --git a/src/Runtime/JSignPdfRuntimeService.php b/src/Runtime/JSignPdfRuntimeService.php index 72412d9..84f8959 100644 --- a/src/Runtime/JSignPdfRuntimeService.php +++ b/src/Runtime/JSignPdfRuntimeService.php @@ -6,10 +6,7 @@ use InvalidArgumentException; use Jeidison\JSignPDF\Sign\JSignParam; -use PharData; -use PharException; use RuntimeException; -use UnexpectedValueException; use ZipArchive; class JSignPdfRuntimeService @@ -29,9 +26,14 @@ public function getPath(JSignParam $params): string if ($downloadUrl && $jsignPdfPath) { $baseDir = preg_replace('/\/JSignPdf.jar$/', '', $jsignPdfPath); if (!is_dir($baseDir)) { - throw new InvalidArgumentException('The JSignPdf base dir is not a real directory: '. $baseDir); + $ok = mkdir($baseDir, 0755, true); + if ($ok === false) { + throw new InvalidArgumentException('The JSignPdf base dir cannot be created: '. $baseDir); + } + } + if (!file_exists($jsignPdfPath)) { + self::downloadAndExtract($params); } - self::downloadAndExtract($params); return $jsignPdfPath; } diff --git a/src/Runtime/JavaRuntimeService.php b/src/Runtime/JavaRuntimeService.php index fa1f525..48208af 100644 --- a/src/Runtime/JavaRuntimeService.php +++ b/src/Runtime/JavaRuntimeService.php @@ -32,7 +32,10 @@ public function getPath(JSignParam $params): string if ($downloadUrl && $javaPath) { $baseDir = preg_replace('/\/bin\/java$/', '', $javaPath); if (!is_dir($baseDir)) { - throw new InvalidArgumentException('The java base dir is not a real directory. Create this directory first: '. $baseDir); + $ok = mkdir($baseDir, 0755, true); + if ($ok === false) { + throw new InvalidArgumentException('The java base dir is not a real directory. Create this directory first: '. $baseDir); + } } try { self::validateVersion($params); diff --git a/src/Sign/JSignParam.php b/src/Sign/JSignParam.php index 2bda0e5..c8d45ee 100644 --- a/src/Sign/JSignParam.php +++ b/src/Sign/JSignParam.php @@ -13,20 +13,22 @@ class JSignParam private $pathPdfSigned; private $JSignParameters = "-a -kst PKCS12"; private $isUseJavaInstalled = false; - private $javaPath = ''; + private string $javaPath = ''; private $tempPath; private $tempName; private $isOutputTypeBase64 = false; - private $jSignPdfJarPath; - private ?string $javaVersion = null; - private ?string $javaDownloadUrl = null; - private ?string $jSignPdfDownloadUrl = null; + private string $jSignPdfJarPath; + private string $javaVersion = 'openjdk version "21.0.8" 2025-07-15 LTS'; + private string $javaDownloadUrl = 'https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/OpenJDK21U-jre_x64_linux_hotspot_21.0.8_9.tar.gz'; + private string $jSignPdfDownloadUrl = 'https://github.com/intoolswetrust/jsignpdf/releases/download/JSignPdf_2_3_0/jsignpdf-2.3.0.zip'; private ?string $jsignPdfVersion = null; public function __construct() { $this->tempName = md5(time() . uniqid() . mt_rand()); $this->tempPath = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR; + $this->javaPath = $this->tempPath . 'java' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'java'; + $this->jSignPdfJarPath = $this->tempPath . 'jsignpdf' . DIRECTORY_SEPARATOR . 'JSignPdf.jar'; } public static function instance() @@ -179,7 +181,7 @@ public function setJavaDownloadUrl(string $url): self return $this; } - public function getJavaDownloadUrl(): ?string + public function getJavaDownloadUrl(): string { return $this->javaDownloadUrl; } @@ -190,7 +192,7 @@ public function setJSignPdfDownloadUrl(string $url): self return $this; } - public function getJSignPdfDownloadUrl(): ?string + public function getJSignPdfDownloadUrl(): string { return $this->jSignPdfDownloadUrl; } diff --git a/tests/JSignPDFTest.php b/tests/JSignPDFTest.php index a12d0e1..d190e50 100644 --- a/tests/JSignPDFTest.php +++ b/tests/JSignPDFTest.php @@ -24,7 +24,7 @@ function exec(string $command, ?array &$output = null, ?int &$return_var = null) */ class JSignPDFTest extends TestCase { - private $service; + private JSignService $service; protected function setUp(): void { @@ -171,7 +171,9 @@ public function testSignWhenPasswordIsInvalid() public function testJSignPDFNotFound() { $this->expectExceptionMessageMatches('/JSignPDF not found/'); - $params = JSignParamBuilder::instance()->withDefault()->setjSignPdfJarPath('invalid_path'); + $params = JSignParamBuilder::instance()->withDefault(); + $params->setJSignPdfDownloadUrl(''); + $params->setjSignPdfJarPath('invalid_path'); $params->setCertificate($this->getNewCert($params->getPassword())); $params->setIsUseJavaInstalled(true); $this->service->getVersion($params); From 9433d835ab58aa69ab41298228101e0f1601105b Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:46:31 -0300 Subject: [PATCH 07/18] fix: make unit test to work after refactor Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/Runtime/JavaRuntimeServiceTest.php | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tests/Runtime/JavaRuntimeServiceTest.php b/tests/Runtime/JavaRuntimeServiceTest.php index bed2b01..cd0ac8f 100644 --- a/tests/Runtime/JavaRuntimeServiceTest.php +++ b/tests/Runtime/JavaRuntimeServiceTest.php @@ -10,8 +10,6 @@ use Jeidison\JSignPDF\Sign\JSignParam; use PharData; use PHPUnit\Framework\TestCase; -use RuntimeException; -use UnexpectedValueException; class JavaRuntimeServiceTest extends TestCase { @@ -32,15 +30,21 @@ public function testGetPathWhenJavaIsInstalled(): void { public function testGetPathWithCustomAndValidJavaPath(): void { $jsignParam = new JSignParam(); $service = new JavaRuntimeService(); - $jsignParam->setJavaPath(PHP_BINARY); + vfsStream::setup('download'); + mkdir('vfs://download/bin'); + touch('vfs://download/bin/java'); + chmod('vfs://download/bin/java', 0755); + $jsignParam->setJavaPath('vfs://download/bin/java'); + $jsignParam->setJavaDownloadUrl(''); $path = $service->getPath($jsignParam); - $this->assertEquals(PHP_BINARY, $path); + $this->assertEquals('vfs://download/bin/java', $path); } public function testGetPathWithCustomAndInvalidJavaPath(): void { $jsignParam = new JSignParam(); $service = new JavaRuntimeService(); $jsignParam->setJavaPath(__FILE__); + $jsignParam->setJavaDownloadUrl(''); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessageMatches('/not executable/'); $service->getPath($jsignParam); @@ -49,7 +53,12 @@ public function testGetPathWithCustomAndInvalidJavaPath(): void { public function testGetPathWithDownloadUrlAndNotRealDirectory(): void { $jsignParam = new JSignParam(); $service = new JavaRuntimeService(); - $jsignParam->setJavaPath(__FILE__); + $root = vfsStream::setup('download'); + $root->chmod(0770) + ->chgrp(vfsStream::GROUP_USER_1) + ->chown(vfsStream::OWNER_USER_1); + chgrp('vfs://download', 44); + $jsignParam->setJavaPath('vfs://download/not_real_directory'); $jsignParam->setJavaDownloadUrl('https://fake.url'); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessageMatches('/not a real directory/'); @@ -59,8 +68,7 @@ public function testGetPathWithDownloadUrlAndNotRealDirectory(): void { public function testGetPathWithDownloadUrlAndEmptyJavaVersion(): void { $jsignParam = new JSignParam(); $service = new JavaRuntimeService(); - $jsignParam->setJavaPath(realpath(__DIR__ . '/../../tmp/')); - $jsignParam->setJavaDownloadUrl('https://fake.url'); + $jsignParam->setJavaVersion(''); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessageMatches('/Java version required/'); $service->getPath($jsignParam); @@ -157,7 +165,7 @@ public function testGetPathWithoutJavaFallback(): void { $jsignParam = new JSignParam(); $service = new JavaRuntimeService(); - $jsignParam->setJavaVersion('21.0.0'); + $jsignParam->setJavaPath(''); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessageMatches('/Java not found/'); $service->getPath($jsignParam); From c776f54d816bffc747457579e9c498ff8f06bf67 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:59:40 -0300 Subject: [PATCH 08/18] chore: remove mark skipped Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- src/Sign/JSignService.php | 2 +- tests/JSignPDFTest.php | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Sign/JSignService.php b/src/Sign/JSignService.php index 35b7537..0b42bce 100644 --- a/src/Sign/JSignService.php +++ b/src/Sign/JSignService.php @@ -75,7 +75,7 @@ public function getVersion(JSignParam $params) $jSignPdf = $params->getjSignPdfJarPath(); $command = "$java -jar $jSignPdf --version 2>&1"; - \exec($command, $output); + exec($command, $output); $lastRow = end($output); if (empty($output) || strpos($lastRow, 'version') === false) { return ''; diff --git a/tests/JSignPDFTest.php b/tests/JSignPDFTest.php index d190e50..7a48008 100644 --- a/tests/JSignPDFTest.php +++ b/tests/JSignPDFTest.php @@ -13,6 +13,7 @@ function exec(string $command, ?array &$output = null, ?int &$return_var = null) namespace Jeidison\JSignPDF\Tests; +use org\bovigo\vfs\vfsStream; use Exception; use Jeidison\JSignPDF\Sign\JSignService; use Jeidison\JSignPDF\Tests\Builder\JSignParamBuilder; @@ -194,10 +195,18 @@ public function testSignWhenJavaNotFound() public function testGetVersion() { - if (!class_exists('JSignPDF\JSignPDFBin\JavaCommandService')) { - $this->markTestSkipped('Install jsignpdf/jsignpdf-bin'); - } + global $mockExec; + $mockExec = ['JSignPdf version 2.3.0']; + $params = JSignParamBuilder::instance()->withDefault(); + vfsStream::setup('download'); + mkdir('vfs://download/bin'); + touch('vfs://download/bin/java'); + chmod('vfs://download/bin/java', 0755); + $params->setJavaPath('vfs://download/bin/java'); + $params->getJSignPdfDownloadUrl('fake_url'); + $params->setIsUseJavaInstalled(true); + $params->setjSignPdfJarPath('faje_path'); $version = $this->service->getVersion($params); $this->assertNotEmpty($version); } From bf332d26ab4a41aa98c5ae10fe42afacb25a4315 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:20:57 -0300 Subject: [PATCH 09/18] chore: reduce mark skipped Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- src/Sign/JSignService.php | 1 - tests/JSignPDFTest.php | 20 +++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Sign/JSignService.php b/src/Sign/JSignService.php index 0b42bce..27eaeae 100644 --- a/src/Sign/JSignService.php +++ b/src/Sign/JSignService.php @@ -120,7 +120,6 @@ private function commandSign(JSignParam $params) list ($pdf, $certificate) = $this->storeTempFiles($params); $java = $this->javaCommand($params); $jSignPdf = $this->getjSignPdfJarPath($params); - $this->throwIf(!file_exists($jSignPdf), 'Jar of JSignPDF not found on path: '. $jSignPdf); $password = escapeshellarg($params->getPassword()); return "$java -Duser.language=en -jar $jSignPdf $pdf -ksf $certificate -ksp {$password} {$params->getJSignParameters()} -d {$params->getPathPdfSigned()} 2>&1"; diff --git a/tests/JSignPDFTest.php b/tests/JSignPDFTest.php index 7a48008..7cd5897 100644 --- a/tests/JSignPDFTest.php +++ b/tests/JSignPDFTest.php @@ -68,13 +68,23 @@ private function getNewCert($password) public function testSignSuccess() { - if (!class_exists('JSignPDF\JSignPDFBin\JavaCommandService')) { - $this->markTestSkipped('Install jsignpdf/jsignpdf-bin'); - } + global $mockExec; + $mockExec = ['Finished: Signature succesfully created.']; $params = JSignParamBuilder::instance()->withDefault(); + vfsStream::setup('download'); + mkdir('vfs://download/bin'); + touch('vfs://download/bin/java'); + chmod('vfs://download/bin/java', 0755); + $params->setJavaPath('vfs://download/bin/java'); + $params->setJavaDownloadUrl(''); + $params->setjSignPdfJarPath('faje_path'); + $params->setJSignPdfDownloadUrl(''); $params->setCertificate($this->getNewCert($params->getPassword())); - $fileSigned = $this->service->sign($params); - $this->assertNotNull($fileSigned); + $params->setPathPdfSigned('vfs://download/temp'); + $signedFilePath = $params->getTempPdfSignedPath(); + file_put_contents($signedFilePath, 'signed file content'); + $fileSignedContent = $this->service->sign($params); + $this->assertEquals('signed file content', $fileSignedContent); } #[DataProvider('providerSignUsingDifferentPasswords')] From 2adf1bc7921228216d863472a3447cd63fea1070 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:30:11 -0300 Subject: [PATCH 10/18] fix: previous commit Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/JSignPDFTest.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/JSignPDFTest.php b/tests/JSignPDFTest.php index 7cd5897..c8d28a2 100644 --- a/tests/JSignPDFTest.php +++ b/tests/JSignPDFTest.php @@ -72,12 +72,13 @@ public function testSignSuccess() $mockExec = ['Finished: Signature succesfully created.']; $params = JSignParamBuilder::instance()->withDefault(); vfsStream::setup('download'); - mkdir('vfs://download/bin'); - touch('vfs://download/bin/java'); - chmod('vfs://download/bin/java', 0755); - $params->setJavaPath('vfs://download/bin/java'); + mkdir('vfs://download/jvava/bin', 0755, true); + touch('vfs://download/jvava/bin/java'); + chmod('vfs://download/jvava/bin/java', 0755); + $params->setJavaPath('vfs://download/jvava/bin/java'); $params->setJavaDownloadUrl(''); - $params->setjSignPdfJarPath('faje_path'); + mkdir('vfs://download/jsignpdf', 0755, true); + $params->setjSignPdfJarPath('vfs://download/jsignpdf'); $params->setJSignPdfDownloadUrl(''); $params->setCertificate($this->getNewCert($params->getPassword())); $params->setPathPdfSigned('vfs://download/temp'); From dff13e87342266a9d9747941beee6a722fe7bf60 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:03:54 -0300 Subject: [PATCH 11/18] chore: remove mark skipped Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/JSignPDFTest.php | 59 +++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/tests/JSignPDFTest.php b/tests/JSignPDFTest.php index c8d28a2..c6cf9b9 100644 --- a/tests/JSignPDFTest.php +++ b/tests/JSignPDFTest.php @@ -44,26 +44,15 @@ private function getNewCert($password) $csrNames = ['commonName' => 'Jhon Doe']; $csr = openssl_csr_new($csrNames, $privateKey, ['digest_alg' => 'sha256']); - $x509 = openssl_csr_sign($csr, null, $privateKey, $days = 365, ['digest_alg' => 'sha256']); + $x509 = openssl_csr_sign($csr, null, $privateKey, 365); - openssl_x509_export($x509, $rootCertificate); - openssl_pkey_export($privateKey, $rootPrivateKey); - - $privateKey = openssl_pkey_new([ - 'private_key_bits' => 2048, - 'private_key_type' => OPENSSL_KEYTYPE_RSA, - ]); - $temporaryFile = tempnam(sys_get_temp_dir(), 'cfg'); - $csr = openssl_csr_new($csrNames, $privateKey); - $x509 = openssl_csr_sign($csr, $rootCertificate, $rootPrivateKey, 365); - $certContent = null; openssl_pkcs12_export( $x509, - $certContent, + $pfxCertificateContent, $privateKey, $password, ); - return $certContent; + return $pfxCertificateContent; } public function testSignSuccess() @@ -92,17 +81,24 @@ public function testSignSuccess() public function testSignUsingDifferentPasswords(string $password): void { global $mockExec; - if (!class_exists('JSignPDF\JSignPDFBin\JavaCommandService')) { - $this->markTestSkipped('Install jsignpdf/jsignpdf-bin'); - } + $mockExec = ['Finished: Signature succesfully created.']; $params = JSignParamBuilder::instance()->withDefault(); + vfsStream::setup('download'); + mkdir('vfs://download/jvava/bin', 0755, true); + touch('vfs://download/jvava/bin/java'); + chmod('vfs://download/jvava/bin/java', 0755); + $params->setJavaPath('vfs://download/jvava/bin/java'); + $params->setJavaDownloadUrl(''); + mkdir('vfs://download/jsignpdf', 0755, true); + $params->setjSignPdfJarPath('vfs://download/jsignpdf'); + $params->setJSignPdfDownloadUrl(''); $params->setCertificate($this->getNewCert($password)); $params->setPassword($password); - $mockExec = 'Finished: Signature succesfully created.'; - $path = $params->getTempPdfSignedPath(); - file_put_contents($path, 'dummy'); - $fileSigned = $this->service->sign($params); - $this->assertNotNull($fileSigned); + $params->setPathPdfSigned('vfs://download/temp'); + $signedFilePath = $params->getTempPdfSignedPath(); + file_put_contents($signedFilePath, 'signed file content'); + $fileSignedContent = $this->service->sign($params); + $this->assertEquals('signed file content', $fileSignedContent); } public static function providerSignUsingDifferentPasswords(): array @@ -117,13 +113,22 @@ public static function providerSignUsingDifferentPasswords(): array public function testCertificateExpired() { - if (!class_exists('JSignPDF\JSignPDFBin\JavaCommandService')) { - $this->markTestSkipped('Install jsignpdf/jsignpdf-bin'); - } $this->expectExceptionMessage('Certificate expired.'); $params = JSignParamBuilder::instance()->withDefault(); - $fileSigned = $this->service->sign($params); - $this->assertNotNull($fileSigned); + vfsStream::setup('download'); + mkdir('vfs://download/jvava/bin', 0755, true); + touch('vfs://download/jvava/bin/java'); + chmod('vfs://download/jvava/bin/java', 0755); + $params->setJavaPath('vfs://download/jvava/bin/java'); + $params->setJavaDownloadUrl(''); + mkdir('vfs://download/jsignpdf', 0755, true); + $params->setjSignPdfJarPath('vfs://download/jsignpdf'); + $params->setJSignPdfDownloadUrl(''); + $params->setCertificate(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'certificado.pfx')); + $params->setPassword('123'); + $signedFilePath = $params->getTempPdfSignedPath(); + file_put_contents($signedFilePath, 'signed file content'); + $this->service->sign($params); } public function testSignError() From 7bc30135f3d8c5d20f73b013fd8c3b8aa3c571dc Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:30:59 -0300 Subject: [PATCH 12/18] chore: remove mark skipped Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/JSignPDFTest.php | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/tests/JSignPDFTest.php b/tests/JSignPDFTest.php index c6cf9b9..01fb4b0 100644 --- a/tests/JSignPDFTest.php +++ b/tests/JSignPDFTest.php @@ -141,13 +141,25 @@ public function testSignError() public function testWithWhenResponseIsBase64() { - if (!class_exists('JSignPDF\JSignPDFBin\JavaCommandService')) { - $this->markTestSkipped('Install jsignpdf/jsignpdf-bin'); - } - $params = JSignParamBuilder::instance()->withDefault()->setIsOutputTypeBase64(true); - $params->setCertificate($this->getNewCert($params->getPassword())); - $fileSigned = $this->service->sign($params); - $this->assertTrue(base64_decode($fileSigned, true) == true); + global $mockExec; + $mockExec = ['Finished: Signature succesfully created.']; + $params = JSignParamBuilder::instance()->withDefault(); + vfsStream::setup('download'); + mkdir('vfs://download/jvava/bin', 0755, true); + touch('vfs://download/jvava/bin/java'); + chmod('vfs://download/jvava/bin/java', 0755); + $params->setJavaPath('vfs://download/jvava/bin/java'); + $params->setJavaDownloadUrl(''); + mkdir('vfs://download/jsignpdf', 0755, true); + $params->setjSignPdfJarPath('vfs://download/jsignpdf'); + $params->setJSignPdfDownloadUrl(''); + $params->setCertificate($this->getNewCert('123')); + $params->setPassword('123'); + $signedFilePath = $params->getTempPdfSignedPath(); + file_put_contents($signedFilePath, 'signed file content'); + $params->setIsOutputTypeBase64(true); + $signedContent = $this->service->sign($params); + $this->assertEquals(base64_encode('signed file content'), $signedContent); } public function testSignWhenCertificateIsNull() From 6dbccdf401a79771ea0ce7a944b5cc24999db290 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:46:21 -0300 Subject: [PATCH 13/18] chore: make possible set the expire days Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/JSignPDFTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/JSignPDFTest.php b/tests/JSignPDFTest.php index 01fb4b0..8a4c847 100644 --- a/tests/JSignPDFTest.php +++ b/tests/JSignPDFTest.php @@ -34,7 +34,7 @@ protected function setUp(): void $this->service = new JSignService(); } - private function getNewCert($password) + private function getNewCert($password, $expireDays = 365) { $privateKey = openssl_pkey_new([ 'private_key_bits' => 2048, @@ -44,7 +44,7 @@ private function getNewCert($password) $csrNames = ['commonName' => 'Jhon Doe']; $csr = openssl_csr_new($csrNames, $privateKey, ['digest_alg' => 'sha256']); - $x509 = openssl_csr_sign($csr, null, $privateKey, 365); + $x509 = openssl_csr_sign($csr, null, $privateKey, $expireDays); openssl_pkcs12_export( $x509, From 3dd5e260a2fa3b4a16842a1c8e398d027175d49e Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:46:46 -0300 Subject: [PATCH 14/18] chore: generate a expired certificate When the days is zero, the expire date will be exactly the same date that the certificate is generated. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/JSignPDFTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/JSignPDFTest.php b/tests/JSignPDFTest.php index 8a4c847..77fc72e 100644 --- a/tests/JSignPDFTest.php +++ b/tests/JSignPDFTest.php @@ -124,7 +124,7 @@ public function testCertificateExpired() mkdir('vfs://download/jsignpdf', 0755, true); $params->setjSignPdfJarPath('vfs://download/jsignpdf'); $params->setJSignPdfDownloadUrl(''); - $params->setCertificate(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'certificado.pfx')); + $params->setCertificate($this->getNewCert('123', 0)); $params->setPassword('123'); $signedFilePath = $params->getTempPdfSignedPath(); file_put_contents($signedFilePath, 'signed file content'); From 724d93302b421c91bbc4c0e1e7c3dc9b75bfbf82 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:49:34 -0300 Subject: [PATCH 15/18] chore: remove unecessary test This already was covered at another place Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/JSignPDFTest.php | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/JSignPDFTest.php b/tests/JSignPDFTest.php index 77fc72e..9a4c0b2 100644 --- a/tests/JSignPDFTest.php +++ b/tests/JSignPDFTest.php @@ -208,19 +208,6 @@ public function testJSignPDFNotFound() $this->service->getVersion($params); } - public function testSignWhenJavaNotFound() - { - $javaVersion = exec("java -version 2>&1"); - $hasJavaVersion = strpos($javaVersion, 'not found') === false; - if ($hasJavaVersion) { - $this->markTestSkipped('Java is already installed, impossible to test if it is not installed'); - } - $this->expectExceptionMessage('Java not installed, set the flag "isUseJavaInstalled" as false or install java.'); - $params = JSignParamBuilder::instance()->withDefault()->setIsUseJavaInstalled(true); - $params->setCertificate($this->getNewCert($params->getPassword())); - $this->service->sign($params); - } - public function testGetVersion() { global $mockExec; From 50a46a0f1073bb0a4bd4055518907fbfd0d5ca01 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:59:51 -0300 Subject: [PATCH 16/18] fix: replace implicit nullable by explicit null Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- src/JSignPDF.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/JSignPDF.php b/src/JSignPDF.php index 508703e..11b12ae 100644 --- a/src/JSignPDF.php +++ b/src/JSignPDF.php @@ -13,13 +13,13 @@ class JSignPDF private $service; private $param; - public function __construct(JSignParam $param = null) + public function __construct(?JSignParam $param = null) { $this->service = new JSignService(); $this->param = $param; } - public static function instance(JSignParam $param = null) + public static function instance(?JSignParam $param = null) { return new self($param); } From ee70dea20a15a49ef57e2ef2e96285875697fbb4 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Wed, 6 Aug 2025 17:18:42 -0300 Subject: [PATCH 17/18] chore: update README with new methods Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 6c23ef4..99c6f69 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,12 @@ With JSignPDF bin: ```php $param->setjSignPdfJarPath('/path/to/jsignpdf'); ``` +With specific Java or JSignPdf version: +```php +$params->getJSignPdfDownloadUrl('the url to download the zip here'); +$params->setJavaDownloadUrl('the url to download the .tar.gz here'); +$params->setJavaVersion('openjdk version "21.0.8" 2025-07-15 LTS') +``` Without JSignPDF bin: ```bash From 9187d8c093219711259ce286fa94bed220ab18d3 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 10 Aug 2025 14:23:24 -0300 Subject: [PATCH 18/18] feat: use a temp file to didentify the java version Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- README.md | 1 - src/Runtime/JSignPdfRuntimeService.php | 11 +++++- src/Runtime/JavaRuntimeService.php | 22 +++-------- src/Sign/JSignParam.php | 23 ----------- tests/JSignPDFTest.php | 6 ++- tests/Runtime/JavaRuntimeServiceTest.php | 50 ++++++++++++++++++------ 6 files changed, 57 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 99c6f69..68dc638 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,6 @@ With specific Java or JSignPdf version: ```php $params->getJSignPdfDownloadUrl('the url to download the zip here'); $params->setJavaDownloadUrl('the url to download the .tar.gz here'); -$params->setJavaVersion('openjdk version "21.0.8" 2025-07-15 LTS') ``` Without JSignPDF bin: diff --git a/src/Runtime/JSignPdfRuntimeService.php b/src/Runtime/JSignPdfRuntimeService.php index 84f8959..c746e53 100644 --- a/src/Runtime/JSignPdfRuntimeService.php +++ b/src/Runtime/JSignPdfRuntimeService.php @@ -31,7 +31,7 @@ public function getPath(JSignParam $params): string throw new InvalidArgumentException('The JSignPdf base dir cannot be created: '. $baseDir); } } - if (!file_exists($jsignPdfPath)) { + if (!file_exists($jsignPdfPath) || !self::validateVersion($params)) { self::downloadAndExtract($params); } return $jsignPdfPath; @@ -40,6 +40,13 @@ public function getPath(JSignParam $params): string throw new InvalidArgumentException('Java not found.'); } + private function validateVersion(JSignParam $params): bool + { + $jsignPdfPath = $params->getjSignPdfJarPath(); + $versionCacheFile = $jsignPdfPath . '/.jsignpdf_version_' . basename($params->getJSignPdfDownloadUrl()); + return file_exists($versionCacheFile); + } + private function downloadAndExtract(JSignParam $params): void { $jsignPdfPath = $params->getjSignPdfJarPath(); @@ -68,10 +75,12 @@ private function downloadAndExtract(JSignParam $params): void } @exec('mv ' . escapeshellarg($baseDir . '/'. $z->getNameIndex(0)) . '/JSignPdf.jar ' . escapeshellarg($baseDir)); @exec('rm -rf ' . escapeshellarg($baseDir . '/'. $z->getNameIndex(0))); + @exec('rm -f ' . escapeshellarg($baseDir) . '/.jsignpdf_version_*'); unlink($baseDir . '/jsignpdf.zip'); if (!file_exists($baseDir . '/JSignPdf.jar')) { throw new RuntimeException('Java binary not found at: ' . $baseDir . '/bin/java'); } + touch($baseDir . '/.jsignpdf_version_' . basename($url)); } private function chunkDownload(string $url, string $destination): void diff --git a/src/Runtime/JavaRuntimeService.php b/src/Runtime/JavaRuntimeService.php index 48208af..15c6283 100644 --- a/src/Runtime/JavaRuntimeService.php +++ b/src/Runtime/JavaRuntimeService.php @@ -37,9 +37,7 @@ public function getPath(JSignParam $params): string throw new InvalidArgumentException('The java base dir is not a real directory. Create this directory first: '. $baseDir); } } - try { - self::validateVersion($params); - } catch (RuntimeException) { + if (!self::validateVersion($params)) { self::downloadAndExtract($downloadUrl, $javaPath); } $params->setJavaDownloadUrl(''); @@ -52,20 +50,10 @@ public function getPath(JSignParam $params): string private function validateVersion(JSignParam $params): bool { - $version = $params->getJavaVersion(); - if (!$version) { - throw new InvalidArgumentException('Java version required'); - } $javaPath = $params->getJavaPath(); - \exec($javaPath . ' -version 2>&1', $javaVersion, $resultCode); - if (count($javaVersion) <= 1) { - throw new RuntimeException('Failed to execute Java. Sounds that your operational system is blocking the JVM.'); - } - if ($resultCode !== 0) { - throw new RuntimeException('Failure to check Java version.'); - } - $javaVersion = current($javaVersion); - return $javaVersion === $version; + $baseDir = preg_replace('/\/bin\/java$/', '', $javaPath); + $lastVersion = $baseDir . '/.java_version_' . basename($params->getJavaDownloadUrl()); + return file_exists($lastVersion); } private function downloadAndExtract(string $url, string $baseDir): void @@ -94,7 +82,9 @@ private function downloadAndExtract(string $url, string $baseDir): void $tar->extractTo(directory: $baseDir, overwrite: true); @exec('mv ' . escapeshellarg($baseDir . '/'. $rootDirInsideTar) . '/* ' . escapeshellarg($baseDir)); @exec('rm -rf ' . escapeshellarg($baseDir . '/'. $rootDirInsideTar)); + @exec('rm -f ' . escapeshellarg($baseDir) . '/.java_version_*'); unlink($baseDir . '/java.tar.gz'); + touch($baseDir . '/.java_version_' . basename($url)); if (!file_exists($baseDir . '/bin/java')) { throw new RuntimeException('Java binary not found at: ' . $baseDir . '/bin/java'); } diff --git a/src/Sign/JSignParam.php b/src/Sign/JSignParam.php index c8d45ee..692dc6d 100644 --- a/src/Sign/JSignParam.php +++ b/src/Sign/JSignParam.php @@ -18,10 +18,8 @@ class JSignParam private $tempName; private $isOutputTypeBase64 = false; private string $jSignPdfJarPath; - private string $javaVersion = 'openjdk version "21.0.8" 2025-07-15 LTS'; private string $javaDownloadUrl = 'https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/OpenJDK21U-jre_x64_linux_hotspot_21.0.8_9.tar.gz'; private string $jSignPdfDownloadUrl = 'https://github.com/intoolswetrust/jsignpdf/releases/download/JSignPdf_2_3_0/jsignpdf-2.3.0.zip'; - private ?string $jsignPdfVersion = null; public function __construct() { @@ -138,17 +136,6 @@ public function getjSignPdfJarPath() return $this->jSignPdfJarPath; } - public function setJsignPdfVersion(string $version): self - { - $this->jsignPdfVersion = $version; - return $this; - } - - public function getJsignPdfVersion(): ?string - { - return $this->jsignPdfVersion; - } - public function isOutputTypeBase64(): bool { return $this->isOutputTypeBase64; @@ -196,14 +183,4 @@ public function getJSignPdfDownloadUrl(): string { return $this->jSignPdfDownloadUrl; } - - public function setJavaVersion(string $javaVersion): self - { - $this->javaVersion = $javaVersion; - return $this; - } - - public function getJavaVersion(): ?string { - return $this->javaVersion; - } } diff --git a/tests/JSignPDFTest.php b/tests/JSignPDFTest.php index 9a4c0b2..7ed04fc 100644 --- a/tests/JSignPDFTest.php +++ b/tests/JSignPDFTest.php @@ -218,10 +218,12 @@ public function testGetVersion() mkdir('vfs://download/bin'); touch('vfs://download/bin/java'); chmod('vfs://download/bin/java', 0755); + mkdir('vfs://download/jsignpdf_fake_path/'); + touch('vfs://download/jsignpdf_fake_path/.jsignpdf_version_fake_url'); $params->setJavaPath('vfs://download/bin/java'); - $params->getJSignPdfDownloadUrl('fake_url'); + $params->setJSignPdfDownloadUrl('fake_url'); $params->setIsUseJavaInstalled(true); - $params->setjSignPdfJarPath('faje_path'); + $params->setjSignPdfJarPath('vfs://download/jsignpdf_fake_path'); $version = $this->service->getVersion($params); $this->assertNotEmpty($version); } diff --git a/tests/Runtime/JavaRuntimeServiceTest.php b/tests/Runtime/JavaRuntimeServiceTest.php index cd0ac8f..a20d13b 100644 --- a/tests/Runtime/JavaRuntimeServiceTest.php +++ b/tests/Runtime/JavaRuntimeServiceTest.php @@ -65,15 +65,6 @@ public function testGetPathWithDownloadUrlAndNotRealDirectory(): void { $service->getPath($jsignParam); } - public function testGetPathWithDownloadUrlAndEmptyJavaVersion(): void { - $jsignParam = new JSignParam(); - $service = new JavaRuntimeService(); - $jsignParam->setJavaVersion(''); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageMatches('/Java version required/'); - $service->getPath($jsignParam); - } - public function testGetPathWithDownloadUrlWithInvalidUrl(): void { vfsStream::setup('download'); mkdir('vfs://download/bin'); @@ -83,7 +74,6 @@ public function testGetPathWithDownloadUrlWithInvalidUrl(): void { $service = new JavaRuntimeService(); $jsignParam->setJavaPath('vfs://download/bin/java'); $jsignParam->setJavaDownloadUrl('invalid_url'); - $jsignParam->setJavaVersion('21.0.0'); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessageMatches('/url.*invalid/'); $service->getPath($jsignParam); @@ -98,7 +88,6 @@ public function testGetPathWithDownloadUrlWith4xxError(): void { $service = new JavaRuntimeService(); $jsignParam->setJavaPath('vfs://download/bin/java'); $jsignParam->setJavaDownloadUrl('https://404.domain'); - $jsignParam->setJavaVersion('21.0.0'); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessageMatches('/Failure to download/'); $service->getPath($jsignParam); @@ -125,7 +114,6 @@ public function testGetPathWithDownloadUrlWithInvalidGzipedFile(): void { $url = $server->getServerRoot(); $jsignParam->setJavaDownloadUrl($url); - $jsignParam->setJavaVersion('21.0.0'); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessageMatches('/cannot be extracted/'); $service->getPath($jsignParam); @@ -153,12 +141,48 @@ public function testGetPathWithDownloadUrlWithInvalidJavaPackage(): void { $url = $server->getServerRoot(); $jsignParam->setJavaDownloadUrl($url); - $jsignParam->setJavaVersion('21.0.0'); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessageMatches('/Invalid tar content/'); $service->getPath($jsignParam); } + public function testGetPathWithDownloadUrlWithInvalidVersion(): void { + $jsignParam = new JSignParam(); + $service = new JavaRuntimeService(); + + // When the version is invalid, will try to download the package + $server = new MockWebServer(); + $server->start(); + $server->setResponseOfPath( + '/', + new Response('invalid response'), + ); + $baseUrl = $server->getServerRoot(); + $url = $baseUrl . '/OpenJDK21U-jre_x64_linux_hotspot_21.0.8_9.tar.gz'; + $jsignParam->setJavaDownloadUrl($url); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/cannot be extracted/'); + $service->getPath($jsignParam); + } + + public function testGetPathWithDownloadUrlWithValidVersion(): void { + $jsignParam = new JSignParam(); + $service = new JavaRuntimeService(); + + $tarGzFilename = 'OpenJDK21U-jre_x64_linux_hotspot_21.0.8_9.tar.gz'; + $url = 'https://fake.url/' . $tarGzFilename; + $jsignParam->setJavaDownloadUrl($url); + + // When have a file with an expected name, will consider that the + // downloaded java version is right + touch($this->testTmpDir . '/.java_version_' . $tarGzFilename); + $jsignParam->setJavaPath($this->testTmpDir . '/bin/java'); + + $javaPath = $service->getPath($jsignParam); + $this->assertEquals($jsignParam->getJavaPath(), $javaPath); + } + public function testGetPathWithoutJavaFallback(): void { mkdir($this->testTmpDir . '/bin', 0755, true);