diff --git a/lib/Migration/Version2040Date20211027183759.php b/lib/Migration/Version2040Date20211027183759.php index 8d83d48609..d5ad0911b0 100644 --- a/lib/Migration/Version2040Date20211027183759.php +++ b/lib/Migration/Version2040Date20211027183759.php @@ -6,13 +6,10 @@ use Closure; use Doctrine\DBAL\Types\Types; -use OC\SystemConfig; -use OCA\Libresign\AppInfo\Application; -use OCA\Libresign\Command\Install; +use OCA\Libresign\Service\PdfParserService; use OCP\DB\ISchemaWrapper; use OCP\Files\File; use OCP\Files\IRootFolder; -use OCP\IConfig; use OCP\IDBConnection; use OCP\Migration\IOutput; use OCP\Migration\SimpleMigrationStep; @@ -22,28 +19,20 @@ class Version2040Date20211027183759 extends SimpleMigrationStep { /** @var IRootFolder*/ private $root; - /** @var IDBConnection */ - private $connection; - /** @var Install */ - private $install; - /** @var IConfig */ - private $config; - /** @var SystemConfig */ - private $systemConfig; + /** @var PdfParserService */ + private $PdfParserService; /** @var array */ private $rows; - public function __construct(IRootFolder $root, - IDBConnection $connection, - IRootFolder $rootfolder, - Install $install, - IConfig $config, - SystemConfig $systemConfig) { + public function __construct(IDBConnection $connection, + IRootFolder $root, + PdfParserService $PdfParserService) { $this->connection = $connection; $this->install = $install; $this->config = $config; $this->systemConfig = $systemConfig; $this->rootFolder = $rootfolder; $this->root = $root; + $this->PdfParserService = $PdfParserService; } public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void { @@ -81,7 +70,7 @@ public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array /** @var File[] */ $file = $userFolder->getById($row['node_id']); if (count($file) >= 1) { - $data = $this->getMetadataFromCli($cli, $file[0]->getPath()); + $data = $this->PdfParserService->getMetadata($file[0]->getPath()); $json = json_encode($data); $query = $this->connection->getQueryBuilder(); $query @@ -89,7 +78,7 @@ public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array ->set('metadata', $query->createNamedParameter($json)) ->where($query->expr()->eq('id', $query->createNamedParameter($row['id']))); - $query->execute(); + $query->executeStatement(); } } } diff --git a/lib/Service/InstallService.php b/lib/Service/InstallService.php new file mode 100644 index 0000000000..78dde44dda --- /dev/null +++ b/lib/Service/InstallService.php @@ -0,0 +1,337 @@ +tempManager = $tempManager; + $this->clientService = $clientService; + $this->config = $config; + $this->systemConfig = $systemConfig; + $this->rootFolder = $rootFolder; + } + + public function setOutput(OutputInterface $output): void { + $this->output = $output; + } + + public function getFolder($path = ''): Folder { + $rootFolder = $this->getAppRootFolder(); + try { + $folder = $rootFolder->newFolder(Application::APP_ID . DIRECTORY_SEPARATOR . $path); + } catch (\Throwable $th) { + $folder = $rootFolder->get(Application::APP_ID . DIRECTORY_SEPARATOR . $path); + } + return $folder; + } + + private function getAppDataFolderName(): string { + $instanceId = $this->systemConfig->getValue('instanceid', null); + if ($instanceId === null) { + throw new \RuntimeException('no instance id!'); + } + + return 'appdata_' . $instanceId; + } + + private function getDataDir(): string { + $dataDir = $this->systemConfig->getValue('datadirectory', \OC::$SERVERROOT . '/data/'); + return $dataDir; + } + + private function getAppRootFolder(): Folder { + $path = $this->getAppDataFolderName(); + try { + $folder = $this->rootFolder->get($path); + } catch (\Throwable $th) { + $folder = $this->rootFolder->newFolder($path); + } + return $folder; + } + + public function getFullPath(): string { + $folder = $this->getFolder(); + return $this->getDataDir() . '/' . $folder->getInternalPath(); + } + + public function installJava(): void { + $extractDir = $this->getFullPath(); + + /** + * To update: + * Check the compatible version of Java to use JSignPdf and update all the follow data + * URL used to get the MD5 and URL to download: + * https://jdk.java.net/java-se-ri/8-MR3 + */ + if (PHP_OS_FAMILY === 'Windows') { + $url = 'https://download.java.net/openjdk/jdk8u41/ri/openjdk-8u41-b04-windows-i586-14_jan_2020.zip'; + $tempFile = $this->tempManager->getTemporaryFile('.zip'); + $executableExtension = '.exe'; + $class = ZIP::class; + $md5 = '48ac2152d1fb0ad1d343104be210d532'; + } else { + $url = 'https://download.java.net/openjdk/jdk8u41/ri/openjdk-8u41-b04-linux-x64-14_jan_2020.tar.gz'; + $tempFile = $this->tempManager->getTemporaryFile('.tar.gz'); + $executableExtension = ''; + $class = TAR::class; + $md5 = '35f515e9436f4fefad091db2c1450c5f'; + } + + $this->download($url, 'java', $tempFile, $md5); + + $extractor = new $class($tempFile); + $extractor->extract($extractDir); + + $this->config->setAppValue(Application::APP_ID, 'java_path', $extractDir . '/java-se-8u41-ri/bin/java' . $executableExtension); + } + + public function uninstallJava(): void { + $javaPath = $this->config->getAppValue(Application::APP_ID, 'java_path'); + if (!$javaPath) { + return; + } + $appFolder = $this->getAppRootFolder(); + $name = $appFolder->getName(); + // Remove prefix + $path = explode($name, $javaPath)[1]; + // Remove binary path + $path = explode(DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR, $path)[0]; + try { + $folder = $appFolder->get($path); + $folder->delete(); + } catch (NotFoundException $e) { + } + $this->config->deleteAppValue(Application::APP_ID, 'java_path'); + } + + public function installJSignPdf(): void { + if (!extension_loaded('zip')) { + throw new RuntimeException('Zip extension is not available'); + } + $extractDir = $this->getFullPath(); + + $tempFile = $this->tempManager->getTemporaryFile('.zip'); + $url = 'https://sourceforge.net/projects/jsignpdf/files/stable/JSignPdf%20' . JSignPdfHandler::VERSION . '/jsignpdf-' . JSignPdfHandler::VERSION . '.zip'; + + $this->download($url, 'JSignPdf', $tempFile); + + $zip = new ZIP($tempFile); + $zip->extract($extractDir); + + $fullPath = $extractDir . DIRECTORY_SEPARATOR. 'jsignpdf-' . JSignPdfHandler::VERSION . DIRECTORY_SEPARATOR. 'JSignPdf.jar'; + $this->config->setAppValue(Application::APP_ID, 'jsignpdf_jar_path', $fullPath); + } + + public function uninstallJSignPdf(): void { + $jsignpdJarPath = $this->config->getAppValue(Application::APP_ID, 'jsignpdf_jar_path'); + if (!$jsignpdJarPath) { + return; + } + $appFolder = $this->getAppRootFolder(); + $name = $appFolder->getName(); + // Remove prefix + $path = explode($name, $jsignpdJarPath)[1]; + // Remove sufix + $path = trim($path, DIRECTORY_SEPARATOR . 'JSignPdf.jar'); + try { + $folder = $appFolder->get($path); + $folder->delete(); + } catch (NotFoundException $e) { + } + $this->config->deleteAppValue(Application::APP_ID, 'jsignpdf_jar_path'); + } + + public function installCli(): void { + $folder = $this->getFolder(); + + if (PHP_OS_FAMILY === 'Windows') { + throw new \RuntimeException('LibreSign CLI do not work in Windows!'); + } elseif (PHP_OS_FAMILY === 'Darwin') { + $url = 'https://github.com/LibreSign/libresign-cli/releases/download/v0.0.4/libresign_0.0.4_Linux_arm64'; + } elseif (PHP_OS_FAMILY === 'Linux') { + if (PHP_INT_SIZE === 4) { + $url = 'https://github.com/LibreSign/libresign-cli/releases/download/v0.0.4/libresign_0.0.4_Linux_i386'; + } else { + $url = 'https://github.com/LibreSign/libresign-cli/releases/download/v0.0.4/libresign_0.0.4_Linux_x86_64'; + } + } + $file = $folder->newFile('libresign-cli'); + $fullPath = $this->getDataDir() . DIRECTORY_SEPARATOR . $file->getInternalPath(); + + $this->download($url, 'libresign-cli', $fullPath); + + if (PHP_OS_FAMILY !== 'Windows') { + chmod($fullPath, 0700); + } + + $this->config->setAppValue(Application::APP_ID, 'libresign_cli_path', $fullPath); + } + + public function uninstallCli(): void { + $libresignCliPath = $this->config->getAppValue(Application::APP_ID, 'libresign_cli_path'); + if (!$libresignCliPath) { + return; + } + $appFolder = $this->getAppRootFolder(); + $name = $appFolder->getName(); + // Remove prefix + $path = explode($name, $libresignCliPath)[1]; + try { + $folder = $appFolder->get($path); + $folder->delete(); + } catch (NotFoundException $e) { + } + $this->config->deleteAppValue(Application::APP_ID, 'libresign_cli_path'); + } + + public function installCfssl(): void { + $folder = $this->getFolder(); + + if (PHP_OS_FAMILY === 'Windows') { + $downloads = [ + [ + 'url' => 'https://github.com/cloudflare/cfssl/releases/download/v1.6.1/cfssl_1.6.1_windows_amd64.exe', + 'destination' => 'cfssl.exe', + ], + [ + 'url' => 'https://github.com/cloudflare/cfssl/releases/download/v1.6.1/cfssljson_1.6.1_windows_amd64.exe', + 'destination' => 'cfssljson.exe', + ], + ]; + } elseif (PHP_OS_FAMILY === 'Darwin') { + $downloads = [ + [ + 'url' => 'https://github.com/cloudflare/cfssl/releases/download/v1.6.1/cfssl_1.6.1_darwin_amd64', + 'destination' => 'cfssl', + ], + [ + 'url' => 'https://github.com/cloudflare/cfssl/releases/download/v1.6.1/cfssljson_1.6.1_darwin_amd64', + 'destination' => 'cfssljson', + ], + ]; + } else { + $downloads = [ + [ + 'url' => 'https://github.com/cloudflare/cfssl/releases/download/v1.6.1/cfssl_1.6.1_linux_amd64', + 'destination' => 'cfssl', + ], + [ + 'url' => 'https://github.com/cloudflare/cfssl/releases/download/v1.6.1/cfssljson_1.6.1_linux_amd64', + 'destination' => 'cfssljson', + ], + ]; + } + foreach ($downloads as $download) { + $file = $folder->newFile($download['destination']); + $fullPath = $this->getDataDir() . DIRECTORY_SEPARATOR . $file->getInternalPath(); + + $this->download($download['url'], $download['destination'], $fullPath); + + if (PHP_OS_FAMILY !== 'Windows') { + chmod($fullPath, 0700); + } + } + + $this->config->setAppValue(Application::APP_ID, 'cfssl_bin', 1); + } + + public function uninstallCfssl(): void { + $cfsslPath = $this->config->getAppValue(Application::APP_ID, 'cfssl_bin'); + if (!$cfsslPath) { + return; + } + $appFolder = $this->getAppRootFolder(); + $name = $appFolder->getName(); + // Remove prefix + $path = explode($name, $cfsslPath)[1]; + try { + $folder = $appFolder->get($path); + $folder->delete(); + } catch (NotFoundException $e) { + } + $this->config->deleteAppValue(Application::APP_ID, 'cfssl_bin'); + } + + protected function download(string $url, string $filename, string $path, ?string $md5 = '') { + if (php_sapi_name() === 'cli' && $this->output instanceof OutputInterface) { + $this->downloadCli($url, $filename, $path, $md5); + return; + } + $client = $this->clientService->newClient(); + try { + $client->get($url, [ + 'sink' => $path, + 'timeout' => 0 + ]); + } catch (\Exception $e) { + throw new LibresignException('Failure on download ' . $filename . " try again.\n" . $e->getMessage()); + } + if ($md5 && file_exists($path) && md5_file($path) !== $md5) { + throw new LibresignException('Failure on download ' . $filename . ' try again. Invalid md5.'); + } + } + + protected function downloadCli(string $url, string $filename, string $path, ?string $md5 = '') { + $client = $this->clientService->newClient(); + $progressBar = new ProgressBar($this->output); + $this->output->writeln('Downloading ' . $filename . '...'); + $progressBar->start(); + try { + $client->get($url, [ + 'sink' => $path, + 'timeout' => 0, + 'progress' => function ($downloadSize, $downloaded) use ($progressBar) { + $progressBar->setMaxSteps($downloadSize); + $progressBar->setProgress($downloaded); + }, + ]); + } catch (\Exception $e) { + $this->output->writeln('Failure on download ' . $filename . ' try again.'); + $this->output->writeln('' . $e->getMessage() . ''); + } + $progressBar->finish(); + $this->output->writeln(''); + $progressBar->finish(); + if ($md5 && file_exists($path) && md5_file($path) !== $md5) { + $this->output->writeln('Failure on download ' . $filename . ' try again'); + $this->output->writeln('Invalid MD5'); + } + } +} diff --git a/lib/Service/PdfParserService.php b/lib/Service/PdfParserService.php new file mode 100644 index 0000000000..1eaa389ce3 --- /dev/null +++ b/lib/Service/PdfParserService.php @@ -0,0 +1,58 @@ +systemConfig = $systemConfig; + $this->config = $config; + $this->installService = $installService; + $this->cliPath = $this->getLibesignCli(); + } + + private function getDataDir(): string { + return $this->systemConfig->getValue('datadirectory', \OC::$SERVERROOT . '/data/'); + } + + public function getMetadata(string $filePath): array { + $fullPath = $this->getDataDir() . $filePath; + $json = shell_exec($this->cliPath . ' info ' . $fullPath); + $array = json_decode($json, true); + $output = [ + 'p' => count($array['pages']), + ]; + foreach ($array['pages'] as $page) { + $output['d'][] = [ + 'w' => $page['width'], + 'h' => $page['height'], + ]; + } + return $output; + } + + private function getLibesignCli(): string { + $path = $this->config->getAppValue(Application::APP_ID, 'libresign_cli_path'); + if (!file_exists($path)) { + $this->installService->installCli(); + $path = $this->config->getAppValue(Application::APP_ID, 'libresign_cli_path'); + } + return $path; + } +} diff --git a/lib/Service/SignFileService.php b/lib/Service/SignFileService.php index d17da02f6d..6cf95efa52 100644 --- a/lib/Service/SignFileService.php +++ b/lib/Service/SignFileService.php @@ -15,7 +15,6 @@ use OCA\Libresign\Exception\LibresignException; use OCA\Libresign\Handler\Pkcs7Handler; use OCA\Libresign\Handler\Pkcs12Handler; -use OCA\Libresign\Handler\TCPDILibresign; use OCA\Libresign\Helper\JSActions; use OCA\Libresign\Helper\ValidateHelper; use OCP\Accounts\IAccountManager; @@ -91,6 +90,10 @@ class SignFileService { private $eventDispatcher; /** @var IURLGenerator */ private $urlGenerator; + /** @var IMimeTypeDetector */ + private $mimeTypeDetector; + /** @var PdfParserService */ + private $pdfParserService; /** @var ITempManager */ private $tempManager; /** @var FileUserEntity */ @@ -129,6 +132,7 @@ public function __construct( FileElementService $fileElementService, IEventDispatcher $eventDispatcher, IURLGenerator $urlGenerator, + PdfParserService $pdfParserService, IMimeTypeDetector $mimeTypeDetector, ITempManager $tempManager ) { @@ -156,6 +160,7 @@ public function __construct( $this->fileElementService = $fileElementService; $this->eventDispatcher = $eventDispatcher; $this->urlGenerator = $urlGenerator; + $this->pdfParserService = $pdfParserService; $this->mimeTypeDetector = $mimeTypeDetector; $this->tempManager = $tempManager; } @@ -232,9 +237,7 @@ public function getFileMetadata(\OCP\Files\Node $node): array { 'extension' => $node->getExtension(), ]; if ($metadata['extension'] === 'pdf') { - $pdf = new TCPDILibresign(); - $pdf->setSourceData($node->getContent()); - $metadata = array_merge($metadata, $pdf->getPagesMetadata()); + $metadata = $this->pdfParserService->getMetadata($node->getPath()); } return $metadata; } diff --git a/tests/Unit/Service/SignFileServiceTest.php b/tests/Unit/Service/SignFileServiceTest.php index 0764f21288..1e067aefee 100644 --- a/tests/Unit/Service/SignFileServiceTest.php +++ b/tests/Unit/Service/SignFileServiceTest.php @@ -14,6 +14,7 @@ use OCA\Libresign\Service\FileElementService; use OCA\Libresign\Service\FolderService; use OCA\Libresign\Service\MailService; +use OCA\Libresign\Service\PdfParserService; use OCA\Libresign\Service\SignFileService; use OCP\Accounts\IAccountManager; use OCP\App\IAppManager; @@ -84,6 +85,8 @@ final class SignFileServiceTest extends \OCA\Libresign\Tests\Unit\TestCase { private $eventDispatcher; /** @var IURLGenerator|MockObject */ private $urlGenerator; + /** @var PdfParserService */ + private $pdfParserService; /** @var IMimeTypeDetector|MockObject */ private $mimeTypeDetector; /** @var ITempManager|MockObject */ @@ -118,6 +121,7 @@ public function setUp(): void { $this->fileElementService = $this->createMock(FileElementService::class); $this->eventDispatcher = $this->createMock(IEventDispatcher::class); $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->pdfParserService = $this->createMock(PdfParserService::class); $this->mimeTypeDetector = $this->createMock(IMimeTypeDetector::class); $this->tempManager = $this->createMock(ITempManager::class); } @@ -148,6 +152,7 @@ private function getService(): SignFileService { $this->fileElementService, $this->eventDispatcher, $this->urlGenerator, + $this->pdfParserService, $this->mimeTypeDetector, $this->tempManager );