Skip to content

Commit b719024

Browse files
authored
Merge pull request #30 from JSignPdf/feat/java-fallback
feat: add java and JSignPdf fallback
2 parents 7ed1ece + 9187d8c commit b719024

12 files changed

Lines changed: 791 additions & 90 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.idea
2+
.vscode
23
.phpunit.result.cache
34
vendor
45
/vendor-bin/**/vendor/

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ With JSignPDF bin:
3838
```php
3939
$param->setjSignPdfJarPath('/path/to/jsignpdf');
4040
```
41+
With specific Java or JSignPdf version:
42+
```php
43+
$params->getJSignPdfDownloadUrl('the url to download the zip here');
44+
$params->setJavaDownloadUrl('the url to download the .tar.gz here');
45+
```
4146

4247
Without JSignPDF bin:
4348
```bash

example/index.php

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,34 @@
33
/**
44
* @author Jeidison Farias <jeidison.farias@gmail.com>
55
*/
6-
require_once "../src/JSignPDF.php";
6+
require_once __DIR__ . '/../vendor/autoload.php';
77

88
use Jeidison\JSignPDF\JSignPDF;
99
use Jeidison\JSignPDF\Sign\JSignParam;
1010

11+
$password = '123';
12+
13+
$privateKey = openssl_pkey_new([
14+
'private_key_bits' => 2048,
15+
'private_key_type' => OPENSSL_KEYTYPE_RSA,
16+
]);
17+
18+
$csr = openssl_csr_new(['commonName' => 'John Doe'], $privateKey, ['digest_alg' => 'sha256']);
19+
$x509 = openssl_csr_sign($csr, null, $privateKey, 365);
20+
21+
openssl_pkcs12_export(
22+
$x509,
23+
$pfxCertificateContent,
24+
$privateKey,
25+
$password,
26+
);
27+
1128
$param = JSignParam::instance();
12-
$param->setCertificate(file_get_contents('../tests/resources/certificado.pfx'));
13-
$param->setPdf(file_get_contents('../tests/resources/pdf-test.pdf'));
14-
$param->setPassword('123');
29+
30+
$param->setCertificate($pfxCertificateContent);
31+
$param->setPdf(file_get_contents(__DIR__ . '/../tests/resources/pdf-test.pdf'));
32+
$param->setPassword($password);
1533

1634
$jSignPdf = new JSignPDF($param);
1735
$fileSigned = $jSignPdf->sign();
18-
file_put_contents('../tmp/file_signed.pdf', $fileSigned);
36+
file_put_contents(__DIR__ . '/../tmp/file_signed.pdf', $fileSigned);

src/JSignPDF.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ class JSignPDF
1313
private $service;
1414
private $param;
1515

16-
public function __construct(JSignParam $param = null)
16+
public function __construct(?JSignParam $param = null)
1717
{
1818
$this->service = new JSignService();
1919
$this->param = $param;
2020
}
2121

22-
public static function instance(JSignParam $param = null)
22+
public static function instance(?JSignParam $param = null)
2323
{
2424
return new self($param);
2525
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Jeidison\JSignPDF\Runtime;
6+
7+
use InvalidArgumentException;
8+
use Jeidison\JSignPDF\Sign\JSignParam;
9+
use RuntimeException;
10+
use ZipArchive;
11+
12+
class JSignPdfRuntimeService
13+
{
14+
public function getPath(JSignParam $params): string
15+
{
16+
$jsignPdfPath = $params->getjSignPdfJarPath();
17+
$downloadUrl = $params->getJSignPdfDownloadUrl();
18+
19+
if ($jsignPdfPath && !$downloadUrl) {
20+
if (file_exists($jsignPdfPath)) {
21+
return $jsignPdfPath;
22+
}
23+
throw new InvalidArgumentException('Jar of JSignPDF not found on path: '. $jsignPdfPath);
24+
}
25+
26+
if ($downloadUrl && $jsignPdfPath) {
27+
$baseDir = preg_replace('/\/JSignPdf.jar$/', '', $jsignPdfPath);
28+
if (!is_dir($baseDir)) {
29+
$ok = mkdir($baseDir, 0755, true);
30+
if ($ok === false) {
31+
throw new InvalidArgumentException('The JSignPdf base dir cannot be created: '. $baseDir);
32+
}
33+
}
34+
if (!file_exists($jsignPdfPath) || !self::validateVersion($params)) {
35+
self::downloadAndExtract($params);
36+
}
37+
return $jsignPdfPath;
38+
}
39+
40+
throw new InvalidArgumentException('Java not found.');
41+
}
42+
43+
private function validateVersion(JSignParam $params): bool
44+
{
45+
$jsignPdfPath = $params->getjSignPdfJarPath();
46+
$versionCacheFile = $jsignPdfPath . '/.jsignpdf_version_' . basename($params->getJSignPdfDownloadUrl());
47+
return file_exists($versionCacheFile);
48+
}
49+
50+
private function downloadAndExtract(JSignParam $params): void
51+
{
52+
$jsignPdfPath = $params->getjSignPdfJarPath();
53+
$url = $params->getJSignPdfDownloadUrl();
54+
55+
$baseDir = preg_replace('/\/JSignPdf.jar$/', '', $jsignPdfPath);
56+
57+
if (!is_dir($baseDir)) {
58+
$ok = mkdir($baseDir, 0755, true);
59+
if (!$ok) {
60+
throw new RuntimeException('Failure to create the folder: ' . $baseDir);
61+
}
62+
}
63+
if (!filter_var($url, FILTER_VALIDATE_URL)) {
64+
throw new InvalidArgumentException('The url to download Java is invalid: ' . $url);
65+
}
66+
$this->chunkDownload($url, $baseDir . '/jsignpdf.zip');
67+
$z = new ZipArchive();
68+
$ok = $z->open($baseDir . '/jsignpdf.zip');
69+
if ($ok !== true) {
70+
throw new InvalidArgumentException('The file ' . $baseDir . '/jsignpdf.zip cannot be extracted');
71+
}
72+
$ok = $z->extractTo(pathto: $baseDir, files: [$z->getNameIndex(0) . 'JSignPdf.jar']);
73+
if ($ok !== true) {
74+
throw new InvalidArgumentException('JSignPdf.jar not found inside path: ' . $z->getNameIndex(0) . 'JSignPdf.jar');
75+
}
76+
@exec('mv ' . escapeshellarg($baseDir . '/'. $z->getNameIndex(0)) . '/JSignPdf.jar ' . escapeshellarg($baseDir));
77+
@exec('rm -rf ' . escapeshellarg($baseDir . '/'. $z->getNameIndex(0)));
78+
@exec('rm -f ' . escapeshellarg($baseDir) . '/.jsignpdf_version_*');
79+
unlink($baseDir . '/jsignpdf.zip');
80+
if (!file_exists($baseDir . '/JSignPdf.jar')) {
81+
throw new RuntimeException('Java binary not found at: ' . $baseDir . '/bin/java');
82+
}
83+
touch($baseDir . '/.jsignpdf_version_' . basename($url));
84+
}
85+
86+
private function chunkDownload(string $url, string $destination): void
87+
{
88+
$fp = fopen($destination, 'w');
89+
90+
if ($fp) {
91+
$ch = curl_init($url);
92+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
93+
curl_setopt($ch, CURLOPT_FILE, $fp);
94+
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
95+
$response = curl_exec($ch);
96+
if ($response === false) {
97+
throw new InvalidArgumentException('Failure to download file using the url ' . $url);
98+
}
99+
curl_close($ch);
100+
fclose($fp);
101+
} else {
102+
throw new InvalidArgumentException("Failute to download file using the url $url");
103+
}
104+
}
105+
}

src/Runtime/JavaRuntimeService.php

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Jeidison\JSignPDF\Runtime;
6+
7+
use InvalidArgumentException;
8+
use Jeidison\JSignPDF\Sign\JSignParam;
9+
use PharData;
10+
use PharException;
11+
use RuntimeException;
12+
use UnexpectedValueException;
13+
14+
class JavaRuntimeService
15+
{
16+
public function getPath(JSignParam $params): string
17+
{
18+
if ($params->isUseJavaInstalled()) {
19+
return 'java';
20+
}
21+
22+
$javaPath = $params->getJavaPath();
23+
$downloadUrl = $params->getJavaDownloadUrl();
24+
25+
if ($javaPath && !$downloadUrl) {
26+
if (is_file($javaPath) && is_executable($javaPath)) {
27+
return $javaPath;
28+
}
29+
throw new InvalidArgumentException('Java path defined is not executable: ' . $javaPath);
30+
}
31+
32+
if ($downloadUrl && $javaPath) {
33+
$baseDir = preg_replace('/\/bin\/java$/', '', $javaPath);
34+
if (!is_dir($baseDir)) {
35+
$ok = mkdir($baseDir, 0755, true);
36+
if ($ok === false) {
37+
throw new InvalidArgumentException('The java base dir is not a real directory. Create this directory first: '. $baseDir);
38+
}
39+
}
40+
if (!self::validateVersion($params)) {
41+
self::downloadAndExtract($downloadUrl, $javaPath);
42+
}
43+
$params->setJavaDownloadUrl('');
44+
$params->setJavaPath($javaPath);
45+
return $javaPath;
46+
}
47+
48+
throw new InvalidArgumentException('Java not found.');
49+
}
50+
51+
private function validateVersion(JSignParam $params): bool
52+
{
53+
$javaPath = $params->getJavaPath();
54+
$baseDir = preg_replace('/\/bin\/java$/', '', $javaPath);
55+
$lastVersion = $baseDir . '/.java_version_' . basename($params->getJavaDownloadUrl());
56+
return file_exists($lastVersion);
57+
}
58+
59+
private function downloadAndExtract(string $url, string $baseDir): void
60+
{
61+
$baseDir = preg_replace('/\/bin\/java$/', '', $baseDir);
62+
63+
if (!is_dir($baseDir)) {
64+
$ok = mkdir($baseDir, 0755, true);
65+
if (!$ok) {
66+
throw new RuntimeException('Failure to create the folder: ' . $baseDir);
67+
}
68+
}
69+
if (!filter_var($url, FILTER_VALIDATE_URL)) {
70+
throw new InvalidArgumentException('The url to download Java is invalid: ' . $url);
71+
}
72+
$this->chunkDownload($url, $baseDir . '/java.tar.gz');
73+
try {
74+
$tar = new PharData($baseDir . '/java.tar.gz');
75+
} catch (PharException|UnexpectedValueException $e) {
76+
throw new InvalidArgumentException('The file ' . $baseDir . '/java.tar.gz cannot be extracted');
77+
}
78+
$rootDirInsideTar = $this->findRootDir($tar, $baseDir . '/java.tar.gz');
79+
if (!$rootDirInsideTar) {
80+
throw new InvalidArgumentException('Invalid tar content.');
81+
}
82+
$tar->extractTo(directory: $baseDir, overwrite: true);
83+
@exec('mv ' . escapeshellarg($baseDir . '/'. $rootDirInsideTar) . '/* ' . escapeshellarg($baseDir));
84+
@exec('rm -rf ' . escapeshellarg($baseDir . '/'. $rootDirInsideTar));
85+
@exec('rm -f ' . escapeshellarg($baseDir) . '/.java_version_*');
86+
unlink($baseDir . '/java.tar.gz');
87+
touch($baseDir . '/.java_version_' . basename($url));
88+
if (!file_exists($baseDir . '/bin/java')) {
89+
throw new RuntimeException('Java binary not found at: ' . $baseDir . '/bin/java');
90+
}
91+
chmod($baseDir . '/bin/java', 0700);
92+
}
93+
94+
private function findRootDir(PharData $phar, $rootDir) {
95+
$files = new \RecursiveIteratorIterator($phar, \RecursiveIteratorIterator::CHILD_FIRST);
96+
$rootDir = realpath($rootDir);
97+
98+
foreach ($files as $file) {
99+
$pathName = $file->getPathname();
100+
if (str_contains($pathName, '/bin/') || str_contains($pathName, '/bin/')) {
101+
$parts = explode($rootDir, $pathName);
102+
$internalFullPath = end($parts);
103+
$parts = explode('/bin/', $internalFullPath);
104+
return trim($parts[0], '/');
105+
}
106+
}
107+
}
108+
109+
private function chunkDownload(string $url, string $destination): void
110+
{
111+
$fp = fopen($destination, 'w');
112+
113+
if ($fp) {
114+
$ch = curl_init($url);
115+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
116+
curl_setopt($ch, CURLOPT_FILE, $fp);
117+
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
118+
$response = curl_exec($ch);
119+
if ($response === false) {
120+
throw new InvalidArgumentException('Failure to download file using the url ' . $url);
121+
}
122+
curl_close($ch);
123+
fclose($fp);
124+
} else {
125+
throw new InvalidArgumentException("Failute to download file using the url $url");
126+
}
127+
}
128+
}

src/Sign/JSignParam.php

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,20 @@ class JSignParam
1313
private $pathPdfSigned;
1414
private $JSignParameters = "-a -kst PKCS12";
1515
private $isUseJavaInstalled = false;
16-
private $javaPath = '';
16+
private string $javaPath = '';
1717
private $tempPath;
1818
private $tempName;
1919
private $isOutputTypeBase64 = false;
20-
private $jSignPdfJarPath;
20+
private string $jSignPdfJarPath;
21+
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';
22+
private string $jSignPdfDownloadUrl = 'https://github.com/intoolswetrust/jsignpdf/releases/download/JSignPdf_2_3_0/jsignpdf-2.3.0.zip';
2123

2224
public function __construct()
2325
{
2426
$this->tempName = md5(time() . uniqid() . mt_rand());
2527
$this->tempPath = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
28+
$this->javaPath = $this->tempPath . 'java' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'java';
29+
$this->jSignPdfJarPath = $this->tempPath . 'jsignpdf' . DIRECTORY_SEPARATOR . 'JSignPdf.jar';
2630
}
2731

2832
public static function instance()
@@ -158,4 +162,25 @@ public function getTempCertificatePath()
158162
return $this->getTempPath() . $this->getTempName('.pfx');
159163
}
160164

165+
public function setJavaDownloadUrl(string $url): self
166+
{
167+
$this->javaDownloadUrl = $url;
168+
return $this;
169+
}
170+
171+
public function getJavaDownloadUrl(): string
172+
{
173+
return $this->javaDownloadUrl;
174+
}
175+
176+
public function setJSignPdfDownloadUrl(string $url): self
177+
{
178+
$this->jSignPdfDownloadUrl = $url;
179+
return $this;
180+
}
181+
182+
public function getJSignPdfDownloadUrl(): string
183+
{
184+
return $this->jSignPdfDownloadUrl;
185+
}
161186
}

0 commit comments

Comments
 (0)