From 206afcf520d9107bc05da6e69dc17d6fa9a5998f Mon Sep 17 00:00:00 2001 From: Marcin Jakubowski Date: Thu, 31 Oct 2024 20:34:34 +0100 Subject: [PATCH 01/11] mostly automated changes to improve code strictness --- composer.json | 26 +- ecs.php | 23 ++ pdfcompare.php | 2 + phpstan.neon.dist | 20 ++ rector.php | 19 ++ src/PDFDoc.php | 267 +++++++++-------- src/PDFDocWithContents.php | 43 +-- src/PDFObject.php | 112 ++++--- src/PDFObjectParser.php | 110 +++---- src/PDFSignatureObject.php | 48 +-- src/PDFUtilFnc.php | 163 +++++----- src/helpers/Buffer.php | 55 ++-- src/helpers/CMS.php | 283 ++++++++--------- src/helpers/DependencyTreeObject.php | 70 +++-- src/helpers/StreamReader.php | 22 +- src/helpers/UUID.php | 45 +-- src/helpers/asn1.php | 180 +++++------ src/helpers/contentgeneration.php | 99 +++--- src/helpers/fpdfhelpers.php | 144 +++++---- src/helpers/helpers.php | 34 ++- src/helpers/mime.php | 360 +++++++++++----------- src/helpers/x509.php | 433 ++++++++++++++------------- src/pdfvalue/PDFValue.php | 71 +++-- src/pdfvalue/PDFValueHexString.php | 4 +- src/pdfvalue/PDFValueList.php | 21 +- src/pdfvalue/PDFValueObject.php | 41 ++- src/pdfvalue/PDFValueReference.php | 2 +- src/pdfvalue/PDFValueSimple.php | 13 +- src/pdfvalue/PDFValueString.php | 2 +- src/pdfvalue/PDFValueType.php | 4 +- 30 files changed, 1437 insertions(+), 1279 deletions(-) create mode 100644 ecs.php create mode 100644 phpstan.neon.dist create mode 100644 rector.php diff --git a/composer.json b/composer.json index bbe9288..8b3148c 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,8 @@ "keywords": ["php", "pdf", "signature", "sign", "electronic signature"], "type": "library", "license": "LGPL-3.0-or-later", + "minimum-stability": "stable", + "prefer-stable": true, "authors": [ { "name": "Carlos A.", @@ -11,11 +13,31 @@ } ], "require": { - "php": ">=7.4" + "php": ">=8.1", + "ext-curl": "*", + "ext-openssl": "*", + "ext-zlib": "*", + "psr/log": "^1|^2|^3" + }, + "require-dev": { + "lendable/composer-license-checker": "^1.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "rector/rector": "*", + "roave/security-advisories": "dev-latest", + "slevomat/coding-standard": "^8.0", + "symplify/easy-coding-standard": "^12.1", + "symplify/phpstan-rules": "^12" }, "autoload": { "psr-4": { "ddn\\sapp\\": "src/"} }, - "minimum-stability": "dev" + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + } } diff --git a/ecs.php b/ecs.php new file mode 100644 index 0000000..dd8d302 --- /dev/null +++ b/ecs.php @@ -0,0 +1,23 @@ +withPaths([ + __DIR__ . '/src', + ]) + + // add a single rule + ->withRules([ + NoUnusedImportsFixer::class, + ]) + ->withPreparedSets( + arrays: true, + comments: true, +// docblocks: true, + spaces: true, + namespaces: true, + ); diff --git a/pdfcompare.php b/pdfcompare.php index 1aa9d72..9b33bc0 100644 --- a/pdfcompare.php +++ b/pdfcompare.php @@ -23,6 +23,7 @@ use function ddn\sapp\helpers\p_debug_var; use function ddn\sapp\helpers\p_debug; use ddn\sapp\pdfvalue\PDFValueObject; +use function ddn\sapp\helpers\p_error; require_once('vendor/autoload.php'); @@ -48,6 +49,7 @@ $differences = $doc1->compare($doc2); foreach ($differences as $oid => $obj) { + p_error(get_debug_type($oid)); print($obj->to_pdf_entry()); } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..876584b --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,20 @@ +parameters: + level: 0 + paths: + - src/ + parallel: + jobSize: 2 + + reportUnmatchedIgnoredErrors: true + inferPrivatePropertyTypeFromConstructor: true + checkMissingCallableSignature: false + treatPhpDocTypesAsCertain: false + + ignoreErrors: + - "#Implicit array creation is not allowed#" + - "#Call to an undefined static method .*asn1::#" + - "#Construct empty\\(\\) is not allowed. Use more strict comparison.#" +# - + # identifier: missingType.iterableValue + # - + # identifier: missingType.generics diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..7f7f29d --- /dev/null +++ b/rector.php @@ -0,0 +1,19 @@ +withPaths([ + __DIR__ . '/src', + ]) + // uncomment to reach your current PHP version + ->withPhpSets(php80: true) + ->withImportNames() + ->withParallel(jobSize: 2) +// ->withTypeCoverageLevel(1) +// ->withDeadCodeLevel(1) +// ->withPreparedSets(deadCode: true, codeQuality: true, codingStyle: true, typeDeclarations: true) + ->withPreparedSets(typeDeclarations: true) + ; diff --git a/src/PDFDoc.php b/src/PDFDoc.php index 6f141c0..bf79951 100644 --- a/src/PDFDoc.php +++ b/src/PDFDoc.php @@ -19,66 +19,72 @@ along with this program. If not, see . */ -namespace ddn\sapp; - -use ddn\sapp\PDFBaseDoc; -use ddn\sapp\PDFBaseObject; -use ddn\sapp\PDFSignatureObject; -use ddn\sapp\pdfvalue\PDFValueObject; -use ddn\sapp\pdfvalue\PDFValueList; -use ddn\sapp\pdfvalue\PDFValueReference; -use ddn\sapp\pdfvalue\PDFValueType; -use ddn\sapp\pdfvalue\PDFValueSimple; -use ddn\sapp\pdfvalue\PDFValueHexString; -use ddn\sapp\pdfvalue\PDFValueString; -use ddn\sapp\helpers\CMS; -use ddn\sapp\helpers\x509; -use ddn\sapp\helpers\asn1; +namespace ddn\sapp; use DateTime; +use function ddn\sapp\helpers\_add_image; use ddn\sapp\helpers\Buffer; -use ddn\sapp\helpers\UUID; +use ddn\sapp\helpers\CMS; use ddn\sapp\helpers\DependencyTreeObject; -use const ddn\sapp\helpers\BLACKLIST; -use function ddn\sapp\helpers\references_in_object; - use function ddn\sapp\helpers\get_random_string; +use ddn\sapp\helpers\LoadHelpers; use function ddn\sapp\helpers\p_debug; -use function ddn\sapp\helpers\p_debug_var; use function ddn\sapp\helpers\p_error; use function ddn\sapp\helpers\p_warning; -use function ddn\sapp\helpers\_add_image; +use function ddn\sapp\helpers\references_in_object; use function ddn\sapp\helpers\timestamp_to_pdfdatestring; - +use ddn\sapp\helpers\UUID; +use ddn\sapp\pdfvalue\PDFValueHexString; +use ddn\sapp\pdfvalue\PDFValueList; +use ddn\sapp\pdfvalue\PDFValueObject; +use ddn\sapp\pdfvalue\PDFValueReference; +use ddn\sapp\pdfvalue\PDFValueSimple; +use ddn\sapp\pdfvalue\PDFValueString; // Loading the functions -use ddn\sapp\helpers\LoadHelpers; -if (!defined("ddn\\sapp\\helpers\\LoadHelpers")) +use Throwable; +if (! defined("ddn\\sapp\\helpers\\LoadHelpers")) new LoadHelpers; -if (!defined('__TMP_FOLDER')) +if (! defined('__TMP_FOLDER')) define('__TMP_FOLDER', sys_get_temp_dir()); // TODO: move the signature of documents to a new class (i.e. PDFDocSignable) // TODO: create a new class "PDFDocIncremental" class PDFDoc extends Buffer { - // The PDF version of the parsed file protected $_pdf_objects = []; + protected $_pdf_version_string = null; + protected $_pdf_trailer_object = null; + protected $_xref_position = 0; + protected $_xref_table = []; + protected $_max_oid = 0; + protected $_buffer = ""; + protected $_backup_state = []; + protected $_certificate = null; + protected $_signature_ltv_data = null; + protected $_signature_tsa = null; + protected $_appearance = null; + protected $_xref_table_version; + protected $_revisions; + protected $_metadata_name = null; + protected $_metadata_reason = null; + protected $_metadata_location = null; + protected $_metadata_contact_info = null; // Array of pages ordered by appearance in the final doc (i.e. index 0 is the first page rendered; index 1 is the second page rendered, etc.) @@ -89,7 +95,7 @@ class PDFDoc extends Buffer { protected $_pages_info = []; // Gets a new oid for a new object - protected function get_new_oid() { + protected function get_new_oid(): int|float { $this->_max_oid++; return $this->_max_oid; } @@ -98,7 +104,7 @@ protected function get_new_oid() { * Retrieve the number of pages in the document (not considered those pages that could be added by the user using this object or derived ones) * @return pagecount number of pages in the original document */ - public function get_page_count() { + public function get_page_count(): int { return count($this->_pages_info); } @@ -107,19 +113,22 @@ public function get_page_count() { * the state using function "pop_state". Many states can be stored, and they will be retrieved in reverse order * using pop_state */ - public function push_state() { + public function push_state(): void { $cloned_objects = []; foreach ($this->_pdf_objects as $oid => $object) { $cloned_objects[$oid] = clone $object; } - array_push($this->_backup_state, [ 'max_oid' => $this->_max_oid, 'pdf_objects' => $cloned_objects ]); + array_push($this->_backup_state, [ + 'max_oid' => $this->_max_oid, + 'pdf_objects' => $cloned_objects, + ]); } /** * Function that retrieves an stored state by means of function "push_state" * @return restored true if a previous state was restored; false if there was no stored state */ - public function pop_state() { + public function pop_state(): bool { if (count($this->_backup_state) > 0) { $state = array_pop($this->_backup_state); $this->_max_oid = $state['max_oid']; @@ -137,7 +146,7 @@ public function pop_state() { * otherwise only the object ids from the latest $depth versions will be considered * (if it is an incremental updated document) */ - public static function from_string($buffer, $depth = null) { + public static function from_string($buffer, $depth = null): false|\ddn\sapp\PDFDoc { $structure = PDFUtilFnc::acquire_structure($buffer, $depth); if ($structure === false) return false; @@ -174,19 +183,19 @@ public static function from_string($buffer, $depth = null) { return $pdfdoc; } - public function get_revision($rev_i) { + public function get_revision($rev_i): string { if ($rev_i === null) $rev_i = count($this->_revisions) - 1; if ($rev_i < 0) $rev_i = count($this->_revisions) + $rev_i - 1; - return substr($this->_buffer, 0, $this->_revisions[$rev_i]); + return substr((string) $this->_buffer, 0, $this->_revisions[$rev_i]); } /** * Function that builds the object list from the xref table */ - public function build_objects_from_xref() { + public function build_objects_from_xref(): void { foreach ($this->_xref_table as $oid => $obj) { $obj = $this->get_object($oid); $this->add_object($obj); @@ -244,16 +253,16 @@ public function get_indirect_object( $reference ) { * more recent object * @return obj the object retrieved (or false if not found) */ - public function get_object($oid, $original_version = false) { + public function get_object(int $oid, $original_version = false) { if ($original_version === true) { // Prioritizing the original version $object = PDFUtilFnc::find_object($this->_buffer, $this->_xref_table, $oid); if ($object === false) - $object = $this->_pdf_objects[$oid]??false; + $object = $this->_pdf_objects[$oid] ?? false; } else { // Prioritizing the new versions - $object = $this->_pdf_objects[$oid]??false; + $object = $this->_pdf_objects[$oid] ?? false; if ($object === false) $object = PDFUtilFnc::find_object($this->_buffer, $this->_xref_table, $oid); } @@ -268,25 +277,25 @@ public function get_object($oid, $original_version = false) { * @param rect the rectangle (in page-based coordinates) where the signature will appear in that page * @param imagefilename an image file name (or an image in a buffer, with symbol '@' prepended) that will be put inside the rect */ - public function set_signature_appearance($page_to_appear = 0, $rect_to_appear = [0, 0, 0, 0], $imagefilename = null) { + public function set_signature_appearance($page_to_appear = 0, $rect_to_appear = [0, 0, 0, 0], $imagefilename = null): void { $this->_appearance = [ "page" => $page_to_appear, "rect" => $rect_to_appear, - "image" => $imagefilename + "image" => $imagefilename, ]; } /** * Removes the settings of signature appearance (i.e. no signature will appear in the document) */ - public function clear_signature_appearance() { + public function clear_signature_appearance(): void { $this->_appearance = null; } /** * Removes the certificate for the signature (i.e. the document will not be signed) */ - public function clear_signature_certificate() { + public function clear_signature_certificate(): void { $this->_certificate = null; } @@ -335,7 +344,7 @@ public function set_signature_certificate($certfile, $certpass = null) { * @param $crlURIorFILE Crl filename/url to validate cert * @param $issuerURIorFILE issuer filename/url */ - public function set_ltv($ocspURI=null, $crlURIorFILE=null, $issuerURIorFILE=null) { + public function set_ltv($ocspURI = null, $crlURIorFILE = null, $issuerURIorFILE = null): void { $this->_signature_ltv_data['ocspURI'] = $ocspURI; $this->_signature_ltv_data['crlURIorFILE'] = $crlURIorFILE; $this->_signature_ltv_data['issuerURIorFILE'] = $issuerURIorFILE; @@ -347,7 +356,7 @@ public function set_ltv($ocspURI=null, $crlURIorFILE=null, $issuerURIorFILE=null * @param $tsauser the user for tsa service * @param $tsapass the password for tsa service */ - public function set_tsa($tsa, $tsauser = null, $tsapass = null) { + public function set_tsa($tsa, $tsauser = null, $tsapass = null): void { $this->_signature_tsa['host'] = $tsa; if ($tsauser && $tsapass) { $this->_signature_tsa['user'] = $tsauser; @@ -363,7 +372,7 @@ public function set_tsa($tsa, $tsauser = null, $tsapass = null) { * @param $contact * @return void */ - public function set_metadata_props($name = null, $reason = null, $location = null, $contact = null) + public function set_metadata_props($name = null, $reason = null, $location = null, $contact = null): void { $this->_metadata_name = $name; $this->_metadata_reason = $reason; @@ -392,7 +401,7 @@ public function set_metadata_props($name = null, $reason = null, $location = nul */ protected function _generate_signature_in_document() { $imagefilename = null; - $recttoappear = [ 0, 0, 0, 0]; + $recttoappear = [0, 0, 0, 0]; $pagetoappear = 0; if ($this->_appearance !== null) { @@ -417,16 +426,16 @@ protected function _generate_signature_in_document() { return p_error("invalid page"); // The objects to update - $updated_objects = [ ]; + $updated_objects = []; // Add the annotation to the page - if (!isset($page_obj["Annots"])) + if (! isset($page_obj["Annots"])) $page_obj["Annots"] = new PDFValueList(); $annots = &$page_obj["Annots"]; - $page_rotation = $page_obj["Rotate"]??new PDFValueSimple(0); + $page_rotation = $page_obj["Rotate"] ?? new PDFValueSimple(0); - if ((($referenced = $annots->get_object_referenced()) !== false) && (!is_array($referenced))) { + if ((($referenced = $annots->get_object_referenced()) !== false) && (! is_array($referenced))) { // It is an indirect object, so we need to update that object $newannots = $this->create_object( $this->get_object($referenced)->get_value() @@ -439,7 +448,8 @@ protected function _generate_signature_in_document() { } // Create the annotation object, annotate the offset and append the object - $annotation_object = $this->create_object([ + $annotation_object = $this->create_object( + [ "Type" => "/Annot", "Subtype" => "/Widget", "FT" => "/Sig", @@ -447,7 +457,7 @@ protected function _generate_signature_in_document() { "T" => new PDFValueString('Signature' . get_random_string()), "P" => new PDFValueReference($page_obj->get_oid()), "Rect" => $recttoappear, - "F" => 132 // TODO: check this value + "F" => 132, // TODO: check this value ] ); @@ -456,9 +466,9 @@ protected function _generate_signature_in_document() { if ($this->_certificate !== null) { // Perform signature test to get signature size to define __SIGNATURE_MAX_LENGTH p_debug(" ########## PERFORM SIGNATURE LENGTH CHECK ##########\n"); - $CMS = new helpers\CMS; + $CMS = new CMS; $CMS->signature_data['signcert'] = $this->_certificate['cert']; - $CMS->signature_data['extracerts'] = $this->_certificate['extracerts']??null; + $CMS->signature_data['extracerts'] = $this->_certificate['extracerts'] ?? null; $CMS->signature_data['hashAlgorithm'] = 'sha256'; $CMS->signature_data['privkey'] = $this->_certificate['pkey']; $CMS->signature_data['tsa'] = $this->_signature_tsa; @@ -493,10 +503,10 @@ protected function _generate_signature_in_document() { // Get the page height, to change the coordinates system (up to down) $pagesize = $this->get_page_size($pagetoappear); - $pagesize = explode(" ", $pagesize[0]->val()); + $pagesize = explode(" ", (string) $pagesize[0]->val()); $pagesize_h = floatval("" . $pagesize[3]) - floatval("" . $pagesize[1]); - $bbox = [ 0, 0, $recttoappear[2] - $recttoappear[0], $recttoappear[3] - $recttoappear[1]]; + $bbox = [0, 0, $recttoappear[2] - $recttoappear[0], $recttoappear[3] - $recttoappear[1]]; $form_object = $this->create_object([ "BBox" => $bbox, "Subtype" => "/Form", @@ -504,26 +514,28 @@ protected function _generate_signature_in_document() { "Group" => [ 'Type' => '/Group', 'S' => '/Transparency', - 'CS' => '/DeviceRGB' - ] + 'CS' => '/DeviceRGB', + ], ]); $container_form_object = $this->create_object([ "BBox" => $bbox, "Subtype" => "/Form", "Type" => "/XObject", - "Resources" => [ "XObject" => [ - "n0" => new PDFValueSimple(""), - "n2" => new PDFValueSimple("") - ] ] - ]); + "Resources" => [ + "XObject" => [ + "n0" => new PDFValueSimple(""), + "n2" => new PDFValueSimple(""), + ], + ], + ]); $container_form_object->set_stream("q 1 0 0 1 0 0 cm /n0 Do Q\nq 1 0 0 1 0 0 cm /n2 Do Q\n", false); $layer_n0 = $this->create_object([ - "BBox" => [ 0.0, 0.0, 100.0, 100.0 ], + "BBox" => [0.0, 0.0, 100.0, 100.0], "Subtype" => "/Form", "Type" => "/XObject", - "Resources" => new PDFValueObject() + "Resources" => new PDFValueObject(), ]); // Add the same structure than Acrobat Reader @@ -533,10 +545,10 @@ protected function _generate_signature_in_document() { "BBox" => $bbox, "Subtype" => "/Form", "Type" => "/XObject", - "Resources" => new PDFValueObject() + "Resources" => new PDFValueObject(), ]); - $result = _add_image([$this, "create_object"], $imagefilename, $bbox[0], $bbox[1], $bbox[2], $bbox[3], $page_rotation->val()); + $result = _add_image($this->create_object(...), $imagefilename, $bbox[0], $bbox[1], $bbox[2], $bbox[3], $page_rotation->val()); if ($result === false) return p_error("could not add the image"); @@ -548,28 +560,30 @@ protected function _generate_signature_in_document() { $form_object['Resources'] = new PDFValueObject([ "XObject" => [ - "FRM" => new PDFValueReference($container_form_object->get_oid()) - ] + "FRM" => new PDFValueReference($container_form_object->get_oid()), + ], ]); $form_object->set_stream("/FRM Do", false); // Set the signature appearance field to the form object - $annotation_object["AP"] = [ "N" => new PDFValueReference($form_object->get_oid())]; - $annotation_object["Rect"] = [ $recttoappear[0], $pagesize_h - $recttoappear[1], $recttoappear[2], $pagesize_h - $recttoappear[3] ]; + $annotation_object["AP"] = [ + "N" => new PDFValueReference($form_object->get_oid()), + ]; + $annotation_object["Rect"] = [$recttoappear[0], $pagesize_h - $recttoappear[1], $recttoappear[2], $pagesize_h - $recttoappear[3]]; } - if (!$newannots->push(new PDFValueReference($annotation_object->get_oid()))) + if (! $newannots->push(new PDFValueReference($annotation_object->get_oid()))) return p_error("Could not update the page where the signature has to appear"); $page_obj["Annots"] = new PDFValueReference($newannots->get_oid()); array_push($updated_objects, $page_obj); // AcroForm may be an indirect object - if (!isset($root_obj["AcroForm"])) + if (! isset($root_obj["AcroForm"])) $root_obj["AcroForm"] = new PDFValueObject(); $acroform = &$root_obj["AcroForm"]; - if ((($referenced = $acroform->get_object_referenced()) !== false) && (!is_array($referenced))) { + if ((($referenced = $acroform->get_object_referenced()) !== false) && (! is_array($referenced))) { $acroform = $this->get_object($referenced); array_push($updated_objects, $acroform); } else { @@ -578,11 +592,11 @@ protected function _generate_signature_in_document() { // Add the annotation to the interactive form $acroform["SigFlags"] = 3; - if (!isset($acroform['Fields'])) + if (! isset($acroform['Fields'])) $acroform['Fields'] = new PDFValueList(); // Add the annotation object to the interactive form - if (!$acroform['Fields']->push(new PDFValueReference($annotation_object->get_oid()))) { + if (! $acroform['Fields']->push(new PDFValueReference($annotation_object->get_oid()))) { return p_error("could not create the signature field"); } @@ -600,7 +614,7 @@ protected function _generate_signature_in_document() { * @param date a DateTime object that contains the date to be set; null to set "now" * @return ok true if the date could be set; false otherwise */ - protected function update_mod_date(\DateTime $date = null) { + protected function update_mod_date(DateTime $date = null) { // First of all, we are searching for the root object (which should be in the trailer) $root = $this->_pdf_trailer_object["Root"]; @@ -612,17 +626,17 @@ protected function update_mod_date(\DateTime $date = null) { return p_error("invalid root object"); if ($date === null) - $date = new \DateTime(); + $date = new DateTime(); // Update the xmp metadata if exists if (isset($root_obj["Metadata"])) { $metadata = $root_obj["Metadata"]; - if ((($referenced = $metadata->get_object_referenced()) !== false) && (!is_array($referenced))) { + if ((($referenced = $metadata->get_object_referenced()) !== false) && (! is_array($referenced))) { $metadata = $this->get_object($referenced); $metastream = $metadata->get_stream(); - $metastream = preg_replace('/([^<]*)<\/xmp:ModifyDate>/', '' . $date->format("c") . '', $metastream); - $metastream = preg_replace('/([^<]*)<\/xmp:MetadataDate>/', '' . $date->format("c") . '', $metastream); - $metastream = preg_replace('/([^<]*)<\/xmpMM:InstanceID>/', 'uuid:' . UUID::v4() . '', $metastream); + $metastream = preg_replace('/([^<]*)<\/xmp:ModifyDate>/', '' . $date->format("c") . '', (string) $metastream); + $metastream = preg_replace('/([^<]*)<\/xmp:MetadataDate>/', '' . $date->format("c") . '', (string) $metastream); + $metastream = preg_replace('/([^<]*)<\/xmpMM:InstanceID>/', 'uuid:' . UUID::v4() . '', (string) $metastream); $metadata->set_stream($metastream, false); $this->add_object($metadata); } @@ -665,8 +679,8 @@ public function get_version() { * @param version the version of the PDF document (it shall have the form PDF-1.x) * @return correct true if the version had the proper form; false otherwise */ - public function set_version($version) { - if (preg_match("/PDF-1.\[0-9\]/", $version) !== 1) { + public function set_version($version): bool { + if (preg_match("/PDF-1.\[0-9\]/", (string) $version) !== 1) { return false; } $this->_pdf_version_string = $version; @@ -692,7 +706,7 @@ public function create_object($value = [], $class = "ddn\\sapp\\PDFObject", $aut * @param pdf_object the object to add to the document * @return true if the object was added; false otherwise (e.g. already exists an object of a greater generation) */ - public function add_object(PDFObject $pdf_object) { + public function add_object(PDFObject $pdf_object): bool { $oid = $pdf_object->get_oid(); if (isset($this->_pdf_objects[$oid])) { @@ -716,9 +730,9 @@ public function add_object(PDFObject $pdf_object) { * consider only the new ones (false) * @return xref_data [ the text corresponding to the objects, array of offsets for each object ] */ - protected function _generate_content_to_xref($rebuild = false) { + protected function _generate_content_to_xref($rebuild = false): array { if ($rebuild === true) { - $result = new Buffer("%$this->_pdf_version_string" . __EOL); + $result = new Buffer("%$this->_pdf_version_string" . __EOL); } else { $result = new Buffer($this->_buffer); } @@ -732,7 +746,7 @@ protected function _generate_content_to_xref($rebuild = false) { if ($rebuild === true) { for ($i = 0; $i <= $this->_max_oid; $i++) { - if (($object = $this->get_object($i)) === false) continue; + if (($object = $this->get_object($i)) === false) continue; $result->data($object->to_pdf_entry()); $offsets[$i] = $offset; @@ -746,7 +760,7 @@ protected function _generate_content_to_xref($rebuild = false) { } } - return [ $result, $offsets ]; + return [$result, $offsets]; } /** @@ -754,7 +768,7 @@ protected function _generate_content_to_xref($rebuild = false) { * @param rebuild whether we are rebuilding the whole xref table or not (in case of incremental versions, we should use "false") * @return buffer a buffer that contains a pdf dumpable document */ - public function to_pdf_file_b($rebuild = false) : Buffer { + public function to_pdf_file_b($rebuild = false): Buffer { // We made no updates, so return the original doc if (($rebuild === false) && (count($this->_pdf_objects) === 0) && ($this->_certificate === null) && ($this->_appearance === null)) return new Buffer($this->_buffer); @@ -775,12 +789,12 @@ public function to_pdf_file_b($rebuild = false) : Buffer { } // Generate the first part of the document - [ $_doc_to_xref, $_obj_offsets ] = $this->_generate_content_to_xref($rebuild); + [$_doc_to_xref, $_obj_offsets] = $this->_generate_content_to_xref($rebuild); $xref_offset = $_doc_to_xref->size(); if ($_signature !== null) { $_obj_offsets[$_signature->get_oid()] = $_doc_to_xref->size(); - $xref_offset += strlen($_signature->to_pdf_entry()); + $xref_offset += strlen((string) $_signature->to_pdf_entry()); } $doc_version_string = str_replace("PDF-", "", $this->_pdf_version_string); @@ -813,16 +827,16 @@ public function to_pdf_file_b($rebuild = false) : Buffer { $xref = PDFUtilFnc::build_xref_1_5($_obj_offsets); // Set the parameters for the trailer - $trailer["Index"] = explode(" ", $xref["Index"]); + $trailer["Index"] = explode(" ", (string) $xref["Index"]); $trailer["W"] = $xref["W"]; $trailer["Size"] = $this->_max_oid + 1; $trailer["Type"] = "/XRef"; // Not needed to generate new IDs, as in metadata the IDs will be set // $ID1 = md5("" . (new \DateTime())->getTimestamp() . "-" . $this->_xref_position . $xref["stream"]); - $ID2 = md5("" . (new \DateTime())->getTimestamp() . "-" . $this->_xref_position . $this->_pdf_trailer_object); + $ID2 = md5("" . (new DateTime())->getTimestamp() . "-" . $this->_xref_position . $this->_pdf_trailer_object); // $trailer["ID"] = [ new PDFValueHexString($ID1), new PDFValueHexString($ID2) ]; - $trailer["ID"] = [ $trailer["ID"][0], new PDFValueHexString(strtoupper($ID2)) ]; + $trailer["ID"] = [$trailer["ID"][0], new PDFValueHexString(strtoupper($ID2))]; // We are not using predictors nor encoding if (isset($trailer["DecodeParms"])) unset($trailer["DecodeParms"]); @@ -841,7 +855,7 @@ public function to_pdf_file_b($rebuild = false) : Buffer { // And generate the part of the document related to the xref $_doc_from_xref = new Buffer($trailer->to_pdf_entry()); - $_doc_from_xref->data("startxref" . __EOL . "$xref_offset" . __EOL ."%%EOF" . __EOL); + $_doc_from_xref->data("startxref" . __EOL . "$xref_offset" . __EOL . "%%EOF" . __EOL); } else { p_debug("generating xref using classic xref...trailer"); $xref_content = PDFUtilFnc::build_xref($_obj_offsets); @@ -877,7 +891,7 @@ public function to_pdf_file_b($rebuild = false) : Buffer { $cms->signature_data['hashAlgorithm'] = 'sha256'; $cms->signature_data['privkey'] = $certificate['pkey']; $cms->signature_data['extracerts'] = $extracerts; - $cms->signature_data['signcert'] = $certificate['cert']; + $cms->signature_data['signcert'] = $certificate['cert']; $cms->signature_data['ltv'] = $_signature->get_ltv(); $cms->signature_data['tsa'] = $_signature->get_tsa(); $signature_contents = $cms->pkcs7_sign($_signable_document->get_raw()); @@ -958,7 +972,7 @@ public function get_page_size($i) { } // The page has not been found - if (($pageinfo === false) || (!isset($pageinfo['size']))) + if (($pageinfo === false) || (! isset($pageinfo['size']))) return false; return $pageinfo['size']; @@ -971,7 +985,7 @@ public function get_page_size($i) { * @return pages the ordered list of page ids corresponding to object oid, or false if any of the kid objects * is not of type page or pages. */ - protected function _get_page_info($oid, $info = []) { + protected function _get_page_info(int $oid, array $info = []) { $object = $this->get_object($oid); if ($object === false) return p_error("could not get information about the page"); @@ -999,7 +1013,10 @@ protected function _get_page_info($oid, $info = []) { case "Page": if (isset($object['MediaBox'])) $info['size'] = $object['MediaBox']->val(); - return [ [ 'id' => $oid, 'info' => $info ] ]; + return [[ + 'id' => $oid, + 'info' => $info, + ]]; default: return false; } @@ -1027,12 +1044,12 @@ protected function _acquire_pages_info() { p_warning("root object does not exist, so cannot get information about pages"); } - /** * This function compares this document with other document, object by object. The idea is to compare the objects with the same oid in the * different documents, checking field by field; it does not take into account the streams. + * @return PDFObject[] */ - public function compare($other) { + public function compare($other): array { $other_objects = []; foreach ($other->get_object_iterator(false) as $oid => $object) { $other_objects[$oid] = $object; @@ -1058,8 +1075,9 @@ public function compare($other) { /** * Obtains the tree of objects in the PDF Document. The result is an array of DependencyTreeObject objects (indexed by the oid), where * each element has a set of children that can be retrieved using the iterator (foreach $o->children() as $oid => $object ...) + * @return DependencyTreeObject[] */ - public function get_object_tree() { + public function get_object_tree(): array { // Prepare the return value $objects = []; @@ -1088,7 +1106,7 @@ public function get_object_tree() { $references = $val->get_object_referenced(); if ($references === false) continue; - if (!is_array($references)) $references = [ $references ]; + if (! is_array($references)) $references = [$references]; } // p_debug("$oid references " . implode(", ", $references)); @@ -1113,7 +1131,7 @@ public function get_object_tree() { // Remove those objects that are child of other objects from the top of the tree foreach ($objects as $oid => $t_object) { - if (($t_object->is_child > 0) || (in_array($t_object->info, [ "/XRef", "/ObjStm"] ))) { + if (($t_object->is_child > 0) || (in_array($t_object->info, ["/XRef", "/ObjStm"] ))) { if (! in_array($oid, $xref_children)) unset($objects[$oid]); } @@ -1122,12 +1140,11 @@ public function get_object_tree() { return $objects; } - /** * Retrieve the signatures in the document * @return array of signatures in the original document */ - public function get_signatures() { + public function get_signatures(): array { // Prepare the return value $signatures = []; @@ -1142,20 +1159,22 @@ public function get_signatures() { if (! is_array($o_value) || ! isset($o_value['Type'])) continue; if ($o_value['Type']->val() != 'Sig') continue; - $signature = ['content' => $o_value['Contents']->val()]; + $signature = [ + 'content' => $o_value['Contents']->val(), + ]; try { - $cert=[]; + $cert = []; openssl_pkcs7_read( "-----BEGIN CERTIFICATE-----\n" - . chunk_split(base64_encode(hex2bin($signature['content'])), 64, "\n") + . chunk_split(base64_encode(hex2bin((string) $signature['content'])), 64, "\n") . "-----END CERTIFICATE-----\n", - $cert + $cert ); $signature += openssl_x509_parse($cert[0] ?? '') ?: []; - } catch (\Throwable $e) {} + } catch (Throwable) {} $signatures[] = $signature; } @@ -1167,11 +1186,10 @@ public function get_signatures() { * Retrieve the number of signatures in the document * @return int signatures number in the original document */ - public function get_signature_count() { + public function get_signature_count(): int { return count($this->get_signatures()); } - /** * Generates a new document that is the result of signing the current * document @@ -1191,10 +1209,9 @@ public function get_signature_count() { public function sign_document($certfile, $password = null, $page_to_appear = 0, $imagefilename = null, $px = 0, $py = 0, $size = null) { if ($imagefilename !== null) { - $position = [ ]; $imagesize = @getimagesize($imagefilename); if ($imagesize === false) { - return p_warning("failed to open the image $image"); + return p_warning("failed to open the image $imagesize"); } if (($page_to_appear < 0) || ($page_to_appear > $this->get_page_count() - 1)) { return p_error("invalid page number"); @@ -1204,13 +1221,13 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, return p_error("failed to get page size"); } - $pagesize = explode(" ", $pagesize[0]->val()); + $pagesize = explode(" ", (string) $pagesize[0]->val()); // Get the bounding box for the image - $p_x = intval("". $pagesize[0]); - $p_y = intval("". $pagesize[1]); - $p_w = intval("". $pagesize[2]) - $p_x; - $p_h = intval("". $pagesize[3]) - $p_y; + $p_x = intval("" . $pagesize[0]); + $p_y = intval("" . $pagesize[1]); + $p_w = intval("" . $pagesize[2]) - $p_x; + $p_h = intval("" . $pagesize[3]) - $p_y; // Add the position for the image $p_x = $p_x + $px; @@ -1235,14 +1252,14 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, return p_error("invalid size format"); } - $i_w = $width===null?$imagesize[0]:$width; - $i_h = $height===null?$imagesize[1]:$height; + $i_w = $width ?? $imagesize[0]; + $i_h = $height ?? $imagesize[1]; // Set the image appearance and the certificate file - $this->set_signature_appearance($page_to_appear, [ $p_x, $p_y, $p_x + $i_w, $p_y + $i_h ], $imagefilename); + $this->set_signature_appearance($page_to_appear, [$p_x, $p_y, $p_x + $i_w, $p_y + $i_h], $imagefilename); } - if (!$this->set_signature_certificate($certfile, $password)) { + if (! $this->set_signature_certificate($certfile, $password)) { return p_error("the certificate or the signature is not valid"); } diff --git a/src/PDFDocWithContents.php b/src/PDFDocWithContents.php index 530a50a..69fc2fd 100644 --- a/src/PDFDocWithContents.php +++ b/src/PDFDocWithContents.php @@ -20,27 +20,14 @@ */ namespace ddn\sapp; - -use ddn\sapp\PDFDoc; -use ddn\sapp\PDFBaseObject; -use ddn\sapp\pdfvalue\PDFValueObject; -use ddn\sapp\pdfvalue\PDFValueList; -use ddn\sapp\pdfvalue\PDFValueReference; -use ddn\sapp\pdfvalue\PDFValueType; -use ddn\sapp\pdfvalue\PDFValueSimple; -use ddn\sapp\pdfvalue\PDFValueHexString; -use ddn\sapp\pdfvalue\PDFValueString; -use ddn\sapp\helpers\Buffer; - use function ddn\sapp\helpers\get_random_string; -use function ddn\sapp\helpers\p_debug; use function ddn\sapp\helpers\p_error; use function ddn\sapp\helpers\p_warning; -use function ddn\sapp\helpers\p_debug_var; -use function ddn\sapp\helpers\_add_image; +use ddn\sapp\pdfvalue\PDFValueList; +use ddn\sapp\pdfvalue\PDFValueObject; +use ddn\sapp\pdfvalue\PDFValueReference; class PDFDocWithContents extends PDFDoc { - const T_STANDARD_FONTS = [ "Times-Roman", "Times-Bold", @@ -55,7 +42,7 @@ class PDFDocWithContents extends PDFDoc { "Helvetica-Oblique", "Helvetica-BoldOblique", "Symbol", - "ZapfDingbats" + "ZapfDingbats", ]; /** @@ -78,7 +65,7 @@ public function add_text($page_to_appear, $text, $x, $y, $params = []) { "font" => "Helvetica", "size" => 24, "color" => "#000000", - "angle" => 0 + "angle" => 0, ]; $params = array_merge($default, $params); @@ -111,7 +98,7 @@ public function add_text($page_to_appear, $text, $x, $y, $params = []) { $pagesize_h = floatval("" . $pagesize[3]) - floatval("" . $pagesize[1]); $angle = $params["angle"]; - $angle *= M_PI/180; + $angle *= M_PI / 180; $c = cos($angle); $s = sin($angle); $cx = $x; @@ -121,7 +108,7 @@ public function add_text($page_to_appear, $text, $x, $y, $params = []) { $rotate_command = sprintf("%.5F %.5F %.5F %.5F %.2F %.2F cm 1 0 0 1 %.2F %.2F cm", $c, $s, -$s, $c, $cx, $cy, -$cx, -$cy); $text_command = "BT "; - $text_command .= "/$font_id " . $params['size'] . " Tf "; + $text_command .= "/$font_id " . $params['size'] . " Tf "; $text_command .= sprintf("%.2f %.2f Td ", $x, $pagesize_h - $y); // Ubicar en x, y $text_command .= sprintf("(%s) Tj ", $text); $text_command .= "ET "; @@ -130,11 +117,11 @@ public function add_text($page_to_appear, $text, $x, $y, $params = []) { if ($color[0] === '#') { $colorvalid = true; $r = null; - switch (strlen($color)) { + switch (strlen((string) $color)) { case 4: $color = "#" . $color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3]; case 7: - list($r, $g, $b) = sscanf($color, "#%02x%02x%02x"); + [$r, $g, $b] = sscanf($color, "#%02x%02x%02x"); break; default: p_error("please use html-like colors (e.g. #ffbbaa)"); @@ -146,7 +133,7 @@ public function add_text($page_to_appear, $text, $x, $y, $params = []) { if ($angle !== 0) $text_command = " q $rotate_command $text_command Q"; - + $data .= $text_command; $contents_obj->set_stream($data, false); @@ -168,7 +155,7 @@ public function add_text($page_to_appear, $text, $x, $y, $params = []) { * @param w the width of the image * @param w the height of the image */ - public function add_image($page_obj, $filename, $x=0, $y=0, $w=0, $h=0) { + public function add_image($page_obj, $filename, $x = 0, $y = 0, $w = 0, $h = 0) { // TODO: maybe we can create a function that "adds content to a page", and that // function will search for the content field and merge the resources, if @@ -181,7 +168,7 @@ public function add_image($page_obj, $filename, $x=0, $y=0, $w=0, $h=0) { if ($page_obj === false) return p_error("invalid page"); - + // Get the page height, to change the coordinates system (up to down) $pagesize = $this->get_page_size($page_obj); $pagesize_h = floatval("" . $pagesize[3]) - floatval("" . $pagesize[1]); @@ -192,10 +179,10 @@ public function add_image($page_obj, $filename, $x=0, $y=0, $w=0, $h=0) { // Get the resources for the page $resources_obj = $this->get_indirect_object($page_obj['Resources']); - if (!isset($resources_obj['ProcSet'])) + if (! isset($resources_obj['ProcSet'])) $resources_obj['ProcSet'] = new PDFValueList(['/PDF']); $resources_obj['ProcSet']->push(['/ImageB', '/ImageC', '/ImageI']); - if (!isset($resources_obj['XObject'])) + if (! isset($resources_obj['XObject'])) $resources_obj['XObject'] = new PDFValueObject(); $resources_obj['XObject'][$info['i']] = new PDFValueReference($images_objects[0]->get_oid()); @@ -219,7 +206,7 @@ public function add_image($page_obj, $filename, $x=0, $y=0, $w=0, $h=0) { $page_obj['Group'] = new PDFValueObject([ 'Type' => '/Group', 'S' => '/Transparency', - 'CS' => '/DeviceRGB' + 'CS' => '/DeviceRGB', ]); $this->add_object($page_obj); } diff --git a/src/PDFObject.php b/src/PDFObject.php index c3233ba..c240cfd 100644 --- a/src/PDFObject.php +++ b/src/PDFObject.php @@ -21,15 +21,15 @@ namespace ddn\sapp; -use ddn\sapp\pdfvalue\PDFValueObject; -use ddn\sapp\pdfvalue\PDFValueSimple; use \ArrayAccess; - use ddn\sapp\helpers\Buffer; - -// Loading the functions use ddn\sapp\helpers\LoadHelpers; -if (!defined("ddn\\sapp\\helpers\\LoadHelpers")) +use ddn\sapp\pdfvalue\PDFValueObject; +use ddn\sapp\pdfvalue\PDFValueSimple; +use ReturnTypeWillChange; +// Loading the functions +use Stringable; +if (! defined("ddn\\sapp\\helpers\\LoadHelpers")) new LoadHelpers; use function ddn\sapp\helpers\p_debug; @@ -38,24 +38,30 @@ use function ddn\sapp\helpers\p_warning; // The character used to end lines -if (!defined('__EOL')) +if (! defined('__EOL')) define('__EOL', "\n"); /** - * Class to gather the information of a PDF object: the OID, the definition and the stream. The purpose is to + * Class to gather the information of a PDF object: the OID, the definition and the stream. The purpose is to * ease the generation of the PDF entries for an individual object. */ -class PDFObject implements ArrayAccess { +class PDFObject implements ArrayAccess, Stringable +{ protected static $_revisions; + protected static $_xref_table_version; - protected $_oid = null; protected $_stream = null; + protected $_value = null; + protected $_generation; - - public function __construct($oid, $value = null, $generation = 0) { + public function __construct( + protected $_oid, + $value = null, + $generation = 0 + ) { if ($generation !== 0) p_warning("Objects of non-zero generation are not fully checked... please double check your document and (if possible) please send examples via issues to https://github.com/dealfonso/sapp/issues/"); @@ -71,8 +77,6 @@ public function __construct($oid, $value = null, $generation = 0) { } $value = $obj; } - - $this->_oid = $oid; $this->_value = $value; $this->_generation = $generation; } @@ -81,7 +85,7 @@ public function get_keys() { return $this->_value->get_keys(); } - public function set_oid($oid) { + public function set_oid($oid): void { $this->_oid = $oid; } @@ -89,16 +93,18 @@ public function get_generation() { return $this->_generation; } - public function __toString() { - return "$this->_oid 0 obj\n" . + public function __toString(): string { + return "$this->_oid 0 obj\n" . "$this->_value\n" . - ($this->_stream === null?"": + ( + $this->_stream === null ? "" : "stream\n" . - '...' . + '...' . "\nendstream\n" ) . "endobj\n"; } + /** * Converts the object to a well-formed PDF entry with a form like * 1 0 obj @@ -109,16 +115,18 @@ public function __toString() { * endobj * @return pdfentry a string that contains the PDF entry */ - public function to_pdf_entry() { - return "$this->_oid 0 obj" . __EOL . + public function to_pdf_entry(): string { + return "$this->_oid 0 obj" . __EOL . "$this->_value" . __EOL . - ($this->_stream === null?"": + ( + $this->_stream === null ? "" : "stream\r\n" . - $this->_stream . + $this->_stream . __EOL . "endstream" . __EOL ) . "endobj" . __EOL; } + /** * Gets the object ID * @return oid the object id @@ -126,6 +134,7 @@ public function to_pdf_entry() { public function get_oid() { return $this->_oid; } + /** * Gets the definition of the object (a PDFValue object) * @return value the definition of the object @@ -133,7 +142,8 @@ public function get_oid() { public function get_value() { return $this->_value; } - protected static function FlateDecode($_stream, $params) { + + protected static function FlateDecode($_stream, array $params) { switch ($params["Predictor"]->get_int()) { case 1: return $_stream; @@ -166,7 +176,7 @@ protected static function FlateDecode($_stream, $params) { $columns = $params['Columns']->get_int(); $row_len = $columns + 1; - $stream_len = strlen($_stream); + $stream_len = strlen((string) $_stream); // The previous row is zero $data_prev = str_pad("", $columns, chr(0)); @@ -177,7 +187,7 @@ protected static function FlateDecode($_stream, $params) { $filter_byte = ord($_stream[$pos_i++]); // Get the current row - $data = substr($_stream, $pos_i, $columns); + $data = substr((string) $_stream, $pos_i, $columns); $pos_i += strlen($data); // Zero pad, in case that the content is not paired @@ -185,18 +195,18 @@ protected static function FlateDecode($_stream, $params) { // Depending on the filter byte of the row, we should unpack on one way or another switch ($filter_byte) { - case 0: + case 0: break; - case 1: + case 1: for ($i = 1; $i < $columns; $i++) - $data[$i] = ($data[$i] + $data[$i-1]) % 256; + $data[$i] = ($data[$i] + $data[$i - 1]) % 256; break; - case 2: + case 2: for ($i = 0; $i < $columns; $i++) { $data[$i] = chr((ord($data[$i]) + ord($data_prev[$i])) % 256); } break; - default: + default: return p_error("Unsupported stream"); } @@ -219,15 +229,15 @@ public function get_stream($raw = true) { if (isset($this->_value['Filter'])) { switch ($this->_value['Filter']) { case '/FlateDecode': - $DecodeParams = $this->_value['DecodeParms']??[]; + $DecodeParams = $this->_value['DecodeParms'] ?? []; $params = [ - "Columns" => $DecodeParams['Columns']??new PDFValueSimple(0), - "Predictor" => $DecodeParams['Predictor']??new PDFValueSimple(1), - "BitsPerComponent" => $DecodeParams['BitsPerComponent']??new PDFValueSimple(8), - "Colors" => $DecodeParams['Colors']??new PDFValueSimple(1) + "Columns" => $DecodeParams['Columns'] ?? new PDFValueSimple(0), + "Predictor" => $DecodeParams['Predictor'] ?? new PDFValueSimple(1), + "BitsPerComponent" => $DecodeParams['BitsPerComponent'] ?? new PDFValueSimple(8), + "Colors" => $DecodeParams['Colors'] ?? new PDFValueSimple(1), ]; return self::FlateDecode(gzuncompress($this->_stream), $params); - + break; default: return p_error('unknown compression method ' . $this->_value['Filter']); @@ -235,11 +245,12 @@ public function get_stream($raw = true) { } return $this->_stream; } + /** * Sets the stream for the object (overwrites a previous existing stream) * @param stream the stream for the object */ - public function set_stream($stream, $raw = true) { + public function set_stream($stream, $raw = true): void { if ($raw === true) { $this->_stream = $stream; return; @@ -247,55 +258,58 @@ public function set_stream($stream, $raw = true) { if (isset($this->_value['Filter'])) { switch ($this->_value['Filter']) { case '/FlateDecode': - $stream = gzcompress($stream); + $stream = gzcompress((string) $stream); break; default: p_error('unknown compression method ' . $this->_value['Filter']); } } - $this->_value['Length'] = strlen($stream); + $this->_value['Length'] = strlen((string) $stream); $this->_stream = $stream; - } + } + /** * The next functions enble to make use of this object in an array-like manner, * using the name of the fields as positions in the array. It is useful is the * value is of type PDFValueObject or PDFValueList, using indexes */ - - /** + /** * Sets the value of the field offset, using notation $obj['field'] = $value * @param field the field to set the value * @param value the value to set * @return void */ - public function offsetSet($field, $value) : void { + public function offsetSet($field, $value): void { $this->_value[$field] = $value; } + /** * Checks whether the field exists in the object or not (or if the index exists * in the list) * @param field the field to check wether exists or not * @return exists true if the field exists; false otherwise */ - public function offsetExists ( $field ) : bool { + public function offsetExists ( $field ): bool { return $this->_value->offsetExists($field); } + /** * Gets the value of the field (or the value at position) * @param field the field to get the value * @return value the value of the field */ - #[\ReturnTypeWillChange] - public function offsetGet ( $field ) { + #[ReturnTypeWillChange] + public function offsetGet ( $field ) { return $this->_value[$field]; } + /** * Unsets the value of the field (or the value at position) * @param field the field to unset the value */ - public function offsetUnset($field ) : void { + public function offsetUnset($field ): void { $this->_value->offsetUnset($field); - } + } public function push($v) { return $this->_value->push($v); diff --git a/src/PDFObjectParser.php b/src/PDFObjectParser.php index 0158ad9..cf6dfa2 100644 --- a/src/PDFObjectParser.php +++ b/src/PDFObjectParser.php @@ -19,26 +19,18 @@ along with this program. If not, see . */ - namespace ddn\sapp; - +namespace ddn\sapp; + + use \Exception; + use ddn\sapp\helpers\StreamReader; use ddn\sapp\pdfvalue\PDFValue; use ddn\sapp\pdfvalue\PDFValueHexString; use ddn\sapp\pdfvalue\PDFValueList; use ddn\sapp\pdfvalue\PDFValueObject; - use ddn\sapp\pdfvalue\PDFValueReference; use ddn\sapp\pdfvalue\PDFValueSimple; use ddn\sapp\pdfvalue\PDFValueString; use ddn\sapp\pdfvalue\PDFValueType; - use ddn\sapp\helpers\StreamReader; - use \Exception; - - use ddn\sapp\helpers\Buffer; - - use function ddn\sapp\helpers\p_debug; - use function ddn\sapp\helpers\p_debug_var; - use function ddn\sapp\helpers\p_error; - use function ddn\sapp\helpers\p_warning; - + use Stringable; /** * Class devoted to parse a single PDF object @@ -56,22 +48,34 @@ * * - At the end, it is a simple syntax checker */ - class PDFObjectParser { - + class PDFObjectParser implements Stringable { // Possible tokens in a PDF document const T_NOTOKEN = 0; + const T_LIST_START = 1; + const T_LIST_END = 2; + const T_FIELD = 3; + const T_STRING = 4; + const T_HEX_STRING = 12; + const T_SIMPLE = 5; + const T_DICT_START = 6; + const T_DICT_END = 7; + const T_OBJECT_BEGIN = 8; + const T_OBJECT_END = 9; + const T_STREAM_BEGIN = 10; + const T_STREAM_END = 11; + const T_COMMENT = 13; const T_NAMES = [ @@ -88,7 +92,7 @@ class PDFObjectParser { self::T_OBJECT_END => 'object end', self::T_STREAM_BEGIN => 'stream begin', self::T_STREAM_END => 'stream end', - self::T_COMMENT => 'comment' + self::T_COMMENT => 'comment', ]; const T_SIMPLE_OBJECTS = [ @@ -97,13 +101,17 @@ class PDFObjectParser { self::T_OBJECT_END, self::T_STREAM_BEGIN, self::T_STREAM_END, - self::T_COMMENT + self::T_COMMENT, ]; protected $_buffer = null; + protected $_c = false; + protected $_n = false; + protected $_t = false; + protected $_tt = self::T_NOTOKEN; /** @@ -144,14 +152,14 @@ protected function start(&$buffer) { /** * Parses the document */ - public function parse(&$stream) { // $str, $offset = 0) { + public function parse(&$stream): PDFValueObject|PDFValueList|PDFValueString|PDFValueType|PDFValueSimple|false|null { // $str, $offset = 0) { $this->start($stream); //$str, $offset); $this->nexttoken(); $result = $this->_parse_value(); return $result; } - public function parsestr($str, $offset = 0) { + public function parsestr($str, $offset = 0): PDFValueObject|PDFValueList|PDFValueString|PDFValueType|PDFValueSimple|false|null { $stream = new StreamReader($str); $stream->goto($offset); return $this->parse($stream); @@ -161,7 +169,7 @@ public function parsestr($str, $offset = 0) { * Simple output of the object * @return output the output of the object */ - public function __toString() { + public function __toString(): string { return "pos: " . $this->_buffer->getpos() . ", c: $this->_c, n: $this->_n, t: $this->_t, tt: " . self::T_NAMES[$this->_tt] . ', b: ' . $this->_buffer->substratpos(50) . "\n"; @@ -171,17 +179,17 @@ public function __toString() { * Obtains the next token and returns it */ public function nexttoken() { - [ $this->_t, $this->_tt ] = $this->token(); + [$this->_t, $this->_tt] = $this->token(); return $this->_t; } /** * Function that returns wether the current char is a separator or not */ - protected function _c_is_separator() { - $DSEPS =[ "<<", ">>" ]; + protected function _c_is_separator(): bool { + $DSEPS = ["<<", ">>"]; - return (($this->_c === false) || (strpos("%<>()[]{}/ \n\r\t", $this->_c) !== false) || ((array_search($this->_c . $this->_n, $DSEPS)) !== false)); + return (($this->_c === false) || (str_contains("%<>()[]{}/ \n\r\t", (string) $this->_c)) || ((array_search($this->_c . $this->_n, $DSEPS)) !== false)); } /** @@ -194,13 +202,13 @@ protected function _parse_hex_string() { if ($this->_c !== "<") throw new Exception("Invalid hex string"); $this->nextchar(); // This char is "<" - while (($this->_c !== '>')&&(strpos("0123456789abcdefABCDEF \t\r\n\f", $this->_c) !== false)) { + while (($this->_c !== '>') && (str_contains("0123456789abcdefABCDEF \t\r\n\f", $this->_c))) { $token .= $this->_c; if ($this->nextchar() === false) { break; } } - if (($this->_c !== false) && (strpos(">0123456789abcdefABCDEF \t\r\n\f", $this->_c) === false)) + if (($this->_c !== false) && (! str_contains(">0123456789abcdefABCDEF \t\r\n\f", $this->_c))) throw new Exception("invalid hex string"); // The only way to get to here is that char is ">" @@ -217,12 +225,12 @@ protected function _parse_string() { $n_parenthesis = 1; while ($this->_c !== false) { $this->nextchar(); - if (($this->_c === ')') && (!strlen($token) || ($token[strlen($token) - 1] !== '\\'))) { + if (($this->_c === ')') && (! strlen($token) || ($token[strlen($token) - 1] !== '\\'))) { $n_parenthesis--; if ($n_parenthesis == 0) break; } else { - if (($this->_c === '(') && (!strlen($token) || ($token[strlen($token) - 1] !== '\\'))) { + if (($this->_c === '(') && (! strlen($token) || ($token[strlen($token) - 1] !== '\\'))) { $n_parenthesis++; } $token .= $this->_c; @@ -238,13 +246,13 @@ protected function _parse_string() { } protected function token() { - if ($this->_c === false) return [ false, false ]; - + if ($this->_c === false) return [false, false]; + $token = false; while ($this->_c !== false) { // Skip the spaces - while ((strpos("\t\n\r ", $this->_c) !== false) && ($this->nextchar() !== false)) ; + while ((str_contains("\t\n\r ", (string) $this->_c)) && ($this->nextchar() !== false)); $token_type = self::T_NOTOKEN; @@ -253,7 +261,7 @@ protected function token() { case '%': $this->nextchar(); $token = ""; - while (strpos("\n\r", $this->_c) === false) { + while (! str_contains("\n\r", (string) $this->_c)) { $token .= $this->_c; $this->nextchar(); } @@ -297,7 +305,7 @@ protected function token() { $this->nextchar(); // We are assuming any char (i.e. /MY+difficult_id is valid) - while (!$this->_c_is_separator()) { + while (! $this->_c_is_separator()) { $token .= $this->_c; if ($this->nextchar() === false) break; } @@ -307,30 +315,25 @@ protected function token() { if ($token === false) { $token = ""; - while (!$this->_c_is_separator()) { + while (! $this->_c_is_separator()) { $token .= $this->_c; if ($this->nextchar() === false) break; } - switch ($token) { - case 'obj': - $token_type = self::T_OBJECT_BEGIN; break; - case 'endobj': - $token_type = self::T_OBJECT_END; break; - case 'stream': - $token_type = self::T_STREAM_BEGIN; break; - case 'endstream': - $token_type = self::T_STREAM_END; break; - default: - $token_type = self::T_SIMPLE; break; - } - + $token_type = match ($token) { + 'obj' => self::T_OBJECT_BEGIN, + 'endobj' => self::T_OBJECT_END, + 'stream' => self::T_STREAM_BEGIN, + 'endstream' => self::T_STREAM_END, + default => self::T_SIMPLE, + }; + } - return [ $token, $token_type ]; + return [$token, $token_type]; } } - protected function _parse_obj() { + protected function _parse_obj(): PDFValueObject|false { if ($this->_tt !== self::T_DICT_START) { throw new Exception("Invalid object definition"); } @@ -355,7 +358,7 @@ protected function _parse_obj() { return false; } - protected function _parse_list() { + protected function _parse_list(): PDFValueList { if ($this->_tt !== self::T_LIST_START) { throw new Exception("Invalid list definition"); } @@ -385,7 +388,7 @@ protected function _parse_list() { return new PDFValueList($list); } - protected function _parse_value() { + protected function _parse_value(): PDFValueObject|false|PDFValueList|PDFValueString|PDFValueHexString|PDFValueType|null|PDFValueSimple { while ($this->_t !== false) { switch ($this->_tt) { case self::T_DICT_START: @@ -411,7 +414,6 @@ protected function _parse_value() { case self::T_OBJECT_BEGIN: case self::T_STREAM_END: throw new Exception("invalid keyword"); - case self::T_OBJECT_END: case self::T_STREAM_BEGIN: return null; @@ -433,7 +435,7 @@ protected function _parse_value() { */ return new PDFValueSimple($simple_value); break; - + default: throw new Exception("Invalid token: $this"); } @@ -441,7 +443,7 @@ protected function _parse_value() { return false; } - function tokenize() { + function tokenize(): void { $this->start(); while ($this->nexttoken() !== false) { echo "$this->_t\n"; diff --git a/src/PDFSignatureObject.php b/src/PDFSignatureObject.php index c9794da..122450c 100644 --- a/src/PDFSignatureObject.php +++ b/src/PDFSignatureObject.php @@ -21,16 +21,9 @@ namespace ddn\sapp; -use ddn\sapp\PDFObject; -use ddn\sapp\pdfvalue\PDFValue; -use ddn\sapp\pdfvalue\PDFValueHexString; -use ddn\sapp\pdfvalue\PDFValueList; -use ddn\sapp\pdfvalue\PDFValueObject; -use ddn\sapp\pdfvalue\PDFValueReference; +use function ddn\sapp\helpers\timestamp_to_pdfdatestring; use ddn\sapp\pdfvalue\PDFValueSimple; use ddn\sapp\pdfvalue\PDFValueString; -use ddn\sapp\pdfvalue\PDFValueType; -use function ddn\sapp\helpers\timestamp_to_pdfdatestring; // This is an special object that has a set of fields class PDFSignatureObject extends PDFObject { @@ -42,27 +35,34 @@ class PDFSignatureObject extends PDFObject { // is not known. 68 digits enable 20 digits for the size of the document public static $__BYTERANGE_SIZE = 68; - protected $_prev_content_size = 0; + protected int $_prev_content_size; + protected $_post_content_size = null; // A placeholder for the certificate to use to sign the document protected $_certificate = null; + protected $_signature_ltv_data = null; + protected $_signature_tsa = null; + /** * Sets the certificate to use to sign * @param cert the pem-formatted certificate and private to use to sign as * [ 'cert' => ..., 'pkey' => ... ] */ - public function set_certificate($certificate) { + public function set_certificate($certificate): void { $this->_certificate = $certificate; } - public function set_signature_ltv($signature_ltv_data) { + + public function set_signature_ltv($signature_ltv_data): void { $this->_signature_ltv_data = $signature_ltv_data; } - public function set_signature_tsa($signature_tsa) { + + public function set_signature_tsa($signature_tsa): void { $this->_signature_tsa = $signature_tsa; } + /** * Obtains the certificate set with function set_certificate * @return cert the certificate @@ -70,12 +70,15 @@ public function set_signature_tsa($signature_tsa) { public function get_certificate() { return $this->_certificate; } + public function get_tsa() { return $this->_signature_tsa; } + public function get_ltv() { return $this->_signature_ltv_data; } + /** * Constructs the object and sets the default values needed to sign * @param oid the oid for the object @@ -92,6 +95,7 @@ public function __construct($oid) { 'M' => new PDFValueString(timestamp_to_pdfdatestring()), ]); } + /** * Function used to add some metadata fields to the signature: name, reason of signature, etc. * @param name the name of the signer @@ -99,7 +103,7 @@ public function __construct($oid) { * @param location the location of signature * @param contact the contact info */ - public function set_metadata($name = null, $reason = null, $location = null, $contact = null) { + public function set_metadata($name = null, $reason = null, $location = null, $contact = null): void { if ($name !== null) { $this->_value["Name"] = new PDFValueString($name); } @@ -113,46 +117,50 @@ public function set_metadata($name = null, $reason = null, $location = null, $co $this->_value["ContactInfo"] = new PDFValueString($contact); } } + /** * Function that sets the size of the content that will appear in the file, previous to this object, * and the content that will be included after. This is needed to get the range of bytes of the * signature. */ - public function set_sizes($prev_content_size, $post_content_size = null) { + public function set_sizes(int $prev_content_size, $post_content_size = null): void { $this->_prev_content_size = $prev_content_size; $this->_post_content_size = $post_content_size; } + /** * This function gets the offset of the marker, relative to this object. To make correct, the offset of the object * shall have properly been set. It makes use of the parent "to_pdf_entry" function to avoid recursivity. * @return position the position of the <0000 marker */ - public function get_signature_marker_offset() { + public function get_signature_marker_offset(): int { $tmp_output = parent::to_pdf_entry(); $marker = "/Contents"; $position = strpos($tmp_output, $marker); return $position + strlen($marker); } + /** * Overrides the parent function to calculate the proper range of bytes, according to the sizes provided and the * string representation of this object * @return str the string representation of this object */ - public function to_pdf_entry() { + public function to_pdf_entry(): string { $signature_size = strlen(parent::to_pdf_entry()); $offset = $this->get_signature_marker_offset(); $starting_second_part = $this->_prev_content_size + $offset + self::$__SIGNATURE_MAX_LENGTH + 2; $contents_size = strlen("" . $this->_value['Contents']); - $byterange_str = "[ 0 " . + $byterange_str = "[ 0 " . ($this->_prev_content_size + $offset) . " " . ($starting_second_part) . " " . - ($this->_post_content_size!==null?$this->_post_content_size + ($signature_size - $contents_size - $offset):0) . " ]"; + ($this->_post_content_size !== null ? $this->_post_content_size + ($signature_size - $contents_size - $offset) : 0) . " ]"; $this->_value['ByteRange'] = - new PDFValueSimple($byterange_str . str_repeat(" ", self::$__BYTERANGE_SIZE - strlen($byterange_str) + 1) - ); + new PDFValueSimple( + $byterange_str . str_repeat(" ", self::$__BYTERANGE_SIZE - strlen($byterange_str) + 1) + ); return parent::to_pdf_entry(); } diff --git a/src/PDFUtilFnc.php b/src/PDFUtilFnc.php index 083247b..a145dc8 100644 --- a/src/PDFUtilFnc.php +++ b/src/PDFUtilFnc.php @@ -21,28 +21,25 @@ namespace ddn\sapp; -use ddn\sapp\PDFObjectParser; -use ddn\sapp\helpers\StreamReader; use ddn\sapp\helpers\Buffer; +use ddn\sapp\helpers\LoadHelpers; use function ddn\sapp\helpers\p_debug; use function ddn\sapp\helpers\p_debug_var; use function ddn\sapp\helpers\p_error; use function ddn\sapp\helpers\p_warning; use function ddn\sapp\helpers\show_bytes; - -use ddn\sapp\helpers\LoadHelpers; -if (!defined("ddn\\sapp\\helpers\\LoadHelpers")) +use ddn\sapp\helpers\StreamReader; +if (! defined("ddn\\sapp\\helpers\\LoadHelpers")) new LoadHelpers; // TODO: use the streamreader to deal with the document in the file, instead of a buffer class PDFUtilFnc { - public static function get_trailer(&$_buffer, $trailer_pos) { // Search for the trailer structure - if (preg_match('/trailer\s*(.*)\s*startxref/ms', $_buffer, $matches, 0, $trailer_pos) !== 1) + if (preg_match('/trailer\s*(.*)\s*startxref/ms', (string) $_buffer, $matches, 0, $trailer_pos) !== 1) return p_error("trailer not found"); - + $trailer_str = $matches[1]; // We create the object to parse (this is not innefficient, because it is disposed when returning from the function) @@ -50,14 +47,14 @@ public static function get_trailer(&$_buffer, $trailer_pos) { $parser = new PDFObjectParser(); try { $trailer_obj = $parser->parsestr($trailer_str); - } catch (Exception $e) { + } catch (Exception) { return p_error("trailer is not valid"); } return $trailer_obj; } - public static function build_xref_1_5($offsets) { + public static function build_xref_1_5($offsets): array { if (isset($offsets[0])) unset($offsets[0]); $k = array_keys($offsets); sort($k); @@ -105,9 +102,9 @@ public static function build_xref_1_5($offsets) { // p_debug(show_bytes($result, 6)); return [ - "W" => [ 1, 4, 1 ], + "W" => [1, 4, 1], "Index" => $indexes, - "stream" => $result + "stream" => $result, ]; } @@ -127,11 +124,11 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) { if ($xref_o === false) return p_error("cross reference object not found when parsing xref at position $xref_pos", [false, false, false]); - if (!(isset($xref_o["Type"])) || ($xref_o["Type"]->val() !== "XRef")) + if (! (isset($xref_o["Type"])) || ($xref_o["Type"]->val() !== "XRef")) return p_error("invalid xref table", [false, false, false]); $stream = $xref_o->get_stream(false); - if ($stream === null) + if ($stream === null) return p_error("cross reference stream not found when parsing xref at position $xref_pos", [false, false, false]); $W = $xref_o["W"]->val(true); @@ -146,7 +143,7 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) { if ($Size === false) return p_error("could not get the size of the xref table", [false, false, false]); - $Index = [ 0, $Size ]; + $Index = [0, $Size]; if (isset($xref_o["Index"])) $Index = $xref_o["Index"]->val(true); @@ -167,9 +164,9 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) { return p_error("invalid reference to a previous xref table", [false, false, false]); // When dealing with 1.5 cross references, we do not allow to use other than cross references - [ $xref_table, $trailer_obj ] = PDFUtilFnc::get_xref_1_5($_buffer, $Prev, $depth); + [$xref_table, $trailer_obj] = PDFUtilFnc::get_xref_1_5($_buffer, $Prev, $depth); // p_debug_var($xref_table); - } + } } // p_debug("xref table found at $xref_pos (oid: " . $xref_o->get_oid() . ")"); @@ -179,25 +176,20 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) { $get_fmt_function = function($f) { if ($f === false) return false; - - switch ($f) { - case 0: return function ($v) { return 0; }; - case 1: return function ($v) { return unpack('C', str_pad($v, 1, chr(0), STR_PAD_LEFT))[1]; }; - case 2: return function ($v) { return unpack('n', str_pad($v, 2, chr(0), STR_PAD_LEFT))[1]; }; - case 3: - case 4: return function ($v) { return unpack('N', str_pad($v, 4, chr(0), STR_PAD_LEFT))[1]; }; - case 5: - case 6: - case 7: - case 8: return function ($v) { return unpack('J', str_pad($v, 8, chr(0), STR_PAD_LEFT))[1]; }; - } - return false; + return match ($f) { + 0 => fn($v): int => 0, + 1 => fn($v) => unpack('C', str_pad($v, 1, chr(0), STR_PAD_LEFT))[1], + 2 => fn($v) => unpack('n', str_pad($v, 2, chr(0), STR_PAD_LEFT))[1], + 3, 4 => fn($v) => unpack('N', str_pad($v, 4, chr(0), STR_PAD_LEFT))[1], + 5, 6, 7, 8 => fn($v) => unpack('J', str_pad($v, 8, chr(0), STR_PAD_LEFT))[1], + default => false, + }; }; $fmt_function = [ $get_fmt_function($W[0]), $get_fmt_function($W[1]), - $get_fmt_function($W[2]) + $get_fmt_function($W[2]), ]; // p_debug("xref entries at $xref_pos for object " . $xref_o->get_oid()); @@ -210,7 +202,7 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) { $object_count = $Index[$index_i++]; while (($stream_v->currentchar() !== false) && ($object_count > 0)) { - $f1 = $W[0]!=0?($fmt_function[0]($stream_v->nextchars($W[0]))):1; + $f1 = $W[0] != 0 ? ($fmt_function[0]($stream_v->nextchars($W[0]))) : 1; $f2 = $fmt_function[1]($stream_v->nextchars($W[1])); $f3 = $fmt_function[2]($stream_v->nextchars($W[2])); @@ -235,7 +227,10 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) { case 2: // Stream object // $f2 is the number of a stream object, $f3 is the index in that stream object - $xref_table[$object_i] = array("stmoid" => $f2, "pos" => $f3 ); + $xref_table[$object_i] = [ + "stmoid" => $f2, + "pos" => $f3, + ]; break; default: p_error("do not know about entry of type $f1 in xref table"); @@ -246,7 +241,7 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) { } } - return [ $xref_table, $xref_o->get_value(), "1.5" ]; + return [$xref_table, $xref_o->get_value(), "1.5"]; } public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) { @@ -257,17 +252,17 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) { $depth = $depth - 1; } - $trailer_pos = strpos($_buffer, "trailer", $xref_pos); + $trailer_pos = strpos((string) $_buffer, "trailer", $xref_pos); $min_pdf_version = "1.4"; // Get the xref content and make sure that the buffer passed contains the xref tag at the offset provided - $xref_substr = substr($_buffer, $xref_pos, $trailer_pos - $xref_pos); + $xref_substr = substr((string) $_buffer, $xref_pos, $trailer_pos - $xref_pos); $separator = "\r\n"; $xref_line = strtok($xref_substr, $separator); if ($xref_line !== 'xref') return p_error("xref tag not found at position $xref_pos", [false, false, false]); - + // Now parse the lines and build the xref table $obj_id = false; $obj_count = 0; @@ -329,7 +324,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) { } } - $obj_count-= 1; + $obj_count -= 1; $obj_id++; continue; } @@ -344,40 +339,40 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) { // If there exists a previous xref (for incremental PDFs), get it and merge the objects that do not exist in the current xref table if (isset($trailer_obj['Prev'])) { - + $xref_prev_pos = $trailer_obj['Prev']->val(); - if (!is_numeric($xref_prev_pos)) + if (! is_numeric($xref_prev_pos)) return p_error("invalid trailer $trailer_obj", [false, false, false]); $xref_prev_pos = intval($xref_prev_pos); - [ $prev_table, $prev_trailer, $prev_min_pdf_version ] = PDFUtilFnc::get_xref_1_4($_buffer, $xref_prev_pos, $depth); + [$prev_table, $prev_trailer, $prev_min_pdf_version] = PDFUtilFnc::get_xref_1_4($_buffer, $xref_prev_pos, $depth); if ($prev_min_pdf_version !== $min_pdf_version) - return p_error("mixed type of xref tables are not supported", [ false, false, false]); + return p_error("mixed type of xref tables are not supported", [false, false, false]); if ($prev_table !== false) { foreach ($prev_table as $obj_id => &$obj_offset) { // Not modifying the objects, but to make sure that it does not consume additional memory // If there not exists a new version, we'll acquire it - if (!isset($xref_table[$obj_id])) { + if (! isset($xref_table[$obj_id])) { $xref_table[$obj_id] = $obj_offset; } } } } - return [ $xref_table, $trailer_obj, $min_pdf_version ]; + return [$xref_table, $trailer_obj, $min_pdf_version]; } - public static function get_xref(&$_buffer, $xref_pos, $depth = null) { - + public static function get_xref(&$_buffer, $xref_pos, $depth = null): array { + // Each xref is immediately followed by a trailer - $trailer_pos = strpos($_buffer, "trailer", $xref_pos); + $trailer_pos = strpos((string) $_buffer, "trailer", $xref_pos); if ($trailer_pos === false) { - [ $xref_table, $trailer_obj, $min_pdf_version ] = PDFUtilFnc::get_xref_1_5($_buffer, $xref_pos, $depth); + [$xref_table, $trailer_obj, $min_pdf_version] = PDFUtilFnc::get_xref_1_5($_buffer, $xref_pos, $depth); } else - [ $xref_table, $trailer_obj, $min_pdf_version ] = PDFUtilFnc::get_xref_1_4($_buffer, $xref_pos, $depth); - return [ $xref_table, $trailer_obj, $min_pdf_version ]; + [$xref_table, $trailer_obj, $min_pdf_version] = PDFUtilFnc::get_xref_1_4($_buffer, $xref_pos, $depth); + return [$xref_table, $trailer_obj, $min_pdf_version]; } public static function acquire_structure(&$_buffer, $depth = null) { @@ -390,10 +385,10 @@ public static function acquire_structure(&$_buffer, $depth = null) { if (preg_match('/^%PDF-[0-9]+\.[0-9]+$/', $pdf_version, $matches) !== 1) return p_error("PDF version string not found"); - if (preg_match_all('/startxref\s*([0-9]+)\s*%%EOF($|[\r\n])/ms', $_buffer, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE) === false) + if (preg_match_all('/startxref\s*([0-9]+)\s*%%EOF($|[\r\n])/ms', (string) $_buffer, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE) === false) return p_error("failed to get structure"); - $_versions=[]; + $_versions = []; /* print_r($matches); exit(); @@ -403,11 +398,11 @@ public static function acquire_structure(&$_buffer, $depth = null) { } // Now get the trailing part and make sure that it has the proper form - $startxref_pos = strrpos($_buffer, "startxref"); + $startxref_pos = strrpos((string) $_buffer, "startxref"); if ($startxref_pos === false) return p_error("startxref not found"); - if (preg_match('/startxref\s*([0-9]+)\s*%%EOF\s*$/ms', $_buffer, $matches, 0, $startxref_pos) !== 1) + if (preg_match('/startxref\s*([0-9]+)\s*%%EOF\s*$/ms', (string) $_buffer, $matches, 0, $startxref_pos) !== 1) return p_error("startxref and %%EOF not found"); $xref_pos = intval($matches[1]); @@ -420,11 +415,11 @@ public static function acquire_structure(&$_buffer, $depth = null) { "xref" => [], "xrefposition" => 0, "xrefversion" => substr($pdf_version, 1), - "revisions" => $_versions - ]; + "revisions" => $_versions, + ]; } - [ $xref_table, $trailer_object, $min_pdf_version ] = PDFUtilFnc::get_xref($_buffer, $xref_pos, $depth); + [$xref_table, $trailer_object, $min_pdf_version] = PDFUtilFnc::get_xref($_buffer, $xref_pos, $depth); // We are providing a lot of information to be able to inspect the problems of a PDF file if ($xref_table === false) { @@ -441,7 +436,7 @@ public static function acquire_structure(&$_buffer, $depth = null) { "xref" => $xref_table, "xrefposition" => $xref_pos, "xrefversion" => $min_pdf_version, - "revisions" => $_versions + "revisions" => $_versions, ]; } @@ -453,7 +448,7 @@ public static function acquire_structure(&$_buffer, $depth = null) { * @param xref_table the xref table, to be able to find indirect objects * @return obj the PDFObject obtained from the file or false if could not be found */ - public static function find_object_at_pos(&$_buffer, $oid, $object_offset, $xref_table) { + public static function find_object_at_pos(&$_buffer, int $oid, $object_offset, $xref_table) { $object = PDFUtilFnc::object_from_string($_buffer, $oid, $object_offset, $offset_end); @@ -462,13 +457,13 @@ public static function find_object_at_pos(&$_buffer, $oid, $object_offset, $xref $_stream_pending = false; // The distinction is required, because we need to get the proper start for the stream, and if using CRLF instead of LF - // - according to https://www.adobe.com/content/dam/acom/en/devnet/pdf/PDF32000_2008.pdf, stream is followed by CRLF + // - according to https://www.adobe.com/content/dam/acom/en/devnet/pdf/PDF32000_2008.pdf, stream is followed by CRLF // or LF, but not single CR. - if (substr($_buffer, $offset_end - 7, 7) === "stream\n") { + if (substr((string) $_buffer, $offset_end - 7, 7) === "stream\n") { $_stream_pending = $offset_end; } - if (substr($_buffer, $offset_end - 7, 8) === "stream\r\n") { + if (substr((string) $_buffer, $offset_end - 7, 8) === "stream\r\n") { $_stream_pending = $offset_end + 1; } @@ -478,7 +473,7 @@ public static function find_object_at_pos(&$_buffer, $oid, $object_offset, $xref if ($length === false) { $length_object_id = $object['Length']->get_object_referenced(); if ($length_object_id === false) { - return p_error("could not get stream for object $obj_id"); + return p_error("could not get stream for object $oid"); } $length_object = PDFUtilFnc::find_object($_buffer, $xref_table, $length_object_id); if ($length_object === false) @@ -488,10 +483,10 @@ public static function find_object_at_pos(&$_buffer, $oid, $object_offset, $xref } if ($length === false) { - return p_error("could not get stream length for object $obj_id"); + return p_error("could not get stream length for object $oid"); } - $object->set_stream(substr($_buffer, $_stream_pending, $length), true); + $object->set_stream(substr((string) $_buffer, $_stream_pending, $length), true); } return $object; @@ -501,41 +496,41 @@ public static function find_object_at_pos(&$_buffer, $oid, $object_offset, $xref * Function that finds a specific object in the document, using the xref table as a base * @param buffer the buffer from which to read the document * @param xref_table the xref table - * @param oid the target object id to read + * @param oid the target object id to read * @return obj the PDFObject obtained from the file or false if could not be found */ - public static function find_object(&$_buffer, $xref_table, $oid) { - + public static function find_object(&$_buffer, $xref_table, int $oid) { + if ($oid === 0) return false; - if (!isset($xref_table[$oid])) return false; + if (! isset($xref_table[$oid])) return false; // Find the object and get where it ends $object_offset = $xref_table[$oid]; - if (!is_array($object_offset)) + if (! is_array($object_offset)) return PDFUtilFnc::find_object_at_pos($_buffer, $oid, $object_offset, $xref_table); else { $object = PDFUtilFnc::find_object_in_objstm($_buffer, $xref_table, $object_offset["stmoid"], $object_offset["pos"], $oid); return $object; - } + } } /** * Function that searches for an object in an object stream */ - public static function find_object_in_objstm(&$_buffer, $xref_table, $objstm_oid, $objpos, $oid) { + public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm_oid, $objpos, int $oid) { $objstm = PDFUtilFnc::find_object($_buffer, $xref_table, $objstm_oid); if ($objstm === false) return p_error("could not get object stream $objstm_oid"); - if (($objstm["Extends"]??false !== false)) + if (($objstm["Extends"] ?? false !== false)) // TODO: support them return p_error("not supporting extended object streams at this time"); - $First = $objstm["First"]??false; - $N = $objstm["N"]??false; - $Type = $objstm["Type"]??false; - + $First = $objstm["First"] ?? false; + $N = $objstm["N"] ?? false; + $Type = $objstm["Type"] ?? false; + if (($First === false) || ($N === false) || ($Type === false)) return p_error("invalid object stream $objstm_oid"); @@ -546,9 +541,9 @@ public static function find_object_in_objstm(&$_buffer, $xref_table, $objstm_oid $N = $N->get_int(); $stream = $objstm->get_stream(false); - $index = substr($stream, 0, $First); + $index = substr((string) $stream, 0, $First); $index = explode(" ", trim($index)); - $stream = substr($stream, $First); + $stream = substr((string) $stream, $First); if (count($index) % 2 !== 0) return p_error("invalid index for object stream $objstm_oid"); @@ -575,10 +570,10 @@ public static function find_object_in_objstm(&$_buffer, $xref_table, $objstm_oid } /** - * Function that parses an object + * Function that parses an object */ public static function object_from_string(&$buffer, $expected_obj_id, $offset = 0, &$offset_end = 0) { - if (preg_match('/([0-9]+)\s+([0-9+])\s+obj(\s+)/ms', $buffer, $matches, 0, $offset) !== 1) { + if (preg_match('/([0-9]+)\s+([0-9+])\s+obj(\s+)/ms', (string) $buffer, $matches, 0, $offset) !== 1) { // p_debug_var(substr($buffer)) return p_error("object is not valid: $expected_obj_id"); } @@ -627,7 +622,7 @@ public static function object_from_string(&$buffer, $expected_obj_id, $offset = * object in the document. * @return xref_string a string that contains the xref table, ready to be inserted in the document */ - public static function build_xref($offsets) { + public static function build_xref(array $offsets): string { $k = array_keys($offsets); sort($k); @@ -651,6 +646,6 @@ public static function build_xref($offsets) { } $result = $result . "$i_k {$count}\n$references"; - return "xref\n$result"; - } + return "xref\n$result"; + } } diff --git a/src/helpers/Buffer.php b/src/helpers/Buffer.php index 88b8f9d..8a6a475 100644 --- a/src/helpers/Buffer.php +++ b/src/helpers/Buffer.php @@ -21,44 +21,48 @@ namespace ddn\sapp\helpers; -if (!defined('__CONVENIENT_MAX_BUFFER_DUMP')) - define('__CONVENIENT_MAX_BUFFER_DUMP', 80); +use Stringable; -use function ddn\sapp\helpers\debug_var; +if (! defined('__CONVENIENT_MAX_BUFFER_DUMP')) + define('__CONVENIENT_MAX_BUFFER_DUMP', 80); /** * This class is used to manage a buffer of characters. The main features are that * it is possible to add data (by usign *data* function), and getting the current * size. Then it is possible to get the whole buffer using function *get_raw* */ -class Buffer { +class Buffer implements Stringable { protected $_buffer = ""; - protected $_bufferlen = 0; + + protected int $_bufferlen; public function __construct($string = null) { if ($string === null) $string = ""; $this->_buffer = $string; - $this->_bufferlen = strlen($string); + $this->_bufferlen = strlen((string) $string); } + /** * Adds raw data to the buffer * @param data the data to add */ - public function data(...$datas) { + public function data(...$datas): void { foreach ($datas as $data) { - $this->_bufferlen += strlen($data); + $this->_bufferlen += strlen((string) $data); $this->_buffer .= $data; } - } + } + /** * Obtains the size of the buffer * @return size the size of the buffer */ - public function size() { + public function size(): int { return $this->_bufferlen; } + /** * Gets the raw data from the buffer * @return buffer the raw data @@ -66,27 +70,29 @@ public function size() { public function get_raw() { return $this->_buffer; } + /** * Appends buffer $b to this buffer * @param b the buffer to be added to this one * @return buffer this object */ - public function append($b) { - if (get_class($b) !== get_class($this)) + public function append($b): static { + if ($b::class !== static::class) throw new Exception('invalid buffer to add to this one'); - + $this->_buffer .= $b->get_raw(); $this->_bufferlen = strlen($this->_buffer); return $this; } + /** * Obtains a new buffer that is the result from the concatenation of this buffer and the parameter * @param b the buffer to be added to this one * @return buffer the resulting buffer (different from this one) */ - public function add(...$bs) { + public function add(...$bs): \ddn\sapp\helpers\Buffer { foreach ($bs as $b) { - if (get_class($b) !== get_class($this)) + if ($b::class !== static::class) throw new Exception('invalid buffer to add to this one'); } @@ -96,33 +102,34 @@ public function add(...$bs) { return $r; } + /** * Returns a new buffer that contains the same data than this one * @return buffer the cloned buffer */ - public function clone() { + public function clone(): \ddn\sapp\helpers\Buffer { $buffer = new Buffer($this->_buffer); return $buffer; } + /** * Provides a easy to read string representation of the buffer, using the "var_dump" output * of the variable, but providing a reduced otput of the buffer * @return str a string with the representation of the buffer */ - public function __toString() { - if (strlen($this->_buffer) < (__CONVENIENT_MAX_BUFFER_DUMP * 2)) - return debug_var($this); + public function __toString(): string { + if (strlen((string) $this->_buffer) < (__CONVENIENT_MAX_BUFFER_DUMP * 2)) + return (string) debug_var($this); $buffer = $this->_buffer; - $this->_buffer = substr($buffer, 0, __CONVENIENT_MAX_BUFFER_DUMP); - $this->_buffer .= "\n...\n" . substr($buffer, -__CONVENIENT_MAX_BUFFER_DUMP); + $this->_buffer = substr((string) $buffer, 0, __CONVENIENT_MAX_BUFFER_DUMP); + $this->_buffer .= "\n...\n" . substr((string) $buffer, -__CONVENIENT_MAX_BUFFER_DUMP); $result = debug_var($this); $this->_buffer = $buffer; - return $result; + return (string) $result; } - - public function show_bytes($columns, $offset = 0, $length = null) { + public function show_bytes($columns, $offset = 0, $length = null): string { if ($length === null) $length = $this->_bufferlen; diff --git a/src/helpers/CMS.php b/src/helpers/CMS.php index 799c107..da2e013 100644 --- a/src/helpers/CMS.php +++ b/src/helpers/CMS.php @@ -1,4 +1,5 @@ $r) { if (stripos($r, 'HTTP/') === 0) { - list(,$code, $status) = explode(' ', $r, 3); + [, $code, $status] = explode(' ', $r, 3); break; } } if($code != '200') { - p_error(" response error! Code=\"$code\", Status=\"".trim($status??"")."\""); + p_error(" response error! Code=\"$code\", Status=\"" . trim($status ?? "") . "\""); return false; } $contentTypeHeader = ''; $headers = explode("\n", $header); foreach ($headers as $key => $r) { // Match the header name up to ':', compare lower case - if (stripos($r, "Content-Type".':') === 0) { - list($headername, $headervalue) = explode(":", $r, 2); + if (stripos($r, "Content-Type" . ':') === 0) { + [$headername, $headervalue] = explode(":", $r, 2); $contentTypeHeader = trim($headervalue); } } @@ -79,50 +80,50 @@ public function sendReq($reqData) { * @return array asn.1 hex structure of tsa response */ private function tsa_parseResp($binaryTsaRespData) { - if(!@$ar = asn1::parse(bin2hex($binaryTsaRespData), 3)) { + if(! @$ar = asn1::parse(bin2hex($binaryTsaRespData), 3)) { p_error(" can't parse invalid tsa Response."); return false; } $curr = $ar; - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if($value['type'] == '30') { - $curr['TimeStampResp']=$curr[$key]; + $curr['TimeStampResp'] = $curr[$key]; unset($curr[$key]); } } - $ar=$curr; + $ar = $curr; $curr = $ar['TimeStampResp']; - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if(is_numeric($key)) { - if($value['type'] == '30' && !array_key_exists('status', $curr)) { - $curr['status']=$curr[$key]; + if($value['type'] == '30' && ! array_key_exists('status', $curr)) { + $curr['status'] = $curr[$key]; unset($curr[$key]); } else if($value['type'] == '30') { - $curr['timeStampToken']=$curr[$key]; + $curr['timeStampToken'] = $curr[$key]; unset($curr[$key]); } } } - $ar['TimeStampResp']=$curr; + $ar['TimeStampResp'] = $curr; $curr = $ar['TimeStampResp']['timeStampToken']; - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if(is_numeric($key)) { if($value['type'] == '06') { - $curr['contentType']=$curr[$key]; + $curr['contentType'] = $curr[$key]; unset($curr[$key]); } if($value['type'] == 'a0') { - $curr['content']=$curr[$key]; + $curr['content'] = $curr[$key]; unset($curr[$key]); } } } $ar['TimeStampResp']['timeStampToken'] = $curr; $curr = $ar['TimeStampResp']['timeStampToken']['content']; - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if(is_numeric($key)) { if($value['type'] == '30') { - $curr['TSTInfo']=$curr[$key]; + $curr['TSTInfo'] = $curr[$key]; unset($curr[$key]); } } @@ -141,24 +142,24 @@ private function tsa_parseResp($binaryTsaRespData) { * @param string $hashAlg hash algorithm * @return string hex TSTinfo. */ - protected function createTimestamp($data, $hashAlg='sha1') { - $TSTInfo=false; + protected function createTimestamp($data, $hashAlg = 'sha1') { + $TSTInfo = false; $tsaQuery = x509::tsa_query($data, $hashAlg); $tsaData = $this->signature_data['tsa']; - $reqData = array( - 'data'=>$tsaQuery, - 'uri'=>$tsaData['host'], - 'req_contentType'=>'application/timestamp-query', - 'resp_contentType'=>'application/timestamp-reply' - ) + $tsaData; + $reqData = [ + 'data' => $tsaQuery, + 'uri' => $tsaData['host'], + 'req_contentType' => 'application/timestamp-query', + 'resp_contentType' => 'application/timestamp-reply', + ] + $tsaData; - p_debug(" sending TSA query to \"".$tsaData['host']."\"..."); - if(!$binaryTsaResp = self::sendReq($reqData)) { + p_debug(" sending TSA query to \"" . $tsaData['host'] . "\"..."); + if(! $binaryTsaResp = self::sendReq($reqData)) { p_error(" TSA query send FAILED!"); } else { p_debug(" TSA query send OK"); p_debug(" Parsing Timestamp response..."); - if(!$tsaResp = $this->tsa_parseResp($binaryTsaResp)) { + if(! $tsaResp = $this->tsa_parseResp($binaryTsaResp)) { p_error(" parsing FAILED!"); } p_debug(" parsing OK"); @@ -175,23 +176,23 @@ protected function createTimestamp($data, $hashAlg='sha1') { * @param string $issuerURIorFILE * @return array */ - protected function LTVvalidation($parsedCert, $ocspURI=null, $crlURIorFILE=null, $issuerURIorFILE=false) { - $ltvResult['issuer']=false; - $ltvResult['ocsp']=false; - $ltvResult['crl']=false; + protected function LTVvalidation($parsedCert, $ocspURI = null, $crlURIorFILE = null, $issuerURIorFILE = false): false|array { + $ltvResult['issuer'] = false; + $ltvResult['ocsp'] = false; + $ltvResult['crl'] = false; $certSigner_parse = $parsedCert; p_debug(" getting OCSP & CRL address..."); p_debug(" reading AIA OCSP attribute..."); $ocspURI = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.1'][0]; - if(empty(trim($ocspURI))) { + if(empty(trim((string) $ocspURI))) { p_warning(" FAILED!"); } else { p_debug(" OK got address:\"$ocspURI\""); } - $ocspURI = trim($ocspURI); + $ocspURI = trim((string) $ocspURI); p_debug(" reading CRL CDP attribute..."); $crlURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['2.5.29.31']['value'][0]; - if(empty(trim($crlURIorFILE??""))) { + if(empty(trim($crlURIorFILE ?? ""))) { p_warning(" FAILED!"); } else { p_debug(" OK got address:\"$crlURIorFILE\""); @@ -202,21 +203,21 @@ protected function LTVvalidation($parsedCert, $ocspURI=null, $crlURIorFILE=null, p_debug(" getting Issuer..."); p_debug(" looking for issuer address from AIA attribute..."); $issuerURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.2'][0]; - $issuerURIorFILE = trim($issuerURIorFILE??""); + $issuerURIorFILE = trim($issuerURIorFILE ?? ""); if(empty($issuerURIorFILE)) { p_debug(" Failed!"); } else { p_debug(" OK got address \"$issuerURIorFILE\"..."); p_debug(" load issuer from \"$issuerURIorFILE\"..."); if($issuerCert = @file_get_contents($issuerURIorFILE)) { - p_debug(" OK. size ".round(strlen($issuerCert)/1024,2)."Kb"); + p_debug(" OK. size " . round(strlen($issuerCert) / 1024, 2) . "Kb"); p_debug(" reading issuer certificate..."); if($issuer_certDER = x509::get_cert($issuerCert)) { p_debug(" OK"); p_debug(" check if issuer is cert issuer..."); $certIssuer_parse = x509::readcert($issuer_certDER, 'oid'); // Parsing Issuer cert $certSigner_signatureField = $certSigner_parse['signatureValue']; - if(openssl_public_decrypt(hex2bin($certSigner_signatureField), $decrypted, x509::x509_der2pem($issuer_certDER), OPENSSL_PKCS1_PADDING)) { + if(openssl_public_decrypt(hex2bin((string) $certSigner_signatureField), $decrypted, x509::x509_der2pem($issuer_certDER), OPENSSL_PKCS1_PADDING)) { p_debug(" OK issuer is cert issuer."); $ltvResult['issuer'] = $issuer_certDER; } else { @@ -229,15 +230,15 @@ protected function LTVvalidation($parsedCert, $ocspURI=null, $crlURIorFILE=null, p_warning(" FAILED!."); } } - - if(!$ltvResult['issuer']) { + + if(! $ltvResult['issuer']) { p_debug(" search for issuer in extracerts....."); if(array_key_exists('extracerts', $this->signature_data) && ($this->signature_data['extracerts'] !== null) && (count($this->signature_data['extracerts']) > 0)) { - $i=0; + $i = 0; foreach($this->signature_data['extracerts'] as $extracert) { p_debug(" extracerts[$i] ..."); $certSigner_signatureField = $certSigner_parse['signatureValue']; - if(openssl_public_decrypt(hex2bin($certSigner_signatureField), $decrypted, $extracert, OPENSSL_PKCS1_PADDING)) { + if(openssl_public_decrypt(hex2bin((string) $certSigner_signatureField), $decrypted, $extracert, OPENSSL_PKCS1_PADDING)) { p_debug(" OK got issuer."); $certIssuer_parse = x509::readcert($extracert, 'oid'); // Parsing Issuer cert $ltvResult['issuer'] = x509::get_cert($extracert); @@ -250,11 +251,11 @@ protected function LTVvalidation($parsedCert, $ocspURI=null, $crlURIorFILE=null, p_error(" FAILED! no extracerts available"); } } - + } - + if($ltvResult['issuer']) { - if(!empty($ocspURI)) { + if(! empty($ocspURI)) { p_debug(" OCSP start..."); $ocspReq_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; $ocspReq_issuerNameHash = $certIssuer_parse['tbsCertificate']['subject']['sha1']; @@ -264,12 +265,12 @@ protected function LTVvalidation($parsedCert, $ocspURI=null, $crlURIorFILE=null, if($ocspReq = x509::ocsp_request($ocspReq_serialNumber, $ocspReq_issuerNameHash, $ocspReq_issuerKeyHash)) { p_debug(" OK."); $ocspBinReq = pack("H*", $ocspReq); - $reqData = array( - 'data'=>$ocspBinReq, - 'uri'=>$ocspURI, - 'req_contentType'=>'application/ocsp-request', - 'resp_contentType'=>'application/ocsp-response' - ); + $reqData = [ + 'data' => $ocspBinReq, + 'uri' => $ocspURI, + 'req_contentType' => 'application/ocsp-request', + 'resp_contentType' => 'application/ocsp-response', + ]; p_debug(" OCSP send request to \"$ocspURI\"..."); if($ocspResp = self::sendReq($reqData)) { p_debug(" OK."); @@ -283,7 +284,7 @@ protected function LTVvalidation($parsedCert, $ocspURI=null, $crlURIorFILE=null, $ocspRespHex = $ocsp_parse['hexdump']; $ltvResult['ocsp'] = $ocspRespHex; } else { - p_warning(" FAILED! cert not valid, status:\"".strtoupper($certStatus)."\""); + p_warning(" FAILED! cert not valid, status:\"" . strtoupper((string) $certStatus) . "\""); } } else { p_warning(" FAILED! Ocsp server status \"$return\""); @@ -295,45 +296,45 @@ protected function LTVvalidation($parsedCert, $ocspURI=null, $crlURIorFILE=null, p_warning(" FAILED!"); } } - - if(!$ltvResult['ocsp']) {// CRL not processed if OCSP validation already success - if(!empty($crlURIorFILE)) { + + if(! $ltvResult['ocsp']) {// CRL not processed if OCSP validation already success + if(! empty($crlURIorFILE)) { p_debug(" processing CRL validation since OCSP not done/failed..."); p_debug(" getting crl from \"$crlURIorFILE\"..."); if($crl = @file_get_contents($crlURIorFILE)) { - p_debug(" OK. size ".round(strlen($crl)/1024,2)."Kb"); + p_debug(" OK. size " . round(strlen($crl) / 1024, 2) . "Kb"); p_debug(" reading crl..."); - if($crlread=x509::crl_read($crl)) { + if($crlread = x509::crl_read($crl)) { p_debug(" OK"); p_debug(" verify crl signature..."); $crl_signatureField = $crlread['parse']['signature']; - if(openssl_public_decrypt(hex2bin($crl_signatureField), $decrypted, x509::x509_der2pem($ltvResult['issuer']), OPENSSL_PKCS1_PADDING)) { + if(openssl_public_decrypt(hex2bin((string) $crl_signatureField), $decrypted, x509::x509_der2pem($ltvResult['issuer']), OPENSSL_PKCS1_PADDING)) { p_debug(" OK"); p_debug(" check CRL validity..."); - $crl_parse=$crlread['parse']; - $thisUpdate = str_pad($crl_parse['TBSCertList']['thisUpdate'], 15, "20", STR_PAD_LEFT); + $crl_parse = $crlread['parse']; + $thisUpdate = str_pad((string) $crl_parse['TBSCertList']['thisUpdate'], 15, "20", STR_PAD_LEFT); $thisUpdateTime = strtotime($thisUpdate); - $nextUpdate = str_pad($crl_parse['TBSCertList']['nextUpdate'], 15, "20", STR_PAD_LEFT); + $nextUpdate = str_pad((string) $crl_parse['TBSCertList']['nextUpdate'], 15, "20", STR_PAD_LEFT); $nextUpdateTime = strtotime($nextUpdate); $nowz = strtotime("now"); - if(($nowz-$thisUpdateTime) < 0) { // 0 sec after valid - p_error(" FAILED! not yet valid! valid at ".date("d/m/Y H:i:s", $thisUpdateTime)); - } elseif(($nextUpdateTime-$nowz) < 1) { // not accept if crl 1 sec remain to expired - p_error(" FAILED! Expired crl at ".date("d/m/Y H:i:s", $nextUpdateTime)." and now ".date("d/m/Y H:i:s", $nowz)."!"); + if(($nowz - $thisUpdateTime) < 0) { // 0 sec after valid + p_error(" FAILED! not yet valid! valid at " . date("d/m/Y H:i:s", $thisUpdateTime)); + } elseif(($nextUpdateTime - $nowz) < 1) { // not accept if crl 1 sec remain to expired + p_error(" FAILED! Expired crl at " . date("d/m/Y H:i:s", $nextUpdateTime) . " and now " . date("d/m/Y H:i:s", $nowz) . "!"); } else { - p_debug(" OK CRL still valid until ".date("d/m/Y H:i:s", $nextUpdateTime).""); - $crlCertValid=true; + p_debug(" OK CRL still valid until " . date("d/m/Y H:i:s", $nextUpdateTime) . ""); + $crlCertValid = true; p_debug(" check if cert not revoked..."); if(array_key_exists('revokedCertificates', $crl_parse['TBSCertList'])) { $certSigner_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; if(array_key_exists($certSigner_serialNumber, $crl_parse['TBSCertList']['revokedCertificates']['lists'])) { - $crlCertValid=false; + $crlCertValid = false; p_error(" FAILED! Certificate Revoked!"); } } if($crlCertValid == true) { p_debug(" OK. VALID"); - $crlHex = current(unpack('H*', $crlread['der'])); + $crlHex = current(unpack('H*', (string) $crlread['der'])); $ltvResult['crl'] = $crlHex; } } @@ -349,15 +350,15 @@ protected function LTVvalidation($parsedCert, $ocspURI=null, $crlURIorFILE=null, } } } - if(!$ltvResult['issuer']) { + if(! $ltvResult['issuer']) { return false; } - if(!$ltvResult['ocsp'] && !$ltvResult['crl']) { + if(! $ltvResult['ocsp'] && ! $ltvResult['crl']) { return false; } return $ltvResult; } - + /** * Perform PKCS7 Signing * @param string $binaryData @@ -365,40 +366,40 @@ protected function LTVvalidation($parsedCert, $ocspURI=null, $crlURIorFILE=null, * @public */ public function pkcs7_sign($binaryData) { - $hexOidHashAlgos = array( - 'md2'=>'06082A864886F70D0202', - 'md4'=>'06082A864886F70D0204', - 'md5'=>'06082A864886F70D0205', - 'sha1'=>'06052B0E03021A', - 'sha224'=>'0609608648016503040204', - 'sha256'=>'0609608648016503040201', - 'sha384'=>'0609608648016503040202', - 'sha512'=>'0609608648016503040203' - ); + $hexOidHashAlgos = [ + 'md2' => '06082A864886F70D0202', + 'md4' => '06082A864886F70D0204', + 'md5' => '06082A864886F70D0205', + 'sha1' => '06052B0E03021A', + 'sha224' => '0609608648016503040204', + 'sha256' => '0609608648016503040201', + 'sha384' => '0609608648016503040202', + 'sha512' => '0609608648016503040203', + ]; $hashAlgorithm = $this->signature_data['hashAlgorithm']; - if(!array_key_exists($hashAlgorithm, $hexOidHashAlgos)) { + if(! array_key_exists($hashAlgorithm, $hexOidHashAlgos)) { p_error("not support hash algorithm!"); return false; } p_debug("hash algorithm is \"$hashAlgorithm\""); $x509 = new x509; - if(!$certParse = $x509->readcert($this->signature_data['signcert'])) { + if(! $certParse = $x509->readcert($this->signature_data['signcert'])) { p_error("certificate error! check certificate"); } $hexEmbedCerts[] = bin2hex($x509->get_cert($this->signature_data['signcert'])); $appendLTV = ''; $ltvData = $this->signature_data['ltv']; - if(!empty($ltvData)) { + if(! empty($ltvData)) { p_debug(" LTV Validation start..."); $appendLTV = ''; $LTVvalidation_ocsp = ''; $LTVvalidation_crl = ''; $LTVvalidation_issuer = ''; $LTVvalidationEnd = false; - + $isRootCA = false; if($certParse['tbsCertificate']['issuer']['hexdump'] == $certParse['tbsCertificate']['subject']['hexdump']) { // check whether root ca - if(openssl_public_decrypt(hex2bin($certParse['signatureValue']), $decrypted, x509::x509_der2pem($x509->get_cert($this->signature_data['signcert'])), OPENSSL_PKCS1_PADDING)) { + if(openssl_public_decrypt(hex2bin((string) $certParse['signatureValue']), $decrypted, x509::x509_der2pem($x509->get_cert($this->signature_data['signcert'])), OPENSSL_PKCS1_PADDING)) { p_debug("***** \"{$certParse['tbsCertificate']['subject']['2.5.4.3'][0]}\" is a ROOT CA. No validation performed ***"); $isRootCA = true; } @@ -417,11 +418,11 @@ public function pkcs7_sign($binaryData) { if(@$LTVvalidation['ocsp'] || @$LTVvalidation['crl']) { $LTVvalidation_ocsp .= $LTVvalidation['ocsp']; $LTVvalidation_crl .= $LTVvalidation['crl']; - $hexEmbedCerts[] = bin2hex($LTVvalidation['issuer']); + $hexEmbedCerts[] = bin2hex((string) $LTVvalidation['issuer']); } - + if($certtoCheck['tbsCertificate']['issuer']['hexdump'] == $certtoCheck['tbsCertificate']['subject']['hexdump']) { // check whether root ca - if(openssl_public_decrypt(hex2bin($certtoCheck['signatureValue']), $decrypted, $x509->x509_der2pem($curr_issuer), OPENSSL_PKCS1_PADDING)) { + if(openssl_public_decrypt(hex2bin((string) $certtoCheck['signatureValue']), $decrypted, $x509->x509_der2pem($curr_issuer), OPENSSL_PKCS1_PADDING)) { p_debug("========= FINISH Reached ROOT CA \"{$certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0]}\"==============="); $LTVvalidationEnd = true; break; @@ -429,83 +430,85 @@ public function pkcs7_sign($binaryData) { } } } - + if($LTVvalidationEnd) { p_debug(" LTV Validation SUCCESS\n"); $ocsp = ''; - if(!empty($LTVvalidation_ocsp)) { - $ocsp = asn1::expl(1, - asn1::seq( - $LTVvalidation_ocsp - ) - ); + if(! empty($LTVvalidation_ocsp)) { + $ocsp = asn1::expl( + 1, + asn1::seq( + $LTVvalidation_ocsp + ) + ); } $crl = ''; - if(!empty($LTVvalidation_crl)) { - $crl = asn1::expl(0, - asn1::seq( - $LTVvalidation_crl - ) - ); + if(! empty($LTVvalidation_crl)) { + $crl = asn1::expl( + 0, + asn1::seq( + $LTVvalidation_crl + ) + ); } $appendLTV = asn1::seq( - "06092A864886F72F010108". // adbe-revocationInfoArchival (1.2.840.113583.1.1.8) + "06092A864886F72F010108" . // adbe-revocationInfoArchival (1.2.840.113583.1.1.8) asn1::set( - asn1::seq( - $ocsp. + asn1::seq( + $ocsp . $crl - ) + ) ) - ); + ); } else { p_warning(" LTV Validation FAILED!\n"); } } foreach($this->signature_data['extracerts'] ?? [] as $extracert) { $hex_extracert = bin2hex($x509->x509_pem2der($extracert)); - if(!in_array($hex_extracert, $hexEmbedCerts)) { + if(! in_array($hex_extracert, $hexEmbedCerts)) { $hexEmbedCerts[] = $hex_extracert; } } } $messageDigest = hash($hashAlgorithm, $binaryData); - $authenticatedAttributes= asn1::seq( - '06092A864886F70D010903'. //OBJ_pkcs9_contentType 1.2.840.113549.1.9.3 + $authenticatedAttributes = asn1::seq( + '06092A864886F70D010903' . //OBJ_pkcs9_contentType 1.2.840.113549.1.9.3 asn1::set('06092A864886F70D010701') //OBJ_pkcs7_data 1.2.840.113549.1.7.1 - ). + ) . asn1::seq( // signing time - '06092A864886F70D010905'. //OBJ_pkcs9_signingTime 1.2.840.113549.1.9.5 + '06092A864886F70D010905' . //OBJ_pkcs9_signingTime 1.2.840.113549.1.9.5 asn1::set( asn1::utime(date("ymdHis")) //UTTC Time ) - ). + ) . asn1::seq( // messageDigest - '06092A864886F70D010904'. //OBJ_pkcs9_messageDigest 1.2.840.113549.1.9.4 + '06092A864886F70D010904' . //OBJ_pkcs9_messageDigest 1.2.840.113549.1.9.4 asn1::set(asn1::oct($messageDigest)) - ). + ) . $appendLTV; $tohash = asn1::set($authenticatedAttributes); $hash = hash($hashAlgorithm, hex2bin($tohash)); - $toencrypt = asn1::seq( - asn1::seq($hexOidHashAlgos[$hashAlgorithm]."0500"). // OBJ $messageDigest & OBJ_null + $toencrypt = asn1::seq( + asn1::seq($hexOidHashAlgos[$hashAlgorithm] . "0500") . // OBJ $messageDigest & OBJ_null asn1::oct($hash) ); $pkey = $this->signature_data['privkey']; - if(!openssl_private_encrypt(hex2bin($toencrypt), $encryptedDigest, $pkey, OPENSSL_PKCS1_PADDING)) { + if(! openssl_private_encrypt(hex2bin($toencrypt), $encryptedDigest, $pkey, OPENSSL_PKCS1_PADDING)) { p_error("openssl_private_encrypt error! can't encrypt"); return false; } - $hexencryptedDigest = bin2hex($encryptedDigest); + $hexencryptedDigest = bin2hex((string) $encryptedDigest); $timeStamp = ''; - if(!empty($this->signature_data['tsa'])) { + if(! empty($this->signature_data['tsa'])) { p_debug(" Timestamping process start..."); if($TSTInfo = self::createTimestamp($encryptedDigest, $hashAlgorithm)) { p_debug(" Timestamping SUCCESS."); $TimeStampToken = asn1::seq( - "060B2A864886F70D010910020E". // OBJ_id_smime_aa_timeStampToken 1.2.840.113549.1.9.16.2.14 + "060B2A864886F70D010910020E" . // OBJ_id_smime_aa_timeStampToken 1.2.840.113549.1.9.16.2.14 asn1::set($TSTInfo) ); - $timeStamp = asn1::expl(1,$TimeStampToken); + $timeStamp = asn1::expl(1, $TimeStampToken); } else { p_warning(" Timestamping FAILED!"); } @@ -513,28 +516,28 @@ public function pkcs7_sign($binaryData) { $issuerName = $certParse['tbsCertificate']['issuer']['hexdump']; $serialNumber = $certParse['tbsCertificate']['serialNumber']; $signerinfos = asn1::seq( - asn1::int('1'). - asn1::seq($issuerName . asn1::int($serialNumber)). - asn1::seq($hexOidHashAlgos[$hashAlgorithm].'0500'). - asn1::expl(0, $authenticatedAttributes). + asn1::int('1') . + asn1::seq($issuerName . asn1::int($serialNumber)) . + asn1::seq($hexOidHashAlgos[$hashAlgorithm] . '0500') . + asn1::expl(0, $authenticatedAttributes) . asn1::seq( - '06092A864886F70D010101'. //OBJ_rsaEncryption + '06092A864886F70D010101' . //OBJ_rsaEncryption '0500' - ). - asn1::oct($hexencryptedDigest). + ) . + asn1::oct($hexencryptedDigest) . $timeStamp ); - $certs = asn1::expl(0,implode('', $hexEmbedCerts)); + $certs = asn1::expl(0, implode('', $hexEmbedCerts)); $pkcs7contentSignedData = asn1::seq( - asn1::int('1'). - asn1::set(asn1::seq($hexOidHashAlgos[$hashAlgorithm].'0500')). - asn1::seq('06092A864886F70D010701'). //OBJ_pkcs7_data - $certs. + asn1::int('1') . + asn1::set(asn1::seq($hexOidHashAlgos[$hashAlgorithm] . '0500')) . + asn1::seq('06092A864886F70D010701') . //OBJ_pkcs7_data + $certs . asn1::set($signerinfos) ); $pkcs7ContentInfo = asn1::seq( - "06092A864886F70D010702". // Hexadecimal form of pkcs7-signedData - asn1::expl(0,$pkcs7contentSignedData) + "06092A864886F70D010702" . // Hexadecimal form of pkcs7-signedData + asn1::expl(0, $pkcs7contentSignedData) ); return $pkcs7ContentInfo; } diff --git a/src/helpers/DependencyTreeObject.php b/src/helpers/DependencyTreeObject.php index d611ed5..635cf4d 100644 --- a/src/helpers/DependencyTreeObject.php +++ b/src/helpers/DependencyTreeObject.php @@ -20,24 +20,28 @@ */ namespace ddn\sapp\helpers; -use function ddn\sapp\helpers\p_warning; + +use Stringable; /** * A class for the PDFObjects in the dependency tree */ -class DependencyTreeObject { - function __construct($oid, $info = null) { - $this->info = $info; +class DependencyTreeObject implements Stringable { + private int $is_child; + + function __construct( + private int $oid, + private mixed $info = null + ) { $this->is_child = 0; - $this->oid = $oid; } /** * Function that links one object to its parent (i.e. adds the object to the list of children of this object) * - the function increases the amount of times that one object has been added to a parent object, to detect problems in building the tree */ - function addchild($oid, $o) { - if (!isset($this->children)) $this->children = []; + function addchild($oid, $o): void { + if (! isset($this->children)) $this->children = []; $this->children[$oid] = $o; if ($o->is_child != 0) p_warning("object $o->oid is already a child of other object"); @@ -48,7 +52,7 @@ function addchild($oid, $o) { /** * This is an iterator for the children of this object */ - function children() { + function children(): \Generator { if (isset($this->children)) foreach ($this->children as $oid => $object) { yield $oid; @@ -58,22 +62,22 @@ function children() { /** * Gets a string that represents the object, prepending a number of spaces, proportional to the depth in the tree */ - protected function _getstr($spaces = "", $mychcount = 0) { + protected function _getstr(?string $spaces = "", $mychcount = 0): string { // $info = $this->oid . ($this->info?" ($this->info)":"") . (($this->is_child > 1)?" $this->is_child":""); - $info = $this->oid . ($this->info?" ($this->info)":""); + $info = $this->oid . ($this->info ? " ($this->info)" : ""); if ($spaces === null) { - $lines = [ "{$spaces} " . json_decode('"\u2501"') . " $info" ]; + $lines = ["{$spaces} " . json_decode('"\u2501"') . " $info"]; } else if ($mychcount == 0) - $lines = [ "{$spaces} " . json_decode('"\u2514\u2500"') . " $info" ]; + $lines = ["{$spaces} " . json_decode('"\u2514\u2500"') . " $info"]; else - $lines = [ "{$spaces} " . json_decode('"\u251c\u2500"') . " $info" ]; + $lines = ["{$spaces} " . json_decode('"\u251c\u2500"') . " $info"]; if (isset($this->children)) { $chcount = count($this->children); foreach ($this->children as $oid => $child) { $chcount--; if (($spaces === null) || ($mychcount == 0)) { - array_push($lines, $child->_getstr($spaces . " " , $chcount)); + array_push($lines, $child->_getstr($spaces . " ", $chcount)); } else array_push($lines, $child->_getstr($spaces . " " . json_decode('"\u2502"'), $chcount)); } @@ -81,9 +85,9 @@ protected function _getstr($spaces = "", $mychcount = 0) { return implode("\n", $lines); } - protected function _old_getstr($depth = 0) { + protected function _old_getstr($depth = 0): string { $spaces = str_repeat(" " . json_decode('"\u2502"'), $depth); - $lines = [ "{$spaces} " . json_decode('"\u251c\u2500"') ." " . $this->oid . ($this->info?" ($this->info)":"") . (($this->is_child > 1)?" $this->is_child":"") ]; + $lines = ["{$spaces} " . json_decode('"\u251c\u2500"') . " " . $this->oid . ($this->info ? " ($this->info)" : "") . (($this->is_child > 1) ? " $this->is_child" : "")]; if (isset($this->children)) { foreach ($this->children as $oid => $child) { array_push($lines, $child->_getstr($depth + 1)); @@ -92,28 +96,30 @@ protected function _old_getstr($depth = 0) { return implode("\n", $lines); } - public function __toString() { - return $this->_getstr(null, isset($this->children)?count($this->children):0); + public function __toString(): string { + return (string) $this->_getstr(null, isset($this->children) ? count($this->children) : 0); } } - -/** - * Fields that are blacklisted for referencing the fields; - * i.e. a if a reference to a object appears in a fields in the blacklist, it won't be considered as a reference to other object to build the tree - * +/** + * Fields that are blacklisted for referencing the fields; + * i.e. a if a reference to a object appears in a fields in the blacklist, it won't be considered as a reference to other object to build the tree + * * The blacklist is indexed by the type of the node; * means "any type" (including the others in the blacklist) */ const BLACKLIST = [ // Field "Parent" for any type of object - "*" => [ "Parent" ], + "*" => ["Parent"], // Field "P" for nodes of type "Annot" - "Annot" => [ "P" ] + "Annot" => ["P"], ]; -function references_in_object($object, $oid = false) { +/** + * @return mixed[] + */ +function references_in_object(array $object, $oid = false): array { $type = $object["Type"]; - if ($type !== false) + if ($type !== false) $type = $type->val(); else $type = ""; @@ -124,11 +130,11 @@ function references_in_object($object, $oid = false) { $valid = true; // We'll skip those blacklisted fields - if (in_array($key, BLACKLIST["*"])) + if (in_array($key, BLACKLIST["*"])) continue; if (array_key_exists($type, BLACKLIST)) - if (in_array($key, BLACKLIST[$type])) + if (in_array($key, BLACKLIST[$type])) continue; $r_objects = []; @@ -142,8 +148,8 @@ function references_in_object($object, $oid = false) { // If the value does not have the form of a reference, it returns false if ($r_objects === false) continue; - - if (!is_array($r_objects)) $r_objects = [ $r_objects ]; + + if (! is_array($r_objects)) $r_objects = [$r_objects]; } // p_debug($key . "=>" . implode(",",$r_objects)); @@ -152,4 +158,4 @@ function references_in_object($object, $oid = false) { // Return the list of references in the fields of the object return $references; -} \ No newline at end of file +} diff --git a/src/helpers/StreamReader.php b/src/helpers/StreamReader.php index de83a1b..8961c8b 100644 --- a/src/helpers/StreamReader.php +++ b/src/helpers/StreamReader.php @@ -34,7 +34,9 @@ */ class StreamReader { protected $_buffer = ""; - protected $_bufferlen = 0; + + protected int $_bufferlen; + protected $_pos = 0; public function __construct($string = null, $offset = 0) { @@ -42,7 +44,7 @@ public function __construct($string = null, $offset = 0) { $string = ""; $this->_buffer = $string; - $this->_bufferlen = strlen($string); + $this->_bufferlen = strlen((string) $string); $this->goto($offset); } @@ -61,9 +63,9 @@ public function nextchar() { * @param n the number of chars to read * @return str the substring obtained (with at most, n chars) */ - public function nextchars($n) { + public function nextchars($n): string { $n = min($n, $this->_bufferlen - $this->_pos); - $retval = substr($this->_buffer, $this->_pos, $n); + $retval = substr((string) $this->_buffer, $this->_pos, $n); $this->_pos += $n; return $retval; } @@ -83,7 +85,7 @@ public function currentchar() { * Returns whether the stream has finished or not * @return finished true if there are no more chars to read from the stream; false otherwise */ - public function eos() { + public function eos(): bool { return $this->_pos >= $this->_bufferlen; } @@ -91,7 +93,7 @@ public function eos() { * Sets the position of the buffer to the position in the parameter * @param pos the position to which the buffer must be set */ - public function goto($pos = 0) { + public function goto($pos = 0): void { $this->_pos = min(max(0, $pos), $this->_bufferlen); } @@ -100,11 +102,11 @@ public function goto($pos = 0) { * @param length length of the substring to obtain (0 or <0 will obtain the whole buffer from the current position) * @return substr the substring */ - public function substratpos($length = 0) { + public function substratpos($length = 0): string { if ($length > 0) - return substr($this->_buffer, $this->_pos, $length); + return substr((string) $this->_buffer, $this->_pos, $length); else - return substr($this->_buffer, $this->_pos); + return substr((string) $this->_buffer, $this->_pos); } /** @@ -119,7 +121,7 @@ public function getpos() { * Obtains the size of the buffer * @return size the size of the buffer */ - public function size() { + public function size(): int { return $this->_bufferlen; } } diff --git a/src/helpers/UUID.php b/src/helpers/UUID.php index 7810793..00c2d55 100644 --- a/src/helpers/UUID.php +++ b/src/helpers/UUID.php @@ -1,25 +1,27 @@ "ASN1_EOC", - "01" => "ASN1_BOOLEAN", - "02" => "ASN1_INTEGER", - "03" => "ASN1_BIT_STRING", - "04" => "ASN1_OCTET_STRING", - "05" => "ASN1_NULL", - "06" => "ASN1_OBJECT", - "07" => "ASN1_OBJECT_DESCRIPTOR", - "08" => "ASN1_EXTERNAL", - "09" => "ASN1_REAL", - "0a" => "ASN1_ENUMERATED", - "0c" => "ASN1_UTF8STRING", - "30" => "ASN1_SEQUENCE", - "31" => "ASN1_SET", - "12" => "ASN1_NUMERICSTRING", - "13" => "ASN1_PRINTABLESTRING", - "14" => "ASN1_T61STRING", - "15" => "ASN1_VIDEOTEXSTRING", - "16" => "ASN1_IA5STRING", - "17" => "ASN1_UTCTIME", - "18" => "ASN1_GENERALIZEDTIME", - "19" => "ASN1_GRAPHICSTRING", - "1a" => "ASN1_VISIBLESTRING", - "1b" => "ASN1_GENERALSTRING", - "1c" => "ASN1_UNIVERSALSTRING", - "1d" => "ASN1_BMPSTRING" - ); - return array_key_exists($id,$asn1_Types)?$asn1_Types[$id]:$id; + $asn1_Types = [ + "00" => "ASN1_EOC", + "01" => "ASN1_BOOLEAN", + "02" => "ASN1_INTEGER", + "03" => "ASN1_BIT_STRING", + "04" => "ASN1_OCTET_STRING", + "05" => "ASN1_NULL", + "06" => "ASN1_OBJECT", + "07" => "ASN1_OBJECT_DESCRIPTOR", + "08" => "ASN1_EXTERNAL", + "09" => "ASN1_REAL", + "0a" => "ASN1_ENUMERATED", + "0c" => "ASN1_UTF8STRING", + "30" => "ASN1_SEQUENCE", + "31" => "ASN1_SET", + "12" => "ASN1_NUMERICSTRING", + "13" => "ASN1_PRINTABLESTRING", + "14" => "ASN1_T61STRING", + "15" => "ASN1_VIDEOTEXSTRING", + "16" => "ASN1_IA5STRING", + "17" => "ASN1_UTCTIME", + "18" => "ASN1_GENERALIZEDTIME", + "19" => "ASN1_GRAPHICSTRING", + "1a" => "ASN1_VISIBLESTRING", + "1b" => "ASN1_GENERALSTRING", + "1c" => "ASN1_UNIVERSALSTRING", + "1d" => "ASN1_BMPSTRING", + ]; + return array_key_exists($id, $asn1_Types) ? $asn1_Types[$id] : $id; } /** @@ -60,7 +61,7 @@ protected static function oneParse($hex) { if($hex == '') { return false; } - if(!@ctype_xdigit($hex) || @strlen($hex)%2!=0) { + if(! @ctype_xdigit($hex) || @strlen($hex) % 2 != 0) { echo "input:\"$hex\" not hex string!.\n"; return false; } @@ -69,29 +70,29 @@ protected static function oneParse($hex) { $asn1_type = substr($hex, 0, 2); $tlv_tagLength = hexdec(substr($hex, 2, 2)); if($tlv_tagLength > 127) { - $tlv_lengthLength = $tlv_tagLength-128; - $tlv_valueLength = substr($hex, 4, ($tlv_lengthLength*2)); + $tlv_lengthLength = $tlv_tagLength - 128; + $tlv_valueLength = substr($hex, 4, ($tlv_lengthLength * 2)); } else { $tlv_lengthLength = 0; - $tlv_valueLength = substr($hex, 2, 2+($tlv_lengthLength*2)); + $tlv_valueLength = substr($hex, 2, 2 + ($tlv_lengthLength * 2)); } - if($tlv_lengthLength >4) { // limit tlv_lengthLength to FFFF + if($tlv_lengthLength > 4) { // limit tlv_lengthLength to FFFF return false; } $tlv_valueLength = hexdec($tlv_valueLength); - $totalTlLength = 2+2+($tlv_lengthLength*2); - $reduction = 2+2+($tlv_lengthLength*2)+($tlv_valueLength*2); - $tlv_value = substr($hex, $totalTlLength, $tlv_valueLength*2); - $remain = substr($hex, $totalTlLength+($tlv_valueLength*2)); - $newhexdump = substr($hex, 0, $totalTlLength+($tlv_valueLength*2)); - $result[] = array( - 'tlv_tagLength'=>strlen(dechex($tlv_tagLength))%2==0?dechex($tlv_tagLength):'0'.dechex($tlv_tagLength), - 'tlv_lengthLength'=>$tlv_lengthLength, - 'tlv_valueLength'=>$tlv_valueLength, - 'newhexdump'=>$newhexdump, - 'typ'=>$asn1_type, - 'tlv_value'=>$tlv_value - ); + $totalTlLength = 2 + 2 + ($tlv_lengthLength * 2); + $reduction = 2 + 2 + ($tlv_lengthLength * 2) + ($tlv_valueLength * 2); + $tlv_value = substr($hex, $totalTlLength, $tlv_valueLength * 2); + $remain = substr($hex, $totalTlLength + ($tlv_valueLength * 2)); + $newhexdump = substr($hex, 0, $totalTlLength + ($tlv_valueLength * 2)); + $result[] = [ + 'tlv_tagLength' => strlen(dechex($tlv_tagLength)) % 2 == 0 ? dechex($tlv_tagLength) : '0' . dechex($tlv_tagLength), + 'tlv_lengthLength' => $tlv_lengthLength, + 'tlv_valueLength' => $tlv_valueLength, + 'newhexdump' => $newhexdump, + 'typ' => $asn1_type, + 'tlv_value' => $tlv_value, + ]; if($remain == '') { // if remains string was empty & contents also empty, function return FALSE $stop = true; } else { @@ -107,8 +108,8 @@ protected static function oneParse($hex) { * @param int $maxDepth maximum parsing depth * @return array asn.1 structure recursively to specific depth */ - public static function parse($hex, $maxDepth=5) { - $result = array(); + public static function parse($hex, $maxDepth = 5): array { + $result = []; static $currentDepth = 0; if($asn1parse_array = self::oneParse($hex)) { foreach($asn1parse_array as $ff){ @@ -116,8 +117,8 @@ public static function parse($hex, $maxDepth=5) { unset($info); $k = $ff['typ']; $v = $ff['tlv_value']; - $info['depth']=$currentDepth; - $info['hexdump']=$ff['newhexdump']; + $info['depth'] = $currentDepth; + $info['hexdump'] = $ff['newhexdump']; $info['type'] = $k; $info['typeName'] = self::type($k); $info['value_hex'] = $v; @@ -125,7 +126,7 @@ public static function parse($hex, $maxDepth=5) { if($k == '06') { } else if(in_array($k, ['13', '18'])) { - $info['value'] = hex2bin($info['value_hex']); + $info['value'] = hex2bin((string) $info['value_hex']); } else if(in_array($k, ['03', '02', 'a04'])) { $info['value'] = $v; } else { @@ -145,7 +146,6 @@ public static function parse($hex, $maxDepth=5) { } // =====End ASN.1 Parser section===== - // =====Begin ASN.1 Builder section===== /** * create asn.1 TLV tag length, length length and value length @@ -153,15 +153,15 @@ public static function parse($hex, $maxDepth=5) { * @param string $str string value of asn.1 * @return string hex of asn.1 TLV tag length */ - protected static function asn1_header($str) { - $len = strlen($str)/2; + protected static function asn1_header($str): string { + $len = strlen($str) / 2; $ret = dechex($len); - if(strlen($ret)%2 != 0) { + if(strlen($ret) % 2 != 0) { $ret = "0$ret"; } - $headerLength = strlen($ret)/2; + $headerLength = strlen($ret) / 2; if($len > 127) { - $ret = "8".$headerLength.$ret; + $ret = "8" . $headerLength . $ret; } return $ret; } @@ -169,25 +169,25 @@ protected static function asn1_header($str) { /** * create various dynamic function for asn1 */ - private static function asn1Tag($name) { - $functionList = array( - 'seq'=>'30', - 'oct'=>'04', - 'obj'=>'06', - 'bit'=>'03', - 'printable'=>'13', - 'int'=>'02', - 'set'=>'31', - 'expl'=>'a', - 'utime'=>'17', - 'gtime'=>'18', - 'utf8'=>'0c', - 'ia5'=>'16', - 'visible'=>'1a', - 't61'=>'14', - 'impl'=>'80', - 'other'=>'' - ); + private static function asn1Tag($name): string|false { + $functionList = [ + 'seq' => '30', + 'oct' => '04', + 'obj' => '06', + 'bit' => '03', + 'printable' => '13', + 'int' => '02', + 'set' => '31', + 'expl' => 'a', + 'utime' => '17', + 'gtime' => '18', + 'utf8' => '0c', + 'ia5' => '16', + 'visible' => '1a', + 't61' => '14', + 'impl' => '80', + 'other' => '', + ]; if(array_key_exists($name, $functionList)) { return $functionList[$name]; } else { @@ -197,26 +197,26 @@ private static function asn1Tag($name) { } public static function __callStatic($func, $params) { - $func = strtolower($func); + $func = strtolower((string) $func); $asn1Tag = self::asn1Tag($func); if($asn1Tag !== false){ $num = $asn1Tag; //valu of array $hex = $params[0]; $val = $hex; if(in_array($func, ['printable', 'utf8', 'ia5', 'visible', 't61'])) { // ($string) - $val = bin2hex($hex); + $val = bin2hex((string) $hex); } if($func == 'int') { - $val = (strlen($val)%2 != 0)?"0$val":"$val"; + $val = (strlen((string) $val) % 2 != 0) ? "0$val" : "$val"; } if($func == 'expl') { //expl($num, $hex) - $num = $num.$params[0]; + $num = $num . $params[0]; $val = $params[1]; } if($func == 'impl') { //impl($num="0") - $val = (!$val)?"00":$val; - $val = (strlen($val)%2 != 0)?"0$val":$val; - return $num.$val; + $val = (! $val) ? "00" : $val; + $val = (strlen((string) $val) % 2 != 0) ? "0$val" : $val; + return $num . $val; } if($func == 'other') { //OTHER($id, $hex, $chr = false) $id = $params[0]; @@ -224,9 +224,9 @@ public static function __callStatic($func, $params) { $chr = @$params[2]; $str = $hex; if($chr != false) { - $str = bin2hex($hex); + $str = bin2hex((string) $hex); } - $ret = "$id".self::asn1_header($str).$str; + $ret = "$id" . self::asn1_header($str) . $str; return $ret; } if($func == 'utime') { @@ -235,10 +235,10 @@ public static function __callStatic($func, $params) { date_default_timezone_set("UTC"); $time = date("ymdHis", $time); date_default_timezone_set($oldTz); - $val = bin2hex($time."Z"); + $val = bin2hex($time . "Z"); } if($func == 'gtime') { - if(!$time = strtotime($params[0])) { + if(! $time = strtotime((string) $params[0])) { // echo "asn1::GTIME function strtotime cant recognize time!! please check at input=\"{$params[0]}\""; return false; } @@ -246,10 +246,10 @@ public static function __callStatic($func, $params) { // date_default_timezone_set("UTC"); $time = date("YmdHis", $time); date_default_timezone_set($oldTz); - $val = bin2hex($time."Z"); + $val = bin2hex($time . "Z"); } $hdr = self::asn1_header($val); - return $num.$hdr.$val; + return $num . $hdr . $val; } else { // echo "asn1 \"$func\" not exists!"; } diff --git a/src/helpers/contentgeneration.php b/src/helpers/contentgeneration.php index b87cf7a..c5a4b27 100644 --- a/src/helpers/contentgeneration.php +++ b/src/helpers/contentgeneration.php @@ -21,33 +21,24 @@ namespace ddn\sapp\helpers; -use ddn\sapp\PDFBaseDoc; -use ddn\sapp\PDFBaseObject; -use ddn\sapp\pdfvalue\PDFValueObject; use ddn\sapp\pdfvalue\PDFValueList; +use ddn\sapp\pdfvalue\PDFValueObject; use ddn\sapp\pdfvalue\PDFValueReference; use ddn\sapp\pdfvalue\PDFValueType; -use ddn\sapp\pdfvalue\PDFValueHexString; -use ddn\sapp\pdfvalue\PDFValueString; - -use function ddn\sapp\helpers\get_random_string; -use function ddn\sapp\helpers\mime_to_ext; -use function ddn\sapp\helpers\_parsejpg; -use function ddn\sapp\helpers\_parsepng; -use function ddn\sapp\helpers\p_error; +use finfo; -function tx($x, $y) { +function tx($x, $y): string { return sprintf(" 1 0 0 1 %.2F %.2F cm", $x, $y); } -function sx($w, $h) { +function sx($w, $h): string { return sprintf(" %.2F 0 0 %.2F 0 0 cm", $w, $h); } -function deg2rad($angle) { +function deg2rad($angle): float { return $angle * pi() / 180; } -function rx($angle) { +function rx($angle): string { $angle = deg2rad($angle); - return sprintf(" %.2F %.2F %.2F %.2F 0 0 cm", cos($angle), sin($angle), -sin($angle), cos($angle)); + return sprintf(" %.2F %.2F %.2F %.2F 0 0 cm", cos($angle), sin($angle), -sin($angle), cos($angle)); } /** @@ -55,24 +46,25 @@ function rx($angle) { * NOTE: the image inclusion is taken from http://www.fpdf.org/; this is a translation * of function _putimage */ -function _create_image_objects($info, $object_factory) { +function _create_image_objects($info, $object_factory): array { $objects = []; - $image = call_user_func($object_factory, + $image = call_user_func( + $object_factory, [ 'Type' => '/XObject', 'Subtype' => '/Image', 'Width' => $info['w'], 'Height' => $info['h'], - 'ColorSpace' => [ ], + 'ColorSpace' => [], 'BitsPerComponent' => $info['bpc'], - 'Length' => strlen($info['data']), - ] + 'Length' => strlen((string) $info['data']), + ] ); switch ($info['cs']) { case 'Indexed': - $data = gzcompress($info['pal']); + $data = gzcompress((string) $info['pal']); $streamobject = call_user_func($object_factory, [ 'Filter' => '/FlateDecode', 'Length' => strlen($data), @@ -80,7 +72,7 @@ function _create_image_objects($info, $object_factory) { $streamobject->set_stream($data); $image['ColorSpace']->push([ - '/Indexed', '/DeviceRGB', (strlen($info['pal']) / 3) - 1, new PDFValueReference($streamobject->get_oid()) + '/Indexed', '/DeviceRGB', (strlen((string) $info['pal']) / 3) - 1, new PDFValueReference($streamobject->get_oid()), ]); array_push($objects, $streamobject); break; @@ -102,13 +94,13 @@ function _create_image_objects($info, $object_factory) { if (isset($info['smask'])) { $smaskinfo = [ - 'w' => $info['w'], - 'h' => $info['h'], - 'cs' => 'DeviceGray', - 'bpc' => 8, - 'f' => $info['f'], - 'dp' => '/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns '.$info['w'], - 'data' => $info['smask'] + 'w' => $info['w'], + 'h' => $info['h'], + 'cs' => 'DeviceGray', + 'bpc' => 8, + 'f' => $info['f'], + 'dp' => '/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns ' . $info['w'], + 'data' => $info['smask'], ]; // In principle, it may return multiple objects @@ -124,12 +116,12 @@ function _create_image_objects($info, $object_factory) { return $objects; } -function is_base64($string){ +function is_base64($string): bool{ // Check if there are valid base64 characters - if (!preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $string)) return false; + if (! preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', (string) $string)) return false; // Decode the string in strict mode and check the results - $decoded = base64_decode($string, true); + $decoded = base64_decode((string) $string, true); if(false === $decoded) return false; // Encode the string again @@ -149,22 +141,22 @@ function is_base64($string){ * @param w width of the rectangle in which to appear the image (image will be scaled, and the units are "content-defined" (i.e. depending on the size of the page)) * @param h height of the rectangle in which to appear the image (image will be scaled, and the units are "content-defined" (i.e. depending on the size of the page)) * @param angle the rotation angle in degrees; the image will be rotated using the center - * @param keep_proportions if true, the image will keep the proportions when rotated, then the image will not occupy the full + * @param keep_proportions if true, the image will keep the proportions when rotated, then the image will not occupy the full * @return result an array with the next fields: * "images": objects of the corresponding images (i.e. position [0] is the image, the rest elements are masks, if needed) * "resources": PDFValueObject with keys that needs to be incorporated to the resources of the object in which the images will appear * "alpha": true if the image has alpha * "command": pdf command to draw the image */ -function _add_image($object_factory, $filename, $x=0, $y=0, $w=0, $h=0, $angle = 0, $keep_proportions = true) { +function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, $angle = 0, $keep_proportions = true) { if (empty($filename)) return p_error('invalid image name or stream'); if ($filename[0] === '@') { - $filecontent = substr($filename, 1); + $filecontent = substr((string) $filename, 1); } else if (is_base64($filename)) { - $filecontent = base64_decode($filename); + $filecontent = base64_decode((string) $filename, true); } else { $filecontent = @file_get_contents($filename); @@ -172,7 +164,7 @@ function _add_image($object_factory, $filename, $x=0, $y=0, $w=0, $h=0, $angle = return p_error("failed to get the image"); } - $finfo = new \finfo(); + $finfo = new finfo(); $content_type = $finfo->buffer($filecontent, FILEINFO_MIME_TYPE); $ext = mime_to_ext($content_type); @@ -200,14 +192,14 @@ function _add_image($object_factory, $filename, $x=0, $y=0, $w=0, $h=0, $angle = if ($h === null) $h = -96; - if($w<0) - $w = -$info['w']*72/$w; - if($h<0) - $h = -$info['h']*72/$h; - if($w==0) - $w = $h*$info['w']/$info['h']; - if($h==0) - $h = $w*$info['h']/$info['w']; + if($w < 0) + $w = -$info['w'] * 72 / $w; + if($h < 0) + $h = -$info['h'] * 72 / $h; + if($w == 0) + $w = $h * $info['w'] / $info['h']; + if($h == 0) + $h = $w * $info['h'] / $info['w']; $images_objects = _create_image_objects($info, $object_factory); @@ -253,16 +245,21 @@ function _add_image($object_factory, $filename, $x=0, $y=0, $w=0, $h=0, $angle = if ($angle != 0) { $data .= tx(0.5, 0.5); $data .= rx($angle); - $data .= tx(-0.5,-0.5); + $data .= tx(-0.5, -0.5); } $data .= sprintf(" /%s Do Q", $info['i']); $resources = new PDFValueObject( [ - 'ProcSet' => [ '/PDF', '/Text', '/ImageB', '/ImageC', '/ImageI' ], + 'ProcSet' => ['/PDF', '/Text', '/ImageB', '/ImageC', '/ImageI'], 'XObject' => new PDFValueObject ([ - $info['i'] => new PDFValueReference($images_objects[0]->get_oid()), - ]) + $info['i'] => new PDFValueReference($images_objects[0]->get_oid()), + ]), ]); - return [ "image" => $images_objects[0], 'command' => $data, 'resources' => $resources, 'alpha' => $add_alpha ]; + return [ + "image" => $images_objects[0], + 'command' => $data, + 'resources' => $resources, + 'alpha' => $add_alpha, + ]; } diff --git a/src/helpers/fpdfhelpers.php b/src/helpers/fpdfhelpers.php index 561f0ab..0cc306a 100644 --- a/src/helpers/fpdfhelpers.php +++ b/src/helpers/fpdfhelpers.php @@ -17,7 +17,7 @@ You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . - + --------- The code in this file is an adaptation of a part of the code included in @@ -34,25 +34,31 @@ */ namespace ddn\sapp\helpers; -use function ddn\sapp\helpers\p_error; function _parsejpg($filecontent) { // Extract info from a JPEG file $a = getimagesizefromstring($filecontent); - if(!$a) + if(! $a) return p_error('Missing or incorrect image'); - if($a[2]!=2) - return perror('Not a JPEG image'); - if(!isset($a['channels']) || $a['channels']==3) + if($a[2] != 2) + return p_error('Not a JPEG image'); + if(! isset($a['channels']) || $a['channels'] == 3) $colspace = 'DeviceRGB'; - elseif($a['channels']==4) + elseif($a['channels'] == 4) $colspace = 'DeviceCMYK'; else $colspace = 'DeviceGray'; - $bpc = isset($a['bits']) ? $a['bits'] : 8; + $bpc = $a['bits'] ?? 8; $data = $filecontent; - return array('w'=>$a[0], 'h'=>$a[1], 'cs'=>$colspace, 'bpc'=>$bpc, 'f'=>'DCTDecode', 'data'=>$data); + return [ + 'w' => $a[0], + 'h' => $a[1], + 'cs' => $colspace, + 'bpc' => $bpc, + 'f' => 'DCTDecode', + 'data' => $data, +]; } function _parsepng($filecontent) @@ -66,35 +72,35 @@ function _parsepng($filecontent) function _parsepngstream(&$f) { // Check signature - if(($res=_readstream($f,8))!=chr(137).'PNG'.chr(13).chr(10).chr(26).chr(10)) + if(($res = _readstream($f, 8)) != chr(137) . 'PNG' . chr(13) . chr(10) . chr(26) . chr(10)) return p_error("Not a PNG image $res"); // Read header chunk - _readstream($f,4); - if(_readstream($f,4)!='IHDR') + _readstream($f, 4); + if(_readstream($f, 4) != 'IHDR') return p_error('Incorrect PNG image'); $w = _readint($f); $h = _readint($f); - $bpc = ord(_readstream($f,1)); - if($bpc>8) + $bpc = ord(_readstream($f, 1)); + if($bpc > 8) return p_error('16-bit depth not supported'); - $ct = ord(_readstream($f,1)); - if($ct==0 || $ct==4) + $ct = ord(_readstream($f, 1)); + if($ct == 0 || $ct == 4) $colspace = 'DeviceGray'; - elseif($ct==2 || $ct==6) + elseif($ct == 2 || $ct == 6) $colspace = 'DeviceRGB'; - elseif($ct==3) + elseif($ct == 3) $colspace = 'Indexed'; else return p_error('Unknown color type'); - if(ord(_readstream($f,1))!=0) + if(ord(_readstream($f, 1)) != 0) return p_error('Unknown compression method'); - if(ord(_readstream($f,1))!=0) + if(ord(_readstream($f, 1)) != 0) return p_error('Unknown filter method'); - if(ord(_readstream($f,1))!=0) + if(ord(_readstream($f, 1)) != 0) return p_error('Interlacing not supported'); - _readstream($f,4); - $dp = '/Predictor 15 /Colors '.($colspace=='DeviceRGB' ? 3 : 1).' /BitsPerComponent '.$bpc.' /Columns '.$w; + _readstream($f, 4); + $dp = '/Predictor 15 /Colors ' . ($colspace == 'DeviceRGB' ? 3 : 1) . ' /BitsPerComponent ' . $bpc . ' /Columns ' . $w; // Scan chunks looking for palette, transparency and image data $pal = ''; @@ -103,81 +109,90 @@ function _parsepngstream(&$f) do { $n = _readint($f); - $type = _readstream($f,4); - if($type=='PLTE') + $type = _readstream($f, 4); + if($type == 'PLTE') { // Read palette - $pal = _readstream($f,$n); - _readstream($f,4); + $pal = _readstream($f, $n); + _readstream($f, 4); } - elseif($type=='tRNS') + elseif($type == 'tRNS') { // Read transparency info - $t = _readstream($f,$n); - if($ct==0) - $trns = array(ord(substr($t,1,1))); - elseif($ct==2) - $trns = array(ord(substr($t,1,1)), ord(substr($t,3,1)), ord(substr($t,5,1))); + $t = _readstream($f, $n); + if($ct == 0) + $trns = [ord(substr((string) $t, 1, 1))]; + elseif($ct == 2) + $trns = [ord(substr((string) $t, 1, 1)), ord(substr((string) $t, 3, 1)), ord(substr((string) $t, 5, 1))]; else { - $pos = strpos($t,chr(0)); - if($pos!==false) - $trns = array($pos); + $pos = strpos((string) $t, chr(0)); + if($pos !== false) + $trns = [$pos]; } - _readstream($f,4); + _readstream($f, 4); } - elseif($type=='IDAT') + elseif($type == 'IDAT') { // Read image data block - $data .= _readstream($f,$n); - _readstream($f,4); + $data .= _readstream($f, $n); + _readstream($f, 4); } - elseif($type=='IEND') + elseif($type == 'IEND') break; else - _readstream($f,$n+4); + _readstream($f, $n + 4); } while($n); - if($colspace=='Indexed' && empty($pal)) + if($colspace == 'Indexed' && empty($pal)) return p_error('Missing palette in image'); - $info = array('w'=>$w, 'h'=>$h, 'cs'=>$colspace, 'bpc'=>$bpc, 'f'=>'FlateDecode', 'dp'=>$dp, 'pal'=>$pal, 'trns'=>$trns); - if($ct>=4) + $info = [ + 'w' => $w, + 'h' => $h, + 'cs' => $colspace, + 'bpc' => $bpc, + 'f' => 'FlateDecode', + 'dp' => $dp, + 'pal' => $pal, + 'trns' => $trns, + ]; + if($ct >= 4) { // Extract alpha channel - if(!function_exists('gzuncompress')) + if(! function_exists('gzuncompress')) return p_error('Zlib not available, can\'t handle alpha channel'); $data = gzuncompress($data); if ($data === false) return p_error('failed to uncompress the image'); $color = ''; $alpha = ''; - if($ct==4) + if($ct == 4) { // Gray image - $len = 2*$w; - for($i=0;$i<$h;$i++) + $len = 2 * $w; + for($i = 0; $i < $h; $i++) { - $pos = (1+$len)*$i; + $pos = (1 + $len) * $i; $color .= $data[$pos]; $alpha .= $data[$pos]; - $line = substr($data,$pos+1,$len); - $color .= preg_replace('/(.)./s','$1',$line); - $alpha .= preg_replace('/.(.)/s','$1',$line); + $line = substr($data, $pos + 1, $len); + $color .= preg_replace('/(.)./s', '$1', $line); + $alpha .= preg_replace('/.(.)/s', '$1', $line); } } else { // RGB image - $len = 4*$w; - for($i=0;$i<$h;$i++) + $len = 4 * $w; + for($i = 0; $i < $h; $i++) { - $pos = (1+$len)*$i; + $pos = (1 + $len) * $i; $color .= $data[$pos]; $alpha .= $data[$pos]; - $line = substr($data,$pos+1,$len); - $color .= preg_replace('/(.{3})./s','$1',$line); - $alpha .= preg_replace('/.{3}(.)/s','$1',$line); + $line = substr($data, $pos + 1, $len); + $color .= preg_replace('/(.{3})./s', '$1', $line); + $alpha .= preg_replace('/.{3}(.)/s', '$1', $line); } } unset($data); @@ -196,15 +211,15 @@ function _parsepngstream(&$f) function _readstream(&$f, $n) { $res = ""; - while ($n>0 && !$f->eos()) { + while ($n > 0 && ! $f->eos()) { $s = $f->nextchars($n); if ($s === false) return p_error("Error while reading the stream"); - $n -= strlen($s); + $n -= strlen((string) $s); $res .= $s; } - if ($n>0) + if ($n > 0) return p_error('Unexpected end of stream'); return $res; } @@ -212,11 +227,10 @@ function _readstream(&$f, $n) { function _readint(&$f) { // Read a 4-byte integer from stream - $a = unpack('Ni',_readstream($f,4)); + $a = unpack('Ni', (string) _readstream($f, 4)); return $a['i']; } - /* function _readstream($f, $n) { @@ -241,4 +255,4 @@ function _readint($f) $a = unpack('Ni',_readstream($f,4)); return $a['i']; } -*/ \ No newline at end of file +*/ diff --git a/src/helpers/helpers.php b/src/helpers/helpers.php index edcd305..de8bd25 100644 --- a/src/helpers/helpers.php +++ b/src/helpers/helpers.php @@ -21,6 +21,8 @@ namespace ddn\sapp\helpers; +use DateTime; + if (! defined("_DEBUG_LEVEL")) { define('_DEBUG_LEVEL', 3); } @@ -34,7 +36,7 @@ * @param var the variable to output * @return output the result of the var_dump of the variable */ -function var_dump_to_string($var) { +function var_dump_to_string($var): string|false { ob_start(); var_dump($var); $result = ob_get_clean(); @@ -59,10 +61,10 @@ function debug_var(...$vars) { * Function that writes the representation of some vars to * @param vars comma separated list of variables to output */ -function p_debug_var(...$vars) { +function p_debug_var(...$vars): void { // If the debug level is less than 3, suppress debug messages if (_DEBUG_LEVEL < 3) return; - + foreach ($vars as $var) { $e = var_dump_to_string($var); p_stderr($e, "Debug"); @@ -94,7 +96,7 @@ function varval($e) { * @param level the depth level to output (0 will refer to the function that called p_stderr * call itself, 1 to the function that called to the function that called p_stderr) */ -function p_stderr(&$e, $tag = "Error", $level = 1) { +function p_stderr(&$e, $tag = "Error", $level = 1): void { $dinfo = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT); $dinfo = $dinfo[$level]; $e = sprintf("$tag info at %s:%d: %s", $dinfo['file'], $dinfo['line'], varval($e)); @@ -150,11 +152,11 @@ function p_error($e, $retval = false) { * need more than one key to be written) * @return random_string a random string considering the alphabet */ -function get_random_string($length = 8, $extended = false, $hard = false){ +function get_random_string($length = 8, $extended = false, $hard = false): string{ $token = ""; $codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - $codeAlphabet.= "abcdefghijklmnopqrstuvwxyz"; - $codeAlphabet.= "0123456789"; + $codeAlphabet .= "abcdefghijklmnopqrstuvwxyz"; + $codeAlphabet .= "0123456789"; if ($extended === true) { $codeAlphabet .= "!\"#$%&'()*+,-./:;<=>?@[\\]_{}"; } @@ -162,13 +164,13 @@ function get_random_string($length = 8, $extended = false, $hard = false){ $codeAlphabet .= "^`|~"; } $max = strlen($codeAlphabet); - for ($i=0; $i < $length; $i++) { - $token .= $codeAlphabet[random_int(0, $max-1)]; + for ($i = 0; $i < $length; $i++) { + $token .= $codeAlphabet[random_int(0, $max - 1)]; } return $token; } -function get_memory_limit() { +function get_memory_limit(): int { $memory_limit = ini_get('memory_limit'); if (preg_match('/^(\d+)(.)$/', $memory_limit, $matches) === 1) { $memory_limit = intval($matches[1]); @@ -189,12 +191,12 @@ function get_memory_limit() { return $memory_limit; } -function show_bytes($str, $columns = null) { +function show_bytes($str, $columns = null): string { $result = ""; if ($columns === null) - $columns = strlen($str); + $columns = strlen((string) $str); $c = $columns; - for ($i = 0; $i < strlen($str); $i++) { + for ($i = 0; $i < strlen((string) $str); $i++) { $result .= sprintf("%02x ", ord($str[$i])); $c--; if ($c === 0) { @@ -211,9 +213,9 @@ function show_bytes($str, $columns = null) { * @param timestamp the timestamp to conver (or 0 if get "now") * @return date_string the date string in PDF format */ -function timestamp_to_pdfdatestring($date = null) { +function timestamp_to_pdfdatestring($date = null): string { if ($date === null) - $date = new \DateTime(); + $date = new DateTime(); $timestamp = $date->getTimestamp(); return 'D:' . get_pdf_formatted_date($timestamp); @@ -225,5 +227,5 @@ function timestamp_to_pdfdatestring($date = null) { * @since 5.9.152 (2012-03-23) */ function get_pdf_formatted_date($time) { - return substr_replace(date('YmdHisO', intval($time)), '\'', (0 - 2), 0).'\''; + return substr_replace(date('YmdHisO', intval($time)), '\'', (0 - 2), 0) . '\''; } diff --git a/src/helpers/mime.php b/src/helpers/mime.php index 29145fb..cb60c91 100644 --- a/src/helpers/mime.php +++ b/src/helpers/mime.php @@ -21,187 +21,187 @@ namespace ddn\sapp\helpers; -function mime_to_ext($mime) { +function mime_to_ext($mime): string|false { $mime_map = [ - 'video/3gpp2' => '3g2', - 'video/3gp' => '3gp', - 'video/3gpp' => '3gp', - 'application/x-compressed' => '7zip', - 'audio/x-acc' => 'aac', - 'audio/ac3' => 'ac3', - 'application/postscript' => 'ai', - 'audio/x-aiff' => 'aif', - 'audio/aiff' => 'aif', - 'audio/x-au' => 'au', - 'video/x-msvideo' => 'avi', - 'video/msvideo' => 'avi', - 'video/avi' => 'avi', - 'application/x-troff-msvideo' => 'avi', - 'application/macbinary' => 'bin', - 'application/mac-binary' => 'bin', - 'application/x-binary' => 'bin', - 'application/x-macbinary' => 'bin', - 'image/bmp' => 'bmp', - 'image/x-bmp' => 'bmp', - 'image/x-bitmap' => 'bmp', - 'image/x-xbitmap' => 'bmp', - 'image/x-win-bitmap' => 'bmp', - 'image/x-windows-bmp' => 'bmp', - 'image/ms-bmp' => 'bmp', - 'image/x-ms-bmp' => 'bmp', - 'application/bmp' => 'bmp', - 'application/x-bmp' => 'bmp', - 'application/x-win-bitmap' => 'bmp', - 'application/cdr' => 'cdr', - 'application/coreldraw' => 'cdr', - 'application/x-cdr' => 'cdr', - 'application/x-coreldraw' => 'cdr', - 'image/cdr' => 'cdr', - 'image/x-cdr' => 'cdr', - 'zz-application/zz-winassoc-cdr' => 'cdr', - 'application/mac-compactpro' => 'cpt', - 'application/pkix-crl' => 'crl', - 'application/pkcs-crl' => 'crl', - 'application/x-x509-ca-cert' => 'crt', - 'application/pkix-cert' => 'crt', - 'text/css' => 'css', - 'text/x-comma-separated-values' => 'csv', - 'text/comma-separated-values' => 'csv', - 'application/vnd.msexcel' => 'csv', - 'application/x-director' => 'dcr', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', - 'application/x-dvi' => 'dvi', - 'message/rfc822' => 'eml', - 'application/x-msdownload' => 'exe', - 'video/x-f4v' => 'f4v', - 'audio/x-flac' => 'flac', - 'video/x-flv' => 'flv', - 'image/gif' => 'gif', - 'application/gpg-keys' => 'gpg', - 'application/x-gtar' => 'gtar', - 'application/x-gzip' => 'gzip', - 'application/mac-binhex40' => 'hqx', - 'application/mac-binhex' => 'hqx', - 'application/x-binhex40' => 'hqx', - 'application/x-mac-binhex40' => 'hqx', - 'text/html' => 'html', - 'image/x-icon' => 'ico', - 'image/x-ico' => 'ico', - 'image/vnd.microsoft.icon' => 'ico', - 'text/calendar' => 'ics', - 'application/java-archive' => 'jar', - 'application/x-java-application' => 'jar', - 'application/x-jar' => 'jar', - 'image/jp2' => 'jp2', - 'video/mj2' => 'jp2', - 'image/jpx' => 'jp2', - 'image/jpm' => 'jp2', - 'image/jpeg' => 'jpg', - 'image/pjpeg' => 'jpg', - 'application/x-javascript' => 'js', - 'application/json' => 'json', - 'text/json' => 'json', - 'application/vnd.google-earth.kml+xml' => 'kml', - 'application/vnd.google-earth.kmz' => 'kmz', - 'text/x-log' => 'log', - 'audio/x-m4a' => 'm4a', - 'audio/mp4' => 'm4a', - 'application/vnd.mpegurl' => 'm4u', - 'audio/midi' => 'mid', - 'application/vnd.mif' => 'mif', - 'video/quicktime' => 'mov', - 'video/x-sgi-movie' => 'movie', - 'audio/mpeg' => 'mp3', - 'audio/mpg' => 'mp3', - 'audio/mpeg3' => 'mp3', - 'audio/mp3' => 'mp3', - 'video/mp4' => 'mp4', - 'video/mpeg' => 'mpeg', - 'application/oda' => 'oda', - 'audio/ogg' => 'ogg', - 'video/ogg' => 'ogg', - 'application/ogg' => 'ogg', - 'application/x-pkcs10' => 'p10', - 'application/pkcs10' => 'p10', - 'application/x-pkcs12' => 'p12', - 'application/x-pkcs7-signature' => 'p7a', - 'application/pkcs7-mime' => 'p7c', - 'application/x-pkcs7-mime' => 'p7c', - 'application/x-pkcs7-certreqresp' => 'p7r', - 'application/pkcs7-signature' => 'p7s', - 'application/pdf' => 'pdf', - 'application/octet-stream' => 'pdf', - 'application/x-x509-user-cert' => 'pem', - 'application/x-pem-file' => 'pem', - 'application/pgp' => 'pgp', - 'application/x-httpd-php' => 'php', - 'application/php' => 'php', - 'application/x-php' => 'php', - 'text/php' => 'php', - 'text/x-php' => 'php', - 'application/x-httpd-php-source' => 'php', - 'image/png' => 'png', - 'image/x-png' => 'png', - 'application/powerpoint' => 'ppt', - 'application/vnd.ms-powerpoint' => 'ppt', - 'application/vnd.ms-office' => 'ppt', - 'application/msword' => 'ppt', + 'video/3gpp2' => '3g2', + 'video/3gp' => '3gp', + 'video/3gpp' => '3gp', + 'application/x-compressed' => '7zip', + 'audio/x-acc' => 'aac', + 'audio/ac3' => 'ac3', + 'application/postscript' => 'ai', + 'audio/x-aiff' => 'aif', + 'audio/aiff' => 'aif', + 'audio/x-au' => 'au', + 'video/x-msvideo' => 'avi', + 'video/msvideo' => 'avi', + 'video/avi' => 'avi', + 'application/x-troff-msvideo' => 'avi', + 'application/macbinary' => 'bin', + 'application/mac-binary' => 'bin', + 'application/x-binary' => 'bin', + 'application/x-macbinary' => 'bin', + 'image/bmp' => 'bmp', + 'image/x-bmp' => 'bmp', + 'image/x-bitmap' => 'bmp', + 'image/x-xbitmap' => 'bmp', + 'image/x-win-bitmap' => 'bmp', + 'image/x-windows-bmp' => 'bmp', + 'image/ms-bmp' => 'bmp', + 'image/x-ms-bmp' => 'bmp', + 'application/bmp' => 'bmp', + 'application/x-bmp' => 'bmp', + 'application/x-win-bitmap' => 'bmp', + 'application/cdr' => 'cdr', + 'application/coreldraw' => 'cdr', + 'application/x-cdr' => 'cdr', + 'application/x-coreldraw' => 'cdr', + 'image/cdr' => 'cdr', + 'image/x-cdr' => 'cdr', + 'zz-application/zz-winassoc-cdr' => 'cdr', + 'application/mac-compactpro' => 'cpt', + 'application/pkix-crl' => 'crl', + 'application/pkcs-crl' => 'crl', + 'application/x-x509-ca-cert' => 'crt', + 'application/pkix-cert' => 'crt', + 'text/css' => 'css', + 'text/x-comma-separated-values' => 'csv', + 'text/comma-separated-values' => 'csv', + 'application/vnd.msexcel' => 'csv', + 'application/x-director' => 'dcr', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', + 'application/x-dvi' => 'dvi', + 'message/rfc822' => 'eml', + 'application/x-msdownload' => 'exe', + 'video/x-f4v' => 'f4v', + 'audio/x-flac' => 'flac', + 'video/x-flv' => 'flv', + 'image/gif' => 'gif', + 'application/gpg-keys' => 'gpg', + 'application/x-gtar' => 'gtar', + 'application/x-gzip' => 'gzip', + 'application/mac-binhex40' => 'hqx', + 'application/mac-binhex' => 'hqx', + 'application/x-binhex40' => 'hqx', + 'application/x-mac-binhex40' => 'hqx', + 'text/html' => 'html', + 'image/x-icon' => 'ico', + 'image/x-ico' => 'ico', + 'image/vnd.microsoft.icon' => 'ico', + 'text/calendar' => 'ics', + 'application/java-archive' => 'jar', + 'application/x-java-application' => 'jar', + 'application/x-jar' => 'jar', + 'image/jp2' => 'jp2', + 'video/mj2' => 'jp2', + 'image/jpx' => 'jp2', + 'image/jpm' => 'jp2', + 'image/jpeg' => 'jpg', + 'image/pjpeg' => 'jpg', + 'application/x-javascript' => 'js', + 'application/json' => 'json', + 'text/json' => 'json', + 'application/vnd.google-earth.kml+xml' => 'kml', + 'application/vnd.google-earth.kmz' => 'kmz', + 'text/x-log' => 'log', + 'audio/x-m4a' => 'm4a', + 'audio/mp4' => 'm4a', + 'application/vnd.mpegurl' => 'm4u', + 'audio/midi' => 'mid', + 'application/vnd.mif' => 'mif', + 'video/quicktime' => 'mov', + 'video/x-sgi-movie' => 'movie', + 'audio/mpeg' => 'mp3', + 'audio/mpg' => 'mp3', + 'audio/mpeg3' => 'mp3', + 'audio/mp3' => 'mp3', + 'video/mp4' => 'mp4', + 'video/mpeg' => 'mpeg', + 'application/oda' => 'oda', + 'audio/ogg' => 'ogg', + 'video/ogg' => 'ogg', + 'application/ogg' => 'ogg', + 'application/x-pkcs10' => 'p10', + 'application/pkcs10' => 'p10', + 'application/x-pkcs12' => 'p12', + 'application/x-pkcs7-signature' => 'p7a', + 'application/pkcs7-mime' => 'p7c', + 'application/x-pkcs7-mime' => 'p7c', + 'application/x-pkcs7-certreqresp' => 'p7r', + 'application/pkcs7-signature' => 'p7s', + 'application/pdf' => 'pdf', + 'application/octet-stream' => 'pdf', + 'application/x-x509-user-cert' => 'pem', + 'application/x-pem-file' => 'pem', + 'application/pgp' => 'pgp', + 'application/x-httpd-php' => 'php', + 'application/php' => 'php', + 'application/x-php' => 'php', + 'text/php' => 'php', + 'text/x-php' => 'php', + 'application/x-httpd-php-source' => 'php', + 'image/png' => 'png', + 'image/x-png' => 'png', + 'application/powerpoint' => 'ppt', + 'application/vnd.ms-powerpoint' => 'ppt', + 'application/vnd.ms-office' => 'ppt', + 'application/msword' => 'ppt', 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', - 'application/x-photoshop' => 'psd', - 'image/vnd.adobe.photoshop' => 'psd', - 'audio/x-realaudio' => 'ra', - 'audio/x-pn-realaudio' => 'ram', - 'application/x-rar' => 'rar', - 'application/rar' => 'rar', - 'application/x-rar-compressed' => 'rar', - 'audio/x-pn-realaudio-plugin' => 'rpm', - 'application/x-pkcs7' => 'rsa', - 'text/rtf' => 'rtf', - 'text/richtext' => 'rtx', - 'video/vnd.rn-realvideo' => 'rv', - 'application/x-stuffit' => 'sit', - 'application/smil' => 'smil', - 'text/srt' => 'srt', - 'image/svg+xml' => 'svg', - 'application/x-shockwave-flash' => 'swf', - 'application/x-tar' => 'tar', - 'application/x-gzip-compressed' => 'tgz', - 'image/tiff' => 'tiff', - 'text/plain' => 'txt', - 'text/x-vcard' => 'vcf', - 'application/videolan' => 'vlc', - 'text/vtt' => 'vtt', - 'audio/x-wav' => 'wav', - 'audio/wave' => 'wav', - 'audio/wav' => 'wav', - 'application/wbxml' => 'wbxml', - 'video/webm' => 'webm', - 'audio/x-ms-wma' => 'wma', - 'application/wmlc' => 'wmlc', - 'video/x-ms-wmv' => 'wmv', - 'video/x-ms-asf' => 'wmv', - 'application/xhtml+xml' => 'xhtml', - 'application/excel' => 'xl', - 'application/msexcel' => 'xls', - 'application/x-msexcel' => 'xls', - 'application/x-ms-excel' => 'xls', - 'application/x-excel' => 'xls', - 'application/x-dos_ms_excel' => 'xls', - 'application/xls' => 'xls', - 'application/x-xls' => 'xls', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', - 'application/vnd.ms-excel' => 'xlsx', - 'application/xml' => 'xml', - 'text/xml' => 'xml', - 'text/xsl' => 'xsl', - 'application/xspf+xml' => 'xspf', - 'application/x-compress' => 'z', - 'application/x-zip' => 'zip', - 'application/zip' => 'zip', - 'application/x-zip-compressed' => 'zip', - 'application/s-compressed' => 'zip', - 'multipart/x-zip' => 'zip', - 'text/x-scriptzsh' => 'zsh', + 'application/x-photoshop' => 'psd', + 'image/vnd.adobe.photoshop' => 'psd', + 'audio/x-realaudio' => 'ra', + 'audio/x-pn-realaudio' => 'ram', + 'application/x-rar' => 'rar', + 'application/rar' => 'rar', + 'application/x-rar-compressed' => 'rar', + 'audio/x-pn-realaudio-plugin' => 'rpm', + 'application/x-pkcs7' => 'rsa', + 'text/rtf' => 'rtf', + 'text/richtext' => 'rtx', + 'video/vnd.rn-realvideo' => 'rv', + 'application/x-stuffit' => 'sit', + 'application/smil' => 'smil', + 'text/srt' => 'srt', + 'image/svg+xml' => 'svg', + 'application/x-shockwave-flash' => 'swf', + 'application/x-tar' => 'tar', + 'application/x-gzip-compressed' => 'tgz', + 'image/tiff' => 'tiff', + 'text/plain' => 'txt', + 'text/x-vcard' => 'vcf', + 'application/videolan' => 'vlc', + 'text/vtt' => 'vtt', + 'audio/x-wav' => 'wav', + 'audio/wave' => 'wav', + 'audio/wav' => 'wav', + 'application/wbxml' => 'wbxml', + 'video/webm' => 'webm', + 'audio/x-ms-wma' => 'wma', + 'application/wmlc' => 'wmlc', + 'video/x-ms-wmv' => 'wmv', + 'video/x-ms-asf' => 'wmv', + 'application/xhtml+xml' => 'xhtml', + 'application/excel' => 'xl', + 'application/msexcel' => 'xls', + 'application/x-msexcel' => 'xls', + 'application/x-ms-excel' => 'xls', + 'application/x-excel' => 'xls', + 'application/x-dos_ms_excel' => 'xls', + 'application/xls' => 'xls', + 'application/x-xls' => 'xls', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', + 'application/vnd.ms-excel' => 'xlsx', + 'application/xml' => 'xml', + 'text/xml' => 'xml', + 'text/xsl' => 'xsl', + 'application/xspf+xml' => 'xspf', + 'application/x-compress' => 'z', + 'application/x-zip' => 'zip', + 'application/zip' => 'zip', + 'application/x-zip-compressed' => 'zip', + 'application/s-compressed' => 'zip', + 'multipart/x-zip' => 'zip', + 'text/x-scriptzsh' => 'zsh', ]; - return isset($mime_map[$mime]) ? $mime_map[$mime] : false; + return $mime_map[$mime] ?? false; } diff --git a/src/helpers/x509.php b/src/helpers/x509.php index af4a000..4b609d5 100644 --- a/src/helpers/x509.php +++ b/src/helpers/x509.php @@ -1,4 +1,5 @@ '06082A864886F70D0202', - 'md4'=>'06082A864886F70D0204', - 'md5'=>'06082A864886F70D0205', - 'sha1'=>'06052B0E03021A', - 'sha224'=>'0609608648016503040204', - 'sha256'=>'0609608648016503040201', - 'sha384'=>'0609608648016503040202', - 'sha512'=>'0609608648016503040203' - ); - if(!array_key_exists($hashAlg, $hexOidHashAlgos)) { + public static function tsa_query($binaryData, $hashAlg = 'sha256'): false|string { + $hashAlg = strtolower((string) $hashAlg); + $hexOidHashAlgos = [ + 'md2' => '06082A864886F70D0202', + 'md4' => '06082A864886F70D0204', + 'md5' => '06082A864886F70D0205', + 'sha1' => '06052B0E03021A', + 'sha224' => '0609608648016503040204', + 'sha256' => '0609608648016503040201', + 'sha384' => '0609608648016503040202', + 'sha512' => '0609608648016503040203', + ]; + if(! array_key_exists($hashAlg, $hexOidHashAlgos)) { return false; } - $hash = hash($hashAlg, $binaryData); + $hash = hash($hashAlg, (string) $binaryData); $tsReqData = asn1::seq( - asn1::int(1). + asn1::int(1) . asn1::seq( - asn1::seq($hexOidHashAlgos[$hashAlg]."0500"). // object OBJ $hexOidHashAlgos[$hashAlg] & OBJ_null + asn1::seq($hexOidHashAlgos[$hashAlg] . "0500") . // object OBJ $hexOidHashAlgos[$hashAlg] & OBJ_null asn1::oct($hash) - ). - asn1::int(hash('crc32', rand()).'001'). // tsa nonce + ) . + asn1::int(hash('crc32', random_int(0, mt_getrandmax())) . '001') . // tsa nonce '0101ff' // req return cert ); return hex2bin($tsReqData); @@ -52,15 +53,15 @@ public static function tsa_query($binaryData, $hashAlg='sha256') { * @param string $hex_subjSequence hex subject name sequence * @return array subject hash old and new */ - private static function opensslSubjHash($hex_subjSequence){ - $parse = asn1::parse($hex_subjSequence,3); - $hex_subjSequence_new=''; - foreach($parse[0] as $k=>$v) { + private static function opensslSubjHash($hex_subjSequence): array{ + $parse = asn1::parse($hex_subjSequence, 3); + $hex_subjSequence_new = ''; + foreach($parse[0] as $k => $v) { if(is_numeric($k)) { $hex_subjSequence_new .= asn1::set( asn1::seq( - $v[0][0]['hexdump']. - asn1::utf8(strtolower(hex2bin($v[0][1]['value_hex']))) + $v[0][0]['hexdump'] . + asn1::utf8(strtolower(hex2bin((string) $v[0][1]['value_hex']))) ) ); } @@ -76,10 +77,10 @@ private static function opensslSubjHash($hex_subjSequence){ $openssl_subjHash_old = str_split($openssl_subjHash_old, 2); $openssl_subjHash_old = array_reverse($openssl_subjHash_old); $openssl_subjHash_old = implode("", $openssl_subjHash_old); - return array( - "old"=>$openssl_subjHash_old, - "new"=>$openssl_subjHash_new - ); + return [ + "old" => $openssl_subjHash_old, + "new" => $openssl_subjHash_new, + ]; } /** @@ -87,22 +88,22 @@ private static function opensslSubjHash($hex_subjSequence){ * @param string $binaryOcspResp binary ocsp response * @return array ocsp response structure */ - public static function ocsp_response_parse($binaryOcspResp, &$status='') { + public static function ocsp_response_parse($binaryOcspResp, &$status = '') { $hex = current(unpack("H*", $binaryOcspResp)); - $parse = asn1::parse($hex,10); + $parse = asn1::parse($hex, 10); if($parse[0]['type'] == '30') { $ocsp = $parse[0]; } else { return false; } - foreach($ocsp as $key=>$value) { + foreach($ocsp as $key => $value) { if(is_numeric($key)) { if($value['type'] == '0a') { - $ocsp['responseStatus']=$value['value_hex']; + $ocsp['responseStatus'] = $value['value_hex']; unset($ocsp[$key]); } if($value['type'] == 'a0') { - $ocsp['responseBytes']=$value; + $ocsp['responseBytes'] = $value; unset($ocsp[$key]); } } else { @@ -121,22 +122,22 @@ public static function ocsp_response_parse($binaryOcspResp, &$status='') { // sigRequired (5), --Must sign the request // unauthorized (6) --Request unauthorized if(@$ocsp['responseStatus'] != '00') { - $responseStatus['01']='malformedRequest'; - $responseStatus['02']='internalError'; - $responseStatus['03']='tryLater'; - $responseStatus['05']='sigRequired'; - $responseStatus['06']='unauthorized'; + $responseStatus['01'] = 'malformedRequest'; + $responseStatus['02'] = 'internalError'; + $responseStatus['03'] = 'tryLater'; + $responseStatus['05'] = 'sigRequired'; + $responseStatus['06'] = 'unauthorized'; $status = @$responseStatus[$ocsp['responseStatus']]; return false; } - if(!@$curr = $ocsp['responseBytes']) { + if(! @$curr = $ocsp['responseBytes']) { return false; } - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if(is_numeric($key)) { if($value['type'] == '30') { - $curr['responseType']=self::oidfromhex($value[0]['value_hex']); - $curr['response']=$value[1]; + $curr['responseType'] = self::oidfromhex($value[0]['value_hex']); + $curr['response'] = $value[1]; unset($curr[$key]); } } else { @@ -147,10 +148,10 @@ public static function ocsp_response_parse($binaryOcspResp, &$status='') { } $ocsp['responseBytes'] = $curr; $curr = $ocsp['responseBytes']['response']; - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if(is_numeric($key)) { if($value['type'] == '30') { - $curr['BasicOCSPResponse']=$value; + $curr['BasicOCSPResponse'] = $value; unset($curr[$key]); } } else { @@ -161,29 +162,29 @@ public static function ocsp_response_parse($binaryOcspResp, &$status='') { } $ocsp['responseBytes']['response'] = $curr; $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']; - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if(is_numeric($key)) { - if($value['type'] == '30' && !array_key_exists('tbsResponseData', $curr)) { - $curr['tbsResponseData']=$value; + if($value['type'] == '30' && ! array_key_exists('tbsResponseData', $curr)) { + $curr['tbsResponseData'] = $value; unset($curr[$key]); continue; } - if($value['type'] == '30' && !array_key_exists('signatureAlgorithm', $curr)) { - $curr['signatureAlgorithm']=$value[0]['value_hex']; + if($value['type'] == '30' && ! array_key_exists('signatureAlgorithm', $curr)) { + $curr['signatureAlgorithm'] = $value[0]['value_hex']; unset($curr[$key]); continue; } if($value['type'] == '03') { - $curr['signature']=substr($value['value_hex'], 2); + $curr['signature'] = substr((string) $value['value_hex'], 2); unset($curr[$key]); } if($value['type'] == 'a0') { - foreach($value[0] as $certsK=>$certsV) { + foreach($value[0] as $certsK => $certsV) { if(is_numeric($certsK)) { $certs[$certsK] = $certsV['value_hex']; } } - $curr['certs']=$certs; + $curr['certs'] = $certs; unset($curr[$key]); } } else { @@ -194,30 +195,30 @@ public static function ocsp_response_parse($binaryOcspResp, &$status='') { } $ocsp['responseBytes']['response']['BasicOCSPResponse'] = $curr; $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']; - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if(is_numeric($key)) { if($value['type'] == 'a0') { - $curr['version']=$value[0]['value']; + $curr['version'] = $value[0]['value']; unset($curr[$key]); } - if($value['type'] == 'a1' && !array_key_exists('responderID', $curr)) { - $curr['responderID']=$value; + if($value['type'] == 'a1' && ! array_key_exists('responderID', $curr)) { + $curr['responderID'] = $value; unset($curr[$key]); } if($value['type'] == 'a2') { - $curr['responderID']=$value; + $curr['responderID'] = $value; unset($curr[$key]); } if($value['type'] == '18') { - $curr['producedAt']=$value['value']; + $curr['producedAt'] = $value['value']; unset($curr[$key]); } if($value['type'] == '30') { - $curr['responses']=$value; + $curr['responses'] = $value; unset($curr[$key]); } if($value['type'] == 'a1') { - $curr['responseExtensions']=$value; + $curr['responseExtensions'] = $value; unset($curr[$key]); } } else { @@ -228,10 +229,10 @@ public static function ocsp_response_parse($binaryOcspResp, &$status='') { } $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData'] = $curr; $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions']; - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if(is_numeric($key)) { if($value['type'] == '30') { - $curr['lists']=$value; + $curr['lists'] = $value; unset($curr[$key]); } } else { @@ -242,13 +243,13 @@ public static function ocsp_response_parse($binaryOcspResp, &$status='') { } $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions'] = $curr; $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions']['lists']; - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if(is_numeric($key)) { if($value['type'] == '30') { if($value[0]['value_hex'] == '2b0601050507300102') { // nonce - $curr['nonce']=$value[0]['value_hex']; + $curr['nonce'] = $value[0]['value_hex']; } else { - $curr[$value[0]['value_hex']]=$value[1]; + $curr[$value[0]['value_hex']] = $value[1]; } unset($curr[$key]); } @@ -260,18 +261,18 @@ public static function ocsp_response_parse($binaryOcspResp, &$status='') { } $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions']['lists'] = $curr; $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses']; - $i=0; - foreach($curr as $key=>$value) { + $i = 0; + foreach($curr as $key => $value) { if(is_numeric($key)) { - foreach($value as $SingleResponseK=>$SingleResponseV) { + foreach($value as $SingleResponseK => $SingleResponseV) { if(is_numeric($SingleResponseK)) { if($SingleResponseK == 0) { - foreach($SingleResponseV as $certIDk=>$certIDv) { + foreach($SingleResponseV as $certIDk => $certIDv) { if(is_numeric($certIDk)) { if($certIDv['type'] == '30') { $certID['hashAlgorithm'] = $certIDv[0]['value_hex']; } - if($certIDv['type'] == '04' && !array_key_exists('issuerNameHash', $certID)) { + if($certIDv['type'] == '04' && ! array_key_exists('issuerNameHash', $certID)) { $certID['issuerNameHash'] = $certIDv['value_hex']; } if($certIDv['type'] == '04') { @@ -314,23 +315,23 @@ public static function ocsp_response_parse($binaryOcspResp, &$status='') { } } $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses'] = $curr; - $arrModel = array( - 'responseStatus'=>'', - 'responseBytes'=>array( - 'response'=>'', - 'responseType'=>'' - ) - ); - $differ=array_diff_key($arrModel,$ocsp); + $arrModel = [ + 'responseStatus' => '', + 'responseBytes' => [ + 'response' => '', + 'responseType' => '', + ], + ]; + $differ = array_diff_key($arrModel, $ocsp); if(count($differ) == 0) { - $differ=array_diff_key($arrModel['responseBytes'],$ocsp['responseBytes']); + $differ = array_diff_key($arrModel['responseBytes'], $ocsp['responseBytes']); if(count($differ) > 0) { - foreach($differ as $key=>$val) { + foreach($differ as $key => $val) { } return false; } } else { - foreach($differ as $key=>$val) { + foreach($differ as $key => $val) { } return false; } @@ -347,16 +348,16 @@ public static function ocsp_response_parse($binaryOcspResp, &$status='') { * @param string $subjectName hex form of asn1 subject * @return string hex form ocsp request */ - public static function ocsp_request($serialNumber, $issuerNameHash, $issuerKeyHash, $signer_cert = false, $signer_key = false, $subjectName=false) { + public static function ocsp_request($serialNumber, $issuerNameHash, $issuerKeyHash, $signer_cert = false, $signer_key = false, $subjectName = false) { $Request = false; $hashAlgorithm = asn1::seq( - "06052B0E03021A". // OBJ_sha1 + "06052B0E03021A" . // OBJ_sha1 "0500" ); $issuerNameHash = asn1::oct($issuerNameHash); $issuerKeyHash = asn1::oct($issuerKeyHash); $serialNumber = asn1::int($serialNumber); - $CertID = asn1::seq($hashAlgorithm.$issuerNameHash.$issuerKeyHash.$serialNumber); + $CertID = asn1::seq($hashAlgorithm . $issuerNameHash . $issuerKeyHash . $serialNumber); $Request = asn1::seq($CertID); // one request if($signer_cert) { $requestorName = asn1::expl("1", asn1::expl("4", $subjectName)); @@ -364,29 +365,29 @@ public static function ocsp_request($serialNumber, $issuerNameHash, $issuerKeyHa $requestorName = false; } $requestList = asn1::seq($Request); // add more request into sequence - $rand = microtime (true)*rand(); - $nonce = md5(base64_encode($rand).$rand); + $rand = microtime (true) * random_int(0, mt_getrandmax()); + $nonce = md5(base64_encode($rand) . $rand); $ReqExts = asn1::seq( - '06092B0601050507300102'. // OBJ_id_pkix_OCSP_Nonce - asn1::oct("0410".$nonce) + '06092B0601050507300102' . // OBJ_id_pkix_OCSP_Nonce + asn1::oct("0410" . $nonce) ); $requestExtensions = asn1::expl( "2", asn1::seq($ReqExts)); - $TBSRequest = asn1::seq($requestorName.$requestList.$requestExtensions); + $TBSRequest = asn1::seq($requestorName . $requestList . $requestExtensions); $optionalSignature = ''; if($signer_cert) { - if(!openssl_sign (hex2bin($TBSRequest), $signature_value, $signer_key)) { + if(! openssl_sign (hex2bin($TBSRequest), $signature_value, $signer_key)) { return false; } $signatureAlgorithm = asn1::seq( - '06092A864886F70D010105'. // OBJ_sha1WithRSAEncryption. + '06092A864886F70D010105' . // OBJ_sha1WithRSAEncryption. "0500" ); - $signature = asn1::bit("00".bin2hex($signature_value)); + $signature = asn1::bit("00" . bin2hex((string) $signature_value)); $signer_cert = x509::x509_pem2der($signer_cert); $certs = asn1::expl("0", asn1::seq(bin2hex($signer_cert))); - $optionalSignature = asn1::expl("0",asn1::seq($signatureAlgorithm.$signature.$certs)); + $optionalSignature = asn1::expl("0", asn1::seq($signatureAlgorithm . $signature . $certs)); } - $OCSPRequest = asn1::seq($TBSRequest.$optionalSignature); + $OCSPRequest = asn1::seq($TBSRequest . $optionalSignature); return $OCSPRequest; } @@ -395,22 +396,22 @@ public static function ocsp_request($serialNumber, $issuerNameHash, $issuerKeyHa * @param string $crl pem crl to convert * @return string der crl form */ - public static function crl_pem2der($crl) { + public static function crl_pem2der($crl): false|string { $begin = '-----BEGIN X509 CRL-----'; $end = '-----END X509 CRL-----'; $beginPos = stripos($crl, $begin); - if($beginPos===false) { + if($beginPos === false) { return false; } - $crl = substr($crl, $beginPos+strlen($begin)); + $crl = substr($crl, $beginPos + strlen($begin)); $endPos = stripos($crl, $end); - if($endPos===false) { + if($endPos === false) { return false; } $crl = substr($crl, 0, $endPos); $crl = str_replace("\n", "", $crl); $crl = str_replace("\r", "", $crl); - $dercrl = base64_decode($crl); + $dercrl = base64_decode($crl, true); return $dercrl; } @@ -419,12 +420,12 @@ public static function crl_pem2der($crl) { * @param string $crl pem or der crl * @return array der crl and parsed crl */ - public static function crl_read($crl) { - if(!$crlparse=self::parsecrl($crl)) { // if cant read, thats not crl + public static function crl_read($crl): false|array { + if(! $crlparse = self::parsecrl($crl)) { // if cant read, thats not crl return false; } - if(!$dercrl=self::crl_pem2der($crl)) { // if not pem, thats already der - $dercrl=$crl; + if(! $dercrl = self::crl_pem2der($crl)) { // if not pem, thats already der + $dercrl = $crl; } $res['der'] = $dercrl; $res['parse'] = $crlparse; @@ -437,36 +438,36 @@ public static function crl_read($crl) { * @param string $oidprint option show obj as hex/oid * @return array parsed crl */ - private static function parsecrl($crl, $oidprint = false) { + private static function parsecrl(array $crl, $oidprint = false) { if($derCrl = self::crl_pem2der($crl)) { $derCrl = bin2hex($derCrl); } else { $derCrl = bin2hex($crl); } $curr = asn1::parse($derCrl, 7); - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if($value['type'] == '30') { - $curr['crl']=$curr[$key]; + $curr['crl'] = $curr[$key]; unset($curr[$key]); } } - $ar=$curr; - if(!array_key_exists('crl', $ar)) { + $ar = $curr; + if(! array_key_exists('crl', $ar)) { return false; } $curr = $ar['crl']; - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if(is_numeric($key)) { - if($value['type'] == '30' && !array_key_exists('TBSCertList', $curr)) { - $curr['TBSCertList']=$curr[$key]; + if($value['type'] == '30' && ! array_key_exists('TBSCertList', $curr)) { + $curr['TBSCertList'] = $curr[$key]; unset($curr[$key]); } if($value['type'] == '30') { - $curr['signatureAlgorithm']=self::oidfromhex($value[0]['value_hex']); + $curr['signatureAlgorithm'] = self::oidfromhex($value[0]['value_hex']); unset($curr[$key]); } if($value['type'] == '03') { - $curr['signature']=substr($value['value'], 2); + $curr['signature'] = substr((string) $value['value'], 2); unset($curr[$key]); } } else { @@ -475,39 +476,39 @@ private static function parsecrl($crl, $oidprint = false) { } $ar['crl'] = $curr; $curr = $ar['crl']['TBSCertList']; - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if(is_numeric($key)) { if($value['type'] == '02') { - $curr['version']=$curr[$key]['value']; + $curr['version'] = $curr[$key]['value']; unset($curr[$key]); } - if($value['type'] == '30' && !array_key_exists('signature', $curr)) { - $curr['signature']=$value[0]['value_hex']; + if($value['type'] == '30' && ! array_key_exists('signature', $curr)) { + $curr['signature'] = $value[0]['value_hex']; unset($curr[$key]); continue; } - if($value['type'] == '30' && !array_key_exists('issuer', $curr)) { - $curr['issuer']=$value; + if($value['type'] == '30' && ! array_key_exists('issuer', $curr)) { + $curr['issuer'] = $value; unset($curr[$key]); continue; } - if($value['type'] == '17' && !array_key_exists('thisUpdate', $curr)) { - $curr['thisUpdate']=hex2bin($value['value_hex']); + if($value['type'] == '17' && ! array_key_exists('thisUpdate', $curr)) { + $curr['thisUpdate'] = hex2bin((string) $value['value_hex']); unset($curr[$key]); continue; } - if($value['type'] == '17' && !array_key_exists('nextUpdate', $curr)) { - $curr['nextUpdate']=hex2bin($value['value_hex']); + if($value['type'] == '17' && ! array_key_exists('nextUpdate', $curr)) { + $curr['nextUpdate'] = hex2bin((string) $value['value_hex']); unset($curr[$key]); continue; } - if($value['type'] == '30' && !array_key_exists('revokedCertificates', $curr)) { - $curr['revokedCertificates']=$value; + if($value['type'] == '30' && ! array_key_exists('revokedCertificates', $curr)) { + $curr['revokedCertificates'] = $value; unset($curr[$key]); continue; } if($value['type'] == 'a0') { - $curr['crlExtensions']=$curr[$key]; + $curr['crlExtensions'] = $curr[$key]; unset($curr[$key]); } } else { @@ -517,12 +518,12 @@ private static function parsecrl($crl, $oidprint = false) { $ar['crl']['TBSCertList'] = $curr; if(array_key_exists('revokedCertificates', $curr)) { $curr = $ar['crl']['TBSCertList']['revokedCertificates']; - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if(is_numeric($key)) { if($value['type'] == '30') { $serial = $value[0]['value']; - $revoked['time']=hex2bin($value[1]['value_hex']); - $lists[$serial]=$revoked; + $revoked['time'] = hex2bin((string) $value[1]['value_hex']); + $lists[$serial] = $revoked; unset($curr[$key]); } } else { @@ -537,7 +538,7 @@ private static function parsecrl($crl, $oidprint = false) { if(array_key_exists('crlExtensions', $ar['crl']['TBSCertList'])) { $curr = $ar['crl']['TBSCertList']['crlExtensions'][0]; unset($ar['crl']['TBSCertList']['crlExtensions']); - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if(is_numeric($key)) { $attributes_name = self::oidfromhex($value[0]['value_hex']); if($oidprint == 'oid') { @@ -549,11 +550,11 @@ private static function parsecrl($crl, $oidprint = false) { $attributes_oid = self::oidfromhex($value[0]['value_hex']); if($value['type'] == '30') { $crlExtensionsValue = $value[1][0]; - if($attributes_oid == '2.5.29.20') { // OBJ_crl_number + if($attributes_oid == '2.5.29.20') { // OBJ_crl_number $crlExtensionsValue = $crlExtensionsValue['value']; } - if($attributes_oid == '2.5.29.35') { // OBJ_authority_key_identifier - foreach($crlExtensionsValue as $authority_key_identifierValueK=>$authority_key_identifierV) { + if($attributes_oid == '2.5.29.35') { // OBJ_authority_key_identifier + foreach($crlExtensionsValue as $authority_key_identifierValueK => $authority_key_identifierV) { if(is_numeric($authority_key_identifierValueK)) { if($authority_key_identifierV['type'] == '80') { $authority_key_identifier['keyIdentifier'] = $authority_key_identifierV['value_hex']; @@ -568,14 +569,14 @@ private static function parsecrl($crl, $oidprint = false) { } $crlExtensionsValue = $authority_key_identifier; } - $attribute_list=$crlExtensionsValue; + $attribute_list = $crlExtensionsValue; } $ar['crl']['TBSCertList']['crlExtensions'][$attributes_name] = $attribute_list; } } } $curr = $ar['crl']['TBSCertList']['issuer']; - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if(is_numeric($key)) { if($value['type'] == '31') { if($oidprint == 'oid') { @@ -585,7 +586,7 @@ private static function parsecrl($crl, $oidprint = false) { } else { $subjOID = self::oidfromhex($curr[$key][0][0]['value_hex']); } - $curr[$subjOID][]=hex2bin($curr[$key][0][1]['value_hex']); + $curr[$subjOID][] = hex2bin((string) $curr[$key][0][1]['value_hex']); unset($curr[$key]); } @@ -594,7 +595,7 @@ private static function parsecrl($crl, $oidprint = false) { unset($curr['type']); unset($curr['typeName']); if($key == 'hexdump') { - $curr['sha1']=hash('sha1', pack("H*", $value)); + $curr['sha1'] = hash('sha1', pack("H*", $value)); } } } @@ -607,16 +608,16 @@ private static function parsecrl($crl, $oidprint = false) { $arrModel['signatureAlgorithm'] = ''; $arrModel['signature'] = ''; $crl = $ar['crl']; - $differ=array_diff_key($arrModel,$crl); + $differ = array_diff_key($arrModel, $crl); if(count($differ) == 0) { - $differ=array_diff_key($arrModel['TBSCertList'],$crl['TBSCertList']); + $differ = array_diff_key($arrModel['TBSCertList'], $crl['TBSCertList']); if(count($differ) > 0) { - foreach($differ as $key=>$val) { + foreach($differ as $key => $val) { } return false; } } else { - foreach($differ as $key=>$val) { + foreach($differ as $key => $val) { } return false; } @@ -628,21 +629,21 @@ private static function parsecrl($crl, $oidprint = false) { * @param string $pem pem form cert * @return string der form cert */ - public static function x509_pem2der($pem) { + public static function x509_pem2der($pem): string|false { $x509_der = false; if($x509_res = @openssl_x509_read($pem)) { - openssl_x509_export ($x509_res, $x509_pem); - $arr_x509_pem = explode("\n", $x509_pem); + openssl_x509_export ($x509_res, $x509_pem); + $arr_x509_pem = explode("\n", (string) $x509_pem); $numarr = count($arr_x509_pem); - $i=0; + $i = 0; $cert_pem = false; foreach($arr_x509_pem as $val) { - if($i > 0 && $i < ($numarr-2)) { + if($i > 0 && $i < ($numarr - 2)) { $cert_pem .= $val; } $i++; } - $x509_der = base64_decode($cert_pem); + $x509_der = base64_decode($cert_pem, true); } return $x509_der; } @@ -652,9 +653,9 @@ public static function x509_pem2der($pem) { * @param string $der_cert der form cert * @return string pem form cert */ - public static function x509_der2pem($der_cert) { + public static function x509_der2pem($der_cert): string { $x509_pem = "-----BEGIN CERTIFICATE-----\r\n"; - $x509_pem .= chunk_split(base64_encode($der_cert),64); + $x509_pem .= chunk_split(base64_encode($der_cert), 64); $x509_pem .= "-----END CERTIFICATE-----\r\n"; return $x509_pem; } @@ -664,7 +665,7 @@ public static function x509_der2pem($der_cert) { * @param string $certin pem/der form cert * @return string der form cert */ - public static function get_cert($certin) { + public static function get_cert($certin): string|false { if($rsccert = @openssl_x509_read ($certin)) { openssl_x509_export ($rsccert, $cert); return self::x509_pem2der($cert); @@ -685,32 +686,32 @@ public static function get_cert($certin) { * @param string $oidprint show oid as oid number or hex * @return array cert structure */ - public static function readcert($cert_in, $oidprint=false) { - if(!$der = self::get_cert($cert_in)) { + public static function readcert($cert_in, $oidprint = false) { + if(! $der = self::get_cert($cert_in)) { return false; } $hex = bin2hex($der); - $curr = asn1::parse($hex,10); - foreach($curr as $key=>$value) { + $curr = asn1::parse($hex, 10); + foreach($curr as $key => $value) { if($value['type'] == '30') { - $curr['cert']=$curr[$key]; + $curr['cert'] = $curr[$key]; unset($curr[$key]); } } - $ar=$curr; + $ar = $curr; $curr = $ar['cert']; - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if(is_numeric($key)) { - if($value['type'] == '30' && !array_key_exists('tbsCertificate', $curr)) { - $curr['tbsCertificate']=$curr[$key]; + if($value['type'] == '30' && ! array_key_exists('tbsCertificate', $curr)) { + $curr['tbsCertificate'] = $curr[$key]; unset($curr[$key]); } if($value['type'] == '30') { - $curr['signatureAlgorithm']=self::oidfromhex($value[0]['value_hex']); + $curr['signatureAlgorithm'] = self::oidfromhex($value[0]['value_hex']); unset($curr[$key]); } if($value['type'] == '03') { - $curr['signatureValue']=substr($value['value'], 2); + $curr['signatureValue'] = substr((string) $value['value'], 2); unset($curr[$key]); } } else { @@ -718,26 +719,26 @@ public static function readcert($cert_in, $oidprint=false) { } } $ar['cert'] = $curr; - $ar['cert']['sha1Fingerprint']=hash('sha1', $der); + $ar['cert']['sha1Fingerprint'] = hash('sha1', $der); $curr = $ar['cert']['tbsCertificate']; - $i=0; - foreach($curr as $key=>$value) { + $i = 0; + foreach($curr as $key => $value) { if(is_numeric($key)) { if($value['type'] == 'a0') { - $curr['version']=$value[0]['value']; + $curr['version'] = $value[0]['value']; unset($curr[$key]); } if($value['type'] == '02') { - $curr['serialNumber']=$value['value']; + $curr['serialNumber'] = $value['value']; unset($curr[$key]); } - if($value['type'] == '30' && !array_key_exists('signature', $curr)) { - $curr['signature']=$value[0]['value_hex']; + if($value['type'] == '30' && ! array_key_exists('signature', $curr)) { + $curr['signature'] = $value[0]['value_hex']; unset($curr[$key]); continue; } - if($value['type'] == '30' && !array_key_exists('issuer', $curr)) { - foreach($value as $issuerK=>$issuerV) { + if($value['type'] == '30' && ! array_key_exists('issuer', $curr)) { + foreach($value as $issuerK => $issuerV) { if(is_numeric($issuerK)) { $issuerOID = $issuerV[0][0]['value_hex']; if($oidprint == 'oid') { @@ -746,26 +747,26 @@ public static function readcert($cert_in, $oidprint=false) { } else { $issuerOID = self::oidfromhex($issuerOID); } - $issuer[$issuerOID][] = hex2bin($issuerV[0][1]['value_hex']); + $issuer[$issuerOID][] = hex2bin((string) $issuerV[0][1]['value_hex']); } } $hexdump = $value['hexdump']; - $issuer['sha1'] = hash('sha1', hex2bin($hexdump)); + $issuer['sha1'] = hash('sha1', hex2bin((string) $hexdump)); $issuer['opensslHash'] = self::opensslSubjHash($hexdump); $issuer['hexdump'] = $hexdump; - $curr['issuer']=$issuer; + $curr['issuer'] = $issuer; unset($curr[$key]); continue; } - if($value['type'] == '30' && !array_key_exists('validity', $curr)) { - $curr['validity']['notBefore']=hex2bin($value[0]['value_hex']); - $curr['validity']['notAfter']=hex2bin($value[1]['value_hex']); + if($value['type'] == '30' && ! array_key_exists('validity', $curr)) { + $curr['validity']['notBefore'] = hex2bin((string) $value[0]['value_hex']); + $curr['validity']['notAfter'] = hex2bin((string) $value[1]['value_hex']); unset($curr[$key]); continue; } - if($value['type'] == '30' && !array_key_exists('subject', $curr)) { + if($value['type'] == '30' && ! array_key_exists('subject', $curr)) { $asn1SubjectToHash = ''; - foreach($value as $subjectK=>$subjectV) { + foreach($value as $subjectK => $subjectV) { if(is_numeric($subjectK)) { $subjectOID = $subjectV[0][0]['value_hex']; if($oidprint == 'oid') { @@ -774,51 +775,51 @@ public static function readcert($cert_in, $oidprint=false) { } else { $subjectOID = self::oidfromhex($subjectOID); } - $subject[$subjectOID][] = hex2bin($subjectV[0][1]['value_hex']); + $subject[$subjectOID][] = hex2bin((string) $subjectV[0][1]['value_hex']); } } $hexdump = $value['hexdump']; - $subject['sha1'] = hash('sha1', hex2bin($hexdump)); + $subject['sha1'] = hash('sha1', hex2bin((string) $hexdump)); $subject['opensslHash'] = self::opensslSubjHash($hexdump); $subject['hexdump'] = $hexdump; - $curr['subject']=$subject; + $curr['subject'] = $subject; unset($curr[$key]); continue; } - if($value['type'] == '30' && !array_key_exists('subjectPublicKeyInfo', $curr)) { - foreach($value as $subjectPublicKeyInfoK=>$subjectPublicKeyInfoV) { + if($value['type'] == '30' && ! array_key_exists('subjectPublicKeyInfo', $curr)) { + foreach($value as $subjectPublicKeyInfoK => $subjectPublicKeyInfoV) { if(is_numeric($subjectPublicKeyInfoK)) { if($subjectPublicKeyInfoV['type'] == '30') { - $subjectPublicKeyInfo['algorithm']=self::oidfromhex($subjectPublicKeyInfoV[0]['value_hex']); + $subjectPublicKeyInfo['algorithm'] = self::oidfromhex($subjectPublicKeyInfoV[0]['value_hex']); } if($subjectPublicKeyInfoV['type'] == '03') { - $subjectPublicKeyInfo['subjectPublicKey']=substr($subjectPublicKeyInfoV['value'], 2); + $subjectPublicKeyInfo['subjectPublicKey'] = substr((string) $subjectPublicKeyInfoV['value'], 2); } } else { unset($curr[$key]); } } - $subjectPublicKeyInfo['hex']=$value['hexdump']; - $subjectPublicKey_parse =asn1::parse($subjectPublicKeyInfo['subjectPublicKey']); - $subjectPublicKeyInfo['keyLength']=(strlen(substr($subjectPublicKey_parse[0][0]['value'], 2))/2)*8; - $subjectPublicKeyInfo['sha1']=hash('sha1', pack('H*', $subjectPublicKeyInfo['subjectPublicKey'])); - $curr['subjectPublicKeyInfo']=$subjectPublicKeyInfo; + $subjectPublicKeyInfo['hex'] = $value['hexdump']; + $subjectPublicKey_parse = asn1::parse($subjectPublicKeyInfo['subjectPublicKey']); + $subjectPublicKeyInfo['keyLength'] = (strlen(substr((string) $subjectPublicKey_parse[0][0]['value'], 2)) / 2) * 8; + $subjectPublicKeyInfo['sha1'] = hash('sha1', pack('H*', $subjectPublicKeyInfo['subjectPublicKey'])); + $curr['subjectPublicKeyInfo'] = $subjectPublicKeyInfo; unset($curr[$key]); continue; } if($value['type'] == 'a3') { - $curr['attributes']=$value[0]; + $curr['attributes'] = $value[0]; unset($curr[$key]); } $i++; } else { - $tbsCertificateTag[$key]=$value; + $tbsCertificateTag[$key] = $value; } } $ar['cert']['tbsCertificate'] = $curr; if(array_key_exists('attributes', $ar['cert']['tbsCertificate'])) { $curr = $ar['cert']['tbsCertificate']['attributes']; - foreach($curr as $key=>$value) { + foreach($curr as $key => $value) { if(is_numeric($key)) { if($value['type'] == '30') { $critical = 0; @@ -833,7 +834,7 @@ public static function readcert($cert_in, $oidprint=false) { $extvalue = $value[1][0]['value_hex']; } if($name_hex == '551d23') { // OBJ_authority_key_identifier - foreach($value[1][0] as $OBJ_authority_key_identifierKey=>$OBJ_authority_key_identifierVal) { + foreach($value[1][0] as $OBJ_authority_key_identifierKey => $OBJ_authority_key_identifierVal) { if(is_numeric($OBJ_authority_key_identifierKey)) { if($OBJ_authority_key_identifierVal['type'] == '80') { $OBJ_authority_key_identifier['keyid'] = $OBJ_authority_key_identifierVal['value_hex']; @@ -849,20 +850,20 @@ public static function readcert($cert_in, $oidprint=false) { $extvalue = $OBJ_authority_key_identifier; } if($name_hex == '2b06010505070101') { // OBJ_info_access - foreach($value[1][0] as $OBJ_info_accessK=>$OBJ_info_accessV) { + foreach($value[1][0] as $OBJ_info_accessK => $OBJ_info_accessV) { if(is_numeric($OBJ_info_accessK)) { $OBJ_info_accessHEX = $OBJ_info_accessV[0]['value_hex']; $OBJ_info_accessOID = self::oidfromhex($OBJ_info_accessHEX); $OBJ_info_accessNAME = $OBJ_info_accessOID; - $OBJ_info_access[$OBJ_info_accessNAME][] = hex2bin($OBJ_info_accessV[1]['value_hex']); + $OBJ_info_access[$OBJ_info_accessNAME][] = hex2bin((string) $OBJ_info_accessV[1]['value_hex']); } } $extvalue = $OBJ_info_access; } if($name_hex == '551d1f') { // OBJ_crl_distribution_points 551d1f - foreach($value[1][0] as $OBJ_crl_distribution_pointsK=>$OBJ_crl_distribution_pointsV) { + foreach($value[1][0] as $OBJ_crl_distribution_pointsK => $OBJ_crl_distribution_pointsV) { if(is_numeric($OBJ_crl_distribution_pointsK)) { - $OBJ_crl_distribution_points[] = hex2bin($OBJ_crl_distribution_pointsV[0][0][0]['value_hex']); + $OBJ_crl_distribution_points[] = hex2bin((string) $OBJ_crl_distribution_pointsV[0][0][0]['value_hex']); } } $extvalue = $OBJ_crl_distribution_points; @@ -873,7 +874,7 @@ public static function readcert($cert_in, $oidprint=false) { if($name_hex == '551d13') { // OBJ_basic_constraints $bc['ca'] = '0'; $bc['pathLength'] = ''; - foreach($extvalue[0] as $bck=>$bcv) { + foreach($extvalue[0] as $bck => $bcv) { if(is_numeric($bck)) { if($bcv['type'] == '01') { if($bcv['value_hex'] == 'ff') { @@ -888,7 +889,7 @@ public static function readcert($cert_in, $oidprint=false) { $extvalue = $bc; } if($name_hex == '551d25') { // OBJ_ext_key_usage 551d1f - foreach($extvalue[0] as $OBJ_ext_key_usageK=>$OBJ_ext_key_usageV) { + foreach($extvalue[0] as $OBJ_ext_key_usageK => $OBJ_ext_key_usageV) { if(is_numeric($OBJ_ext_key_usageK)) { $OBJ_ext_key_usageHEX = $OBJ_ext_key_usageV['value_hex']; $OBJ_ext_key_usageOID = self::oidfromhex($OBJ_ext_key_usageHEX); @@ -898,13 +899,13 @@ public static function readcert($cert_in, $oidprint=false) { } $extvalue = $OBJ_ext_key_usage; } - $extsVal=array( - 'name_hex'=>$value[0]['value_hex'], - 'name_oid'=>self::oidfromhex($value[0]['value_hex']), - 'name'=>self::oidfromhex($value[0]['value_hex']), - 'critical'=>$critical, - 'value'=>$extvalue - ); + $extsVal = [ + 'name_hex' => $value[0]['value_hex'], + 'name_oid' => self::oidfromhex($value[0]['value_hex']), + 'name' => self::oidfromhex($value[0]['value_hex']), + 'critical' => $critical, + 'value' => $extvalue, + ]; $extNameOID = $value[0]['value_hex']; if($oidprint == 'oid') { $extNameOID = self::oidfromhex($extNameOID); @@ -930,12 +931,12 @@ public static function readcert($cert_in, $oidprint=false) { * @param string $hex hex form oid number * @return string oid number */ - private static function oidfromhex($hex) { + private static function oidfromhex($hex): string { $split = str_split($hex, 2); $i = 0; foreach($split as $val) { $dec = hexdec($val); - $mplx[$i] = ($dec-128)*128; + $mplx[$i] = ($dec - 128) * 128; $i++; } $i = 0; @@ -945,22 +946,22 @@ private static function oidfromhex($hex) { $dec = hexdec($val); if($i == 0) { if($dec >= 128) { - $nex = (128*($dec-128))-80; + $nex = (128 * ($dec - 128)) - 80; if($dec > 129) { - $nex = (128*($dec-128))-80; + $nex = (128 * ($dec - 128)) - 80; } $result = "2."; } if($dec >= 80 && $dec < 128) { - $first = $dec-80; + $first = $dec - 80; $result = "2.$first."; } if($dec >= 40 && $dec < 80) { - $first = $dec-40; + $first = $dec - 40; $result = "1.$first."; } if($dec < 40) { - $first = $dec-0; + $first = $dec - 0; $result = "0.$first."; } } else { @@ -968,10 +969,10 @@ private static function oidfromhex($hex) { if($nex == false) { $nex = $mplx[$i]; } else { - $nex = ($nex*128)+$mplx[$i]; + $nex = ($nex * 128) + $mplx[$i]; } } else { - $result .= ($dec+$nex)."."; + $result .= ($dec + $nex) . "."; if($dec <= 127) { $nex = 0; } diff --git a/src/pdfvalue/PDFValue.php b/src/pdfvalue/PDFValue.php index 30ce38a..b836ef7 100644 --- a/src/pdfvalue/PDFValue.php +++ b/src/pdfvalue/PDFValue.php @@ -21,63 +21,78 @@ namespace ddn\sapp\pdfvalue; use \ArrayAccess; -use function ddn\sapp\helpers\p_debug_var; +use ReturnTypeWillChange; +use Stringable; -class PDFValue implements ArrayAccess { - protected $value = null; - public function __construct($v) { - $this->value = $v; +class PDFValue implements ArrayAccess, Stringable +{ + public function __construct( + protected $value + ) + { } + public function val() { return $this->value; } - public function __toString() { + + public function __toString(): string { return "" . $this->value; } - public function offsetExists ( $offset ) : bool { - if (!is_array($this->value)) return false; + + public function offsetExists ( $offset ): bool { + if (! is_array($this->value)) return false; return isset($this->value[$offset]); } - #[\ReturnTypeWillChange] + + #[ReturnTypeWillChange] public function offsetGet ( $offset ) { - if (!is_array($this->value)) return false; - if (!isset($this->value[$offset])) return false; + if (! is_array($this->value)) return false; + if (! isset($this->value[$offset])) return false; return $this->value[$offset]; } - public function offsetSet($offset , $value ) : void { - if (!is_array($this->value)) return; + + public function offsetSet($offset, $value ): void { + if (! is_array($this->value)) return; $this->value[$offset] = $value; } - public function offsetUnset($offset ) : void { - if ((!is_array($this->value)) || (!isset($this->value[$offset]))) + + public function offsetUnset($offset ): void { + if ((! is_array($this->value)) || (! isset($this->value[$offset]))) throw new Exception('invalid offset'); unset($this->value[$offset]); - } - public function push($v) { + } + + public function push($v): bool { /*if (get_class($v) !== get_class($this)) throw new Exception('invalid object to concat to this one');*/ return false; } - public function get_int() { + + public function get_int(): false|int { return false; } - public function get_object_referenced() { + + public function get_object_referenced(): false|array|int { return false; - } - public function get_keys() { + } + + public function get_keys(): false|array { return false; } + /** * Returns the difference between this and other object (false means "cannot compare", null means "equal" and any value means "different": things in this object that are different from the other) */ public function diff($other) { - if (!is_a($other, get_class($this))) + if (! is_a($other, static::class)) return false; if ($this->value === $other->value) { return null; } return $this->value; } + /** * Function that converts standard types into PDFValue* types * - integer, double are translated into PDFValueSimple @@ -86,7 +101,7 @@ public function diff($other) { * - other strings are translated into PDFValueString * - array is translated into PDFValueList, and its inner elements are also converted. * @param value a standard php object (e.g. string, integer, double, array, etc.) - * @return pdfvalue an object of type PDFValue*, depending on the + * @return pdfvalue an object of type PDFValue*, depending on the */ protected static function _convert($value) { switch (gettype($value)) { @@ -99,18 +114,18 @@ protected static function _convert($value) { $value = new PDFValueType(substr($value, 1)); else if (preg_match("/\s/ms", $value) === 1) - $value = new PDFValueString($value); + $value = new PDFValueString($value); else - $value = new PDFValueSimple($value); + $value = new PDFValueSimple($value); break; - case 'array': + case 'array': if (count($value) === 0) { // An empty list is assumed to be a list $value = new PDFValueList(); } else { // Try to parse it as an object (i.e. [ 'Field' => 'Value', ...]) - $obj = PDFValueObject::fromarray($value); + $obj = PDFValueObject::fromarray($value); if ($obj !== false) $value = $obj; else { @@ -126,5 +141,5 @@ protected static function _convert($value) { break; } return $value; - } + } } diff --git a/src/pdfvalue/PDFValueHexString.php b/src/pdfvalue/PDFValueHexString.php index 101dc3d..a6d4c99 100644 --- a/src/pdfvalue/PDFValueHexString.php +++ b/src/pdfvalue/PDFValueHexString.php @@ -22,7 +22,7 @@ namespace ddn\sapp\pdfvalue; class PDFValueHexString extends PDFValueString { - public function __toString() { - return "<" . trim($this->value) . ">"; + public function __toString(): string { + return "<" . trim((string) $this->value) . ">"; } } diff --git a/src/pdfvalue/PDFValueList.php b/src/pdfvalue/PDFValueList.php index 4bb6b36..9c9fe3f 100644 --- a/src/pdfvalue/PDFValueList.php +++ b/src/pdfvalue/PDFValueList.php @@ -20,19 +20,16 @@ */ namespace ddn\sapp\pdfvalue; -use function ddn\sapp\helpers\p_debug_var; -use function ddn\sapp\helpers\p_debug; -use ddn\sapp\pdfvalue\PDFValueSimple; - class PDFValueList extends PDFValue { public function __construct($value = []) { parent::__construct($value); } - public function __toString() { + + public function __toString(): string { return '[' . implode(' ', $this->value) . ']'; } - public function diff($other) { + public function diff($other): false|null|self { $different = parent::diff($other); if (($different === false) || ($different === null)) return $different; @@ -51,9 +48,9 @@ public function val($list = false) { $result = []; foreach ($this->value as $v) { if (is_a($v, "ddn\\sapp\\pdfvalue\\PDFValueSimple")) { - $v = explode(" ", $v->val()); + $v = explode(" ", (string) $v->val()); } else { - $v = [ $v->val() ]; + $v = [$v->val()]; } array_push($result, ...$v); } @@ -65,7 +62,7 @@ public function val($list = false) { /** * This function returns a list of objects that are referenced in the list, only if all of them are references to objects */ - public function get_object_referenced() { + public function get_object_referenced(): false|array { $ids = []; $plain_text_val = implode(' ', $this->value); if (trim($plain_text_val) !== "") { @@ -90,12 +87,12 @@ public function get_object_referenced() { * - if it is a list object, the lists are merged; * - otherwise the object is converted to a PDFValue* object and it is appended to the list */ - public function push($v) { - if (is_object($v) && (get_class($v) === get_class($this))) { + public function push($v): bool { + if (is_object($v) && ($v::class === static::class)) { // If a list is pushed to another list, the elements are merged $v = $v->val(); } - if (!is_array($v)) $v = [ $v ]; + if (! is_array($v)) $v = [$v]; foreach ($v as $e) { $e = self::_convert($e); array_push($this->value, $e); diff --git a/src/pdfvalue/PDFValueObject.php b/src/pdfvalue/PDFValueObject.php index 48fcf40..4a75a3a 100644 --- a/src/pdfvalue/PDFValueObject.php +++ b/src/pdfvalue/PDFValueObject.php @@ -30,7 +30,7 @@ public function __construct($value = []) { parent::__construct($result); } - public function diff($other) { + public function diff($other): false|null|\ddn\sapp\pdfvalue\PDFValueObject { $different = parent::diff($other); if (($different === false) || ($different === null)) return $different; @@ -44,12 +44,12 @@ public function diff($other) { if ($different === false) { $result[$k] = $v; $differences++; - } else + } else if ($different !== null) { $result[$k] = $different; $differences++; - } - } + } + } } else { $result[$k] = $v; $differences++; @@ -57,11 +57,11 @@ public function diff($other) { } if ($differences === 0) return null; - + return $result; } - public static function fromarray($parts) { + public static function fromarray($parts): false|\ddn\sapp\pdfvalue\PDFValueObject { $k = array_keys($parts); $intkeys = false; $result = []; @@ -77,11 +77,11 @@ public static function fromarray($parts) { return new PDFValueObject($result); } - public static function fromstring($str) { + public static function fromstring($str): false|\ddn\sapp\pdfvalue\PDFValueObject { $result = []; $field = null; $value = null; - $parts = explode(' ', $str); + $parts = explode(' ', (string) $str); for ($i = 0; $i < count($parts); $i++) { if ($field === null) { $field = $parts[$i]; @@ -100,7 +100,7 @@ public static function fromstring($str) { return new PDFValueObject($result); } - public function get_keys() { + public function get_keys(): false|array { return array_keys($this->value); } @@ -111,7 +111,7 @@ public function get_keys() { * @param value the value to set to that index (it will be converted to a PDFValue* object) * @return value the value set to the field */ - public function offsetSet($offset , $value) : void { + public function offsetSet($offset, $value): void { if ($value === null) { if (isset($this->value[$offset])) unset($this->value[$offset]); @@ -120,7 +120,8 @@ public function offsetSet($offset , $value) : void { $this->value[$offset] = self::_convert($value); // return $this->value[$offset]; } - public function offsetExists ( $offset ) : bool { + + public function offsetExists ( $offset ): bool { return isset($this->value[$offset]); } @@ -128,7 +129,7 @@ public function offsetExists ( $offset ) : bool { * Function to output the object using the PDF format, and trying to make it compact (by reducing spaces, depending on the values) * @return pdfentry the PDF entry for the object */ - public function __toString() { + public function __toString(): string { $result = []; foreach ($this->value as $k => $v) { $v = "" . $v; @@ -136,17 +137,11 @@ public function __toString() { array_push($result, "/$k"); continue; } - switch ($v[0]) { - case '/': - case '[': - case '(': - case '<': - array_push($result, "/$k$v"); - break; - default: - array_push($result, "/$k $v"); - } + match ($v[0]) { + '/', '[', '(', '<' => array_push($result, "/$k$v"), + default => array_push($result, "/$k $v"), + }; } return "<<" . implode('', $result) . ">>"; } -} \ No newline at end of file +} diff --git a/src/pdfvalue/PDFValueReference.php b/src/pdfvalue/PDFValueReference.php index 1d7790f..f13c2ee 100644 --- a/src/pdfvalue/PDFValueReference.php +++ b/src/pdfvalue/PDFValueReference.php @@ -28,4 +28,4 @@ class PDFValueReference extends PDFValueSimple { public function __construct($oid) { parent::__construct(sprintf("%d 0 R", $oid)); } -}; \ No newline at end of file +}; diff --git a/src/pdfvalue/PDFValueSimple.php b/src/pdfvalue/PDFValueSimple.php index b05f7ab..07d6e61 100644 --- a/src/pdfvalue/PDFValueSimple.php +++ b/src/pdfvalue/PDFValueSimple.php @@ -25,21 +25,24 @@ class PDFValueSimple extends PDFValue { public function __construct($v) { parent::__construct($v); } - public function push($v) { - if (get_class($v) === get_class($this)) { + + public function push($v): bool { + if ($v::class === static::class) { // Can push $this->value = $this->value . ' ' . $v->val(); return true; } return false; } - public function get_object_referenced() { - if (! preg_match('/^\s*([0-9]+)\s+([0-9]+)\s+R\s*$/ms', $this->value, $matches)) { + + public function get_object_referenced(): false|int { + if (! preg_match('/^\s*([0-9]+)\s+([0-9]+)\s+R\s*$/ms', (string) $this->value, $matches)) { return false; } return intval($matches[1]); } - public function get_int() { + + public function get_int(): false|int { if (! is_numeric($this->value)) return false; return intval($this->value); } diff --git a/src/pdfvalue/PDFValueString.php b/src/pdfvalue/PDFValueString.php index ae68d60..bfb7563 100644 --- a/src/pdfvalue/PDFValueString.php +++ b/src/pdfvalue/PDFValueString.php @@ -22,7 +22,7 @@ namespace ddn\sapp\pdfvalue; class PDFValueString extends PDFValue { - public function __toString() { + public function __toString(): string { return "(" . $this->value . ")"; } } \ No newline at end of file diff --git a/src/pdfvalue/PDFValueType.php b/src/pdfvalue/PDFValueType.php index 3352192..6475d22 100644 --- a/src/pdfvalue/PDFValueType.php +++ b/src/pdfvalue/PDFValueType.php @@ -22,7 +22,7 @@ namespace ddn\sapp\pdfvalue; class PDFValueType extends PDFValue { - public function __toString() { - return "/" . trim($this->value); + public function __toString(): string { + return "/" . trim((string) $this->value); } } From 4e00e3c7ca81753805d4bd0fc4cba26e7e74a3c4 Mon Sep 17 00:00:00 2001 From: Marcin Jakubowski Date: Thu, 31 Oct 2024 20:39:04 +0100 Subject: [PATCH 02/11] mostly automated changes to improve code style --- src/PDFDoc.php | 454 ++++-- src/PDFDocWithContents.php | 87 +- src/PDFObject.php | 119 +- src/PDFObjectParser.php | 745 +++++----- src/PDFSignatureObject.php | 43 +- src/PDFUtilFnc.php | 201 ++- src/helpers/Buffer.php | 67 +- src/helpers/CMS.php | 1002 +++++++------- src/helpers/DependencyTreeObject.php | 69 +- src/helpers/LoadHelpers.php | 4 +- src/helpers/StreamReader.php | 56 +- src/helpers/UUID.php | 182 +-- src/helpers/asn1.php | 484 ++++--- src/helpers/contentgeneration.php | 97 +- src/helpers/fpdfhelpers.php | 354 ++--- src/helpers/helpers.php | 109 +- src/helpers/mime.php | 6 +- src/helpers/x509.php | 1924 +++++++++++++------------- src/pdfvalue/PDFValue.php | 88 +- src/pdfvalue/PDFValueHexString.php | 6 +- src/pdfvalue/PDFValueList.php | 57 +- src/pdfvalue/PDFValueObject.php | 75 +- src/pdfvalue/PDFValueReference.php | 8 +- src/pdfvalue/PDFValueSimple.php | 25 +- src/pdfvalue/PDFValueString.php | 8 +- src/pdfvalue/PDFValueType.php | 6 +- 26 files changed, 3519 insertions(+), 2757 deletions(-) diff --git a/src/PDFDoc.php b/src/PDFDoc.php index bf79951..d5587c0 100644 --- a/src/PDFDoc.php +++ b/src/PDFDoc.php @@ -19,7 +19,9 @@ along with this program. If not, see . */ -namespace ddn\sapp; use DateTime; +namespace ddn\sapp; + +use DateTime; use function ddn\sapp\helpers\_add_image; use ddn\sapp\helpers\Buffer; use ddn\sapp\helpers\CMS; @@ -38,18 +40,23 @@ use ddn\sapp\pdfvalue\PDFValueReference; use ddn\sapp\pdfvalue\PDFValueSimple; use ddn\sapp\pdfvalue\PDFValueString; -// Loading the functions use Throwable; -if (! defined("ddn\\sapp\\helpers\\LoadHelpers")) - new LoadHelpers; -if (! defined('__TMP_FOLDER')) +// Loading the functions + +if (! defined("ddn\\sapp\\helpers\\LoadHelpers")) { + new LoadHelpers(); +} + +if (! defined('__TMP_FOLDER')) { define('__TMP_FOLDER', sys_get_temp_dir()); +} // TODO: move the signature of documents to a new class (i.e. PDFDocSignable) // TODO: create a new class "PDFDocIncremental" -class PDFDoc extends Buffer { +class PDFDoc extends Buffer +{ // The PDF version of the parsed file protected $_pdf_objects = []; @@ -95,16 +102,20 @@ class PDFDoc extends Buffer { protected $_pages_info = []; // Gets a new oid for a new object - protected function get_new_oid(): int|float { + protected function get_new_oid(): int|float + { $this->_max_oid++; + return $this->_max_oid; } /** * Retrieve the number of pages in the document (not considered those pages that could be added by the user using this object or derived ones) + * * @return pagecount number of pages in the original document */ - public function get_page_count(): int { + public function get_page_count(): int + { return count($this->_pages_info); } @@ -113,7 +124,8 @@ public function get_page_count(): int { * the state using function "pop_state". Many states can be stored, and they will be retrieved in reverse order * using pop_state */ - public function push_state(): void { + public function push_state(): void + { $cloned_objects = []; foreach ($this->_pdf_objects as $oid => $object) { $cloned_objects[$oid] = clone $object; @@ -126,30 +138,37 @@ public function push_state(): void { /** * Function that retrieves an stored state by means of function "push_state" + * * @return restored true if a previous state was restored; false if there was no stored state */ - public function pop_state(): bool { + public function pop_state(): bool + { if (count($this->_backup_state) > 0) { $state = array_pop($this->_backup_state); $this->_max_oid = $state['max_oid']; $this->_pdf_objects = $state['pdf_objects']; + return true; } + return false; } /** * The function parses a document from a string: analyzes the structure and obtains and object * of type PDFDoc (if possible), or false, if an error happens. + * * @param buffer a string that contains the file to analyze * @param depth the number of previous versions to consider; if null, will consider any version; * otherwise only the object ids from the latest $depth versions will be considered * (if it is an incremental updated document) */ - public static function from_string($buffer, $depth = null): false|\ddn\sapp\PDFDoc { + public static function from_string($buffer, $depth = null): false|PDFDoc + { $structure = PDFUtilFnc::acquire_structure($buffer, $depth); - if ($structure === false) + if ($structure === false) { return false; + } $trailer = $structure["trailer"]; $version = $structure["version"]; @@ -166,28 +185,34 @@ public static function from_string($buffer, $depth = null): false|\ddn\sapp\PDFD $pdfdoc->_revisions = $revisions; $pdfdoc->_buffer = $buffer; - if ($trailer !== false) - if ($trailer['Encrypt'] !== false) - // TODO: include encryption (maybe borrowing some code: http://www.fpdf.org/en/script/script37.php) + if ($trailer !== false) { + if ($trailer['Encrypt'] !== false) // TODO: include encryption (maybe borrowing some code: http://www.fpdf.org/en/script/script37.php) + { p_error("encrypted documents are not fully supported; maybe you cannot get the expected results"); + } + } $oids = array_keys($xref_table); sort($oids); $pdfdoc->_max_oid = array_pop($oids); - if ($trailer === false) + if ($trailer === false) { p_warning("invalid trailer object"); - else + } else { $pdfdoc->_acquire_pages_info(); + } return $pdfdoc; } - public function get_revision($rev_i): string { - if ($rev_i === null) + public function get_revision($rev_i): string + { + if ($rev_i === null) { $rev_i = count($this->_revisions) - 1; - if ($rev_i < 0) + } + if ($rev_i < 0) { $rev_i = count($this->_revisions) + $rev_i - 1; + } return substr((string) $this->_buffer, 0, $this->_revisions[$rev_i]); } @@ -195,7 +220,8 @@ public function get_revision($rev_i): string { /** * Function that builds the object list from the xref table */ - public function build_objects_from_xref(): void { + public function build_objects_from_xref(): void + { foreach ($this->_xref_table as $oid => $obj) { $obj = $this->get_object($oid); $this->add_object($obj); @@ -207,21 +233,28 @@ public function build_objects_from_xref(): void { * This mechanism enables to walk over any object, either they are new ones or they were in the original doc. * Enables: * foreach ($doc->get_object_iterator() as $oid => obj) { ... } + * * @param allobjects the iterator obtains any possible object, according to the oids; otherwise, only will return the * objects that appear in the current version of the xref + * * @return oid=>obj the objects */ - public function get_object_iterator($allobjects = false) { + public function get_object_iterator($allobjects = false) + { if ($allobjects === true) { for ($i = 0; $i <= $this->_max_oid; $i++) { yield $i => $this->get_object($i); } } else { foreach ($this->_xref_table as $oid => $offset) { - if ($offset === null) continue; + if ($offset === null) { + continue; + } $o = $this->get_object($oid); - if ($o === false) continue; + if ($o === false) { + continue; + } yield $oid => $o; } @@ -231,40 +264,50 @@ public function get_object_iterator($allobjects = false) { /** * This function checks whether the passed object is a reference or not, and in case that * it is a reference, it returns the referenced object; otherwise it return the object itself + * * @param reference the reference value to obtain + * * @return obj it reference can be interpreted as a reference, the referenced object; otherwise, the object itself. * If the passed value is an array of references, it will return false */ - public function get_indirect_object( $reference ) { + public function get_indirect_object($reference) + { $object_id = $reference->get_object_referenced(); if ($object_id !== false) { - if (is_array($object_id)) + if (is_array($object_id)) { return false; + } + return $this->get_object($object_id); } + return $reference; } /** * Obtains an object from the document, usign the oid in the PDF document. + * * @param oid the oid of the object that is being retrieved * @param original if true and the object has been overwritten in this document, the object * retrieved will be the original one. Setting to false will retrieve the * more recent object + * * @return obj the object retrieved (or false if not found) */ - public function get_object(int $oid, $original_version = false) { + public function get_object(int $oid, $original_version = false) + { if ($original_version === true) { // Prioritizing the original version $object = PDFUtilFnc::find_object($this->_buffer, $this->_xref_table, $oid); - if ($object === false) + if ($object === false) { $object = $this->_pdf_objects[$oid] ?? false; - + } } else { // Prioritizing the new versions $object = $this->_pdf_objects[$oid] ?? false; - if ($object === false) + if ($object === false) { $object = PDFUtilFnc::find_object($this->_buffer, $this->_xref_table, $oid); + } } return $object; @@ -273,11 +316,13 @@ public function get_object(int $oid, $original_version = false) { /** * Function that sets the appearance of the signature (if the document is to be signed). At this time, it is possible to set * the page in which the signature will appear, the rectangle, and an image that will be shown in the signature form. + * * @param page the page (zero based) in which the signature will appear * @param rect the rectangle (in page-based coordinates) where the signature will appear in that page * @param imagefilename an image file name (or an image in a buffer, with symbol '@' prepended) that will be put inside the rect */ - public function set_signature_appearance($page_to_appear = 0, $rect_to_appear = [0, 0, 0, 0], $imagefilename = null): void { + public function set_signature_appearance($page_to_appear = 0, $rect_to_appear = [0, 0, 0, 0], $imagefilename = null): void + { $this->_appearance = [ "page" => $page_to_appear, "rect" => $rect_to_appear, @@ -288,48 +333,58 @@ public function set_signature_appearance($page_to_appear = 0, $rect_to_appear = /** * Removes the settings of signature appearance (i.e. no signature will appear in the document) */ - public function clear_signature_appearance(): void { + public function clear_signature_appearance(): void + { $this->_appearance = null; } /** * Removes the certificate for the signature (i.e. the document will not be signed) */ - public function clear_signature_certificate(): void { + public function clear_signature_certificate(): void + { $this->_certificate = null; } /** * Function that stores the certificate to use, when signing the document + * * @param certfile a file that contains a user certificate in pkcs12 format, * or an array [ 'cert' => , 'pkey' => , 'extracerts' => ] * that would be the output of openssl_pkcs12_read * @param password the password to read the private key + * * @return valid true if the certificate can be used to sign the document, false otherwise */ - public function set_signature_certificate($certfile, $certpass = null) { + public function set_signature_certificate($certfile, $certpass = null) + { // First we read the certificate if (is_array($certfile)) { $certificate = $certfile; $certificate["pkey"] = [$certificate["pkey"], $certpass]; // If a password is provided, we'll try to decode the private key - if (openssl_pkey_get_private($certificate["pkey"]) === false) + if (openssl_pkey_get_private($certificate["pkey"]) === false) { return p_error("invalid private key"); - if (! openssl_x509_check_private_key($certificate["cert"], $certificate["pkey"])) + } + if (! openssl_x509_check_private_key($certificate["cert"], $certificate["pkey"])) { return p_error("private key doesn't corresponds to certificate"); + } if (is_string($certificate['extracerts'] ?? null)) { $certificate['extracerts'] = array_filter(explode("-----END CERTIFICATE-----\n", $certificate['extracerts'])); - foreach ($certificate['extracerts'] as &$extracerts) + foreach ($certificate['extracerts'] as &$extracerts) { $extracerts = $extracerts . "-----END CERTIFICATE-----\n"; + } } } else { $certfilecontent = file_get_contents($certfile); - if ($certfilecontent === false) + if ($certfilecontent === false) { return p_error("could not read file $certfile"); - if (openssl_pkcs12_read($certfilecontent, $certificate, $certpass) === false) + } + if (openssl_pkcs12_read($certfilecontent, $certificate, $certpass) === false) { return p_error("could not get the certificates from file $certfile"); + } } // Store the certificate @@ -340,11 +395,13 @@ public function set_signature_certificate($certfile, $certpass = null) { /** * Function that stores the ltv configuration to use, when signing the document + * * @param $ocspURI OCSP Url to validate cert file * @param $crlURIorFILE Crl filename/url to validate cert * @param $issuerURIorFILE issuer filename/url */ - public function set_ltv($ocspURI = null, $crlURIorFILE = null, $issuerURIorFILE = null): void { + public function set_ltv($ocspURI = null, $crlURIorFILE = null, $issuerURIorFILE = null): void + { $this->_signature_ltv_data['ocspURI'] = $ocspURI; $this->_signature_ltv_data['crlURIorFILE'] = $crlURIorFILE; $this->_signature_ltv_data['issuerURIorFILE'] = $issuerURIorFILE; @@ -352,11 +409,13 @@ public function set_ltv($ocspURI = null, $crlURIorFILE = null, $issuerURIorFILE /** * Function that stores the tsa configuration to use, when signing the document + * * @param $tsaurl Link to tsa service * @param $tsauser the user for tsa service * @param $tsapass the password for tsa service */ - public function set_tsa($tsa, $tsauser = null, $tsapass = null): void { + public function set_tsa($tsa, $tsauser = null, $tsapass = null): void + { $this->_signature_tsa['host'] = $tsa; if ($tsauser && $tsapass) { $this->_signature_tsa['user'] = $tsauser; @@ -366,10 +425,12 @@ public function set_tsa($tsa, $tsauser = null, $tsapass = null): void { /** * Function to set the metadata properties for the certificate options + * * @param $name * @param $reason * @param $location * @param $contact + * * @return void */ public function set_metadata_props($name = null, $reason = null, $location = null, $contact = null): void @@ -387,10 +448,8 @@ public function set_metadata_props($name = null, $reason = null, $location = nul * - create a form object (along with other objects) that will hold the appearance of the annotation object * - modify the root object to make acroform point to the annotation object * - modify the page object to make the annotations of that page include the annotation object - * * > If the appearance is not set, the image will not appear, and the signature object will be invisible. * > If the certificate is not set, the signature created will be a placeholder (that acrobat will able to sign) - * * LIMITATIONS: one document can be signed once at a time; if wanted more signatures, then chain the documents: * $o1->set_signature_certificate(...); * $o2 = PDFDoc::fromstring($o1->to_pdf_file_s); @@ -399,7 +458,8 @@ public function set_metadata_props($name = null, $reason = null, $location = nul * * @return signature a signature object, or null if the document is not signed; false if an error happens */ - protected function _generate_signature_in_document() { + protected function _generate_signature_in_document() + { $imagefilename = null; $recttoappear = [0, 0, 0, 0]; $pagetoappear = 0; @@ -413,24 +473,28 @@ protected function _generate_signature_in_document() { // First of all, we are searching for the root object (which should be in the trailer) $root = $this->_pdf_trailer_object["Root"]; - if (($root === false) || (($root = $root->get_object_referenced()) === false)) + if (($root === false) || (($root = $root->get_object_referenced()) === false)) { return p_error("could not find the root object from the trailer"); + } $root_obj = $this->get_object($root); - if ($root_obj === false) + if ($root_obj === false) { return p_error("invalid root object"); + } // Now the object corresponding to the page number in which to appear $page_obj = $this->get_page($pagetoappear); - if ($page_obj === false) + if ($page_obj === false) { return p_error("invalid page"); + } // The objects to update $updated_objects = []; // Add the annotation to the page - if (! isset($page_obj["Annots"])) + if (! isset($page_obj["Annots"])) { $page_obj["Annots"] = new PDFValueList(); + } $annots = &$page_obj["Annots"]; $page_rotation = $page_obj["Rotate"] ?? new PDFValueSimple(0); @@ -466,7 +530,7 @@ protected function _generate_signature_in_document() { if ($this->_certificate !== null) { // Perform signature test to get signature size to define __SIGNATURE_MAX_LENGTH p_debug(" ########## PERFORM SIGNATURE LENGTH CHECK ##########\n"); - $CMS = new CMS; + $CMS = new CMS(); $CMS->signature_data['signcert'] = $this->_certificate['cert']; $CMS->signature_data['extracerts'] = $this->_certificate['extracerts'] ?? null; $CMS->signature_data['hashAlgorithm'] = 'sha256'; @@ -483,11 +547,11 @@ protected function _generate_signature_in_document() { //$signature = new PDFSignatureObject([]); $signature->set_metadata($this->_metadata_name, $this->_metadata_reason, $this->_metadata_location, $this->_metadata_contact_info); $signature->set_certificate($this->_certificate); - if($this->_signature_tsa !== null) { - $signature->set_signature_tsa($this->_signature_tsa); + if ($this->_signature_tsa !== null) { + $signature->set_signature_tsa($this->_signature_tsa); } - if($this->_signature_ltv_data !== null) { - $signature->set_signature_ltv($this->_signature_ltv_data); + if ($this->_signature_ltv_data !== null) { + $signature->set_signature_ltv($this->_signature_ltv_data); } // Update the value to the annotation object @@ -549,8 +613,9 @@ protected function _generate_signature_in_document() { ]); $result = _add_image($this->create_object(...), $imagefilename, $bbox[0], $bbox[1], $bbox[2], $bbox[3], $page_rotation->val()); - if ($result === false) + if ($result === false) { return p_error("could not add the image"); + } $layer_n2["Resources"] = $result["resources"]; $layer_n2->set_stream($result['command'], false); @@ -572,15 +637,17 @@ protected function _generate_signature_in_document() { $annotation_object["Rect"] = [$recttoappear[0], $pagesize_h - $recttoappear[1], $recttoappear[2], $pagesize_h - $recttoappear[3]]; } - if (! $newannots->push(new PDFValueReference($annotation_object->get_oid()))) + if (! $newannots->push(new PDFValueReference($annotation_object->get_oid()))) { return p_error("Could not update the page where the signature has to appear"); + } $page_obj["Annots"] = new PDFValueReference($newannots->get_oid()); array_push($updated_objects, $page_obj); // AcroForm may be an indirect object - if (! isset($root_obj["AcroForm"])) + if (! isset($root_obj["AcroForm"])) { $root_obj["AcroForm"] = new PDFValueObject(); + } $acroform = &$root_obj["AcroForm"]; if ((($referenced = $acroform->get_object_referenced()) !== false) && (! is_array($referenced))) { @@ -592,8 +659,9 @@ protected function _generate_signature_in_document() { // Add the annotation to the interactive form $acroform["SigFlags"] = 3; - if (! isset($acroform['Fields'])) + if (! isset($acroform['Fields'])) { $acroform['Fields'] = new PDFValueList(); + } // Add the annotation object to the interactive form if (! $acroform['Fields']->push(new PDFValueReference($annotation_object->get_oid()))) { @@ -611,22 +679,28 @@ protected function _generate_signature_in_document() { /** * Function that updates the modification date of the document. If modifies two parts: the "info" field of the trailer object * and the xmp metadata field pointed by the root object. + * * @param date a DateTime object that contains the date to be set; null to set "now" + * * @return ok true if the date could be set; false otherwise */ - protected function update_mod_date(DateTime $date = null) { + protected function update_mod_date(DateTime $date = null) + { // First of all, we are searching for the root object (which should be in the trailer) $root = $this->_pdf_trailer_object["Root"]; - if (($root === false) || (($root = $root->get_object_referenced()) === false)) + if (($root === false) || (($root = $root->get_object_referenced()) === false)) { return p_error("could not find the root object from the trailer"); + } $root_obj = $this->get_object($root); - if ($root_obj === false) + if ($root_obj === false) { return p_error("invalid root object"); + } - if ($date === null) + if ($date === null) { $date = new DateTime(); + } // Update the xmp metadata if exists if (isset($root_obj["Metadata"])) { @@ -644,46 +718,57 @@ protected function update_mod_date(DateTime $date = null) { // Update the information object (not really needed) $info = $this->_pdf_trailer_object["Info"]; - if (($info === false) || (($info = $info->get_object_referenced()) === false)) + if (($info === false) || (($info = $info->get_object_referenced()) === false)) { return p_error("could not find the info object from the trailer"); + } $info_obj = $this->get_object($info); - if ($info_obj === false) + if ($info_obj === false) { return p_error("invalid info object"); + } $info_obj["ModDate"] = new PDFValueString(timestamp_to_pdfdatestring($date)); $info_obj["Producer"] = new PDFValueString("Modificado con SAPP"); $this->add_object($info_obj); + return true; } /** * Function that gets the objects that have been read from the document + * * @return objects an array of objects, indexed by the oid of each object */ - public function get_objects() { + public function get_objects() + { return $this->_pdf_objects; } /** * Function that gets the version of the document. It will have the form * PDF-1.x + * * @return version the PDF version */ - public function get_version() { + public function get_version() + { return $this->_pdf_version_string; } /** * Function that sets the version for the document. + * * @param version the version of the PDF document (it shall have the form PDF-1.x) + * * @return correct true if the version had the proper form; false otherwise */ - public function set_version($version): bool { + public function set_version($version): bool + { if (preg_match("/PDF-1.\[0-9\]/", (string) $version) !== 1) { return false; } $this->_pdf_version_string = $version; + return true; } @@ -691,22 +776,30 @@ public function set_version($version): bool { * Function that creates a new PDFObject and stores it in the document object list, so that * it is automatically managed by the document. The returned object can be modified and * that modifications will be reflected in the document. + * * @param value the value that the object will contain + * * @return obj the PDFObject created */ - public function create_object($value = [], $class = "ddn\\sapp\\PDFObject", $autoadd = true): PDFObject { + public function create_object($value = [], $class = "ddn\\sapp\\PDFObject", $autoadd = true): PDFObject + { $o = new $class($this->get_new_oid(), $value); - if ($autoadd === true) + if ($autoadd === true) { $this->add_object($o); + } + return $o; } /** * Adds a pdf object to the document (overwrites the one with the same oid, if existed) + * * @param pdf_object the object to add to the document + * * @return true if the object was added; false otherwise (e.g. already exists an object of a greater generation) */ - public function add_object(PDFObject $pdf_object): bool { + public function add_object(PDFObject $pdf_object): bool + { $oid = $pdf_object->get_oid(); if (isset($this->_pdf_objects[$oid])) { @@ -718,22 +811,26 @@ public function add_object(PDFObject $pdf_object): bool { $this->_pdf_objects[$oid] = $pdf_object; // Update the maximum oid - if ($oid > $this->_max_oid) + if ($oid > $this->_max_oid) { $this->_max_oid = $oid; + } return true; } /** * This function generates all the contents of the file up to the xref entry. + * * @param rebuild whether to generate the xref with all the objects in the document (true) or * consider only the new ones (false) + * * @return xref_data [ the text corresponding to the objects, array of offsets for each object ] */ - protected function _generate_content_to_xref($rebuild = false): array { + protected function _generate_content_to_xref($rebuild = false): array + { if ($rebuild === true) { $result = new Buffer("%$this->_pdf_version_string" . __EOL); - } else { + } else { $result = new Buffer($this->_buffer); } @@ -746,7 +843,9 @@ protected function _generate_content_to_xref($rebuild = false): array { if ($rebuild === true) { for ($i = 0; $i <= $this->_max_oid; $i++) { - if (($object = $this->get_object($i)) === false) continue; + if (($object = $this->get_object($i)) === false) { + continue; + } $result->data($object->to_pdf_entry()); $offsets[$i] = $offset; @@ -765,13 +864,17 @@ protected function _generate_content_to_xref($rebuild = false): array { /** * This functions outputs the document to a buffer object, ready to be dumped to a file. + * * @param rebuild whether we are rebuilding the whole xref table or not (in case of incremental versions, we should use "false") + * * @return buffer a buffer that contains a pdf dumpable document */ - public function to_pdf_file_b($rebuild = false): Buffer { + public function to_pdf_file_b($rebuild = false): Buffer + { // We made no updates, so return the original doc - if (($rebuild === false) && (count($this->_pdf_objects) === 0) && ($this->_certificate === null) && ($this->_appearance === null)) + if (($rebuild === false) && (count($this->_pdf_objects) === 0) && ($this->_certificate === null) && ($this->_appearance === null)) { return new Buffer($this->_buffer); + } // Save the state prior to generating the objects $this->push_state(); @@ -784,6 +887,7 @@ public function to_pdf_file_b($rebuild = false): Buffer { $_signature = $this->_generate_signature_in_document(); if ($_signature === false) { $this->pop_state(); + return p_error("could not generate the signed document"); } } @@ -804,12 +908,14 @@ public function to_pdf_file_b($rebuild = false): Buffer { $target_version = $this->_xref_table_version; if ($this->_xref_table_version >= "1.5") { // i.e. xref streams - if ($doc_version_string > $target_version) + if ($doc_version_string > $target_version) { $target_version = $doc_version_string; + } } else { // i.e. xref+trailer - if ($doc_version_string < $target_version) + if ($doc_version_string < $target_version) { $target_version = $doc_version_string; + } } if ($target_version >= "1.5") { @@ -839,19 +945,25 @@ public function to_pdf_file_b($rebuild = false): Buffer { $trailer["ID"] = [$trailer["ID"][0], new PDFValueHexString(strtoupper($ID2))]; // We are not using predictors nor encoding - if (isset($trailer["DecodeParms"])) unset($trailer["DecodeParms"]); + if (isset($trailer["DecodeParms"])) { + unset($trailer["DecodeParms"]); + } // We are not compressing the stream - if (isset($trailer["Filter"])) unset($trailer["Filter"]); + if (isset($trailer["Filter"])) { + unset($trailer["Filter"]); + } $trailer->set_stream($xref["stream"], false); // If creating an incremental modification, point to the previous xref table - if ($rebuild === false) + if ($rebuild === false) { $trailer['Prev'] = $this->_xref_position; - else - // If rebuilding the document, remove the references to previous xref tables, because it will be only one - if (isset($trailer['Prev'])) + } else // If rebuilding the document, remove the references to previous xref tables, because it will be only one + { + if (isset($trailer['Prev'])) { unset($trailer['Prev']); + } + } // And generate the part of the document related to the xref $_doc_from_xref = new Buffer($trailer->to_pdf_entry()); @@ -863,8 +975,9 @@ public function to_pdf_file_b($rebuild = false): Buffer { // Update the trailer $this->_pdf_trailer_object['Size'] = $this->_max_oid + 1; - if ($rebuild === false) + if ($rebuild === false) { $this->_pdf_trailer_object['Prev'] = $this->_xref_position; + } // Not needed to generate new IDs, as in metadata the IDs may be set // $ID1 = md5("" . (new \DateTime())->getTimestamp() . "-" . $this->_xref_position . $xref_content); @@ -887,7 +1000,7 @@ public function to_pdf_file_b($rebuild = false): Buffer { $_signable_document = new Buffer($_doc_to_xref->get_raw() . $_signature->to_pdf_entry() . $_doc_from_xref->get_raw()); $certificate = $_signature->get_certificate(); $extracerts = (array_key_exists('extracerts', $certificate)) ? $certificate['extracerts'] : null; - $cms = new CMS; + $cms = new CMS(); $cms->signature_data['hashAlgorithm'] = 'sha256'; $cms->signature_data['privkey'] = $certificate['pkey']; $cms->signature_data['extracerts'] = $extracerts; @@ -906,24 +1019,31 @@ public function to_pdf_file_b($rebuild = false): Buffer { // Reset the state to make signature objects not to mess with the user's objects $this->pop_state(); + return new Buffer($_doc_to_xref->get_raw() . $_doc_from_xref->get_raw()); } /** * This functions outputs the document to a string, ready to be written + * * @return buffer a buffer that contains a pdf document */ - public function to_pdf_file_s($rebuild = false) { + public function to_pdf_file_s($rebuild = false) + { $pdf_content = $this->to_pdf_file_b($rebuild); + return $pdf_content->get_raw(); } /** * This function writes the document to a file + * * @param filename the name of the file to be written (it will be overwritten, if exists) + * * @return written true if the file has been correcly written to the file; false otherwise */ - public function to_pdf_file($filename, $rebuild = false) { + public function to_pdf_file($filename, $rebuild = false) + { $pdf_content = $this->to_pdf_file_b($rebuild); $file = fopen($filename, "wb"); @@ -932,34 +1052,51 @@ public function to_pdf_file($filename, $rebuild = false) { } if (fwrite($file, $pdf_content->get_raw()) !== $pdf_content->size()) { fclose($file); + return p_error("failed to write to file"); } fclose($file); + return true; } /** * Gets the page object which is rendered in position i + * * @param i the number of page (according to the rendering order) + * * @return page the page object */ - public function get_page($i) { - if ($i < 0) return false; - if ($i >= count($this->_pages_info)) return false; + public function get_page($i) + { + if ($i < 0) { + return false; + } + if ($i >= count($this->_pages_info)) { + return false; + } + return $this->get_object($this->_pages_info[$i]['id']); } /** * Gets the size of the page in the form of a rectangle [ x0 y0 x1 y1 ] + * * @param i the number of page (according to the rendering order), or the page object + * * @return box the bounding box of the page */ - public function get_page_size($i) { + public function get_page_size($i) + { $pageinfo = false; if (is_int($i)) { - if ($i < 0) return false; - if ($i > count($this->_pages_info)) return false; + if ($i < 0) { + return false; + } + if ($i > count($this->_pages_info)) { + return false; + } $pageinfo = $this->_pages_info[$i]['info']; } else { @@ -972,8 +1109,9 @@ public function get_page_size($i) { } // The page has not been found - if (($pageinfo === false) || (! isset($pageinfo['size']))) + if (($pageinfo === false) || (! isset($pageinfo['size']))) { return false; + } return $pageinfo['size']; } @@ -981,14 +1119,18 @@ public function get_page_size($i) { /** * This function builds the page IDs for object with id oid. If it is a page, it returns the oid; if it is not and it has * kids and every kid is a page (or a set of pages), it finds the pages. + * * @param oid the object id to inspect + * * @return pages the ordered list of page ids corresponding to object oid, or false if any of the kid objects * is not of type page or pages. */ - protected function _get_page_info(int $oid, array $info = []) { + protected function _get_page_info(int $oid, array $info = []) + { $object = $this->get_object($oid); - if ($object === false) + if ($object === false) { return p_error("could not get information about the page"); + } $page_ids = []; @@ -1002,8 +1144,9 @@ protected function _get_page_info(int $oid, array $info = []) { } foreach ($kids as $kid) { $ids = $this->_get_page_info($kid, $info); - if ($ids === false) + if ($ids === false) { return false; + } array_push($page_ids, ...$ids); } } else { @@ -1011,45 +1154,57 @@ protected function _get_page_info(int $oid, array $info = []) { } break; case "Page": - if (isset($object['MediaBox'])) + if (isset($object['MediaBox'])) { $info['size'] = $object['MediaBox']->val(); - return [[ - 'id' => $oid, - 'info' => $info, - ]]; + } + + return [ + [ + 'id' => $oid, + 'info' => $info, + ], + ]; default: return false; } + return $page_ids; } /** * Obtains an ordered list of objects that contain the ids of the page objects of the document. * The order is made according to the catalog and the document structure. + * * @return list an ordered list of the id of the page objects, or false if could not be found */ - protected function _acquire_pages_info() { + protected function _acquire_pages_info() + { $root = $this->_pdf_trailer_object["Root"]; - if (($root === false) || (($root = $root->get_object_referenced()) === false)) + if (($root === false) || (($root = $root->get_object_referenced()) === false)) { return p_error("could not find the root object from the trailer"); + } $root = $this->get_object($root); if ($root !== false) { $pages = $root["Pages"]; - if (($pages === false) || (($pages = $pages->get_object_referenced()) === false)) + if (($pages === false) || (($pages = $pages->get_object_referenced()) === false)) { return p_error("could not find the pages for the document"); + } $this->_pages_info = $this->_get_page_info($pages); - } else + } else { p_warning("root object does not exist, so cannot get information about pages"); + } } /** * This function compares this document with other document, object by object. The idea is to compare the objects with the same oid in the * different documents, checking field by field; it does not take into account the streams. + * * @return PDFObject[] */ - public function compare($other): array { + public function compare($other): array + { $other_objects = []; foreach ($other->get_object_iterator(false) as $oid => $object) { $other_objects[$oid] = $object; @@ -1067,28 +1222,33 @@ public function compare($other): array { } else { $differences[$oid] = new PDFObject($oid, $object->get_value()); } - } + return $differences; } /** * Obtains the tree of objects in the PDF Document. The result is an array of DependencyTreeObject objects (indexed by the oid), where * each element has a set of children that can be retrieved using the iterator (foreach $o->children() as $oid => $object ...) + * * @return DependencyTreeObject[] */ - public function get_object_tree(): array { - + public function get_object_tree(): array + { // Prepare the return value $objects = []; foreach ($this->_xref_table as $oid => $offset) { - if ($offset === null) continue; + if ($offset === null) { + continue; + } $o = $this->get_object($oid); - if ($o === false) continue; + if ($o === false) { + continue; + } - // foreach ($this->get_object_iterator() as $oid => $o) { + // foreach ($this->get_object_iterator() as $oid => $o) { // Create the object in the dependency tree and add it to the list of objects if (! array_key_exists($oid, $objects)) { @@ -1104,9 +1264,12 @@ public function get_object_tree(): array { $references = references_in_object($val, $oid); } else { $references = $val->get_object_referenced(); - if ($references === false) + if ($references === false) { continue; - if (! is_array($references)) $references = [$references]; + } + if (! is_array($references)) { + $references = [$references]; + } } // p_debug("$oid references " . implode(", ", $references)); @@ -1131,9 +1294,10 @@ public function get_object_tree(): array { // Remove those objects that are child of other objects from the top of the tree foreach ($objects as $oid => $t_object) { - if (($t_object->is_child > 0) || (in_array($t_object->info, ["/XRef", "/ObjStm"] ))) { - if (! in_array($oid, $xref_children)) + if (($t_object->is_child > 0) || (in_array($t_object->info, ["/XRef", "/ObjStm"]))) { + if (! in_array($oid, $xref_children)) { unset($objects[$oid]); + } } } @@ -1142,22 +1306,31 @@ public function get_object_tree(): array { /** * Retrieve the signatures in the document + * * @return array of signatures in the original document */ - public function get_signatures(): array { - + public function get_signatures(): array + { // Prepare the return value $signatures = []; foreach ($this->_xref_table as $oid => $offset) { - if ($offset === null) continue; + if ($offset === null) { + continue; + } $o = $this->get_object($oid); - if ($o === false) continue; + if ($o === false) { + continue; + } $o_value = $o->get_value()->val(); - if (! is_array($o_value) || ! isset($o_value['Type'])) continue; - if ($o_value['Type']->val() != 'Sig') continue; + if (! is_array($o_value) || ! isset($o_value['Type'])) { + continue; + } + if ($o_value['Type']->val() != 'Sig') { + continue; + } $signature = [ 'content' => $o_value['Contents']->val(), @@ -1168,13 +1341,14 @@ public function get_signatures(): array { openssl_pkcs7_read( "-----BEGIN CERTIFICATE-----\n" - . chunk_split(base64_encode(hex2bin((string) $signature['content'])), 64, "\n") - . "-----END CERTIFICATE-----\n", + . chunk_split(base64_encode(hex2bin((string) $signature['content'])), 64, "\n") + . "-----END CERTIFICATE-----\n", $cert ); $signature += openssl_x509_parse($cert[0] ?? '') ?: []; - } catch (Throwable) {} + } catch (Throwable) { + } $signatures[] = $signature; } @@ -1184,15 +1358,18 @@ public function get_signatures(): array { /** * Retrieve the number of signatures in the document + * * @return int signatures number in the original document */ - public function get_signature_count(): int { + public function get_signature_count(): int + { return count($this->get_signatures()); } /** * Generates a new document that is the result of signing the current * document + * * @param certfile a file that contains a user certificate in pkcs12 format, or an array [ 'cert' => , 'pkey' => ] * that would be the output of openssl_pkcs12_read * @param password the password to read the private key @@ -1206,8 +1383,8 @@ public function get_signature_count(): int { * - if array [ width, height ], it will be the width and the height for the image to be included as a signature appearance (if * one of these values is null, it will fallback to the actual width or height of the image) */ - public function sign_document($certfile, $password = null, $page_to_appear = 0, $imagefilename = null, $px = 0, $py = 0, $size = null) { - + public function sign_document($certfile, $password = null, $page_to_appear = 0, $imagefilename = null, $px = 0, $py = 0, $size = null) + { if ($imagefilename !== null) { $imagesize = @getimagesize($imagefilename); if ($imagesize === false) { @@ -1242,14 +1419,18 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, } $width = $size[0]; $height = $size[1]; - } else if ($size === null) { - $width = $i_w; - $height = $i_h; - } else if (is_float($size) || is_int($size)) { - $width = $i_w * $size; - $height = $i_h * $size; } else { - return p_error("invalid size format"); + if ($size === null) { + $width = $i_w; + $height = $i_h; + } else { + if (is_float($size) || is_int($size)) { + $width = $i_w * $size; + $height = $i_h * $size; + } else { + return p_error("invalid size format"); + } + } } $i_w = $width ?? $imagesize[0]; @@ -1267,6 +1448,7 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, if ($docsigned === false) { return p_error("failed to sign the document"); } + return PDFDoc::from_string($docsigned); } } diff --git a/src/PDFDocWithContents.php b/src/PDFDocWithContents.php index 69fc2fd..c05295b 100644 --- a/src/PDFDocWithContents.php +++ b/src/PDFDocWithContents.php @@ -20,6 +20,7 @@ */ namespace ddn\sapp; + use function ddn\sapp\helpers\get_random_string; use function ddn\sapp\helpers\p_error; use function ddn\sapp\helpers\p_warning; @@ -27,35 +28,38 @@ use ddn\sapp\pdfvalue\PDFValueObject; use ddn\sapp\pdfvalue\PDFValueReference; -class PDFDocWithContents extends PDFDoc { +class PDFDocWithContents extends PDFDoc +{ const T_STANDARD_FONTS = [ - "Times-Roman", - "Times-Bold", - "Time-Italic", - "Time-BoldItalic", - "Courier", - "Courier-Bold", - "Courier-Oblique", + "Times-Roman", + "Times-Bold", + "Time-Italic", + "Time-BoldItalic", + "Courier", + "Courier-Bold", + "Courier-Oblique", "Courier-BoldOblique", - "Helvetica", - "Helvetica-Bold", - "Helvetica-Oblique", - "Helvetica-BoldOblique", - "Symbol", + "Helvetica", + "Helvetica-Bold", + "Helvetica-Oblique", + "Helvetica-BoldOblique", + "Symbol", "ZapfDingbats", ]; /** * This is a function that allows to add a very basic text to a page, using a standard font. * The function is mainly oriented to add banners and so on, and not to use for writting. + * * @param page the number of page in which the text should appear * @param text the text * @param x the x offset from left for the text (we do not take care of margins) * @param y the y offset from top for the text (we do not take care of margins) - * @param params an array of values [ "font" => , "size" => , + * @param params an array of values [ "font" => , "size" => , * "color" => <#hexcolor>, "angle" => ] */ - public function add_text($page_to_appear, $text, $x, $y, $params = []) { + public function add_text($page_to_appear, $text, $x, $y, $params = []) + { // TODO: maybe we can create a function that "adds content to a page", and that // function will search for the content field and merge the resources, if // needed @@ -71,13 +75,15 @@ public function add_text($page_to_appear, $text, $x, $y, $params = []) { $params = array_merge($default, $params); $page_obj = $this->get_page($page_to_appear); - if ($page_obj === false) + if ($page_obj === false) { return p_error("invalid page"); + } $resources_obj = $this->get_indirect_object($page_obj['Resources']); - if (array_search($params["font"], self::T_STANDARD_FONTS) === false) + if (array_search($params["font"], self::T_STANDARD_FONTS) === false) { return p_error("only standard fonts are allowed Times-Roman, Helvetica, Courier, Symbol, ZapfDingbats"); + } $font_id = "F" . get_random_string(4); $resources_obj['Font'][$font_id] = [ @@ -90,8 +96,9 @@ public function add_text($page_to_appear, $text, $x, $y, $params = []) { $contents_obj = $this->get_indirect_object($page_obj['Contents']); $data = $contents_obj->get_stream(false); - if ($data === false) + if ($data === false) { return p_error("could not interpret the contents of the page"); + } // Get the page height, to change the coordinates system (up to down) $pagesize = $this->get_page_size($page_to_appear); @@ -104,8 +111,9 @@ public function add_text($page_to_appear, $text, $x, $y, $params = []) { $cx = $x; $cy = ($pagesize_h - $y); - if ($angle !== 0) + if ($angle !== 0) { $rotate_command = sprintf("%.5F %.5F %.5F %.5F %.2F %.2F cm 1 0 0 1 %.2F %.2F cm", $c, $s, -$s, $c, $cx, $cy, -$cx, -$cy); + } $text_command = "BT "; $text_command .= "/$font_id " . $params['size'] . " Tf "; @@ -121,18 +129,21 @@ public function add_text($page_to_appear, $text, $x, $y, $params = []) { case 4: $color = "#" . $color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3]; case 7: - [$r, $g, $b] = sscanf($color, "#%02x%02x%02x"); + [$r, $g, $b] = sscanf($color, "#%02x%02x%02x"); break; default: p_error("please use html-like colors (e.g. #ffbbaa)"); } - if ($r !== null) - $text_command = " q $r $g $b rg $text_command Q"; // Color RGB - } else + if ($r !== null) { + $text_command = " q $r $g $b rg $text_command Q"; + } // Color RGB + } else { p_error("please use html-like colors (e.g. #ffbbaa)"); + } - if ($angle !== 0) - $text_command = " q $rotate_command $text_command Q"; + if ($angle !== 0) { + $text_command = " q $rotate_command $text_command Q"; + } $data .= $text_command; @@ -140,14 +151,15 @@ public function add_text($page_to_appear, $text, $x, $y, $params = []) { // Update the contents $this->add_object($resources_obj); - $this->add_object($contents_obj); + $this->add_object($contents_obj); } /** * Adds an image to the document, in the specific page * NOTE: the image inclusion is taken from http://www.fpdf.org/; this is an adaptation - * and simplification of function Image(); it does not take care about units nor + * and simplification of function Image(); it does not take care about units nor * page breaks + * * @param page_obj the page object (or the page number) in which the image will appear * @param filename the name of the file that contains the image (or the content of the file, with the character '@' prepended) * @param x the x position (in pixels) where the image will appear @@ -155,19 +167,21 @@ public function add_text($page_to_appear, $text, $x, $y, $params = []) { * @param w the width of the image * @param w the height of the image */ - public function add_image($page_obj, $filename, $x = 0, $y = 0, $w = 0, $h = 0) { - + public function add_image($page_obj, $filename, $x = 0, $y = 0, $w = 0, $h = 0) + { // TODO: maybe we can create a function that "adds content to a page", and that // function will search for the content field and merge the resources, if // needed p_warning("This function still needs work"); // Check that the page is valid - if (is_int($page_obj)) + if (is_int($page_obj)) { $page_obj = $this->get_page($page_obj); + } - if ($page_obj === false) + if ($page_obj === false) { return p_error("invalid page"); + } // Get the page height, to change the coordinates system (up to down) $pagesize = $this->get_page_size($page_obj); @@ -179,11 +193,13 @@ public function add_image($page_obj, $filename, $x = 0, $y = 0, $w = 0, $h = 0) // Get the resources for the page $resources_obj = $this->get_indirect_object($page_obj['Resources']); - if (! isset($resources_obj['ProcSet'])) + if (! isset($resources_obj['ProcSet'])) { $resources_obj['ProcSet'] = new PDFValueList(['/PDF']); + } $resources_obj['ProcSet']->push(['/ImageB', '/ImageC', '/ImageI']); - if (! isset($resources_obj['XObject'])) + if (! isset($resources_obj['XObject'])) { $resources_obj['XObject'] = new PDFValueObject(); + } $resources_obj['XObject'][$info['i']] = new PDFValueReference($images_objects[0]->get_oid()); // TODO: get the contents object in which to add the image. @@ -211,9 +227,10 @@ public function add_image($page_obj, $filename, $x = 0, $y = 0, $w = 0, $h = 0) $this->add_object($page_obj); } - foreach ([$resources_obj, $contents_obj] as $o ) + foreach ([$resources_obj, $contents_obj] as $o) { $this->add_object($o); + } return true; } -} \ No newline at end of file +} diff --git a/src/PDFObject.php b/src/PDFObject.php index c240cfd..5895616 100644 --- a/src/PDFObject.php +++ b/src/PDFObject.php @@ -21,25 +21,26 @@ namespace ddn\sapp; -use \ArrayAccess; +use ArrayAccess; use ddn\sapp\helpers\Buffer; use ddn\sapp\helpers\LoadHelpers; +use function ddn\sapp\helpers\p_error; +use function ddn\sapp\helpers\p_warning; use ddn\sapp\pdfvalue\PDFValueObject; use ddn\sapp\pdfvalue\PDFValueSimple; use ReturnTypeWillChange; -// Loading the functions use Stringable; -if (! defined("ddn\\sapp\\helpers\\LoadHelpers")) - new LoadHelpers; -use function ddn\sapp\helpers\p_debug; -use function ddn\sapp\helpers\p_debug_var; -use function ddn\sapp\helpers\p_error; -use function ddn\sapp\helpers\p_warning; +// Loading the functions + +if (! defined("ddn\\sapp\\helpers\\LoadHelpers")) { + new LoadHelpers(); +} // The character used to end lines -if (! defined('__EOL')) +if (! defined('__EOL')) { define('__EOL', "\n"); +} /** * Class to gather the information of a PDF object: the OID, the definition and the stream. The purpose is to @@ -60,14 +61,16 @@ class PDFObject implements ArrayAccess, Stringable public function __construct( protected $_oid, $value = null, - $generation = 0 + $generation = 0, ) { - if ($generation !== 0) + if ($generation !== 0) { p_warning("Objects of non-zero generation are not fully checked... please double check your document and (if possible) please send examples via issues to https://github.com/dealfonso/sapp/issues/"); + } // If the value is null, we suppose that we are creating an empty object - if ($value === null) + if ($value === null) { $value = new PDFValueObject(); + } // Ease the creation of the object if (is_array($value)) { @@ -81,19 +84,23 @@ public function __construct( $this->_generation = $generation; } - public function get_keys() { + public function get_keys() + { return $this->_value->get_keys(); } - public function set_oid($oid): void { + public function set_oid($oid): void + { $this->_oid = $oid; } - public function get_generation() { + public function get_generation() + { return $this->_generation; } - public function __toString(): string { + public function __toString(): string + { return "$this->_oid 0 obj\n" . "$this->_value\n" . ( @@ -113,59 +120,66 @@ public function __toString(): string { * ... * endstream * endobj + * * @return pdfentry a string that contains the PDF entry */ - public function to_pdf_entry(): string { + public function to_pdf_entry(): string + { return "$this->_oid 0 obj" . __EOL . - "$this->_value" . __EOL . - ( - $this->_stream === null ? "" : - "stream\r\n" . - $this->_stream . - __EOL . "endstream" . __EOL - ) . - "endobj" . __EOL; + "$this->_value" . __EOL . + ( + $this->_stream === null ? "" : + "stream\r\n" . + $this->_stream . + __EOL . "endstream" . __EOL + ) . + "endobj" . __EOL; } /** * Gets the object ID + * * @return oid the object id */ - public function get_oid() { + public function get_oid() + { return $this->_oid; } /** * Gets the definition of the object (a PDFValue object) + * * @return value the definition of the object */ - public function get_value() { + public function get_value() + { return $this->_value; } - protected static function FlateDecode($_stream, array $params) { + protected static function FlateDecode($_stream, array $params) + { switch ($params["Predictor"]->get_int()) { case 1: - return $_stream; + return $_stream; case 10: case 11: case 12: case 13: case 14: case 15: - break; + break; default: - return p_error("other predictor than PNG is not supported in this version"); + return p_error("other predictor than PNG is not supported in this version"); } - switch($params["Colors"]->get_int()) { + switch ($params["Colors"]->get_int()) { case 1: break; default: return p_error("other color count than 1 is not supported in this version"); } - switch($params["BitsPerComponent"]->get_int()) { + switch ($params["BitsPerComponent"]->get_int()) { case 8: break; default: @@ -198,8 +212,9 @@ protected static function FlateDecode($_stream, array $params) { case 0: break; case 1: - for ($i = 1; $i < $columns; $i++) + for ($i = 1; $i < $columns; $i++) { $data[$i] = ($data[$i] + $data[$i - 1]) % 256; + } break; case 2: for ($i = 0; $i < $columns; $i++) { @@ -221,11 +236,14 @@ protected static function FlateDecode($_stream, array $params) { /** * Gets the stream of the object + * * @return stream a string that contains the stream of the object */ - public function get_stream($raw = true) { - if ($raw === true) + public function get_stream($raw = true) + { + if ($raw === true) { return $this->_stream; + } if (isset($this->_value['Filter'])) { switch ($this->_value['Filter']) { case '/FlateDecode': @@ -236,6 +254,7 @@ public function get_stream($raw = true) { "BitsPerComponent" => $DecodeParams['BitsPerComponent'] ?? new PDFValueSimple(8), "Colors" => $DecodeParams['Colors'] ?? new PDFValueSimple(1), ]; + return self::FlateDecode(gzuncompress($this->_stream), $params); break; @@ -243,16 +262,20 @@ public function get_stream($raw = true) { return p_error('unknown compression method ' . $this->_value['Filter']); } } + return $this->_stream; } /** * Sets the stream for the object (overwrites a previous existing stream) + * * @param stream the stream for the object */ - public function set_stream($stream, $raw = true): void { + public function set_stream($stream, $raw = true): void + { if ($raw === true) { $this->_stream = $stream; + return; } if (isset($this->_value['Filter'])) { @@ -275,43 +298,55 @@ public function set_stream($stream, $raw = true): void { */ /** * Sets the value of the field offset, using notation $obj['field'] = $value + * * @param field the field to set the value * @param value the value to set + * * @return void */ - public function offsetSet($field, $value): void { + public function offsetSet($field, $value): void + { $this->_value[$field] = $value; } /** * Checks whether the field exists in the object or not (or if the index exists * in the list) + * * @param field the field to check wether exists or not + * * @return exists true if the field exists; false otherwise */ - public function offsetExists ( $field ): bool { + public function offsetExists($field): bool + { return $this->_value->offsetExists($field); } /** * Gets the value of the field (or the value at position) + * * @param field the field to get the value + * * @return value the value of the field */ #[ReturnTypeWillChange] - public function offsetGet ( $field ) { + public function offsetGet($field) + { return $this->_value[$field]; } /** * Unsets the value of the field (or the value at position) + * * @param field the field to unset the value */ - public function offsetUnset($field ): void { + public function offsetUnset($field): void + { $this->_value->offsetUnset($field); } - public function push($v) { + public function push($v) + { return $this->_value->push($v); } } diff --git a/src/PDFObjectParser.php b/src/PDFObjectParser.php index cf6dfa2..2abfd08 100644 --- a/src/PDFObjectParser.php +++ b/src/PDFObjectParser.php @@ -21,432 +21,479 @@ namespace ddn\sapp; - use \Exception; - use ddn\sapp\helpers\StreamReader; - use ddn\sapp\pdfvalue\PDFValue; - use ddn\sapp\pdfvalue\PDFValueHexString; - use ddn\sapp\pdfvalue\PDFValueList; - use ddn\sapp\pdfvalue\PDFValueObject; - use ddn\sapp\pdfvalue\PDFValueSimple; - use ddn\sapp\pdfvalue\PDFValueString; - use ddn\sapp\pdfvalue\PDFValueType; - use Stringable; +use ddn\sapp\helpers\StreamReader; +use ddn\sapp\pdfvalue\PDFValueHexString; +use ddn\sapp\pdfvalue\PDFValueList; +use ddn\sapp\pdfvalue\PDFValueObject; +use ddn\sapp\pdfvalue\PDFValueSimple; +use ddn\sapp\pdfvalue\PDFValueString; +use ddn\sapp\pdfvalue\PDFValueType; +use Exception; +use Stringable; - /** - * Class devoted to parse a single PDF object - * - * A PDF Document is made of objects with the following structure (e.g for object 1 version 0) - * - * 1 0 obj - * ...content... - * [stream - * ...stream... - * endstream] - * endobject - * - * This PDF class transforms the definition string within ...content... into a PDFValue class. - * - * - At the end, it is a simple syntax checker - */ - class PDFObjectParser implements Stringable { - // Possible tokens in a PDF document - const T_NOTOKEN = 0; +/** + * Class devoted to parse a single PDF object + * A PDF Document is made of objects with the following structure (e.g for object 1 version 0) + * 1 0 obj + * ...content... + * [stream + * ...stream... + * endstream] + * endobject + * This PDF class transforms the definition string within ...content... into a PDFValue class. + * - At the end, it is a simple syntax checker + */ +class PDFObjectParser implements Stringable +{ + // Possible tokens in a PDF document + const T_NOTOKEN = 0; - const T_LIST_START = 1; + const T_LIST_START = 1; - const T_LIST_END = 2; + const T_LIST_END = 2; - const T_FIELD = 3; + const T_FIELD = 3; - const T_STRING = 4; + const T_STRING = 4; - const T_HEX_STRING = 12; + const T_HEX_STRING = 12; - const T_SIMPLE = 5; + const T_SIMPLE = 5; - const T_DICT_START = 6; + const T_DICT_START = 6; - const T_DICT_END = 7; + const T_DICT_END = 7; - const T_OBJECT_BEGIN = 8; + const T_OBJECT_BEGIN = 8; - const T_OBJECT_END = 9; + const T_OBJECT_END = 9; - const T_STREAM_BEGIN = 10; + const T_STREAM_BEGIN = 10; - const T_STREAM_END = 11; + const T_STREAM_END = 11; - const T_COMMENT = 13; + const T_COMMENT = 13; - const T_NAMES = [ - self::T_NOTOKEN => 'no token', - self::T_LIST_START => 'list start', - self::T_LIST_END => 'list end', - self::T_FIELD => 'field', - self::T_STRING => 'string', - self::T_HEX_STRING => 'hex string', - self::T_SIMPLE => 'simple', - self::T_DICT_START => 'dict start', - self::T_DICT_END => 'dict end', - self::T_OBJECT_BEGIN => 'object begin', - self::T_OBJECT_END => 'object end', - self::T_STREAM_BEGIN => 'stream begin', - self::T_STREAM_END => 'stream end', - self::T_COMMENT => 'comment', - ]; + const T_NAMES = [ + self::T_NOTOKEN => 'no token', + self::T_LIST_START => 'list start', + self::T_LIST_END => 'list end', + self::T_FIELD => 'field', + self::T_STRING => 'string', + self::T_HEX_STRING => 'hex string', + self::T_SIMPLE => 'simple', + self::T_DICT_START => 'dict start', + self::T_DICT_END => 'dict end', + self::T_OBJECT_BEGIN => 'object begin', + self::T_OBJECT_END => 'object end', + self::T_STREAM_BEGIN => 'stream begin', + self::T_STREAM_END => 'stream end', + self::T_COMMENT => 'comment', + ]; - const T_SIMPLE_OBJECTS = [ - self::T_SIMPLE, - self::T_OBJECT_BEGIN, - self::T_OBJECT_END, - self::T_STREAM_BEGIN, - self::T_STREAM_END, - self::T_COMMENT, - ]; + const T_SIMPLE_OBJECTS = [ + self::T_SIMPLE, + self::T_OBJECT_BEGIN, + self::T_OBJECT_END, + self::T_STREAM_BEGIN, + self::T_STREAM_END, + self::T_COMMENT, + ]; - protected $_buffer = null; + protected $_buffer = null; - protected $_c = false; + protected $_c = false; - protected $_n = false; + protected $_n = false; - protected $_t = false; + protected $_t = false; - protected $_tt = self::T_NOTOKEN; + protected $_tt = self::T_NOTOKEN; - /** - * Retrieves the current token type (one of T_* constants) - * @return token the current token - */ - public function current_token() { - return $this->_tt; - } + /** + * Retrieves the current token type (one of T_* constants) + * + * @return token the current token + */ + public function current_token() + { + return $this->_tt; + } - /** - * Obtains the next char and prepares the variable $this->_c and $this->_n to contain the current char and the next char - * - if EOF, _c will be false - * - if the last char before EOF, _n will be false - * @return char the next char - */ - protected function nextchar() { - $this->_c = $this->_n; - $this->_n = $this->_buffer->nextchar(); - return $this->_c; - } + /** + * Obtains the next char and prepares the variable $this->_c and $this->_n to contain the current char and the next char + * - if EOF, _c will be false + * - if the last char before EOF, _n will be false + * + * @return char the next char + */ + protected function nextchar() + { + $this->_c = $this->_n; + $this->_n = $this->_buffer->nextchar(); - /** - * Prepares the parser to analythe the text (i.e. prepares the parsing variables) - */ - protected function start(&$buffer) { - $this->_buffer = $buffer; - $this->_c = false; - $this->_n = false; - $this->_t = false; - $this->_tt = self::T_NOTOKEN; - - if ($this->_buffer->size() === 0) return false; - $this->_n = $this->_buffer->currentchar(); - $this->nextchar(); - } + return $this->_c; + } - /** - * Parses the document - */ - public function parse(&$stream): PDFValueObject|PDFValueList|PDFValueString|PDFValueType|PDFValueSimple|false|null { // $str, $offset = 0) { - $this->start($stream); //$str, $offset); - $this->nexttoken(); - $result = $this->_parse_value(); - return $result; + /** + * Prepares the parser to analythe the text (i.e. prepares the parsing variables) + */ + protected function start(&$buffer) + { + $this->_buffer = $buffer; + $this->_c = false; + $this->_n = false; + $this->_t = false; + $this->_tt = self::T_NOTOKEN; + + if ($this->_buffer->size() === 0) { + return false; } + $this->_n = $this->_buffer->currentchar(); + $this->nextchar(); + } - public function parsestr($str, $offset = 0): PDFValueObject|PDFValueList|PDFValueString|PDFValueType|PDFValueSimple|false|null { - $stream = new StreamReader($str); - $stream->goto($offset); - return $this->parse($stream); - } + /** + * Parses the document + */ + public function parse(&$stream): PDFValueObject|PDFValueList|PDFValueString|PDFValueType|PDFValueSimple|false|null + { // $str, $offset = 0) { + $this->start($stream); //$str, $offset); + $this->nexttoken(); + $result = $this->_parse_value(); - /** - * Simple output of the object - * @return output the output of the object - */ - public function __toString(): string { - return "pos: " . $this->_buffer->getpos() . ", c: $this->_c, n: $this->_n, t: $this->_t, tt: " . + return $result; + } + + public function parsestr($str, $offset = 0): PDFValueObject|PDFValueList|PDFValueString|PDFValueType|PDFValueSimple|false|null + { + $stream = new StreamReader($str); + $stream->goto($offset); + + return $this->parse($stream); + } + + /** + * Simple output of the object + * + * @return output the output of the object + */ + public function __toString(): string + { + return "pos: " . $this->_buffer->getpos() . ", c: $this->_c, n: $this->_n, t: $this->_t, tt: " . self::T_NAMES[$this->_tt] . ', b: ' . $this->_buffer->substratpos(50) . "\n"; - } + } - /** - * Obtains the next token and returns it - */ - public function nexttoken() { - [$this->_t, $this->_tt] = $this->token(); - return $this->_t; - } + /** + * Obtains the next token and returns it + */ + public function nexttoken() + { + [$this->_t, $this->_tt] = $this->token(); - /** - * Function that returns wether the current char is a separator or not - */ - protected function _c_is_separator(): bool { - $DSEPS = ["<<", ">>"]; + return $this->_t; + } - return (($this->_c === false) || (str_contains("%<>()[]{}/ \n\r\t", (string) $this->_c)) || ((array_search($this->_c . $this->_n, $DSEPS)) !== false)); - } + /** + * Function that returns wether the current char is a separator or not + */ + protected function _c_is_separator(): bool + { + $DSEPS = ["<<", ">>"]; + + return (($this->_c === false) || (str_contains("%<>()[]{}/ \n\r\t", (string) $this->_c)) || ((array_search($this->_c . $this->_n, $DSEPS)) !== false)); + } - /** - * This function assumes that the next content is an hex string, so it should be called after "<" is detected; it skips the trailing ">" - * @return string the hex string - */ - protected function _parse_hex_string() { - $token = ""; + /** + * This function assumes that the next content is an hex string, so it should be called after "<" is detected; it skips the trailing ">" + * + * @return string the hex string + */ + protected function _parse_hex_string() + { + $token = ""; - if ($this->_c !== "<") throw new Exception("Invalid hex string"); + if ($this->_c !== "<") { + throw new Exception("Invalid hex string"); + } - $this->nextchar(); // This char is "<" - while (($this->_c !== '>') && (str_contains("0123456789abcdefABCDEF \t\r\n\f", $this->_c))) { - $token .= $this->_c; - if ($this->nextchar() === false) { - break; - } + $this->nextchar(); // This char is "<" + while (($this->_c !== '>') && (str_contains("0123456789abcdefABCDEF \t\r\n\f", $this->_c))) { + $token .= $this->_c; + if ($this->nextchar() === false) { + break; } - if (($this->_c !== false) && (! str_contains(">0123456789abcdefABCDEF \t\r\n\f", $this->_c))) - throw new Exception("invalid hex string"); - - // The only way to get to here is that char is ">" - if ($this->_c !== ">") throw new Exception("Invalid hex string"); + } + if (($this->_c !== false) && (! str_contains(">0123456789abcdefABCDEF \t\r\n\f", $this->_c))) { + throw new Exception("invalid hex string"); + } - $this->nextchar(); - return $token; + // The only way to get to here is that char is ">" + if ($this->_c !== ">") { + throw new Exception("Invalid hex string"); } - protected function _parse_string() { - $token = ""; - if ($this->_c !== "(") throw new Exception("Invalid string"); + $this->nextchar(); - $n_parenthesis = 1; - while ($this->_c !== false) { - $this->nextchar(); - if (($this->_c === ')') && (! strlen($token) || ($token[strlen($token) - 1] !== '\\'))) { - $n_parenthesis--; - if ($n_parenthesis == 0) - break; - } else { - if (($this->_c === '(') && (! strlen($token) || ($token[strlen($token) - 1] !== '\\'))) { - $n_parenthesis++; - } - $token .= $this->_c; - } - } + return $token; + } - if ($this->_c !== ")") { - throw new Exception("Invalid string"); - } + protected function _parse_string() + { + $token = ""; + if ($this->_c !== "(") { + throw new Exception("Invalid string"); + } + + $n_parenthesis = 1; + while ($this->_c !== false) { $this->nextchar(); + if (($this->_c === ')') && (! strlen($token) || ($token[strlen($token) - 1] !== '\\'))) { + $n_parenthesis--; + if ($n_parenthesis == 0) { + break; + } + } else { + if (($this->_c === '(') && (! strlen($token) || ($token[strlen($token) - 1] !== '\\'))) { + $n_parenthesis++; + } + $token .= $this->_c; + } + } - return $token; + if ($this->_c !== ")") { + throw new Exception("Invalid string"); } + $this->nextchar(); - protected function token() { - if ($this->_c === false) return [false, false]; + return $token; + } - $token = false; + protected function token() + { + if ($this->_c === false) { + return [false, false]; + } - while ($this->_c !== false) { - // Skip the spaces - while ((str_contains("\t\n\r ", (string) $this->_c)) && ($this->nextchar() !== false)); + $token = false; - $token_type = self::T_NOTOKEN; + while ($this->_c !== false) { + // Skip the spaces + while ((str_contains("\t\n\r ", (string) $this->_c)) && ($this->nextchar() !== false)) { + } - // TODO: also the special characters are not "strictly" considered, according to section 7.3.4.2: \n \r \t \b \f \( \) \\ are valid; the other not; but also \bbb should be considered; all of them are "sufficiently" treated, but other unknown caracters such as \u are also accepted - switch ($this->_c) { - case '%': + $token_type = self::T_NOTOKEN; + + // TODO: also the special characters are not "strictly" considered, according to section 7.3.4.2: \n \r \t \b \f \( \) \\ are valid; the other not; but also \bbb should be considered; all of them are "sufficiently" treated, but other unknown caracters such as \u are also accepted + switch ($this->_c) { + case '%': + $this->nextchar(); + $token = ""; + while (! str_contains("\n\r", (string) $this->_c)) { + $token .= $this->_c; $this->nextchar(); - $token = ""; - while (! str_contains("\n\r", (string) $this->_c)) { - $token .= $this->_c; - $this->nextchar(); - } - $token_type = self::T_COMMENT; - break; - case '<': - if ($this->_n === '<') { - $this->nextchar(); - $this->nextchar(); - $token = '<<'; - $token_type = self::T_DICT_START; - } else { - $token = $this->_parse_hex_string(); - $token_type = self::T_HEX_STRING; - } - break; - case '(': - $token = $this->_parse_string(); - $token_type = self::T_STRING; - break; - case '>': - if ($this->_n === '>') { - $this->nextchar(); - $this->nextchar(); - $token = '>>'; - $token_type = self::T_DICT_END; - } - break; - case '[': - $token = $this->_c; + } + $token_type = self::T_COMMENT; + break; + case '<': + if ($this->_n === '<') { $this->nextchar(); - $token_type = self::T_LIST_START; - break; - case ']': - $token = $this->_c; $this->nextchar(); - $token_type = self::T_LIST_END; - break; - case '/': - // Skip the field idenifyer + $token = '<<'; + $token_type = self::T_DICT_START; + } else { + $token = $this->_parse_hex_string(); + $token_type = self::T_HEX_STRING; + } + break; + case '(': + $token = $this->_parse_string(); + $token_type = self::T_STRING; + break; + case '>': + if ($this->_n === '>') { + $this->nextchar(); $this->nextchar(); + $token = '>>'; + $token_type = self::T_DICT_END; + } + break; + case '[': + $token = $this->_c; + $this->nextchar(); + $token_type = self::T_LIST_START; + break; + case ']': + $token = $this->_c; + $this->nextchar(); + $token_type = self::T_LIST_END; + break; + case '/': + // Skip the field idenifyer + $this->nextchar(); - // We are assuming any char (i.e. /MY+difficult_id is valid) - while (! $this->_c_is_separator()) { - $token .= $this->_c; - if ($this->nextchar() === false) break; - } - $token_type = self::T_FIELD; - break; - } - if ($token === false) { - $token = ""; - + // We are assuming any char (i.e. /MY+difficult_id is valid) while (! $this->_c_is_separator()) { $token .= $this->_c; - if ($this->nextchar() === false) break; + if ($this->nextchar() === false) { + break; + } } + $token_type = self::T_FIELD; + break; + } + if ($token === false) { + $token = ""; - $token_type = match ($token) { - 'obj' => self::T_OBJECT_BEGIN, - 'endobj' => self::T_OBJECT_END, - 'stream' => self::T_STREAM_BEGIN, - 'endstream' => self::T_STREAM_END, - default => self::T_SIMPLE, - }; - + while (! $this->_c_is_separator()) { + $token .= $this->_c; + if ($this->nextchar() === false) { + break; + } } - return [$token, $token_type]; + + $token_type = match ($token) { + 'obj' => self::T_OBJECT_BEGIN, + 'endobj' => self::T_OBJECT_END, + 'stream' => self::T_STREAM_BEGIN, + 'endstream' => self::T_STREAM_END, + default => self::T_SIMPLE, + }; } + + return [$token, $token_type]; } + } - protected function _parse_obj(): PDFValueObject|false { - if ($this->_tt !== self::T_DICT_START) { - throw new Exception("Invalid object definition"); - } + protected function _parse_obj(): PDFValueObject|false + { + if ($this->_tt !== self::T_DICT_START) { + throw new Exception("Invalid object definition"); + } - $this->nexttoken(); - $object = []; - while ($this->_t !== false) { - switch ($this->_tt) { - case self::T_FIELD: - $field = $this->_t; - $this->nexttoken(); - $object[$field] = $this->_parse_value(); - break; - case self::T_DICT_END: - $this->nexttoken(); - return new PDFValueObject($object); - break; - default: - throw new Exception("Invalid token: $this"); - } + $this->nexttoken(); + $object = []; + while ($this->_t !== false) { + switch ($this->_tt) { + case self::T_FIELD: + $field = $this->_t; + $this->nexttoken(); + $object[$field] = $this->_parse_value(); + break; + case self::T_DICT_END: + $this->nexttoken(); + + return new PDFValueObject($object); + break; + default: + throw new Exception("Invalid token: $this"); } - return false; } - protected function _parse_list(): PDFValueList { - if ($this->_tt !== self::T_LIST_START) { - throw new Exception("Invalid list definition"); - } + return false; + } - $this->nexttoken(); - $list = []; - while ($this->_t !== false) { - switch ($this->_tt) { - case self::T_LIST_END: - $this->nexttoken(); - return new PDFValueList($list); + protected function _parse_list(): PDFValueList + { + if ($this->_tt !== self::T_LIST_START) { + throw new Exception("Invalid list definition"); + } - case self::T_OBJECT_BEGIN: - case self::T_OBJECT_END: - case self::T_STREAM_BEGIN: - case self::T_STREAM_END: - throw new Exception("Invalid list definition"); - break; - default: - $value = $this->_parse_value(); - if ($value !== false) { - array_push($list, $value); - } - break; - } + $this->nexttoken(); + $list = []; + while ($this->_t !== false) { + switch ($this->_tt) { + case self::T_LIST_END: + $this->nexttoken(); + + return new PDFValueList($list); + + case self::T_OBJECT_BEGIN: + case self::T_OBJECT_END: + case self::T_STREAM_BEGIN: + case self::T_STREAM_END: + throw new Exception("Invalid list definition"); + break; + default: + $value = $this->_parse_value(); + if ($value !== false) { + array_push($list, $value); + } + break; } - return new PDFValueList($list); } - protected function _parse_value(): PDFValueObject|false|PDFValueList|PDFValueString|PDFValueHexString|PDFValueType|null|PDFValueSimple { - while ($this->_t !== false) { - switch ($this->_tt) { - case self::T_DICT_START: - return $this->_parse_obj(); - break; - case self::T_LIST_START: - return $this->_parse_list(); - break; - case self::T_STRING: - $string = new PDFValueString($this->_t); - $this->nexttoken(); - return $string; - break; - case self::T_HEX_STRING: - $string = new PDFValueHexString($this->_t); - $this->nexttoken(); - return $string; - break; - case self::T_FIELD: - $field = new PDFValueType($this->_t); - $this->nexttoken(); - return $field; - case self::T_OBJECT_BEGIN: - case self::T_STREAM_END: - throw new Exception("invalid keyword"); - case self::T_OBJECT_END: - case self::T_STREAM_BEGIN: - return null; - case self::T_COMMENT: - $this->nexttoken(); - break; - case self::T_SIMPLE: - $simple_value = $this->_t; + return new PDFValueList($list); + } + + protected function _parse_value(): PDFValueObject|false|PDFValueList|PDFValueString|PDFValueHexString|PDFValueType|null|PDFValueSimple + { + while ($this->_t !== false) { + switch ($this->_tt) { + case self::T_DICT_START: + return $this->_parse_obj(); + break; + case self::T_LIST_START: + return $this->_parse_list(); + break; + case self::T_STRING: + $string = new PDFValueString($this->_t); + $this->nexttoken(); + + return $string; + break; + case self::T_HEX_STRING: + $string = new PDFValueHexString($this->_t); + $this->nexttoken(); + + return $string; + break; + case self::T_FIELD: + $field = new PDFValueType($this->_t); + $this->nexttoken(); + + return $field; + case self::T_OBJECT_BEGIN: + case self::T_STREAM_END: + throw new Exception("invalid keyword"); + case self::T_OBJECT_END: + case self::T_STREAM_BEGIN: + return null; + case self::T_COMMENT: + $this->nexttoken(); + break; + case self::T_SIMPLE: + $simple_value = $this->_t; + $this->nexttoken(); + + while (($this->_t !== false) && ($this->_tt == self::T_SIMPLE)) { + $simple_value .= " " . $this->_t; $this->nexttoken(); + } - while (($this->_t !== false) && ($this->_tt == self::T_SIMPLE)) { - $simple_value .= " " . $this->_t; - $this->nexttoken(); - } - /* - WON'T DO IT: the meaning of each element in a list is contextual; e.g. [ 10 10 0 R ] may mean [ 10 "reference to object 10" ], or [ 10 10 0 R ], where R is - a value for a flag... as SAPP is agnostic, we won't break it into objects; instead we offer the "get_referenced_object" for "simple" and "list" objects - and then the app that uses SAPP will decide wether it may use the values as object references or not. - */ - return new PDFValueSimple($simple_value); - break; + /* + WON'T DO IT: the meaning of each element in a list is contextual; e.g. [ 10 10 0 R ] may mean [ 10 "reference to object 10" ], or [ 10 10 0 R ], where R is + a value for a flag... as SAPP is agnostic, we won't break it into objects; instead we offer the "get_referenced_object" for "simple" and "list" objects + and then the app that uses SAPP will decide wether it may use the values as object references or not. + */ - default: - throw new Exception("Invalid token: $this"); - } + return new PDFValueSimple($simple_value); + break; + + default: + throw new Exception("Invalid token: $this"); } - return false; } - function tokenize(): void { - $this->start(); - while ($this->nexttoken() !== false) { - echo "$this->_t\n"; - } + return false; + } + + function tokenize(): void + { + $this->start(); + while ($this->nexttoken() !== false) { + echo "$this->_t\n"; } } +} diff --git a/src/PDFSignatureObject.php b/src/PDFSignatureObject.php index 122450c..7d484a5 100644 --- a/src/PDFSignatureObject.php +++ b/src/PDFSignatureObject.php @@ -26,7 +26,8 @@ use ddn\sapp\pdfvalue\PDFValueString; // This is an special object that has a set of fields -class PDFSignatureObject extends PDFObject { +class PDFSignatureObject extends PDFObject +{ // The maximum signature length, needed to create a placeholder to calculate the range of bytes // that will cover the signature. public static $__SIGNATURE_MAX_LENGTH = 27742; @@ -48,42 +49,52 @@ class PDFSignatureObject extends PDFObject { /** * Sets the certificate to use to sign + * * @param cert the pem-formatted certificate and private to use to sign as * [ 'cert' => ..., 'pkey' => ... ] */ - public function set_certificate($certificate): void { + public function set_certificate($certificate): void + { $this->_certificate = $certificate; } - public function set_signature_ltv($signature_ltv_data): void { + public function set_signature_ltv($signature_ltv_data): void + { $this->_signature_ltv_data = $signature_ltv_data; } - public function set_signature_tsa($signature_tsa): void { + public function set_signature_tsa($signature_tsa): void + { $this->_signature_tsa = $signature_tsa; } /** * Obtains the certificate set with function set_certificate + * * @return cert the certificate */ - public function get_certificate() { + public function get_certificate() + { return $this->_certificate; } - public function get_tsa() { + public function get_tsa() + { return $this->_signature_tsa; } - public function get_ltv() { + public function get_ltv() + { return $this->_signature_ltv_data; } /** * Constructs the object and sets the default values needed to sign + * * @param oid the oid for the object */ - public function __construct($oid) { + public function __construct($oid) + { $this->_prev_content_size = 0; $this->_post_content_size = null; parent::__construct($oid, [ @@ -98,12 +109,14 @@ public function __construct($oid) { /** * Function used to add some metadata fields to the signature: name, reason of signature, etc. + * * @param name the name of the signer * @param reason the reason for the signature * @param location the location of signature * @param contact the contact info */ - public function set_metadata($name = null, $reason = null, $location = null, $contact = null): void { + public function set_metadata($name = null, $reason = null, $location = null, $contact = null): void + { if ($name !== null) { $this->_value["Name"] = new PDFValueString($name); } @@ -123,7 +136,8 @@ public function set_metadata($name = null, $reason = null, $location = null, $co * and the content that will be included after. This is needed to get the range of bytes of the * signature. */ - public function set_sizes(int $prev_content_size, $post_content_size = null): void { + public function set_sizes(int $prev_content_size, $post_content_size = null): void + { $this->_prev_content_size = $prev_content_size; $this->_post_content_size = $post_content_size; } @@ -131,21 +145,26 @@ public function set_sizes(int $prev_content_size, $post_content_size = null): vo /** * This function gets the offset of the marker, relative to this object. To make correct, the offset of the object * shall have properly been set. It makes use of the parent "to_pdf_entry" function to avoid recursivity. + * * @return position the position of the <0000 marker */ - public function get_signature_marker_offset(): int { + public function get_signature_marker_offset(): int + { $tmp_output = parent::to_pdf_entry(); $marker = "/Contents"; $position = strpos($tmp_output, $marker); + return $position + strlen($marker); } /** * Overrides the parent function to calculate the proper range of bytes, according to the sizes provided and the * string representation of this object + * * @return str the string representation of this object */ - public function to_pdf_entry(): string { + public function to_pdf_entry(): string + { $signature_size = strlen(parent::to_pdf_entry()); $offset = $this->get_signature_marker_offset(); $starting_second_part = $this->_prev_content_size + $offset + self::$__SIGNATURE_MAX_LENGTH + 2; diff --git a/src/PDFUtilFnc.php b/src/PDFUtilFnc.php index a145dc8..0005b58 100644 --- a/src/PDFUtilFnc.php +++ b/src/PDFUtilFnc.php @@ -23,22 +23,24 @@ use ddn\sapp\helpers\Buffer; use ddn\sapp\helpers\LoadHelpers; -use function ddn\sapp\helpers\p_debug; -use function ddn\sapp\helpers\p_debug_var; use function ddn\sapp\helpers\p_error; use function ddn\sapp\helpers\p_warning; -use function ddn\sapp\helpers\show_bytes; use ddn\sapp\helpers\StreamReader; -if (! defined("ddn\\sapp\\helpers\\LoadHelpers")) - new LoadHelpers; + +if (! defined("ddn\\sapp\\helpers\\LoadHelpers")) { + new LoadHelpers(); +} // TODO: use the streamreader to deal with the document in the file, instead of a buffer -class PDFUtilFnc { - public static function get_trailer(&$_buffer, $trailer_pos) { +class PDFUtilFnc +{ + public static function get_trailer(&$_buffer, $trailer_pos) + { // Search for the trailer structure - if (preg_match('/trailer\s*(.*)\s*startxref/ms', (string) $_buffer, $matches, 0, $trailer_pos) !== 1) + if (preg_match('/trailer\s*(.*)\s*startxref/ms', (string) $_buffer, $matches, 0, $trailer_pos) !== 1) { return p_error("trailer not found"); + } $trailer_str = $matches[1]; @@ -54,8 +56,11 @@ public static function get_trailer(&$_buffer, $trailer_pos) { return $trailer_obj; } - public static function build_xref_1_5($offsets): array { - if (isset($offsets[0])) unset($offsets[0]); + public static function build_xref_1_5($offsets): array + { + if (isset($offsets[0])) { + unset($offsets[0]); + } $k = array_keys($offsets); sort($k); @@ -112,43 +117,52 @@ public static function build_xref_1_5($offsets): array { * This function obtains the xref from the cross reference streams (7.5.8 Cross-Reference Streams) * which started in PDF 1.5. */ - public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) { + public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) + { if ($depth !== null) { - if ($depth <= 0) + if ($depth <= 0) { return false; + } $depth = $depth - 1; } $xref_o = PDFUtilFnc::find_object_at_pos($_buffer, null, $xref_pos, []); - if ($xref_o === false) + if ($xref_o === false) { return p_error("cross reference object not found when parsing xref at position $xref_pos", [false, false, false]); + } - if (! (isset($xref_o["Type"])) || ($xref_o["Type"]->val() !== "XRef")) + if (! (isset($xref_o["Type"])) || ($xref_o["Type"]->val() !== "XRef")) { return p_error("invalid xref table", [false, false, false]); + } $stream = $xref_o->get_stream(false); - if ($stream === null) + if ($stream === null) { return p_error("cross reference stream not found when parsing xref at position $xref_pos", [false, false, false]); + } $W = $xref_o["W"]->val(true); - if (count($W) !== 3) + if (count($W) !== 3) { return p_error("invalid cross reference object", [false, false, false]); + } $W[0] = intval($W[0]); $W[1] = intval($W[1]); $W[2] = intval($W[2]); $Size = $xref_o["Size"]->get_int(); - if ($Size === false) + if ($Size === false) { return p_error("could not get the size of the xref table", [false, false, false]); + } $Index = [0, $Size]; - if (isset($xref_o["Index"])) + if (isset($xref_o["Index"])) { $Index = $xref_o["Index"]->val(true); + } - if (count($Index) % 2 !== 0) + if (count($Index) % 2 !== 0) { return p_error("invalid indexes of xref table", [false, false, false]); + } // Get the previous xref table, to build up on it $trailer_obj = null; @@ -160,8 +174,9 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) { if (isset($xref_o["Prev"])) { $Prev = $xref_o["Prev"]; $Prev = $Prev->get_int(); - if ($Prev === false) + if ($Prev === false) { return p_error("invalid reference to a previous xref table", [false, false, false]); + } // When dealing with 1.5 cross references, we do not allow to use other than cross references [$xref_table, $trailer_obj] = PDFUtilFnc::get_xref_1_5($_buffer, $Prev, $depth); @@ -173,9 +188,11 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) { $stream_v = new StreamReader($stream); // Get the format function to un pack the values - $get_fmt_function = function($f) { - if ($f === false) + $get_fmt_function = function ($f) { + if ($f === false) { return false; + } + return match ($f) { 0 => fn($v): int => 0, 1 => fn($v) => unpack('C', str_pad($v, 1, chr(0), STR_PAD_LEFT))[1], @@ -206,8 +223,9 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) { $f2 = $fmt_function[1]($stream_v->nextchars($W[1])); $f3 = $fmt_function[2]($stream_v->nextchars($W[2])); - if (($f1 === false) || ($f2 === false) || ($f3 === false)) + if (($f1 === false) || ($f2 === false) || ($f3 === false)) { return p_error("invalid stream for xref table", [false, false, false]); + } switch ($f1) { case 0: @@ -220,8 +238,9 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) { /* TODO: consider creating a generation table, but for the purpose of the xref there is no matter... if the document if well-formed. */ - if ($f3 !== 0) + if ($f3 !== 0) { p_warning("Objects of non-zero generation are not fully checked... please double check your document and (if possible) please send examples via issues to https://github.com/dealfonso/sapp/issues/"); + } break; case 2: @@ -244,10 +263,12 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) { return [$xref_table, $xref_o->get_value(), "1.5"]; } - public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) { + public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) + { if ($depth !== null) { - if ($depth <= 0) + if ($depth <= 0) { return false; + } $depth = $depth - 1; } @@ -260,8 +281,9 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) { $separator = "\r\n"; $xref_line = strtok($xref_substr, $separator); - if ($xref_line !== 'xref') + if ($xref_line !== 'xref') { return p_error("xref tag not found at position $xref_pos", [false, false, false]); + } // Now parse the lines and build the xref table $obj_id = false; @@ -269,7 +291,6 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) { $xref_table = []; while (($xref_line = strtok($separator)) !== false) { - // The first type of entry contains the id of the next object and the amount of continuous objects defined if (preg_match('/([0-9]+) ([0-9]+)$/', $xref_line, $matches) === 1) { if ($obj_count > 0) { @@ -283,10 +304,10 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) { // The other type of entry contains the offset of the object, the generation and the command (which is "f" for "free" or "n" for "new") if (preg_match('/^([0-9]+) ([0-9]+) (.)\s*/', $xref_line, $matches) === 1) { - // If no object expected, we'll assume that the xref is malformed - if ($obj_count === 0) + if ($obj_count === 0) { return p_error("unexpected entry for xref: $xref_line", [false, false, false]); + } $obj_offset = intval($matches[1]); $obj_generation = intval($matches[2]); @@ -315,8 +336,9 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) { // in the actual offset. // TODO: consider creating a "generation table" $xref_table[$obj_id] = $obj_offset; - if ($obj_generation != 0) + if ($obj_generation != 0) { p_warning("Objects of non-zero generation are not fully checked... please double check your document and (if possible) please send examples via issues to https://github.com/dealfonso/sapp/issues/"); + } break; default: // If it is not one of the expected, let's skip the object @@ -339,17 +361,18 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) { // If there exists a previous xref (for incremental PDFs), get it and merge the objects that do not exist in the current xref table if (isset($trailer_obj['Prev'])) { - $xref_prev_pos = $trailer_obj['Prev']->val(); - if (! is_numeric($xref_prev_pos)) + if (! is_numeric($xref_prev_pos)) { return p_error("invalid trailer $trailer_obj", [false, false, false]); + } $xref_prev_pos = intval($xref_prev_pos); [$prev_table, $prev_trailer, $prev_min_pdf_version] = PDFUtilFnc::get_xref_1_4($_buffer, $xref_prev_pos, $depth); - if ($prev_min_pdf_version !== $min_pdf_version) + if ($prev_min_pdf_version !== $min_pdf_version) { return p_error("mixed type of xref tables are not supported", [false, false, false]); + } if ($prev_table !== false) { foreach ($prev_table as $obj_id => &$obj_offset) { // Not modifying the objects, but to make sure that it does not consume additional memory @@ -364,29 +387,35 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) { return [$xref_table, $trailer_obj, $min_pdf_version]; } - public static function get_xref(&$_buffer, $xref_pos, $depth = null): array { - + public static function get_xref(&$_buffer, $xref_pos, $depth = null): array + { // Each xref is immediately followed by a trailer $trailer_pos = strpos((string) $_buffer, "trailer", $xref_pos); if ($trailer_pos === false) { [$xref_table, $trailer_obj, $min_pdf_version] = PDFUtilFnc::get_xref_1_5($_buffer, $xref_pos, $depth); - } else + } else { [$xref_table, $trailer_obj, $min_pdf_version] = PDFUtilFnc::get_xref_1_4($_buffer, $xref_pos, $depth); + } + return [$xref_table, $trailer_obj, $min_pdf_version]; } - public static function acquire_structure(&$_buffer, $depth = null) { + public static function acquire_structure(&$_buffer, $depth = null) + { // Get the first line and acquire the PDF version of the document $separator = "\r\n"; $pdf_version = strtok($_buffer, $separator); - if ($pdf_version === false) + if ($pdf_version === false) { return false; + } - if (preg_match('/^%PDF-[0-9]+\.[0-9]+$/', $pdf_version, $matches) !== 1) + if (preg_match('/^%PDF-[0-9]+\.[0-9]+$/', $pdf_version, $matches) !== 1) { return p_error("PDF version string not found"); + } - if (preg_match_all('/startxref\s*([0-9]+)\s*%%EOF($|[\r\n])/ms', (string) $_buffer, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE) === false) + if (preg_match_all('/startxref\s*([0-9]+)\s*%%EOF($|[\r\n])/ms', (string) $_buffer, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE) === false) { return p_error("failed to get structure"); + } $_versions = []; /* @@ -399,11 +428,13 @@ public static function acquire_structure(&$_buffer, $depth = null) { // Now get the trailing part and make sure that it has the proper form $startxref_pos = strrpos((string) $_buffer, "startxref"); - if ($startxref_pos === false) + if ($startxref_pos === false) { return p_error("startxref not found"); + } - if (preg_match('/startxref\s*([0-9]+)\s*%%EOF\s*$/ms', (string) $_buffer, $matches, 0, $startxref_pos) !== 1) + if (preg_match('/startxref\s*([0-9]+)\s*%%EOF\s*$/ms', (string) $_buffer, $matches, 0, $startxref_pos) !== 1) { return p_error("startxref and %%EOF not found"); + } $xref_pos = intval($matches[1]); @@ -427,8 +458,9 @@ public static function acquire_structure(&$_buffer, $depth = null) { return p_error("could not find the xref table"); } - if ($trailer_object === false) + if ($trailer_object === false) { return p_error("could not find the trailer object"); + } return [ "trailer" => $trailer_object, @@ -442,17 +474,21 @@ public static function acquire_structure(&$_buffer, $depth = null) { /** * Function that finds a the object at the specific position in the buffer + * * @param buffer the buffer from which to read the document * @param oid the target object id to read (if null, will return the first object, if found) * @param offset the offset at which the object is expected to be * @param xref_table the xref table, to be able to find indirect objects + * * @return obj the PDFObject obtained from the file or false if could not be found */ - public static function find_object_at_pos(&$_buffer, int $oid, $object_offset, $xref_table) { - + public static function find_object_at_pos(&$_buffer, int $oid, $object_offset, $xref_table) + { $object = PDFUtilFnc::object_from_string($_buffer, $oid, $object_offset, $offset_end); - if ($object === false) return false; + if ($object === false) { + return false; + } $_stream_pending = false; @@ -476,8 +512,9 @@ public static function find_object_at_pos(&$_buffer, int $oid, $object_offset, $ return p_error("could not get stream for object $oid"); } $length_object = PDFUtilFnc::find_object($_buffer, $xref_table, $length_object_id); - if ($length_object === false) + if ($length_object === false) { return p_error("could not get object $oid"); + } $length = $length_object->get_value()->get_int(); } @@ -494,23 +531,30 @@ public static function find_object_at_pos(&$_buffer, int $oid, $object_offset, $ /** * Function that finds a specific object in the document, using the xref table as a base + * * @param buffer the buffer from which to read the document * @param xref_table the xref table * @param oid the target object id to read + * * @return obj the PDFObject obtained from the file or false if could not be found */ - public static function find_object(&$_buffer, $xref_table, int $oid) { - - if ($oid === 0) return false; - if (! isset($xref_table[$oid])) return false; + public static function find_object(&$_buffer, $xref_table, int $oid) + { + if ($oid === 0) { + return false; + } + if (! isset($xref_table[$oid])) { + return false; + } // Find the object and get where it ends $object_offset = $xref_table[$oid]; - if (! is_array($object_offset)) + if (! is_array($object_offset)) { return PDFUtilFnc::find_object_at_pos($_buffer, $oid, $object_offset, $xref_table); - else { + } else { $object = PDFUtilFnc::find_object_in_objstm($_buffer, $xref_table, $object_offset["stmoid"], $object_offset["pos"], $oid); + return $object; } } @@ -518,24 +562,29 @@ public static function find_object(&$_buffer, $xref_table, int $oid) { /** * Function that searches for an object in an object stream */ - public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm_oid, $objpos, int $oid) { + public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm_oid, $objpos, int $oid) + { $objstm = PDFUtilFnc::find_object($_buffer, $xref_table, $objstm_oid); - if ($objstm === false) + if ($objstm === false) { return p_error("could not get object stream $objstm_oid"); + } - if (($objstm["Extends"] ?? false !== false)) - // TODO: support them + if (($objstm["Extends"] ?? false !== false)) // TODO: support them + { return p_error("not supporting extended object streams at this time"); + } $First = $objstm["First"] ?? false; $N = $objstm["N"] ?? false; $Type = $objstm["Type"] ?? false; - if (($First === false) || ($N === false) || ($Type === false)) + if (($First === false) || ($N === false) || ($Type === false)) { return p_error("invalid object stream $objstm_oid"); + } - if ($Type->val() !== "ObjStm") + if ($Type->val() !== "ObjStm") { return p_error("object $objstm_oid is not an object stream"); + } $First = $First->get_int(); $N = $N->get_int(); @@ -545,34 +594,40 @@ public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm $index = explode(" ", trim($index)); $stream = substr((string) $stream, $First); - if (count($index) % 2 !== 0) + if (count($index) % 2 !== 0) { return p_error("invalid index for object stream $objstm_oid"); + } $objpos = $objpos * 2; - if ($objpos > count($index)) + if ($objpos > count($index)) { return p_error("object $oid not found in object stream $objstm_oid"); + } $offset = intval($index[$objpos + 1]); $next = 0; $offsets = []; - for ($i = 1; ($i < count($index)); $i = $i + 2) + for ($i = 1; ($i < count($index)); $i = $i + 2) { array_push($offsets, intval($index[$i])); + } array_push($offsets, strlen($stream)); sort($offsets); - for ($i = 0; ($i < count($offsets)) && ($offset >= $offsets[$i]); $i++); + for ($i = 0; ($i < count($offsets)) && ($offset >= $offsets[$i]); $i++) { + } $next = $offsets[$i]; $object_def_str = "$oid 0 obj " . substr($stream, $offset, $next - $offset) . " endobj"; $object_def = PDFUtilFnc::object_from_string($object_def_str, $oid); + return $object_def; } /** * Function that parses an object */ - public static function object_from_string(&$buffer, $expected_obj_id, $offset = 0, &$offset_end = 0) { + public static function object_from_string(&$buffer, $expected_obj_id, $offset = 0, &$offset_end = 0) + { if (preg_match('/([0-9]+)\s+([0-9+])\s+obj(\s+)/ms', (string) $buffer, $matches, 0, $offset) !== 1) { // p_debug_var(substr($buffer)) return p_error("object is not valid: $expected_obj_id"); @@ -582,8 +637,9 @@ public static function object_from_string(&$buffer, $expected_obj_id, $offset = $found_obj_id = intval($matches[1]); $found_obj_generation = intval($matches[2]); - if ($expected_obj_id === null) + if ($expected_obj_id === null) { $expected_obj_id = $found_obj_id; + } if ($found_obj_id !== $expected_obj_id) { return p_error("pdf structure is corrupt: found obj $found_obj_id while searching for obj $expected_obj_id (at $offset)"); @@ -598,8 +654,9 @@ public static function object_from_string(&$buffer, $expected_obj_id, $offset = $stream = new StreamReader($buffer, $offset); $obj_parsed = $parser->parse($stream); - if ($obj_parsed === false) + if ($obj_parsed === false) { return p_error("object $expected_obj_id could not be parsed"); + } switch ($parser->current_token()) { case PDFObjectParser::T_OBJECT_END: @@ -613,16 +670,20 @@ public static function object_from_string(&$buffer, $expected_obj_id, $offset = } $offset_end = $stream->getpos(); + return new PDFObject($found_obj_id, $obj_parsed, $found_obj_generation); } /** * Builds the xref for the document, using the list of objects + * * @param offsets an array indexed by the oid of the objects, with the offset of each * object in the document. + * * @return xref_string a string that contains the xref table, ready to be inserted in the document */ - public static function build_xref(array $offsets): string { + public static function build_xref(array $offsets): string + { $k = array_keys($offsets); sort($k); @@ -632,7 +693,9 @@ public static function build_xref(array $offsets): string { $result = ""; $references = "0000000000 65535 f \n"; for ($i = 0; $i < count($k); $i++) { - if ($k[$i] === 0) continue; + if ($k[$i] === 0) { + continue; + } if ($k[$i] === $c_k + 1) { $count++; } else { diff --git a/src/helpers/Buffer.php b/src/helpers/Buffer.php index 8a6a475..f4de798 100644 --- a/src/helpers/Buffer.php +++ b/src/helpers/Buffer.php @@ -23,22 +23,26 @@ use Stringable; -if (! defined('__CONVENIENT_MAX_BUFFER_DUMP')) +if (! defined('__CONVENIENT_MAX_BUFFER_DUMP')) { define('__CONVENIENT_MAX_BUFFER_DUMP', 80); +} /** * This class is used to manage a buffer of characters. The main features are that * it is possible to add data (by usign *data* function), and getting the current * size. Then it is possible to get the whole buffer using function *get_raw* */ -class Buffer implements Stringable { +class Buffer implements Stringable +{ protected $_buffer = ""; protected int $_bufferlen; - public function __construct($string = null) { - if ($string === null) + public function __construct($string = null) + { + if ($string === null) { $string = ""; + } $this->_buffer = $string; $this->_bufferlen = strlen((string) $string); @@ -46,9 +50,11 @@ public function __construct($string = null) { /** * Adds raw data to the buffer + * * @param data the data to add */ - public function data(...$datas): void { + public function data(...$datas): void + { foreach ($datas as $data) { $this->_bufferlen += strlen((string) $data); $this->_buffer .= $data; @@ -57,81 +63,104 @@ public function data(...$datas): void { /** * Obtains the size of the buffer + * * @return size the size of the buffer */ - public function size(): int { + public function size(): int + { return $this->_bufferlen; } /** * Gets the raw data from the buffer + * * @return buffer the raw data */ - public function get_raw() { + public function get_raw() + { return $this->_buffer; } /** * Appends buffer $b to this buffer + * * @param b the buffer to be added to this one + * * @return buffer this object */ - public function append($b): static { - if ($b::class !== static::class) + public function append($b): static + { + if ($b::class !== static::class) { throw new Exception('invalid buffer to add to this one'); + } $this->_buffer .= $b->get_raw(); $this->_bufferlen = strlen($this->_buffer); + return $this; } /** * Obtains a new buffer that is the result from the concatenation of this buffer and the parameter + * * @param b the buffer to be added to this one + * * @return buffer the resulting buffer (different from this one) */ - public function add(...$bs): \ddn\sapp\helpers\Buffer { + public function add(...$bs): Buffer + { foreach ($bs as $b) { - if ($b::class !== static::class) + if ($b::class !== static::class) { throw new Exception('invalid buffer to add to this one'); + } } $r = new Buffer($this->_buffer); - foreach ($bs as $b) + foreach ($bs as $b) { $r->append($b); + } return $r; } /** * Returns a new buffer that contains the same data than this one + * * @return buffer the cloned buffer */ - public function clone(): \ddn\sapp\helpers\Buffer { + public function clone(): Buffer + { $buffer = new Buffer($this->_buffer); + return $buffer; } /** * Provides a easy to read string representation of the buffer, using the "var_dump" output * of the variable, but providing a reduced otput of the buffer + * * @return str a string with the representation of the buffer */ - public function __toString(): string { - if (strlen((string) $this->_buffer) < (__CONVENIENT_MAX_BUFFER_DUMP * 2)) + public function __toString(): string + { + if (strlen((string) $this->_buffer) < (__CONVENIENT_MAX_BUFFER_DUMP * 2)) { return (string) debug_var($this); + } $buffer = $this->_buffer; $this->_buffer = substr((string) $buffer, 0, __CONVENIENT_MAX_BUFFER_DUMP); $this->_buffer .= "\n...\n" . substr((string) $buffer, -__CONVENIENT_MAX_BUFFER_DUMP); $result = debug_var($this); $this->_buffer = $buffer; + return (string) $result; - } + } - public function show_bytes($columns, $offset = 0, $length = null): string { - if ($length === null) + public function show_bytes($columns, $offset = 0, $length = null): string + { + if ($length === null) { $length = $this->_bufferlen; + } $result = ""; $length = min($length, $this->_bufferlen); @@ -144,4 +173,4 @@ public function show_bytes($columns, $offset = 0, $length = null): string { return $result; } -} \ No newline at end of file +} diff --git a/src/helpers/CMS.php b/src/helpers/CMS.php index da2e013..f07ba21 100644 --- a/src/helpers/CMS.php +++ b/src/helpers/CMS.php @@ -1,6 +1,7 @@ $r) { - if (stripos($r, 'HTTP/') === 0) { - [, $code, $status] = explode(' ', $r, 3); - break; + /** + * send tsa/ocsp query with curl + * + * @param array $reqData + * + * @return string response body + * @public + */ + public function sendReq($reqData) + { + if (! function_exists('curl_init')) { + p_error(' Please enable cURL PHP extension!'); } - } - if($code != '200') { - p_error(" response error! Code=\"$code\", Status=\"" . trim($status ?? "") . "\""); - return false; - } - $contentTypeHeader = ''; - $headers = explode("\n", $header); - foreach ($headers as $key => $r) { - // Match the header name up to ':', compare lower case - if (stripos($r, "Content-Type" . ':') === 0) { - [$headername, $headervalue] = explode(":", $r, 2); - $contentTypeHeader = trim($headervalue); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $reqData['uri']); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: {$reqData['req_contentType']}", 'User-Agent: SAPP PDF']); + curl_setopt($ch, CURLOPT_POSTFIELDS, $reqData['data']); + if (($reqData['user'] ?? null) && ($reqData['password'] ?? null)) { + curl_setopt($ch, CURLOPT_USERPWD, $reqData['user'] . ':' . $reqData['password']); } - } - if($contentTypeHeader != $reqData['resp_contentType']) { - p_error(" response content type not {$reqData['resp_contentType']}, but: \"$contentTypeHeader\""); - return false; - } - if(empty($body)) { - p_error(' error empty response!'); - } - return $body; // binary response - } - } + $tsResponse = curl_exec($ch); - /** - * parse tsa response to array - * @param string $binaryTsaRespData binary tsa response to parse - * @return array asn.1 hex structure of tsa response - */ - private function tsa_parseResp($binaryTsaRespData) { - if(! @$ar = asn1::parse(bin2hex($binaryTsaRespData), 3)) { - p_error(" can't parse invalid tsa Response."); - return false; - } - $curr = $ar; - foreach($curr as $key => $value) { - if($value['type'] == '30') { - $curr['TimeStampResp'] = $curr[$key]; - unset($curr[$key]); - } - } - $ar = $curr; - $curr = $ar['TimeStampResp']; - foreach($curr as $key => $value) { - if(is_numeric($key)) { - if($value['type'] == '30' && ! array_key_exists('status', $curr)) { - $curr['status'] = $curr[$key]; - unset($curr[$key]); - } else if($value['type'] == '30') { - $curr['timeStampToken'] = $curr[$key]; - unset($curr[$key]); + if ($tsResponse) { + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + curl_close($ch); + $header = substr($tsResponse, 0, $header_size); + $body = substr($tsResponse, $header_size); + // Get the HTTP response code + $headers = explode("\n", $header); + foreach ($headers as $key => $r) { + if (stripos($r, 'HTTP/') === 0) { + [, $code, $status] = explode(' ', $r, 3); + break; + } + } + if ($code != '200') { + p_error(" response error! Code=\"$code\", Status=\"" . trim($status ?? "") . "\""); + + return false; + } + $contentTypeHeader = ''; + $headers = explode("\n", $header); + foreach ($headers as $key => $r) { + // Match the header name up to ':', compare lower case + if (stripos($r, "Content-Type" . ':') === 0) { + [$headername, $headervalue] = explode(":", $r, 2); + $contentTypeHeader = trim($headervalue); + } + } + if ($contentTypeHeader != $reqData['resp_contentType']) { + p_error(" response content type not {$reqData['resp_contentType']}, but: \"$contentTypeHeader\""); + + return false; + } + if (empty($body)) { + p_error(' error empty response!'); + } + + return $body; // binary response } - } } - $ar['TimeStampResp'] = $curr; - $curr = $ar['TimeStampResp']['timeStampToken']; - foreach($curr as $key => $value) { - if(is_numeric($key)) { - if($value['type'] == '06') { - $curr['contentType'] = $curr[$key]; - unset($curr[$key]); + + /** + * parse tsa response to array + * + * @param string $binaryTsaRespData binary tsa response to parse + * + * @return array asn.1 hex structure of tsa response + */ + private function tsa_parseResp($binaryTsaRespData) + { + if (! @$ar = asn1::parse(bin2hex($binaryTsaRespData), 3)) { + p_error(" can't parse invalid tsa Response."); + + return false; } - if($value['type'] == 'a0') { - $curr['content'] = $curr[$key]; - unset($curr[$key]); + $curr = $ar; + foreach ($curr as $key => $value) { + if ($value['type'] == '30') { + $curr['TimeStampResp'] = $curr[$key]; + unset($curr[$key]); + } } - } - } - $ar['TimeStampResp']['timeStampToken'] = $curr; - $curr = $ar['TimeStampResp']['timeStampToken']['content']; - foreach($curr as $key => $value) { - if(is_numeric($key)) { - if($value['type'] == '30') { - $curr['TSTInfo'] = $curr[$key]; - unset($curr[$key]); + $ar = $curr; + $curr = $ar['TimeStampResp']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '30' && ! array_key_exists('status', $curr)) { + $curr['status'] = $curr[$key]; + unset($curr[$key]); + } else { + if ($value['type'] == '30') { + $curr['timeStampToken'] = $curr[$key]; + unset($curr[$key]); + } + } + } + } + $ar['TimeStampResp'] = $curr; + $curr = $ar['TimeStampResp']['timeStampToken']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '06') { + $curr['contentType'] = $curr[$key]; + unset($curr[$key]); + } + if ($value['type'] == 'a0') { + $curr['content'] = $curr[$key]; + unset($curr[$key]); + } + } + } + $ar['TimeStampResp']['timeStampToken'] = $curr; + $curr = $ar['TimeStampResp']['timeStampToken']['content']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '30') { + $curr['TSTInfo'] = $curr[$key]; + unset($curr[$key]); + } + } + } + $ar['TimeStampResp']['timeStampToken']['content'] = $curr; + if (@$ar['TimeStampResp']['timeStampToken']['content']['hexdump'] != '') { + return $ar; + } else { + return false; } - } - } - $ar['TimeStampResp']['timeStampToken']['content'] = $curr; - if(@$ar['TimeStampResp']['timeStampToken']['content']['hexdump'] != '') { - return $ar; - } else { - return false; } - } - /** - * Create timestamp query - * @param string $data binary data to hashed/digested - * @param string $hashAlg hash algorithm - * @return string hex TSTinfo. - */ - protected function createTimestamp($data, $hashAlg = 'sha1') { - $TSTInfo = false; - $tsaQuery = x509::tsa_query($data, $hashAlg); - $tsaData = $this->signature_data['tsa']; - $reqData = [ - 'data' => $tsaQuery, - 'uri' => $tsaData['host'], - 'req_contentType' => 'application/timestamp-query', - 'resp_contentType' => 'application/timestamp-reply', - ] + $tsaData; + /** + * Create timestamp query + * + * @param string $data binary data to hashed/digested + * @param string $hashAlg hash algorithm + * + * @return string hex TSTinfo. + */ + protected function createTimestamp($data, $hashAlg = 'sha1') + { + $TSTInfo = false; + $tsaQuery = x509::tsa_query($data, $hashAlg); + $tsaData = $this->signature_data['tsa']; + $reqData = [ + 'data' => $tsaQuery, + 'uri' => $tsaData['host'], + 'req_contentType' => 'application/timestamp-query', + 'resp_contentType' => 'application/timestamp-reply', + ] + $tsaData; - p_debug(" sending TSA query to \"" . $tsaData['host'] . "\"..."); - if(! $binaryTsaResp = self::sendReq($reqData)) { - p_error(" TSA query send FAILED!"); - } else { - p_debug(" TSA query send OK"); - p_debug(" Parsing Timestamp response..."); - if(! $tsaResp = $this->tsa_parseResp($binaryTsaResp)) { - p_error(" parsing FAILED!"); - } - p_debug(" parsing OK"); - $TSTInfo = $tsaResp['TimeStampResp']['timeStampToken']['hexdump']; - } - return $TSTInfo; - } + p_debug(" sending TSA query to \"" . $tsaData['host'] . "\"..."); + if (! $binaryTsaResp = self::sendReq($reqData)) { + p_error(" TSA query send FAILED!"); + } else { + p_debug(" TSA query send OK"); + p_debug(" Parsing Timestamp response..."); + if (! $tsaResp = $this->tsa_parseResp($binaryTsaResp)) { + p_error(" parsing FAILED!"); + } + p_debug(" parsing OK"); + $TSTInfo = $tsaResp['TimeStampResp']['timeStampToken']['hexdump']; + } - /** - * Perform OCSP/CRL Validation - * @param array $parsedCert parsed certificate - * @param string $ocspURI - * @param string $crlURIorFILE - * @param string $issuerURIorFILE - * @return array - */ - protected function LTVvalidation($parsedCert, $ocspURI = null, $crlURIorFILE = null, $issuerURIorFILE = false): false|array { - $ltvResult['issuer'] = false; - $ltvResult['ocsp'] = false; - $ltvResult['crl'] = false; - $certSigner_parse = $parsedCert; - p_debug(" getting OCSP & CRL address..."); - p_debug(" reading AIA OCSP attribute..."); - $ocspURI = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.1'][0]; - if(empty(trim((string) $ocspURI))) { - p_warning(" FAILED!"); - } else { - p_debug(" OK got address:\"$ocspURI\""); + return $TSTInfo; } - $ocspURI = trim((string) $ocspURI); - p_debug(" reading CRL CDP attribute..."); - $crlURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['2.5.29.31']['value'][0]; - if(empty(trim($crlURIorFILE ?? ""))) { - p_warning(" FAILED!"); - } else { - p_debug(" OK got address:\"$crlURIorFILE\""); - } - if(empty($ocspURI) && empty($crlURIorFILE)) { - p_error(" can't get OCSP/CRL address! Process terminated."); - } else { // Perform if either ocspURI/crlURIorFILE exists - p_debug(" getting Issuer..."); - p_debug(" looking for issuer address from AIA attribute..."); - $issuerURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.2'][0]; - $issuerURIorFILE = trim($issuerURIorFILE ?? ""); - if(empty($issuerURIorFILE)) { - p_debug(" Failed!"); - } else { - p_debug(" OK got address \"$issuerURIorFILE\"..."); - p_debug(" load issuer from \"$issuerURIorFILE\"..."); - if($issuerCert = @file_get_contents($issuerURIorFILE)) { - p_debug(" OK. size " . round(strlen($issuerCert) / 1024, 2) . "Kb"); - p_debug(" reading issuer certificate..."); - if($issuer_certDER = x509::get_cert($issuerCert)) { - p_debug(" OK"); - p_debug(" check if issuer is cert issuer..."); - $certIssuer_parse = x509::readcert($issuer_certDER, 'oid'); // Parsing Issuer cert - $certSigner_signatureField = $certSigner_parse['signatureValue']; - if(openssl_public_decrypt(hex2bin((string) $certSigner_signatureField), $decrypted, x509::x509_der2pem($issuer_certDER), OPENSSL_PKCS1_PADDING)) { - p_debug(" OK issuer is cert issuer."); - $ltvResult['issuer'] = $issuer_certDER; - } else { - p_warning(" FAILED! issuer is not cert issuer."); - } - } else { + + /** + * Perform OCSP/CRL Validation + * + * @param array $parsedCert parsed certificate + * @param string $ocspURI + * @param string $crlURIorFILE + * @param string $issuerURIorFILE + * + * @return array + */ + protected function LTVvalidation($parsedCert): false|array + { + $ltvResult['issuer'] = false; + $ltvResult['ocsp'] = false; + $ltvResult['crl'] = false; + $certSigner_parse = $parsedCert; + p_debug(" getting OCSP & CRL address..."); + p_debug(" reading AIA OCSP attribute..."); + $ocspURI = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.1'][0]; + if (empty(trim((string) $ocspURI))) { p_warning(" FAILED!"); - } } else { - p_warning(" FAILED!."); + p_debug(" OK got address:\"$ocspURI\""); } - } - - if(! $ltvResult['issuer']) { - p_debug(" search for issuer in extracerts....."); - if(array_key_exists('extracerts', $this->signature_data) && ($this->signature_data['extracerts'] !== null) && (count($this->signature_data['extracerts']) > 0)) { - $i = 0; - foreach($this->signature_data['extracerts'] as $extracert) { - p_debug(" extracerts[$i] ..."); - $certSigner_signatureField = $certSigner_parse['signatureValue']; - if(openssl_public_decrypt(hex2bin((string) $certSigner_signatureField), $decrypted, $extracert, OPENSSL_PKCS1_PADDING)) { - p_debug(" OK got issuer."); - $certIssuer_parse = x509::readcert($extracert, 'oid'); // Parsing Issuer cert - $ltvResult['issuer'] = x509::get_cert($extracert); - } else { - p_debug(" FAIL!"); - } - $i++; - } + $ocspURI = trim((string) $ocspURI); + p_debug(" reading CRL CDP attribute..."); + $crlURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['2.5.29.31']['value'][0]; + if (empty(trim($crlURIorFILE ?? ""))) { + p_warning(" FAILED!"); } else { - p_error(" FAILED! no extracerts available"); + p_debug(" OK got address:\"$crlURIorFILE\""); } - } - - } - - if($ltvResult['issuer']) { - if(! empty($ocspURI)) { - p_debug(" OCSP start..."); - $ocspReq_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; - $ocspReq_issuerNameHash = $certIssuer_parse['tbsCertificate']['subject']['sha1']; - $ocspReq_issuerKeyHash = $certIssuer_parse['tbsCertificate']['subjectPublicKeyInfo']['sha1']; - $ocspRequestorSubjName = $certSigner_parse['tbsCertificate']['subject']['hexdump']; - p_debug(" OCSP create request..."); - if($ocspReq = x509::ocsp_request($ocspReq_serialNumber, $ocspReq_issuerNameHash, $ocspReq_issuerKeyHash)) { - p_debug(" OK."); - $ocspBinReq = pack("H*", $ocspReq); - $reqData = [ - 'data' => $ocspBinReq, - 'uri' => $ocspURI, - 'req_contentType' => 'application/ocsp-request', - 'resp_contentType' => 'application/ocsp-response', - ]; - p_debug(" OCSP send request to \"$ocspURI\"..."); - if($ocspResp = self::sendReq($reqData)) { - p_debug(" OK."); - p_debug(" OCSP parsing response..."); - if($ocsp_parse = x509::ocsp_response_parse($ocspResp, $return)) { - p_debug(" OK."); - p_debug(" OCSP check cert validity..."); - $certStatus = $ocsp_parse['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses'][0]['certStatus']; - if($certStatus == 'valid') { - p_debug(" OK. VALID."); - $ocspRespHex = $ocsp_parse['hexdump']; - $ltvResult['ocsp'] = $ocspRespHex; - } else { - p_warning(" FAILED! cert not valid, status:\"" . strtoupper((string) $certStatus) . "\""); - } + if (empty($ocspURI) && empty($crlURIorFILE)) { + p_error(" can't get OCSP/CRL address! Process terminated."); + } else { // Perform if either ocspURI/crlURIorFILE exists + p_debug(" getting Issuer..."); + p_debug(" looking for issuer address from AIA attribute..."); + $issuerURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.2'][0]; + $issuerURIorFILE = trim($issuerURIorFILE ?? ""); + if (empty($issuerURIorFILE)) { + p_debug(" Failed!"); } else { - p_warning(" FAILED! Ocsp server status \"$return\""); + p_debug(" OK got address \"$issuerURIorFILE\"..."); + p_debug(" load issuer from \"$issuerURIorFILE\"..."); + if ($issuerCert = @file_get_contents($issuerURIorFILE)) { + p_debug(" OK. size " . round(strlen($issuerCert) / 1024, 2) . "Kb"); + p_debug(" reading issuer certificate..."); + if ($issuer_certDER = x509::get_cert($issuerCert)) { + p_debug(" OK"); + p_debug(" check if issuer is cert issuer..."); + $certIssuer_parse = x509::readcert($issuer_certDER, 'oid'); // Parsing Issuer cert + $certSigner_signatureField = $certSigner_parse['signatureValue']; + if (openssl_public_decrypt(hex2bin((string) $certSigner_signatureField), $decrypted, x509::x509_der2pem($issuer_certDER), OPENSSL_PKCS1_PADDING)) { + p_debug(" OK issuer is cert issuer."); + $ltvResult['issuer'] = $issuer_certDER; + } else { + p_warning(" FAILED! issuer is not cert issuer."); + } + } else { + p_warning(" FAILED!"); + } + } else { + p_warning(" FAILED!."); + } + } + + if (! $ltvResult['issuer']) { + p_debug(" search for issuer in extracerts....."); + if (array_key_exists('extracerts', $this->signature_data) && ($this->signature_data['extracerts'] !== null) && (count($this->signature_data['extracerts']) > 0)) { + $i = 0; + foreach ($this->signature_data['extracerts'] as $extracert) { + p_debug(" extracerts[$i] ..."); + $certSigner_signatureField = $certSigner_parse['signatureValue']; + if (openssl_public_decrypt(hex2bin((string) $certSigner_signatureField), $decrypted, $extracert, OPENSSL_PKCS1_PADDING)) { + p_debug(" OK got issuer."); + $certIssuer_parse = x509::readcert($extracert, 'oid'); // Parsing Issuer cert + $ltvResult['issuer'] = x509::get_cert($extracert); + } else { + p_debug(" FAIL!"); + } + $i++; + } + } else { + p_error(" FAILED! no extracerts available"); + } } - } else { - p_warning(" FAILED!"); - } - } else { - p_warning(" FAILED!"); } - } - if(! $ltvResult['ocsp']) {// CRL not processed if OCSP validation already success - if(! empty($crlURIorFILE)) { - p_debug(" processing CRL validation since OCSP not done/failed..."); - p_debug(" getting crl from \"$crlURIorFILE\"..."); - if($crl = @file_get_contents($crlURIorFILE)) { - p_debug(" OK. size " . round(strlen($crl) / 1024, 2) . "Kb"); - p_debug(" reading crl..."); - if($crlread = x509::crl_read($crl)) { - p_debug(" OK"); - p_debug(" verify crl signature..."); - $crl_signatureField = $crlread['parse']['signature']; - if(openssl_public_decrypt(hex2bin((string) $crl_signatureField), $decrypted, x509::x509_der2pem($ltvResult['issuer']), OPENSSL_PKCS1_PADDING)) { - p_debug(" OK"); - p_debug(" check CRL validity..."); - $crl_parse = $crlread['parse']; - $thisUpdate = str_pad((string) $crl_parse['TBSCertList']['thisUpdate'], 15, "20", STR_PAD_LEFT); - $thisUpdateTime = strtotime($thisUpdate); - $nextUpdate = str_pad((string) $crl_parse['TBSCertList']['nextUpdate'], 15, "20", STR_PAD_LEFT); - $nextUpdateTime = strtotime($nextUpdate); - $nowz = strtotime("now"); - if(($nowz - $thisUpdateTime) < 0) { // 0 sec after valid - p_error(" FAILED! not yet valid! valid at " . date("d/m/Y H:i:s", $thisUpdateTime)); - } elseif(($nextUpdateTime - $nowz) < 1) { // not accept if crl 1 sec remain to expired - p_error(" FAILED! Expired crl at " . date("d/m/Y H:i:s", $nextUpdateTime) . " and now " . date("d/m/Y H:i:s", $nowz) . "!"); + if ($ltvResult['issuer']) { + if (! empty($ocspURI)) { + p_debug(" OCSP start..."); + $ocspReq_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; + $ocspReq_issuerNameHash = $certIssuer_parse['tbsCertificate']['subject']['sha1']; + $ocspReq_issuerKeyHash = $certIssuer_parse['tbsCertificate']['subjectPublicKeyInfo']['sha1']; + $ocspRequestorSubjName = $certSigner_parse['tbsCertificate']['subject']['hexdump']; + p_debug(" OCSP create request..."); + if ($ocspReq = x509::ocsp_request($ocspReq_serialNumber, $ocspReq_issuerNameHash, $ocspReq_issuerKeyHash)) { + p_debug(" OK."); + $ocspBinReq = pack("H*", $ocspReq); + $reqData = [ + 'data' => $ocspBinReq, + 'uri' => $ocspURI, + 'req_contentType' => 'application/ocsp-request', + 'resp_contentType' => 'application/ocsp-response', + ]; + p_debug(" OCSP send request to \"$ocspURI\"..."); + if ($ocspResp = self::sendReq($reqData)) { + p_debug(" OK."); + p_debug(" OCSP parsing response..."); + if ($ocsp_parse = x509::ocsp_response_parse($ocspResp, $return)) { + p_debug(" OK."); + p_debug(" OCSP check cert validity..."); + $certStatus = $ocsp_parse['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses'][0]['certStatus']; + if ($certStatus == 'valid') { + p_debug(" OK. VALID."); + $ocspRespHex = $ocsp_parse['hexdump']; + $ltvResult['ocsp'] = $ocspRespHex; + } else { + p_warning(" FAILED! cert not valid, status:\"" . strtoupper((string) $certStatus) . "\""); + } + } else { + p_warning(" FAILED! Ocsp server status \"$return\""); + } + } else { + p_warning(" FAILED!"); + } } else { - p_debug(" OK CRL still valid until " . date("d/m/Y H:i:s", $nextUpdateTime) . ""); - $crlCertValid = true; - p_debug(" check if cert not revoked..."); - if(array_key_exists('revokedCertificates', $crl_parse['TBSCertList'])) { - $certSigner_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; - if(array_key_exists($certSigner_serialNumber, $crl_parse['TBSCertList']['revokedCertificates']['lists'])) { - $crlCertValid = false; - p_error(" FAILED! Certificate Revoked!"); + p_warning(" FAILED!"); + } + } + + if (! $ltvResult['ocsp']) {// CRL not processed if OCSP validation already success + if (! empty($crlURIorFILE)) { + p_debug(" processing CRL validation since OCSP not done/failed..."); + p_debug(" getting crl from \"$crlURIorFILE\"..."); + if ($crl = @file_get_contents($crlURIorFILE)) { + p_debug(" OK. size " . round(strlen($crl) / 1024, 2) . "Kb"); + p_debug(" reading crl..."); + if ($crlread = x509::crl_read($crl)) { + p_debug(" OK"); + p_debug(" verify crl signature..."); + $crl_signatureField = $crlread['parse']['signature']; + if (openssl_public_decrypt(hex2bin((string) $crl_signatureField), $decrypted, x509::x509_der2pem($ltvResult['issuer']), OPENSSL_PKCS1_PADDING)) { + p_debug(" OK"); + p_debug(" check CRL validity..."); + $crl_parse = $crlread['parse']; + $thisUpdate = str_pad((string) $crl_parse['TBSCertList']['thisUpdate'], 15, "20", STR_PAD_LEFT); + $thisUpdateTime = strtotime($thisUpdate); + $nextUpdate = str_pad((string) $crl_parse['TBSCertList']['nextUpdate'], 15, "20", STR_PAD_LEFT); + $nextUpdateTime = strtotime($nextUpdate); + $nowz = time(); + if (($nowz - $thisUpdateTime) < 0) { // 0 sec after valid + p_error(" FAILED! not yet valid! valid at " . date("d/m/Y H:i:s", $thisUpdateTime)); + } elseif (($nextUpdateTime - $nowz) < 1) { // not accept if crl 1 sec remain to expired + p_error(" FAILED! Expired crl at " . date("d/m/Y H:i:s", $nextUpdateTime) . " and now " . date("d/m/Y H:i:s", $nowz) . "!"); + } else { + p_debug(" OK CRL still valid until " . date("d/m/Y H:i:s", $nextUpdateTime) . ""); + $crlCertValid = true; + p_debug(" check if cert not revoked..."); + if (array_key_exists('revokedCertificates', $crl_parse['TBSCertList'])) { + $certSigner_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; + if (array_key_exists($certSigner_serialNumber, $crl_parse['TBSCertList']['revokedCertificates']['lists'])) { + $crlCertValid = false; + p_error(" FAILED! Certificate Revoked!"); + } + } + if ($crlCertValid == true) { + p_debug(" OK. VALID"); + $crlHex = current(unpack('H*', (string) $crlread['der'])); + $ltvResult['crl'] = $crlHex; + } + } + } else { + p_error(" FAILED! Wrong CRL."); + } + } else { + p_error(" FAILED! can't read crl"); + } + } else { + p_error(" FAILED! can't get crl"); } - } - if($crlCertValid == true) { - p_debug(" OK. VALID"); - $crlHex = current(unpack('H*', (string) $crlread['der'])); - $ltvResult['crl'] = $crlHex; - } } - } else { - p_error(" FAILED! Wrong CRL."); - } - } else { - p_error(" FAILED! can't read crl"); } - } else { - p_error(" FAILED! can't get crl"); - } } - } - } - if(! $ltvResult['issuer']) { - return false; - } - if(! $ltvResult['ocsp'] && ! $ltvResult['crl']) { - return false; - } - return $ltvResult; - } + if (! $ltvResult['issuer']) { + return false; + } + if (! $ltvResult['ocsp'] && ! $ltvResult['crl']) { + return false; + } - /** - * Perform PKCS7 Signing - * @param string $binaryData - * @return string hex + padding 0 - * @public - */ - public function pkcs7_sign($binaryData) { - $hexOidHashAlgos = [ - 'md2' => '06082A864886F70D0202', - 'md4' => '06082A864886F70D0204', - 'md5' => '06082A864886F70D0205', - 'sha1' => '06052B0E03021A', - 'sha224' => '0609608648016503040204', - 'sha256' => '0609608648016503040201', - 'sha384' => '0609608648016503040202', - 'sha512' => '0609608648016503040203', - ]; - $hashAlgorithm = $this->signature_data['hashAlgorithm']; - if(! array_key_exists($hashAlgorithm, $hexOidHashAlgos)) { - p_error("not support hash algorithm!"); - return false; - } - p_debug("hash algorithm is \"$hashAlgorithm\""); - $x509 = new x509; - if(! $certParse = $x509->readcert($this->signature_data['signcert'])) { - p_error("certificate error! check certificate"); + return $ltvResult; } - $hexEmbedCerts[] = bin2hex($x509->get_cert($this->signature_data['signcert'])); - $appendLTV = ''; - $ltvData = $this->signature_data['ltv']; - if(! empty($ltvData)) { - p_debug(" LTV Validation start..."); - $appendLTV = ''; - $LTVvalidation_ocsp = ''; - $LTVvalidation_crl = ''; - $LTVvalidation_issuer = ''; - $LTVvalidationEnd = false; - $isRootCA = false; - if($certParse['tbsCertificate']['issuer']['hexdump'] == $certParse['tbsCertificate']['subject']['hexdump']) { // check whether root ca - if(openssl_public_decrypt(hex2bin((string) $certParse['signatureValue']), $decrypted, x509::x509_der2pem($x509->get_cert($this->signature_data['signcert'])), OPENSSL_PKCS1_PADDING)) { - p_debug("***** \"{$certParse['tbsCertificate']['subject']['2.5.4.3'][0]}\" is a ROOT CA. No validation performed ***"); - $isRootCA = true; + /** + * Perform PKCS7 Signing + * + * @param string $binaryData + * + * @return string hex + padding 0 + * @public + */ + public function pkcs7_sign($binaryData) + { + $hexOidHashAlgos = [ + 'md2' => '06082A864886F70D0202', + 'md4' => '06082A864886F70D0204', + 'md5' => '06082A864886F70D0205', + 'sha1' => '06052B0E03021A', + 'sha224' => '0609608648016503040204', + 'sha256' => '0609608648016503040201', + 'sha384' => '0609608648016503040202', + 'sha512' => '0609608648016503040203', + ]; + $hashAlgorithm = $this->signature_data['hashAlgorithm']; + if (! array_key_exists($hashAlgorithm, $hexOidHashAlgos)) { + p_error("not support hash algorithm!"); + + return false; } - } - if($isRootCA == false) { - $i = 0; - $LTVvalidation = true; - $certtoCheck = $certParse; - while($LTVvalidation !== false) { - p_debug("========= $i checking \"{$certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0]}\"==============="); - $LTVvalidation = self::LTVvalidation($certtoCheck); - $i++; - if($LTVvalidation) { - $curr_issuer = $LTVvalidation['issuer']; - $certtoCheck = $x509->readcert($curr_issuer, 'oid'); - if(@$LTVvalidation['ocsp'] || @$LTVvalidation['crl']) { - $LTVvalidation_ocsp .= $LTVvalidation['ocsp']; - $LTVvalidation_crl .= $LTVvalidation['crl']; - $hexEmbedCerts[] = bin2hex((string) $LTVvalidation['issuer']); + p_debug("hash algorithm is \"$hashAlgorithm\""); + $x509 = new x509(); + if (! $certParse = $x509->readcert($this->signature_data['signcert'])) { + p_error("certificate error! check certificate"); + } + $hexEmbedCerts[] = bin2hex($x509->get_cert($this->signature_data['signcert'])); + $appendLTV = ''; + $ltvData = $this->signature_data['ltv']; + if (! empty($ltvData)) { + p_debug(" LTV Validation start..."); + $appendLTV = ''; + $LTVvalidation_ocsp = ''; + $LTVvalidation_crl = ''; + $LTVvalidation_issuer = ''; + $LTVvalidationEnd = false; + + $isRootCA = false; + if ($certParse['tbsCertificate']['issuer']['hexdump'] == $certParse['tbsCertificate']['subject']['hexdump']) { // check whether root ca + if (openssl_public_decrypt(hex2bin((string) $certParse['signatureValue']), $decrypted, x509::x509_der2pem($x509->get_cert($this->signature_data['signcert'])), OPENSSL_PKCS1_PADDING)) { + p_debug("***** \"{$certParse['tbsCertificate']['subject']['2.5.4.3'][0]}\" is a ROOT CA. No validation performed ***"); + $isRootCA = true; + } } + if ($isRootCA == false) { + $i = 0; + $LTVvalidation = true; + $certtoCheck = $certParse; + while ($LTVvalidation !== false) { + p_debug("========= $i checking \"{$certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0]}\"==============="); + $LTVvalidation = self::LTVvalidation($certtoCheck); + $i++; + if ($LTVvalidation) { + $curr_issuer = $LTVvalidation['issuer']; + $certtoCheck = $x509->readcert($curr_issuer, 'oid'); + if (@$LTVvalidation['ocsp'] || @$LTVvalidation['crl']) { + $LTVvalidation_ocsp .= $LTVvalidation['ocsp']; + $LTVvalidation_crl .= $LTVvalidation['crl']; + $hexEmbedCerts[] = bin2hex((string) $LTVvalidation['issuer']); + } - if($certtoCheck['tbsCertificate']['issuer']['hexdump'] == $certtoCheck['tbsCertificate']['subject']['hexdump']) { // check whether root ca - if(openssl_public_decrypt(hex2bin((string) $certtoCheck['signatureValue']), $decrypted, $x509->x509_der2pem($curr_issuer), OPENSSL_PKCS1_PADDING)) { - p_debug("========= FINISH Reached ROOT CA \"{$certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0]}\"==============="); - $LTVvalidationEnd = true; - break; - } + if ($certtoCheck['tbsCertificate']['issuer']['hexdump'] == $certtoCheck['tbsCertificate']['subject']['hexdump']) { // check whether root ca + if (openssl_public_decrypt(hex2bin((string) $certtoCheck['signatureValue']), $decrypted, $x509->x509_der2pem($curr_issuer), OPENSSL_PKCS1_PADDING)) { + p_debug("========= FINISH Reached ROOT CA \"{$certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0]}\"==============="); + $LTVvalidationEnd = true; + break; + } + } + } + } + + if ($LTVvalidationEnd) { + p_debug(" LTV Validation SUCCESS\n"); + $ocsp = ''; + if (! empty($LTVvalidation_ocsp)) { + $ocsp = asn1::expl( + 1, + asn1::seq( + $LTVvalidation_ocsp + ) + ); + } + $crl = ''; + if (! empty($LTVvalidation_crl)) { + $crl = asn1::expl( + 0, + asn1::seq( + $LTVvalidation_crl + ) + ); + } + $appendLTV = asn1::seq( + "06092A864886F72F010108" . // adbe-revocationInfoArchival (1.2.840.113583.1.1.8) + asn1::set( + asn1::seq( + $ocsp . + $crl + ) + ) + ); + } else { + p_warning(" LTV Validation FAILED!\n"); + } + } + foreach ($this->signature_data['extracerts'] ?? [] as $extracert) { + $hex_extracert = bin2hex($x509->x509_pem2der($extracert)); + if (! in_array($hex_extracert, $hexEmbedCerts)) { + $hexEmbedCerts[] = $hex_extracert; + } } - } } - - if($LTVvalidationEnd) { - p_debug(" LTV Validation SUCCESS\n"); - $ocsp = ''; - if(! empty($LTVvalidation_ocsp)) { - $ocsp = asn1::expl( - 1, - asn1::seq( - $LTVvalidation_ocsp - ) - ); - } - $crl = ''; - if(! empty($LTVvalidation_crl)) { - $crl = asn1::expl( - 0, - asn1::seq( - $LTVvalidation_crl + $messageDigest = hash($hashAlgorithm, $binaryData); + $authenticatedAttributes = asn1::seq( + '06092A864886F70D010903' . //OBJ_pkcs9_contentType 1.2.840.113549.1.9.3 + asn1::set('06092A864886F70D010701') //OBJ_pkcs7_data 1.2.840.113549.1.7.1 + ) . + asn1::seq( // signing time + '06092A864886F70D010905' . //OBJ_pkcs9_signingTime 1.2.840.113549.1.9.5 + asn1::set( + asn1::utime(date("ymdHis")) //UTTC Time ) - ); - } - $appendLTV = asn1::seq( - "06092A864886F72F010108" . // adbe-revocationInfoArchival (1.2.840.113583.1.1.8) - asn1::set( - asn1::seq( - $ocsp . - $crl - ) - ) - ); - } else { - p_warning(" LTV Validation FAILED!\n"); + ) . + asn1::seq( // messageDigest + '06092A864886F70D010904' . //OBJ_pkcs9_messageDigest 1.2.840.113549.1.9.4 + asn1::set(asn1::oct($messageDigest)) + ) . + $appendLTV; + $tohash = asn1::set($authenticatedAttributes); + $hash = hash($hashAlgorithm, hex2bin($tohash)); + $toencrypt = asn1::seq( + asn1::seq($hexOidHashAlgos[$hashAlgorithm] . "0500") . // OBJ $messageDigest & OBJ_null + asn1::oct($hash) + ); + $pkey = $this->signature_data['privkey']; + if (! openssl_private_encrypt(hex2bin($toencrypt), $encryptedDigest, $pkey, OPENSSL_PKCS1_PADDING)) { + p_error("openssl_private_encrypt error! can't encrypt"); + + return false; } - } - foreach($this->signature_data['extracerts'] ?? [] as $extracert) { - $hex_extracert = bin2hex($x509->x509_pem2der($extracert)); - if(! in_array($hex_extracert, $hexEmbedCerts)) { - $hexEmbedCerts[] = $hex_extracert; + $hexencryptedDigest = bin2hex((string) $encryptedDigest); + $timeStamp = ''; + if (! empty($this->signature_data['tsa'])) { + p_debug(" Timestamping process start..."); + if ($TSTInfo = self::createTimestamp($encryptedDigest, $hashAlgorithm)) { + p_debug(" Timestamping SUCCESS."); + $TimeStampToken = asn1::seq( + "060B2A864886F70D010910020E" . // OBJ_id_smime_aa_timeStampToken 1.2.840.113549.1.9.16.2.14 + asn1::set($TSTInfo) + ); + $timeStamp = asn1::expl(1, $TimeStampToken); + } else { + p_warning(" Timestamping FAILED!"); + } } - } - } - $messageDigest = hash($hashAlgorithm, $binaryData); - $authenticatedAttributes = asn1::seq( - '06092A864886F70D010903' . //OBJ_pkcs9_contentType 1.2.840.113549.1.9.3 - asn1::set('06092A864886F70D010701') //OBJ_pkcs7_data 1.2.840.113549.1.7.1 - ) . - asn1::seq( // signing time - '06092A864886F70D010905' . //OBJ_pkcs9_signingTime 1.2.840.113549.1.9.5 - asn1::set( - asn1::utime(date("ymdHis")) //UTTC Time - ) - ) . - asn1::seq( // messageDigest - '06092A864886F70D010904' . //OBJ_pkcs9_messageDigest 1.2.840.113549.1.9.4 - asn1::set(asn1::oct($messageDigest)) - ) . - $appendLTV; - $tohash = asn1::set($authenticatedAttributes); - $hash = hash($hashAlgorithm, hex2bin($tohash)); - $toencrypt = asn1::seq( - asn1::seq($hexOidHashAlgos[$hashAlgorithm] . "0500") . // OBJ $messageDigest & OBJ_null - asn1::oct($hash) - ); - $pkey = $this->signature_data['privkey']; - if(! openssl_private_encrypt(hex2bin($toencrypt), $encryptedDigest, $pkey, OPENSSL_PKCS1_PADDING)) { - p_error("openssl_private_encrypt error! can't encrypt"); - return false; - } - $hexencryptedDigest = bin2hex((string) $encryptedDigest); - $timeStamp = ''; - if(! empty($this->signature_data['tsa'])) { - p_debug(" Timestamping process start..."); - if($TSTInfo = self::createTimestamp($encryptedDigest, $hashAlgorithm)) { - p_debug(" Timestamping SUCCESS."); - $TimeStampToken = asn1::seq( - "060B2A864886F70D010910020E" . // OBJ_id_smime_aa_timeStampToken 1.2.840.113549.1.9.16.2.14 - asn1::set($TSTInfo) + $issuerName = $certParse['tbsCertificate']['issuer']['hexdump']; + $serialNumber = $certParse['tbsCertificate']['serialNumber']; + $signerinfos = asn1::seq( + asn1::int('1') . + asn1::seq($issuerName . asn1::int($serialNumber)) . + asn1::seq($hexOidHashAlgos[$hashAlgorithm] . '0500') . + asn1::expl(0, $authenticatedAttributes) . + asn1::seq( + '06092A864886F70D010101' . //OBJ_rsaEncryption + '0500' + ) . + asn1::oct($hexencryptedDigest) . + $timeStamp + ); + $certs = asn1::expl(0, implode('', $hexEmbedCerts)); + $pkcs7contentSignedData = asn1::seq( + asn1::int('1') . + asn1::set(asn1::seq($hexOidHashAlgos[$hashAlgorithm] . '0500')) . + asn1::seq('06092A864886F70D010701') . //OBJ_pkcs7_data + $certs . + asn1::set($signerinfos) ); - $timeStamp = asn1::expl(1, $TimeStampToken); - } else { - p_warning(" Timestamping FAILED!"); - } + $pkcs7ContentInfo = asn1::seq( + "06092A864886F70D010702" . // Hexadecimal form of pkcs7-signedData + asn1::expl(0, $pkcs7contentSignedData) + ); + + return $pkcs7ContentInfo; } - $issuerName = $certParse['tbsCertificate']['issuer']['hexdump']; - $serialNumber = $certParse['tbsCertificate']['serialNumber']; - $signerinfos = asn1::seq( - asn1::int('1') . - asn1::seq($issuerName . asn1::int($serialNumber)) . - asn1::seq($hexOidHashAlgos[$hashAlgorithm] . '0500') . - asn1::expl(0, $authenticatedAttributes) . - asn1::seq( - '06092A864886F70D010101' . //OBJ_rsaEncryption - '0500' - ) . - asn1::oct($hexencryptedDigest) . - $timeStamp - ); - $certs = asn1::expl(0, implode('', $hexEmbedCerts)); - $pkcs7contentSignedData = asn1::seq( - asn1::int('1') . - asn1::set(asn1::seq($hexOidHashAlgos[$hashAlgorithm] . '0500')) . - asn1::seq('06092A864886F70D010701') . //OBJ_pkcs7_data - $certs . - asn1::set($signerinfos) - ); - $pkcs7ContentInfo = asn1::seq( - "06092A864886F70D010702" . // Hexadecimal form of pkcs7-signedData - asn1::expl(0, $pkcs7contentSignedData) - ); - return $pkcs7ContentInfo; - } } diff --git a/src/helpers/DependencyTreeObject.php b/src/helpers/DependencyTreeObject.php index 635cf4d..16aed55 100644 --- a/src/helpers/DependencyTreeObject.php +++ b/src/helpers/DependencyTreeObject.php @@ -21,17 +21,19 @@ namespace ddn\sapp\helpers; +use Generator; use Stringable; /** * A class for the PDFObjects in the dependency tree */ -class DependencyTreeObject implements Stringable { +class DependencyTreeObject implements Stringable +{ private int $is_child; function __construct( private int $oid, - private mixed $info = null + private mixed $info = null, ) { $this->is_child = 0; } @@ -40,11 +42,15 @@ function __construct( * Function that links one object to its parent (i.e. adds the object to the list of children of this object) * - the function increases the amount of times that one object has been added to a parent object, to detect problems in building the tree */ - function addchild($oid, $o): void { - if (! isset($this->children)) $this->children = []; + function addchild($oid, $o): void + { + if (! isset($this->children)) { + $this->children = []; + } $this->children[$oid] = $o; - if ($o->is_child != 0) + if ($o->is_child != 0) { p_warning("object $o->oid is already a child of other object"); + } $o->is_child = $o->is_child + 1; } @@ -52,40 +58,48 @@ function addchild($oid, $o): void { /** * This is an iterator for the children of this object */ - function children(): \Generator { - if (isset($this->children)) + function children(): Generator + { + if (isset($this->children)) { foreach ($this->children as $oid => $object) { yield $oid; } + } } /** * Gets a string that represents the object, prepending a number of spaces, proportional to the depth in the tree */ - protected function _getstr(?string $spaces = "", $mychcount = 0): string { + protected function _getstr(?string $spaces = "", $mychcount = 0): string + { // $info = $this->oid . ($this->info?" ($this->info)":"") . (($this->is_child > 1)?" $this->is_child":""); $info = $this->oid . ($this->info ? " ($this->info)" : ""); if ($spaces === null) { $lines = ["{$spaces} " . json_decode('"\u2501"') . " $info"]; - } else - if ($mychcount == 0) + } else { + if ($mychcount == 0) { $lines = ["{$spaces} " . json_decode('"\u2514\u2500"') . " $info"]; - else + } else { $lines = ["{$spaces} " . json_decode('"\u251c\u2500"') . " $info"]; + } + } if (isset($this->children)) { $chcount = count($this->children); foreach ($this->children as $oid => $child) { $chcount--; if (($spaces === null) || ($mychcount == 0)) { array_push($lines, $child->_getstr($spaces . " ", $chcount)); - } else + } else { array_push($lines, $child->_getstr($spaces . " " . json_decode('"\u2502"'), $chcount)); + } } } + return implode("\n", $lines); } - protected function _old_getstr($depth = 0): string { + protected function _old_getstr($depth = 0): string + { $spaces = str_repeat(" " . json_decode('"\u2502"'), $depth); $lines = ["{$spaces} " . json_decode('"\u251c\u2500"') . " " . $this->oid . ($this->info ? " ($this->info)" : "") . (($this->is_child > 1) ? " $this->is_child" : "")]; if (isset($this->children)) { @@ -93,10 +107,12 @@ protected function _old_getstr($depth = 0): string { array_push($lines, $child->_getstr($depth + 1)); } } + return implode("\n", $lines); } - public function __toString(): string { + public function __toString(): string + { return (string) $this->_getstr(null, isset($this->children) ? count($this->children) : 0); } } @@ -104,7 +120,6 @@ public function __toString(): string { /** * Fields that are blacklisted for referencing the fields; * i.e. a if a reference to a object appears in a fields in the blacklist, it won't be considered as a reference to other object to build the tree - * * The blacklist is indexed by the type of the node; * means "any type" (including the others in the blacklist) */ const BLACKLIST = [ @@ -117,12 +132,14 @@ public function __toString(): string { /** * @return mixed[] */ -function references_in_object(array $object, $oid = false): array { +function references_in_object(array $object, $oid = false): array +{ $type = $object["Type"]; - if ($type !== false) + if ($type !== false) { $type = $type->val(); - else + } else { $type = ""; + } $references = []; @@ -130,12 +147,15 @@ function references_in_object(array $object, $oid = false): array { $valid = true; // We'll skip those blacklisted fields - if (in_array($key, BLACKLIST["*"])) + if (in_array($key, BLACKLIST["*"])) { continue; + } - if (array_key_exists($type, BLACKLIST)) - if (in_array($key, BLACKLIST[$type])) + if (array_key_exists($type, BLACKLIST)) { + if (in_array($key, BLACKLIST[$type])) { continue; + } + } $r_objects = []; if (is_a($object[$key], "ddn\\sapp\\pdfvalue\\PDFValueObject")) { @@ -146,10 +166,13 @@ function references_in_object(array $object, $oid = false): array { $r_objects = $object[$key]->get_object_referenced(); // If the value does not have the form of a reference, it returns false - if ($r_objects === false) + if ($r_objects === false) { continue; + } - if (! is_array($r_objects)) $r_objects = [$r_objects]; + if (! is_array($r_objects)) { + $r_objects = [$r_objects]; + } } // p_debug($key . "=>" . implode(",",$r_objects)); diff --git a/src/helpers/LoadHelpers.php b/src/helpers/LoadHelpers.php index a535a1c..67c05ee 100644 --- a/src/helpers/LoadHelpers.php +++ b/src/helpers/LoadHelpers.php @@ -6,4 +6,6 @@ include_once($i); } -class LoadHelpers {} \ No newline at end of file +class LoadHelpers +{ +} diff --git a/src/helpers/StreamReader.php b/src/helpers/StreamReader.php index 8961c8b..91691ec 100644 --- a/src/helpers/StreamReader.php +++ b/src/helpers/StreamReader.php @@ -25,23 +25,24 @@ * This class abstracts the reading from a stream of data (i.e. a string). The objective of * using this class is to enable the creation of other classes (e.g. FileStreamReader) to * read from other char streams (e.g. a file) - * * The class gets a string that will be used as the buffer to read, and then it is possible * to sequentially get the characters from the string using funcion *nextchar*, that will * return "false" when the stream is finished. - * * Other functions to change the position are also available (e.g. goto) */ -class StreamReader { +class StreamReader +{ protected $_buffer = ""; protected int $_bufferlen; protected $_pos = 0; - public function __construct($string = null, $offset = 0) { - if ($string === null) + public function __construct($string = null, $offset = 0) + { + if ($string === null) { $string = ""; + } $this->_buffer = $string; $this->_bufferlen = strlen((string) $string); @@ -50,78 +51,99 @@ public function __construct($string = null, $offset = 0) { /** * Advances the buffer to the next char and returns it + * * @return char the next char in the buffer - * */ - public function nextchar() { + public function nextchar() + { $this->_pos = min($this->_pos + 1, $this->_bufferlen); + return $this->currentchar(); } /** * Advances the buffer to the next n chars and returns them + * * @param n the number of chars to read + * * @return str the substring obtained (with at most, n chars) */ - public function nextchars($n): string { + public function nextchars($n): string + { $n = min($n, $this->_bufferlen - $this->_pos); $retval = substr((string) $this->_buffer, $this->_pos, $n); $this->_pos += $n; + return $retval; } /** * Returns the current char + * * @return char the current char */ - public function currentchar() { - if ($this->_pos >= $this->_bufferlen) + public function currentchar() + { + if ($this->_pos >= $this->_bufferlen) { return false; + } return $this->_buffer[$this->_pos]; } /** * Returns whether the stream has finished or not + * * @return finished true if there are no more chars to read from the stream; false otherwise */ - public function eos(): bool { + public function eos(): bool + { return $this->_pos >= $this->_bufferlen; } /** * Sets the position of the buffer to the position in the parameter + * * @param pos the position to which the buffer must be set */ - public function goto($pos = 0): void { + public function goto($pos = 0): void + { $this->_pos = min(max(0, $pos), $this->_bufferlen); } /** * Obtains a substring that begins at current position. + * * @param length length of the substring to obtain (0 or <0 will obtain the whole buffer from the current position) + * * @return substr the substring */ - public function substratpos($length = 0): string { - if ($length > 0) + public function substratpos($length = 0): string + { + if ($length > 0) { return substr((string) $this->_buffer, $this->_pos, $length); - else + } else { return substr((string) $this->_buffer, $this->_pos); + } } /** * Gets the current position of the buffer + * * @return position the position of the buffer */ - public function getpos() { + public function getpos() + { return $this->_pos; } /** * Obtains the size of the buffer + * * @return size the size of the buffer */ - public function size(): int { + public function size(): int + { return $this->_bufferlen; } } diff --git a/src/helpers/UUID.php b/src/helpers/UUID.php index 00c2d55..b6681a3 100644 --- a/src/helpers/UUID.php +++ b/src/helpers/UUID.php @@ -2,116 +2,128 @@ namespace ddn\sapp\helpers; -class UUID { - public static function v3($namespace, string $name): false|string { - if(! self::is_valid($namespace)) return false; +class UUID +{ + public static function v3($namespace, string $name): false|string + { + if (! self::is_valid($namespace)) { + return false; + } - // Get hexadecimal components of namespace - $nhex = str_replace(['-', '{', '}'], '', $namespace); + // Get hexadecimal components of namespace + $nhex = str_replace(['-', '{', '}'], '', $namespace); - // Binary Value - $nstr = ''; + // Binary Value + $nstr = ''; - // Convert Namespace UUID to bits - for($i = 0; $i < strlen($nhex); $i += 2) { - $nstr .= chr(hexdec($nhex[$i] . $nhex[$i + 1])); - } + // Convert Namespace UUID to bits + for ($i = 0; $i < strlen($nhex); $i += 2) { + $nstr .= chr(hexdec($nhex[$i] . $nhex[$i + 1])); + } - // Calculate hash value - $hash = md5($nstr . $name); + // Calculate hash value + $hash = md5($nstr . $name); - return sprintf( - '%08s-%04s-%04x-%04x-%12s', + return sprintf( + '%08s-%04s-%04x-%04x-%12s', - // 32 bits for "time_low" - substr($hash, 0, 8), + // 32 bits for "time_low" + substr($hash, 0, 8), - // 16 bits for "time_mid" - substr($hash, 8, 4), + // 16 bits for "time_mid" + substr($hash, 8, 4), - // 16 bits for "time_hi_and_version", - // four most significant bits holds version number 3 - (hexdec(substr($hash, 12, 4)) & 0x0fff) | 0x3000, + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 3 + (hexdec(substr($hash, 12, 4)) & 0x0fff) | 0x3000, - // 16 bits, 8 bits for "clk_seq_hi_res", - // 8 bits for "clk_seq_low", - // two most significant bits holds zero and one for variant DCE1.1 - (hexdec(substr($hash, 16, 4)) & 0x3fff) | 0x8000, + // 16 bits, 8 bits for "clk_seq_hi_res", + // 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + (hexdec(substr($hash, 16, 4)) & 0x3fff) | 0x8000, - // 48 bits for "node" - substr($hash, 20, 12) - ); - } + // 48 bits for "node" + substr($hash, 20, 12) + ); + } - public static function v4(): string { - return sprintf( - '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + public static function v4(): string + { + return sprintf( + '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', - // 32 bits for "time_low" - mt_rand(0, 0xffff), - mt_rand(0, 0xffff), + // 32 bits for "time_low" + mt_rand(0, 0xffff), + mt_rand(0, 0xffff), - // 16 bits for "time_mid" - mt_rand(0, 0xffff), + // 16 bits for "time_mid" + mt_rand(0, 0xffff), - // 16 bits for "time_hi_and_version", - // four most significant bits holds version number 4 - mt_rand(0, 0x0fff) | 0x4000, + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 4 + mt_rand(0, 0x0fff) | 0x4000, - // 16 bits, 8 bits for "clk_seq_hi_res", - // 8 bits for "clk_seq_low", - // two most significant bits holds zero and one for variant DCE1.1 - mt_rand(0, 0x3fff) | 0x8000, + // 16 bits, 8 bits for "clk_seq_hi_res", + // 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + mt_rand(0, 0x3fff) | 0x8000, - // 48 bits for "node" - mt_rand(0, 0xffff), - mt_rand(0, 0xffff), - mt_rand(0, 0xffff) - ); - } + // 48 bits for "node" + mt_rand(0, 0xffff), + mt_rand(0, 0xffff), + mt_rand(0, 0xffff) + ); + } - public static function v5($namespace, string $name): false|string { - if(! self::is_valid($namespace)) return false; + public static function v5($namespace, string $name): false|string + { + if (! self::is_valid($namespace)) { + return false; + } - // Get hexadecimal components of namespace - $nhex = str_replace(['-', '{', '}'], '', $namespace); + // Get hexadecimal components of namespace + $nhex = str_replace(['-', '{', '}'], '', $namespace); - // Binary Value - $nstr = ''; + // Binary Value + $nstr = ''; - // Convert Namespace UUID to bits - for($i = 0; $i < strlen($nhex); $i += 2) { - $nstr .= chr(hexdec($nhex[$i] . $nhex[$i + 1])); - } + // Convert Namespace UUID to bits + for ($i = 0; $i < strlen($nhex); $i += 2) { + $nstr .= chr(hexdec($nhex[$i] . $nhex[$i + 1])); + } - // Calculate hash value - $hash = sha1($nstr . $name); + // Calculate hash value + $hash = sha1($nstr . $name); - return sprintf( - '%08s-%04s-%04x-%04x-%12s', + return sprintf( + '%08s-%04s-%04x-%04x-%12s', - // 32 bits for "time_low" - substr($hash, 0, 8), + // 32 bits for "time_low" + substr($hash, 0, 8), - // 16 bits for "time_mid" - substr($hash, 8, 4), + // 16 bits for "time_mid" + substr($hash, 8, 4), - // 16 bits for "time_hi_and_version", - // four most significant bits holds version number 5 - (hexdec(substr($hash, 12, 4)) & 0x0fff) | 0x5000, + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 5 + (hexdec(substr($hash, 12, 4)) & 0x0fff) | 0x5000, - // 16 bits, 8 bits for "clk_seq_hi_res", - // 8 bits for "clk_seq_low", - // two most significant bits holds zero and one for variant DCE1.1 - (hexdec(substr($hash, 16, 4)) & 0x3fff) | 0x8000, + // 16 bits, 8 bits for "clk_seq_hi_res", + // 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + (hexdec(substr($hash, 16, 4)) & 0x3fff) | 0x8000, - // 48 bits for "node" - substr($hash, 20, 12) - ); - } + // 48 bits for "node" + substr($hash, 20, 12) + ); + } - public static function is_valid($uuid): bool { - return preg_match('/^\{?[0-9a-f]{8}\-?[0-9a-f]{4}\-?[0-9a-f]{4}\-?' . - '[0-9a-f]{4}\-?[0-9a-f]{12}\}?$/i', (string) $uuid) === 1; - } -} \ No newline at end of file + public static function is_valid($uuid): bool + { + return preg_match( + '/^\{?[0-9a-f]{8}\-?[0-9a-f]{4}\-?[0-9a-f]{4}\-?' . + '[0-9a-f]{4}\-?[0-9a-f]{12}\}?$/i', + (string) $uuid + ) === 1; + } +} diff --git a/src/helpers/asn1.php b/src/helpers/asn1.php index 74bd9cc..3869239 100644 --- a/src/helpers/asn1.php +++ b/src/helpers/asn1.php @@ -1,6 +1,7 @@ "ASN1_EOC", - "01" => "ASN1_BOOLEAN", - "02" => "ASN1_INTEGER", - "03" => "ASN1_BIT_STRING", - "04" => "ASN1_OCTET_STRING", - "05" => "ASN1_NULL", - "06" => "ASN1_OBJECT", - "07" => "ASN1_OBJECT_DESCRIPTOR", - "08" => "ASN1_EXTERNAL", - "09" => "ASN1_REAL", - "0a" => "ASN1_ENUMERATED", - "0c" => "ASN1_UTF8STRING", - "30" => "ASN1_SEQUENCE", - "31" => "ASN1_SET", - "12" => "ASN1_NUMERICSTRING", - "13" => "ASN1_PRINTABLESTRING", - "14" => "ASN1_T61STRING", - "15" => "ASN1_VIDEOTEXSTRING", - "16" => "ASN1_IA5STRING", - "17" => "ASN1_UTCTIME", - "18" => "ASN1_GENERALIZEDTIME", - "19" => "ASN1_GRAPHICSTRING", - "1a" => "ASN1_VISIBLESTRING", - "1b" => "ASN1_GENERALSTRING", - "1c" => "ASN1_UNIVERSALSTRING", - "1d" => "ASN1_BMPSTRING", - ]; - return array_key_exists($id, $asn1_Types) ? $asn1_Types[$id] : $id; - } +class asn1 +{ + // =====Begin ASN.1 Parser section===== + /** + * get asn.1 type tag name + * + * @param string $id hex asn.1 type tag + * + * @return string asn.1 tag name + */ + protected static function type($id) + { + $asn1_Types = [ + "00" => "ASN1_EOC", + "01" => "ASN1_BOOLEAN", + "02" => "ASN1_INTEGER", + "03" => "ASN1_BIT_STRING", + "04" => "ASN1_OCTET_STRING", + "05" => "ASN1_NULL", + "06" => "ASN1_OBJECT", + "07" => "ASN1_OBJECT_DESCRIPTOR", + "08" => "ASN1_EXTERNAL", + "09" => "ASN1_REAL", + "0a" => "ASN1_ENUMERATED", + "0c" => "ASN1_UTF8STRING", + "30" => "ASN1_SEQUENCE", + "31" => "ASN1_SET", + "12" => "ASN1_NUMERICSTRING", + "13" => "ASN1_PRINTABLESTRING", + "14" => "ASN1_T61STRING", + "15" => "ASN1_VIDEOTEXSTRING", + "16" => "ASN1_IA5STRING", + "17" => "ASN1_UTCTIME", + "18" => "ASN1_GENERALIZEDTIME", + "19" => "ASN1_GRAPHICSTRING", + "1a" => "ASN1_VISIBLESTRING", + "1b" => "ASN1_GENERALSTRING", + "1c" => "ASN1_UNIVERSALSTRING", + "1d" => "ASN1_BMPSTRING", + ]; - /** - * parse asn.1 to array - * to be called from parse() function - * @param string $hex asn.1 hex form - * @return array asn.1 structure - */ - protected static function oneParse($hex) { - if($hex == '') { - return false; - } - if(! @ctype_xdigit($hex) || @strlen($hex) % 2 != 0) { - echo "input:\"$hex\" not hex string!.\n"; - return false; - } - $stop = false; - while($stop == false) { - $asn1_type = substr($hex, 0, 2); - $tlv_tagLength = hexdec(substr($hex, 2, 2)); - if($tlv_tagLength > 127) { - $tlv_lengthLength = $tlv_tagLength - 128; - $tlv_valueLength = substr($hex, 4, ($tlv_lengthLength * 2)); - } else { - $tlv_lengthLength = 0; - $tlv_valueLength = substr($hex, 2, 2 + ($tlv_lengthLength * 2)); - } - if($tlv_lengthLength > 4) { // limit tlv_lengthLength to FFFF - return false; - } - $tlv_valueLength = hexdec($tlv_valueLength); - $totalTlLength = 2 + 2 + ($tlv_lengthLength * 2); - $reduction = 2 + 2 + ($tlv_lengthLength * 2) + ($tlv_valueLength * 2); - $tlv_value = substr($hex, $totalTlLength, $tlv_valueLength * 2); - $remain = substr($hex, $totalTlLength + ($tlv_valueLength * 2)); - $newhexdump = substr($hex, 0, $totalTlLength + ($tlv_valueLength * 2)); - $result[] = [ - 'tlv_tagLength' => strlen(dechex($tlv_tagLength)) % 2 == 0 ? dechex($tlv_tagLength) : '0' . dechex($tlv_tagLength), - 'tlv_lengthLength' => $tlv_lengthLength, - 'tlv_valueLength' => $tlv_valueLength, - 'newhexdump' => $newhexdump, - 'typ' => $asn1_type, - 'tlv_value' => $tlv_value, - ]; - if($remain == '') { // if remains string was empty & contents also empty, function return FALSE - $stop = true; - } else { - $hex = $remain; - } + return array_key_exists($id, $asn1_Types) ? $asn1_Types[$id] : $id; } - return $result; - } - /** - * parse asn.1 to array recursively - * @param string $hex asn.1 hex form - * @param int $maxDepth maximum parsing depth - * @return array asn.1 structure recursively to specific depth - */ - public static function parse($hex, $maxDepth = 5): array { - $result = []; - static $currentDepth = 0; - if($asn1parse_array = self::oneParse($hex)) { - foreach($asn1parse_array as $ff){ - $parse_recursive = false; - unset($info); - $k = $ff['typ']; - $v = $ff['tlv_value']; - $info['depth'] = $currentDepth; - $info['hexdump'] = $ff['newhexdump']; - $info['type'] = $k; - $info['typeName'] = self::type($k); - $info['value_hex'] = $v; - if(($currentDepth <= $maxDepth)) { - if($k == '06') { + /** + * parse asn.1 to array + * to be called from parse() function + * + * @param string $hex asn.1 hex form + * + * @return array asn.1 structure + */ + protected static function oneParse($hex) + { + if ($hex == '') { + return false; + } + if (! @ctype_xdigit($hex) || @strlen($hex) % 2 != 0) { + echo "input:\"$hex\" not hex string!.\n"; - } else if(in_array($k, ['13', '18'])) { - $info['value'] = hex2bin((string) $info['value_hex']); - } else if(in_array($k, ['03', '02', 'a04'])) { - $info['value'] = $v; - } else { - $currentDepth++; - $parse_recursive = self::parse($v, $maxDepth); - $currentDepth--; - } - if($parse_recursive) { - $result[] = array_merge($info, $parse_recursive); - } else { - $result[] = $info; - } + return false; + } + $stop = false; + while ($stop == false) { + $asn1_type = substr($hex, 0, 2); + $tlv_tagLength = hexdec(substr($hex, 2, 2)); + if ($tlv_tagLength > 127) { + $tlv_lengthLength = $tlv_tagLength - 128; + $tlv_valueLength = substr($hex, 4, ($tlv_lengthLength * 2)); + } else { + $tlv_lengthLength = 0; + $tlv_valueLength = substr($hex, 2, 2 + ($tlv_lengthLength * 2)); + } + if ($tlv_lengthLength > 4) { // limit tlv_lengthLength to FFFF + return false; + } + $tlv_valueLength = hexdec($tlv_valueLength); + $totalTlLength = 2 + 2 + ($tlv_lengthLength * 2); + $reduction = 2 + 2 + ($tlv_lengthLength * 2) + ($tlv_valueLength * 2); + $tlv_value = substr($hex, $totalTlLength, $tlv_valueLength * 2); + $remain = substr($hex, $totalTlLength + ($tlv_valueLength * 2)); + $newhexdump = substr($hex, 0, $totalTlLength + ($tlv_valueLength * 2)); + $result[] = [ + 'tlv_tagLength' => strlen(dechex($tlv_tagLength)) % 2 == 0 ? dechex($tlv_tagLength) : '0' . dechex($tlv_tagLength), + 'tlv_lengthLength' => $tlv_lengthLength, + 'tlv_valueLength' => $tlv_valueLength, + 'newhexdump' => $newhexdump, + 'typ' => $asn1_type, + 'tlv_value' => $tlv_value, + ]; + if ($remain == '') { // if remains string was empty & contents also empty, function return FALSE + $stop = true; + } else { + $hex = $remain; + } } - } - } - return $result; - } - // =====End ASN.1 Parser section===== - // =====Begin ASN.1 Builder section===== - /** - * create asn.1 TLV tag length, length length and value length - * to be called from asn.1 builder functions - * @param string $str string value of asn.1 - * @return string hex of asn.1 TLV tag length - */ - protected static function asn1_header($str): string { - $len = strlen($str) / 2; - $ret = dechex($len); - if(strlen($ret) % 2 != 0) { - $ret = "0$ret"; - } - $headerLength = strlen($ret) / 2; - if($len > 127) { - $ret = "8" . $headerLength . $ret; + return $result; } - return $ret; - } - /** - * create various dynamic function for asn1 - */ - private static function asn1Tag($name): string|false { - $functionList = [ - 'seq' => '30', - 'oct' => '04', - 'obj' => '06', - 'bit' => '03', - 'printable' => '13', - 'int' => '02', - 'set' => '31', - 'expl' => 'a', - 'utime' => '17', - 'gtime' => '18', - 'utf8' => '0c', - 'ia5' => '16', - 'visible' => '1a', - 't61' => '14', - 'impl' => '80', - 'other' => '', - ]; - if(array_key_exists($name, $functionList)) { - return $functionList[$name]; - } else { - // echo "func \"$name\" not available"; - return false; + /** + * parse asn.1 to array recursively + * + * @param string $hex asn.1 hex form + * @param int $maxDepth maximum parsing depth + * + * @return array asn.1 structure recursively to specific depth + */ + public static function parse($hex, $maxDepth = 5): array + { + $result = []; + static $currentDepth = 0; + if ($asn1parse_array = self::oneParse($hex)) { + foreach ($asn1parse_array as $ff) { + $parse_recursive = false; + unset($info); + $k = $ff['typ']; + $v = $ff['tlv_value']; + $info['depth'] = $currentDepth; + $info['hexdump'] = $ff['newhexdump']; + $info['type'] = $k; + $info['typeName'] = self::type($k); + $info['value_hex'] = $v; + if (($currentDepth <= $maxDepth)) { + if ($k == '06') { + } else { + if (in_array($k, ['13', '18'])) { + $info['value'] = hex2bin((string) $info['value_hex']); + } else { + if (in_array($k, ['03', '02', 'a04'])) { + $info['value'] = $v; + } else { + $currentDepth++; + $parse_recursive = self::parse($v, $maxDepth); + $currentDepth--; + } + } + } + if ($parse_recursive) { + $result[] = array_merge($info, $parse_recursive); + } else { + $result[] = $info; + } + } + } + } + + return $result; } - } + // =====End ASN.1 Parser section===== - public static function __callStatic($func, $params) { - $func = strtolower((string) $func); - $asn1Tag = self::asn1Tag($func); - if($asn1Tag !== false){ - $num = $asn1Tag; //valu of array - $hex = $params[0]; - $val = $hex; - if(in_array($func, ['printable', 'utf8', 'ia5', 'visible', 't61'])) { // ($string) - $val = bin2hex((string) $hex); - } - if($func == 'int') { - $val = (strlen((string) $val) % 2 != 0) ? "0$val" : "$val"; + // =====Begin ASN.1 Builder section===== + /** + * create asn.1 TLV tag length, length length and value length + * to be called from asn.1 builder functions + * + * @param string $str string value of asn.1 + * + * @return string hex of asn.1 TLV tag length + */ + protected static function asn1_header($str): string + { + $len = strlen($str) / 2; + $ret = dechex($len); + if (strlen($ret) % 2 != 0) { + $ret = "0$ret"; } - if($func == 'expl') { //expl($num, $hex) - $num = $num . $params[0]; - $val = $params[1]; + $headerLength = strlen($ret) / 2; + if ($len > 127) { + $ret = "8" . $headerLength . $ret; } - if($func == 'impl') { //impl($num="0") - $val = (! $val) ? "00" : $val; - $val = (strlen((string) $val) % 2 != 0) ? "0$val" : $val; - return $num . $val; - } - if($func == 'other') { //OTHER($id, $hex, $chr = false) - $id = $params[0]; - $hex = $params[1]; - $chr = @$params[2]; - $str = $hex; - if($chr != false) { - $str = bin2hex((string) $hex); - } - $ret = "$id" . self::asn1_header($str) . $str; - return $ret; - } - if($func == 'utime') { - $time = $params[0]; //yymmddhhiiss - $oldTz = date_default_timezone_get(); - date_default_timezone_set("UTC"); - $time = date("ymdHis", $time); - date_default_timezone_set($oldTz); - $val = bin2hex($time . "Z"); - } - if($func == 'gtime') { - if(! $time = strtotime((string) $params[0])) { - // echo "asn1::GTIME function strtotime cant recognize time!! please check at input=\"{$params[0]}\""; + + return $ret; + } + + /** + * create various dynamic function for asn1 + */ + private static function asn1Tag($name): string|false + { + $functionList = [ + 'seq' => '30', + 'oct' => '04', + 'obj' => '06', + 'bit' => '03', + 'printable' => '13', + 'int' => '02', + 'set' => '31', + 'expl' => 'a', + 'utime' => '17', + 'gtime' => '18', + 'utf8' => '0c', + 'ia5' => '16', + 'visible' => '1a', + 't61' => '14', + 'impl' => '80', + 'other' => '', + ]; + if (array_key_exists($name, $functionList)) { + return $functionList[$name]; + } else { + // echo "func \"$name\" not available"; return false; - } - $oldTz = date_default_timezone_get(); - // date_default_timezone_set("UTC"); - $time = date("YmdHis", $time); - date_default_timezone_set($oldTz); - $val = bin2hex($time . "Z"); } - $hdr = self::asn1_header($val); - return $num . $hdr . $val; - } else { - // echo "asn1 \"$func\" not exists!"; } - } - // =====End ASN.1 Builder section===== + + public static function __callStatic($func, $params) + { + $func = strtolower((string) $func); + $asn1Tag = self::asn1Tag($func); + if ($asn1Tag !== false) { + $num = $asn1Tag; //valu of array + $hex = $params[0]; + $val = $hex; + if (in_array($func, ['printable', 'utf8', 'ia5', 'visible', 't61'])) { // ($string) + $val = bin2hex((string) $hex); + } + if ($func == 'int') { + $val = (strlen((string) $val) % 2 != 0) ? "0$val" : "$val"; + } + if ($func == 'expl') { //expl($num, $hex) + $num = $num . $params[0]; + $val = $params[1]; + } + if ($func == 'impl') { //impl($num="0") + $val = (! $val) ? "00" : $val; + $val = (strlen((string) $val) % 2 != 0) ? "0$val" : $val; + + return $num . $val; + } + if ($func == 'other') { //OTHER($id, $hex, $chr = false) + $id = $params[0]; + $hex = $params[1]; + $chr = @$params[2]; + $str = $hex; + if ($chr != false) { + $str = bin2hex((string) $hex); + } + $ret = "$id" . self::asn1_header($str) . $str; + + return $ret; + } + if ($func == 'utime') { + $time = $params[0]; //yymmddhhiiss + $oldTz = date_default_timezone_get(); + date_default_timezone_set("UTC"); + $time = date("ymdHis", $time); + date_default_timezone_set($oldTz); + $val = bin2hex($time . "Z"); + } + if ($func == 'gtime') { + if (! $time = strtotime((string) $params[0])) { + // echo "asn1::GTIME function strtotime cant recognize time!! please check at input=\"{$params[0]}\""; + return false; + } + $oldTz = date_default_timezone_get(); + // date_default_timezone_set("UTC"); + $time = date("YmdHis", $time); + date_default_timezone_set($oldTz); + $val = bin2hex($time . "Z"); + } + $hdr = self::asn1_header($val); + + return $num . $hdr . $val; + } else { + // echo "asn1 \"$func\" not exists!"; + } + } + // =====End ASN.1 Builder section===== } diff --git a/src/helpers/contentgeneration.php b/src/helpers/contentgeneration.php index c5a4b27..6e4efdf 100644 --- a/src/helpers/contentgeneration.php +++ b/src/helpers/contentgeneration.php @@ -27,17 +27,25 @@ use ddn\sapp\pdfvalue\PDFValueType; use finfo; -function tx($x, $y): string { +function tx($x, $y): string +{ return sprintf(" 1 0 0 1 %.2F %.2F cm", $x, $y); } -function sx($w, $h): string { + +function sx($w, $h): string +{ return sprintf(" %.2F 0 0 %.2F 0 0 cm", $w, $h); } -function deg2rad($angle): float { + +function deg2rad($angle): float +{ return $angle * pi() / 180; } -function rx($angle): string { + +function rx($angle): string +{ $angle = deg2rad($angle); + return sprintf(" %.2F %.2F %.2F %.2F 0 0 cm", cos($angle), sin($angle), -sin($angle), cos($angle)); } @@ -46,7 +54,8 @@ function rx($angle): string { * NOTE: the image inclusion is taken from http://www.fpdf.org/; this is a translation * of function _putimage */ -function _create_image_objects($info, $object_factory): array { +function _create_image_objects($info, $object_factory): array +{ $objects = []; $image = call_user_func( @@ -72,25 +81,31 @@ function _create_image_objects($info, $object_factory): array { $streamobject->set_stream($data); $image['ColorSpace']->push([ - '/Indexed', '/DeviceRGB', (strlen((string) $info['pal']) / 3) - 1, new PDFValueReference($streamobject->get_oid()), + '/Indexed', + '/DeviceRGB', + (strlen((string) $info['pal']) / 3) - 1, + new PDFValueReference($streamobject->get_oid()), ]); array_push($objects, $streamobject); break; case 'DeviceCMYK': $image["Decode"] = new PDFValueList([1, 0, 1, 0, 1, 0, 1, 0]); default: - $image['ColorSpace'] = new PDFValueType( $info['cs'] ); + $image['ColorSpace'] = new PDFValueType($info['cs']); break; } - if (isset($info['f'])) + if (isset($info['f'])) { $image['Filter'] = new PDFValueType($info['f']); + } - if(isset($info['dp'])) + if (isset($info['dp'])) { $image['DecodeParms'] = PDFValueObject::fromstring($info['dp']); + } - if (isset($info['trns']) && is_array($info['trns'])) + if (isset($info['trns']) && is_array($info['trns'])) { $image['Mask'] = new PDFValueList($info['trns']); + } if (isset($info['smask'])) { $smaskinfo = [ @@ -105,8 +120,9 @@ function _create_image_objects($info, $object_factory): array { // In principle, it may return multiple objects $smasks = _create_image_objects($smaskinfo, $object_factory); - foreach ($smasks as $smask) + foreach ($smasks as $smask) { array_push($objects, $smask); + } $image['SMask'] = new PDFValueReference($smask->get_oid()); } @@ -116,16 +132,23 @@ function _create_image_objects($info, $object_factory): array { return $objects; } -function is_base64($string): bool{ +function is_base64($string): bool +{ // Check if there are valid base64 characters - if (! preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', (string) $string)) return false; + if (! preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', (string) $string)) { + return false; + } // Decode the string in strict mode and check the results $decoded = base64_decode((string) $string, true); - if(false === $decoded) return false; + if (false === $decoded) { + return false; + } // Encode the string again - if(base64_encode($decoded) != $string) return false; + if (base64_encode($decoded) != $string) { + return false; + } return true; } @@ -134,6 +157,7 @@ function is_base64($string): bool{ * This function creates the objects needed to add an image to the document, at a specific position and size. * The function is agnostic from the place in which the image is to be created, and just creates the objects * with its contents and prepares the PDF command to place the image + * * @param filename the file name that contains the image, or a string that contains the image (with character '@' * prepended) * @param x points from left in which to appear the image (the units are "content-defined" (i.e. depending on the size of the page)) @@ -142,26 +166,31 @@ function is_base64($string): bool{ * @param h height of the rectangle in which to appear the image (image will be scaled, and the units are "content-defined" (i.e. depending on the size of the page)) * @param angle the rotation angle in degrees; the image will be rotated using the center * @param keep_proportions if true, the image will keep the proportions when rotated, then the image will not occupy the full + * * @return result an array with the next fields: * "images": objects of the corresponding images (i.e. position [0] is the image, the rest elements are masks, if needed) * "resources": PDFValueObject with keys that needs to be incorporated to the resources of the object in which the images will appear * "alpha": true if the image has alpha * "command": pdf command to draw the image */ -function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, $angle = 0, $keep_proportions = true) { - - if (empty($filename)) +function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, $angle = 0, $keep_proportions = true) +{ + if (empty($filename)) { return p_error('invalid image name or stream'); + } if ($filename[0] === '@') { $filecontent = substr((string) $filename, 1); - } else if (is_base64($filename)) { - $filecontent = base64_decode((string) $filename, true); } else { - $filecontent = @file_get_contents($filename); - - if ($filecontent === false) - return p_error("failed to get the image"); + if (is_base64($filename)) { + $filecontent = base64_decode((string) $filename, true); + } else { + $filecontent = @file_get_contents($filename); + + if ($filecontent === false) { + return p_error("failed to get the image"); + } + } } $finfo = new finfo(); @@ -187,19 +216,25 @@ function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, // Generate a new identifier for the image $info['i'] = "Im" . get_random_string(4); - if ($w === null) + if ($w === null) { $w = -96; - if ($h === null) + } + if ($h === null) { $h = -96; + } - if($w < 0) + if ($w < 0) { $w = -$info['w'] * 72 / $w; - if($h < 0) + } + if ($h < 0) { $h = -$info['h'] * 72 / $h; - if($w == 0) + } + if ($w == 0) { $w = $h * $info['w'] / $info['h']; - if($h == 0) + } + if ($h == 0) { $h = $w * $info['h'] / $info['w']; + } $images_objects = _create_image_objects($info, $object_factory); @@ -249,7 +284,7 @@ function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, } $data .= sprintf(" /%s Do Q", $info['i']); - $resources = new PDFValueObject( [ + $resources = new PDFValueObject([ 'ProcSet' => ['/PDF', '/Text', '/ImageB', '/ImageC', '/ImageI'], 'XObject' => new PDFValueObject ([ $info['i'] => new PDFValueReference($images_objects[0]->get_oid()), diff --git a/src/helpers/fpdfhelpers.php b/src/helpers/fpdfhelpers.php index 0cc306a..3f694ea 100644 --- a/src/helpers/fpdfhelpers.php +++ b/src/helpers/fpdfhelpers.php @@ -37,198 +37,206 @@ function _parsejpg($filecontent) { - // Extract info from a JPEG file - $a = getimagesizefromstring($filecontent); - if(! $a) - return p_error('Missing or incorrect image'); - if($a[2] != 2) - return p_error('Not a JPEG image'); - if(! isset($a['channels']) || $a['channels'] == 3) - $colspace = 'DeviceRGB'; - elseif($a['channels'] == 4) - $colspace = 'DeviceCMYK'; - else - $colspace = 'DeviceGray'; - $bpc = $a['bits'] ?? 8; - $data = $filecontent; - return [ - 'w' => $a[0], - 'h' => $a[1], - 'cs' => $colspace, - 'bpc' => $bpc, - 'f' => 'DCTDecode', - 'data' => $data, -]; + // Extract info from a JPEG file + $a = getimagesizefromstring($filecontent); + if (! $a) { + return p_error('Missing or incorrect image'); + } + if ($a[2] != 2) { + return p_error('Not a JPEG image'); + } + if (! isset($a['channels']) || $a['channels'] == 3) { + $colspace = 'DeviceRGB'; + } elseif ($a['channels'] == 4) { + $colspace = 'DeviceCMYK'; + } else { + $colspace = 'DeviceGray'; + } + $bpc = $a['bits'] ?? 8; + $data = $filecontent; + + return [ + 'w' => $a[0], + 'h' => $a[1], + 'cs' => $colspace, + 'bpc' => $bpc, + 'f' => 'DCTDecode', + 'data' => $data, + ]; } function _parsepng($filecontent) { - // Extract info from a PNG file - $f = new StreamReader($filecontent); - $info = _parsepngstream($f); - return $info; + // Extract info from a PNG file + $f = new StreamReader($filecontent); + $info = _parsepngstream($f); + + return $info; } function _parsepngstream(&$f) { - // Check signature - if(($res = _readstream($f, 8)) != chr(137) . 'PNG' . chr(13) . chr(10) . chr(26) . chr(10)) - return p_error("Not a PNG image $res"); + // Check signature + if (($res = _readstream($f, 8)) != chr(137) . 'PNG' . chr(13) . chr(10) . chr(26) . chr(10)) { + return p_error("Not a PNG image $res"); + } - // Read header chunk - _readstream($f, 4); - if(_readstream($f, 4) != 'IHDR') - return p_error('Incorrect PNG image'); - $w = _readint($f); - $h = _readint($f); - $bpc = ord(_readstream($f, 1)); - if($bpc > 8) - return p_error('16-bit depth not supported'); - $ct = ord(_readstream($f, 1)); - if($ct == 0 || $ct == 4) - $colspace = 'DeviceGray'; - elseif($ct == 2 || $ct == 6) - $colspace = 'DeviceRGB'; - elseif($ct == 3) - $colspace = 'Indexed'; - else - return p_error('Unknown color type'); - if(ord(_readstream($f, 1)) != 0) - return p_error('Unknown compression method'); - if(ord(_readstream($f, 1)) != 0) - return p_error('Unknown filter method'); - if(ord(_readstream($f, 1)) != 0) - return p_error('Interlacing not supported'); - _readstream($f, 4); - $dp = '/Predictor 15 /Colors ' . ($colspace == 'DeviceRGB' ? 3 : 1) . ' /BitsPerComponent ' . $bpc . ' /Columns ' . $w; + // Read header chunk + _readstream($f, 4); + if (_readstream($f, 4) != 'IHDR') { + return p_error('Incorrect PNG image'); + } + $w = _readint($f); + $h = _readint($f); + $bpc = ord(_readstream($f, 1)); + if ($bpc > 8) { + return p_error('16-bit depth not supported'); + } + $ct = ord(_readstream($f, 1)); + if ($ct == 0 || $ct == 4) { + $colspace = 'DeviceGray'; + } elseif ($ct == 2 || $ct == 6) { + $colspace = 'DeviceRGB'; + } elseif ($ct == 3) { + $colspace = 'Indexed'; + } else { + return p_error('Unknown color type'); + } + if (ord(_readstream($f, 1)) != 0) { + return p_error('Unknown compression method'); + } + if (ord(_readstream($f, 1)) != 0) { + return p_error('Unknown filter method'); + } + if (ord(_readstream($f, 1)) != 0) { + return p_error('Interlacing not supported'); + } + _readstream($f, 4); + $dp = '/Predictor 15 /Colors ' . ($colspace == 'DeviceRGB' ? 3 : 1) . ' /BitsPerComponent ' . $bpc . ' /Columns ' . $w; - // Scan chunks looking for palette, transparency and image data - $pal = ''; - $trns = ''; - $data = ''; - do - { - $n = _readint($f); - $type = _readstream($f, 4); - if($type == 'PLTE') - { - // Read palette - $pal = _readstream($f, $n); - _readstream($f, 4); - } - elseif($type == 'tRNS') - { - // Read transparency info - $t = _readstream($f, $n); - if($ct == 0) - $trns = [ord(substr((string) $t, 1, 1))]; - elseif($ct == 2) - $trns = [ord(substr((string) $t, 1, 1)), ord(substr((string) $t, 3, 1)), ord(substr((string) $t, 5, 1))]; - else - { - $pos = strpos((string) $t, chr(0)); - if($pos !== false) - $trns = [$pos]; - } - _readstream($f, 4); - } - elseif($type == 'IDAT') - { - // Read image data block - $data .= _readstream($f, $n); - _readstream($f, 4); - } - elseif($type == 'IEND') - break; - else - _readstream($f, $n + 4); - } - while($n); + // Scan chunks looking for palette, transparency and image data + $pal = ''; + $trns = ''; + $data = ''; + do { + $n = _readint($f); + $type = _readstream($f, 4); + if ($type == 'PLTE') { + // Read palette + $pal = _readstream($f, $n); + _readstream($f, 4); + } elseif ($type == 'tRNS') { + // Read transparency info + $t = _readstream($f, $n); + if ($ct == 0) { + $trns = [ord(substr((string) $t, 1, 1))]; + } elseif ($ct == 2) { + $trns = [ord(substr((string) $t, 1, 1)), ord(substr((string) $t, 3, 1)), ord(substr((string) $t, 5, 1))]; + } else { + $pos = strpos((string) $t, chr(0)); + if ($pos !== false) { + $trns = [$pos]; + } + } + _readstream($f, 4); + } elseif ($type == 'IDAT') { + // Read image data block + $data .= _readstream($f, $n); + _readstream($f, 4); + } elseif ($type == 'IEND') { + break; + } else { + _readstream($f, $n + 4); + } + } while ($n); - if($colspace == 'Indexed' && empty($pal)) - return p_error('Missing palette in image'); - $info = [ - 'w' => $w, - 'h' => $h, - 'cs' => $colspace, - 'bpc' => $bpc, - 'f' => 'FlateDecode', - 'dp' => $dp, - 'pal' => $pal, - 'trns' => $trns, - ]; - if($ct >= 4) - { - // Extract alpha channel - if(! function_exists('gzuncompress')) - return p_error('Zlib not available, can\'t handle alpha channel'); - $data = gzuncompress($data); - if ($data === false) - return p_error('failed to uncompress the image'); - $color = ''; - $alpha = ''; - if($ct == 4) - { - // Gray image - $len = 2 * $w; - for($i = 0; $i < $h; $i++) - { - $pos = (1 + $len) * $i; - $color .= $data[$pos]; - $alpha .= $data[$pos]; - $line = substr($data, $pos + 1, $len); - $color .= preg_replace('/(.)./s', '$1', $line); - $alpha .= preg_replace('/.(.)/s', '$1', $line); - } - } - else - { - // RGB image - $len = 4 * $w; - for($i = 0; $i < $h; $i++) - { - $pos = (1 + $len) * $i; - $color .= $data[$pos]; - $alpha .= $data[$pos]; - $line = substr($data, $pos + 1, $len); - $color .= preg_replace('/(.{3})./s', '$1', $line); - $alpha .= preg_replace('/.{3}(.)/s', '$1', $line); - } - } - unset($data); - $data = gzcompress($color); - $info['smask'] = gzcompress($alpha); - /* - $this->WithAlpha = true; - if($this->PDFVersion<'1.4') - $this->PDFVersion = '1.4'; - */ - } - $info['data'] = $data; - return $info; + if ($colspace == 'Indexed' && empty($pal)) { + return p_error('Missing palette in image'); + } + $info = [ + 'w' => $w, + 'h' => $h, + 'cs' => $colspace, + 'bpc' => $bpc, + 'f' => 'FlateDecode', + 'dp' => $dp, + 'pal' => $pal, + 'trns' => $trns, + ]; + if ($ct >= 4) { + // Extract alpha channel + if (! function_exists('gzuncompress')) { + return p_error('Zlib not available, can\'t handle alpha channel'); + } + $data = gzuncompress($data); + if ($data === false) { + return p_error('failed to uncompress the image'); + } + $color = ''; + $alpha = ''; + if ($ct == 4) { + // Gray image + $len = 2 * $w; + for ($i = 0; $i < $h; $i++) { + $pos = (1 + $len) * $i; + $color .= $data[$pos]; + $alpha .= $data[$pos]; + $line = substr($data, $pos + 1, $len); + $color .= preg_replace('/(.)./s', '$1', $line); + $alpha .= preg_replace('/.(.)/s', '$1', $line); + } + } else { + // RGB image + $len = 4 * $w; + for ($i = 0; $i < $h; $i++) { + $pos = (1 + $len) * $i; + $color .= $data[$pos]; + $alpha .= $data[$pos]; + $line = substr($data, $pos + 1, $len); + $color .= preg_replace('/(.{3})./s', '$1', $line); + $alpha .= preg_replace('/.{3}(.)/s', '$1', $line); + } + } + unset($data); + $data = gzcompress($color); + $info['smask'] = gzcompress($alpha); + /* + $this->WithAlpha = true; + if($this->PDFVersion<'1.4') + $this->PDFVersion = '1.4'; + */ + } + $info['data'] = $data; + + return $info; } -function _readstream(&$f, $n) { - $res = ""; +function _readstream(&$f, $n) +{ + $res = ""; + + while ($n > 0 && ! $f->eos()) { + $s = $f->nextchars($n); + if ($s === false) { + return p_error("Error while reading the stream"); + } + $n -= strlen((string) $s); + $res .= $s; + } - while ($n > 0 && ! $f->eos()) { - $s = $f->nextchars($n); - if ($s === false) - return p_error("Error while reading the stream"); - $n -= strlen((string) $s); - $res .= $s; - } + if ($n > 0) { + return p_error('Unexpected end of stream'); + } - if ($n > 0) - return p_error('Unexpected end of stream'); - return $res; + return $res; } function _readint(&$f) { - // Read a 4-byte integer from stream - $a = unpack('Ni', (string) _readstream($f, 4)); - return $a['i']; + // Read a 4-byte integer from stream + $a = unpack('Ni', (string) _readstream($f, 4)); + + return $a['i']; } /* diff --git a/src/helpers/helpers.php b/src/helpers/helpers.php index de8bd25..f0bc5db 100644 --- a/src/helpers/helpers.php +++ b/src/helpers/helpers.php @@ -31,53 +31,73 @@ define('STDERR', fopen('php://stderr', 'wb')); } -/** +/** * Outputs a var to a string, using the PHP var_dump function + * * @param var the variable to output + * * @return output the result of the var_dump of the variable -*/ -function var_dump_to_string($var): string|false { + */ +function var_dump_to_string($var): string|false +{ ob_start(); var_dump($var); $result = ob_get_clean(); + return $result; } + /** * Outputs a set of vars to a string, that is returned + * * @param vars the vars to dump + * * @return str the var_dump output of the variables */ -function debug_var(...$vars) { +function debug_var(...$vars) +{ // If the debug level is less than 3, suppress debug messages - if (_DEBUG_LEVEL < 3) return; + if (_DEBUG_LEVEL < 3) { + return; + } $result = []; foreach ($vars as $var) { array_push($result, var_dump_to_string($var)); } + return implode("\n", $result); } + /** - * Function that writes the representation of some vars to + * Function that writes the representation of some vars to + * * @param vars comma separated list of variables to output */ -function p_debug_var(...$vars): void { +function p_debug_var(...$vars): void +{ // If the debug level is less than 3, suppress debug messages - if (_DEBUG_LEVEL < 3) return; + if (_DEBUG_LEVEL < 3) { + return; + } foreach ($vars as $var) { $e = var_dump_to_string($var); p_stderr($e, "Debug"); } } + /** * Function that converts an array into a string, but also recursively converts its values * just in case that they are also arrays. In case that it is not an array, it returns its * string representation + * * @param e the variable to convert + * * @return str the string representation of the array */ -function varval($e) { +function varval($e) +{ $retval = $e; if (is_array($e)) { $a = []; @@ -87,72 +107,95 @@ function varval($e) { } $retval = "[ " . implode(", ", $a) . " ]"; } + return $retval; } + /** * Function that writes a string to stderr, including some information about the call stack + * * @param e the string to write to stderr * @param tag the tag to prepend to the string and the debug information - * @param level the depth level to output (0 will refer to the function that called p_stderr + * @param level the depth level to output (0 will refer to the function that called p_stderr * call itself, 1 to the function that called to the function that called p_stderr) */ -function p_stderr(&$e, $tag = "Error", $level = 1): void { - $dinfo = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT); +function p_stderr(&$e, $tag = "Error", $level = 1): void +{ + $dinfo = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT); $dinfo = $dinfo[$level]; $e = sprintf("$tag info at %s:%d: %s", $dinfo['file'], $dinfo['line'], varval($e)); fwrite(STDERR, "$e\n"); } + /** * Function that writes a string to stderr and returns a value (to ease coding like return p_debug(...)) + * * @param e the debug message * @param retval the value to return (default: false) + * * @return retval */ -function p_debug($e, $retval = false) { +function p_debug($e, $retval = false) +{ // If the debug level is less than 3, suppress debug messages if (_DEBUG_LEVEL >= 3) { p_stderr($e, "Debug"); } + return $retval; } + /** * Function that writes a string to stderr and returns a value (to ease coding like return p_warning(...)) + * * @param e the debug message * @param retval the value to return (default: false) + * * @return retval */ -function p_warning($e, $retval = false) { +function p_warning($e, $retval = false) +{ // If the debug level is less than 2, suppress warning messages if (_DEBUG_LEVEL >= 2) { p_stderr($e, "Warning"); } + return $retval; } + /** * Function that writes a string to stderr and returns a value (to ease coding like return p_error(...)) + * * @param e the error message * @param retval the value to return (default: false) + * * @return retval */ -function p_error($e, $retval = false) { +function p_error($e, $retval = false) +{ // If the debug level is less than 1, suppress error messages if (_DEBUG_LEVEL >= 1) { p_stderr($e, "Error"); } + return $retval; } + /** * Obtains a random string from a printable character set: alphanumeric, extended with * common symbols, an extended with less common symbols. * Note: does not consider space (0x20) nor delete (0x7f) for the alphabet. All the * other printable ascii chars are considered + * * @param length length of the resulting random string (default: 8) * @param extended true if the alphabet should consider also the common symbols (e.g. :,(...)) - * @param hard true if the alphabet should consider also the hard symbols: ^`|~ (which use to + * @param hard true if the alphabet should consider also the hard symbols: ^`|~ (which use to * need more than one key to be written) + * * @return random_string a random string considering the alphabet */ -function get_random_string($length = 8, $extended = false, $hard = false): string{ +function get_random_string($length = 8, $extended = false, $hard = false): string +{ $token = ""; $codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; $codeAlphabet .= "abcdefghijklmnopqrstuvwxyz"; @@ -167,10 +210,12 @@ function get_random_string($length = 8, $extended = false, $hard = false): strin for ($i = 0; $i < $length; $i++) { $token .= $codeAlphabet[random_int(0, $max - 1)]; } - return $token; -} -function get_memory_limit(): int { + return $token; +} + +function get_memory_limit(): int +{ $memory_limit = ini_get('memory_limit'); if (preg_match('/^(\d+)(.)$/', $memory_limit, $matches) === 1) { $memory_limit = intval($matches[1]); @@ -188,13 +233,16 @@ function get_memory_limit(): int { } else { $memory_limit = 0; } + return $memory_limit; } -function show_bytes($str, $columns = null): string { +function show_bytes($str, $columns = null): string +{ $result = ""; - if ($columns === null) + if ($columns === null) { $columns = strlen((string) $str); + } $c = $columns; for ($i = 0; $i < strlen((string) $str); $i++) { $result .= sprintf("%02x ", ord($str[$i])); @@ -203,29 +251,38 @@ function show_bytes($str, $columns = null): string { $c = $columns; $result .= "\n"; } - } + return $result; } /** * Function that outputs a timestamp to a PDF compliant string (including the D:) + * * @param timestamp the timestamp to conver (or 0 if get "now") + * * @return date_string the date string in PDF format */ -function timestamp_to_pdfdatestring($date = null): string { - if ($date === null) +function timestamp_to_pdfdatestring($date = null): string +{ + if ($date === null) { $date = new DateTime(); + } $timestamp = $date->getTimestamp(); + return 'D:' . get_pdf_formatted_date($timestamp); } + /** * Returns a formatted date-time. + * * @param $time (int) Time in seconds. + * * @return string escaped date string. * @since 5.9.152 (2012-03-23) */ -function get_pdf_formatted_date($time) { +function get_pdf_formatted_date($time) +{ return substr_replace(date('YmdHisO', intval($time)), '\'', (0 - 2), 0) . '\''; } diff --git a/src/helpers/mime.php b/src/helpers/mime.php index cb60c91..1ec1677 100644 --- a/src/helpers/mime.php +++ b/src/helpers/mime.php @@ -21,7 +21,8 @@ namespace ddn\sapp\helpers; -function mime_to_ext($mime): string|false { +function mime_to_ext($mime): string|false +{ $mime_map = [ 'video/3gpp2' => '3g2', 'video/3gp' => '3gp', @@ -203,5 +204,6 @@ function mime_to_ext($mime): string|false { 'multipart/x-zip' => 'zip', 'text/x-scriptzsh' => 'zsh', ]; + return $mime_map[$mime] ?? false; -} +} diff --git a/src/helpers/x509.php b/src/helpers/x509.php index 4b609d5..28ff6be 100644 --- a/src/helpers/x509.php +++ b/src/helpers/x509.php @@ -1,6 +1,7 @@ '06082A864886F70D0202', - 'md4' => '06082A864886F70D0204', - 'md5' => '06082A864886F70D0205', - 'sha1' => '06052B0E03021A', - 'sha224' => '0609608648016503040204', - 'sha256' => '0609608648016503040201', - 'sha384' => '0609608648016503040202', - 'sha512' => '0609608648016503040203', - ]; - if(! array_key_exists($hashAlg, $hexOidHashAlgos)) { - return false; - } - $hash = hash($hashAlg, (string) $binaryData); - $tsReqData = asn1::seq( - asn1::int(1) . - asn1::seq( - asn1::seq($hexOidHashAlgos[$hashAlg] . "0500") . // object OBJ $hexOidHashAlgos[$hashAlg] & OBJ_null - asn1::oct($hash) - ) . - asn1::int(hash('crc32', random_int(0, mt_getrandmax())) . '001') . // tsa nonce - '0101ff' // req return cert - ); - return hex2bin($tsReqData); - } - - /** - * Calculate 32bit (8 hex) openssl subject hash old and new - * @param string $hex_subjSequence hex subject name sequence - * @return array subject hash old and new - */ - private static function opensslSubjHash($hex_subjSequence): array{ - $parse = asn1::parse($hex_subjSequence, 3); - $hex_subjSequence_new = ''; - foreach($parse[0] as $k => $v) { - if(is_numeric($k)) { - $hex_subjSequence_new .= asn1::set( - asn1::seq( - $v[0][0]['hexdump'] . - asn1::utf8(strtolower(hex2bin((string) $v[0][1]['value_hex']))) - ) - ); +class x509 +{ + /* + * create tsa request/query with nonce and cert req extension + * @param string $binaryData raw/binary data of tsa query + * @param string $hashAlg hash Algorithm + * @return string binary tsa query + * @public + */ + public static function tsa_query($binaryData, $hashAlg = 'sha256'): false|string + { + $hashAlg = strtolower((string) $hashAlg); + $hexOidHashAlgos = [ + 'md2' => '06082A864886F70D0202', + 'md4' => '06082A864886F70D0204', + 'md5' => '06082A864886F70D0205', + 'sha1' => '06052B0E03021A', + 'sha224' => '0609608648016503040204', + 'sha256' => '0609608648016503040201', + 'sha384' => '0609608648016503040202', + 'sha512' => '0609608648016503040203', + ]; + if (! array_key_exists($hashAlg, $hexOidHashAlgos)) { + return false; } + $hash = hash($hashAlg, (string) $binaryData); + $tsReqData = asn1::seq( + asn1::int(1) . + asn1::seq( + asn1::seq($hexOidHashAlgos[$hashAlg] . "0500") . // object OBJ $hexOidHashAlgos[$hashAlg] & OBJ_null + asn1::oct($hash) + ) . + asn1::int(hash('crc32', random_int(0, mt_getrandmax())) . '001') . // tsa nonce + '0101ff' // req return cert + ); + + return hex2bin($tsReqData); } - $tohash = pack("H*", $hex_subjSequence_new); - $openssl_subjHash_new = hash('sha1', $tohash); - $openssl_subjHash_new = substr($openssl_subjHash_new, 0, 8); - $openssl_subjHash_new = str_split($openssl_subjHash_new, 2); - $openssl_subjHash_new = array_reverse($openssl_subjHash_new); - $openssl_subjHash_new = implode("", $openssl_subjHash_new); - $openssl_subjHash_old = hash('md5', hex2bin($hex_subjSequence)); - $openssl_subjHash_old = substr($openssl_subjHash_old, 0, 8); - $openssl_subjHash_old = str_split($openssl_subjHash_old, 2); - $openssl_subjHash_old = array_reverse($openssl_subjHash_old); - $openssl_subjHash_old = implode("", $openssl_subjHash_old); - return [ - "old" => $openssl_subjHash_old, - "new" => $openssl_subjHash_new, - ]; - } - - /** - * Parsing ocsp response data - * @param string $binaryOcspResp binary ocsp response - * @return array ocsp response structure - */ - public static function ocsp_response_parse($binaryOcspResp, &$status = '') { - $hex = current(unpack("H*", $binaryOcspResp)); - $parse = asn1::parse($hex, 10); - if($parse[0]['type'] == '30') { - $ocsp = $parse[0]; - } else { - return false; - } - foreach($ocsp as $key => $value) { - if(is_numeric($key)) { - if($value['type'] == '0a') { - $ocsp['responseStatus'] = $value['value_hex']; - unset($ocsp[$key]); - } - if($value['type'] == 'a0') { - $ocsp['responseBytes'] = $value; - unset($ocsp[$key]); - } - } else { - unset($ocsp['depth']); - unset($ocsp['type']); - unset($ocsp['typeName']); - unset($ocsp['value_hex']); - } - } - //OCSPResponseStatus ::= ENUMERATED - // successful (0), --Response has valid confirmations - // malformedRequest (1), --Illegal confirmation request - // internalError (2), --Internal error in issuer - // tryLater (3), --Try again later - // --(4) is not used - // sigRequired (5), --Must sign the request - // unauthorized (6) --Request unauthorized - if(@$ocsp['responseStatus'] != '00') { - $responseStatus['01'] = 'malformedRequest'; - $responseStatus['02'] = 'internalError'; - $responseStatus['03'] = 'tryLater'; - $responseStatus['05'] = 'sigRequired'; - $responseStatus['06'] = 'unauthorized'; - $status = @$responseStatus[$ocsp['responseStatus']]; - return false; - } - if(! @$curr = $ocsp['responseBytes']) { - return false; - } - foreach($curr as $key => $value) { - if(is_numeric($key)) { - if($value['type'] == '30') { - $curr['responseType'] = self::oidfromhex($value[0]['value_hex']); - $curr['response'] = $value[1]; - unset($curr[$key]); - } - } else { - unset($curr['typeName']); - unset($curr['type']); - unset($curr['depth']); - } - } - $ocsp['responseBytes'] = $curr; - $curr = $ocsp['responseBytes']['response']; - foreach($curr as $key => $value) { - if(is_numeric($key)) { - if($value['type'] == '30') { - $curr['BasicOCSPResponse'] = $value; - unset($curr[$key]); - } - } else { - unset($curr['typeName']); - unset($curr['type']); - unset($curr['depth']); - } - } - $ocsp['responseBytes']['response'] = $curr; - $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']; - foreach($curr as $key => $value) { - if(is_numeric($key)) { - if($value['type'] == '30' && ! array_key_exists('tbsResponseData', $curr)) { - $curr['tbsResponseData'] = $value; - unset($curr[$key]); - continue; - } - if($value['type'] == '30' && ! array_key_exists('signatureAlgorithm', $curr)) { - $curr['signatureAlgorithm'] = $value[0]['value_hex']; - unset($curr[$key]); - continue; - } - if($value['type'] == '03') { - $curr['signature'] = substr((string) $value['value_hex'], 2); - unset($curr[$key]); - } - if($value['type'] == 'a0') { - foreach($value[0] as $certsK => $certsV) { - if(is_numeric($certsK)) { - $certs[$certsK] = $certsV['value_hex']; + + /** + * Calculate 32bit (8 hex) openssl subject hash old and new + * + * @param string $hex_subjSequence hex subject name sequence + * + * @return array subject hash old and new + */ + private static function opensslSubjHash($hex_subjSequence): array + { + $parse = asn1::parse($hex_subjSequence, 3); + $hex_subjSequence_new = ''; + foreach ($parse[0] as $k => $v) { + if (is_numeric($k)) { + $hex_subjSequence_new .= asn1::set( + asn1::seq( + $v[0][0]['hexdump'] . + asn1::utf8(strtolower(hex2bin((string) $v[0][1]['value_hex']))) + ) + ); } - } - $curr['certs'] = $certs; - unset($curr[$key]); - } - } else { - unset($curr['typeName']); - unset($curr['type']); - unset($curr['depth']); - } - } - $ocsp['responseBytes']['response']['BasicOCSPResponse'] = $curr; - $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']; - foreach($curr as $key => $value) { - if(is_numeric($key)) { - if($value['type'] == 'a0') { - $curr['version'] = $value[0]['value']; - unset($curr[$key]); - } - if($value['type'] == 'a1' && ! array_key_exists('responderID', $curr)) { - $curr['responderID'] = $value; - unset($curr[$key]); - } - if($value['type'] == 'a2') { - $curr['responderID'] = $value; - unset($curr[$key]); - } - if($value['type'] == '18') { - $curr['producedAt'] = $value['value']; - unset($curr[$key]); - } - if($value['type'] == '30') { - $curr['responses'] = $value; - unset($curr[$key]); - } - if($value['type'] == 'a1') { - $curr['responseExtensions'] = $value; - unset($curr[$key]); - } - } else { - unset($curr['typeName']); - unset($curr['type']); - unset($curr['depth']); - } - } - $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData'] = $curr; - $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions']; - foreach($curr as $key => $value) { - if(is_numeric($key)) { - if($value['type'] == '30') { - $curr['lists'] = $value; - unset($curr[$key]); - } - } else { - unset($curr['typeName']); - unset($curr['type']); - unset($curr['depth']); - } - } - $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions'] = $curr; - $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions']['lists']; - foreach($curr as $key => $value) { - if(is_numeric($key)) { - if($value['type'] == '30') { - if($value[0]['value_hex'] == '2b0601050507300102') { // nonce - $curr['nonce'] = $value[0]['value_hex']; - } else { - $curr[$value[0]['value_hex']] = $value[1]; - } - unset($curr[$key]); - } - } else { - unset($curr['typeName']); - unset($curr['type']); - unset($curr['depth']); - } + } + $tohash = pack("H*", $hex_subjSequence_new); + $openssl_subjHash_new = hash('sha1', $tohash); + $openssl_subjHash_new = substr($openssl_subjHash_new, 0, 8); + $openssl_subjHash_new = str_split($openssl_subjHash_new, 2); + $openssl_subjHash_new = array_reverse($openssl_subjHash_new); + $openssl_subjHash_new = implode("", $openssl_subjHash_new); + $openssl_subjHash_old = hash('md5', hex2bin($hex_subjSequence)); + $openssl_subjHash_old = substr($openssl_subjHash_old, 0, 8); + $openssl_subjHash_old = str_split($openssl_subjHash_old, 2); + $openssl_subjHash_old = array_reverse($openssl_subjHash_old); + $openssl_subjHash_old = implode("", $openssl_subjHash_old); + + return [ + "old" => $openssl_subjHash_old, + "new" => $openssl_subjHash_new, + ]; } - $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions']['lists'] = $curr; - $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses']; - $i = 0; - foreach($curr as $key => $value) { - if(is_numeric($key)) { - foreach($value as $SingleResponseK => $SingleResponseV) { - if(is_numeric($SingleResponseK)) { - if($SingleResponseK == 0) { - foreach($SingleResponseV as $certIDk => $certIDv) { - if(is_numeric($certIDk)) { - if($certIDv['type'] == '30') { - $certID['hashAlgorithm'] = $certIDv[0]['value_hex']; - } - if($certIDv['type'] == '04' && ! array_key_exists('issuerNameHash', $certID)) { - $certID['issuerNameHash'] = $certIDv['value_hex']; + + /** + * Parsing ocsp response data + * + * @param string $binaryOcspResp binary ocsp response + * + * @return array ocsp response structure + */ + public static function ocsp_response_parse($binaryOcspResp, &$status = '') + { + $hex = current(unpack("H*", $binaryOcspResp)); + $parse = asn1::parse($hex, 10); + if ($parse[0]['type'] == '30') { + $ocsp = $parse[0]; + } else { + return false; + } + foreach ($ocsp as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '0a') { + $ocsp['responseStatus'] = $value['value_hex']; + unset($ocsp[$key]); + } + if ($value['type'] == 'a0') { + $ocsp['responseBytes'] = $value; + unset($ocsp[$key]); + } + } else { + unset($ocsp['depth']); + unset($ocsp['type']); + unset($ocsp['typeName']); + unset($ocsp['value_hex']); + } + } + //OCSPResponseStatus ::= ENUMERATED + // successful (0), --Response has valid confirmations + // malformedRequest (1), --Illegal confirmation request + // internalError (2), --Internal error in issuer + // tryLater (3), --Try again later + // --(4) is not used + // sigRequired (5), --Must sign the request + // unauthorized (6) --Request unauthorized + if (@$ocsp['responseStatus'] != '00') { + $responseStatus['01'] = 'malformedRequest'; + $responseStatus['02'] = 'internalError'; + $responseStatus['03'] = 'tryLater'; + $responseStatus['05'] = 'sigRequired'; + $responseStatus['06'] = 'unauthorized'; + $status = @$responseStatus[$ocsp['responseStatus']]; + + return false; + } + if (! @$curr = $ocsp['responseBytes']) { + return false; + } + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '30') { + $curr['responseType'] = self::oidfromhex($value[0]['value_hex']); + $curr['response'] = $value[1]; + unset($curr[$key]); + } + } else { + unset($curr['typeName']); + unset($curr['type']); + unset($curr['depth']); + } + } + $ocsp['responseBytes'] = $curr; + $curr = $ocsp['responseBytes']['response']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '30') { + $curr['BasicOCSPResponse'] = $value; + unset($curr[$key]); + } + } else { + unset($curr['typeName']); + unset($curr['type']); + unset($curr['depth']); + } + } + $ocsp['responseBytes']['response'] = $curr; + $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '30' && ! array_key_exists('tbsResponseData', $curr)) { + $curr['tbsResponseData'] = $value; + unset($curr[$key]); + continue; + } + if ($value['type'] == '30' && ! array_key_exists('signatureAlgorithm', $curr)) { + $curr['signatureAlgorithm'] = $value[0]['value_hex']; + unset($curr[$key]); + continue; + } + if ($value['type'] == '03') { + $curr['signature'] = substr((string) $value['value_hex'], 2); + unset($curr[$key]); + } + if ($value['type'] == 'a0') { + foreach ($value[0] as $certsK => $certsV) { + if (is_numeric($certsK)) { + $certs[$certsK] = $certsV['value_hex']; + } } - if($certIDv['type'] == '04') { - $certID['issuerKeyHash'] = $certIDv['value_hex']; + $curr['certs'] = $certs; + unset($curr[$key]); + } + } else { + unset($curr['typeName']); + unset($curr['type']); + unset($curr['depth']); + } + } + $ocsp['responseBytes']['response']['BasicOCSPResponse'] = $curr; + $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == 'a0') { + $curr['version'] = $value[0]['value']; + unset($curr[$key]); + } + if ($value['type'] == 'a1' && ! array_key_exists('responderID', $curr)) { + $curr['responderID'] = $value; + unset($curr[$key]); + } + if ($value['type'] == 'a2') { + $curr['responderID'] = $value; + unset($curr[$key]); + } + if ($value['type'] == '18') { + $curr['producedAt'] = $value['value']; + unset($curr[$key]); + } + if ($value['type'] == '30') { + $curr['responses'] = $value; + unset($curr[$key]); + } + if ($value['type'] == 'a1') { + $curr['responseExtensions'] = $value; + unset($curr[$key]); + } + } else { + unset($curr['typeName']); + unset($curr['type']); + unset($curr['depth']); + } + } + $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData'] = $curr; + $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '30') { + $curr['lists'] = $value; + unset($curr[$key]); + } + } else { + unset($curr['typeName']); + unset($curr['type']); + unset($curr['depth']); + } + } + $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions'] = $curr; + $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions']['lists']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '30') { + if ($value[0]['value_hex'] == '2b0601050507300102') { // nonce + $curr['nonce'] = $value[0]['value_hex']; + } else { + $curr[$value[0]['value_hex']] = $value[1]; } - if($certIDv['type'] == '02') { - $certID['serialNumber'] = $certIDv['value_hex']; + unset($curr[$key]); + } + } else { + unset($curr['typeName']); + unset($curr['type']); + unset($curr['depth']); + } + } + $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions']['lists'] = $curr; + $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses']; + $i = 0; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + foreach ($value as $SingleResponseK => $SingleResponseV) { + if (is_numeric($SingleResponseK)) { + if ($SingleResponseK == 0) { + foreach ($SingleResponseV as $certIDk => $certIDv) { + if (is_numeric($certIDk)) { + if ($certIDv['type'] == '30') { + $certID['hashAlgorithm'] = $certIDv[0]['value_hex']; + } + if ($certIDv['type'] == '04' && ! array_key_exists('issuerNameHash', $certID)) { + $certID['issuerNameHash'] = $certIDv['value_hex']; + } + if ($certIDv['type'] == '04') { + $certID['issuerKeyHash'] = $certIDv['value_hex']; + } + if ($certIDv['type'] == '02') { + $certID['serialNumber'] = $certIDv['value_hex']; + } + } + } + $cert['certID'] = $certID; + } + if ($SingleResponseK == 1) { + if ($SingleResponseV['type'] == '82') { + $certStatus = 'unknown'; + } elseif ($SingleResponseV['type'] == '80') { + $certStatus = 'valid'; + } else { + $certStatus = 'revoked'; + } + $cert['certStatus'] = $certStatus; + } + if ($SingleResponseK == 2) { + $cert['thisUpdate'] = $SingleResponseV['value']; + } + if ($SingleResponseK == 3) { + $cert['nextUpdate'] = $SingleResponseV[0]['value']; + } + if ($SingleResponseK == 4) { + $cert['singleExtensions'] = $SingleResponseV; + } } - } - } - $cert['certID'] = $certID; - } - if($SingleResponseK == 1) { - if($SingleResponseV['type'] == '82') { - $certStatus = 'unknown'; - } elseif($SingleResponseV['type'] == '80') { - $certStatus = 'valid'; - } else { - $certStatus = 'revoked'; - } - $cert['certStatus'] = $certStatus; - } - if($SingleResponseK == 2) { - $cert['thisUpdate'] = $SingleResponseV['value']; - } - if($SingleResponseK == 3) { - $cert['nextUpdate'] = $SingleResponseV[0]['value']; - } - if($SingleResponseK == 4) { - $cert['singleExtensions'] = $SingleResponseV; - } + } + $curr[$i] = $cert; + } else { + unset($curr[$key]); + unset($curr['typeName']); + unset($curr['type']); + unset($curr['depth']); } - } - $curr[$i] = $cert; - } else { - unset($curr[$key]); - unset($curr['typeName']); - unset($curr['type']); - unset($curr['depth']); - } - } - $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses'] = $curr; - $arrModel = [ - 'responseStatus' => '', - 'responseBytes' => [ - 'response' => '', - 'responseType' => '', - ], - ]; - $differ = array_diff_key($arrModel, $ocsp); - if(count($differ) == 0) { - $differ = array_diff_key($arrModel['responseBytes'], $ocsp['responseBytes']); - if(count($differ) > 0) { - foreach($differ as $key => $val) { - } - return false; - } - } else { - foreach($differ as $key => $val) { - } - return false; - } - return $ocsp; - } - - /** - * Create ocsp request - * @param string $serialNumber serial number to check - * @param string $issuerNameHash sha1 hex form of issuer subject hash - * @param string $issuerKeyHash sha1 hex form of issuer subject public info hash - * @param string $signer_cert cert to sign ocsp request - * @param string $signer_key privkey to sign ocsp request - * @param string $subjectName hex form of asn1 subject - * @return string hex form ocsp request - */ - public static function ocsp_request($serialNumber, $issuerNameHash, $issuerKeyHash, $signer_cert = false, $signer_key = false, $subjectName = false) { - $Request = false; - $hashAlgorithm = asn1::seq( - "06052B0E03021A" . // OBJ_sha1 - "0500" - ); - $issuerNameHash = asn1::oct($issuerNameHash); - $issuerKeyHash = asn1::oct($issuerKeyHash); - $serialNumber = asn1::int($serialNumber); - $CertID = asn1::seq($hashAlgorithm . $issuerNameHash . $issuerKeyHash . $serialNumber); - $Request = asn1::seq($CertID); // one request - if($signer_cert) { - $requestorName = asn1::expl("1", asn1::expl("4", $subjectName)); - } else { - $requestorName = false; - } - $requestList = asn1::seq($Request); // add more request into sequence - $rand = microtime (true) * random_int(0, mt_getrandmax()); - $nonce = md5(base64_encode($rand) . $rand); - $ReqExts = asn1::seq( - '06092B0601050507300102' . // OBJ_id_pkix_OCSP_Nonce - asn1::oct("0410" . $nonce) - ); - $requestExtensions = asn1::expl( "2", asn1::seq($ReqExts)); - $TBSRequest = asn1::seq($requestorName . $requestList . $requestExtensions); - $optionalSignature = ''; - if($signer_cert) { - if(! openssl_sign (hex2bin($TBSRequest), $signature_value, $signer_key)) { - return false; - } - $signatureAlgorithm = asn1::seq( - '06092A864886F70D010105' . // OBJ_sha1WithRSAEncryption. - "0500" - ); - $signature = asn1::bit("00" . bin2hex((string) $signature_value)); - $signer_cert = x509::x509_pem2der($signer_cert); - $certs = asn1::expl("0", asn1::seq(bin2hex($signer_cert))); - $optionalSignature = asn1::expl("0", asn1::seq($signatureAlgorithm . $signature . $certs)); - } - $OCSPRequest = asn1::seq($TBSRequest . $optionalSignature); - return $OCSPRequest; - } - - /** - * Convert crl from pem to der (binary) - * @param string $crl pem crl to convert - * @return string der crl form - */ - public static function crl_pem2der($crl): false|string { - $begin = '-----BEGIN X509 CRL-----'; - $end = '-----END X509 CRL-----'; - $beginPos = stripos($crl, $begin); - if($beginPos === false) { - return false; - } - $crl = substr($crl, $beginPos + strlen($begin)); - $endPos = stripos($crl, $end); - if($endPos === false) { - return false; - } - $crl = substr($crl, 0, $endPos); - $crl = str_replace("\n", "", $crl); - $crl = str_replace("\r", "", $crl); - $dercrl = base64_decode($crl, true); - return $dercrl; - } - - /** - * Read crl from pem or der (binary) - * @param string $crl pem or der crl - * @return array der crl and parsed crl - */ - public static function crl_read($crl): false|array { - if(! $crlparse = self::parsecrl($crl)) { // if cant read, thats not crl - return false; - } - if(! $dercrl = self::crl_pem2der($crl)) { // if not pem, thats already der - $dercrl = $crl; - } - $res['der'] = $dercrl; - $res['parse'] = $crlparse; - return $res; - } - - /** - * parsing crl from pem or der (binary) - * @param string $crl pem or der crl - * @param string $oidprint option show obj as hex/oid - * @return array parsed crl - */ - private static function parsecrl(array $crl, $oidprint = false) { - if($derCrl = self::crl_pem2der($crl)) { - $derCrl = bin2hex($derCrl); - } else { - $derCrl = bin2hex($crl); - } - $curr = asn1::parse($derCrl, 7); - foreach($curr as $key => $value) { - if($value['type'] == '30') { - $curr['crl'] = $curr[$key]; - unset($curr[$key]); - } - } - $ar = $curr; - if(! array_key_exists('crl', $ar)) { - return false; - } - $curr = $ar['crl']; - foreach($curr as $key => $value) { - if(is_numeric($key)) { - if($value['type'] == '30' && ! array_key_exists('TBSCertList', $curr)) { - $curr['TBSCertList'] = $curr[$key]; - unset($curr[$key]); - } - if($value['type'] == '30') { - $curr['signatureAlgorithm'] = self::oidfromhex($value[0]['value_hex']); - unset($curr[$key]); - } - if($value['type'] == '03') { - $curr['signature'] = substr((string) $value['value'], 2); - unset($curr[$key]); - } - } else { - unset($curr[$key]); - } - } - $ar['crl'] = $curr; - $curr = $ar['crl']['TBSCertList']; - foreach($curr as $key => $value) { - if(is_numeric($key)) { - if($value['type'] == '02') { - $curr['version'] = $curr[$key]['value']; - unset($curr[$key]); - } - if($value['type'] == '30' && ! array_key_exists('signature', $curr)) { - $curr['signature'] = $value[0]['value_hex']; - unset($curr[$key]); - continue; - } - if($value['type'] == '30' && ! array_key_exists('issuer', $curr)) { - $curr['issuer'] = $value; - unset($curr[$key]); - continue; - } - if($value['type'] == '17' && ! array_key_exists('thisUpdate', $curr)) { - $curr['thisUpdate'] = hex2bin((string) $value['value_hex']); - unset($curr[$key]); - continue; - } - if($value['type'] == '17' && ! array_key_exists('nextUpdate', $curr)) { - $curr['nextUpdate'] = hex2bin((string) $value['value_hex']); - unset($curr[$key]); - continue; - } - if($value['type'] == '30' && ! array_key_exists('revokedCertificates', $curr)) { - $curr['revokedCertificates'] = $value; - unset($curr[$key]); - continue; - } - if($value['type'] == 'a0') { - $curr['crlExtensions'] = $curr[$key]; - unset($curr[$key]); - } - } else { - unset($curr[$key]); - } - } - $ar['crl']['TBSCertList'] = $curr; - if(array_key_exists('revokedCertificates', $curr)) { - $curr = $ar['crl']['TBSCertList']['revokedCertificates']; - foreach($curr as $key => $value) { - if(is_numeric($key)) { - if($value['type'] == '30') { - $serial = $value[0]['value']; - $revoked['time'] = hex2bin((string) $value[1]['value_hex']); - $lists[$serial] = $revoked; - unset($curr[$key]); - } - } else { - unset($curr['depth']); - unset($curr['type']); - unset($curr['typeName']); } - } - $curr['lists'] = $lists; - $ar['crl']['TBSCertList']['revokedCertificates'] = $curr; - } - if(array_key_exists('crlExtensions', $ar['crl']['TBSCertList'])) { - $curr = $ar['crl']['TBSCertList']['crlExtensions'][0]; - unset($ar['crl']['TBSCertList']['crlExtensions']); - foreach($curr as $key => $value) { - if(is_numeric($key)) { - $attributes_name = self::oidfromhex($value[0]['value_hex']); - if($oidprint == 'oid') { - $attributes_name = self::oidfromhex($value[0]['value_hex']); - } - if($oidprint == 'hex') { - $attributes_name = $value[0]['value_hex']; - } - $attributes_oid = self::oidfromhex($value[0]['value_hex']); - if($value['type'] == '30') { - $crlExtensionsValue = $value[1][0]; - if($attributes_oid == '2.5.29.20') { // OBJ_crl_number - $crlExtensionsValue = $crlExtensionsValue['value']; + $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses'] = $curr; + $arrModel = [ + 'responseStatus' => '', + 'responseBytes' => [ + 'response' => '', + 'responseType' => '', + ], + ]; + $differ = array_diff_key($arrModel, $ocsp); + if (count($differ) == 0) { + $differ = array_diff_key($arrModel['responseBytes'], $ocsp['responseBytes']); + if (count($differ) > 0) { + foreach ($differ as $key => $val) { + } + + return false; } - if($attributes_oid == '2.5.29.35') { // OBJ_authority_key_identifier - foreach($crlExtensionsValue as $authority_key_identifierValueK => $authority_key_identifierV) { - if(is_numeric($authority_key_identifierValueK)) { - if($authority_key_identifierV['type'] == '80') { - $authority_key_identifier['keyIdentifier'] = $authority_key_identifierV['value_hex']; - } - if($authority_key_identifierV['type'] == 'a1') { - $authority_key_identifier['authorityCertIssuer'] = $authority_key_identifierV['value_hex']; - } - if($authority_key_identifierV['type'] == '82') { - $authority_key_identifier['authorityCertSerialNumber'] = $authority_key_identifierV['value_hex']; - } - } - } - $crlExtensionsValue = $authority_key_identifier; + } else { + foreach ($differ as $key => $val) { } - $attribute_list = $crlExtensionsValue; - } - $ar['crl']['TBSCertList']['crlExtensions'][$attributes_name] = $attribute_list; + + return false; } - } - } - $curr = $ar['crl']['TBSCertList']['issuer']; - foreach($curr as $key => $value) { - if(is_numeric($key)) { - if($value['type'] == '31') { - if($oidprint == 'oid') { - $subjOID = self::oidfromhex($curr[$key][0][0]['value_hex']); - } elseif($oidprint == 'hex') { - $subjOID = $curr[$key][0][0]['value_hex']; - } else { - $subjOID = self::oidfromhex($curr[$key][0][0]['value_hex']); - } - $curr[$subjOID][] = hex2bin((string) $curr[$key][0][1]['value_hex']); - unset($curr[$key]); - - } - } else { - unset($curr['depth']); - unset($curr['type']); - unset($curr['typeName']); - if($key == 'hexdump') { - $curr['sha1'] = hash('sha1', pack("H*", $value)); - } - } - } - $ar['crl']['TBSCertList']['issuer'] = $curr; - $arrModel['TBSCertList']['version'] = ''; - $arrModel['TBSCertList']['signature'] = ''; - $arrModel['TBSCertList']['issuer'] = ''; - $arrModel['TBSCertList']['thisUpdate'] = ''; - $arrModel['TBSCertList']['nextUpdate'] = ''; - $arrModel['signatureAlgorithm'] = ''; - $arrModel['signature'] = ''; - $crl = $ar['crl']; - $differ = array_diff_key($arrModel, $crl); - if(count($differ) == 0) { - $differ = array_diff_key($arrModel['TBSCertList'], $crl['TBSCertList']); - if(count($differ) > 0) { - foreach($differ as $key => $val) { - } - return false; - } - } else { - foreach($differ as $key => $val) { - } - return false; - } - return $ar['crl']; - } - - /** - * Convert x509 pem certificate to x509 der - * @param string $pem pem form cert - * @return string der form cert - */ - public static function x509_pem2der($pem): string|false { - $x509_der = false; - if($x509_res = @openssl_x509_read($pem)) { - openssl_x509_export ($x509_res, $x509_pem); - $arr_x509_pem = explode("\n", (string) $x509_pem); - $numarr = count($arr_x509_pem); - $i = 0; - $cert_pem = false; - foreach($arr_x509_pem as $val) { - if($i > 0 && $i < ($numarr - 2)) { - $cert_pem .= $val; - } - $i++; - } - $x509_der = base64_decode($cert_pem, true); - } - return $x509_der; - } - - /** - * Convert x509 der certificate to x509 pem form - * @param string $der_cert der form cert - * @return string pem form cert - */ - public static function x509_der2pem($der_cert): string { - $x509_pem = "-----BEGIN CERTIFICATE-----\r\n"; - $x509_pem .= chunk_split(base64_encode($der_cert), 64); - $x509_pem .= "-----END CERTIFICATE-----\r\n"; - return $x509_pem; - } - - /** - * get x.509 DER/PEM Certificate and return DER encoded x.509 Certificate - * @param string $certin pem/der form cert - * @return string der form cert - */ - public static function get_cert($certin): string|false { - if($rsccert = @openssl_x509_read ($certin)) { - openssl_x509_export ($rsccert, $cert); - return self::x509_pem2der($cert); - } else { - $pem = @self::x509_der2pem($certin); - if($rsccert = @openssl_x509_read ($pem)) { - openssl_x509_export ($rsccert, $cert); - return self::x509_pem2der($cert); - } else { - return false; - } + + return $ocsp; } - } - - /** - * parse x.509 DER/PEM Certificate structure - * @param string $certin pem/der form cert - * @param string $oidprint show oid as oid number or hex - * @return array cert structure - */ - public static function readcert($cert_in, $oidprint = false) { - if(! $der = self::get_cert($cert_in)) { - return false; + + /** + * Create ocsp request + * + * @param string $serialNumber serial number to check + * @param string $issuerNameHash sha1 hex form of issuer subject hash + * @param string $issuerKeyHash sha1 hex form of issuer subject public info hash + * @param string $signer_cert cert to sign ocsp request + * @param string $signer_key privkey to sign ocsp request + * @param string $subjectName hex form of asn1 subject + * + * @return string hex form ocsp request + */ + public static function ocsp_request($serialNumber, $issuerNameHash, $issuerKeyHash, $signer_cert = false, $signer_key = false, $subjectName = false) + { + $Request = false; + $hashAlgorithm = asn1::seq( + "06052B0E03021A" . // OBJ_sha1 + "0500" + ); + $issuerNameHash = asn1::oct($issuerNameHash); + $issuerKeyHash = asn1::oct($issuerKeyHash); + $serialNumber = asn1::int($serialNumber); + $CertID = asn1::seq($hashAlgorithm . $issuerNameHash . $issuerKeyHash . $serialNumber); + $Request = asn1::seq($CertID); // one request + if ($signer_cert) { + $requestorName = asn1::expl("1", asn1::expl("4", $subjectName)); + } else { + $requestorName = false; + } + $requestList = asn1::seq($Request); // add more request into sequence + $rand = microtime(true) * random_int(0, mt_getrandmax()); + $nonce = md5(base64_encode($rand) . $rand); + $ReqExts = asn1::seq( + '06092B0601050507300102' . // OBJ_id_pkix_OCSP_Nonce + asn1::oct("0410" . $nonce) + ); + $requestExtensions = asn1::expl("2", asn1::seq($ReqExts)); + $TBSRequest = asn1::seq($requestorName . $requestList . $requestExtensions); + $optionalSignature = ''; + if ($signer_cert) { + if (! openssl_sign(hex2bin($TBSRequest), $signature_value, $signer_key)) { + return false; + } + $signatureAlgorithm = asn1::seq( + '06092A864886F70D010105' . // OBJ_sha1WithRSAEncryption. + "0500" + ); + $signature = asn1::bit("00" . bin2hex((string) $signature_value)); + $signer_cert = x509::x509_pem2der($signer_cert); + $certs = asn1::expl("0", asn1::seq(bin2hex($signer_cert))); + $optionalSignature = asn1::expl("0", asn1::seq($signatureAlgorithm . $signature . $certs)); + } + $OCSPRequest = asn1::seq($TBSRequest . $optionalSignature); + + return $OCSPRequest; } - $hex = bin2hex($der); - $curr = asn1::parse($hex, 10); - foreach($curr as $key => $value) { - if($value['type'] == '30') { - $curr['cert'] = $curr[$key]; - unset($curr[$key]); - } + + /** + * Convert crl from pem to der (binary) + * + * @param string $crl pem crl to convert + * + * @return string der crl form + */ + public static function crl_pem2der($crl): false|string + { + $begin = '-----BEGIN X509 CRL-----'; + $end = '-----END X509 CRL-----'; + $beginPos = stripos($crl, $begin); + if ($beginPos === false) { + return false; + } + $crl = substr($crl, $beginPos + strlen($begin)); + $endPos = stripos($crl, $end); + if ($endPos === false) { + return false; + } + $crl = substr($crl, 0, $endPos); + $crl = str_replace("\n", "", $crl); + $crl = str_replace("\r", "", $crl); + $dercrl = base64_decode($crl, true); + + return $dercrl; } - $ar = $curr; - $curr = $ar['cert']; - foreach($curr as $key => $value) { - if(is_numeric($key)) { - if($value['type'] == '30' && ! array_key_exists('tbsCertificate', $curr)) { - $curr['tbsCertificate'] = $curr[$key]; - unset($curr[$key]); - } - if($value['type'] == '30') { - $curr['signatureAlgorithm'] = self::oidfromhex($value[0]['value_hex']); - unset($curr[$key]); - } - if($value['type'] == '03') { - $curr['signatureValue'] = substr((string) $value['value'], 2); - unset($curr[$key]); - } - } else { - unset($curr[$key]); - } + + /** + * Read crl from pem or der (binary) + * + * @param string $crl pem or der crl + * + * @return array der crl and parsed crl + */ + public static function crl_read($crl): false|array + { + if (! $crlparse = self::parsecrl($crl)) { // if cant read, thats not crl + return false; + } + if (! $dercrl = self::crl_pem2der($crl)) { // if not pem, thats already der + $dercrl = $crl; + } + $res['der'] = $dercrl; + $res['parse'] = $crlparse; + + return $res; } - $ar['cert'] = $curr; - $ar['cert']['sha1Fingerprint'] = hash('sha1', $der); - $curr = $ar['cert']['tbsCertificate']; - $i = 0; - foreach($curr as $key => $value) { - if(is_numeric($key)) { - if($value['type'] == 'a0') { - $curr['version'] = $value[0]['value']; - unset($curr[$key]); - } - if($value['type'] == '02') { - $curr['serialNumber'] = $value['value']; - unset($curr[$key]); - } - if($value['type'] == '30' && ! array_key_exists('signature', $curr)) { - $curr['signature'] = $value[0]['value_hex']; - unset($curr[$key]); - continue; - } - if($value['type'] == '30' && ! array_key_exists('issuer', $curr)) { - foreach($value as $issuerK => $issuerV) { - if(is_numeric($issuerK)) { - $issuerOID = $issuerV[0][0]['value_hex']; - if($oidprint == 'oid') { - $issuerOID = self::oidfromhex($issuerOID); - } elseif($oidprint == 'hex') { - } else { - $issuerOID = self::oidfromhex($issuerOID); - } - $issuer[$issuerOID][] = hex2bin((string) $issuerV[0][1]['value_hex']); - } - } - $hexdump = $value['hexdump']; - $issuer['sha1'] = hash('sha1', hex2bin((string) $hexdump)); - $issuer['opensslHash'] = self::opensslSubjHash($hexdump); - $issuer['hexdump'] = $hexdump; - $curr['issuer'] = $issuer; - unset($curr[$key]); - continue; - } - if($value['type'] == '30' && ! array_key_exists('validity', $curr)) { - $curr['validity']['notBefore'] = hex2bin((string) $value[0]['value_hex']); - $curr['validity']['notAfter'] = hex2bin((string) $value[1]['value_hex']); - unset($curr[$key]); - continue; - } - if($value['type'] == '30' && ! array_key_exists('subject', $curr)) { - $asn1SubjectToHash = ''; - foreach($value as $subjectK => $subjectV) { - if(is_numeric($subjectK)) { - $subjectOID = $subjectV[0][0]['value_hex']; - if($oidprint == 'oid') { - $subjectOID = self::oidfromhex($subjectOID); - } elseif($oidprint == 'hex') { - } else { - $subjectOID = self::oidfromhex($subjectOID); - } - $subject[$subjectOID][] = hex2bin((string) $subjectV[0][1]['value_hex']); + + /** + * parsing crl from pem or der (binary) + * + * @param string $crl pem or der crl + * @param string $oidprint option show obj as hex/oid + * + * @return array parsed crl + */ + private static function parsecrl(array $crl, $oidprint = false) + { + if ($derCrl = self::crl_pem2der($crl)) { + $derCrl = bin2hex($derCrl); + } else { + $derCrl = bin2hex($crl); + } + $curr = asn1::parse($derCrl, 7); + foreach ($curr as $key => $value) { + if ($value['type'] == '30') { + $curr['crl'] = $curr[$key]; + unset($curr[$key]); } - } - $hexdump = $value['hexdump']; - $subject['sha1'] = hash('sha1', hex2bin((string) $hexdump)); - $subject['opensslHash'] = self::opensslSubjHash($hexdump); - $subject['hexdump'] = $hexdump; - $curr['subject'] = $subject; - unset($curr[$key]); - continue; - } - if($value['type'] == '30' && ! array_key_exists('subjectPublicKeyInfo', $curr)) { - foreach($value as $subjectPublicKeyInfoK => $subjectPublicKeyInfoV) { - if(is_numeric($subjectPublicKeyInfoK)) { - if($subjectPublicKeyInfoV['type'] == '30') { - $subjectPublicKeyInfo['algorithm'] = self::oidfromhex($subjectPublicKeyInfoV[0]['value_hex']); - } - if($subjectPublicKeyInfoV['type'] == '03') { - $subjectPublicKeyInfo['subjectPublicKey'] = substr((string) $subjectPublicKeyInfoV['value'], 2); - } + } + $ar = $curr; + if (! array_key_exists('crl', $ar)) { + return false; + } + $curr = $ar['crl']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '30' && ! array_key_exists('TBSCertList', $curr)) { + $curr['TBSCertList'] = $curr[$key]; + unset($curr[$key]); + } + if ($value['type'] == '30') { + $curr['signatureAlgorithm'] = self::oidfromhex($value[0]['value_hex']); + unset($curr[$key]); + } + if ($value['type'] == '03') { + $curr['signature'] = substr((string) $value['value'], 2); + unset($curr[$key]); + } } else { - unset($curr[$key]); + unset($curr[$key]); } - } - $subjectPublicKeyInfo['hex'] = $value['hexdump']; - $subjectPublicKey_parse = asn1::parse($subjectPublicKeyInfo['subjectPublicKey']); - $subjectPublicKeyInfo['keyLength'] = (strlen(substr((string) $subjectPublicKey_parse[0][0]['value'], 2)) / 2) * 8; - $subjectPublicKeyInfo['sha1'] = hash('sha1', pack('H*', $subjectPublicKeyInfo['subjectPublicKey'])); - $curr['subjectPublicKeyInfo'] = $subjectPublicKeyInfo; - unset($curr[$key]); - continue; - } - if($value['type'] == 'a3') { - $curr['attributes'] = $value[0]; - unset($curr[$key]); - } - $i++; - } else { - $tbsCertificateTag[$key] = $value; - } - } - $ar['cert']['tbsCertificate'] = $curr; - if(array_key_exists('attributes', $ar['cert']['tbsCertificate'])) { - $curr = $ar['cert']['tbsCertificate']['attributes']; - foreach($curr as $key => $value) { - if(is_numeric($key)) { - if($value['type'] == '30') { - $critical = 0; - $extvalue = $value[1]; - $name_hex = $value[0]['value_hex']; - $value_hex = $value[1]['hexdump']; - if($value[1]['type'] == '01' && $value[1]['value_hex'] == 'ff') { - $critical = 1; - $extvalue = $value[2]; + } + $ar['crl'] = $curr; + $curr = $ar['crl']['TBSCertList']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '02') { + $curr['version'] = $curr[$key]['value']; + unset($curr[$key]); + } + if ($value['type'] == '30' && ! array_key_exists('signature', $curr)) { + $curr['signature'] = $value[0]['value_hex']; + unset($curr[$key]); + continue; + } + if ($value['type'] == '30' && ! array_key_exists('issuer', $curr)) { + $curr['issuer'] = $value; + unset($curr[$key]); + continue; + } + if ($value['type'] == '17' && ! array_key_exists('thisUpdate', $curr)) { + $curr['thisUpdate'] = hex2bin((string) $value['value_hex']); + unset($curr[$key]); + continue; + } + if ($value['type'] == '17' && ! array_key_exists('nextUpdate', $curr)) { + $curr['nextUpdate'] = hex2bin((string) $value['value_hex']); + unset($curr[$key]); + continue; + } + if ($value['type'] == '30' && ! array_key_exists('revokedCertificates', $curr)) { + $curr['revokedCertificates'] = $value; + unset($curr[$key]); + continue; + } + if ($value['type'] == 'a0') { + $curr['crlExtensions'] = $curr[$key]; + unset($curr[$key]); + } + } else { + unset($curr[$key]); } - if($name_hex == '551d0e') { // OBJ_subject_key_identifier - $extvalue = $value[1][0]['value_hex']; + } + $ar['crl']['TBSCertList'] = $curr; + if (array_key_exists('revokedCertificates', $curr)) { + $curr = $ar['crl']['TBSCertList']['revokedCertificates']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '30') { + $serial = $value[0]['value']; + $revoked['time'] = hex2bin((string) $value[1]['value_hex']); + $lists[$serial] = $revoked; + unset($curr[$key]); + } + } else { + unset($curr['depth']); + unset($curr['type']); + unset($curr['typeName']); + } } - if($name_hex == '551d23') { // OBJ_authority_key_identifier - foreach($value[1][0] as $OBJ_authority_key_identifierKey => $OBJ_authority_key_identifierVal) { - if(is_numeric($OBJ_authority_key_identifierKey)) { - if($OBJ_authority_key_identifierVal['type'] == '80') { - $OBJ_authority_key_identifier['keyid'] = $OBJ_authority_key_identifierVal['value_hex']; - } - if($OBJ_authority_key_identifierVal['type'] == 'a1') { - $OBJ_authority_key_identifier['issuerName'] = $OBJ_authority_key_identifierVal['value_hex']; - } - if($OBJ_authority_key_identifierVal['type'] == '82') { - $OBJ_authority_key_identifier['issuerSerial'] = $OBJ_authority_key_identifierVal['value_hex']; - } - } - } - $extvalue = $OBJ_authority_key_identifier; + $curr['lists'] = $lists; + $ar['crl']['TBSCertList']['revokedCertificates'] = $curr; + } + if (array_key_exists('crlExtensions', $ar['crl']['TBSCertList'])) { + $curr = $ar['crl']['TBSCertList']['crlExtensions'][0]; + unset($ar['crl']['TBSCertList']['crlExtensions']); + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + $attributes_name = self::oidfromhex($value[0]['value_hex']); + if ($oidprint == 'oid') { + $attributes_name = self::oidfromhex($value[0]['value_hex']); + } + if ($oidprint == 'hex') { + $attributes_name = $value[0]['value_hex']; + } + $attributes_oid = self::oidfromhex($value[0]['value_hex']); + if ($value['type'] == '30') { + $crlExtensionsValue = $value[1][0]; + if ($attributes_oid == '2.5.29.20') { // OBJ_crl_number + $crlExtensionsValue = $crlExtensionsValue['value']; + } + if ($attributes_oid == '2.5.29.35') { // OBJ_authority_key_identifier + foreach ($crlExtensionsValue as $authority_key_identifierValueK => $authority_key_identifierV) { + if (is_numeric($authority_key_identifierValueK)) { + if ($authority_key_identifierV['type'] == '80') { + $authority_key_identifier['keyIdentifier'] = $authority_key_identifierV['value_hex']; + } + if ($authority_key_identifierV['type'] == 'a1') { + $authority_key_identifier['authorityCertIssuer'] = $authority_key_identifierV['value_hex']; + } + if ($authority_key_identifierV['type'] == '82') { + $authority_key_identifier['authorityCertSerialNumber'] = $authority_key_identifierV['value_hex']; + } + } + } + $crlExtensionsValue = $authority_key_identifier; + } + $attribute_list = $crlExtensionsValue; + } + $ar['crl']['TBSCertList']['crlExtensions'][$attributes_name] = $attribute_list; + } } - if($name_hex == '2b06010505070101') { // OBJ_info_access - foreach($value[1][0] as $OBJ_info_accessK => $OBJ_info_accessV) { - if(is_numeric($OBJ_info_accessK)) { - $OBJ_info_accessHEX = $OBJ_info_accessV[0]['value_hex']; - $OBJ_info_accessOID = self::oidfromhex($OBJ_info_accessHEX); - $OBJ_info_accessNAME = $OBJ_info_accessOID; - $OBJ_info_access[$OBJ_info_accessNAME][] = hex2bin((string) $OBJ_info_accessV[1]['value_hex']); - } - } - $extvalue = $OBJ_info_access; + } + $curr = $ar['crl']['TBSCertList']['issuer']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '31') { + if ($oidprint == 'oid') { + $subjOID = self::oidfromhex($curr[$key][0][0]['value_hex']); + } elseif ($oidprint == 'hex') { + $subjOID = $curr[$key][0][0]['value_hex']; + } else { + $subjOID = self::oidfromhex($curr[$key][0][0]['value_hex']); + } + $curr[$subjOID][] = hex2bin((string) $curr[$key][0][1]['value_hex']); + unset($curr[$key]); + } + } else { + unset($curr['depth']); + unset($curr['type']); + unset($curr['typeName']); + if ($key == 'hexdump') { + $curr['sha1'] = hash('sha1', pack("H*", $value)); + } } - if($name_hex == '551d1f') { // OBJ_crl_distribution_points 551d1f - foreach($value[1][0] as $OBJ_crl_distribution_pointsK => $OBJ_crl_distribution_pointsV) { - if(is_numeric($OBJ_crl_distribution_pointsK)) { - $OBJ_crl_distribution_points[] = hex2bin((string) $OBJ_crl_distribution_pointsV[0][0][0]['value_hex']); + } + $ar['crl']['TBSCertList']['issuer'] = $curr; + $arrModel['TBSCertList']['version'] = ''; + $arrModel['TBSCertList']['signature'] = ''; + $arrModel['TBSCertList']['issuer'] = ''; + $arrModel['TBSCertList']['thisUpdate'] = ''; + $arrModel['TBSCertList']['nextUpdate'] = ''; + $arrModel['signatureAlgorithm'] = ''; + $arrModel['signature'] = ''; + $crl = $ar['crl']; + $differ = array_diff_key($arrModel, $crl); + if (count($differ) == 0) { + $differ = array_diff_key($arrModel['TBSCertList'], $crl['TBSCertList']); + if (count($differ) > 0) { + foreach ($differ as $key => $val) { } - } - $extvalue = $OBJ_crl_distribution_points; + + return false; } - if($name_hex == '551d0f') { // OBJ_key_usage - // $extvalue = self::parse_keyUsage($extvalue[0]['value']); + } else { + foreach ($differ as $key => $val) { } - if($name_hex == '551d13') { // OBJ_basic_constraints - $bc['ca'] = '0'; - $bc['pathLength'] = ''; - foreach($extvalue[0] as $bck => $bcv) { - if(is_numeric($bck)) { - if($bcv['type'] == '01') { - if($bcv['value_hex'] == 'ff') { - $bc['ca'] = '1'; - } - } - if($bcv['type'] == '02') { - $bc['pathLength'] = $bcv['value']; - } + + return false; + } + + return $ar['crl']; + } + + /** + * Convert x509 pem certificate to x509 der + * + * @param string $pem pem form cert + * + * @return string der form cert + */ + public static function x509_pem2der($pem): string|false + { + $x509_der = false; + if ($x509_res = @openssl_x509_read($pem)) { + openssl_x509_export($x509_res, $x509_pem); + $arr_x509_pem = explode("\n", (string) $x509_pem); + $numarr = count($arr_x509_pem); + $i = 0; + $cert_pem = false; + foreach ($arr_x509_pem as $val) { + if ($i > 0 && $i < ($numarr - 2)) { + $cert_pem .= $val; } - } - $extvalue = $bc; - } - if($name_hex == '551d25') { // OBJ_ext_key_usage 551d1f - foreach($extvalue[0] as $OBJ_ext_key_usageK => $OBJ_ext_key_usageV) { - if(is_numeric($OBJ_ext_key_usageK)) { - $OBJ_ext_key_usageHEX = $OBJ_ext_key_usageV['value_hex']; - $OBJ_ext_key_usageOID = self::oidfromhex($OBJ_ext_key_usageHEX); - $OBJ_ext_key_usageNAME = $OBJ_ext_key_usageOID; - $OBJ_ext_key_usage[] = $OBJ_ext_key_usageNAME; - } - } - $extvalue = $OBJ_ext_key_usage; + $i++; } - $extsVal = [ - 'name_hex' => $value[0]['value_hex'], - 'name_oid' => self::oidfromhex($value[0]['value_hex']), - 'name' => self::oidfromhex($value[0]['value_hex']), - 'critical' => $critical, - 'value' => $extvalue, - ]; - $extNameOID = $value[0]['value_hex']; - if($oidprint == 'oid') { - $extNameOID = self::oidfromhex($extNameOID); - } elseif($oidprint == 'hex') { + $x509_der = base64_decode($cert_pem, true); + } + + return $x509_der; + } + + /** + * Convert x509 der certificate to x509 pem form + * + * @param string $der_cert der form cert + * + * @return string pem form cert + */ + public static function x509_der2pem($der_cert): string + { + $x509_pem = "-----BEGIN CERTIFICATE-----\r\n"; + $x509_pem .= chunk_split(base64_encode($der_cert), 64); + $x509_pem .= "-----END CERTIFICATE-----\r\n"; + + return $x509_pem; + } + + /** + * get x.509 DER/PEM Certificate and return DER encoded x.509 Certificate + * + * @param string $certin pem/der form cert + * + * @return string der form cert + */ + public static function get_cert($certin): string|false + { + if ($rsccert = @openssl_x509_read($certin)) { + openssl_x509_export($rsccert, $cert); + + return self::x509_pem2der($cert); + } else { + $pem = @self::x509_der2pem($certin); + if ($rsccert = @openssl_x509_read($pem)) { + openssl_x509_export($rsccert, $cert); + + return self::x509_pem2der($cert); } else { - $extNameOID = self::oidfromhex($extNameOID); + return false; } - $curr[$extNameOID] = $extsVal; - unset($curr[$key]); - } - } else { - unset($curr[$key]); } - unset($ar['cert']['tbsCertificate']['attributes']); - $ar['cert']['tbsCertificate']['attributes'] = $curr; - } } - return $ar['cert']; - } - - /** - * read oid number of given hex (convert hex to oid) - * @param string $hex hex form oid number - * @return string oid number - */ - private static function oidfromhex($hex): string { - $split = str_split($hex, 2); - $i = 0; - foreach($split as $val) { - $dec = hexdec($val); - $mplx[$i] = ($dec - 128) * 128; - $i++; + + /** + * parse x.509 DER/PEM Certificate structure + * + * @param string $certin pem/der form cert + * @param string $oidprint show oid as oid number or hex + * + * @return array cert structure + */ + public static function readcert($cert_in, $oidprint = false) + { + if (! $der = self::get_cert($cert_in)) { + return false; + } + $hex = bin2hex($der); + $curr = asn1::parse($hex, 10); + foreach ($curr as $key => $value) { + if ($value['type'] == '30') { + $curr['cert'] = $curr[$key]; + unset($curr[$key]); + } + } + $ar = $curr; + $curr = $ar['cert']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '30' && ! array_key_exists('tbsCertificate', $curr)) { + $curr['tbsCertificate'] = $curr[$key]; + unset($curr[$key]); + } + if ($value['type'] == '30') { + $curr['signatureAlgorithm'] = self::oidfromhex($value[0]['value_hex']); + unset($curr[$key]); + } + if ($value['type'] == '03') { + $curr['signatureValue'] = substr((string) $value['value'], 2); + unset($curr[$key]); + } + } else { + unset($curr[$key]); + } + } + $ar['cert'] = $curr; + $ar['cert']['sha1Fingerprint'] = hash('sha1', $der); + $curr = $ar['cert']['tbsCertificate']; + $i = 0; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == 'a0') { + $curr['version'] = $value[0]['value']; + unset($curr[$key]); + } + if ($value['type'] == '02') { + $curr['serialNumber'] = $value['value']; + unset($curr[$key]); + } + if ($value['type'] == '30' && ! array_key_exists('signature', $curr)) { + $curr['signature'] = $value[0]['value_hex']; + unset($curr[$key]); + continue; + } + if ($value['type'] == '30' && ! array_key_exists('issuer', $curr)) { + foreach ($value as $issuerK => $issuerV) { + if (is_numeric($issuerK)) { + $issuerOID = $issuerV[0][0]['value_hex']; + if ($oidprint == 'oid') { + $issuerOID = self::oidfromhex($issuerOID); + } elseif ($oidprint == 'hex') { + } else { + $issuerOID = self::oidfromhex($issuerOID); + } + $issuer[$issuerOID][] = hex2bin((string) $issuerV[0][1]['value_hex']); + } + } + $hexdump = $value['hexdump']; + $issuer['sha1'] = hash('sha1', hex2bin((string) $hexdump)); + $issuer['opensslHash'] = self::opensslSubjHash($hexdump); + $issuer['hexdump'] = $hexdump; + $curr['issuer'] = $issuer; + unset($curr[$key]); + continue; + } + if ($value['type'] == '30' && ! array_key_exists('validity', $curr)) { + $curr['validity']['notBefore'] = hex2bin((string) $value[0]['value_hex']); + $curr['validity']['notAfter'] = hex2bin((string) $value[1]['value_hex']); + unset($curr[$key]); + continue; + } + if ($value['type'] == '30' && ! array_key_exists('subject', $curr)) { + $asn1SubjectToHash = ''; + foreach ($value as $subjectK => $subjectV) { + if (is_numeric($subjectK)) { + $subjectOID = $subjectV[0][0]['value_hex']; + if ($oidprint == 'oid') { + $subjectOID = self::oidfromhex($subjectOID); + } elseif ($oidprint == 'hex') { + } else { + $subjectOID = self::oidfromhex($subjectOID); + } + $subject[$subjectOID][] = hex2bin((string) $subjectV[0][1]['value_hex']); + } + } + $hexdump = $value['hexdump']; + $subject['sha1'] = hash('sha1', hex2bin((string) $hexdump)); + $subject['opensslHash'] = self::opensslSubjHash($hexdump); + $subject['hexdump'] = $hexdump; + $curr['subject'] = $subject; + unset($curr[$key]); + continue; + } + if ($value['type'] == '30' && ! array_key_exists('subjectPublicKeyInfo', $curr)) { + foreach ($value as $subjectPublicKeyInfoK => $subjectPublicKeyInfoV) { + if (is_numeric($subjectPublicKeyInfoK)) { + if ($subjectPublicKeyInfoV['type'] == '30') { + $subjectPublicKeyInfo['algorithm'] = self::oidfromhex($subjectPublicKeyInfoV[0]['value_hex']); + } + if ($subjectPublicKeyInfoV['type'] == '03') { + $subjectPublicKeyInfo['subjectPublicKey'] = substr((string) $subjectPublicKeyInfoV['value'], 2); + } + } else { + unset($curr[$key]); + } + } + $subjectPublicKeyInfo['hex'] = $value['hexdump']; + $subjectPublicKey_parse = asn1::parse($subjectPublicKeyInfo['subjectPublicKey']); + $subjectPublicKeyInfo['keyLength'] = (strlen(substr((string) $subjectPublicKey_parse[0][0]['value'], 2)) / 2) * 8; + $subjectPublicKeyInfo['sha1'] = hash('sha1', pack('H*', $subjectPublicKeyInfo['subjectPublicKey'])); + $curr['subjectPublicKeyInfo'] = $subjectPublicKeyInfo; + unset($curr[$key]); + continue; + } + if ($value['type'] == 'a3') { + $curr['attributes'] = $value[0]; + unset($curr[$key]); + } + $i++; + } else { + $tbsCertificateTag[$key] = $value; + } + } + $ar['cert']['tbsCertificate'] = $curr; + if (array_key_exists('attributes', $ar['cert']['tbsCertificate'])) { + $curr = $ar['cert']['tbsCertificate']['attributes']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '30') { + $critical = 0; + $extvalue = $value[1]; + $name_hex = $value[0]['value_hex']; + $value_hex = $value[1]['hexdump']; + if ($value[1]['type'] == '01' && $value[1]['value_hex'] == 'ff') { + $critical = 1; + $extvalue = $value[2]; + } + if ($name_hex == '551d0e') { // OBJ_subject_key_identifier + $extvalue = $value[1][0]['value_hex']; + } + if ($name_hex == '551d23') { // OBJ_authority_key_identifier + foreach ($value[1][0] as $OBJ_authority_key_identifierKey => $OBJ_authority_key_identifierVal) { + if (is_numeric($OBJ_authority_key_identifierKey)) { + if ($OBJ_authority_key_identifierVal['type'] == '80') { + $OBJ_authority_key_identifier['keyid'] = $OBJ_authority_key_identifierVal['value_hex']; + } + if ($OBJ_authority_key_identifierVal['type'] == 'a1') { + $OBJ_authority_key_identifier['issuerName'] = $OBJ_authority_key_identifierVal['value_hex']; + } + if ($OBJ_authority_key_identifierVal['type'] == '82') { + $OBJ_authority_key_identifier['issuerSerial'] = $OBJ_authority_key_identifierVal['value_hex']; + } + } + } + $extvalue = $OBJ_authority_key_identifier; + } + if ($name_hex == '2b06010505070101') { // OBJ_info_access + foreach ($value[1][0] as $OBJ_info_accessK => $OBJ_info_accessV) { + if (is_numeric($OBJ_info_accessK)) { + $OBJ_info_accessHEX = $OBJ_info_accessV[0]['value_hex']; + $OBJ_info_accessOID = self::oidfromhex($OBJ_info_accessHEX); + $OBJ_info_accessNAME = $OBJ_info_accessOID; + $OBJ_info_access[$OBJ_info_accessNAME][] = hex2bin((string) $OBJ_info_accessV[1]['value_hex']); + } + } + $extvalue = $OBJ_info_access; + } + if ($name_hex == '551d1f') { // OBJ_crl_distribution_points 551d1f + foreach ($value[1][0] as $OBJ_crl_distribution_pointsK => $OBJ_crl_distribution_pointsV) { + if (is_numeric($OBJ_crl_distribution_pointsK)) { + $OBJ_crl_distribution_points[] = hex2bin((string) $OBJ_crl_distribution_pointsV[0][0][0]['value_hex']); + } + } + $extvalue = $OBJ_crl_distribution_points; + } + if ($name_hex == '551d0f') { // OBJ_key_usage + // $extvalue = self::parse_keyUsage($extvalue[0]['value']); + } + if ($name_hex == '551d13') { // OBJ_basic_constraints + $bc['ca'] = '0'; + $bc['pathLength'] = ''; + foreach ($extvalue[0] as $bck => $bcv) { + if (is_numeric($bck)) { + if ($bcv['type'] == '01') { + if ($bcv['value_hex'] == 'ff') { + $bc['ca'] = '1'; + } + } + if ($bcv['type'] == '02') { + $bc['pathLength'] = $bcv['value']; + } + } + } + $extvalue = $bc; + } + if ($name_hex == '551d25') { // OBJ_ext_key_usage 551d1f + foreach ($extvalue[0] as $OBJ_ext_key_usageK => $OBJ_ext_key_usageV) { + if (is_numeric($OBJ_ext_key_usageK)) { + $OBJ_ext_key_usageHEX = $OBJ_ext_key_usageV['value_hex']; + $OBJ_ext_key_usageOID = self::oidfromhex($OBJ_ext_key_usageHEX); + $OBJ_ext_key_usageNAME = $OBJ_ext_key_usageOID; + $OBJ_ext_key_usage[] = $OBJ_ext_key_usageNAME; + } + } + $extvalue = $OBJ_ext_key_usage; + } + $extsVal = [ + 'name_hex' => $value[0]['value_hex'], + 'name_oid' => self::oidfromhex($value[0]['value_hex']), + 'name' => self::oidfromhex($value[0]['value_hex']), + 'critical' => $critical, + 'value' => $extvalue, + ]; + $extNameOID = $value[0]['value_hex']; + if ($oidprint == 'oid') { + $extNameOID = self::oidfromhex($extNameOID); + } elseif ($oidprint == 'hex') { + } else { + $extNameOID = self::oidfromhex($extNameOID); + } + $curr[$extNameOID] = $extsVal; + unset($curr[$key]); + } + } else { + unset($curr[$key]); + } + unset($ar['cert']['tbsCertificate']['attributes']); + $ar['cert']['tbsCertificate']['attributes'] = $curr; + } + } + + return $ar['cert']; } - $i = 0; - $nex = false; - $result = false; - foreach($split as $val) { - $dec = hexdec($val); - if($i == 0) { - if($dec >= 128) { - $nex = (128 * ($dec - 128)) - 80; - if($dec > 129) { - $nex = (128 * ($dec - 128)) - 80; - } - $result = "2."; - } - if($dec >= 80 && $dec < 128) { - $first = $dec - 80; - $result = "2.$first."; - } - if($dec >= 40 && $dec < 80) { - $first = $dec - 40; - $result = "1.$first."; - } - if($dec < 40) { - $first = $dec - 0; - $result = "0.$first."; - } - } else { - if($dec > 127) { - if($nex == false) { - $nex = $mplx[$i]; - } else { - $nex = ($nex * 128) + $mplx[$i]; - } - } else { - $result .= ($dec + $nex) . "."; - if($dec <= 127) { - $nex = 0; - } + + /** + * read oid number of given hex (convert hex to oid) + * + * @param string $hex hex form oid number + * + * @return string oid number + */ + private static function oidfromhex($hex): string + { + $split = str_split($hex, 2); + $i = 0; + foreach ($split as $val) { + $dec = hexdec($val); + $mplx[$i] = ($dec - 128) * 128; + $i++; } - } - $i++; + $i = 0; + $nex = false; + $result = false; + foreach ($split as $val) { + $dec = hexdec($val); + if ($i == 0) { + if ($dec >= 128) { + $nex = (128 * ($dec - 128)) - 80; + if ($dec > 129) { + $nex = (128 * ($dec - 128)) - 80; + } + $result = "2."; + } + if ($dec >= 80 && $dec < 128) { + $first = $dec - 80; + $result = "2.$first."; + } + if ($dec >= 40 && $dec < 80) { + $first = $dec - 40; + $result = "1.$first."; + } + if ($dec < 40) { + $first = $dec - 0; + $result = "0.$first."; + } + } else { + if ($dec > 127) { + if ($nex == false) { + $nex = $mplx[$i]; + } else { + $nex = ($nex * 128) + $mplx[$i]; + } + } else { + $result .= ($dec + $nex) . "."; + if ($dec <= 127) { + $nex = 0; + } + } + } + $i++; + } + + return rtrim($result, "."); } - return rtrim($result, "."); - } } diff --git a/src/pdfvalue/PDFValue.php b/src/pdfvalue/PDFValue.php index b836ef7..3f8cda2 100644 --- a/src/pdfvalue/PDFValue.php +++ b/src/pdfvalue/PDFValue.php @@ -20,76 +20,100 @@ */ namespace ddn\sapp\pdfvalue; -use \ArrayAccess; + +use ArrayAccess; use ReturnTypeWillChange; use Stringable; class PDFValue implements ArrayAccess, Stringable { public function __construct( - protected $value - ) - { + protected $value, + ) { } - public function val() { + public function val() + { return $this->value; } - public function __toString(): string { + public function __toString(): string + { return "" . $this->value; } - public function offsetExists ( $offset ): bool { - if (! is_array($this->value)) return false; + public function offsetExists($offset): bool + { + if (! is_array($this->value)) { + return false; + } + return isset($this->value[$offset]); } #[ReturnTypeWillChange] - public function offsetGet ( $offset ) { - if (! is_array($this->value)) return false; - if (! isset($this->value[$offset])) return false; + public function offsetGet($offset) + { + if (! is_array($this->value)) { + return false; + } + if (! isset($this->value[$offset])) { + return false; + } + return $this->value[$offset]; } - public function offsetSet($offset, $value ): void { - if (! is_array($this->value)) return; + public function offsetSet($offset, $value): void + { + if (! is_array($this->value)) { + return; + } $this->value[$offset] = $value; } - public function offsetUnset($offset ): void { - if ((! is_array($this->value)) || (! isset($this->value[$offset]))) + public function offsetUnset($offset): void + { + if ((! is_array($this->value)) || (! isset($this->value[$offset]))) { throw new Exception('invalid offset'); + } unset($this->value[$offset]); } - public function push($v): bool { + public function push($v): bool + { /*if (get_class($v) !== get_class($this)) throw new Exception('invalid object to concat to this one');*/ return false; } - public function get_int(): false|int { + public function get_int(): false|int + { return false; } - public function get_object_referenced(): false|array|int { + public function get_object_referenced(): false|array|int + { return false; } - public function get_keys(): false|array { + public function get_keys(): false|array + { return false; } /** * Returns the difference between this and other object (false means "cannot compare", null means "equal" and any value means "different": things in this object that are different from the other) */ - public function diff($other) { - if (! is_a($other, static::class)) + public function diff($other) + { + if (! is_a($other, static::class)) { return false; + } if ($this->value === $other->value) { return null; } + return $this->value; } @@ -100,36 +124,39 @@ public function diff($other) { * - string without separator (e.g. "\t\n ") are translated into PDFValueSimple * - other strings are translated into PDFValueString * - array is translated into PDFValueList, and its inner elements are also converted. + * * @param value a standard php object (e.g. string, integer, double, array, etc.) + * * @return pdfvalue an object of type PDFValue*, depending on the */ - protected static function _convert($value) { + protected static function _convert($value) + { switch (gettype($value)) { case 'integer': case 'double': $value = new PDFValueSimple($value); break; case 'string': - if ($value[0] === '/') + if ($value[0] === '/') { $value = new PDFValueType(substr($value, 1)); - else - if (preg_match("/\s/ms", $value) === 1) + } else { + if (preg_match("/\s/ms", $value) === 1) { $value = new PDFValueString($value); - else + } else { $value = new PDFValueSimple($value); + } + } break; case 'array': if (count($value) === 0) { // An empty list is assumed to be a list $value = new PDFValueList(); } else { - // Try to parse it as an object (i.e. [ 'Field' => 'Value', ...]) $obj = PDFValueObject::fromarray($value); - if ($obj !== false) + if ($obj !== false) { $value = $obj; - else { - + } else { // If not an object, it is a list $list = []; foreach ($value as $v) { @@ -140,6 +167,7 @@ protected static function _convert($value) { } break; } + return $value; } } diff --git a/src/pdfvalue/PDFValueHexString.php b/src/pdfvalue/PDFValueHexString.php index a6d4c99..6800fb4 100644 --- a/src/pdfvalue/PDFValueHexString.php +++ b/src/pdfvalue/PDFValueHexString.php @@ -21,8 +21,10 @@ namespace ddn\sapp\pdfvalue; -class PDFValueHexString extends PDFValueString { - public function __toString(): string { +class PDFValueHexString extends PDFValueString +{ + public function __toString(): string + { return "<" . trim((string) $this->value) . ">"; } } diff --git a/src/pdfvalue/PDFValueList.php b/src/pdfvalue/PDFValueList.php index 9c9fe3f..40660fa 100644 --- a/src/pdfvalue/PDFValueList.php +++ b/src/pdfvalue/PDFValueList.php @@ -20,30 +20,41 @@ */ namespace ddn\sapp\pdfvalue; -class PDFValueList extends PDFValue { - public function __construct($value = []) { + +class PDFValueList extends PDFValue +{ + public function __construct($value = []) + { parent::__construct($value); } - public function __toString(): string { + public function __toString(): string + { return '[' . implode(' ', $this->value) . ']'; } - public function diff($other): false|null|self { + public function diff($other): false|null|self + { $different = parent::diff($other); - if (($different === false) || ($different === null)) return $different; + if (($different === false) || ($different === null)) { + return $different; + } $s1 = $this->__toString(); $s2 = $other->__toString(); - if ($s1 === $s2) return null; + if ($s1 === $s2) { + return null; + } + return $this; } /** - * This function + * This function */ - public function val($list = false) { + public function val($list = false) + { if ($list === true) { $result = []; foreach ($this->value as $v) { @@ -54,15 +65,18 @@ public function val($list = false) { } array_push($result, ...$v); } + return $result; - } else + } else { return parent::val(); + } } /** * This function returns a list of objects that are referenced in the list, only if all of them are references to objects */ - public function get_object_referenced(): false|array { + public function get_object_referenced(): false|array + { $ids = []; $plain_text_val = implode(' ', $this->value); if (trim($plain_text_val) !== "") { @@ -72,31 +86,38 @@ public function get_object_referenced(): false|array { $plain_text_val = preg_replace('/\s+/ms', ' ', $plain_text_val); if ($plain_text_val === $rebuilt) { // Any content is a reference - foreach ($matches[2] as $id) + foreach ($matches[2] as $id) { array_push($ids, intval($id)); - } - } else + } + } + } else { return false; + } } + return $ids; } /** - * This method pushes the parameter to the list; - * - if it is an array, the list is merged; - * - if it is a list object, the lists are merged; + * This method pushes the parameter to the list; + * - if it is an array, the list is merged; + * - if it is a list object, the lists are merged; * - otherwise the object is converted to a PDFValue* object and it is appended to the list */ - public function push($v): bool { + public function push($v): bool + { if (is_object($v) && ($v::class === static::class)) { // If a list is pushed to another list, the elements are merged $v = $v->val(); } - if (! is_array($v)) $v = [$v]; + if (! is_array($v)) { + $v = [$v]; + } foreach ($v as $e) { $e = self::_convert($e); array_push($this->value, $e); } + return true; } } diff --git a/src/pdfvalue/PDFValueObject.php b/src/pdfvalue/PDFValueObject.php index 4a75a3a..33edb70 100644 --- a/src/pdfvalue/PDFValueObject.php +++ b/src/pdfvalue/PDFValueObject.php @@ -21,8 +21,10 @@ namespace ddn\sapp\pdfvalue; -class PDFValueObject extends PDFValue { - public function __construct($value = []) { +class PDFValueObject extends PDFValue +{ + public function __construct($value = []) + { $result = []; foreach ($value as $k => $v) { $result[$k] = self::_convert($v); @@ -30,9 +32,12 @@ public function __construct($value = []) { parent::__construct($result); } - public function diff($other): false|null|\ddn\sapp\pdfvalue\PDFValueObject { + public function diff($other): false|null|PDFValueObject + { $different = parent::diff($other); - if (($different === false) || ($different === null)) return $different; + if (($different === false) || ($different === null)) { + return $different; + } $result = new PDFValueObject(); $differences = 0; @@ -44,10 +49,11 @@ public function diff($other): false|null|\ddn\sapp\pdfvalue\PDFValueObject { if ($different === false) { $result[$k] = $v; $differences++; - } else - if ($different !== null) { - $result[$k] = $different; - $differences++; + } else { + if ($different !== null) { + $result[$k] = $different; + $differences++; + } } } } else { @@ -55,29 +61,36 @@ public function diff($other): false|null|\ddn\sapp\pdfvalue\PDFValueObject { $differences++; } } - if ($differences === 0) + if ($differences === 0) { return null; + } return $result; } - public static function fromarray($parts): false|\ddn\sapp\pdfvalue\PDFValueObject { + public static function fromarray($parts): false|PDFValueObject + { $k = array_keys($parts); $intkeys = false; $result = []; - foreach ($k as $ck) + foreach ($k as $ck) { if (is_int($ck)) { $intkeys = true; break; } - if ($intkeys) return false; + } + if ($intkeys) { + return false; + } foreach ($parts as $k => $v) { $result[$k] = self::_convert($v); } + return new PDFValueObject($result); } - public static function fromstring($str): false|\ddn\sapp\pdfvalue\PDFValueObject { + public static function fromstring($str): false|PDFValueObject + { $result = []; $field = null; $value = null; @@ -85,10 +98,16 @@ public static function fromstring($str): false|\ddn\sapp\pdfvalue\PDFValueObject for ($i = 0; $i < count($parts); $i++) { if ($field === null) { $field = $parts[$i]; - if ($field === '') return false; - if ($field[0] !== '/') return false; + if ($field === '') { + return false; + } + if ($field[0] !== '/') { + return false; + } $field = substr($field, 1); - if ($field === '') return false; + if ($field === '') { + return false; + } continue; } $value = $parts[$i]; @@ -96,40 +115,51 @@ public static function fromstring($str): false|\ddn\sapp\pdfvalue\PDFValueObject $field = null; } // If there is no pair of values, there is no valid - if ($field !== null) return false; + if ($field !== null) { + return false; + } + return new PDFValueObject($result); } - public function get_keys(): false|array { + public function get_keys(): false|array + { return array_keys($this->value); } /** * Function used to enable using [x] to set values to the fields of the object (from ArrayAccess interface) * i.e. object[offset]=value + * * @param offset the index used inside the braces * @param value the value to set to that index (it will be converted to a PDFValue* object) + * * @return value the value set to the field */ - public function offsetSet($offset, $value): void { + public function offsetSet($offset, $value): void + { if ($value === null) { - if (isset($this->value[$offset])) + if (isset($this->value[$offset])) { unset($this->value[$offset]); + } // return null; } $this->value[$offset] = self::_convert($value); // return $this->value[$offset]; } - public function offsetExists ( $offset ): bool { + public function offsetExists($offset): bool + { return isset($this->value[$offset]); } /** * Function to output the object using the PDF format, and trying to make it compact (by reducing spaces, depending on the values) + * * @return pdfentry the PDF entry for the object */ - public function __toString(): string { + public function __toString(): string + { $result = []; foreach ($this->value as $k => $v) { $v = "" . $v; @@ -142,6 +172,7 @@ public function __toString(): string { default => array_push($result, "/$k $v"), }; } + return "<<" . implode('', $result) . ">>"; } } diff --git a/src/pdfvalue/PDFValueReference.php b/src/pdfvalue/PDFValueReference.php index f13c2ee..4776b02 100644 --- a/src/pdfvalue/PDFValueReference.php +++ b/src/pdfvalue/PDFValueReference.php @@ -24,8 +24,10 @@ /** * Class to create a reference to an object */ -class PDFValueReference extends PDFValueSimple { - public function __construct($oid) { +class PDFValueReference extends PDFValueSimple +{ + public function __construct($oid) + { parent::__construct(sprintf("%d 0 R", $oid)); } -}; +} diff --git a/src/pdfvalue/PDFValueSimple.php b/src/pdfvalue/PDFValueSimple.php index 07d6e61..f119c13 100644 --- a/src/pdfvalue/PDFValueSimple.php +++ b/src/pdfvalue/PDFValueSimple.php @@ -21,29 +21,40 @@ namespace ddn\sapp\pdfvalue; -class PDFValueSimple extends PDFValue { - public function __construct($v) { +class PDFValueSimple extends PDFValue +{ + public function __construct($v) + { parent::__construct($v); } - public function push($v): bool { + public function push($v): bool + { if ($v::class === static::class) { // Can push $this->value = $this->value . ' ' . $v->val(); + return true; } + return false; } - public function get_object_referenced(): false|int { + public function get_object_referenced(): false|int + { if (! preg_match('/^\s*([0-9]+)\s+([0-9]+)\s+R\s*$/ms', (string) $this->value, $matches)) { return false; } + return intval($matches[1]); } - public function get_int(): false|int { - if (! is_numeric($this->value)) return false; + public function get_int(): false|int + { + if (! is_numeric($this->value)) { + return false; + } + return intval($this->value); } -} \ No newline at end of file +} diff --git a/src/pdfvalue/PDFValueString.php b/src/pdfvalue/PDFValueString.php index bfb7563..f1e4ece 100644 --- a/src/pdfvalue/PDFValueString.php +++ b/src/pdfvalue/PDFValueString.php @@ -21,8 +21,10 @@ namespace ddn\sapp\pdfvalue; -class PDFValueString extends PDFValue { - public function __toString(): string { +class PDFValueString extends PDFValue +{ + public function __toString(): string + { return "(" . $this->value . ")"; } -} \ No newline at end of file +} diff --git a/src/pdfvalue/PDFValueType.php b/src/pdfvalue/PDFValueType.php index 6475d22..86aee6d 100644 --- a/src/pdfvalue/PDFValueType.php +++ b/src/pdfvalue/PDFValueType.php @@ -21,8 +21,10 @@ namespace ddn\sapp\pdfvalue; -class PDFValueType extends PDFValue { - public function __toString(): string { +class PDFValueType extends PDFValue +{ + public function __toString(): string + { return "/" . trim((string) $this->value); } } From 2751e450d9c02ce27da1181956e64cfb2c0ba2cc Mon Sep 17 00:00:00 2001 From: Marcin Jakubowski Date: Thu, 31 Oct 2024 20:54:01 +0100 Subject: [PATCH 03/11] mostly automated changes to improve code style --- ecs.php | 2 + src/PDFDoc.php | 1010 +++++++++++++------------- src/PDFDocWithContents.php | 103 +-- src/PDFObject.php | 214 +++--- src/PDFObjectParser.php | 172 ++--- src/PDFSignatureObject.php | 62 +- src/PDFUtilFnc.php | 221 +++--- src/helpers/Buffer.php | 58 +- src/helpers/CMS.php | 566 +++++++-------- src/helpers/DependencyTreeObject.php | 48 +- src/helpers/LoadHelpers.php | 2 +- src/helpers/StreamReader.php | 8 +- src/helpers/asn1.php | 288 ++++---- src/helpers/contentgeneration.php | 29 +- src/helpers/fpdfhelpers.php | 58 +- src/helpers/helpers.php | 36 +- src/helpers/x509.php | 527 +++++++------- src/pdfvalue/PDFValue.php | 8 +- src/pdfvalue/PDFValueHexString.php | 2 +- src/pdfvalue/PDFValueList.php | 12 +- src/pdfvalue/PDFValueObject.php | 58 +- src/pdfvalue/PDFValueReference.php | 2 +- src/pdfvalue/PDFValueString.php | 2 +- src/pdfvalue/PDFValueType.php | 2 +- 24 files changed, 1746 insertions(+), 1744 deletions(-) diff --git a/ecs.php b/ecs.php index dd8d302..90ced41 100644 --- a/ecs.php +++ b/ecs.php @@ -16,6 +16,8 @@ ]) ->withPreparedSets( arrays: true, + controlStructures: true, + psr12: true, comments: true, // docblocks: true, spaces: true, diff --git a/src/PDFDoc.php b/src/PDFDoc.php index d5587c0..8897930 100644 --- a/src/PDFDoc.php +++ b/src/PDFDoc.php @@ -22,17 +22,10 @@ namespace ddn\sapp; use DateTime; -use function ddn\sapp\helpers\_add_image; use ddn\sapp\helpers\Buffer; use ddn\sapp\helpers\CMS; use ddn\sapp\helpers\DependencyTreeObject; -use function ddn\sapp\helpers\get_random_string; use ddn\sapp\helpers\LoadHelpers; -use function ddn\sapp\helpers\p_debug; -use function ddn\sapp\helpers\p_error; -use function ddn\sapp\helpers\p_warning; -use function ddn\sapp\helpers\references_in_object; -use function ddn\sapp\helpers\timestamp_to_pdfdatestring; use ddn\sapp\helpers\UUID; use ddn\sapp\pdfvalue\PDFValueHexString; use ddn\sapp\pdfvalue\PDFValueList; @@ -41,10 +34,17 @@ use ddn\sapp\pdfvalue\PDFValueSimple; use ddn\sapp\pdfvalue\PDFValueString; use Throwable; +use function ddn\sapp\helpers\_add_image; +use function ddn\sapp\helpers\get_random_string; +use function ddn\sapp\helpers\p_debug; +use function ddn\sapp\helpers\p_error; +use function ddn\sapp\helpers\p_warning; +use function ddn\sapp\helpers\references_in_object; +use function ddn\sapp\helpers\timestamp_to_pdfdatestring; // Loading the functions -if (! defined("ddn\\sapp\\helpers\\LoadHelpers")) { +if (! defined('ddn\\sapp\\helpers\\LoadHelpers')) { new LoadHelpers(); } @@ -70,7 +70,7 @@ class PDFDoc extends Buffer protected $_max_oid = 0; - protected $_buffer = ""; + protected $_buffer = ''; protected $_backup_state = []; @@ -101,14 +101,6 @@ class PDFDoc extends Buffer // - size: the size of the page protected $_pages_info = []; - // Gets a new oid for a new object - protected function get_new_oid(): int|float - { - $this->_max_oid++; - - return $this->_max_oid; - } - /** * Retrieve the number of pages in the document (not considered those pages that could be added by the user using this object or derived ones) * @@ -163,32 +155,31 @@ public function pop_state(): bool * otherwise only the object ids from the latest $depth versions will be considered * (if it is an incremental updated document) */ - public static function from_string($buffer, $depth = null): false|PDFDoc + public static function from_string($buffer, $depth = null): false|self { $structure = PDFUtilFnc::acquire_structure($buffer, $depth); if ($structure === false) { return false; } - $trailer = $structure["trailer"]; - $version = $structure["version"]; - $xref_table = $structure["xref"]; - $xref_position = $structure["xrefposition"]; - $revisions = $structure["revisions"]; + $trailer = $structure['trailer']; + $version = $structure['version']; + $xref_table = $structure['xref']; + $xref_position = $structure['xrefposition']; + $revisions = $structure['revisions']; - $pdfdoc = new PDFDoc(); + $pdfdoc = new self(); $pdfdoc->_pdf_version_string = $version; $pdfdoc->_pdf_trailer_object = $trailer; $pdfdoc->_xref_position = $xref_position; $pdfdoc->_xref_table = $xref_table; - $pdfdoc->_xref_table_version = $structure["xrefversion"]; + $pdfdoc->_xref_table_version = $structure['xrefversion']; $pdfdoc->_revisions = $revisions; $pdfdoc->_buffer = $buffer; if ($trailer !== false) { - if ($trailer['Encrypt'] !== false) // TODO: include encryption (maybe borrowing some code: http://www.fpdf.org/en/script/script37.php) - { - p_error("encrypted documents are not fully supported; maybe you cannot get the expected results"); + if ($trailer['Encrypt'] !== false) { // TODO: include encryption (maybe borrowing some code: http://www.fpdf.org/en/script/script37.php) + p_error('encrypted documents are not fully supported; maybe you cannot get the expected results'); } } @@ -197,7 +188,7 @@ public static function from_string($buffer, $depth = null): false|PDFDoc $pdfdoc->_max_oid = array_pop($oids); if ($trailer === false) { - p_warning("invalid trailer object"); + p_warning('invalid trailer object'); } else { $pdfdoc->_acquire_pages_info(); } @@ -324,9 +315,9 @@ public function get_object(int $oid, $original_version = false) public function set_signature_appearance($page_to_appear = 0, $rect_to_appear = [0, 0, 0, 0], $imagefilename = null): void { $this->_appearance = [ - "page" => $page_to_appear, - "rect" => $rect_to_appear, - "image" => $imagefilename, + 'page' => $page_to_appear, + 'rect' => $rect_to_appear, + 'image' => $imagefilename, ]; } @@ -361,13 +352,13 @@ public function set_signature_certificate($certfile, $certpass = null) // First we read the certificate if (is_array($certfile)) { $certificate = $certfile; - $certificate["pkey"] = [$certificate["pkey"], $certpass]; + $certificate['pkey'] = [$certificate['pkey'], $certpass]; // If a password is provided, we'll try to decode the private key - if (openssl_pkey_get_private($certificate["pkey"]) === false) { - return p_error("invalid private key"); + if (openssl_pkey_get_private($certificate['pkey']) === false) { + return p_error('invalid private key'); } - if (! openssl_x509_check_private_key($certificate["cert"], $certificate["pkey"])) { + if (! openssl_x509_check_private_key($certificate['cert'], $certificate['pkey'])) { return p_error("private key doesn't corresponds to certificate"); } @@ -380,10 +371,10 @@ public function set_signature_certificate($certfile, $certpass = null) } else { $certfilecontent = file_get_contents($certfile); if ($certfilecontent === false) { - return p_error("could not read file $certfile"); + return p_error("could not read file {$certfile}"); } if (openssl_pkcs12_read($certfilecontent, $certificate, $certpass) === false) { - return p_error("could not get the certificates from file $certfile"); + return p_error("could not get the certificates from file {$certfile}"); } } @@ -441,299 +432,6 @@ public function set_metadata_props($name = null, $reason = null, $location = nul $this->_metadata_contact_info = $contact; } - /** - * Function that creates and updates the PDF objects needed to sign the document. The workflow for a signature is: - * - create a signature object - * - create an annotation object whose value is the signature object - * - create a form object (along with other objects) that will hold the appearance of the annotation object - * - modify the root object to make acroform point to the annotation object - * - modify the page object to make the annotations of that page include the annotation object - * > If the appearance is not set, the image will not appear, and the signature object will be invisible. - * > If the certificate is not set, the signature created will be a placeholder (that acrobat will able to sign) - * LIMITATIONS: one document can be signed once at a time; if wanted more signatures, then chain the documents: - * $o1->set_signature_certificate(...); - * $o2 = PDFDoc::fromstring($o1->to_pdf_file_s); - * $o2->set_signature_certificate(...); - * $o2->to_pdf_file_s(); - * - * @return signature a signature object, or null if the document is not signed; false if an error happens - */ - protected function _generate_signature_in_document() - { - $imagefilename = null; - $recttoappear = [0, 0, 0, 0]; - $pagetoappear = 0; - - if ($this->_appearance !== null) { - $imagefilename = $this->_appearance["image"]; - $recttoappear = $this->_appearance["rect"]; - $pagetoappear = $this->_appearance["page"]; - } - - // First of all, we are searching for the root object (which should be in the trailer) - $root = $this->_pdf_trailer_object["Root"]; - - if (($root === false) || (($root = $root->get_object_referenced()) === false)) { - return p_error("could not find the root object from the trailer"); - } - - $root_obj = $this->get_object($root); - if ($root_obj === false) { - return p_error("invalid root object"); - } - - // Now the object corresponding to the page number in which to appear - $page_obj = $this->get_page($pagetoappear); - if ($page_obj === false) { - return p_error("invalid page"); - } - - // The objects to update - $updated_objects = []; - - // Add the annotation to the page - if (! isset($page_obj["Annots"])) { - $page_obj["Annots"] = new PDFValueList(); - } - - $annots = &$page_obj["Annots"]; - $page_rotation = $page_obj["Rotate"] ?? new PDFValueSimple(0); - - if ((($referenced = $annots->get_object_referenced()) !== false) && (! is_array($referenced))) { - // It is an indirect object, so we need to update that object - $newannots = $this->create_object( - $this->get_object($referenced)->get_value() - ); - } else { - $newannots = $this->create_object( - new PDFValueList() - ); - $newannots->push($annots); - } - - // Create the annotation object, annotate the offset and append the object - $annotation_object = $this->create_object( - [ - "Type" => "/Annot", - "Subtype" => "/Widget", - "FT" => "/Sig", - "V" => new PDFValueString(""), - "T" => new PDFValueString('Signature' . get_random_string()), - "P" => new PDFValueReference($page_obj->get_oid()), - "Rect" => $recttoappear, - "F" => 132, // TODO: check this value - ] - ); - - // Prepare the signature object (we need references to it) - $signature = null; - if ($this->_certificate !== null) { - // Perform signature test to get signature size to define __SIGNATURE_MAX_LENGTH - p_debug(" ########## PERFORM SIGNATURE LENGTH CHECK ##########\n"); - $CMS = new CMS(); - $CMS->signature_data['signcert'] = $this->_certificate['cert']; - $CMS->signature_data['extracerts'] = $this->_certificate['extracerts'] ?? null; - $CMS->signature_data['hashAlgorithm'] = 'sha256'; - $CMS->signature_data['privkey'] = $this->_certificate['pkey']; - $CMS->signature_data['tsa'] = $this->_signature_tsa; - $CMS->signature_data['ltv'] = $this->_signature_ltv_data; - $res = $CMS->pkcs7_sign('0'); - $len = strlen($res); - p_debug(" Signature Length is \"$len\" Bytes"); - p_debug(" ########## FINISHED SIGNATURE LENGTH CHECK #########\n\n"); - PDFSignatureObject::$__SIGNATURE_MAX_LENGTH = $len; - - $signature = $this->create_object([], PDFSignatureObject::class, false); - //$signature = new PDFSignatureObject([]); - $signature->set_metadata($this->_metadata_name, $this->_metadata_reason, $this->_metadata_location, $this->_metadata_contact_info); - $signature->set_certificate($this->_certificate); - if ($this->_signature_tsa !== null) { - $signature->set_signature_tsa($this->_signature_tsa); - } - if ($this->_signature_ltv_data !== null) { - $signature->set_signature_ltv($this->_signature_ltv_data); - } - - // Update the value to the annotation object - $annotation_object["V"] = new PDFValueReference($signature->get_oid()); - } - - // If an image is provided, let's load it - if ($imagefilename !== null) { - // Signature with appearance, following the Adobe workflow: - // 1. form - // 2. layers /n0 (empty) and /n2 - // https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/acrobat_digital_signature_appearances_v9.pdf - - // Get the page height, to change the coordinates system (up to down) - $pagesize = $this->get_page_size($pagetoappear); - $pagesize = explode(" ", (string) $pagesize[0]->val()); - $pagesize_h = floatval("" . $pagesize[3]) - floatval("" . $pagesize[1]); - - $bbox = [0, 0, $recttoappear[2] - $recttoappear[0], $recttoappear[3] - $recttoappear[1]]; - $form_object = $this->create_object([ - "BBox" => $bbox, - "Subtype" => "/Form", - "Type" => "/XObject", - "Group" => [ - 'Type' => '/Group', - 'S' => '/Transparency', - 'CS' => '/DeviceRGB', - ], - ]); - - $container_form_object = $this->create_object([ - "BBox" => $bbox, - "Subtype" => "/Form", - "Type" => "/XObject", - "Resources" => [ - "XObject" => [ - "n0" => new PDFValueSimple(""), - "n2" => new PDFValueSimple(""), - ], - ], - ]); - $container_form_object->set_stream("q 1 0 0 1 0 0 cm /n0 Do Q\nq 1 0 0 1 0 0 cm /n2 Do Q\n", false); - - $layer_n0 = $this->create_object([ - "BBox" => [0.0, 0.0, 100.0, 100.0], - "Subtype" => "/Form", - "Type" => "/XObject", - "Resources" => new PDFValueObject(), - ]); - - // Add the same structure than Acrobat Reader - $layer_n0->set_stream("% DSBlank" . __EOL, false); - - $layer_n2 = $this->create_object([ - "BBox" => $bbox, - "Subtype" => "/Form", - "Type" => "/XObject", - "Resources" => new PDFValueObject(), - ]); - - $result = _add_image($this->create_object(...), $imagefilename, $bbox[0], $bbox[1], $bbox[2], $bbox[3], $page_rotation->val()); - if ($result === false) { - return p_error("could not add the image"); - } - - $layer_n2["Resources"] = $result["resources"]; - $layer_n2->set_stream($result['command'], false); - - $container_form_object["Resources"]["XObject"]["n0"] = new PDFValueReference($layer_n0->get_oid()); - $container_form_object["Resources"]["XObject"]["n2"] = new PDFValueReference($layer_n2->get_oid()); - - $form_object['Resources'] = new PDFValueObject([ - "XObject" => [ - "FRM" => new PDFValueReference($container_form_object->get_oid()), - ], - ]); - $form_object->set_stream("/FRM Do", false); - - // Set the signature appearance field to the form object - $annotation_object["AP"] = [ - "N" => new PDFValueReference($form_object->get_oid()), - ]; - $annotation_object["Rect"] = [$recttoappear[0], $pagesize_h - $recttoappear[1], $recttoappear[2], $pagesize_h - $recttoappear[3]]; - } - - if (! $newannots->push(new PDFValueReference($annotation_object->get_oid()))) { - return p_error("Could not update the page where the signature has to appear"); - } - - $page_obj["Annots"] = new PDFValueReference($newannots->get_oid()); - array_push($updated_objects, $page_obj); - - // AcroForm may be an indirect object - if (! isset($root_obj["AcroForm"])) { - $root_obj["AcroForm"] = new PDFValueObject(); - } - - $acroform = &$root_obj["AcroForm"]; - if ((($referenced = $acroform->get_object_referenced()) !== false) && (! is_array($referenced))) { - $acroform = $this->get_object($referenced); - array_push($updated_objects, $acroform); - } else { - array_push($updated_objects, $root_obj); - } - - // Add the annotation to the interactive form - $acroform["SigFlags"] = 3; - if (! isset($acroform['Fields'])) { - $acroform['Fields'] = new PDFValueList(); - } - - // Add the annotation object to the interactive form - if (! $acroform['Fields']->push(new PDFValueReference($annotation_object->get_oid()))) { - return p_error("could not create the signature field"); - } - - // Store the objects - foreach ($updated_objects as &$object) { - $this->add_object($object); - } - - return $signature; - } - - /** - * Function that updates the modification date of the document. If modifies two parts: the "info" field of the trailer object - * and the xmp metadata field pointed by the root object. - * - * @param date a DateTime object that contains the date to be set; null to set "now" - * - * @return ok true if the date could be set; false otherwise - */ - protected function update_mod_date(DateTime $date = null) - { - // First of all, we are searching for the root object (which should be in the trailer) - $root = $this->_pdf_trailer_object["Root"]; - - if (($root === false) || (($root = $root->get_object_referenced()) === false)) { - return p_error("could not find the root object from the trailer"); - } - - $root_obj = $this->get_object($root); - if ($root_obj === false) { - return p_error("invalid root object"); - } - - if ($date === null) { - $date = new DateTime(); - } - - // Update the xmp metadata if exists - if (isset($root_obj["Metadata"])) { - $metadata = $root_obj["Metadata"]; - if ((($referenced = $metadata->get_object_referenced()) !== false) && (! is_array($referenced))) { - $metadata = $this->get_object($referenced); - $metastream = $metadata->get_stream(); - $metastream = preg_replace('/([^<]*)<\/xmp:ModifyDate>/', '' . $date->format("c") . '', (string) $metastream); - $metastream = preg_replace('/([^<]*)<\/xmp:MetadataDate>/', '' . $date->format("c") . '', (string) $metastream); - $metastream = preg_replace('/([^<]*)<\/xmpMM:InstanceID>/', 'uuid:' . UUID::v4() . '', (string) $metastream); - $metadata->set_stream($metastream, false); - $this->add_object($metadata); - } - } - - // Update the information object (not really needed) - $info = $this->_pdf_trailer_object["Info"]; - if (($info === false) || (($info = $info->get_object_referenced()) === false)) { - return p_error("could not find the info object from the trailer"); - } - - $info_obj = $this->get_object($info); - if ($info_obj === false) { - return p_error("invalid info object"); - } - - $info_obj["ModDate"] = new PDFValueString(timestamp_to_pdfdatestring($date)); - $info_obj["Producer"] = new PDFValueString("Modificado con SAPP"); - $this->add_object($info_obj); - - return true; - } - /** * Function that gets the objects that have been read from the document * @@ -781,7 +479,7 @@ public function set_version($version): bool * * @return obj the PDFObject created */ - public function create_object($value = [], $class = "ddn\\sapp\\PDFObject", $autoadd = true): PDFObject + public function create_object($value = [], $class = 'ddn\\sapp\\PDFObject', $autoadd = true): PDFObject { $o = new $class($this->get_new_oid(), $value); if ($autoadd === true) { @@ -819,57 +517,13 @@ public function add_object(PDFObject $pdf_object): bool } /** - * This function generates all the contents of the file up to the xref entry. + * This functions outputs the document to a buffer object, ready to be dumped to a file. * - * @param rebuild whether to generate the xref with all the objects in the document (true) or - * consider only the new ones (false) + * @param rebuild whether we are rebuilding the whole xref table or not (in case of incremental versions, we should use "false") * - * @return xref_data [ the text corresponding to the objects, array of offsets for each object ] + * @return buffer a buffer that contains a pdf dumpable document */ - protected function _generate_content_to_xref($rebuild = false): array - { - if ($rebuild === true) { - $result = new Buffer("%$this->_pdf_version_string" . __EOL); - } else { - $result = new Buffer($this->_buffer); - } - - // Need to calculate the objects offset - $offsets = []; - $offsets[0] = 0; - - // The objects - $offset = $result->size(); - - if ($rebuild === true) { - for ($i = 0; $i <= $this->_max_oid; $i++) { - if (($object = $this->get_object($i)) === false) { - continue; - } - - $result->data($object->to_pdf_entry()); - $offsets[$i] = $offset; - $offset = $result->size(); - } - } else { - foreach ($this->_pdf_objects as $obj_id => $object) { - $result->data($object->to_pdf_entry()); - $offsets[$obj_id] = $offset; - $offset = $result->size(); - } - } - - return [$result, $offsets]; - } - - /** - * This functions outputs the document to a buffer object, ready to be dumped to a file. - * - * @param rebuild whether we are rebuilding the whole xref table or not (in case of incremental versions, we should use "false") - * - * @return buffer a buffer that contains a pdf dumpable document - */ - public function to_pdf_file_b($rebuild = false): Buffer + public function to_pdf_file_b($rebuild = false): Buffer { // We made no updates, so return the original doc if (($rebuild === false) && (count($this->_pdf_objects) === 0) && ($this->_certificate === null) && ($this->_appearance === null)) { @@ -888,7 +542,7 @@ public function to_pdf_file_b($rebuild = false): Buffer if ($_signature === false) { $this->pop_state(); - return p_error("could not generate the signed document"); + return p_error('could not generate the signed document'); } } @@ -901,12 +555,12 @@ public function to_pdf_file_b($rebuild = false): Buffer $xref_offset += strlen((string) $_signature->to_pdf_entry()); } - $doc_version_string = str_replace("PDF-", "", $this->_pdf_version_string); + $doc_version_string = str_replace('PDF-', '', $this->_pdf_version_string); // The version considered for the cross reference table depends on the version of the current xref table, // as it is not possible to mix xref tables. Anyway we are $target_version = $this->_xref_table_version; - if ($this->_xref_table_version >= "1.5") { + if ($this->_xref_table_version >= '1.5') { // i.e. xref streams if ($doc_version_string > $target_version) { $target_version = $doc_version_string; @@ -918,8 +572,8 @@ public function to_pdf_file_b($rebuild = false): Buffer } } - if ($target_version >= "1.5") { - p_debug("generating xref using cross-reference streams"); + if ($target_version >= '1.5') { + p_debug('generating xref using cross-reference streams'); // Create a new object for the trailer $trailer = $this->create_object( @@ -933,33 +587,32 @@ public function to_pdf_file_b($rebuild = false): Buffer $xref = PDFUtilFnc::build_xref_1_5($_obj_offsets); // Set the parameters for the trailer - $trailer["Index"] = explode(" ", (string) $xref["Index"]); - $trailer["W"] = $xref["W"]; - $trailer["Size"] = $this->_max_oid + 1; - $trailer["Type"] = "/XRef"; + $trailer['Index'] = explode(' ', (string) $xref['Index']); + $trailer['W'] = $xref['W']; + $trailer['Size'] = $this->_max_oid + 1; + $trailer['Type'] = '/XRef'; // Not needed to generate new IDs, as in metadata the IDs will be set // $ID1 = md5("" . (new \DateTime())->getTimestamp() . "-" . $this->_xref_position . $xref["stream"]); - $ID2 = md5("" . (new DateTime())->getTimestamp() . "-" . $this->_xref_position . $this->_pdf_trailer_object); + $ID2 = md5('' . (new DateTime())->getTimestamp() . '-' . $this->_xref_position . $this->_pdf_trailer_object); // $trailer["ID"] = [ new PDFValueHexString($ID1), new PDFValueHexString($ID2) ]; - $trailer["ID"] = [$trailer["ID"][0], new PDFValueHexString(strtoupper($ID2))]; + $trailer['ID'] = [$trailer['ID'][0], new PDFValueHexString(strtoupper($ID2))]; // We are not using predictors nor encoding - if (isset($trailer["DecodeParms"])) { - unset($trailer["DecodeParms"]); + if (isset($trailer['DecodeParms'])) { + unset($trailer['DecodeParms']); } // We are not compressing the stream - if (isset($trailer["Filter"])) { - unset($trailer["Filter"]); + if (isset($trailer['Filter'])) { + unset($trailer['Filter']); } - $trailer->set_stream($xref["stream"], false); + $trailer->set_stream($xref['stream'], false); // If creating an incremental modification, point to the previous xref table if ($rebuild === false) { $trailer['Prev'] = $this->_xref_position; - } else // If rebuilding the document, remove the references to previous xref tables, because it will be only one - { + } else { // If rebuilding the document, remove the references to previous xref tables, because it will be only one if (isset($trailer['Prev'])) { unset($trailer['Prev']); } @@ -967,9 +620,9 @@ public function to_pdf_file_b($rebuild = false): Buffer // And generate the part of the document related to the xref $_doc_from_xref = new Buffer($trailer->to_pdf_entry()); - $_doc_from_xref->data("startxref" . __EOL . "$xref_offset" . __EOL . "%%EOF" . __EOL); + $_doc_from_xref->data('startxref' . __EOL . "{$xref_offset}" . __EOL . '%%EOF' . __EOL); } else { - p_debug("generating xref using classic xref...trailer"); + p_debug('generating xref using classic xref...trailer'); $xref_content = PDFUtilFnc::build_xref($_obj_offsets); // Update the trailer @@ -988,15 +641,15 @@ public function to_pdf_file_b($rebuild = false): Buffer // Generate the part of the document related to the xref $_doc_from_xref = new Buffer($xref_content); - $_doc_from_xref->data("trailer\n$this->_pdf_trailer_object"); - $_doc_from_xref->data("\nstartxref\n$xref_offset\n%%EOF\n"); + $_doc_from_xref->data("trailer\n{$this->_pdf_trailer_object}"); + $_doc_from_xref->data("\nstartxref\n{$xref_offset}\n%%EOF\n"); } if ($_signature !== null) { // In case that the document is signed, calculate the signature $_signature->set_sizes($_doc_to_xref->size(), $_doc_from_xref->size()); - $_signature["Contents"] = new PDFValueSimple(""); + $_signature['Contents'] = new PDFValueSimple(''); $_signable_document = new Buffer($_doc_to_xref->get_raw() . $_signature->to_pdf_entry() . $_doc_from_xref->get_raw()); $certificate = $_signature->get_certificate(); $extracerts = (array_key_exists('extracerts', $certificate)) ? $certificate['extracerts'] : null; @@ -1011,7 +664,7 @@ public function to_pdf_file_b($rebuild = false): Buffer //$signature_contents = str_pad($signature_contents, strlen($signature_contents), '0'); // Then restore the contents field - $_signature["Contents"] = new PDFValueHexString($signature_contents); + $_signature['Contents'] = new PDFValueHexString($signature_contents); // Add this object to the content previous to this document xref $_doc_to_xref->data($_signature->to_pdf_entry()); @@ -1046,14 +699,14 @@ public function to_pdf_file($filename, $rebuild = false) { $pdf_content = $this->to_pdf_file_b($rebuild); - $file = fopen($filename, "wb"); + $file = fopen($filename, 'wb'); if ($file === false) { - return p_error("failed to create the file"); + return p_error('failed to create the file'); } if (fwrite($file, $pdf_content->get_raw()) !== $pdf_content->size()) { fclose($file); - return p_error("failed to write to file"); + return p_error('failed to write to file'); } fclose($file); @@ -1116,87 +769,6 @@ public function get_page_size($i) return $pageinfo['size']; } - /** - * This function builds the page IDs for object with id oid. If it is a page, it returns the oid; if it is not and it has - * kids and every kid is a page (or a set of pages), it finds the pages. - * - * @param oid the object id to inspect - * - * @return pages the ordered list of page ids corresponding to object oid, or false if any of the kid objects - * is not of type page or pages. - */ - protected function _get_page_info(int $oid, array $info = []) - { - $object = $this->get_object($oid); - if ($object === false) { - return p_error("could not get information about the page"); - } - - $page_ids = []; - - switch ($object["Type"]->val()) { - case "Pages": - $kids = $object["Kids"]; - $kids = $kids->get_object_referenced(); - if ($kids !== false) { - if (isset($object['MediaBox'])) { - $info['size'] = $object['MediaBox']->val(); - } - foreach ($kids as $kid) { - $ids = $this->_get_page_info($kid, $info); - if ($ids === false) { - return false; - } - array_push($page_ids, ...$ids); - } - } else { - return p_error("could not get the pages"); - } - break; - case "Page": - if (isset($object['MediaBox'])) { - $info['size'] = $object['MediaBox']->val(); - } - - return [ - [ - 'id' => $oid, - 'info' => $info, - ], - ]; - default: - return false; - } - - return $page_ids; - } - - /** - * Obtains an ordered list of objects that contain the ids of the page objects of the document. - * The order is made according to the catalog and the document structure. - * - * @return list an ordered list of the id of the page objects, or false if could not be found - */ - protected function _acquire_pages_info() - { - $root = $this->_pdf_trailer_object["Root"]; - if (($root === false) || (($root = $root->get_object_referenced()) === false)) { - return p_error("could not find the root object from the trailer"); - } - - $root = $this->get_object($root); - if ($root !== false) { - $pages = $root["Pages"]; - if (($pages === false) || (($pages = $pages->get_object_referenced()) === false)) { - return p_error("could not find the pages for the document"); - } - - $this->_pages_info = $this->_get_page_info($pages); - } else { - p_warning("root object does not exist, so cannot get information about pages"); - } - } - /** * This function compares this document with other document, object by object. The idea is to compare the objects with the same oid in the * different documents, checking field by field; it does not take into account the streams. @@ -1252,7 +824,7 @@ public function get_object_tree(): array // Create the object in the dependency tree and add it to the list of objects if (! array_key_exists($oid, $objects)) { - $objects[$oid] = new DependencyTreeObject($oid, $o["Type"]); + $objects[$oid] = new DependencyTreeObject($oid, $o['Type']); } // The object is a PDFObject so we need the PDFValueObject to get the value of the fields @@ -1260,7 +832,7 @@ public function get_object_tree(): array $val = $o->get_value(); // We'll only consider those objects that may create an structure (i.e. the objects, whose fields may include references to other objects) - if (is_a($val, "ddn\\sapp\\pdfvalue\\PDFValueObject")) { + if (is_a($val, 'ddn\\sapp\\pdfvalue\\PDFValueObject')) { $references = references_in_object($val, $oid); } else { $references = $val->get_object_referenced(); @@ -1276,7 +848,7 @@ public function get_object_tree(): array foreach ($references as $r_object) { if (! array_key_exists($r_object, $objects)) { $r_object_o = $this->get_object($r_object); - $objects[$r_object] = new DependencyTreeObject($r_object, $r_object_o["Type"]); + $objects[$r_object] = new DependencyTreeObject($r_object, $r_object_o['Type']); } $object->addchild($r_object, $objects[$r_object]); } @@ -1285,7 +857,7 @@ public function get_object_tree(): array // $xref_children = []; foreach ($objects as $oid => $t_object) { - if ($t_object->info == "/XRef") { + if ($t_object->info == '/XRef') { array_push($xref_children, ...iterator_to_array($t_object->children())); } } @@ -1294,7 +866,7 @@ public function get_object_tree(): array // Remove those objects that are child of other objects from the top of the tree foreach ($objects as $oid => $t_object) { - if (($t_object->is_child > 0) || (in_array($t_object->info, ["/XRef", "/ObjStm"]))) { + if (($t_object->is_child > 0) || (in_array($t_object->info, ['/XRef', '/ObjStm']))) { if (! in_array($oid, $xref_children)) { unset($objects[$oid]); } @@ -1388,23 +960,23 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, if ($imagefilename !== null) { $imagesize = @getimagesize($imagefilename); if ($imagesize === false) { - return p_warning("failed to open the image $imagesize"); + return p_warning("failed to open the image {$imagesize}"); } if (($page_to_appear < 0) || ($page_to_appear > $this->get_page_count() - 1)) { - return p_error("invalid page number"); + return p_error('invalid page number'); } $pagesize = $this->get_page_size($page_to_appear); if ($pagesize === false) { - return p_error("failed to get page size"); + return p_error('failed to get page size'); } - $pagesize = explode(" ", (string) $pagesize[0]->val()); + $pagesize = explode(' ', (string) $pagesize[0]->val()); // Get the bounding box for the image - $p_x = intval("" . $pagesize[0]); - $p_y = intval("" . $pagesize[1]); - $p_w = intval("" . $pagesize[2]) - $p_x; - $p_h = intval("" . $pagesize[3]) - $p_y; + $p_x = intval('' . $pagesize[0]); + $p_y = intval('' . $pagesize[1]); + $p_w = intval('' . $pagesize[2]) - $p_x; + $p_h = intval('' . $pagesize[3]) - $p_y; // Add the position for the image $p_x = $p_x + $px; @@ -1415,7 +987,7 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, if (is_array($size)) { if (count($size) != 2) { - return p_error("invalid size"); + return p_error('invalid size'); } $width = $size[0]; $height = $size[1]; @@ -1428,7 +1000,7 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, $width = $i_w * $size; $height = $i_h * $size; } else { - return p_error("invalid size format"); + return p_error('invalid size format'); } } } @@ -1441,14 +1013,440 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, } if (! $this->set_signature_certificate($certfile, $password)) { - return p_error("the certificate or the signature is not valid"); + return p_error('the certificate or the signature is not valid'); } $docsigned = $this->to_pdf_file_s(); if ($docsigned === false) { - return p_error("failed to sign the document"); + return p_error('failed to sign the document'); + } + + return self::from_string($docsigned); + } + + // Gets a new oid for a new object + protected function get_new_oid(): int|float + { + $this->_max_oid++; + + return $this->_max_oid; + } + + /** + * Function that creates and updates the PDF objects needed to sign the document. The workflow for a signature is: + * - create a signature object + * - create an annotation object whose value is the signature object + * - create a form object (along with other objects) that will hold the appearance of the annotation object + * - modify the root object to make acroform point to the annotation object + * - modify the page object to make the annotations of that page include the annotation object + * > If the appearance is not set, the image will not appear, and the signature object will be invisible. + * > If the certificate is not set, the signature created will be a placeholder (that acrobat will able to sign) + * LIMITATIONS: one document can be signed once at a time; if wanted more signatures, then chain the documents: + * $o1->set_signature_certificate(...); + * $o2 = PDFDoc::fromstring($o1->to_pdf_file_s); + * $o2->set_signature_certificate(...); + * $o2->to_pdf_file_s(); + * + * @return signature a signature object, or null if the document is not signed; false if an error happens + */ + protected function _generate_signature_in_document() + { + $imagefilename = null; + $recttoappear = [0, 0, 0, 0]; + $pagetoappear = 0; + + if ($this->_appearance !== null) { + $imagefilename = $this->_appearance['image']; + $recttoappear = $this->_appearance['rect']; + $pagetoappear = $this->_appearance['page']; + } + + // First of all, we are searching for the root object (which should be in the trailer) + $root = $this->_pdf_trailer_object['Root']; + + if (($root === false) || (($root = $root->get_object_referenced()) === false)) { + return p_error('could not find the root object from the trailer'); + } + + $root_obj = $this->get_object($root); + if ($root_obj === false) { + return p_error('invalid root object'); + } + + // Now the object corresponding to the page number in which to appear + $page_obj = $this->get_page($pagetoappear); + if ($page_obj === false) { + return p_error('invalid page'); + } + + // The objects to update + $updated_objects = []; + + // Add the annotation to the page + if (! isset($page_obj['Annots'])) { + $page_obj['Annots'] = new PDFValueList(); + } + + $annots = &$page_obj['Annots']; + $page_rotation = $page_obj['Rotate'] ?? new PDFValueSimple(0); + + if ((($referenced = $annots->get_object_referenced()) !== false) && (! is_array($referenced))) { + // It is an indirect object, so we need to update that object + $newannots = $this->create_object( + $this->get_object($referenced)->get_value() + ); + } else { + $newannots = $this->create_object( + new PDFValueList() + ); + $newannots->push($annots); + } + + // Create the annotation object, annotate the offset and append the object + $annotation_object = $this->create_object( + [ + 'Type' => '/Annot', + 'Subtype' => '/Widget', + 'FT' => '/Sig', + 'V' => new PDFValueString(''), + 'T' => new PDFValueString('Signature' . get_random_string()), + 'P' => new PDFValueReference($page_obj->get_oid()), + 'Rect' => $recttoappear, + 'F' => 132, // TODO: check this value + ] + ); + + // Prepare the signature object (we need references to it) + $signature = null; + if ($this->_certificate !== null) { + // Perform signature test to get signature size to define __SIGNATURE_MAX_LENGTH + p_debug(" ########## PERFORM SIGNATURE LENGTH CHECK ##########\n"); + $CMS = new CMS(); + $CMS->signature_data['signcert'] = $this->_certificate['cert']; + $CMS->signature_data['extracerts'] = $this->_certificate['extracerts'] ?? null; + $CMS->signature_data['hashAlgorithm'] = 'sha256'; + $CMS->signature_data['privkey'] = $this->_certificate['pkey']; + $CMS->signature_data['tsa'] = $this->_signature_tsa; + $CMS->signature_data['ltv'] = $this->_signature_ltv_data; + $res = $CMS->pkcs7_sign('0'); + $len = strlen($res); + p_debug(" Signature Length is \"{$len}\" Bytes"); + p_debug(" ########## FINISHED SIGNATURE LENGTH CHECK #########\n\n"); + PDFSignatureObject::$__SIGNATURE_MAX_LENGTH = $len; + + $signature = $this->create_object([], PDFSignatureObject::class, false); + //$signature = new PDFSignatureObject([]); + $signature->set_metadata($this->_metadata_name, $this->_metadata_reason, $this->_metadata_location, $this->_metadata_contact_info); + $signature->set_certificate($this->_certificate); + if ($this->_signature_tsa !== null) { + $signature->set_signature_tsa($this->_signature_tsa); + } + if ($this->_signature_ltv_data !== null) { + $signature->set_signature_ltv($this->_signature_ltv_data); + } + + // Update the value to the annotation object + $annotation_object['V'] = new PDFValueReference($signature->get_oid()); + } + + // If an image is provided, let's load it + if ($imagefilename !== null) { + // Signature with appearance, following the Adobe workflow: + // 1. form + // 2. layers /n0 (empty) and /n2 + // https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/acrobat_digital_signature_appearances_v9.pdf + + // Get the page height, to change the coordinates system (up to down) + $pagesize = $this->get_page_size($pagetoappear); + $pagesize = explode(' ', (string) $pagesize[0]->val()); + $pagesize_h = floatval('' . $pagesize[3]) - floatval('' . $pagesize[1]); + + $bbox = [0, 0, $recttoappear[2] - $recttoappear[0], $recttoappear[3] - $recttoappear[1]]; + $form_object = $this->create_object([ + 'BBox' => $bbox, + 'Subtype' => '/Form', + 'Type' => '/XObject', + 'Group' => [ + 'Type' => '/Group', + 'S' => '/Transparency', + 'CS' => '/DeviceRGB', + ], + ]); + + $container_form_object = $this->create_object([ + 'BBox' => $bbox, + 'Subtype' => '/Form', + 'Type' => '/XObject', + 'Resources' => [ + 'XObject' => [ + 'n0' => new PDFValueSimple(''), + 'n2' => new PDFValueSimple(''), + ], + ], + ]); + $container_form_object->set_stream("q 1 0 0 1 0 0 cm /n0 Do Q\nq 1 0 0 1 0 0 cm /n2 Do Q\n", false); + + $layer_n0 = $this->create_object([ + 'BBox' => [0.0, 0.0, 100.0, 100.0], + 'Subtype' => '/Form', + 'Type' => '/XObject', + 'Resources' => new PDFValueObject(), + ]); + + // Add the same structure than Acrobat Reader + $layer_n0->set_stream('% DSBlank' . __EOL, false); + + $layer_n2 = $this->create_object([ + 'BBox' => $bbox, + 'Subtype' => '/Form', + 'Type' => '/XObject', + 'Resources' => new PDFValueObject(), + ]); + + $result = _add_image($this->create_object(...), $imagefilename, $bbox[0], $bbox[1], $bbox[2], $bbox[3], $page_rotation->val()); + if ($result === false) { + return p_error('could not add the image'); + } + + $layer_n2['Resources'] = $result['resources']; + $layer_n2->set_stream($result['command'], false); + + $container_form_object['Resources']['XObject']['n0'] = new PDFValueReference($layer_n0->get_oid()); + $container_form_object['Resources']['XObject']['n2'] = new PDFValueReference($layer_n2->get_oid()); + + $form_object['Resources'] = new PDFValueObject([ + 'XObject' => [ + 'FRM' => new PDFValueReference($container_form_object->get_oid()), + ], + ]); + $form_object->set_stream('/FRM Do', false); + + // Set the signature appearance field to the form object + $annotation_object['AP'] = [ + 'N' => new PDFValueReference($form_object->get_oid()), + ]; + $annotation_object['Rect'] = [$recttoappear[0], $pagesize_h - $recttoappear[1], $recttoappear[2], $pagesize_h - $recttoappear[3]]; + } + + if (! $newannots->push(new PDFValueReference($annotation_object->get_oid()))) { + return p_error('Could not update the page where the signature has to appear'); + } + + $page_obj['Annots'] = new PDFValueReference($newannots->get_oid()); + array_push($updated_objects, $page_obj); + + // AcroForm may be an indirect object + if (! isset($root_obj['AcroForm'])) { + $root_obj['AcroForm'] = new PDFValueObject(); + } + + $acroform = &$root_obj['AcroForm']; + if ((($referenced = $acroform->get_object_referenced()) !== false) && (! is_array($referenced))) { + $acroform = $this->get_object($referenced); + array_push($updated_objects, $acroform); + } else { + array_push($updated_objects, $root_obj); } - return PDFDoc::from_string($docsigned); + // Add the annotation to the interactive form + $acroform['SigFlags'] = 3; + if (! isset($acroform['Fields'])) { + $acroform['Fields'] = new PDFValueList(); + } + + // Add the annotation object to the interactive form + if (! $acroform['Fields']->push(new PDFValueReference($annotation_object->get_oid()))) { + return p_error('could not create the signature field'); + } + + // Store the objects + foreach ($updated_objects as &$object) { + $this->add_object($object); + } + + return $signature; + } + + /** + * Function that updates the modification date of the document. If modifies two parts: the "info" field of the trailer object + * and the xmp metadata field pointed by the root object. + * + * @param date a DateTime object that contains the date to be set; null to set "now" + * + * @return ok true if the date could be set; false otherwise + */ + protected function update_mod_date(DateTime $date = null) + { + // First of all, we are searching for the root object (which should be in the trailer) + $root = $this->_pdf_trailer_object['Root']; + + if (($root === false) || (($root = $root->get_object_referenced()) === false)) { + return p_error('could not find the root object from the trailer'); + } + + $root_obj = $this->get_object($root); + if ($root_obj === false) { + return p_error('invalid root object'); + } + + if ($date === null) { + $date = new DateTime(); + } + + // Update the xmp metadata if exists + if (isset($root_obj['Metadata'])) { + $metadata = $root_obj['Metadata']; + if ((($referenced = $metadata->get_object_referenced()) !== false) && (! is_array($referenced))) { + $metadata = $this->get_object($referenced); + $metastream = $metadata->get_stream(); + $metastream = preg_replace('/([^<]*)<\/xmp:ModifyDate>/', '' . $date->format('c') . '', (string) $metastream); + $metastream = preg_replace('/([^<]*)<\/xmp:MetadataDate>/', '' . $date->format('c') . '', (string) $metastream); + $metastream = preg_replace('/([^<]*)<\/xmpMM:InstanceID>/', 'uuid:' . UUID::v4() . '', (string) $metastream); + $metadata->set_stream($metastream, false); + $this->add_object($metadata); + } + } + + // Update the information object (not really needed) + $info = $this->_pdf_trailer_object['Info']; + if (($info === false) || (($info = $info->get_object_referenced()) === false)) { + return p_error('could not find the info object from the trailer'); + } + + $info_obj = $this->get_object($info); + if ($info_obj === false) { + return p_error('invalid info object'); + } + + $info_obj['ModDate'] = new PDFValueString(timestamp_to_pdfdatestring($date)); + $info_obj['Producer'] = new PDFValueString('Modificado con SAPP'); + $this->add_object($info_obj); + + return true; + } + + /** + * This function generates all the contents of the file up to the xref entry. + * + * @param rebuild whether to generate the xref with all the objects in the document (true) or + * consider only the new ones (false) + * + * @return xref_data [ the text corresponding to the objects, array of offsets for each object ] + */ + protected function _generate_content_to_xref($rebuild = false): array + { + if ($rebuild === true) { + $result = new Buffer("%{$this->_pdf_version_string}" . __EOL); + } else { + $result = new Buffer($this->_buffer); + } + + // Need to calculate the objects offset + $offsets = []; + $offsets[0] = 0; + + // The objects + $offset = $result->size(); + + if ($rebuild === true) { + for ($i = 0; $i <= $this->_max_oid; $i++) { + if (($object = $this->get_object($i)) === false) { + continue; + } + + $result->data($object->to_pdf_entry()); + $offsets[$i] = $offset; + $offset = $result->size(); + } + } else { + foreach ($this->_pdf_objects as $obj_id => $object) { + $result->data($object->to_pdf_entry()); + $offsets[$obj_id] = $offset; + $offset = $result->size(); + } + } + + return [$result, $offsets]; + } + + /** + * This function builds the page IDs for object with id oid. If it is a page, it returns the oid; if it is not and it has + * kids and every kid is a page (or a set of pages), it finds the pages. + * + * @param oid the object id to inspect + * + * @return pages the ordered list of page ids corresponding to object oid, or false if any of the kid objects + * is not of type page or pages. + */ + protected function _get_page_info(int $oid, array $info = []) + { + $object = $this->get_object($oid); + if ($object === false) { + return p_error('could not get information about the page'); + } + + $page_ids = []; + + switch ($object['Type']->val()) { + case 'Pages': + $kids = $object['Kids']; + $kids = $kids->get_object_referenced(); + if ($kids !== false) { + if (isset($object['MediaBox'])) { + $info['size'] = $object['MediaBox']->val(); + } + foreach ($kids as $kid) { + $ids = $this->_get_page_info($kid, $info); + if ($ids === false) { + return false; + } + array_push($page_ids, ...$ids); + } + } else { + return p_error('could not get the pages'); + } + break; + case 'Page': + if (isset($object['MediaBox'])) { + $info['size'] = $object['MediaBox']->val(); + } + + return [ + [ + 'id' => $oid, + 'info' => $info, + ], + ]; + default: + return false; + } + + return $page_ids; + } + + /** + * Obtains an ordered list of objects that contain the ids of the page objects of the document. + * The order is made according to the catalog and the document structure. + * + * @return list an ordered list of the id of the page objects, or false if could not be found + */ + protected function _acquire_pages_info() + { + $root = $this->_pdf_trailer_object['Root']; + if (($root === false) || (($root = $root->get_object_referenced()) === false)) { + return p_error('could not find the root object from the trailer'); + } + + $root = $this->get_object($root); + if ($root !== false) { + $pages = $root['Pages']; + if (($pages === false) || (($pages = $pages->get_object_referenced()) === false)) { + return p_error('could not find the pages for the document'); + } + + $this->_pages_info = $this->_get_page_info($pages); + } else { + p_warning('root object does not exist, so cannot get information about pages'); + } } } diff --git a/src/PDFDocWithContents.php b/src/PDFDocWithContents.php index c05295b..cf8259b 100644 --- a/src/PDFDocWithContents.php +++ b/src/PDFDocWithContents.php @@ -21,30 +21,30 @@ namespace ddn\sapp; -use function ddn\sapp\helpers\get_random_string; -use function ddn\sapp\helpers\p_error; -use function ddn\sapp\helpers\p_warning; use ddn\sapp\pdfvalue\PDFValueList; use ddn\sapp\pdfvalue\PDFValueObject; use ddn\sapp\pdfvalue\PDFValueReference; +use function ddn\sapp\helpers\get_random_string; +use function ddn\sapp\helpers\p_error; +use function ddn\sapp\helpers\p_warning; class PDFDocWithContents extends PDFDoc { - const T_STANDARD_FONTS = [ - "Times-Roman", - "Times-Bold", - "Time-Italic", - "Time-BoldItalic", - "Courier", - "Courier-Bold", - "Courier-Oblique", - "Courier-BoldOblique", - "Helvetica", - "Helvetica-Bold", - "Helvetica-Oblique", - "Helvetica-BoldOblique", - "Symbol", - "ZapfDingbats", + public const T_STANDARD_FONTS = [ + 'Times-Roman', + 'Times-Bold', + 'Time-Italic', + 'Time-BoldItalic', + 'Courier', + 'Courier-Bold', + 'Courier-Oblique', + 'Courier-BoldOblique', + 'Helvetica', + 'Helvetica-Bold', + 'Helvetica-Oblique', + 'Helvetica-BoldOblique', + 'Symbol', + 'ZapfDingbats', ]; /** @@ -63,33 +63,33 @@ public function add_text($page_to_appear, $text, $x, $y, $params = []) // TODO: maybe we can create a function that "adds content to a page", and that // function will search for the content field and merge the resources, if // needed - p_warning("This function still needs work"); + p_warning('This function still needs work'); $default = [ - "font" => "Helvetica", - "size" => 24, - "color" => "#000000", - "angle" => 0, + 'font' => 'Helvetica', + 'size' => 24, + 'color' => '#000000', + 'angle' => 0, ]; $params = array_merge($default, $params); $page_obj = $this->get_page($page_to_appear); if ($page_obj === false) { - return p_error("invalid page"); + return p_error('invalid page'); } $resources_obj = $this->get_indirect_object($page_obj['Resources']); - if (array_search($params["font"], self::T_STANDARD_FONTS) === false) { - return p_error("only standard fonts are allowed Times-Roman, Helvetica, Courier, Symbol, ZapfDingbats"); + if (array_search($params['font'], self::T_STANDARD_FONTS) === false) { + return p_error('only standard fonts are allowed Times-Roman, Helvetica, Courier, Symbol, ZapfDingbats'); } - $font_id = "F" . get_random_string(4); + $font_id = 'F' . get_random_string(4); $resources_obj['Font'][$font_id] = [ - "Type" => "/Font", - "Subtype" => "/Type1", - "BaseFont" => "/" . $params['font'], + 'Type' => '/Font', + 'Subtype' => '/Type1', + 'BaseFont' => '/' . $params['font'], ]; // Get the contents for the page @@ -97,14 +97,14 @@ public function add_text($page_to_appear, $text, $x, $y, $params = []) $data = $contents_obj->get_stream(false); if ($data === false) { - return p_error("could not interpret the contents of the page"); + return p_error('could not interpret the contents of the page'); } // Get the page height, to change the coordinates system (up to down) $pagesize = $this->get_page_size($page_to_appear); - $pagesize_h = floatval("" . $pagesize[3]) - floatval("" . $pagesize[1]); + $pagesize_h = floatval('' . $pagesize[3]) - floatval('' . $pagesize[1]); - $angle = $params["angle"]; + $angle = $params['angle']; $angle *= M_PI / 180; $c = cos($angle); $s = sin($angle); @@ -112,37 +112,38 @@ public function add_text($page_to_appear, $text, $x, $y, $params = []) $cy = ($pagesize_h - $y); if ($angle !== 0) { - $rotate_command = sprintf("%.5F %.5F %.5F %.5F %.2F %.2F cm 1 0 0 1 %.2F %.2F cm", $c, $s, -$s, $c, $cx, $cy, -$cx, -$cy); + $rotate_command = sprintf('%.5F %.5F %.5F %.5F %.2F %.2F cm 1 0 0 1 %.2F %.2F cm', $c, $s, -$s, $c, $cx, $cy, -$cx, -$cy); } - $text_command = "BT "; - $text_command .= "/$font_id " . $params['size'] . " Tf "; - $text_command .= sprintf("%.2f %.2f Td ", $x, $pagesize_h - $y); // Ubicar en x, y - $text_command .= sprintf("(%s) Tj ", $text); - $text_command .= "ET "; + $text_command = 'BT '; + $text_command .= "/{$font_id} " . $params['size'] . ' Tf '; + $text_command .= sprintf('%.2f %.2f Td ', $x, $pagesize_h - $y); // Ubicar en x, y + $text_command .= sprintf('(%s) Tj ', $text); + $text_command .= 'ET '; - $color = $params["color"]; + $color = $params['color']; if ($color[0] === '#') { $colorvalid = true; $r = null; switch (strlen((string) $color)) { case 4: - $color = "#" . $color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3]; + $color = '#' . $color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3]; + // no break case 7: - [$r, $g, $b] = sscanf($color, "#%02x%02x%02x"); + [$r, $g, $b] = sscanf($color, '#%02x%02x%02x'); break; default: - p_error("please use html-like colors (e.g. #ffbbaa)"); + p_error('please use html-like colors (e.g. #ffbbaa)'); } if ($r !== null) { - $text_command = " q $r $g $b rg $text_command Q"; + $text_command = " q {$r} {$g} {$b} rg {$text_command} Q"; } // Color RGB } else { - p_error("please use html-like colors (e.g. #ffbbaa)"); + p_error('please use html-like colors (e.g. #ffbbaa)'); } if ($angle !== 0) { - $text_command = " q $rotate_command $text_command Q"; + $text_command = " q {$rotate_command} {$text_command} Q"; } $data .= $text_command; @@ -172,7 +173,7 @@ public function add_image($page_obj, $filename, $x = 0, $y = 0, $w = 0, $h = 0) // TODO: maybe we can create a function that "adds content to a page", and that // function will search for the content field and merge the resources, if // needed - p_warning("This function still needs work"); + p_warning('This function still needs work'); // Check that the page is valid if (is_int($page_obj)) { @@ -180,16 +181,16 @@ public function add_image($page_obj, $filename, $x = 0, $y = 0, $w = 0, $h = 0) } if ($page_obj === false) { - return p_error("invalid page"); + return p_error('invalid page'); } // Get the page height, to change the coordinates system (up to down) $pagesize = $this->get_page_size($page_obj); - $pagesize_h = floatval("" . $pagesize[3]) - floatval("" . $pagesize[1]); + $pagesize_h = floatval('' . $pagesize[3]) - floatval('' . $pagesize[1]); $result = $this->_add_image($filename, $x, $pagesize_h - $y, $w, $h); - return p_error("this function still needs work"); + return p_error('this function still needs work'); // Get the resources for the page $resources_obj = $this->get_indirect_object($page_obj['Resources']); @@ -209,7 +210,7 @@ public function add_image($page_obj, $filename, $x = 0, $y = 0, $w = 0, $h = 0) $data = $contents_obj->get_stream(false); if ($data === false) { - return p_error("could not interpret the contents of the page"); + return p_error('could not interpret the contents of the page'); } // Append the command to draw the image diff --git a/src/PDFObject.php b/src/PDFObject.php index 5895616..f4a9111 100644 --- a/src/PDFObject.php +++ b/src/PDFObject.php @@ -24,16 +24,16 @@ use ArrayAccess; use ddn\sapp\helpers\Buffer; use ddn\sapp\helpers\LoadHelpers; -use function ddn\sapp\helpers\p_error; -use function ddn\sapp\helpers\p_warning; use ddn\sapp\pdfvalue\PDFValueObject; use ddn\sapp\pdfvalue\PDFValueSimple; use ReturnTypeWillChange; use Stringable; +use function ddn\sapp\helpers\p_error; +use function ddn\sapp\helpers\p_warning; // Loading the functions -if (! defined("ddn\\sapp\\helpers\\LoadHelpers")) { +if (! defined('ddn\\sapp\\helpers\\LoadHelpers')) { new LoadHelpers(); } @@ -59,12 +59,12 @@ class PDFObject implements ArrayAccess, Stringable protected $_generation; public function __construct( - protected $_oid, + protected int $_oid, $value = null, - $generation = 0, + int $generation = 0, ) { if ($generation !== 0) { - p_warning("Objects of non-zero generation are not fully checked... please double check your document and (if possible) please send examples via issues to https://github.com/dealfonso/sapp/issues/"); + p_warning('Objects of non-zero generation are not fully checked... please double check your document and (if possible) please send examples via issues to https://github.com/dealfonso/sapp/issues/'); } // If the value is null, we suppose that we are creating an empty object @@ -84,6 +84,19 @@ public function __construct( $this->_generation = $generation; } + public function __toString(): string + { + return "{$this->_oid} 0 obj\n" . + "{$this->_value}\n" . + ( + $this->_stream === null ? '' : + "stream\n" . + '...' . + "\nendstream\n" + ) . + "endobj\n"; + } + public function get_keys() { return $this->_value->get_keys(); @@ -99,19 +112,6 @@ public function get_generation() return $this->_generation; } - public function __toString(): string - { - return "$this->_oid 0 obj\n" . - "$this->_value\n" . - ( - $this->_stream === null ? "" : - "stream\n" . - '...' . - "\nendstream\n" - ) . - "endobj\n"; - } - /** * Converts the object to a well-formed PDF entry with a form like * 1 0 obj @@ -125,15 +125,15 @@ public function __toString(): string */ public function to_pdf_entry(): string { - return "$this->_oid 0 obj" . __EOL . - "$this->_value" . __EOL . + return "{$this->_oid} 0 obj" . __EOL . + "{$this->_value}" . __EOL . ( - $this->_stream === null ? "" : + $this->_stream === null ? '' : "stream\r\n" . $this->_stream . - __EOL . "endstream" . __EOL + __EOL . 'endstream' . __EOL ) . - "endobj" . __EOL; + 'endobj' . __EOL; } /** @@ -141,7 +141,7 @@ public function to_pdf_entry(): string * * @return oid the object id */ - public function get_oid() + public function get_oid(): int { return $this->_oid; } @@ -156,84 +156,6 @@ public function get_value() return $this->_value; } - protected static function FlateDecode($_stream, array $params) - { - switch ($params["Predictor"]->get_int()) { - case 1: - return $_stream; - case 10: - case 11: - case 12: - case 13: - case 14: - case 15: - break; - default: - return p_error("other predictor than PNG is not supported in this version"); - } - - switch ($params["Colors"]->get_int()) { - case 1: - break; - default: - return p_error("other color count than 1 is not supported in this version"); - } - - switch ($params["BitsPerComponent"]->get_int()) { - case 8: - break; - default: - return p_error("other bit count than 8 is not supported in this version"); - } - - $decoded = new Buffer(); - $columns = $params['Columns']->get_int(); - - $row_len = $columns + 1; - $stream_len = strlen((string) $_stream); - - // The previous row is zero - $data_prev = str_pad("", $columns, chr(0)); - $row_i = 0; - $pos_i = 0; - $data = str_pad("", $columns, chr(0)); - while ($pos_i < $stream_len) { - $filter_byte = ord($_stream[$pos_i++]); - - // Get the current row - $data = substr((string) $_stream, $pos_i, $columns); - $pos_i += strlen($data); - - // Zero pad, in case that the content is not paired - $data = str_pad($data, $columns, chr(0)); - - // Depending on the filter byte of the row, we should unpack on one way or another - switch ($filter_byte) { - case 0: - break; - case 1: - for ($i = 1; $i < $columns; $i++) { - $data[$i] = ($data[$i] + $data[$i - 1]) % 256; - } - break; - case 2: - for ($i = 0; $i < $columns; $i++) { - $data[$i] = chr((ord($data[$i]) + ord($data_prev[$i])) % 256); - } - break; - default: - return p_error("Unsupported stream"); - } - - // Store and prepare the previous row - $decoded->data($data); - $data_prev = $data; - } - - // p_debug_var($decoded->show_bytes($columns)); - return $decoded->get_raw(); - } - /** * Gets the stream of the object * @@ -249,10 +171,10 @@ public function get_stream($raw = true) case '/FlateDecode': $DecodeParams = $this->_value['DecodeParms'] ?? []; $params = [ - "Columns" => $DecodeParams['Columns'] ?? new PDFValueSimple(0), - "Predictor" => $DecodeParams['Predictor'] ?? new PDFValueSimple(1), - "BitsPerComponent" => $DecodeParams['BitsPerComponent'] ?? new PDFValueSimple(8), - "Colors" => $DecodeParams['Colors'] ?? new PDFValueSimple(1), + 'Columns' => $DecodeParams['Columns'] ?? new PDFValueSimple(0), + 'Predictor' => $DecodeParams['Predictor'] ?? new PDFValueSimple(1), + 'BitsPerComponent' => $DecodeParams['BitsPerComponent'] ?? new PDFValueSimple(8), + 'Colors' => $DecodeParams['Colors'] ?? new PDFValueSimple(1), ]; return self::FlateDecode(gzuncompress($this->_stream), $params); @@ -349,4 +271,82 @@ public function push($v) { return $this->_value->push($v); } + + protected static function FlateDecode($_stream, array $params) + { + switch ($params['Predictor']->get_int()) { + case 1: + return $_stream; + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + break; + default: + return p_error('other predictor than PNG is not supported in this version'); + } + + switch ($params['Colors']->get_int()) { + case 1: + break; + default: + return p_error('other color count than 1 is not supported in this version'); + } + + switch ($params['BitsPerComponent']->get_int()) { + case 8: + break; + default: + return p_error('other bit count than 8 is not supported in this version'); + } + + $decoded = new Buffer(); + $columns = $params['Columns']->get_int(); + + $row_len = $columns + 1; + $stream_len = strlen((string) $_stream); + + // The previous row is zero + $data_prev = str_pad('', $columns, chr(0)); + $row_i = 0; + $pos_i = 0; + $data = str_pad('', $columns, chr(0)); + while ($pos_i < $stream_len) { + $filter_byte = ord($_stream[$pos_i++]); + + // Get the current row + $data = substr((string) $_stream, $pos_i, $columns); + $pos_i += strlen($data); + + // Zero pad, in case that the content is not paired + $data = str_pad($data, $columns, chr(0)); + + // Depending on the filter byte of the row, we should unpack on one way or another + switch ($filter_byte) { + case 0: + break; + case 1: + for ($i = 1; $i < $columns; $i++) { + $data[$i] = ($data[$i] + $data[$i - 1]) % 256; + } + break; + case 2: + for ($i = 0; $i < $columns; $i++) { + $data[$i] = chr((ord($data[$i]) + ord($data_prev[$i])) % 256); + } + break; + default: + return p_error('Unsupported stream'); + } + + // Store and prepare the previous row + $decoded->data($data); + $data_prev = $data; + } + + // p_debug_var($decoded->show_bytes($columns)); + return $decoded->get_raw(); + } } diff --git a/src/PDFObjectParser.php b/src/PDFObjectParser.php index 2abfd08..3da44c8 100644 --- a/src/PDFObjectParser.php +++ b/src/PDFObjectParser.php @@ -46,35 +46,35 @@ class PDFObjectParser implements Stringable { // Possible tokens in a PDF document - const T_NOTOKEN = 0; + public const T_NOTOKEN = 0; - const T_LIST_START = 1; + public const T_LIST_START = 1; - const T_LIST_END = 2; + public const T_LIST_END = 2; - const T_FIELD = 3; + public const T_FIELD = 3; - const T_STRING = 4; + public const T_STRING = 4; - const T_HEX_STRING = 12; + public const T_HEX_STRING = 12; - const T_SIMPLE = 5; + public const T_SIMPLE = 5; - const T_DICT_START = 6; + public const T_DICT_START = 6; - const T_DICT_END = 7; + public const T_DICT_END = 7; - const T_OBJECT_BEGIN = 8; + public const T_OBJECT_BEGIN = 8; - const T_OBJECT_END = 9; + public const T_OBJECT_END = 9; - const T_STREAM_BEGIN = 10; + public const T_STREAM_BEGIN = 10; - const T_STREAM_END = 11; + public const T_STREAM_END = 11; - const T_COMMENT = 13; + public const T_COMMENT = 13; - const T_NAMES = [ + public const T_NAMES = [ self::T_NOTOKEN => 'no token', self::T_LIST_START => 'list start', self::T_LIST_END => 'list end', @@ -91,7 +91,7 @@ class PDFObjectParser implements Stringable self::T_COMMENT => 'comment', ]; - const T_SIMPLE_OBJECTS = [ + public const T_SIMPLE_OBJECTS = [ self::T_SIMPLE, self::T_OBJECT_BEGIN, self::T_OBJECT_END, @@ -111,46 +111,25 @@ class PDFObjectParser implements Stringable protected $_tt = self::T_NOTOKEN; /** - * Retrieves the current token type (one of T_* constants) + * Simple output of the object * - * @return token the current token + * @return output the output of the object */ - public function current_token() + public function __toString(): string { - return $this->_tt; + return 'pos: ' . $this->_buffer->getpos() . ", c: {$this->_c}, n: {$this->_n}, t: {$this->_t}, tt: " . + self::T_NAMES[$this->_tt] . ', b: ' . $this->_buffer->substratpos(50) . + "\n"; } /** - * Obtains the next char and prepares the variable $this->_c and $this->_n to contain the current char and the next char - * - if EOF, _c will be false - * - if the last char before EOF, _n will be false + * Retrieves the current token type (one of T_* constants) * - * @return char the next char - */ - protected function nextchar() - { - $this->_c = $this->_n; - $this->_n = $this->_buffer->nextchar(); - - return $this->_c; - } - - /** - * Prepares the parser to analythe the text (i.e. prepares the parsing variables) + * @return token the current token */ - protected function start(&$buffer) + public function current_token() { - $this->_buffer = $buffer; - $this->_c = false; - $this->_n = false; - $this->_t = false; - $this->_tt = self::T_NOTOKEN; - - if ($this->_buffer->size() === 0) { - return false; - } - $this->_n = $this->_buffer->currentchar(); - $this->nextchar(); + return $this->_tt; } /** @@ -174,25 +153,54 @@ public function parsestr($str, $offset = 0): PDFValueObject|PDFValueList|PDFValu } /** - * Simple output of the object + * Obtains the next token and returns it + */ + public function nexttoken() + { + [$this->_t, $this->_tt] = $this->token(); + + return $this->_t; + } + + public function tokenize(): void + { + $this->start(); + while ($this->nexttoken() !== false) { + echo "{$this->_t}\n"; + } + } + + /** + * Obtains the next char and prepares the variable $this->_c and $this->_n to contain the current char and the next char + * - if EOF, _c will be false + * - if the last char before EOF, _n will be false * - * @return output the output of the object + * @return char the next char */ - public function __toString(): string + protected function nextchar() { - return "pos: " . $this->_buffer->getpos() . ", c: $this->_c, n: $this->_n, t: $this->_t, tt: " . - self::T_NAMES[$this->_tt] . ', b: ' . $this->_buffer->substratpos(50) . - "\n"; + $this->_c = $this->_n; + $this->_n = $this->_buffer->nextchar(); + + return $this->_c; } /** - * Obtains the next token and returns it + * Prepares the parser to analythe the text (i.e. prepares the parsing variables) */ - public function nexttoken() + protected function start(&$buffer) { - [$this->_t, $this->_tt] = $this->token(); + $this->_buffer = $buffer; + $this->_c = false; + $this->_n = false; + $this->_t = false; + $this->_tt = self::T_NOTOKEN; - return $this->_t; + if ($this->_buffer->size() === 0) { + return false; + } + $this->_n = $this->_buffer->currentchar(); + $this->nextchar(); } /** @@ -200,7 +208,7 @@ public function nexttoken() */ protected function _c_is_separator(): bool { - $DSEPS = ["<<", ">>"]; + $DSEPS = ['<<', '>>']; return (($this->_c === false) || (str_contains("%<>()[]{}/ \n\r\t", (string) $this->_c)) || ((array_search($this->_c . $this->_n, $DSEPS)) !== false)); } @@ -212,10 +220,10 @@ protected function _c_is_separator(): bool */ protected function _parse_hex_string() { - $token = ""; + $token = ''; - if ($this->_c !== "<") { - throw new Exception("Invalid hex string"); + if ($this->_c !== '<') { + throw new Exception('Invalid hex string'); } $this->nextchar(); // This char is "<" @@ -226,12 +234,12 @@ protected function _parse_hex_string() } } if (($this->_c !== false) && (! str_contains(">0123456789abcdefABCDEF \t\r\n\f", $this->_c))) { - throw new Exception("invalid hex string"); + throw new Exception('invalid hex string'); } // The only way to get to here is that char is ">" - if ($this->_c !== ">") { - throw new Exception("Invalid hex string"); + if ($this->_c !== '>') { + throw new Exception('Invalid hex string'); } $this->nextchar(); @@ -241,9 +249,9 @@ protected function _parse_hex_string() protected function _parse_string() { - $token = ""; - if ($this->_c !== "(") { - throw new Exception("Invalid string"); + $token = ''; + if ($this->_c !== '(') { + throw new Exception('Invalid string'); } $n_parenthesis = 1; @@ -262,8 +270,8 @@ protected function _parse_string() } } - if ($this->_c !== ")") { - throw new Exception("Invalid string"); + if ($this->_c !== ')') { + throw new Exception('Invalid string'); } $this->nextchar(); @@ -289,7 +297,7 @@ protected function token() switch ($this->_c) { case '%': $this->nextchar(); - $token = ""; + $token = ''; while (! str_contains("\n\r", (string) $this->_c)) { $token .= $this->_c; $this->nextchar(); @@ -344,7 +352,7 @@ protected function token() break; } if ($token === false) { - $token = ""; + $token = ''; while (! $this->_c_is_separator()) { $token .= $this->_c; @@ -369,7 +377,7 @@ protected function token() protected function _parse_obj(): PDFValueObject|false { if ($this->_tt !== self::T_DICT_START) { - throw new Exception("Invalid object definition"); + throw new Exception('Invalid object definition'); } $this->nexttoken(); @@ -387,7 +395,7 @@ protected function _parse_obj(): PDFValueObject|false return new PDFValueObject($object); break; default: - throw new Exception("Invalid token: $this"); + throw new Exception("Invalid token: {$this}"); } } @@ -397,7 +405,7 @@ protected function _parse_obj(): PDFValueObject|false protected function _parse_list(): PDFValueList { if ($this->_tt !== self::T_LIST_START) { - throw new Exception("Invalid list definition"); + throw new Exception('Invalid list definition'); } $this->nexttoken(); @@ -413,7 +421,7 @@ protected function _parse_list(): PDFValueList case self::T_OBJECT_END: case self::T_STREAM_BEGIN: case self::T_STREAM_END: - throw new Exception("Invalid list definition"); + throw new Exception('Invalid list definition'); break; default: $value = $this->_parse_value(); @@ -456,7 +464,7 @@ protected function _parse_value(): PDFValueObject|false|PDFValueList|PDFValueStr return $field; case self::T_OBJECT_BEGIN: case self::T_STREAM_END: - throw new Exception("invalid keyword"); + throw new Exception('invalid keyword'); case self::T_OBJECT_END: case self::T_STREAM_BEGIN: return null; @@ -468,7 +476,7 @@ protected function _parse_value(): PDFValueObject|false|PDFValueList|PDFValueStr $this->nexttoken(); while (($this->_t !== false) && ($this->_tt == self::T_SIMPLE)) { - $simple_value .= " " . $this->_t; + $simple_value .= ' ' . $this->_t; $this->nexttoken(); } @@ -482,18 +490,10 @@ protected function _parse_value(): PDFValueObject|false|PDFValueList|PDFValueStr break; default: - throw new Exception("Invalid token: $this"); + throw new Exception("Invalid token: {$this}"); } } return false; } - - function tokenize(): void - { - $this->start(); - while ($this->nexttoken() !== false) { - echo "$this->_t\n"; - } - } } diff --git a/src/PDFSignatureObject.php b/src/PDFSignatureObject.php index 7d484a5..2c1aaf8 100644 --- a/src/PDFSignatureObject.php +++ b/src/PDFSignatureObject.php @@ -21,9 +21,9 @@ namespace ddn\sapp; -use function ddn\sapp\helpers\timestamp_to_pdfdatestring; use ddn\sapp\pdfvalue\PDFValueSimple; use ddn\sapp\pdfvalue\PDFValueString; +use function ddn\sapp\helpers\timestamp_to_pdfdatestring; // This is an special object that has a set of fields class PDFSignatureObject extends PDFObject @@ -47,6 +47,25 @@ class PDFSignatureObject extends PDFObject protected $_signature_tsa = null; + /** + * Constructs the object and sets the default values needed to sign + * + * @param oid the oid for the object + */ + public function __construct(int $oid) + { + $this->_prev_content_size = 0; + $this->_post_content_size = null; + parent::__construct($oid, [ + 'Filter' => '/Adobe.PPKLite', + 'Type' => '/Sig', + 'SubFilter' => '/adbe.pkcs7.detached', + 'ByteRange' => new PDFValueSimple(str_repeat(' ', self::$__BYTERANGE_SIZE)), + 'Contents' => '<' . str_repeat('0', self::$__SIGNATURE_MAX_LENGTH) . '>', + 'M' => new PDFValueString(timestamp_to_pdfdatestring()), + ]); + } + /** * Sets the certificate to use to sign * @@ -88,25 +107,6 @@ public function get_ltv() return $this->_signature_ltv_data; } - /** - * Constructs the object and sets the default values needed to sign - * - * @param oid the oid for the object - */ - public function __construct($oid) - { - $this->_prev_content_size = 0; - $this->_post_content_size = null; - parent::__construct($oid, [ - 'Filter' => "/Adobe.PPKLite", - 'Type' => "/Sig", - 'SubFilter' => "/adbe.pkcs7.detached", - 'ByteRange' => new PDFValueSimple(str_repeat(" ", self::$__BYTERANGE_SIZE)), - 'Contents' => "<" . str_repeat("0", self::$__SIGNATURE_MAX_LENGTH) . ">", - 'M' => new PDFValueString(timestamp_to_pdfdatestring()), - ]); - } - /** * Function used to add some metadata fields to the signature: name, reason of signature, etc. * @@ -118,16 +118,16 @@ public function __construct($oid) public function set_metadata($name = null, $reason = null, $location = null, $contact = null): void { if ($name !== null) { - $this->_value["Name"] = new PDFValueString($name); + $this->_value['Name'] = new PDFValueString($name); } if ($reason !== null) { - $this->_value["Reason"] = new PDFValueString($reason); + $this->_value['Reason'] = new PDFValueString($reason); } if ($location !== null) { - $this->_value["Location"] = new PDFValueString($location); + $this->_value['Location'] = new PDFValueString($location); } if ($contact !== null) { - $this->_value["ContactInfo"] = new PDFValueString($contact); + $this->_value['ContactInfo'] = new PDFValueString($contact); } } @@ -151,7 +151,7 @@ public function set_sizes(int $prev_content_size, $post_content_size = null): vo public function get_signature_marker_offset(): int { $tmp_output = parent::to_pdf_entry(); - $marker = "/Contents"; + $marker = '/Contents'; $position = strpos($tmp_output, $marker); return $position + strlen($marker); @@ -169,16 +169,16 @@ public function to_pdf_entry(): string $offset = $this->get_signature_marker_offset(); $starting_second_part = $this->_prev_content_size + $offset + self::$__SIGNATURE_MAX_LENGTH + 2; - $contents_size = strlen("" . $this->_value['Contents']); + $contents_size = strlen('' . $this->_value['Contents']); - $byterange_str = "[ 0 " . - ($this->_prev_content_size + $offset) . " " . - ($starting_second_part) . " " . - ($this->_post_content_size !== null ? $this->_post_content_size + ($signature_size - $contents_size - $offset) : 0) . " ]"; + $byterange_str = '[ 0 ' . + ($this->_prev_content_size + $offset) . ' ' . + ($starting_second_part) . ' ' . + ($this->_post_content_size !== null ? $this->_post_content_size + ($signature_size - $contents_size - $offset) : 0) . ' ]'; $this->_value['ByteRange'] = new PDFValueSimple( - $byterange_str . str_repeat(" ", self::$__BYTERANGE_SIZE - strlen($byterange_str) + 1) + $byterange_str . str_repeat(' ', self::$__BYTERANGE_SIZE - strlen($byterange_str) + 1) ); return parent::to_pdf_entry(); diff --git a/src/PDFUtilFnc.php b/src/PDFUtilFnc.php index 0005b58..ed426c5 100644 --- a/src/PDFUtilFnc.php +++ b/src/PDFUtilFnc.php @@ -23,11 +23,11 @@ use ddn\sapp\helpers\Buffer; use ddn\sapp\helpers\LoadHelpers; +use ddn\sapp\helpers\StreamReader; use function ddn\sapp\helpers\p_error; use function ddn\sapp\helpers\p_warning; -use ddn\sapp\helpers\StreamReader; -if (! defined("ddn\\sapp\\helpers\\LoadHelpers")) { +if (! defined('ddn\\sapp\\helpers\\LoadHelpers')) { new LoadHelpers(); } @@ -39,7 +39,7 @@ public static function get_trailer(&$_buffer, $trailer_pos) { // Search for the trailer structure if (preg_match('/trailer\s*(.*)\s*startxref/ms', (string) $_buffer, $matches, 0, $trailer_pos) !== 1) { - return p_error("trailer not found"); + return p_error('trailer not found'); } $trailer_str = $matches[1]; @@ -50,7 +50,7 @@ public static function get_trailer(&$_buffer, $trailer_pos) try { $trailer_obj = $parser->parsestr($trailer_str); } catch (Exception) { - return p_error("trailer is not valid"); + return p_error('trailer is not valid'); } return $trailer_obj; @@ -68,7 +68,7 @@ public static function build_xref_1_5($offsets): array $i_k = 0; $c_k = 0; $count = 1; - $result = ""; + $result = ''; for ($i = 0; $i < count($k); $i++) { if ($c_k === 0) { $c_k = $k[$i] - 1; @@ -78,7 +78,7 @@ public static function build_xref_1_5($offsets): array if ($k[$i] === $c_k + 1) { $count++; } else { - array_push($indexes, "$i_k $count"); + array_push($indexes, "{$i_k} {$count}"); $count = 1; $i_k = $k[$i]; } @@ -86,8 +86,8 @@ public static function build_xref_1_5($offsets): array if (is_array($c_offset)) { $result .= pack('C', 2); - $result .= pack('N', $c_offset["stmoid"]); - $result .= pack('C', $c_offset["pos"]); + $result .= pack('N', $c_offset['stmoid']); + $result .= pack('C', $c_offset['pos']); } else { if ($c_offset === null) { $result .= pack('C', 0); @@ -101,15 +101,15 @@ public static function build_xref_1_5($offsets): array } $c_k = $k[$i]; } - array_push($indexes, "$i_k $count"); - $indexes = implode(" ", $indexes); + array_push($indexes, "{$i_k} {$count}"); + $indexes = implode(' ', $indexes); // p_debug(show_bytes($result, 6)); return [ - "W" => [1, 4, 1], - "Index" => $indexes, - "stream" => $result, + 'W' => [1, 4, 1], + 'Index' => $indexes, + 'stream' => $result, ]; } @@ -127,41 +127,41 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) $depth = $depth - 1; } - $xref_o = PDFUtilFnc::find_object_at_pos($_buffer, null, $xref_pos, []); + $xref_o = self::find_object_at_pos($_buffer, null, $xref_pos, []); if ($xref_o === false) { - return p_error("cross reference object not found when parsing xref at position $xref_pos", [false, false, false]); + return p_error("cross reference object not found when parsing xref at position {$xref_pos}", [false, false, false]); } - if (! (isset($xref_o["Type"])) || ($xref_o["Type"]->val() !== "XRef")) { - return p_error("invalid xref table", [false, false, false]); + if (! (isset($xref_o['Type'])) || ($xref_o['Type']->val() !== 'XRef')) { + return p_error('invalid xref table', [false, false, false]); } $stream = $xref_o->get_stream(false); if ($stream === null) { - return p_error("cross reference stream not found when parsing xref at position $xref_pos", [false, false, false]); + return p_error("cross reference stream not found when parsing xref at position {$xref_pos}", [false, false, false]); } - $W = $xref_o["W"]->val(true); + $W = $xref_o['W']->val(true); if (count($W) !== 3) { - return p_error("invalid cross reference object", [false, false, false]); + return p_error('invalid cross reference object', [false, false, false]); } $W[0] = intval($W[0]); $W[1] = intval($W[1]); $W[2] = intval($W[2]); - $Size = $xref_o["Size"]->get_int(); + $Size = $xref_o['Size']->get_int(); if ($Size === false) { - return p_error("could not get the size of the xref table", [false, false, false]); + return p_error('could not get the size of the xref table', [false, false, false]); } $Index = [0, $Size]; - if (isset($xref_o["Index"])) { - $Index = $xref_o["Index"]->val(true); + if (isset($xref_o['Index'])) { + $Index = $xref_o['Index']->val(true); } if (count($Index) % 2 !== 0) { - return p_error("invalid indexes of xref table", [false, false, false]); + return p_error('invalid indexes of xref table', [false, false, false]); } // Get the previous xref table, to build up on it @@ -171,15 +171,15 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) if (($depth === null) || ($depth > 0)) { // If still want to get more versions, let's check whether there is a previous xref table or not - if (isset($xref_o["Prev"])) { - $Prev = $xref_o["Prev"]; + if (isset($xref_o['Prev'])) { + $Prev = $xref_o['Prev']; $Prev = $Prev->get_int(); if ($Prev === false) { - return p_error("invalid reference to a previous xref table", [false, false, false]); + return p_error('invalid reference to a previous xref table', [false, false, false]); } // When dealing with 1.5 cross references, we do not allow to use other than cross references - [$xref_table, $trailer_obj] = PDFUtilFnc::get_xref_1_5($_buffer, $Prev, $depth); + [$xref_table, $trailer_obj] = self::get_xref_1_5($_buffer, $Prev, $depth); // p_debug_var($xref_table); } } @@ -194,11 +194,11 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) } return match ($f) { - 0 => fn($v): int => 0, - 1 => fn($v) => unpack('C', str_pad($v, 1, chr(0), STR_PAD_LEFT))[1], - 2 => fn($v) => unpack('n', str_pad($v, 2, chr(0), STR_PAD_LEFT))[1], - 3, 4 => fn($v) => unpack('N', str_pad($v, 4, chr(0), STR_PAD_LEFT))[1], - 5, 6, 7, 8 => fn($v) => unpack('J', str_pad($v, 8, chr(0), STR_PAD_LEFT))[1], + 0 => fn ($v): int => 0, + 1 => fn ($v) => unpack('C', str_pad($v, 1, chr(0), STR_PAD_LEFT))[1], + 2 => fn ($v) => unpack('n', str_pad($v, 2, chr(0), STR_PAD_LEFT))[1], + 3, 4 => fn ($v) => unpack('N', str_pad($v, 4, chr(0), STR_PAD_LEFT))[1], + 5, 6, 7, 8 => fn ($v) => unpack('J', str_pad($v, 8, chr(0), STR_PAD_LEFT))[1], default => false, }; }; @@ -224,7 +224,7 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) $f3 = $fmt_function[2]($stream_v->nextchars($W[2])); if (($f1 === false) || ($f2 === false) || ($f3 === false)) { - return p_error("invalid stream for xref table", [false, false, false]); + return p_error('invalid stream for xref table', [false, false, false]); } switch ($f1) { @@ -239,7 +239,7 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) TODO: consider creating a generation table, but for the purpose of the xref there is no matter... if the document if well-formed. */ if ($f3 !== 0) { - p_warning("Objects of non-zero generation are not fully checked... please double check your document and (if possible) please send examples via issues to https://github.com/dealfonso/sapp/issues/"); + p_warning('Objects of non-zero generation are not fully checked... please double check your document and (if possible) please send examples via issues to https://github.com/dealfonso/sapp/issues/'); } break; @@ -247,12 +247,12 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) // Stream object // $f2 is the number of a stream object, $f3 is the index in that stream object $xref_table[$object_i] = [ - "stmoid" => $f2, - "pos" => $f3, + 'stmoid' => $f2, + 'pos' => $f3, ]; break; default: - p_error("do not know about entry of type $f1 in xref table"); + p_error("do not know about entry of type {$f1} in xref table"); } $object_i++; @@ -260,7 +260,7 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) } } - return [$xref_table, $xref_o->get_value(), "1.5"]; + return [$xref_table, $xref_o->get_value(), '1.5']; } public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) @@ -273,8 +273,8 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) $depth = $depth - 1; } - $trailer_pos = strpos((string) $_buffer, "trailer", $xref_pos); - $min_pdf_version = "1.4"; + $trailer_pos = strpos((string) $_buffer, 'trailer', $xref_pos); + $min_pdf_version = '1.4'; // Get the xref content and make sure that the buffer passed contains the xref tag at the offset provided $xref_substr = substr((string) $_buffer, $xref_pos, $trailer_pos - $xref_pos); @@ -282,7 +282,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) $separator = "\r\n"; $xref_line = strtok($xref_substr, $separator); if ($xref_line !== 'xref') { - return p_error("xref tag not found at position $xref_pos", [false, false, false]); + return p_error("xref tag not found at position {$xref_pos}", [false, false, false]); } // Now parse the lines and build the xref table @@ -295,7 +295,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) if (preg_match('/([0-9]+) ([0-9]+)$/', $xref_line, $matches) === 1) { if ($obj_count > 0) { // If still expecting objects, we'll assume that the xref is malformed - return p_error("malformed xref at position $xref_pos", [false, false, false]); + return p_error("malformed xref at position {$xref_pos}", [false, false, false]); } $obj_id = intval($matches[1]); $obj_count = intval($matches[2]); @@ -306,7 +306,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) if (preg_match('/^([0-9]+) ([0-9]+) (.)\s*/', $xref_line, $matches) === 1) { // If no object expected, we'll assume that the xref is malformed if ($obj_count === 0) { - return p_error("unexpected entry for xref: $xref_line", [false, false, false]); + return p_error("unexpected entry for xref: {$xref_line}", [false, false, false]); } $obj_offset = intval($matches[1]); @@ -337,41 +337,41 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) // TODO: consider creating a "generation table" $xref_table[$obj_id] = $obj_offset; if ($obj_generation != 0) { - p_warning("Objects of non-zero generation are not fully checked... please double check your document and (if possible) please send examples via issues to https://github.com/dealfonso/sapp/issues/"); + p_warning('Objects of non-zero generation are not fully checked... please double check your document and (if possible) please send examples via issues to https://github.com/dealfonso/sapp/issues/'); } break; default: // If it is not one of the expected, let's skip the object - p_error("invalid entry for xref: $xref_line", [false, false, false]); + p_error("invalid entry for xref: {$xref_line}", [false, false, false]); } } - $obj_count -= 1; + --$obj_count; $obj_id++; continue; } // If the entry is not recongised, show the error - p_error("invalid xref entry $xref_line"); + p_error("invalid xref entry {$xref_line}"); $xref_line = strtok($separator); } // Get the trailer object - $trailer_obj = PDFUtilFnc::get_trailer($_buffer, $trailer_pos); + $trailer_obj = self::get_trailer($_buffer, $trailer_pos); // If there exists a previous xref (for incremental PDFs), get it and merge the objects that do not exist in the current xref table if (isset($trailer_obj['Prev'])) { $xref_prev_pos = $trailer_obj['Prev']->val(); if (! is_numeric($xref_prev_pos)) { - return p_error("invalid trailer $trailer_obj", [false, false, false]); + return p_error("invalid trailer {$trailer_obj}", [false, false, false]); } $xref_prev_pos = intval($xref_prev_pos); - [$prev_table, $prev_trailer, $prev_min_pdf_version] = PDFUtilFnc::get_xref_1_4($_buffer, $xref_prev_pos, $depth); + [$prev_table, $prev_trailer, $prev_min_pdf_version] = self::get_xref_1_4($_buffer, $xref_prev_pos, $depth); if ($prev_min_pdf_version !== $min_pdf_version) { - return p_error("mixed type of xref tables are not supported", [false, false, false]); + return p_error('mixed type of xref tables are not supported', [false, false, false]); } if ($prev_table !== false) { @@ -390,11 +390,11 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) public static function get_xref(&$_buffer, $xref_pos, $depth = null): array { // Each xref is immediately followed by a trailer - $trailer_pos = strpos((string) $_buffer, "trailer", $xref_pos); + $trailer_pos = strpos((string) $_buffer, 'trailer', $xref_pos); if ($trailer_pos === false) { - [$xref_table, $trailer_obj, $min_pdf_version] = PDFUtilFnc::get_xref_1_5($_buffer, $xref_pos, $depth); + [$xref_table, $trailer_obj, $min_pdf_version] = self::get_xref_1_5($_buffer, $xref_pos, $depth); } else { - [$xref_table, $trailer_obj, $min_pdf_version] = PDFUtilFnc::get_xref_1_4($_buffer, $xref_pos, $depth); + [$xref_table, $trailer_obj, $min_pdf_version] = self::get_xref_1_4($_buffer, $xref_pos, $depth); } return [$xref_table, $trailer_obj, $min_pdf_version]; @@ -410,11 +410,11 @@ public static function acquire_structure(&$_buffer, $depth = null) } if (preg_match('/^%PDF-[0-9]+\.[0-9]+$/', $pdf_version, $matches) !== 1) { - return p_error("PDF version string not found"); + return p_error('PDF version string not found'); } if (preg_match_all('/startxref\s*([0-9]+)\s*%%EOF($|[\r\n])/ms', (string) $_buffer, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE) === false) { - return p_error("failed to get structure"); + return p_error('failed to get structure'); } $_versions = []; @@ -427,13 +427,13 @@ public static function acquire_structure(&$_buffer, $depth = null) } // Now get the trailing part and make sure that it has the proper form - $startxref_pos = strrpos((string) $_buffer, "startxref"); + $startxref_pos = strrpos((string) $_buffer, 'startxref'); if ($startxref_pos === false) { - return p_error("startxref not found"); + return p_error('startxref not found'); } if (preg_match('/startxref\s*([0-9]+)\s*%%EOF\s*$/ms', (string) $_buffer, $matches, 0, $startxref_pos) !== 1) { - return p_error("startxref and %%EOF not found"); + return p_error('startxref and %%EOF not found'); } $xref_pos = intval($matches[1]); @@ -441,34 +441,34 @@ public static function acquire_structure(&$_buffer, $depth = null) if ($xref_pos === 0) { // This is a dummy xref position from linearized documents return [ - "trailer" => false, - "version" => substr($pdf_version, 1), - "xref" => [], - "xrefposition" => 0, - "xrefversion" => substr($pdf_version, 1), - "revisions" => $_versions, + 'trailer' => false, + 'version' => substr($pdf_version, 1), + 'xref' => [], + 'xrefposition' => 0, + 'xrefversion' => substr($pdf_version, 1), + 'revisions' => $_versions, ]; } - [$xref_table, $trailer_object, $min_pdf_version] = PDFUtilFnc::get_xref($_buffer, $xref_pos, $depth); + [$xref_table, $trailer_object, $min_pdf_version] = self::get_xref($_buffer, $xref_pos, $depth); // We are providing a lot of information to be able to inspect the problems of a PDF file if ($xref_table === false) { // TODO: Maybe we could include a "recovery" method for this: if xref is not at pos $xref_pos, we could search for xref by hand - return p_error("could not find the xref table"); + return p_error('could not find the xref table'); } if ($trailer_object === false) { - return p_error("could not find the trailer object"); + return p_error('could not find the trailer object'); } return [ - "trailer" => $trailer_object, - "version" => substr($pdf_version, 1), - "xref" => $xref_table, - "xrefposition" => $xref_pos, - "xrefversion" => $min_pdf_version, - "revisions" => $_versions, + 'trailer' => $trailer_object, + 'version' => substr($pdf_version, 1), + 'xref' => $xref_table, + 'xrefposition' => $xref_pos, + 'xrefversion' => $min_pdf_version, + 'revisions' => $_versions, ]; } @@ -484,7 +484,7 @@ public static function acquire_structure(&$_buffer, $depth = null) */ public static function find_object_at_pos(&$_buffer, int $oid, $object_offset, $xref_table) { - $object = PDFUtilFnc::object_from_string($_buffer, $oid, $object_offset, $offset_end); + $object = self::object_from_string($_buffer, $oid, $object_offset, $offset_end); if ($object === false) { return false; @@ -509,18 +509,18 @@ public static function find_object_at_pos(&$_buffer, int $oid, $object_offset, $ if ($length === false) { $length_object_id = $object['Length']->get_object_referenced(); if ($length_object_id === false) { - return p_error("could not get stream for object $oid"); + return p_error("could not get stream for object {$oid}"); } - $length_object = PDFUtilFnc::find_object($_buffer, $xref_table, $length_object_id); + $length_object = self::find_object($_buffer, $xref_table, $length_object_id); if ($length_object === false) { - return p_error("could not get object $oid"); + return p_error("could not get object {$oid}"); } $length = $length_object->get_value()->get_int(); } if ($length === false) { - return p_error("could not get stream length for object $oid"); + return p_error("could not get stream length for object {$oid}"); } $object->set_stream(substr((string) $_buffer, $_stream_pending, $length), true); @@ -551,12 +551,12 @@ public static function find_object(&$_buffer, $xref_table, int $oid) $object_offset = $xref_table[$oid]; if (! is_array($object_offset)) { - return PDFUtilFnc::find_object_at_pos($_buffer, $oid, $object_offset, $xref_table); - } else { - $object = PDFUtilFnc::find_object_in_objstm($_buffer, $xref_table, $object_offset["stmoid"], $object_offset["pos"], $oid); - - return $object; + return self::find_object_at_pos($_buffer, $oid, $object_offset, $xref_table); } + $object = self::find_object_in_objstm($_buffer, $xref_table, $object_offset['stmoid'], $object_offset['pos'], $oid); + + return $object; + } /** @@ -564,26 +564,25 @@ public static function find_object(&$_buffer, $xref_table, int $oid) */ public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm_oid, $objpos, int $oid) { - $objstm = PDFUtilFnc::find_object($_buffer, $xref_table, $objstm_oid); + $objstm = self::find_object($_buffer, $xref_table, $objstm_oid); if ($objstm === false) { - return p_error("could not get object stream $objstm_oid"); + return p_error("could not get object stream {$objstm_oid}"); } - if (($objstm["Extends"] ?? false !== false)) // TODO: support them - { - return p_error("not supporting extended object streams at this time"); + if (($objstm['Extends'] ?? false !== false)) { // TODO: support them + return p_error('not supporting extended object streams at this time'); } - $First = $objstm["First"] ?? false; - $N = $objstm["N"] ?? false; - $Type = $objstm["Type"] ?? false; + $First = $objstm['First'] ?? false; + $N = $objstm['N'] ?? false; + $Type = $objstm['Type'] ?? false; if (($First === false) || ($N === false) || ($Type === false)) { - return p_error("invalid object stream $objstm_oid"); + return p_error("invalid object stream {$objstm_oid}"); } - if ($Type->val() !== "ObjStm") { - return p_error("object $objstm_oid is not an object stream"); + if ($Type->val() !== 'ObjStm') { + return p_error("object {$objstm_oid} is not an object stream"); } $First = $First->get_int(); @@ -591,16 +590,16 @@ public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm $stream = $objstm->get_stream(false); $index = substr((string) $stream, 0, $First); - $index = explode(" ", trim($index)); + $index = explode(' ', trim($index)); $stream = substr((string) $stream, $First); if (count($index) % 2 !== 0) { - return p_error("invalid index for object stream $objstm_oid"); + return p_error("invalid index for object stream {$objstm_oid}"); } $objpos = $objpos * 2; if ($objpos > count($index)) { - return p_error("object $oid not found in object stream $objstm_oid"); + return p_error("object {$oid} not found in object stream {$objstm_oid}"); } $offset = intval($index[$objpos + 1]); @@ -617,8 +616,8 @@ public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm $next = $offsets[$i]; - $object_def_str = "$oid 0 obj " . substr($stream, $offset, $next - $offset) . " endobj"; - $object_def = PDFUtilFnc::object_from_string($object_def_str, $oid); + $object_def_str = "{$oid} 0 obj " . substr($stream, $offset, $next - $offset) . ' endobj'; + $object_def = self::object_from_string($object_def_str, $oid); return $object_def; } @@ -630,7 +629,7 @@ public static function object_from_string(&$buffer, $expected_obj_id, $offset = { if (preg_match('/([0-9]+)\s+([0-9+])\s+obj(\s+)/ms', (string) $buffer, $matches, 0, $offset) !== 1) { // p_debug_var(substr($buffer)) - return p_error("object is not valid: $expected_obj_id"); + return p_error("object is not valid: {$expected_obj_id}"); } $found_obj_header = $matches[0]; @@ -642,7 +641,7 @@ public static function object_from_string(&$buffer, $expected_obj_id, $offset = } if ($found_obj_id !== $expected_obj_id) { - return p_error("pdf structure is corrupt: found obj $found_obj_id while searching for obj $expected_obj_id (at $offset)"); + return p_error("pdf structure is corrupt: found obj {$found_obj_id} while searching for obj {$expected_obj_id} (at {$offset})"); } // The object starts after the header @@ -655,7 +654,7 @@ public static function object_from_string(&$buffer, $expected_obj_id, $offset = $obj_parsed = $parser->parse($stream); if ($obj_parsed === false) { - return p_error("object $expected_obj_id could not be parsed"); + return p_error("object {$expected_obj_id} could not be parsed"); } switch ($parser->current_token()) { @@ -666,7 +665,7 @@ public static function object_from_string(&$buffer, $expected_obj_id, $offset = // There is an stream break; default: - return p_error("malformed object"); + return p_error('malformed object'); } $offset_end = $stream->getpos(); @@ -690,7 +689,7 @@ public static function build_xref(array $offsets): string $i_k = 0; $c_k = 0; $count = 1; - $result = ""; + $result = ''; $references = "0000000000 65535 f \n"; for ($i = 0; $i < count($k); $i++) { if ($k[$i] === 0) { @@ -699,16 +698,16 @@ public static function build_xref(array $offsets): string if ($k[$i] === $c_k + 1) { $count++; } else { - $result = $result . "$i_k {$count}\n$references"; + $result = $result . "{$i_k} {$count}\n{$references}"; $count = 1; $i_k = $k[$i]; - $references = ""; + $references = ''; } $references .= sprintf("%010d 00000 n \n", $offsets[$k[$i]]); $c_k = $k[$i]; } - $result = $result . "$i_k {$count}\n$references"; + $result = $result . "{$i_k} {$count}\n{$references}"; - return "xref\n$result"; + return "xref\n{$result}"; } } diff --git a/src/helpers/Buffer.php b/src/helpers/Buffer.php index f4de798..4b613f5 100644 --- a/src/helpers/Buffer.php +++ b/src/helpers/Buffer.php @@ -34,20 +34,41 @@ */ class Buffer implements Stringable { - protected $_buffer = ""; + protected $_buffer = ''; protected int $_bufferlen; public function __construct($string = null) { if ($string === null) { - $string = ""; + $string = ''; } $this->_buffer = $string; $this->_bufferlen = strlen((string) $string); } + /** + * Provides a easy to read string representation of the buffer, using the "var_dump" output + * of the variable, but providing a reduced otput of the buffer + * + * @return str a string with the representation of the buffer + */ + public function __toString(): string + { + if (strlen((string) $this->_buffer) < (__CONVENIENT_MAX_BUFFER_DUMP * 2)) { + return (string) debug_var($this); + } + + $buffer = $this->_buffer; + $this->_buffer = substr((string) $buffer, 0, __CONVENIENT_MAX_BUFFER_DUMP); + $this->_buffer .= "\n...\n" . substr((string) $buffer, -__CONVENIENT_MAX_BUFFER_DUMP); + $result = debug_var($this); + $this->_buffer = $buffer; + + return (string) $result; + } + /** * Adds raw data to the buffer * @@ -107,7 +128,7 @@ public function append($b): static * * @return buffer the resulting buffer (different from this one) */ - public function add(...$bs): Buffer + public function add(...$bs): self { foreach ($bs as $b) { if ($b::class !== static::class) { @@ -115,7 +136,7 @@ public function add(...$bs): Buffer } } - $r = new Buffer($this->_buffer); + $r = new self($this->_buffer); foreach ($bs as $b) { $r->append($b); } @@ -128,45 +149,24 @@ public function add(...$bs): Buffer * * @return buffer the cloned buffer */ - public function clone(): Buffer + public function clone(): self { - $buffer = new Buffer($this->_buffer); + $buffer = new self($this->_buffer); return $buffer; } - /** - * Provides a easy to read string representation of the buffer, using the "var_dump" output - * of the variable, but providing a reduced otput of the buffer - * - * @return str a string with the representation of the buffer - */ - public function __toString(): string - { - if (strlen((string) $this->_buffer) < (__CONVENIENT_MAX_BUFFER_DUMP * 2)) { - return (string) debug_var($this); - } - - $buffer = $this->_buffer; - $this->_buffer = substr((string) $buffer, 0, __CONVENIENT_MAX_BUFFER_DUMP); - $this->_buffer .= "\n...\n" . substr((string) $buffer, -__CONVENIENT_MAX_BUFFER_DUMP); - $result = debug_var($this); - $this->_buffer = $buffer; - - return (string) $result; - } - public function show_bytes($columns, $offset = 0, $length = null): string { if ($length === null) { $length = $this->_bufferlen; } - $result = ""; + $result = ''; $length = min($length, $this->_bufferlen); for ($i = $offset; $i < $length;) { for ($j = 0; ($j < $columns) && ($i < $length); $i++, $j++) { - $result .= sprintf("%02x ", ord($this->_buffer[$i])); + $result .= sprintf('%02x ', ord($this->_buffer[$i])); } $result .= "\n"; } diff --git a/src/helpers/CMS.php b/src/helpers/CMS.php index f07ba21..1661d42 100644 --- a/src/helpers/CMS.php +++ b/src/helpers/CMS.php @@ -57,7 +57,7 @@ public function sendReq($reqData) } } if ($code != '200') { - p_error(" response error! Code=\"$code\", Status=\"" . trim($status ?? "") . "\""); + p_error(" response error! Code=\"{$code}\", Status=\"" . trim($status ?? '') . '"'); return false; } @@ -65,13 +65,13 @@ public function sendReq($reqData) $headers = explode("\n", $header); foreach ($headers as $key => $r) { // Match the header name up to ':', compare lower case - if (stripos($r, "Content-Type" . ':') === 0) { - [$headername, $headervalue] = explode(":", $r, 2); + if (stripos($r, 'Content-Type' . ':') === 0) { + [$headername, $headervalue] = explode(':', $r, 2); $contentTypeHeader = trim($headervalue); } } if ($contentTypeHeader != $reqData['resp_contentType']) { - p_error(" response content type not {$reqData['resp_contentType']}, but: \"$contentTypeHeader\""); + p_error(" response content type not {$reqData['resp_contentType']}, but: \"{$contentTypeHeader}\""); return false; } @@ -84,71 +84,192 @@ public function sendReq($reqData) } /** - * parse tsa response to array + * Perform PKCS7 Signing * - * @param string $binaryTsaRespData binary tsa response to parse + * @param string $binaryData * - * @return array asn.1 hex structure of tsa response + * @return string hex + padding 0 + * @public */ - private function tsa_parseResp($binaryTsaRespData) + public function pkcs7_sign($binaryData) { - if (! @$ar = asn1::parse(bin2hex($binaryTsaRespData), 3)) { - p_error(" can't parse invalid tsa Response."); + $hexOidHashAlgos = [ + 'md2' => '06082A864886F70D0202', + 'md4' => '06082A864886F70D0204', + 'md5' => '06082A864886F70D0205', + 'sha1' => '06052B0E03021A', + 'sha224' => '0609608648016503040204', + 'sha256' => '0609608648016503040201', + 'sha384' => '0609608648016503040202', + 'sha512' => '0609608648016503040203', + ]; + $hashAlgorithm = $this->signature_data['hashAlgorithm']; + if (! array_key_exists($hashAlgorithm, $hexOidHashAlgos)) { + p_error('not support hash algorithm!'); return false; } - $curr = $ar; - foreach ($curr as $key => $value) { - if ($value['type'] == '30') { - $curr['TimeStampResp'] = $curr[$key]; - unset($curr[$key]); - } + p_debug("hash algorithm is \"{$hashAlgorithm}\""); + $x509 = new x509(); + if (! $certParse = $x509->readcert($this->signature_data['signcert'])) { + p_error('certificate error! check certificate'); } - $ar = $curr; - $curr = $ar['TimeStampResp']; - foreach ($curr as $key => $value) { - if (is_numeric($key)) { - if ($value['type'] == '30' && ! array_key_exists('status', $curr)) { - $curr['status'] = $curr[$key]; - unset($curr[$key]); - } else { - if ($value['type'] == '30') { - $curr['timeStampToken'] = $curr[$key]; - unset($curr[$key]); - } + $hexEmbedCerts[] = bin2hex($x509->get_cert($this->signature_data['signcert'])); + $appendLTV = ''; + $ltvData = $this->signature_data['ltv']; + if (! empty($ltvData)) { + p_debug(' LTV Validation start...'); + $appendLTV = ''; + $LTVvalidation_ocsp = ''; + $LTVvalidation_crl = ''; + $LTVvalidation_issuer = ''; + $LTVvalidationEnd = false; + + $isRootCA = false; + if ($certParse['tbsCertificate']['issuer']['hexdump'] == $certParse['tbsCertificate']['subject']['hexdump']) { // check whether root ca + if (openssl_public_decrypt(hex2bin((string) $certParse['signatureValue']), $decrypted, x509::x509_der2pem($x509->get_cert($this->signature_data['signcert'])), OPENSSL_PKCS1_PADDING)) { + p_debug("***** \"{$certParse['tbsCertificate']['subject']['2.5.4.3'][0]}\" is a ROOT CA. No validation performed ***"); + $isRootCA = true; } } - } - $ar['TimeStampResp'] = $curr; - $curr = $ar['TimeStampResp']['timeStampToken']; - foreach ($curr as $key => $value) { - if (is_numeric($key)) { - if ($value['type'] == '06') { - $curr['contentType'] = $curr[$key]; - unset($curr[$key]); + if ($isRootCA == false) { + $i = 0; + $LTVvalidation = true; + $certtoCheck = $certParse; + while ($LTVvalidation !== false) { + p_debug("========= {$i} checking \"{$certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0]}\"==============="); + $LTVvalidation = self::LTVvalidation($certtoCheck); + $i++; + if ($LTVvalidation) { + $curr_issuer = $LTVvalidation['issuer']; + $certtoCheck = $x509->readcert($curr_issuer, 'oid'); + if (@$LTVvalidation['ocsp'] || @$LTVvalidation['crl']) { + $LTVvalidation_ocsp .= $LTVvalidation['ocsp']; + $LTVvalidation_crl .= $LTVvalidation['crl']; + $hexEmbedCerts[] = bin2hex((string) $LTVvalidation['issuer']); + } + + if ($certtoCheck['tbsCertificate']['issuer']['hexdump'] == $certtoCheck['tbsCertificate']['subject']['hexdump']) { // check whether root ca + if (openssl_public_decrypt(hex2bin((string) $certtoCheck['signatureValue']), $decrypted, $x509->x509_der2pem($curr_issuer), OPENSSL_PKCS1_PADDING)) { + p_debug("========= FINISH Reached ROOT CA \"{$certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0]}\"==============="); + $LTVvalidationEnd = true; + break; + } + } + } } - if ($value['type'] == 'a0') { - $curr['content'] = $curr[$key]; - unset($curr[$key]); + + if ($LTVvalidationEnd) { + p_debug(" LTV Validation SUCCESS\n"); + $ocsp = ''; + if (! empty($LTVvalidation_ocsp)) { + $ocsp = asn1::expl( + 1, + asn1::seq( + $LTVvalidation_ocsp + ) + ); + } + $crl = ''; + if (! empty($LTVvalidation_crl)) { + $crl = asn1::expl( + 0, + asn1::seq( + $LTVvalidation_crl + ) + ); + } + $appendLTV = asn1::seq( + '06092A864886F72F010108' . // adbe-revocationInfoArchival (1.2.840.113583.1.1.8) + asn1::set( + asn1::seq( + $ocsp . + $crl + ) + ) + ); + } else { + p_warning(" LTV Validation FAILED!\n"); } } - } - $ar['TimeStampResp']['timeStampToken'] = $curr; - $curr = $ar['TimeStampResp']['timeStampToken']['content']; - foreach ($curr as $key => $value) { - if (is_numeric($key)) { - if ($value['type'] == '30') { - $curr['TSTInfo'] = $curr[$key]; - unset($curr[$key]); + foreach ($this->signature_data['extracerts'] ?? [] as $extracert) { + $hex_extracert = bin2hex($x509->x509_pem2der($extracert)); + if (! in_array($hex_extracert, $hexEmbedCerts)) { + $hexEmbedCerts[] = $hex_extracert; } } } - $ar['TimeStampResp']['timeStampToken']['content'] = $curr; - if (@$ar['TimeStampResp']['timeStampToken']['content']['hexdump'] != '') { - return $ar; - } else { + $messageDigest = hash($hashAlgorithm, $binaryData); + $authenticatedAttributes = asn1::seq( + '06092A864886F70D010903' . //OBJ_pkcs9_contentType 1.2.840.113549.1.9.3 + asn1::set('06092A864886F70D010701') //OBJ_pkcs7_data 1.2.840.113549.1.7.1 + ) . + asn1::seq( // signing time + '06092A864886F70D010905' . //OBJ_pkcs9_signingTime 1.2.840.113549.1.9.5 + asn1::set( + asn1::utime(date('ymdHis')) //UTTC Time + ) + ) . + asn1::seq( // messageDigest + '06092A864886F70D010904' . //OBJ_pkcs9_messageDigest 1.2.840.113549.1.9.4 + asn1::set(asn1::oct($messageDigest)) + ) . + $appendLTV; + $tohash = asn1::set($authenticatedAttributes); + $hash = hash($hashAlgorithm, hex2bin($tohash)); + $toencrypt = asn1::seq( + asn1::seq($hexOidHashAlgos[$hashAlgorithm] . '0500') . // OBJ $messageDigest & OBJ_null + asn1::oct($hash) + ); + $pkey = $this->signature_data['privkey']; + if (! openssl_private_encrypt(hex2bin($toencrypt), $encryptedDigest, $pkey, OPENSSL_PKCS1_PADDING)) { + p_error("openssl_private_encrypt error! can't encrypt"); + return false; } + $hexencryptedDigest = bin2hex((string) $encryptedDigest); + $timeStamp = ''; + if (! empty($this->signature_data['tsa'])) { + p_debug(' Timestamping process start...'); + if ($TSTInfo = self::createTimestamp($encryptedDigest, $hashAlgorithm)) { + p_debug(' Timestamping SUCCESS.'); + $TimeStampToken = asn1::seq( + '060B2A864886F70D010910020E' . // OBJ_id_smime_aa_timeStampToken 1.2.840.113549.1.9.16.2.14 + asn1::set($TSTInfo) + ); + $timeStamp = asn1::expl(1, $TimeStampToken); + } else { + p_warning(' Timestamping FAILED!'); + } + } + $issuerName = $certParse['tbsCertificate']['issuer']['hexdump']; + $serialNumber = $certParse['tbsCertificate']['serialNumber']; + $signerinfos = asn1::seq( + asn1::int('1') . + asn1::seq($issuerName . asn1::int($serialNumber)) . + asn1::seq($hexOidHashAlgos[$hashAlgorithm] . '0500') . + asn1::expl(0, $authenticatedAttributes) . + asn1::seq( + '06092A864886F70D010101' . //OBJ_rsaEncryption + '0500' + ) . + asn1::oct($hexencryptedDigest) . + $timeStamp + ); + $certs = asn1::expl(0, implode('', $hexEmbedCerts)); + $pkcs7contentSignedData = asn1::seq( + asn1::int('1') . + asn1::set(asn1::seq($hexOidHashAlgos[$hashAlgorithm] . '0500')) . + asn1::seq('06092A864886F70D010701') . //OBJ_pkcs7_data + $certs . + asn1::set($signerinfos) + ); + $pkcs7ContentInfo = asn1::seq( + '06092A864886F70D010702' . // Hexadecimal form of pkcs7-signedData + asn1::expl(0, $pkcs7contentSignedData) + ); + + return $pkcs7ContentInfo; } /** @@ -171,16 +292,16 @@ protected function createTimestamp($data, $hashAlg = 'sha1') 'resp_contentType' => 'application/timestamp-reply', ] + $tsaData; - p_debug(" sending TSA query to \"" . $tsaData['host'] . "\"..."); + p_debug(' sending TSA query to "' . $tsaData['host'] . '"...'); if (! $binaryTsaResp = self::sendReq($reqData)) { - p_error(" TSA query send FAILED!"); + p_error(' TSA query send FAILED!'); } else { - p_debug(" TSA query send OK"); - p_debug(" Parsing Timestamp response..."); + p_debug(' TSA query send OK'); + p_debug(' Parsing Timestamp response...'); if (! $tsaResp = $this->tsa_parseResp($binaryTsaResp)) { - p_error(" parsing FAILED!"); + p_error(' parsing FAILED!'); } - p_debug(" parsing OK"); + p_debug(' parsing OK'); $TSTInfo = $tsaResp['TimeStampResp']['timeStampToken']['hexdump']; } @@ -203,164 +324,164 @@ protected function LTVvalidation($parsedCert): false|array $ltvResult['ocsp'] = false; $ltvResult['crl'] = false; $certSigner_parse = $parsedCert; - p_debug(" getting OCSP & CRL address..."); - p_debug(" reading AIA OCSP attribute..."); + p_debug(' getting OCSP & CRL address...'); + p_debug(' reading AIA OCSP attribute...'); $ocspURI = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.1'][0]; if (empty(trim((string) $ocspURI))) { - p_warning(" FAILED!"); + p_warning(' FAILED!'); } else { - p_debug(" OK got address:\"$ocspURI\""); + p_debug(" OK got address:\"{$ocspURI}\""); } $ocspURI = trim((string) $ocspURI); - p_debug(" reading CRL CDP attribute..."); + p_debug(' reading CRL CDP attribute...'); $crlURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['2.5.29.31']['value'][0]; - if (empty(trim($crlURIorFILE ?? ""))) { - p_warning(" FAILED!"); + if (empty(trim($crlURIorFILE ?? ''))) { + p_warning(' FAILED!'); } else { - p_debug(" OK got address:\"$crlURIorFILE\""); + p_debug(" OK got address:\"{$crlURIorFILE}\""); } if (empty($ocspURI) && empty($crlURIorFILE)) { p_error(" can't get OCSP/CRL address! Process terminated."); } else { // Perform if either ocspURI/crlURIorFILE exists - p_debug(" getting Issuer..."); - p_debug(" looking for issuer address from AIA attribute..."); + p_debug(' getting Issuer...'); + p_debug(' looking for issuer address from AIA attribute...'); $issuerURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.2'][0]; - $issuerURIorFILE = trim($issuerURIorFILE ?? ""); + $issuerURIorFILE = trim($issuerURIorFILE ?? ''); if (empty($issuerURIorFILE)) { - p_debug(" Failed!"); + p_debug(' Failed!'); } else { - p_debug(" OK got address \"$issuerURIorFILE\"..."); - p_debug(" load issuer from \"$issuerURIorFILE\"..."); + p_debug(" OK got address \"{$issuerURIorFILE}\"..."); + p_debug(" load issuer from \"{$issuerURIorFILE}\"..."); if ($issuerCert = @file_get_contents($issuerURIorFILE)) { - p_debug(" OK. size " . round(strlen($issuerCert) / 1024, 2) . "Kb"); - p_debug(" reading issuer certificate..."); + p_debug(' OK. size ' . round(strlen($issuerCert) / 1024, 2) . 'Kb'); + p_debug(' reading issuer certificate...'); if ($issuer_certDER = x509::get_cert($issuerCert)) { - p_debug(" OK"); - p_debug(" check if issuer is cert issuer..."); + p_debug(' OK'); + p_debug(' check if issuer is cert issuer...'); $certIssuer_parse = x509::readcert($issuer_certDER, 'oid'); // Parsing Issuer cert $certSigner_signatureField = $certSigner_parse['signatureValue']; if (openssl_public_decrypt(hex2bin((string) $certSigner_signatureField), $decrypted, x509::x509_der2pem($issuer_certDER), OPENSSL_PKCS1_PADDING)) { - p_debug(" OK issuer is cert issuer."); + p_debug(' OK issuer is cert issuer.'); $ltvResult['issuer'] = $issuer_certDER; } else { - p_warning(" FAILED! issuer is not cert issuer."); + p_warning(' FAILED! issuer is not cert issuer.'); } } else { - p_warning(" FAILED!"); + p_warning(' FAILED!'); } } else { - p_warning(" FAILED!."); + p_warning(' FAILED!.'); } } if (! $ltvResult['issuer']) { - p_debug(" search for issuer in extracerts....."); + p_debug(' search for issuer in extracerts.....'); if (array_key_exists('extracerts', $this->signature_data) && ($this->signature_data['extracerts'] !== null) && (count($this->signature_data['extracerts']) > 0)) { $i = 0; foreach ($this->signature_data['extracerts'] as $extracert) { - p_debug(" extracerts[$i] ..."); + p_debug(" extracerts[{$i}] ..."); $certSigner_signatureField = $certSigner_parse['signatureValue']; if (openssl_public_decrypt(hex2bin((string) $certSigner_signatureField), $decrypted, $extracert, OPENSSL_PKCS1_PADDING)) { - p_debug(" OK got issuer."); + p_debug(' OK got issuer.'); $certIssuer_parse = x509::readcert($extracert, 'oid'); // Parsing Issuer cert $ltvResult['issuer'] = x509::get_cert($extracert); } else { - p_debug(" FAIL!"); + p_debug(' FAIL!'); } $i++; } } else { - p_error(" FAILED! no extracerts available"); + p_error(' FAILED! no extracerts available'); } } } if ($ltvResult['issuer']) { if (! empty($ocspURI)) { - p_debug(" OCSP start..."); + p_debug(' OCSP start...'); $ocspReq_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; $ocspReq_issuerNameHash = $certIssuer_parse['tbsCertificate']['subject']['sha1']; $ocspReq_issuerKeyHash = $certIssuer_parse['tbsCertificate']['subjectPublicKeyInfo']['sha1']; $ocspRequestorSubjName = $certSigner_parse['tbsCertificate']['subject']['hexdump']; - p_debug(" OCSP create request..."); + p_debug(' OCSP create request...'); if ($ocspReq = x509::ocsp_request($ocspReq_serialNumber, $ocspReq_issuerNameHash, $ocspReq_issuerKeyHash)) { - p_debug(" OK."); - $ocspBinReq = pack("H*", $ocspReq); + p_debug(' OK.'); + $ocspBinReq = pack('H*', $ocspReq); $reqData = [ 'data' => $ocspBinReq, 'uri' => $ocspURI, 'req_contentType' => 'application/ocsp-request', 'resp_contentType' => 'application/ocsp-response', ]; - p_debug(" OCSP send request to \"$ocspURI\"..."); + p_debug(" OCSP send request to \"{$ocspURI}\"..."); if ($ocspResp = self::sendReq($reqData)) { - p_debug(" OK."); - p_debug(" OCSP parsing response..."); + p_debug(' OK.'); + p_debug(' OCSP parsing response...'); if ($ocsp_parse = x509::ocsp_response_parse($ocspResp, $return)) { - p_debug(" OK."); - p_debug(" OCSP check cert validity..."); + p_debug(' OK.'); + p_debug(' OCSP check cert validity...'); $certStatus = $ocsp_parse['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses'][0]['certStatus']; if ($certStatus == 'valid') { - p_debug(" OK. VALID."); + p_debug(' OK. VALID.'); $ocspRespHex = $ocsp_parse['hexdump']; $ltvResult['ocsp'] = $ocspRespHex; } else { - p_warning(" FAILED! cert not valid, status:\"" . strtoupper((string) $certStatus) . "\""); + p_warning(' FAILED! cert not valid, status:"' . strtoupper((string) $certStatus) . '"'); } } else { - p_warning(" FAILED! Ocsp server status \"$return\""); + p_warning(" FAILED! Ocsp server status \"{$return}\""); } } else { - p_warning(" FAILED!"); + p_warning(' FAILED!'); } } else { - p_warning(" FAILED!"); + p_warning(' FAILED!'); } } if (! $ltvResult['ocsp']) {// CRL not processed if OCSP validation already success if (! empty($crlURIorFILE)) { - p_debug(" processing CRL validation since OCSP not done/failed..."); - p_debug(" getting crl from \"$crlURIorFILE\"..."); + p_debug(' processing CRL validation since OCSP not done/failed...'); + p_debug(" getting crl from \"{$crlURIorFILE}\"..."); if ($crl = @file_get_contents($crlURIorFILE)) { - p_debug(" OK. size " . round(strlen($crl) / 1024, 2) . "Kb"); - p_debug(" reading crl..."); + p_debug(' OK. size ' . round(strlen($crl) / 1024, 2) . 'Kb'); + p_debug(' reading crl...'); if ($crlread = x509::crl_read($crl)) { - p_debug(" OK"); - p_debug(" verify crl signature..."); + p_debug(' OK'); + p_debug(' verify crl signature...'); $crl_signatureField = $crlread['parse']['signature']; if (openssl_public_decrypt(hex2bin((string) $crl_signatureField), $decrypted, x509::x509_der2pem($ltvResult['issuer']), OPENSSL_PKCS1_PADDING)) { - p_debug(" OK"); - p_debug(" check CRL validity..."); + p_debug(' OK'); + p_debug(' check CRL validity...'); $crl_parse = $crlread['parse']; - $thisUpdate = str_pad((string) $crl_parse['TBSCertList']['thisUpdate'], 15, "20", STR_PAD_LEFT); + $thisUpdate = str_pad((string) $crl_parse['TBSCertList']['thisUpdate'], 15, '20', STR_PAD_LEFT); $thisUpdateTime = strtotime($thisUpdate); - $nextUpdate = str_pad((string) $crl_parse['TBSCertList']['nextUpdate'], 15, "20", STR_PAD_LEFT); + $nextUpdate = str_pad((string) $crl_parse['TBSCertList']['nextUpdate'], 15, '20', STR_PAD_LEFT); $nextUpdateTime = strtotime($nextUpdate); $nowz = time(); if (($nowz - $thisUpdateTime) < 0) { // 0 sec after valid - p_error(" FAILED! not yet valid! valid at " . date("d/m/Y H:i:s", $thisUpdateTime)); + p_error(' FAILED! not yet valid! valid at ' . date('d/m/Y H:i:s', $thisUpdateTime)); } elseif (($nextUpdateTime - $nowz) < 1) { // not accept if crl 1 sec remain to expired - p_error(" FAILED! Expired crl at " . date("d/m/Y H:i:s", $nextUpdateTime) . " and now " . date("d/m/Y H:i:s", $nowz) . "!"); + p_error(' FAILED! Expired crl at ' . date('d/m/Y H:i:s', $nextUpdateTime) . ' and now ' . date('d/m/Y H:i:s', $nowz) . '!'); } else { - p_debug(" OK CRL still valid until " . date("d/m/Y H:i:s", $nextUpdateTime) . ""); + p_debug(' OK CRL still valid until ' . date('d/m/Y H:i:s', $nextUpdateTime) . ''); $crlCertValid = true; - p_debug(" check if cert not revoked..."); + p_debug(' check if cert not revoked...'); if (array_key_exists('revokedCertificates', $crl_parse['TBSCertList'])) { $certSigner_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; if (array_key_exists($certSigner_serialNumber, $crl_parse['TBSCertList']['revokedCertificates']['lists'])) { $crlCertValid = false; - p_error(" FAILED! Certificate Revoked!"); + p_error(' FAILED! Certificate Revoked!'); } } if ($crlCertValid == true) { - p_debug(" OK. VALID"); + p_debug(' OK. VALID'); $crlHex = current(unpack('H*', (string) $crlread['der'])); $ltvResult['crl'] = $crlHex; } } } else { - p_error(" FAILED! Wrong CRL."); + p_error(' FAILED! Wrong CRL.'); } } else { p_error(" FAILED! can't read crl"); @@ -382,191 +503,70 @@ protected function LTVvalidation($parsedCert): false|array } /** - * Perform PKCS7 Signing + * parse tsa response to array * - * @param string $binaryData + * @param string $binaryTsaRespData binary tsa response to parse * - * @return string hex + padding 0 - * @public + * @return array asn.1 hex structure of tsa response */ - public function pkcs7_sign($binaryData) + private function tsa_parseResp($binaryTsaRespData) { - $hexOidHashAlgos = [ - 'md2' => '06082A864886F70D0202', - 'md4' => '06082A864886F70D0204', - 'md5' => '06082A864886F70D0205', - 'sha1' => '06052B0E03021A', - 'sha224' => '0609608648016503040204', - 'sha256' => '0609608648016503040201', - 'sha384' => '0609608648016503040202', - 'sha512' => '0609608648016503040203', - ]; - $hashAlgorithm = $this->signature_data['hashAlgorithm']; - if (! array_key_exists($hashAlgorithm, $hexOidHashAlgos)) { - p_error("not support hash algorithm!"); + if (! @$ar = asn1::parse(bin2hex($binaryTsaRespData), 3)) { + p_error(" can't parse invalid tsa Response."); return false; } - p_debug("hash algorithm is \"$hashAlgorithm\""); - $x509 = new x509(); - if (! $certParse = $x509->readcert($this->signature_data['signcert'])) { - p_error("certificate error! check certificate"); + $curr = $ar; + foreach ($curr as $key => $value) { + if ($value['type'] == '30') { + $curr['TimeStampResp'] = $curr[$key]; + unset($curr[$key]); + } } - $hexEmbedCerts[] = bin2hex($x509->get_cert($this->signature_data['signcert'])); - $appendLTV = ''; - $ltvData = $this->signature_data['ltv']; - if (! empty($ltvData)) { - p_debug(" LTV Validation start..."); - $appendLTV = ''; - $LTVvalidation_ocsp = ''; - $LTVvalidation_crl = ''; - $LTVvalidation_issuer = ''; - $LTVvalidationEnd = false; - - $isRootCA = false; - if ($certParse['tbsCertificate']['issuer']['hexdump'] == $certParse['tbsCertificate']['subject']['hexdump']) { // check whether root ca - if (openssl_public_decrypt(hex2bin((string) $certParse['signatureValue']), $decrypted, x509::x509_der2pem($x509->get_cert($this->signature_data['signcert'])), OPENSSL_PKCS1_PADDING)) { - p_debug("***** \"{$certParse['tbsCertificate']['subject']['2.5.4.3'][0]}\" is a ROOT CA. No validation performed ***"); - $isRootCA = true; + $ar = $curr; + $curr = $ar['TimeStampResp']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '30' && ! array_key_exists('status', $curr)) { + $curr['status'] = $curr[$key]; + unset($curr[$key]); + } else { + if ($value['type'] == '30') { + $curr['timeStampToken'] = $curr[$key]; + unset($curr[$key]); + } } } - if ($isRootCA == false) { - $i = 0; - $LTVvalidation = true; - $certtoCheck = $certParse; - while ($LTVvalidation !== false) { - p_debug("========= $i checking \"{$certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0]}\"==============="); - $LTVvalidation = self::LTVvalidation($certtoCheck); - $i++; - if ($LTVvalidation) { - $curr_issuer = $LTVvalidation['issuer']; - $certtoCheck = $x509->readcert($curr_issuer, 'oid'); - if (@$LTVvalidation['ocsp'] || @$LTVvalidation['crl']) { - $LTVvalidation_ocsp .= $LTVvalidation['ocsp']; - $LTVvalidation_crl .= $LTVvalidation['crl']; - $hexEmbedCerts[] = bin2hex((string) $LTVvalidation['issuer']); - } - - if ($certtoCheck['tbsCertificate']['issuer']['hexdump'] == $certtoCheck['tbsCertificate']['subject']['hexdump']) { // check whether root ca - if (openssl_public_decrypt(hex2bin((string) $certtoCheck['signatureValue']), $decrypted, $x509->x509_der2pem($curr_issuer), OPENSSL_PKCS1_PADDING)) { - p_debug("========= FINISH Reached ROOT CA \"{$certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0]}\"==============="); - $LTVvalidationEnd = true; - break; - } - } - } + } + $ar['TimeStampResp'] = $curr; + $curr = $ar['TimeStampResp']['timeStampToken']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '06') { + $curr['contentType'] = $curr[$key]; + unset($curr[$key]); } - - if ($LTVvalidationEnd) { - p_debug(" LTV Validation SUCCESS\n"); - $ocsp = ''; - if (! empty($LTVvalidation_ocsp)) { - $ocsp = asn1::expl( - 1, - asn1::seq( - $LTVvalidation_ocsp - ) - ); - } - $crl = ''; - if (! empty($LTVvalidation_crl)) { - $crl = asn1::expl( - 0, - asn1::seq( - $LTVvalidation_crl - ) - ); - } - $appendLTV = asn1::seq( - "06092A864886F72F010108" . // adbe-revocationInfoArchival (1.2.840.113583.1.1.8) - asn1::set( - asn1::seq( - $ocsp . - $crl - ) - ) - ); - } else { - p_warning(" LTV Validation FAILED!\n"); + if ($value['type'] == 'a0') { + $curr['content'] = $curr[$key]; + unset($curr[$key]); } } - foreach ($this->signature_data['extracerts'] ?? [] as $extracert) { - $hex_extracert = bin2hex($x509->x509_pem2der($extracert)); - if (! in_array($hex_extracert, $hexEmbedCerts)) { - $hexEmbedCerts[] = $hex_extracert; + } + $ar['TimeStampResp']['timeStampToken'] = $curr; + $curr = $ar['TimeStampResp']['timeStampToken']['content']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '30') { + $curr['TSTInfo'] = $curr[$key]; + unset($curr[$key]); } } } - $messageDigest = hash($hashAlgorithm, $binaryData); - $authenticatedAttributes = asn1::seq( - '06092A864886F70D010903' . //OBJ_pkcs9_contentType 1.2.840.113549.1.9.3 - asn1::set('06092A864886F70D010701') //OBJ_pkcs7_data 1.2.840.113549.1.7.1 - ) . - asn1::seq( // signing time - '06092A864886F70D010905' . //OBJ_pkcs9_signingTime 1.2.840.113549.1.9.5 - asn1::set( - asn1::utime(date("ymdHis")) //UTTC Time - ) - ) . - asn1::seq( // messageDigest - '06092A864886F70D010904' . //OBJ_pkcs9_messageDigest 1.2.840.113549.1.9.4 - asn1::set(asn1::oct($messageDigest)) - ) . - $appendLTV; - $tohash = asn1::set($authenticatedAttributes); - $hash = hash($hashAlgorithm, hex2bin($tohash)); - $toencrypt = asn1::seq( - asn1::seq($hexOidHashAlgos[$hashAlgorithm] . "0500") . // OBJ $messageDigest & OBJ_null - asn1::oct($hash) - ); - $pkey = $this->signature_data['privkey']; - if (! openssl_private_encrypt(hex2bin($toencrypt), $encryptedDigest, $pkey, OPENSSL_PKCS1_PADDING)) { - p_error("openssl_private_encrypt error! can't encrypt"); - - return false; - } - $hexencryptedDigest = bin2hex((string) $encryptedDigest); - $timeStamp = ''; - if (! empty($this->signature_data['tsa'])) { - p_debug(" Timestamping process start..."); - if ($TSTInfo = self::createTimestamp($encryptedDigest, $hashAlgorithm)) { - p_debug(" Timestamping SUCCESS."); - $TimeStampToken = asn1::seq( - "060B2A864886F70D010910020E" . // OBJ_id_smime_aa_timeStampToken 1.2.840.113549.1.9.16.2.14 - asn1::set($TSTInfo) - ); - $timeStamp = asn1::expl(1, $TimeStampToken); - } else { - p_warning(" Timestamping FAILED!"); - } + $ar['TimeStampResp']['timeStampToken']['content'] = $curr; + if (@$ar['TimeStampResp']['timeStampToken']['content']['hexdump'] != '') { + return $ar; } - $issuerName = $certParse['tbsCertificate']['issuer']['hexdump']; - $serialNumber = $certParse['tbsCertificate']['serialNumber']; - $signerinfos = asn1::seq( - asn1::int('1') . - asn1::seq($issuerName . asn1::int($serialNumber)) . - asn1::seq($hexOidHashAlgos[$hashAlgorithm] . '0500') . - asn1::expl(0, $authenticatedAttributes) . - asn1::seq( - '06092A864886F70D010101' . //OBJ_rsaEncryption - '0500' - ) . - asn1::oct($hexencryptedDigest) . - $timeStamp - ); - $certs = asn1::expl(0, implode('', $hexEmbedCerts)); - $pkcs7contentSignedData = asn1::seq( - asn1::int('1') . - asn1::set(asn1::seq($hexOidHashAlgos[$hashAlgorithm] . '0500')) . - asn1::seq('06092A864886F70D010701') . //OBJ_pkcs7_data - $certs . - asn1::set($signerinfos) - ); - $pkcs7ContentInfo = asn1::seq( - "06092A864886F70D010702" . // Hexadecimal form of pkcs7-signedData - asn1::expl(0, $pkcs7contentSignedData) - ); + return false; - return $pkcs7ContentInfo; } } diff --git a/src/helpers/DependencyTreeObject.php b/src/helpers/DependencyTreeObject.php index 16aed55..d2a05fd 100644 --- a/src/helpers/DependencyTreeObject.php +++ b/src/helpers/DependencyTreeObject.php @@ -31,25 +31,30 @@ class DependencyTreeObject implements Stringable { private int $is_child; - function __construct( + public function __construct( private int $oid, private mixed $info = null, ) { $this->is_child = 0; } + public function __toString(): string + { + return (string) $this->_getstr(null, isset($this->children) ? count($this->children) : 0); + } + /** * Function that links one object to its parent (i.e. adds the object to the list of children of this object) * - the function increases the amount of times that one object has been added to a parent object, to detect problems in building the tree */ - function addchild($oid, $o): void + public function addchild($oid, $o): void { if (! isset($this->children)) { $this->children = []; } $this->children[$oid] = $o; if ($o->is_child != 0) { - p_warning("object $o->oid is already a child of other object"); + p_warning("object {$o->oid} is already a child of other object"); } $o->is_child = $o->is_child + 1; @@ -58,7 +63,7 @@ function addchild($oid, $o): void /** * This is an iterator for the children of this object */ - function children(): Generator + public function children(): Generator { if (isset($this->children)) { foreach ($this->children as $oid => $object) { @@ -70,17 +75,17 @@ function children(): Generator /** * Gets a string that represents the object, prepending a number of spaces, proportional to the depth in the tree */ - protected function _getstr(?string $spaces = "", $mychcount = 0): string + protected function _getstr(?string $spaces = '', $mychcount = 0): string { // $info = $this->oid . ($this->info?" ($this->info)":"") . (($this->is_child > 1)?" $this->is_child":""); - $info = $this->oid . ($this->info ? " ($this->info)" : ""); + $info = $this->oid . ($this->info ? " ({$this->info})" : ''); if ($spaces === null) { - $lines = ["{$spaces} " . json_decode('"\u2501"') . " $info"]; + $lines = ["{$spaces} " . json_decode('"\u2501"') . " {$info}"]; } else { if ($mychcount == 0) { - $lines = ["{$spaces} " . json_decode('"\u2514\u2500"') . " $info"]; + $lines = ["{$spaces} " . json_decode('"\u2514\u2500"') . " {$info}"]; } else { - $lines = ["{$spaces} " . json_decode('"\u251c\u2500"') . " $info"]; + $lines = ["{$spaces} " . json_decode('"\u251c\u2500"') . " {$info}"]; } } if (isset($this->children)) { @@ -88,9 +93,9 @@ protected function _getstr(?string $spaces = "", $mychcount = 0): string foreach ($this->children as $oid => $child) { $chcount--; if (($spaces === null) || ($mychcount == 0)) { - array_push($lines, $child->_getstr($spaces . " ", $chcount)); + array_push($lines, $child->_getstr($spaces . ' ', $chcount)); } else { - array_push($lines, $child->_getstr($spaces . " " . json_decode('"\u2502"'), $chcount)); + array_push($lines, $child->_getstr($spaces . ' ' . json_decode('"\u2502"'), $chcount)); } } } @@ -100,8 +105,8 @@ protected function _getstr(?string $spaces = "", $mychcount = 0): string protected function _old_getstr($depth = 0): string { - $spaces = str_repeat(" " . json_decode('"\u2502"'), $depth); - $lines = ["{$spaces} " . json_decode('"\u251c\u2500"') . " " . $this->oid . ($this->info ? " ($this->info)" : "") . (($this->is_child > 1) ? " $this->is_child" : "")]; + $spaces = str_repeat(' ' . json_decode('"\u2502"'), $depth); + $lines = ["{$spaces} " . json_decode('"\u251c\u2500"') . ' ' . $this->oid . ($this->info ? " ({$this->info})" : '') . (($this->is_child > 1) ? " {$this->is_child}" : '')]; if (isset($this->children)) { foreach ($this->children as $oid => $child) { array_push($lines, $child->_getstr($depth + 1)); @@ -110,11 +115,6 @@ protected function _old_getstr($depth = 0): string return implode("\n", $lines); } - - public function __toString(): string - { - return (string) $this->_getstr(null, isset($this->children) ? count($this->children) : 0); - } } /** @@ -124,9 +124,9 @@ public function __toString(): string */ const BLACKLIST = [ // Field "Parent" for any type of object - "*" => ["Parent"], + '*' => ['Parent'], // Field "P" for nodes of type "Annot" - "Annot" => ["P"], + 'Annot' => ['P'], ]; /** @@ -134,11 +134,11 @@ public function __toString(): string */ function references_in_object(array $object, $oid = false): array { - $type = $object["Type"]; + $type = $object['Type']; if ($type !== false) { $type = $type->val(); } else { - $type = ""; + $type = ''; } $references = []; @@ -147,7 +147,7 @@ function references_in_object(array $object, $oid = false): array $valid = true; // We'll skip those blacklisted fields - if (in_array($key, BLACKLIST["*"])) { + if (in_array($key, BLACKLIST['*'])) { continue; } @@ -158,7 +158,7 @@ function references_in_object(array $object, $oid = false): array } $r_objects = []; - if (is_a($object[$key], "ddn\\sapp\\pdfvalue\\PDFValueObject")) { + if (is_a($object[$key], 'ddn\\sapp\\pdfvalue\\PDFValueObject')) { $r_objects = references_in_object($object[$key]); } else { // Function get_object_referenced checks whether the value (or values in a list) have the form of object references, and if they have the form diff --git a/src/helpers/LoadHelpers.php b/src/helpers/LoadHelpers.php index 67c05ee..60c2a62 100644 --- a/src/helpers/LoadHelpers.php +++ b/src/helpers/LoadHelpers.php @@ -2,7 +2,7 @@ namespace ddn\sapp\helpers; -foreach (glob(__DIR__ . "/*.php") as $i) { +foreach (glob(__DIR__ . '/*.php') as $i) { include_once($i); } diff --git a/src/helpers/StreamReader.php b/src/helpers/StreamReader.php index 91691ec..18fa76b 100644 --- a/src/helpers/StreamReader.php +++ b/src/helpers/StreamReader.php @@ -32,7 +32,7 @@ */ class StreamReader { - protected $_buffer = ""; + protected $_buffer = ''; protected int $_bufferlen; @@ -41,7 +41,7 @@ class StreamReader public function __construct($string = null, $offset = 0) { if ($string === null) { - $string = ""; + $string = ''; } $this->_buffer = $string; @@ -122,9 +122,9 @@ public function substratpos($length = 0): string { if ($length > 0) { return substr((string) $this->_buffer, $this->_pos, $length); - } else { - return substr((string) $this->_buffer, $this->_pos); } + return substr((string) $this->_buffer, $this->_pos); + } /** diff --git a/src/helpers/asn1.php b/src/helpers/asn1.php index 3869239..d83f58e 100644 --- a/src/helpers/asn1.php +++ b/src/helpers/asn1.php @@ -16,6 +16,119 @@ */ class asn1 { + public static function __callStatic($func, $params) + { + $func = strtolower((string) $func); + $asn1Tag = self::asn1Tag($func); + if ($asn1Tag !== false) { + $num = $asn1Tag; //valu of array + $hex = $params[0]; + $val = $hex; + if (in_array($func, ['printable', 'utf8', 'ia5', 'visible', 't61'])) { // ($string) + $val = bin2hex((string) $hex); + } + if ($func == 'int') { + $val = (strlen((string) $val) % 2 != 0) ? "0{$val}" : "{$val}"; + } + if ($func == 'expl') { //expl($num, $hex) + $num = $num . $params[0]; + $val = $params[1]; + } + if ($func == 'impl') { //impl($num="0") + $val = (! $val) ? '00' : $val; + $val = (strlen((string) $val) % 2 != 0) ? "0{$val}" : $val; + + return $num . $val; + } + if ($func == 'other') { //OTHER($id, $hex, $chr = false) + $id = $params[0]; + $hex = $params[1]; + $chr = @$params[2]; + $str = $hex; + if ($chr != false) { + $str = bin2hex((string) $hex); + } + $ret = "{$id}" . self::asn1_header($str) . $str; + + return $ret; + } + if ($func == 'utime') { + $time = $params[0]; //yymmddhhiiss + $oldTz = date_default_timezone_get(); + date_default_timezone_set('UTC'); + $time = date('ymdHis', $time); + date_default_timezone_set($oldTz); + $val = bin2hex($time . 'Z'); + } + if ($func == 'gtime') { + if (! $time = strtotime((string) $params[0])) { + // echo "asn1::GTIME function strtotime cant recognize time!! please check at input=\"{$params[0]}\""; + return false; + } + $oldTz = date_default_timezone_get(); + // date_default_timezone_set("UTC"); + $time = date('YmdHis', $time); + date_default_timezone_set($oldTz); + $val = bin2hex($time . 'Z'); + } + $hdr = self::asn1_header($val); + + return $num . $hdr . $val; + } + // echo "asn1 \"$func\" not exists!"; + + } + + /** + * parse asn.1 to array recursively + * + * @param string $hex asn.1 hex form + * @param int $maxDepth maximum parsing depth + * + * @return array asn.1 structure recursively to specific depth + */ + public static function parse($hex, $maxDepth = 5): array + { + $result = []; + static $currentDepth = 0; + if ($asn1parse_array = self::oneParse($hex)) { + foreach ($asn1parse_array as $ff) { + $parse_recursive = false; + unset($info); + $k = $ff['typ']; + $v = $ff['tlv_value']; + $info['depth'] = $currentDepth; + $info['hexdump'] = $ff['newhexdump']; + $info['type'] = $k; + $info['typeName'] = self::type($k); + $info['value_hex'] = $v; + if (($currentDepth <= $maxDepth)) { + if ($k == '06') { + } else { + if (in_array($k, ['13', '18'])) { + $info['value'] = hex2bin((string) $info['value_hex']); + } else { + if (in_array($k, ['03', '02', 'a04'])) { + $info['value'] = $v; + } else { + $currentDepth++; + $parse_recursive = self::parse($v, $maxDepth); + $currentDepth--; + } + } + } + if ($parse_recursive) { + $result[] = array_merge($info, $parse_recursive); + } else { + $result[] = $info; + } + } + } + } + + return $result; + } + // =====Begin ASN.1 Parser section===== /** * get asn.1 type tag name @@ -27,32 +140,32 @@ class asn1 protected static function type($id) { $asn1_Types = [ - "00" => "ASN1_EOC", - "01" => "ASN1_BOOLEAN", - "02" => "ASN1_INTEGER", - "03" => "ASN1_BIT_STRING", - "04" => "ASN1_OCTET_STRING", - "05" => "ASN1_NULL", - "06" => "ASN1_OBJECT", - "07" => "ASN1_OBJECT_DESCRIPTOR", - "08" => "ASN1_EXTERNAL", - "09" => "ASN1_REAL", - "0a" => "ASN1_ENUMERATED", - "0c" => "ASN1_UTF8STRING", - "30" => "ASN1_SEQUENCE", - "31" => "ASN1_SET", - "12" => "ASN1_NUMERICSTRING", - "13" => "ASN1_PRINTABLESTRING", - "14" => "ASN1_T61STRING", - "15" => "ASN1_VIDEOTEXSTRING", - "16" => "ASN1_IA5STRING", - "17" => "ASN1_UTCTIME", - "18" => "ASN1_GENERALIZEDTIME", - "19" => "ASN1_GRAPHICSTRING", - "1a" => "ASN1_VISIBLESTRING", - "1b" => "ASN1_GENERALSTRING", - "1c" => "ASN1_UNIVERSALSTRING", - "1d" => "ASN1_BMPSTRING", + '00' => 'ASN1_EOC', + '01' => 'ASN1_BOOLEAN', + '02' => 'ASN1_INTEGER', + '03' => 'ASN1_BIT_STRING', + '04' => 'ASN1_OCTET_STRING', + '05' => 'ASN1_NULL', + '06' => 'ASN1_OBJECT', + '07' => 'ASN1_OBJECT_DESCRIPTOR', + '08' => 'ASN1_EXTERNAL', + '09' => 'ASN1_REAL', + '0a' => 'ASN1_ENUMERATED', + '0c' => 'ASN1_UTF8STRING', + '30' => 'ASN1_SEQUENCE', + '31' => 'ASN1_SET', + '12' => 'ASN1_NUMERICSTRING', + '13' => 'ASN1_PRINTABLESTRING', + '14' => 'ASN1_T61STRING', + '15' => 'ASN1_VIDEOTEXSTRING', + '16' => 'ASN1_IA5STRING', + '17' => 'ASN1_UTCTIME', + '18' => 'ASN1_GENERALIZEDTIME', + '19' => 'ASN1_GRAPHICSTRING', + '1a' => 'ASN1_VISIBLESTRING', + '1b' => 'ASN1_GENERALSTRING', + '1c' => 'ASN1_UNIVERSALSTRING', + '1d' => 'ASN1_BMPSTRING', ]; return array_key_exists($id, $asn1_Types) ? $asn1_Types[$id] : $id; @@ -72,7 +185,7 @@ protected static function oneParse($hex) return false; } if (! @ctype_xdigit($hex) || @strlen($hex) % 2 != 0) { - echo "input:\"$hex\" not hex string!.\n"; + echo "input:\"{$hex}\" not hex string!.\n"; return false; } @@ -113,56 +226,6 @@ protected static function oneParse($hex) return $result; } - - /** - * parse asn.1 to array recursively - * - * @param string $hex asn.1 hex form - * @param int $maxDepth maximum parsing depth - * - * @return array asn.1 structure recursively to specific depth - */ - public static function parse($hex, $maxDepth = 5): array - { - $result = []; - static $currentDepth = 0; - if ($asn1parse_array = self::oneParse($hex)) { - foreach ($asn1parse_array as $ff) { - $parse_recursive = false; - unset($info); - $k = $ff['typ']; - $v = $ff['tlv_value']; - $info['depth'] = $currentDepth; - $info['hexdump'] = $ff['newhexdump']; - $info['type'] = $k; - $info['typeName'] = self::type($k); - $info['value_hex'] = $v; - if (($currentDepth <= $maxDepth)) { - if ($k == '06') { - } else { - if (in_array($k, ['13', '18'])) { - $info['value'] = hex2bin((string) $info['value_hex']); - } else { - if (in_array($k, ['03', '02', 'a04'])) { - $info['value'] = $v; - } else { - $currentDepth++; - $parse_recursive = self::parse($v, $maxDepth); - $currentDepth--; - } - } - } - if ($parse_recursive) { - $result[] = array_merge($info, $parse_recursive); - } else { - $result[] = $info; - } - } - } - } - - return $result; - } // =====End ASN.1 Parser section===== // =====Begin ASN.1 Builder section===== @@ -179,11 +242,11 @@ protected static function asn1_header($str): string $len = strlen($str) / 2; $ret = dechex($len); if (strlen($ret) % 2 != 0) { - $ret = "0$ret"; + $ret = "0{$ret}"; } $headerLength = strlen($ret) / 2; if ($len > 127) { - $ret = "8" . $headerLength . $ret; + $ret = '8' . $headerLength . $ret; } return $ret; @@ -214,73 +277,10 @@ private static function asn1Tag($name): string|false ]; if (array_key_exists($name, $functionList)) { return $functionList[$name]; - } else { - // echo "func \"$name\" not available"; - return false; } - } - - public static function __callStatic($func, $params) - { - $func = strtolower((string) $func); - $asn1Tag = self::asn1Tag($func); - if ($asn1Tag !== false) { - $num = $asn1Tag; //valu of array - $hex = $params[0]; - $val = $hex; - if (in_array($func, ['printable', 'utf8', 'ia5', 'visible', 't61'])) { // ($string) - $val = bin2hex((string) $hex); - } - if ($func == 'int') { - $val = (strlen((string) $val) % 2 != 0) ? "0$val" : "$val"; - } - if ($func == 'expl') { //expl($num, $hex) - $num = $num . $params[0]; - $val = $params[1]; - } - if ($func == 'impl') { //impl($num="0") - $val = (! $val) ? "00" : $val; - $val = (strlen((string) $val) % 2 != 0) ? "0$val" : $val; + // echo "func \"$name\" not available"; + return false; - return $num . $val; - } - if ($func == 'other') { //OTHER($id, $hex, $chr = false) - $id = $params[0]; - $hex = $params[1]; - $chr = @$params[2]; - $str = $hex; - if ($chr != false) { - $str = bin2hex((string) $hex); - } - $ret = "$id" . self::asn1_header($str) . $str; - - return $ret; - } - if ($func == 'utime') { - $time = $params[0]; //yymmddhhiiss - $oldTz = date_default_timezone_get(); - date_default_timezone_set("UTC"); - $time = date("ymdHis", $time); - date_default_timezone_set($oldTz); - $val = bin2hex($time . "Z"); - } - if ($func == 'gtime') { - if (! $time = strtotime((string) $params[0])) { - // echo "asn1::GTIME function strtotime cant recognize time!! please check at input=\"{$params[0]}\""; - return false; - } - $oldTz = date_default_timezone_get(); - // date_default_timezone_set("UTC"); - $time = date("YmdHis", $time); - date_default_timezone_set($oldTz); - $val = bin2hex($time . "Z"); - } - $hdr = self::asn1_header($val); - - return $num . $hdr . $val; - } else { - // echo "asn1 \"$func\" not exists!"; - } } // =====End ASN.1 Builder section===== } diff --git a/src/helpers/contentgeneration.php b/src/helpers/contentgeneration.php index 6e4efdf..7dc8cf8 100644 --- a/src/helpers/contentgeneration.php +++ b/src/helpers/contentgeneration.php @@ -29,24 +29,24 @@ function tx($x, $y): string { - return sprintf(" 1 0 0 1 %.2F %.2F cm", $x, $y); + return sprintf(' 1 0 0 1 %.2F %.2F cm', $x, $y); } function sx($w, $h): string { - return sprintf(" %.2F 0 0 %.2F 0 0 cm", $w, $h); + return sprintf(' %.2F 0 0 %.2F 0 0 cm', $w, $h); } function deg2rad($angle): float { - return $angle * pi() / 180; + return $angle * M_PI / 180; } function rx($angle): string { $angle = deg2rad($angle); - return sprintf(" %.2F %.2F %.2F %.2F 0 0 cm", cos($angle), sin($angle), -sin($angle), cos($angle)); + return sprintf(' %.2F %.2F %.2F %.2F 0 0 cm', cos($angle), sin($angle), -sin($angle), cos($angle)); } /** @@ -89,7 +89,8 @@ function _create_image_objects($info, $object_factory): array array_push($objects, $streamobject); break; case 'DeviceCMYK': - $image["Decode"] = new PDFValueList([1, 0, 1, 0, 1, 0, 1, 0]); + $image['Decode'] = new PDFValueList([1, 0, 1, 0, 1, 0, 1, 0]); + // no break default: $image['ColorSpace'] = new PDFValueType($info['cs']); break; @@ -141,7 +142,7 @@ function is_base64($string): bool // Decode the string in strict mode and check the results $decoded = base64_decode((string) $string, true); - if (false === $decoded) { + if ($decoded === false) { return false; } @@ -188,7 +189,7 @@ function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, $filecontent = @file_get_contents($filename); if ($filecontent === false) { - return p_error("failed to get the image"); + return p_error('failed to get the image'); } } } @@ -210,11 +211,11 @@ function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, $info = _parsepng($filecontent); break; default: - return p_error("unsupported mime type"); + return p_error('unsupported mime type'); } // Generate a new identifier for the image - $info['i'] = "Im" . get_random_string(4); + $info['i'] = 'Im' . get_random_string(4); if ($w === null) { $w = -96; @@ -239,7 +240,7 @@ function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, $images_objects = _create_image_objects($info, $object_factory); // Generate the command to translate and scale the image - $data = "q "; + $data = 'q '; if ($keep_proportions) { $angleRads = deg2rad($angle); @@ -274,7 +275,7 @@ function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, $data .= sx($w, $h); */ - $data = "q"; + $data = 'q'; $data .= tx($x, $y); $data .= sx($w, $h); if ($angle != 0) { @@ -282,17 +283,17 @@ function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, $data .= rx($angle); $data .= tx(-0.5, -0.5); } - $data .= sprintf(" /%s Do Q", $info['i']); + $data .= sprintf(' /%s Do Q', $info['i']); $resources = new PDFValueObject([ 'ProcSet' => ['/PDF', '/Text', '/ImageB', '/ImageC', '/ImageI'], - 'XObject' => new PDFValueObject ([ + 'XObject' => new PDFValueObject([ $info['i'] => new PDFValueReference($images_objects[0]->get_oid()), ]), ]); return [ - "image" => $images_objects[0], + 'image' => $images_objects[0], 'command' => $data, 'resources' => $resources, 'alpha' => $add_alpha, diff --git a/src/helpers/fpdfhelpers.php b/src/helpers/fpdfhelpers.php index 3f694ea..38aeb5b 100644 --- a/src/helpers/fpdfhelpers.php +++ b/src/helpers/fpdfhelpers.php @@ -16,21 +16,21 @@ GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License - along with this program. If not, see . + along with this program. If not, see . - --------- + --------- - The code in this file is an adaptation of a part of the code included in - fpdf version 1.82 as downloaded from (http://www.fpdf.org/es/dl.php?v=182&f=tgz) + The code in this file is an adaptation of a part of the code included in + fpdf version 1.82 as downloaded from (http://www.fpdf.org/es/dl.php?v=182&f=tgz) - The fpdf license: + The fpdf license: - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software to use, copy, modify, distribute, sublicense, and/or sell - copies of the software, and to permit persons to whom the software is furnished - to do so. + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software to use, copy, modify, distribute, sublicense, and/or sell + copies of the software, and to permit persons to whom the software is furnished + to do so. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. */ namespace ddn\sapp\helpers; @@ -78,7 +78,7 @@ function _parsepngstream(&$f) { // Check signature if (($res = _readstream($f, 8)) != chr(137) . 'PNG' . chr(13) . chr(10) . chr(26) . chr(10)) { - return p_error("Not a PNG image $res"); + return p_error("Not a PNG image {$res}"); } // Read header chunk @@ -213,12 +213,12 @@ function _parsepngstream(&$f) function _readstream(&$f, $n) { - $res = ""; + $res = ''; while ($n > 0 && ! $f->eos()) { $s = $f->nextchars($n); if ($s === false) { - return p_error("Error while reading the stream"); + return p_error('Error while reading the stream'); } $n -= strlen((string) $s); $res .= $s; @@ -242,25 +242,25 @@ function _readint(&$f) /* function _readstream($f, $n) { - // Read n bytes from stream - $res = ''; - while($n>0 && !feof($f)) - { - $s = fread($f,$n); - if($s===false) - return p_error('Error while reading stream'); - $n -= strlen($s); - $res .= $s; - } - if($n>0) - return p_error('Unexpected end of stream'); - return $res; + // Read n bytes from stream + $res = ''; + while($n>0 && !feof($f)) + { + $s = fread($f,$n); + if($s===false) + return p_error('Error while reading stream'); + $n -= strlen($s); + $res .= $s; + } + if($n>0) + return p_error('Unexpected end of stream'); + return $res; } function _readint($f) { - // Read a 4-byte integer from stream - $a = unpack('Ni',_readstream($f,4)); - return $a['i']; + // Read a 4-byte integer from stream + $a = unpack('Ni',_readstream($f,4)); + return $a['i']; } */ diff --git a/src/helpers/helpers.php b/src/helpers/helpers.php index f0bc5db..1bf1f8f 100644 --- a/src/helpers/helpers.php +++ b/src/helpers/helpers.php @@ -23,7 +23,7 @@ use DateTime; -if (! defined("_DEBUG_LEVEL")) { +if (! defined('_DEBUG_LEVEL')) { define('_DEBUG_LEVEL', 3); } @@ -83,7 +83,7 @@ function p_debug_var(...$vars): void foreach ($vars as $var) { $e = var_dump_to_string($var); - p_stderr($e, "Debug"); + p_stderr($e, 'Debug'); } } @@ -103,9 +103,9 @@ function varval($e) $a = []; foreach ($e as $k => $v) { $v = varval($v); - array_push($a, "$k => $v"); + array_push($a, "{$k} => {$v}"); } - $retval = "[ " . implode(", ", $a) . " ]"; + $retval = '[ ' . implode(', ', $a) . ' ]'; } return $retval; @@ -119,12 +119,12 @@ function varval($e) * @param level the depth level to output (0 will refer to the function that called p_stderr * call itself, 1 to the function that called to the function that called p_stderr) */ -function p_stderr(&$e, $tag = "Error", $level = 1): void +function p_stderr(&$e, $tag = 'Error', $level = 1): void { $dinfo = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT); $dinfo = $dinfo[$level]; - $e = sprintf("$tag info at %s:%d: %s", $dinfo['file'], $dinfo['line'], varval($e)); - fwrite(STDERR, "$e\n"); + $e = sprintf("{$tag} info at %s:%d: %s", $dinfo['file'], $dinfo['line'], varval($e)); + fwrite(STDERR, "{$e}\n"); } /** @@ -139,7 +139,7 @@ function p_debug($e, $retval = false) { // If the debug level is less than 3, suppress debug messages if (_DEBUG_LEVEL >= 3) { - p_stderr($e, "Debug"); + p_stderr($e, 'Debug'); } return $retval; @@ -157,7 +157,7 @@ function p_warning($e, $retval = false) { // If the debug level is less than 2, suppress warning messages if (_DEBUG_LEVEL >= 2) { - p_stderr($e, "Warning"); + p_stderr($e, 'Warning'); } return $retval; @@ -175,7 +175,7 @@ function p_error($e, $retval = false) { // If the debug level is less than 1, suppress error messages if (_DEBUG_LEVEL >= 1) { - p_stderr($e, "Error"); + p_stderr($e, 'Error'); } return $retval; @@ -196,15 +196,15 @@ function p_error($e, $retval = false) */ function get_random_string($length = 8, $extended = false, $hard = false): string { - $token = ""; - $codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - $codeAlphabet .= "abcdefghijklmnopqrstuvwxyz"; - $codeAlphabet .= "0123456789"; + $token = ''; + $codeAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $codeAlphabet .= 'abcdefghijklmnopqrstuvwxyz'; + $codeAlphabet .= '0123456789'; if ($extended === true) { $codeAlphabet .= "!\"#$%&'()*+,-./:;<=>?@[\\]_{}"; } if ($hard === true) { - $codeAlphabet .= "^`|~"; + $codeAlphabet .= '^`|~'; } $max = strlen($codeAlphabet); for ($i = 0; $i < $length; $i++) { @@ -222,8 +222,10 @@ function get_memory_limit(): int switch ($matches[2]) { case 'G': $memory_limit = $memory_limit * 1024; + // no break case 'M': $memory_limit = $memory_limit * 1024; + // no break case 'K': $memory_limit = $memory_limit * 1024; break; @@ -239,13 +241,13 @@ function get_memory_limit(): int function show_bytes($str, $columns = null): string { - $result = ""; + $result = ''; if ($columns === null) { $columns = strlen((string) $str); } $c = $columns; for ($i = 0; $i < strlen((string) $str); $i++) { - $result .= sprintf("%02x ", ord($str[$i])); + $result .= sprintf('%02x ', ord($str[$i])); $c--; if ($c === 0) { $c = $columns; diff --git a/src/helpers/x509.php b/src/helpers/x509.php index 28ff6be..80db0ee 100644 --- a/src/helpers/x509.php +++ b/src/helpers/x509.php @@ -43,7 +43,7 @@ public static function tsa_query($binaryData, $hashAlg = 'sha256'): false|string $tsReqData = asn1::seq( asn1::int(1) . asn1::seq( - asn1::seq($hexOidHashAlgos[$hashAlg] . "0500") . // object OBJ $hexOidHashAlgos[$hashAlg] & OBJ_null + asn1::seq($hexOidHashAlgos[$hashAlg] . '0500') . // object OBJ $hexOidHashAlgos[$hashAlg] & OBJ_null asn1::oct($hash) ) . asn1::int(hash('crc32', random_int(0, mt_getrandmax())) . '001') . // tsa nonce @@ -53,45 +53,6 @@ public static function tsa_query($binaryData, $hashAlg = 'sha256'): false|string return hex2bin($tsReqData); } - /** - * Calculate 32bit (8 hex) openssl subject hash old and new - * - * @param string $hex_subjSequence hex subject name sequence - * - * @return array subject hash old and new - */ - private static function opensslSubjHash($hex_subjSequence): array - { - $parse = asn1::parse($hex_subjSequence, 3); - $hex_subjSequence_new = ''; - foreach ($parse[0] as $k => $v) { - if (is_numeric($k)) { - $hex_subjSequence_new .= asn1::set( - asn1::seq( - $v[0][0]['hexdump'] . - asn1::utf8(strtolower(hex2bin((string) $v[0][1]['value_hex']))) - ) - ); - } - } - $tohash = pack("H*", $hex_subjSequence_new); - $openssl_subjHash_new = hash('sha1', $tohash); - $openssl_subjHash_new = substr($openssl_subjHash_new, 0, 8); - $openssl_subjHash_new = str_split($openssl_subjHash_new, 2); - $openssl_subjHash_new = array_reverse($openssl_subjHash_new); - $openssl_subjHash_new = implode("", $openssl_subjHash_new); - $openssl_subjHash_old = hash('md5', hex2bin($hex_subjSequence)); - $openssl_subjHash_old = substr($openssl_subjHash_old, 0, 8); - $openssl_subjHash_old = str_split($openssl_subjHash_old, 2); - $openssl_subjHash_old = array_reverse($openssl_subjHash_old); - $openssl_subjHash_old = implode("", $openssl_subjHash_old); - - return [ - "old" => $openssl_subjHash_old, - "new" => $openssl_subjHash_new, - ]; - } - /** * Parsing ocsp response data * @@ -101,7 +62,7 @@ private static function opensslSubjHash($hex_subjSequence): array */ public static function ocsp_response_parse($binaryOcspResp, &$status = '') { - $hex = current(unpack("H*", $binaryOcspResp)); + $hex = current(unpack('H*', $binaryOcspResp)); $parse = asn1::parse($hex, 10); if ($parse[0]['type'] == '30') { $ocsp = $parse[0]; @@ -370,8 +331,8 @@ public static function ocsp_request($serialNumber, $issuerNameHash, $issuerKeyHa { $Request = false; $hashAlgorithm = asn1::seq( - "06052B0E03021A" . // OBJ_sha1 - "0500" + '06052B0E03021A' . // OBJ_sha1 + '0500' ); $issuerNameHash = asn1::oct($issuerNameHash); $issuerKeyHash = asn1::oct($issuerKeyHash); @@ -379,7 +340,7 @@ public static function ocsp_request($serialNumber, $issuerNameHash, $issuerKeyHa $CertID = asn1::seq($hashAlgorithm . $issuerNameHash . $issuerKeyHash . $serialNumber); $Request = asn1::seq($CertID); // one request if ($signer_cert) { - $requestorName = asn1::expl("1", asn1::expl("4", $subjectName)); + $requestorName = asn1::expl('1', asn1::expl('4', $subjectName)); } else { $requestorName = false; } @@ -388,9 +349,9 @@ public static function ocsp_request($serialNumber, $issuerNameHash, $issuerKeyHa $nonce = md5(base64_encode($rand) . $rand); $ReqExts = asn1::seq( '06092B0601050507300102' . // OBJ_id_pkix_OCSP_Nonce - asn1::oct("0410" . $nonce) + asn1::oct('0410' . $nonce) ); - $requestExtensions = asn1::expl("2", asn1::seq($ReqExts)); + $requestExtensions = asn1::expl('2', asn1::seq($ReqExts)); $TBSRequest = asn1::seq($requestorName . $requestList . $requestExtensions); $optionalSignature = ''; if ($signer_cert) { @@ -399,12 +360,12 @@ public static function ocsp_request($serialNumber, $issuerNameHash, $issuerKeyHa } $signatureAlgorithm = asn1::seq( '06092A864886F70D010105' . // OBJ_sha1WithRSAEncryption. - "0500" + '0500' ); - $signature = asn1::bit("00" . bin2hex((string) $signature_value)); - $signer_cert = x509::x509_pem2der($signer_cert); - $certs = asn1::expl("0", asn1::seq(bin2hex($signer_cert))); - $optionalSignature = asn1::expl("0", asn1::seq($signatureAlgorithm . $signature . $certs)); + $signature = asn1::bit('00' . bin2hex((string) $signature_value)); + $signer_cert = self::x509_pem2der($signer_cert); + $certs = asn1::expl('0', asn1::seq(bin2hex($signer_cert))); + $optionalSignature = asn1::expl('0', asn1::seq($signatureAlgorithm . $signature . $certs)); } $OCSPRequest = asn1::seq($TBSRequest . $optionalSignature); @@ -432,8 +393,8 @@ public static function crl_pem2der($crl): false|string return false; } $crl = substr($crl, 0, $endPos); - $crl = str_replace("\n", "", $crl); - $crl = str_replace("\r", "", $crl); + $crl = str_replace("\n", '', $crl); + $crl = str_replace("\r", '', $crl); $dercrl = base64_decode($crl, true); return $dercrl; @@ -460,203 +421,6 @@ public static function crl_read($crl): false|array return $res; } - /** - * parsing crl from pem or der (binary) - * - * @param string $crl pem or der crl - * @param string $oidprint option show obj as hex/oid - * - * @return array parsed crl - */ - private static function parsecrl(array $crl, $oidprint = false) - { - if ($derCrl = self::crl_pem2der($crl)) { - $derCrl = bin2hex($derCrl); - } else { - $derCrl = bin2hex($crl); - } - $curr = asn1::parse($derCrl, 7); - foreach ($curr as $key => $value) { - if ($value['type'] == '30') { - $curr['crl'] = $curr[$key]; - unset($curr[$key]); - } - } - $ar = $curr; - if (! array_key_exists('crl', $ar)) { - return false; - } - $curr = $ar['crl']; - foreach ($curr as $key => $value) { - if (is_numeric($key)) { - if ($value['type'] == '30' && ! array_key_exists('TBSCertList', $curr)) { - $curr['TBSCertList'] = $curr[$key]; - unset($curr[$key]); - } - if ($value['type'] == '30') { - $curr['signatureAlgorithm'] = self::oidfromhex($value[0]['value_hex']); - unset($curr[$key]); - } - if ($value['type'] == '03') { - $curr['signature'] = substr((string) $value['value'], 2); - unset($curr[$key]); - } - } else { - unset($curr[$key]); - } - } - $ar['crl'] = $curr; - $curr = $ar['crl']['TBSCertList']; - foreach ($curr as $key => $value) { - if (is_numeric($key)) { - if ($value['type'] == '02') { - $curr['version'] = $curr[$key]['value']; - unset($curr[$key]); - } - if ($value['type'] == '30' && ! array_key_exists('signature', $curr)) { - $curr['signature'] = $value[0]['value_hex']; - unset($curr[$key]); - continue; - } - if ($value['type'] == '30' && ! array_key_exists('issuer', $curr)) { - $curr['issuer'] = $value; - unset($curr[$key]); - continue; - } - if ($value['type'] == '17' && ! array_key_exists('thisUpdate', $curr)) { - $curr['thisUpdate'] = hex2bin((string) $value['value_hex']); - unset($curr[$key]); - continue; - } - if ($value['type'] == '17' && ! array_key_exists('nextUpdate', $curr)) { - $curr['nextUpdate'] = hex2bin((string) $value['value_hex']); - unset($curr[$key]); - continue; - } - if ($value['type'] == '30' && ! array_key_exists('revokedCertificates', $curr)) { - $curr['revokedCertificates'] = $value; - unset($curr[$key]); - continue; - } - if ($value['type'] == 'a0') { - $curr['crlExtensions'] = $curr[$key]; - unset($curr[$key]); - } - } else { - unset($curr[$key]); - } - } - $ar['crl']['TBSCertList'] = $curr; - if (array_key_exists('revokedCertificates', $curr)) { - $curr = $ar['crl']['TBSCertList']['revokedCertificates']; - foreach ($curr as $key => $value) { - if (is_numeric($key)) { - if ($value['type'] == '30') { - $serial = $value[0]['value']; - $revoked['time'] = hex2bin((string) $value[1]['value_hex']); - $lists[$serial] = $revoked; - unset($curr[$key]); - } - } else { - unset($curr['depth']); - unset($curr['type']); - unset($curr['typeName']); - } - } - $curr['lists'] = $lists; - $ar['crl']['TBSCertList']['revokedCertificates'] = $curr; - } - if (array_key_exists('crlExtensions', $ar['crl']['TBSCertList'])) { - $curr = $ar['crl']['TBSCertList']['crlExtensions'][0]; - unset($ar['crl']['TBSCertList']['crlExtensions']); - foreach ($curr as $key => $value) { - if (is_numeric($key)) { - $attributes_name = self::oidfromhex($value[0]['value_hex']); - if ($oidprint == 'oid') { - $attributes_name = self::oidfromhex($value[0]['value_hex']); - } - if ($oidprint == 'hex') { - $attributes_name = $value[0]['value_hex']; - } - $attributes_oid = self::oidfromhex($value[0]['value_hex']); - if ($value['type'] == '30') { - $crlExtensionsValue = $value[1][0]; - if ($attributes_oid == '2.5.29.20') { // OBJ_crl_number - $crlExtensionsValue = $crlExtensionsValue['value']; - } - if ($attributes_oid == '2.5.29.35') { // OBJ_authority_key_identifier - foreach ($crlExtensionsValue as $authority_key_identifierValueK => $authority_key_identifierV) { - if (is_numeric($authority_key_identifierValueK)) { - if ($authority_key_identifierV['type'] == '80') { - $authority_key_identifier['keyIdentifier'] = $authority_key_identifierV['value_hex']; - } - if ($authority_key_identifierV['type'] == 'a1') { - $authority_key_identifier['authorityCertIssuer'] = $authority_key_identifierV['value_hex']; - } - if ($authority_key_identifierV['type'] == '82') { - $authority_key_identifier['authorityCertSerialNumber'] = $authority_key_identifierV['value_hex']; - } - } - } - $crlExtensionsValue = $authority_key_identifier; - } - $attribute_list = $crlExtensionsValue; - } - $ar['crl']['TBSCertList']['crlExtensions'][$attributes_name] = $attribute_list; - } - } - } - $curr = $ar['crl']['TBSCertList']['issuer']; - foreach ($curr as $key => $value) { - if (is_numeric($key)) { - if ($value['type'] == '31') { - if ($oidprint == 'oid') { - $subjOID = self::oidfromhex($curr[$key][0][0]['value_hex']); - } elseif ($oidprint == 'hex') { - $subjOID = $curr[$key][0][0]['value_hex']; - } else { - $subjOID = self::oidfromhex($curr[$key][0][0]['value_hex']); - } - $curr[$subjOID][] = hex2bin((string) $curr[$key][0][1]['value_hex']); - unset($curr[$key]); - } - } else { - unset($curr['depth']); - unset($curr['type']); - unset($curr['typeName']); - if ($key == 'hexdump') { - $curr['sha1'] = hash('sha1', pack("H*", $value)); - } - } - } - $ar['crl']['TBSCertList']['issuer'] = $curr; - $arrModel['TBSCertList']['version'] = ''; - $arrModel['TBSCertList']['signature'] = ''; - $arrModel['TBSCertList']['issuer'] = ''; - $arrModel['TBSCertList']['thisUpdate'] = ''; - $arrModel['TBSCertList']['nextUpdate'] = ''; - $arrModel['signatureAlgorithm'] = ''; - $arrModel['signature'] = ''; - $crl = $ar['crl']; - $differ = array_diff_key($arrModel, $crl); - if (count($differ) == 0) { - $differ = array_diff_key($arrModel['TBSCertList'], $crl['TBSCertList']); - if (count($differ) > 0) { - foreach ($differ as $key => $val) { - } - - return false; - } - } else { - foreach ($differ as $key => $val) { - } - - return false; - } - - return $ar['crl']; - } - /** * Convert x509 pem certificate to x509 der * @@ -714,16 +478,15 @@ public static function get_cert($certin): string|false openssl_x509_export($rsccert, $cert); return self::x509_pem2der($cert); - } else { - $pem = @self::x509_der2pem($certin); - if ($rsccert = @openssl_x509_read($pem)) { - openssl_x509_export($rsccert, $cert); + } + $pem = @self::x509_der2pem($certin); + if ($rsccert = @openssl_x509_read($pem)) { + openssl_x509_export($rsccert, $cert); - return self::x509_pem2der($cert); - } else { - return false; - } + return self::x509_pem2der($cert); } + return false; + } /** @@ -976,6 +739,242 @@ public static function readcert($cert_in, $oidprint = false) return $ar['cert']; } + /** + * Calculate 32bit (8 hex) openssl subject hash old and new + * + * @param string $hex_subjSequence hex subject name sequence + * + * @return array subject hash old and new + */ + private static function opensslSubjHash($hex_subjSequence): array + { + $parse = asn1::parse($hex_subjSequence, 3); + $hex_subjSequence_new = ''; + foreach ($parse[0] as $k => $v) { + if (is_numeric($k)) { + $hex_subjSequence_new .= asn1::set( + asn1::seq( + $v[0][0]['hexdump'] . + asn1::utf8(strtolower(hex2bin((string) $v[0][1]['value_hex']))) + ) + ); + } + } + $tohash = pack('H*', $hex_subjSequence_new); + $openssl_subjHash_new = hash('sha1', $tohash); + $openssl_subjHash_new = substr($openssl_subjHash_new, 0, 8); + $openssl_subjHash_new = str_split($openssl_subjHash_new, 2); + $openssl_subjHash_new = array_reverse($openssl_subjHash_new); + $openssl_subjHash_new = implode('', $openssl_subjHash_new); + $openssl_subjHash_old = hash('md5', hex2bin($hex_subjSequence)); + $openssl_subjHash_old = substr($openssl_subjHash_old, 0, 8); + $openssl_subjHash_old = str_split($openssl_subjHash_old, 2); + $openssl_subjHash_old = array_reverse($openssl_subjHash_old); + $openssl_subjHash_old = implode('', $openssl_subjHash_old); + + return [ + 'old' => $openssl_subjHash_old, + 'new' => $openssl_subjHash_new, + ]; + } + + /** + * parsing crl from pem or der (binary) + * + * @param string $crl pem or der crl + * @param string $oidprint option show obj as hex/oid + * + * @return array parsed crl + */ + private static function parsecrl(array $crl, $oidprint = false) + { + if ($derCrl = self::crl_pem2der($crl)) { + $derCrl = bin2hex($derCrl); + } else { + $derCrl = bin2hex($crl); + } + $curr = asn1::parse($derCrl, 7); + foreach ($curr as $key => $value) { + if ($value['type'] == '30') { + $curr['crl'] = $curr[$key]; + unset($curr[$key]); + } + } + $ar = $curr; + if (! array_key_exists('crl', $ar)) { + return false; + } + $curr = $ar['crl']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '30' && ! array_key_exists('TBSCertList', $curr)) { + $curr['TBSCertList'] = $curr[$key]; + unset($curr[$key]); + } + if ($value['type'] == '30') { + $curr['signatureAlgorithm'] = self::oidfromhex($value[0]['value_hex']); + unset($curr[$key]); + } + if ($value['type'] == '03') { + $curr['signature'] = substr((string) $value['value'], 2); + unset($curr[$key]); + } + } else { + unset($curr[$key]); + } + } + $ar['crl'] = $curr; + $curr = $ar['crl']['TBSCertList']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '02') { + $curr['version'] = $curr[$key]['value']; + unset($curr[$key]); + } + if ($value['type'] == '30' && ! array_key_exists('signature', $curr)) { + $curr['signature'] = $value[0]['value_hex']; + unset($curr[$key]); + continue; + } + if ($value['type'] == '30' && ! array_key_exists('issuer', $curr)) { + $curr['issuer'] = $value; + unset($curr[$key]); + continue; + } + if ($value['type'] == '17' && ! array_key_exists('thisUpdate', $curr)) { + $curr['thisUpdate'] = hex2bin((string) $value['value_hex']); + unset($curr[$key]); + continue; + } + if ($value['type'] == '17' && ! array_key_exists('nextUpdate', $curr)) { + $curr['nextUpdate'] = hex2bin((string) $value['value_hex']); + unset($curr[$key]); + continue; + } + if ($value['type'] == '30' && ! array_key_exists('revokedCertificates', $curr)) { + $curr['revokedCertificates'] = $value; + unset($curr[$key]); + continue; + } + if ($value['type'] == 'a0') { + $curr['crlExtensions'] = $curr[$key]; + unset($curr[$key]); + } + } else { + unset($curr[$key]); + } + } + $ar['crl']['TBSCertList'] = $curr; + if (array_key_exists('revokedCertificates', $curr)) { + $curr = $ar['crl']['TBSCertList']['revokedCertificates']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '30') { + $serial = $value[0]['value']; + $revoked['time'] = hex2bin((string) $value[1]['value_hex']); + $lists[$serial] = $revoked; + unset($curr[$key]); + } + } else { + unset($curr['depth']); + unset($curr['type']); + unset($curr['typeName']); + } + } + $curr['lists'] = $lists; + $ar['crl']['TBSCertList']['revokedCertificates'] = $curr; + } + if (array_key_exists('crlExtensions', $ar['crl']['TBSCertList'])) { + $curr = $ar['crl']['TBSCertList']['crlExtensions'][0]; + unset($ar['crl']['TBSCertList']['crlExtensions']); + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + $attributes_name = self::oidfromhex($value[0]['value_hex']); + if ($oidprint == 'oid') { + $attributes_name = self::oidfromhex($value[0]['value_hex']); + } + if ($oidprint == 'hex') { + $attributes_name = $value[0]['value_hex']; + } + $attributes_oid = self::oidfromhex($value[0]['value_hex']); + if ($value['type'] == '30') { + $crlExtensionsValue = $value[1][0]; + if ($attributes_oid == '2.5.29.20') { // OBJ_crl_number + $crlExtensionsValue = $crlExtensionsValue['value']; + } + if ($attributes_oid == '2.5.29.35') { // OBJ_authority_key_identifier + foreach ($crlExtensionsValue as $authority_key_identifierValueK => $authority_key_identifierV) { + if (is_numeric($authority_key_identifierValueK)) { + if ($authority_key_identifierV['type'] == '80') { + $authority_key_identifier['keyIdentifier'] = $authority_key_identifierV['value_hex']; + } + if ($authority_key_identifierV['type'] == 'a1') { + $authority_key_identifier['authorityCertIssuer'] = $authority_key_identifierV['value_hex']; + } + if ($authority_key_identifierV['type'] == '82') { + $authority_key_identifier['authorityCertSerialNumber'] = $authority_key_identifierV['value_hex']; + } + } + } + $crlExtensionsValue = $authority_key_identifier; + } + $attribute_list = $crlExtensionsValue; + } + $ar['crl']['TBSCertList']['crlExtensions'][$attributes_name] = $attribute_list; + } + } + } + $curr = $ar['crl']['TBSCertList']['issuer']; + foreach ($curr as $key => $value) { + if (is_numeric($key)) { + if ($value['type'] == '31') { + if ($oidprint == 'oid') { + $subjOID = self::oidfromhex($curr[$key][0][0]['value_hex']); + } elseif ($oidprint == 'hex') { + $subjOID = $curr[$key][0][0]['value_hex']; + } else { + $subjOID = self::oidfromhex($curr[$key][0][0]['value_hex']); + } + $curr[$subjOID][] = hex2bin((string) $curr[$key][0][1]['value_hex']); + unset($curr[$key]); + } + } else { + unset($curr['depth']); + unset($curr['type']); + unset($curr['typeName']); + if ($key == 'hexdump') { + $curr['sha1'] = hash('sha1', pack('H*', $value)); + } + } + } + $ar['crl']['TBSCertList']['issuer'] = $curr; + $arrModel['TBSCertList']['version'] = ''; + $arrModel['TBSCertList']['signature'] = ''; + $arrModel['TBSCertList']['issuer'] = ''; + $arrModel['TBSCertList']['thisUpdate'] = ''; + $arrModel['TBSCertList']['nextUpdate'] = ''; + $arrModel['signatureAlgorithm'] = ''; + $arrModel['signature'] = ''; + $crl = $ar['crl']; + $differ = array_diff_key($arrModel, $crl); + if (count($differ) == 0) { + $differ = array_diff_key($arrModel['TBSCertList'], $crl['TBSCertList']); + if (count($differ) > 0) { + foreach ($differ as $key => $val) { + } + + return false; + } + } else { + foreach ($differ as $key => $val) { + } + + return false; + } + + return $ar['crl']; + } + /** * read oid number of given hex (convert hex to oid) * @@ -1003,19 +1002,19 @@ private static function oidfromhex($hex): string if ($dec > 129) { $nex = (128 * ($dec - 128)) - 80; } - $result = "2."; + $result = '2.'; } if ($dec >= 80 && $dec < 128) { $first = $dec - 80; - $result = "2.$first."; + $result = "2.{$first}."; } if ($dec >= 40 && $dec < 80) { $first = $dec - 40; - $result = "1.$first."; + $result = "1.{$first}."; } if ($dec < 40) { $first = $dec - 0; - $result = "0.$first."; + $result = "0.{$first}."; } } else { if ($dec > 127) { @@ -1025,7 +1024,7 @@ private static function oidfromhex($hex): string $nex = ($nex * 128) + $mplx[$i]; } } else { - $result .= ($dec + $nex) . "."; + $result .= ($dec + $nex) . '.'; if ($dec <= 127) { $nex = 0; } @@ -1034,6 +1033,6 @@ private static function oidfromhex($hex): string $i++; } - return rtrim($result, "."); + return rtrim($result, '.'); } } diff --git a/src/pdfvalue/PDFValue.php b/src/pdfvalue/PDFValue.php index 3f8cda2..2955be1 100644 --- a/src/pdfvalue/PDFValue.php +++ b/src/pdfvalue/PDFValue.php @@ -32,14 +32,14 @@ public function __construct( ) { } - public function val() + public function __toString(): string { - return $this->value; + return '' . $this->value; } - public function __toString(): string + public function val() { - return "" . $this->value; + return $this->value; } public function offsetExists($offset): bool diff --git a/src/pdfvalue/PDFValueHexString.php b/src/pdfvalue/PDFValueHexString.php index 6800fb4..802814f 100644 --- a/src/pdfvalue/PDFValueHexString.php +++ b/src/pdfvalue/PDFValueHexString.php @@ -25,6 +25,6 @@ class PDFValueHexString extends PDFValueString { public function __toString(): string { - return "<" . trim((string) $this->value) . ">"; + return '<' . trim((string) $this->value) . '>'; } } diff --git a/src/pdfvalue/PDFValueList.php b/src/pdfvalue/PDFValueList.php index 40660fa..ba4a302 100644 --- a/src/pdfvalue/PDFValueList.php +++ b/src/pdfvalue/PDFValueList.php @@ -58,8 +58,8 @@ public function val($list = false) if ($list === true) { $result = []; foreach ($this->value as $v) { - if (is_a($v, "ddn\\sapp\\pdfvalue\\PDFValueSimple")) { - $v = explode(" ", (string) $v->val()); + if (is_a($v, 'ddn\\sapp\\pdfvalue\\PDFValueSimple')) { + $v = explode(' ', (string) $v->val()); } else { $v = [$v->val()]; } @@ -67,9 +67,9 @@ public function val($list = false) } return $result; - } else { - return parent::val(); } + return parent::val(); + } /** @@ -79,9 +79,9 @@ public function get_object_referenced(): false|array { $ids = []; $plain_text_val = implode(' ', $this->value); - if (trim($plain_text_val) !== "") { + if (trim($plain_text_val) !== '') { if (preg_match_all('/(([0-9]+)\s+[0-9]+\s+R)[^0-9]*/ms', $plain_text_val, $matches) > 0) { - $rebuilt = implode(" ", $matches[0]); + $rebuilt = implode(' ', $matches[0]); $rebuilt = preg_replace('/\s+/ms', ' ', $rebuilt); $plain_text_val = preg_replace('/\s+/ms', ' ', $plain_text_val); if ($plain_text_val === $rebuilt) { diff --git a/src/pdfvalue/PDFValueObject.php b/src/pdfvalue/PDFValueObject.php index 33edb70..b225f73 100644 --- a/src/pdfvalue/PDFValueObject.php +++ b/src/pdfvalue/PDFValueObject.php @@ -32,14 +32,37 @@ public function __construct($value = []) parent::__construct($result); } - public function diff($other): false|null|PDFValueObject + /** + * Function to output the object using the PDF format, and trying to make it compact (by reducing spaces, depending on the values) + * + * @return pdfentry the PDF entry for the object + */ + public function __toString(): string + { + $result = []; + foreach ($this->value as $k => $v) { + $v = '' . $v; + if ($v === '') { + array_push($result, "/{$k}"); + continue; + } + match ($v[0]) { + '/', '[', '(', '<' => array_push($result, "/{$k}{$v}"), + default => array_push($result, "/{$k} {$v}"), + }; + } + + return '<<' . implode('', $result) . '>>'; + } + + public function diff($other): false|null|self { $different = parent::diff($other); if (($different === false) || ($different === null)) { return $different; } - $result = new PDFValueObject(); + $result = new self(); $differences = 0; foreach ($this->value as $k => $v) { @@ -68,7 +91,7 @@ public function diff($other): false|null|PDFValueObject return $result; } - public static function fromarray($parts): false|PDFValueObject + public static function fromarray($parts): false|self { $k = array_keys($parts); $intkeys = false; @@ -86,10 +109,10 @@ public static function fromarray($parts): false|PDFValueObject $result[$k] = self::_convert($v); } - return new PDFValueObject($result); + return new self($result); } - public static function fromstring($str): false|PDFValueObject + public static function fromstring($str): false|self { $result = []; $field = null; @@ -119,7 +142,7 @@ public static function fromstring($str): false|PDFValueObject return false; } - return new PDFValueObject($result); + return new self($result); } public function get_keys(): false|array @@ -152,27 +175,4 @@ public function offsetExists($offset): bool { return isset($this->value[$offset]); } - - /** - * Function to output the object using the PDF format, and trying to make it compact (by reducing spaces, depending on the values) - * - * @return pdfentry the PDF entry for the object - */ - public function __toString(): string - { - $result = []; - foreach ($this->value as $k => $v) { - $v = "" . $v; - if ($v === "") { - array_push($result, "/$k"); - continue; - } - match ($v[0]) { - '/', '[', '(', '<' => array_push($result, "/$k$v"), - default => array_push($result, "/$k $v"), - }; - } - - return "<<" . implode('', $result) . ">>"; - } } diff --git a/src/pdfvalue/PDFValueReference.php b/src/pdfvalue/PDFValueReference.php index 4776b02..bf24801 100644 --- a/src/pdfvalue/PDFValueReference.php +++ b/src/pdfvalue/PDFValueReference.php @@ -28,6 +28,6 @@ class PDFValueReference extends PDFValueSimple { public function __construct($oid) { - parent::__construct(sprintf("%d 0 R", $oid)); + parent::__construct(sprintf('%d 0 R', $oid)); } } diff --git a/src/pdfvalue/PDFValueString.php b/src/pdfvalue/PDFValueString.php index f1e4ece..749e6b2 100644 --- a/src/pdfvalue/PDFValueString.php +++ b/src/pdfvalue/PDFValueString.php @@ -25,6 +25,6 @@ class PDFValueString extends PDFValue { public function __toString(): string { - return "(" . $this->value . ")"; + return '(' . $this->value . ')'; } } diff --git a/src/pdfvalue/PDFValueType.php b/src/pdfvalue/PDFValueType.php index 86aee6d..30010bf 100644 --- a/src/pdfvalue/PDFValueType.php +++ b/src/pdfvalue/PDFValueType.php @@ -25,6 +25,6 @@ class PDFValueType extends PDFValue { public function __toString(): string { - return "/" . trim((string) $this->value); + return '/' . trim((string) $this->value); } } From 537b44d9c200f4e966c5096087ab17e0d25e4404 Mon Sep 17 00:00:00 2001 From: Marcin Jakubowski Date: Thu, 31 Oct 2024 22:32:20 +0100 Subject: [PATCH 04/11] modernization and cleanup --- composer.json | 1 + pdfdeflate.php | 2 +- pdfsigni.php | 133 +++++++++++++++------------ src/PDFDoc.php | 70 +++++++------- src/PDFDocWithContents.php | 11 ++- src/PDFObject.php | 31 +++---- src/PDFObjectParser.php | 33 ++----- src/PDFSignatureObject.php | 5 +- src/PDFUtilFnc.php | 68 +++++++------- src/helpers/Buffer.php | 5 +- src/helpers/CMS.php | 51 +++++----- src/helpers/DependencyTreeObject.php | 63 +++++-------- src/helpers/StreamReader.php | 10 +- src/helpers/UUID.php | 20 ++-- src/helpers/asn1.php | 39 ++++---- src/helpers/contentgeneration.php | 5 +- src/helpers/fpdfhelpers.php | 19 ++-- src/helpers/helpers.php | 39 ++++---- src/helpers/x509.php | 92 +++++++++--------- src/pdfvalue/PDFValue.php | 12 +-- src/pdfvalue/PDFValueList.php | 10 +- src/pdfvalue/PDFValueObject.php | 14 +-- src/pdfvalue/PDFValueSimple.php | 4 +- 23 files changed, 341 insertions(+), 396 deletions(-) diff --git a/composer.json b/composer.json index 8b3148c..503d6bf 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "require": { "php": ">=8.1", "ext-curl": "*", + "ext-fileinfo": "*", "ext-openssl": "*", "ext-zlib": "*", "psr/log": "^1|^2|^3" diff --git a/pdfdeflate.php b/pdfdeflate.php index e8d395f..34c457f 100644 --- a/pdfdeflate.php +++ b/pdfdeflate.php @@ -33,7 +33,7 @@ $toid = null; if ($argc === 3) - $toid = intval($argv[2]); + $toid = (int)$argv[2]; if ($doc === false) fwrite(STDERR, "failed to parse file " . $argv[1]); diff --git a/pdfsigni.php b/pdfsigni.php index cd8bcd4..39c5a1d 100755 --- a/pdfsigni.php +++ b/pdfsigni.php @@ -22,69 +22,80 @@ use ddn\sapp\PDFDoc; +use function ddn\sapp\helpers\p_error; + require_once('vendor/autoload.php'); -if ($argc !== 4) +if ($argc !== 4) { fwrite(STDERR, sprintf("usage: %s ", $argv[0])); -else { - if (!file_exists($argv[1])) - fwrite(STDERR, "failed to open file " . $argv[1]); - else { - // Silently prompt for the password - fwrite(STDERR, "Password: "); - system('stty -echo'); - $password = trim(fgets(STDIN)); - system('stty echo'); - fwrite(STDERR, "\n"); - - $file_content = file_get_contents($argv[1]); - $obj = PDFDoc::from_string($file_content); - - if ($obj === false) - fwrite(STDERR, "failed to parse file " . $argv[1]); - else { - $position = [ ]; - $image = $argv[2]; - $imagesize = @getimagesize($image); - if ($imagesize === false) { - fwrite(STDERR, "failed to open the image $image"); - return; - } - $pagesize = $obj->get_page_size(0); - if ($pagesize === false) - return p_error("failed to get page size"); - - $pagesize = explode(" ", $pagesize[0]->val()); - // Calculate the position of the image according to its size and the size of the page; - // the idea is to keep the aspect ratio and center the image in the page with a size - // of 1/3 of the size of the page. - $p_x = intval("". $pagesize[0]); - $p_y = intval("". $pagesize[1]); - $p_w = intval("". $pagesize[2]) - $p_x; - $p_h = intval("". $pagesize[3]) - $p_y; - $i_w = $imagesize[0]; - $i_h = $imagesize[1]; - - $ratio_x = $p_w / $i_w; - $ratio_y = $p_h / $i_h; - $ratio = min($ratio_x, $ratio_y); - - $i_w = ($i_w * $ratio) / 3; - $i_h = ($i_h * $ratio) / 3; - $p_x = $p_w / 3; - $p_y = $p_h / 3; - - // Set the image appearance and the certificate file - $obj->set_signature_appearance(0, [ $p_x, $p_y, $p_x + $i_w, $p_y + $i_h ], $image); - if (!$obj->set_signature_certificate($argv[3], $password)) { - fwrite(STDERR, "the certificate is not valid"); - } else { - $docsigned = $obj->to_pdf_file_s(); - if ($docsigned === false) - fwrite(STDERR, "could not sign the document"); - else - echo $docsigned; - } - } + exit(1); +} +if (!file_exists($argv[1])) { + fwrite(STDERR, "failed to open file " . $argv[1]); + exit(1); +} + +// Silently prompt for the password +//fwrite(STDERR, "Password: "); +//system('stty -echo'); +//$password = trim(fgets(STDIN)); +//system('stty echo'); +//fwrite(STDERR, "\n"); + +$password=''; + +$file_content = file_get_contents($argv[1]); +$obj = PDFDoc::from_string($file_content); + +if ($obj === false) { + fwrite(STDERR, "failed to parse file " . $argv[1]); + + exit(1); +} + +$position = []; +$image = $argv[2]; +$imagesize = @getimagesize($image); +if ($imagesize === false) { + fwrite(STDERR, "failed to open the image $image"); + + exit(1); +} + + +$pagesize = $obj->get_page_size(0); +if ($pagesize === false) { + return p_error("failed to get page size"); +} + +$pagesize = explode(" ", $pagesize[0]->val()); +// Calculate the position of the image according to its size and the size of the page; +// the idea is to keep the aspect ratio and center the image in the page with a size +// of 1/3 of the size of the page. +$p_x = (int)("" . $pagesize[0]); +$p_y = (int)("" . $pagesize[1]); +$p_w = (int)("" . $pagesize[2]) - $p_x; +$p_h = (int)("" . $pagesize[3]) - $p_y; +$i_w = $imagesize[0]; +$i_h = $imagesize[1]; + +$ratio_x = $p_w / $i_w; +$ratio_y = $p_h / $i_h; +$ratio = min($ratio_x, $ratio_y); + +$i_w = ($i_w * $ratio) / 3; +$i_h = ($i_h * $ratio) / 3; +$p_x = $p_w / 3; +$p_y = $p_h / 3; +// Set the image appearance and the certificate file +$obj->set_signature_appearance(0, [$p_x, $p_y, $p_x + $i_w, $p_y + $i_h], $image); +if (!$obj->set_signature_certificate($argv[3], $password)) { + fwrite(STDERR, "the certificate is not valid"); +} else { + $docsigned = $obj->to_pdf_file_s(); + if ($docsigned === false) { + fwrite(STDERR, "could not sign the document"); + } else { + echo $docsigned; } } diff --git a/src/PDFDoc.php b/src/PDFDoc.php index 8897930..bcbf5e3 100644 --- a/src/PDFDoc.php +++ b/src/PDFDoc.php @@ -44,14 +44,10 @@ // Loading the functions -if (! defined('ddn\\sapp\\helpers\\LoadHelpers')) { +if (! defined(LoadHelpers::class)) { new LoadHelpers(); } -if (! defined('__TMP_FOLDER')) { - define('__TMP_FOLDER', sys_get_temp_dir()); -} - // TODO: move the signature of documents to a new class (i.e. PDFDocSignable) // TODO: create a new class "PDFDocIncremental" @@ -122,10 +118,10 @@ public function push_state(): void foreach ($this->_pdf_objects as $oid => $object) { $cloned_objects[$oid] = clone $object; } - array_push($this->_backup_state, [ + $this->_backup_state[] = [ 'max_oid' => $this->_max_oid, 'pdf_objects' => $cloned_objects, - ]); + ]; } /** @@ -155,7 +151,7 @@ public function pop_state(): bool * otherwise only the object ids from the latest $depth versions will be considered * (if it is an incremental updated document) */ - public static function from_string($buffer, $depth = null): false|self + public static function from_string(string $buffer, ?int $depth = null): false|self { $structure = PDFUtilFnc::acquire_structure($buffer, $depth); if ($structure === false) { @@ -285,7 +281,7 @@ public function get_indirect_object($reference) * * @return obj the object retrieved (or false if not found) */ - public function get_object(int $oid, $original_version = false) + public function get_object(int $oid, bool $original_version = false) { if ($original_version === true) { // Prioritizing the original version @@ -347,7 +343,7 @@ public function clear_signature_certificate(): void * * @return valid true if the certificate can be used to sign the document, false otherwise */ - public function set_signature_certificate($certfile, $certpass = null) + public function set_signature_certificate($certfile, ?string $certpass = null) { // First we read the certificate if (is_array($certfile)) { @@ -479,7 +475,7 @@ public function set_version($version): bool * * @return obj the PDFObject created */ - public function create_object($value = [], $class = 'ddn\\sapp\\PDFObject', $autoadd = true): PDFObject + public function create_object($value = [], $class = PDFObject::class, $autoadd = true): PDFObject { $o = new $class($this->get_new_oid(), $value); if ($autoadd === true) { @@ -523,7 +519,7 @@ public function add_object(PDFObject $pdf_object): bool * * @return buffer a buffer that contains a pdf dumpable document */ - public function to_pdf_file_b($rebuild = false): Buffer + public function to_pdf_file_b(bool $rebuild = false): Buffer { // We made no updates, so return the original doc if (($rebuild === false) && (count($this->_pdf_objects) === 0) && ($this->_certificate === null) && ($this->_appearance === null)) { @@ -620,7 +616,7 @@ public function to_pdf_file_b($rebuild = false): Buffer // And generate the part of the document related to the xref $_doc_from_xref = new Buffer($trailer->to_pdf_entry()); - $_doc_from_xref->data('startxref' . __EOL . "{$xref_offset}" . __EOL . '%%EOF' . __EOL); + $_doc_from_xref->data('startxref' . __EOL . $xref_offset . __EOL . '%%EOF' . __EOL); } else { p_debug('generating xref using classic xref...trailer'); $xref_content = PDFUtilFnc::build_xref($_obj_offsets); @@ -681,11 +677,9 @@ public function to_pdf_file_b($rebuild = false): Buffer * * @return buffer a buffer that contains a pdf document */ - public function to_pdf_file_s($rebuild = false) + public function to_pdf_file_s(bool $rebuild = false) { - $pdf_content = $this->to_pdf_file_b($rebuild); - - return $pdf_content->get_raw(); + return $this->to_pdf_file_b($rebuild)->get_raw(); } /** @@ -695,7 +689,7 @@ public function to_pdf_file_s($rebuild = false) * * @return written true if the file has been correcly written to the file; false otherwise */ - public function to_pdf_file($filename, $rebuild = false) + public function to_pdf_file($filename, bool $rebuild = false) { $pdf_content = $this->to_pdf_file_b($rebuild); @@ -720,7 +714,7 @@ public function to_pdf_file($filename, $rebuild = false) * * @return page the page object */ - public function get_page($i) + public function get_page(int $i): PDFObject|false { if ($i < 0) { return false; @@ -739,7 +733,7 @@ public function get_page($i) * * @return box the bounding box of the page */ - public function get_page_size($i) + public function get_page_size(int $i): false|array { $pageinfo = false; @@ -753,7 +747,7 @@ public function get_page_size($i) $pageinfo = $this->_pages_info[$i]['info']; } else { - foreach ($this->_pages_info as $k => $info) { + foreach ($this->_pages_info as $info) { if ($info['oid'] === $i->get_oid()) { $pageinfo = $info['info']; break; @@ -832,8 +826,8 @@ public function get_object_tree(): array $val = $o->get_value(); // We'll only consider those objects that may create an structure (i.e. the objects, whose fields may include references to other objects) - if (is_a($val, 'ddn\\sapp\\pdfvalue\\PDFValueObject')) { - $references = references_in_object($val, $oid); + if (is_a($val, PDFValueObject::class)) { + $references = references_in_object($val); } else { $references = $val->get_object_referenced(); if ($references === false) { @@ -856,8 +850,8 @@ public function get_object_tree(): array // $xref_children = []; - foreach ($objects as $oid => $t_object) { - if ($t_object->info == '/XRef') { + foreach ($objects as $t_object) { + if ($t_object->info === '/XRef') { array_push($xref_children, ...iterator_to_array($t_object->children())); } } @@ -866,8 +860,8 @@ public function get_object_tree(): array // Remove those objects that are child of other objects from the top of the tree foreach ($objects as $oid => $t_object) { - if (($t_object->is_child > 0) || (in_array($t_object->info, ['/XRef', '/ObjStm']))) { - if (! in_array($oid, $xref_children)) { + if (($t_object->is_child > 0) || (in_array($t_object->info, ['/XRef', '/ObjStm'], true))) { + if (! in_array($oid, $xref_children, true)) { unset($objects[$oid]); } } @@ -900,7 +894,7 @@ public function get_signatures(): array if (! is_array($o_value) || ! isset($o_value['Type'])) { continue; } - if ($o_value['Type']->val() != 'Sig') { + if ($o_value['Type']->val() !== 'Sig') { continue; } @@ -973,10 +967,8 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, $pagesize = explode(' ', (string) $pagesize[0]->val()); // Get the bounding box for the image - $p_x = intval('' . $pagesize[0]); - $p_y = intval('' . $pagesize[1]); - $p_w = intval('' . $pagesize[2]) - $p_x; - $p_h = intval('' . $pagesize[3]) - $p_y; + $p_x = (int) $pagesize[0]; + $p_y = (int) $pagesize[1]; // Add the position for the image $p_x = $p_x + $px; @@ -1049,7 +1041,7 @@ protected function get_new_oid(): int|float * * @return signature a signature object, or null if the document is not signed; false if an error happens */ - protected function _generate_signature_in_document() + protected function _generate_signature_in_document(): PDFSignatureObject|false { $imagefilename = null; $recttoappear = [0, 0, 0, 0]; @@ -1159,7 +1151,7 @@ protected function _generate_signature_in_document() // Get the page height, to change the coordinates system (up to down) $pagesize = $this->get_page_size($pagetoappear); $pagesize = explode(' ', (string) $pagesize[0]->val()); - $pagesize_h = floatval('' . $pagesize[3]) - floatval('' . $pagesize[1]); + $pagesize_h = (float) ('' . $pagesize[3]) - (float) ('' . $pagesize[1]); $bbox = [0, 0, $recttoappear[2] - $recttoappear[0], $recttoappear[3] - $recttoappear[1]]; $form_object = $this->create_object([ @@ -1233,7 +1225,7 @@ protected function _generate_signature_in_document() } $page_obj['Annots'] = new PDFValueReference($newannots->get_oid()); - array_push($updated_objects, $page_obj); + $updated_objects[] = $page_obj; // AcroForm may be an indirect object if (! isset($root_obj['AcroForm'])) { @@ -1243,9 +1235,9 @@ protected function _generate_signature_in_document() $acroform = &$root_obj['AcroForm']; if ((($referenced = $acroform->get_object_referenced()) !== false) && (! is_array($referenced))) { $acroform = $this->get_object($referenced); - array_push($updated_objects, $acroform); + $updated_objects[] = $acroform; } else { - array_push($updated_objects, $root_obj); + $updated_objects[] = $root_obj; } // Add the annotation to the interactive form @@ -1260,7 +1252,7 @@ protected function _generate_signature_in_document() } // Store the objects - foreach ($updated_objects as &$object) { + foreach ($updated_objects as $object) { $this->add_object($object); } @@ -1378,7 +1370,7 @@ protected function _generate_content_to_xref($rebuild = false): array * @return pages the ordered list of page ids corresponding to object oid, or false if any of the kid objects * is not of type page or pages. */ - protected function _get_page_info(int $oid, array $info = []) + protected function _get_page_info(int $oid, array $info = []): array { $object = $this->get_object($oid); if ($object === false) { diff --git a/src/PDFDocWithContents.php b/src/PDFDocWithContents.php index cf8259b..483771e 100644 --- a/src/PDFDocWithContents.php +++ b/src/PDFDocWithContents.php @@ -24,6 +24,7 @@ use ddn\sapp\pdfvalue\PDFValueList; use ddn\sapp\pdfvalue\PDFValueObject; use ddn\sapp\pdfvalue\PDFValueReference; +use function ddn\sapp\helpers\_add_image; use function ddn\sapp\helpers\get_random_string; use function ddn\sapp\helpers\p_error; use function ddn\sapp\helpers\p_warning; @@ -58,7 +59,7 @@ class PDFDocWithContents extends PDFDoc * @param params an array of values [ "font" => , "size" => , * "color" => <#hexcolor>, "angle" => ] */ - public function add_text($page_to_appear, $text, $x, $y, $params = []) + public function add_text(int $page_to_appear, $text, $x, $y, $params = []) { // TODO: maybe we can create a function that "adds content to a page", and that // function will search for the content field and merge the resources, if @@ -81,7 +82,7 @@ public function add_text($page_to_appear, $text, $x, $y, $params = []) $resources_obj = $this->get_indirect_object($page_obj['Resources']); - if (array_search($params['font'], self::T_STANDARD_FONTS) === false) { + if (! in_array($params['font'], self::T_STANDARD_FONTS, true)) { return p_error('only standard fonts are allowed Times-Roman, Helvetica, Courier, Symbol, ZapfDingbats'); } @@ -102,7 +103,7 @@ public function add_text($page_to_appear, $text, $x, $y, $params = []) // Get the page height, to change the coordinates system (up to down) $pagesize = $this->get_page_size($page_to_appear); - $pagesize_h = floatval('' . $pagesize[3]) - floatval('' . $pagesize[1]); + $pagesize_h = (float) ('' . $pagesize[3]) - (float) ('' . $pagesize[1]); $angle = $params['angle']; $angle *= M_PI / 180; @@ -186,9 +187,9 @@ public function add_image($page_obj, $filename, $x = 0, $y = 0, $w = 0, $h = 0) // Get the page height, to change the coordinates system (up to down) $pagesize = $this->get_page_size($page_obj); - $pagesize_h = floatval('' . $pagesize[3]) - floatval('' . $pagesize[1]); + $pagesize_h = (float) ('' . $pagesize[3]) - (float) ('' . $pagesize[1]); - $result = $this->_add_image($filename, $x, $pagesize_h - $y, $w, $h); + $result = _add_image($filename, $x, $pagesize_h - $y, $w, $h); return p_error('this function still needs work'); diff --git a/src/PDFObject.php b/src/PDFObject.php index f4a9111..35ffd40 100644 --- a/src/PDFObject.php +++ b/src/PDFObject.php @@ -23,7 +23,6 @@ use ArrayAccess; use ddn\sapp\helpers\Buffer; -use ddn\sapp\helpers\LoadHelpers; use ddn\sapp\pdfvalue\PDFValueObject; use ddn\sapp\pdfvalue\PDFValueSimple; use ReturnTypeWillChange; @@ -33,10 +32,6 @@ // Loading the functions -if (! defined('ddn\\sapp\\helpers\\LoadHelpers')) { - new LoadHelpers(); -} - // The character used to end lines if (! defined('__EOL')) { define('__EOL', "\n"); @@ -56,7 +51,7 @@ class PDFObject implements ArrayAccess, Stringable protected $_value = null; - protected $_generation; + protected int $_generation; public function __construct( protected int $_oid, @@ -102,12 +97,12 @@ public function get_keys() return $this->_value->get_keys(); } - public function set_oid($oid): void + public function set_oid(int $oid): void { $this->_oid = $oid; } - public function get_generation() + public function get_generation(): int { return $this->_generation; } @@ -126,7 +121,7 @@ public function get_generation() public function to_pdf_entry(): string { return "{$this->_oid} 0 obj" . __EOL . - "{$this->_value}" . __EOL . + $this->_value . __EOL . ( $this->_stream === null ? '' : "stream\r\n" . @@ -178,8 +173,6 @@ public function get_stream($raw = true) ]; return self::FlateDecode(gzuncompress($this->_stream), $params); - - break; default: return p_error('unknown compression method ' . $this->_value['Filter']); } @@ -226,9 +219,9 @@ public function set_stream($stream, $raw = true): void * * @return void */ - public function offsetSet($field, $value): void + public function offsetSet($offset, $value): void { - $this->_value[$field] = $value; + $this->_value[$offset] = $value; } /** @@ -239,9 +232,9 @@ public function offsetSet($field, $value): void * * @return exists true if the field exists; false otherwise */ - public function offsetExists($field): bool + public function offsetExists($offset): bool { - return $this->_value->offsetExists($field); + return $this->_value->offsetExists($offset); } /** @@ -252,9 +245,9 @@ public function offsetExists($field): bool * @return value the value of the field */ #[ReturnTypeWillChange] - public function offsetGet($field) + public function offsetGet($offset) { - return $this->_value[$field]; + return $this->_value[$offset]; } /** @@ -262,9 +255,9 @@ public function offsetGet($field) * * @param field the field to unset the value */ - public function offsetUnset($field): void + public function offsetUnset($offset): void { - $this->_value->offsetUnset($field); + $this->_value->offsetUnset($offset); } public function push($v) diff --git a/src/PDFObjectParser.php b/src/PDFObjectParser.php index 3da44c8..3b8a711 100644 --- a/src/PDFObjectParser.php +++ b/src/PDFObjectParser.php @@ -22,6 +22,7 @@ namespace ddn\sapp; use ddn\sapp\helpers\StreamReader; +use ddn\sapp\pdfvalue\PDFValue; use ddn\sapp\pdfvalue\PDFValueHexString; use ddn\sapp\pdfvalue\PDFValueList; use ddn\sapp\pdfvalue\PDFValueObject; @@ -135,16 +136,15 @@ public function current_token() /** * Parses the document */ - public function parse(&$stream): PDFValueObject|PDFValueList|PDFValueString|PDFValueType|PDFValueSimple|false|null + public function parse(StreamReader &$stream): PDFValue|false|null { // $str, $offset = 0) { $this->start($stream); //$str, $offset); $this->nexttoken(); - $result = $this->_parse_value(); - return $result; + return $this->_parse_value(); } - public function parsestr($str, $offset = 0): PDFValueObject|PDFValueList|PDFValueString|PDFValueType|PDFValueSimple|false|null + public function parsestr($str, int $offset = 0): PDFValue|false|null { $stream = new StreamReader($str); $stream->goto($offset); @@ -162,14 +162,6 @@ public function nexttoken() return $this->_t; } - public function tokenize(): void - { - $this->start(); - while ($this->nexttoken() !== false) { - echo "{$this->_t}\n"; - } - } - /** * Obtains the next char and prepares the variable $this->_c and $this->_n to contain the current char and the next char * - if EOF, _c will be false @@ -188,7 +180,7 @@ protected function nextchar() /** * Prepares the parser to analythe the text (i.e. prepares the parsing variables) */ - protected function start(&$buffer) + protected function start(StreamReader $buffer): bool|null { $this->_buffer = $buffer; $this->_c = false; @@ -201,6 +193,8 @@ protected function start(&$buffer) } $this->_n = $this->_buffer->currentchar(); $this->nextchar(); + + return false; } /** @@ -259,7 +253,7 @@ protected function _parse_string() $this->nextchar(); if (($this->_c === ')') && (! strlen($token) || ($token[strlen($token) - 1] !== '\\'))) { $n_parenthesis--; - if ($n_parenthesis == 0) { + if ($n_parenthesis === 0) { break; } } else { @@ -393,7 +387,6 @@ protected function _parse_obj(): PDFValueObject|false $this->nexttoken(); return new PDFValueObject($object); - break; default: throw new Exception("Invalid token: {$this}"); } @@ -422,11 +415,10 @@ protected function _parse_list(): PDFValueList case self::T_STREAM_BEGIN: case self::T_STREAM_END: throw new Exception('Invalid list definition'); - break; default: $value = $this->_parse_value(); if ($value !== false) { - array_push($list, $value); + $list[] = $value; } break; } @@ -435,28 +427,24 @@ protected function _parse_list(): PDFValueList return new PDFValueList($list); } - protected function _parse_value(): PDFValueObject|false|PDFValueList|PDFValueString|PDFValueHexString|PDFValueType|null|PDFValueSimple + protected function _parse_value(): PDFValue|false|null { while ($this->_t !== false) { switch ($this->_tt) { case self::T_DICT_START: return $this->_parse_obj(); - break; case self::T_LIST_START: return $this->_parse_list(); - break; case self::T_STRING: $string = new PDFValueString($this->_t); $this->nexttoken(); return $string; - break; case self::T_HEX_STRING: $string = new PDFValueHexString($this->_t); $this->nexttoken(); return $string; - break; case self::T_FIELD: $field = new PDFValueType($this->_t); $this->nexttoken(); @@ -487,7 +475,6 @@ protected function _parse_value(): PDFValueObject|false|PDFValueList|PDFValueStr */ return new PDFValueSimple($simple_value); - break; default: throw new Exception("Invalid token: {$this}"); diff --git a/src/PDFSignatureObject.php b/src/PDFSignatureObject.php index 2c1aaf8..2d4dddc 100644 --- a/src/PDFSignatureObject.php +++ b/src/PDFSignatureObject.php @@ -23,6 +23,7 @@ use ddn\sapp\pdfvalue\PDFValueSimple; use ddn\sapp\pdfvalue\PDFValueString; +use function ddn\sapp\helpers\p_error; use function ddn\sapp\helpers\timestamp_to_pdfdatestring; // This is an special object that has a set of fields @@ -79,6 +80,8 @@ public function set_certificate($certificate): void public function set_signature_ltv($signature_ltv_data): void { + p_error(get_debug_type($signature_ltv_data)); + $this->_signature_ltv_data = $signature_ltv_data; } @@ -92,7 +95,7 @@ public function set_signature_tsa($signature_tsa): void * * @return cert the certificate */ - public function get_certificate() + public function get_certificate(): array { return $this->_certificate; } diff --git a/src/PDFUtilFnc.php b/src/PDFUtilFnc.php index ed426c5..0e0def9 100644 --- a/src/PDFUtilFnc.php +++ b/src/PDFUtilFnc.php @@ -27,7 +27,7 @@ use function ddn\sapp\helpers\p_error; use function ddn\sapp\helpers\p_warning; -if (! defined('ddn\\sapp\\helpers\\LoadHelpers')) { +if (! defined(LoadHelpers::class)) { new LoadHelpers(); } @@ -35,7 +35,7 @@ class PDFUtilFnc { - public static function get_trailer(&$_buffer, $trailer_pos) + public static function get_trailer(&$_buffer, int $trailer_pos) { // Search for the trailer structure if (preg_match('/trailer\s*(.*)\s*startxref/ms', (string) $_buffer, $matches, 0, $trailer_pos) !== 1) { @@ -69,7 +69,7 @@ public static function build_xref_1_5($offsets): array $c_k = 0; $count = 1; $result = ''; - for ($i = 0; $i < count($k); $i++) { + for ($i = 0, $iMax = count($k); $i < $iMax; $i++) { if ($c_k === 0) { $c_k = $k[$i] - 1; $i_k = $k[$i]; @@ -78,7 +78,7 @@ public static function build_xref_1_5($offsets): array if ($k[$i] === $c_k + 1) { $count++; } else { - array_push($indexes, "{$i_k} {$count}"); + $indexes[] = "{$i_k} {$count}"; $count = 1; $i_k = $k[$i]; } @@ -91,17 +91,15 @@ public static function build_xref_1_5($offsets): array } else { if ($c_offset === null) { $result .= pack('C', 0); - $result .= pack('N', $c_offset); - $result .= pack('C', 0); } else { $result .= pack('C', 1); - $result .= pack('N', $c_offset); - $result .= pack('C', 0); } + $result .= pack('N', $c_offset); + $result .= pack('C', 0); } $c_k = $k[$i]; } - array_push($indexes, "{$i_k} {$count}"); + $indexes[] = "{$i_k} {$count}"; $indexes = implode(' ', $indexes); // p_debug(show_bytes($result, 6)); @@ -117,7 +115,7 @@ public static function build_xref_1_5($offsets): array * This function obtains the xref from the cross reference streams (7.5.8 Cross-Reference Streams) * which started in PDF 1.5. */ - public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) + public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null) { if ($depth !== null) { if ($depth <= 0) { @@ -146,9 +144,9 @@ public static function get_xref_1_5(&$_buffer, $xref_pos, $depth = null) return p_error('invalid cross reference object', [false, false, false]); } - $W[0] = intval($W[0]); - $W[1] = intval($W[1]); - $W[2] = intval($W[2]); + $W[0] = (int) $W[0]; + $W[1] = (int) $W[1]; + $W[2] = (int) $W[2]; $Size = $xref_o['Size']->get_int(); if ($Size === false) { @@ -297,8 +295,8 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) // If still expecting objects, we'll assume that the xref is malformed return p_error("malformed xref at position {$xref_pos}", [false, false, false]); } - $obj_id = intval($matches[1]); - $obj_count = intval($matches[2]); + $obj_id = (int) $matches[1]; + $obj_count = (int) $matches[2]; continue; } @@ -309,8 +307,8 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) return p_error("unexpected entry for xref: {$xref_line}", [false, false, false]); } - $obj_offset = intval($matches[1]); - $obj_generation = intval($matches[2]); + $obj_offset = (int) $matches[1]; + $obj_generation = (int) $matches[2]; $obj_operation = $matches[3]; if ($obj_offset !== 0) { @@ -366,7 +364,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) return p_error("invalid trailer {$trailer_obj}", [false, false, false]); } - $xref_prev_pos = intval($xref_prev_pos); + $xref_prev_pos = (int) $xref_prev_pos; [$prev_table, $prev_trailer, $prev_min_pdf_version] = self::get_xref_1_4($_buffer, $xref_prev_pos, $depth); @@ -375,7 +373,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) } if ($prev_table !== false) { - foreach ($prev_table as $obj_id => &$obj_offset) { // Not modifying the objects, but to make sure that it does not consume additional memory + foreach ($prev_table as $obj_id => $obj_offset) { // Not modifying the objects, but to make sure that it does not consume additional memory // If there not exists a new version, we'll acquire it if (! isset($xref_table[$obj_id])) { $xref_table[$obj_id] = $obj_offset; @@ -387,7 +385,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) return [$xref_table, $trailer_obj, $min_pdf_version]; } - public static function get_xref(&$_buffer, $xref_pos, $depth = null): array + public static function get_xref(string &$_buffer, ?int $xref_pos, ?int $depth = null): array { // Each xref is immediately followed by a trailer $trailer_pos = strpos((string) $_buffer, 'trailer', $xref_pos); @@ -400,7 +398,7 @@ public static function get_xref(&$_buffer, $xref_pos, $depth = null): array return [$xref_table, $trailer_obj, $min_pdf_version]; } - public static function acquire_structure(&$_buffer, $depth = null) + public static function acquire_structure(string &$_buffer, ?int $depth = null) { // Get the first line and acquire the PDF version of the document $separator = "\r\n"; @@ -423,7 +421,7 @@ public static function acquire_structure(&$_buffer, $depth = null) exit(); */ foreach ($matches as $match) { - array_push($_versions, $match[2][1] + strlen($match[2][0])); + $_versions[] = $match[2][1] + strlen($match[2][0]); } // Now get the trailing part and make sure that it has the proper form @@ -436,7 +434,7 @@ public static function acquire_structure(&$_buffer, $depth = null) return p_error('startxref and %%EOF not found'); } - $xref_pos = intval($matches[1]); + $xref_pos = (int) $matches[1]; if ($xref_pos === 0) { // This is a dummy xref position from linearized documents @@ -482,7 +480,7 @@ public static function acquire_structure(&$_buffer, $depth = null) * * @return obj the PDFObject obtained from the file or false if could not be found */ - public static function find_object_at_pos(&$_buffer, int $oid, $object_offset, $xref_table) + public static function find_object_at_pos(&$_buffer, ?int $oid, int $object_offset, $xref_table) { $object = self::object_from_string($_buffer, $oid, $object_offset, $offset_end); @@ -516,7 +514,7 @@ public static function find_object_at_pos(&$_buffer, int $oid, $object_offset, $ return p_error("could not get object {$oid}"); } - $length = $length_object->get_value()->get_int(); + $length = $length_object->get_value()?->get_int(); } if ($length === false) { @@ -556,7 +554,6 @@ public static function find_object(&$_buffer, $xref_table, int $oid) $object = self::find_object_in_objstm($_buffer, $xref_table, $object_offset['stmoid'], $object_offset['pos'], $oid); return $object; - } /** @@ -569,7 +566,7 @@ public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm return p_error("could not get object stream {$objstm_oid}"); } - if (($objstm['Extends'] ?? false !== false)) { // TODO: support them + if ((($objstm['Extends'] ?? false) !== false)) { // TODO: support them return p_error('not supporting extended object streams at this time'); } @@ -602,14 +599,14 @@ public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm return p_error("object {$oid} not found in object stream {$objstm_oid}"); } - $offset = intval($index[$objpos + 1]); + $offset = (int) $index[$objpos + 1]; $next = 0; $offsets = []; for ($i = 1; ($i < count($index)); $i = $i + 2) { - array_push($offsets, intval($index[$i])); + $offsets[] = (int) $index[$i]; } - array_push($offsets, strlen($stream)); + $offsets[] = strlen($stream); sort($offsets); for ($i = 0; ($i < count($offsets)) && ($offset >= $offsets[$i]); $i++) { } @@ -617,15 +614,14 @@ public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm $next = $offsets[$i]; $object_def_str = "{$oid} 0 obj " . substr($stream, $offset, $next - $offset) . ' endobj'; - $object_def = self::object_from_string($object_def_str, $oid); - return $object_def; + return self::object_from_string($object_def_str, $oid); } /** * Function that parses an object */ - public static function object_from_string(&$buffer, $expected_obj_id, $offset = 0, &$offset_end = 0) + public static function object_from_string(string $buffer, ?int $expected_obj_id, int $offset = 0, ?int &$offset_end = 0) { if (preg_match('/([0-9]+)\s+([0-9+])\s+obj(\s+)/ms', (string) $buffer, $matches, 0, $offset) !== 1) { // p_debug_var(substr($buffer)) @@ -633,8 +629,8 @@ public static function object_from_string(&$buffer, $expected_obj_id, $offset = } $found_obj_header = $matches[0]; - $found_obj_id = intval($matches[1]); - $found_obj_generation = intval($matches[2]); + $found_obj_id = (int) $matches[1]; + $found_obj_generation = (int) $matches[2]; if ($expected_obj_id === null) { $expected_obj_id = $found_obj_id; @@ -691,7 +687,7 @@ public static function build_xref(array $offsets): string $count = 1; $result = ''; $references = "0000000000 65535 f \n"; - for ($i = 0; $i < count($k); $i++) { + for ($i = 0, $iMax = count($k); $i < $iMax; $i++) { if ($k[$i] === 0) { continue; } diff --git a/src/helpers/Buffer.php b/src/helpers/Buffer.php index 4b613f5..4602f06 100644 --- a/src/helpers/Buffer.php +++ b/src/helpers/Buffer.php @@ -21,6 +21,7 @@ namespace ddn\sapp\helpers; +use Exception; use Stringable; if (! defined('__CONVENIENT_MAX_BUFFER_DUMP')) { @@ -151,9 +152,7 @@ public function add(...$bs): self */ public function clone(): self { - $buffer = new self($this->_buffer); - - return $buffer; + return new self($this->_buffer); } public function show_bytes($columns, $offset = 0, $length = null): string diff --git a/src/helpers/CMS.php b/src/helpers/CMS.php index 1661d42..59861f2 100644 --- a/src/helpers/CMS.php +++ b/src/helpers/CMS.php @@ -26,11 +26,8 @@ class CMS * @return string response body * @public */ - public function sendReq($reqData) + public function sendReq(array $reqData) { - if (! function_exists('curl_init')) { - p_error(' Please enable cURL PHP extension!'); - } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $reqData['uri']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); @@ -50,7 +47,7 @@ public function sendReq($reqData) $body = substr($tsResponse, $header_size); // Get the HTTP response code $headers = explode("\n", $header); - foreach ($headers as $key => $r) { + foreach ($headers as $r) { if (stripos($r, 'HTTP/') === 0) { [, $code, $status] = explode(' ', $r, 3); break; @@ -63,10 +60,10 @@ public function sendReq($reqData) } $contentTypeHeader = ''; $headers = explode("\n", $header); - foreach ($headers as $key => $r) { + foreach ($headers as $r) { // Match the header name up to ':', compare lower case if (stripos($r, 'Content-Type' . ':') === 0) { - [$headername, $headervalue] = explode(':', $r, 2); + [, $headervalue] = explode(':', $r, 2); $contentTypeHeader = trim($headervalue); } } @@ -111,23 +108,21 @@ public function pkcs7_sign($binaryData) } p_debug("hash algorithm is \"{$hashAlgorithm}\""); $x509 = new x509(); - if (! $certParse = $x509->readcert($this->signature_data['signcert'])) { + if (! $certParse = $x509::readcert($this->signature_data['signcert'])) { p_error('certificate error! check certificate'); } - $hexEmbedCerts[] = bin2hex($x509->get_cert($this->signature_data['signcert'])); + $hexEmbedCerts[] = bin2hex($x509::get_cert($this->signature_data['signcert'])); $appendLTV = ''; $ltvData = $this->signature_data['ltv']; if (! empty($ltvData)) { p_debug(' LTV Validation start...'); - $appendLTV = ''; $LTVvalidation_ocsp = ''; $LTVvalidation_crl = ''; - $LTVvalidation_issuer = ''; $LTVvalidationEnd = false; $isRootCA = false; if ($certParse['tbsCertificate']['issuer']['hexdump'] == $certParse['tbsCertificate']['subject']['hexdump']) { // check whether root ca - if (openssl_public_decrypt(hex2bin((string) $certParse['signatureValue']), $decrypted, x509::x509_der2pem($x509->get_cert($this->signature_data['signcert'])), OPENSSL_PKCS1_PADDING)) { + if (openssl_public_decrypt(hex2bin((string) $certParse['signatureValue']), $decrypted, x509::x509_der2pem($x509::get_cert($this->signature_data['signcert'])), OPENSSL_PKCS1_PADDING)) { p_debug("***** \"{$certParse['tbsCertificate']['subject']['2.5.4.3'][0]}\" is a ROOT CA. No validation performed ***"); $isRootCA = true; } @@ -138,11 +133,11 @@ public function pkcs7_sign($binaryData) $certtoCheck = $certParse; while ($LTVvalidation !== false) { p_debug("========= {$i} checking \"{$certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0]}\"==============="); - $LTVvalidation = self::LTVvalidation($certtoCheck); + $LTVvalidation = $this->LTVvalidation($certtoCheck); $i++; if ($LTVvalidation) { $curr_issuer = $LTVvalidation['issuer']; - $certtoCheck = $x509->readcert($curr_issuer, 'oid'); + $certtoCheck = $x509::readcert($curr_issuer, 'oid'); if (@$LTVvalidation['ocsp'] || @$LTVvalidation['crl']) { $LTVvalidation_ocsp .= $LTVvalidation['ocsp']; $LTVvalidation_crl .= $LTVvalidation['crl']; @@ -150,7 +145,7 @@ public function pkcs7_sign($binaryData) } if ($certtoCheck['tbsCertificate']['issuer']['hexdump'] == $certtoCheck['tbsCertificate']['subject']['hexdump']) { // check whether root ca - if (openssl_public_decrypt(hex2bin((string) $certtoCheck['signatureValue']), $decrypted, $x509->x509_der2pem($curr_issuer), OPENSSL_PKCS1_PADDING)) { + if (openssl_public_decrypt(hex2bin((string) $certtoCheck['signatureValue']), $decrypted, $x509::x509_der2pem($curr_issuer), OPENSSL_PKCS1_PADDING)) { p_debug("========= FINISH Reached ROOT CA \"{$certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0]}\"==============="); $LTVvalidationEnd = true; break; @@ -193,8 +188,8 @@ public function pkcs7_sign($binaryData) } } foreach ($this->signature_data['extracerts'] ?? [] as $extracert) { - $hex_extracert = bin2hex($x509->x509_pem2der($extracert)); - if (! in_array($hex_extracert, $hexEmbedCerts)) { + $hex_extracert = bin2hex($x509::x509_pem2der($extracert)); + if (! in_array($hex_extracert, $hexEmbedCerts, true)) { $hexEmbedCerts[] = $hex_extracert; } } @@ -227,11 +222,11 @@ public function pkcs7_sign($binaryData) return false; } - $hexencryptedDigest = bin2hex((string) $encryptedDigest); + $hexencryptedDigest = bin2hex($encryptedDigest); $timeStamp = ''; if (! empty($this->signature_data['tsa'])) { p_debug(' Timestamping process start...'); - if ($TSTInfo = self::createTimestamp($encryptedDigest, $hashAlgorithm)) { + if ($TSTInfo = $this->createTimestamp($encryptedDigest, $hashAlgorithm)) { p_debug(' Timestamping SUCCESS.'); $TimeStampToken = asn1::seq( '060B2A864886F70D010910020E' . // OBJ_id_smime_aa_timeStampToken 1.2.840.113549.1.9.16.2.14 @@ -264,12 +259,11 @@ public function pkcs7_sign($binaryData) $certs . asn1::set($signerinfos) ); - $pkcs7ContentInfo = asn1::seq( + + return asn1::seq( '06092A864886F70D010702' . // Hexadecimal form of pkcs7-signedData asn1::expl(0, $pkcs7contentSignedData) ); - - return $pkcs7ContentInfo; } /** @@ -280,7 +274,7 @@ public function pkcs7_sign($binaryData) * * @return string hex TSTinfo. */ - protected function createTimestamp($data, $hashAlg = 'sha1') + protected function createTimestamp($data, string $hashAlg = 'sha1') { $TSTInfo = false; $tsaQuery = x509::tsa_query($data, $hashAlg); @@ -293,7 +287,7 @@ protected function createTimestamp($data, $hashAlg = 'sha1') ] + $tsaData; p_debug(' sending TSA query to "' . $tsaData['host'] . '"...'); - if (! $binaryTsaResp = self::sendReq($reqData)) { + if (! $binaryTsaResp = $this->sendReq($reqData)) { p_error(' TSA query send FAILED!'); } else { p_debug(' TSA query send OK'); @@ -402,7 +396,6 @@ protected function LTVvalidation($parsedCert): false|array $ocspReq_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; $ocspReq_issuerNameHash = $certIssuer_parse['tbsCertificate']['subject']['sha1']; $ocspReq_issuerKeyHash = $certIssuer_parse['tbsCertificate']['subjectPublicKeyInfo']['sha1']; - $ocspRequestorSubjName = $certSigner_parse['tbsCertificate']['subject']['hexdump']; p_debug(' OCSP create request...'); if ($ocspReq = x509::ocsp_request($ocspReq_serialNumber, $ocspReq_issuerNameHash, $ocspReq_issuerKeyHash)) { p_debug(' OK.'); @@ -414,14 +407,14 @@ protected function LTVvalidation($parsedCert): false|array 'resp_contentType' => 'application/ocsp-response', ]; p_debug(" OCSP send request to \"{$ocspURI}\"..."); - if ($ocspResp = self::sendReq($reqData)) { + if ($ocspResp = $this->sendReq($reqData)) { p_debug(' OK.'); p_debug(' OCSP parsing response...'); if ($ocsp_parse = x509::ocsp_response_parse($ocspResp, $return)) { p_debug(' OK.'); p_debug(' OCSP check cert validity...'); $certStatus = $ocsp_parse['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses'][0]['certStatus']; - if ($certStatus == 'valid') { + if ($certStatus === 'valid') { p_debug(' OK. VALID.'); $ocspRespHex = $ocsp_parse['hexdump']; $ltvResult['ocsp'] = $ocspRespHex; @@ -464,7 +457,7 @@ protected function LTVvalidation($parsedCert): false|array } elseif (($nextUpdateTime - $nowz) < 1) { // not accept if crl 1 sec remain to expired p_error(' FAILED! Expired crl at ' . date('d/m/Y H:i:s', $nextUpdateTime) . ' and now ' . date('d/m/Y H:i:s', $nowz) . '!'); } else { - p_debug(' OK CRL still valid until ' . date('d/m/Y H:i:s', $nextUpdateTime) . ''); + p_debug(' OK CRL still valid until ' . date('d/m/Y H:i:s', $nextUpdateTime)); $crlCertValid = true; p_debug(' check if cert not revoked...'); if (array_key_exists('revokedCertificates', $crl_parse['TBSCertList'])) { @@ -546,7 +539,7 @@ private function tsa_parseResp($binaryTsaRespData) $curr['contentType'] = $curr[$key]; unset($curr[$key]); } - if ($value['type'] == 'a0') { + if ($value['type'] === 'a0') { $curr['content'] = $curr[$key]; unset($curr[$key]); } diff --git a/src/helpers/DependencyTreeObject.php b/src/helpers/DependencyTreeObject.php index d2a05fd..e0ea1ab 100644 --- a/src/helpers/DependencyTreeObject.php +++ b/src/helpers/DependencyTreeObject.php @@ -21,6 +21,8 @@ namespace ddn\sapp\helpers; +use ddn\sapp\PDFObject; +use ddn\sapp\pdfvalue\PDFValueObject; use Generator; use Stringable; @@ -29,31 +31,30 @@ */ class DependencyTreeObject implements Stringable { - private int $is_child; + public int $is_child; + + private array $children = []; public function __construct( private int $oid, - private mixed $info = null, + public mixed $info = null, ) { $this->is_child = 0; } public function __toString(): string { - return (string) $this->_getstr(null, isset($this->children) ? count($this->children) : 0); + return $this->_getstr(null, isset($this->children) ? count($this->children) : 0); } /** * Function that links one object to its parent (i.e. adds the object to the list of children of this object) * - the function increases the amount of times that one object has been added to a parent object, to detect problems in building the tree */ - public function addchild($oid, $o): void + public function addchild(int $oid, self $o): void { - if (! isset($this->children)) { - $this->children = []; - } $this->children[$oid] = $o; - if ($o->is_child != 0) { + if ($o->is_child !== 0) { p_warning("object {$o->oid} is already a child of other object"); } @@ -75,46 +76,33 @@ public function children(): Generator /** * Gets a string that represents the object, prepending a number of spaces, proportional to the depth in the tree */ - protected function _getstr(?string $spaces = '', $mychcount = 0): string + protected function _getstr(?string $spaces = '', int $mychcount = 0): string { // $info = $this->oid . ($this->info?" ($this->info)":"") . (($this->is_child > 1)?" $this->is_child":""); - $info = $this->oid . ($this->info ? " ({$this->info})" : ''); + $info = $this->oid . ($this->info !== null ? " ({$this->info})" : ''); if ($spaces === null) { - $lines = ["{$spaces} " . json_decode('"\u2501"') . " {$info}"]; + $lines = ["{$spaces} " . json_decode('"\u2501"', false, 512, JSON_THROW_ON_ERROR) . " {$info}"]; } else { - if ($mychcount == 0) { - $lines = ["{$spaces} " . json_decode('"\u2514\u2500"') . " {$info}"]; + if ($mychcount === 0) { + $lines = ["{$spaces} " . json_decode('"\u2514\u2500"', false, 512, JSON_THROW_ON_ERROR) . " {$info}"]; } else { - $lines = ["{$spaces} " . json_decode('"\u251c\u2500"') . " {$info}"]; + $lines = ["{$spaces} " . json_decode('"\u251c\u2500"', false, 512, JSON_THROW_ON_ERROR) . " {$info}"]; } } if (isset($this->children)) { $chcount = count($this->children); - foreach ($this->children as $oid => $child) { + foreach ($this->children as $child) { $chcount--; - if (($spaces === null) || ($mychcount == 0)) { - array_push($lines, $child->_getstr($spaces . ' ', $chcount)); + if (($spaces === null) || ($mychcount === 0)) { + $lines[] = $child->_getstr($spaces . ' ', $chcount); } else { - array_push($lines, $child->_getstr($spaces . ' ' . json_decode('"\u2502"'), $chcount)); + $lines[] = $child->_getstr($spaces . ' ' . json_decode('"\u2502"', false, 512, JSON_THROW_ON_ERROR), $chcount); } } } return implode("\n", $lines); } - - protected function _old_getstr($depth = 0): string - { - $spaces = str_repeat(' ' . json_decode('"\u2502"'), $depth); - $lines = ["{$spaces} " . json_decode('"\u251c\u2500"') . ' ' . $this->oid . ($this->info ? " ({$this->info})" : '') . (($this->is_child > 1) ? " {$this->is_child}" : '')]; - if (isset($this->children)) { - foreach ($this->children as $oid => $child) { - array_push($lines, $child->_getstr($depth + 1)); - } - } - - return implode("\n", $lines); - } } /** @@ -130,9 +118,9 @@ protected function _old_getstr($depth = 0): string ]; /** - * @return mixed[] + * @return array */ -function references_in_object(array $object, $oid = false): array +function references_in_object(PDFObject $object): array { $type = $object['Type']; if ($type !== false) { @@ -144,21 +132,18 @@ function references_in_object(array $object, $oid = false): array $references = []; foreach ($object->get_keys() as $key) { - $valid = true; - // We'll skip those blacklisted fields - if (in_array($key, BLACKLIST['*'])) { + if (in_array($key, BLACKLIST['*'], true)) { continue; } if (array_key_exists($type, BLACKLIST)) { - if (in_array($key, BLACKLIST[$type])) { + if (in_array($key, BLACKLIST[$type], true)) { continue; } } - $r_objects = []; - if (is_a($object[$key], 'ddn\\sapp\\pdfvalue\\PDFValueObject')) { + if (is_a($object[$key], PDFValueObject::class)) { $r_objects = references_in_object($object[$key]); } else { // Function get_object_referenced checks whether the value (or values in a list) have the form of object references, and if they have the form diff --git a/src/helpers/StreamReader.php b/src/helpers/StreamReader.php index 18fa76b..3c6feb1 100644 --- a/src/helpers/StreamReader.php +++ b/src/helpers/StreamReader.php @@ -32,13 +32,13 @@ */ class StreamReader { - protected $_buffer = ''; + protected ?string $_buffer; protected int $_bufferlen; protected $_pos = 0; - public function __construct($string = null, $offset = 0) + public function __construct(?string $string = null, int $offset = 0) { if ($string === null) { $string = ''; @@ -54,7 +54,7 @@ public function __construct($string = null, $offset = 0) * * @return char the next char in the buffer */ - public function nextchar() + public function nextchar(): string|false { $this->_pos = min($this->_pos + 1, $this->_bufferlen); @@ -82,7 +82,7 @@ public function nextchars($n): string * * @return char the current char */ - public function currentchar() + public function currentchar(): string|false { if ($this->_pos >= $this->_bufferlen) { return false; @@ -123,8 +123,8 @@ public function substratpos($length = 0): string if ($length > 0) { return substr((string) $this->_buffer, $this->_pos, $length); } - return substr((string) $this->_buffer, $this->_pos); + return substr((string) $this->_buffer, $this->_pos); } /** diff --git a/src/helpers/UUID.php b/src/helpers/UUID.php index b6681a3..1c8de34 100644 --- a/src/helpers/UUID.php +++ b/src/helpers/UUID.php @@ -17,7 +17,7 @@ public static function v3($namespace, string $name): false|string $nstr = ''; // Convert Namespace UUID to bits - for ($i = 0; $i < strlen($nhex); $i += 2) { + for ($i = 0, $iMax = strlen($nhex); $i < $iMax; $i += 2) { $nstr .= chr(hexdec($nhex[$i] . $nhex[$i + 1])); } @@ -53,25 +53,25 @@ public static function v4(): string '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', // 32 bits for "time_low" - mt_rand(0, 0xffff), - mt_rand(0, 0xffff), + random_int(0, 0xffff), + random_int(0, 0xffff), // 16 bits for "time_mid" - mt_rand(0, 0xffff), + random_int(0, 0xffff), // 16 bits for "time_hi_and_version", // four most significant bits holds version number 4 - mt_rand(0, 0x0fff) | 0x4000, + random_int(0, 0x0fff) | 0x4000, // 16 bits, 8 bits for "clk_seq_hi_res", // 8 bits for "clk_seq_low", // two most significant bits holds zero and one for variant DCE1.1 - mt_rand(0, 0x3fff) | 0x8000, + random_int(0, 0x3fff) | 0x8000, // 48 bits for "node" - mt_rand(0, 0xffff), - mt_rand(0, 0xffff), - mt_rand(0, 0xffff) + random_int(0, 0xffff), + random_int(0, 0xffff), + random_int(0, 0xffff) ); } @@ -88,7 +88,7 @@ public static function v5($namespace, string $name): false|string $nstr = ''; // Convert Namespace UUID to bits - for ($i = 0; $i < strlen($nhex); $i += 2) { + for ($i = 0, $iMax = strlen($nhex); $i < $iMax; $i += 2) { $nstr .= chr(hexdec($nhex[$i] . $nhex[$i + 1])); } diff --git a/src/helpers/asn1.php b/src/helpers/asn1.php index d83f58e..efeeacf 100644 --- a/src/helpers/asn1.php +++ b/src/helpers/asn1.php @@ -27,32 +27,31 @@ public static function __callStatic($func, $params) if (in_array($func, ['printable', 'utf8', 'ia5', 'visible', 't61'])) { // ($string) $val = bin2hex((string) $hex); } - if ($func == 'int') { - $val = (strlen((string) $val) % 2 != 0) ? "0{$val}" : "{$val}"; + if ($func === 'int') { + $val = (strlen((string) $val) % 2 !== 0) ? "0{$val}" : (string) ($val); } - if ($func == 'expl') { //expl($num, $hex) + if ($func === 'expl') { //expl($num, $hex) $num = $num . $params[0]; $val = $params[1]; } - if ($func == 'impl') { //impl($num="0") + if ($func === 'impl') { //impl($num="0") $val = (! $val) ? '00' : $val; - $val = (strlen((string) $val) % 2 != 0) ? "0{$val}" : $val; + $val = (strlen((string) $val) % 2 !== 0) ? "0{$val}" : $val; return $num . $val; } - if ($func == 'other') { //OTHER($id, $hex, $chr = false) + if ($func === 'other') { //OTHER($id, $hex, $chr = false) $id = $params[0]; $hex = $params[1]; $chr = @$params[2]; $str = $hex; - if ($chr != false) { + if ($chr) { $str = bin2hex((string) $hex); } - $ret = "{$id}" . self::asn1_header($str) . $str; - return $ret; + return ($id) . self::asn1_header($str) . $str; } - if ($func == 'utime') { + if ($func === 'utime') { $time = $params[0]; //yymmddhhiiss $oldTz = date_default_timezone_get(); date_default_timezone_set('UTC'); @@ -60,7 +59,7 @@ public static function __callStatic($func, $params) date_default_timezone_set($oldTz); $val = bin2hex($time . 'Z'); } - if ($func == 'gtime') { + if ($func === 'gtime') { if (! $time = strtotime((string) $params[0])) { // echo "asn1::GTIME function strtotime cant recognize time!! please check at input=\"{$params[0]}\""; return false; @@ -103,12 +102,11 @@ public static function parse($hex, $maxDepth = 5): array $info['typeName'] = self::type($k); $info['value_hex'] = $v; if (($currentDepth <= $maxDepth)) { - if ($k == '06') { - } else { - if (in_array($k, ['13', '18'])) { + if ($k !== '06') { + if (in_array($k, ['13', '18'], true)) { $info['value'] = hex2bin((string) $info['value_hex']); } else { - if (in_array($k, ['03', '02', 'a04'])) { + if (in_array($k, ['03', '02', 'a04'], true)) { $info['value'] = $v; } else { $currentDepth++; @@ -179,12 +177,12 @@ protected static function type($id) * * @return array asn.1 structure */ - protected static function oneParse($hex) + protected static function oneParse($hex): array|false { - if ($hex == '') { + if ($hex === '') { return false; } - if (! @ctype_xdigit($hex) || @strlen($hex) % 2 != 0) { + if (! @ctype_xdigit($hex) || @strlen($hex) % 2 !== 0) { echo "input:\"{$hex}\" not hex string!.\n"; return false; @@ -205,12 +203,11 @@ protected static function oneParse($hex) } $tlv_valueLength = hexdec($tlv_valueLength); $totalTlLength = 2 + 2 + ($tlv_lengthLength * 2); - $reduction = 2 + 2 + ($tlv_lengthLength * 2) + ($tlv_valueLength * 2); $tlv_value = substr($hex, $totalTlLength, $tlv_valueLength * 2); $remain = substr($hex, $totalTlLength + ($tlv_valueLength * 2)); $newhexdump = substr($hex, 0, $totalTlLength + ($tlv_valueLength * 2)); $result[] = [ - 'tlv_tagLength' => strlen(dechex($tlv_tagLength)) % 2 == 0 ? dechex($tlv_tagLength) : '0' . dechex($tlv_tagLength), + 'tlv_tagLength' => strlen(dechex($tlv_tagLength)) % 2 === 0 ? dechex($tlv_tagLength) : '0' . dechex($tlv_tagLength), 'tlv_lengthLength' => $tlv_lengthLength, 'tlv_valueLength' => $tlv_valueLength, 'newhexdump' => $newhexdump, @@ -241,7 +238,7 @@ protected static function asn1_header($str): string { $len = strlen($str) / 2; $ret = dechex($len); - if (strlen($ret) % 2 != 0) { + if (strlen($ret) % 2 !== 0) { $ret = "0{$ret}"; } $headerLength = strlen($ret) / 2; diff --git a/src/helpers/contentgeneration.php b/src/helpers/contentgeneration.php index 7dc8cf8..1257f6a 100644 --- a/src/helpers/contentgeneration.php +++ b/src/helpers/contentgeneration.php @@ -86,7 +86,7 @@ function _create_image_objects($info, $object_factory): array (strlen((string) $info['pal']) / 3) - 1, new PDFValueReference($streamobject->get_oid()), ]); - array_push($objects, $streamobject); + $objects[] = $streamobject; break; case 'DeviceCMYK': $image['Decode'] = new PDFValueList([1, 0, 1, 0, 1, 0, 1, 0]); @@ -122,7 +122,7 @@ function _create_image_objects($info, $object_factory): array // In principle, it may return multiple objects $smasks = _create_image_objects($smaskinfo, $object_factory); foreach ($smasks as $smask) { - array_push($objects, $smask); + $objects[] = $smask; } $image['SMask'] = new PDFValueReference($smask->get_oid()); } @@ -240,7 +240,6 @@ function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, $images_objects = _create_image_objects($info, $object_factory); // Generate the command to translate and scale the image - $data = 'q '; if ($keep_proportions) { $angleRads = deg2rad($angle); diff --git a/src/helpers/fpdfhelpers.php b/src/helpers/fpdfhelpers.php index 38aeb5b..91dfd53 100644 --- a/src/helpers/fpdfhelpers.php +++ b/src/helpers/fpdfhelpers.php @@ -69,9 +69,8 @@ function _parsepng($filecontent) { // Extract info from a PNG file $f = new StreamReader($filecontent); - $info = _parsepngstream($f); - return $info; + return _parsepngstream($f); } function _parsepngstream(&$f) @@ -83,7 +82,7 @@ function _parsepngstream(&$f) // Read header chunk _readstream($f, 4); - if (_readstream($f, 4) != 'IHDR') { + if (_readstream($f, 4) !== 'IHDR') { return p_error('Incorrect PNG image'); } $w = _readint($f); @@ -112,7 +111,7 @@ function _parsepngstream(&$f) return p_error('Interlacing not supported'); } _readstream($f, 4); - $dp = '/Predictor 15 /Colors ' . ($colspace == 'DeviceRGB' ? 3 : 1) . ' /BitsPerComponent ' . $bpc . ' /Columns ' . $w; + $dp = '/Predictor 15 /Colors ' . ($colspace === 'DeviceRGB' ? 3 : 1) . ' /BitsPerComponent ' . $bpc . ' /Columns ' . $w; // Scan chunks looking for palette, transparency and image data $pal = ''; @@ -121,11 +120,11 @@ function _parsepngstream(&$f) do { $n = _readint($f); $type = _readstream($f, 4); - if ($type == 'PLTE') { + if ($type === 'PLTE') { // Read palette $pal = _readstream($f, $n); _readstream($f, 4); - } elseif ($type == 'tRNS') { + } elseif ($type === 'tRNS') { // Read transparency info $t = _readstream($f, $n); if ($ct == 0) { @@ -139,18 +138,18 @@ function _parsepngstream(&$f) } } _readstream($f, 4); - } elseif ($type == 'IDAT') { + } elseif ($type === 'IDAT') { // Read image data block $data .= _readstream($f, $n); _readstream($f, 4); - } elseif ($type == 'IEND') { + } elseif ($type === 'IEND') { break; } else { _readstream($f, $n + 4); } } while ($n); - if ($colspace == 'Indexed' && empty($pal)) { + if ($colspace === 'Indexed' && empty($pal)) { return p_error('Missing palette in image'); } $info = [ @@ -211,7 +210,7 @@ function _parsepngstream(&$f) return $info; } -function _readstream(&$f, $n) +function _readstream($f, $n) { $res = ''; diff --git a/src/helpers/helpers.php b/src/helpers/helpers.php index 1bf1f8f..292f690 100644 --- a/src/helpers/helpers.php +++ b/src/helpers/helpers.php @@ -22,6 +22,7 @@ namespace ddn\sapp\helpers; use DateTime; +use DateTimeInterface; if (! defined('_DEBUG_LEVEL')) { define('_DEBUG_LEVEL', 3); @@ -42,9 +43,8 @@ function var_dump_to_string($var): string|false { ob_start(); var_dump($var); - $result = ob_get_clean(); - return $result; + return ob_get_clean(); } /** @@ -54,16 +54,16 @@ function var_dump_to_string($var): string|false * * @return str the var_dump output of the variables */ -function debug_var(...$vars) +function debug_var(...$vars): ?string { // If the debug level is less than 3, suppress debug messages if (_DEBUG_LEVEL < 3) { - return; + return null; } $result = []; foreach ($vars as $var) { - array_push($result, var_dump_to_string($var)); + $result[] = var_dump_to_string($var); } return implode("\n", $result); @@ -103,7 +103,7 @@ function varval($e) $a = []; foreach ($e as $k => $v) { $v = varval($v); - array_push($a, "{$k} => {$v}"); + $a[] = "{$k} => {$v}"; } $retval = '[ ' . implode(', ', $a) . ' ]'; } @@ -119,7 +119,7 @@ function varval($e) * @param level the depth level to output (0 will refer to the function that called p_stderr * call itself, 1 to the function that called to the function that called p_stderr) */ -function p_stderr(&$e, $tag = 'Error', $level = 1): void +function p_stderr(string &$e, string $tag = 'Error', int $level = 1): void { $dinfo = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT); $dinfo = $dinfo[$level]; @@ -133,9 +133,8 @@ function p_stderr(&$e, $tag = 'Error', $level = 1): void * @param e the debug message * @param retval the value to return (default: false) * - * @return retval */ -function p_debug($e, $retval = false) +function p_debug(string $e, mixed $retval = false) { // If the debug level is less than 3, suppress debug messages if (_DEBUG_LEVEL >= 3) { @@ -151,9 +150,8 @@ function p_debug($e, $retval = false) * @param e the debug message * @param retval the value to return (default: false) * - * @return retval */ -function p_warning($e, $retval = false) +function p_warning(string $e, mixed $retval = false) { // If the debug level is less than 2, suppress warning messages if (_DEBUG_LEVEL >= 2) { @@ -169,9 +167,8 @@ function p_warning($e, $retval = false) * @param e the error message * @param retval the value to return (default: false) * - * @return retval */ -function p_error($e, $retval = false) +function p_error(string $e, mixed $retval = false) { // If the debug level is less than 1, suppress error messages if (_DEBUG_LEVEL >= 1) { @@ -218,16 +215,16 @@ function get_memory_limit(): int { $memory_limit = ini_get('memory_limit'); if (preg_match('/^(\d+)(.)$/', $memory_limit, $matches) === 1) { - $memory_limit = intval($matches[1]); + $memory_limit = (int) $matches[1]; switch ($matches[2]) { case 'G': - $memory_limit = $memory_limit * 1024; + $memory_limit *= 1024; // no break case 'M': - $memory_limit = $memory_limit * 1024; + $memory_limit *= 1024; // no break case 'K': - $memory_limit = $memory_limit * 1024; + $memory_limit *= 1024; break; default: $memory_limit = 0; @@ -246,7 +243,7 @@ function show_bytes($str, $columns = null): string $columns = strlen((string) $str); } $c = $columns; - for ($i = 0; $i < strlen((string) $str); $i++) { + for ($i = 0, $iMax = strlen((string) $str); $i < $iMax; $i++) { $result .= sprintf('%02x ', ord($str[$i])); $c--; if ($c === 0) { @@ -265,7 +262,7 @@ function show_bytes($str, $columns = null): string * * @return date_string the date string in PDF format */ -function timestamp_to_pdfdatestring($date = null): string +function timestamp_to_pdfdatestring(?DateTimeInterface $date = null): string { if ($date === null) { $date = new DateTime(); @@ -284,7 +281,7 @@ function timestamp_to_pdfdatestring($date = null): string * @return string escaped date string. * @since 5.9.152 (2012-03-23) */ -function get_pdf_formatted_date($time) +function get_pdf_formatted_date(int $time) { - return substr_replace(date('YmdHisO', intval($time)), '\'', (0 - 2), 0) . '\''; + return substr_replace(date('YmdHisO', $time), '\'', (0 - 2), 0) . '\''; } diff --git a/src/helpers/x509.php b/src/helpers/x509.php index 80db0ee..6d8ad9f 100644 --- a/src/helpers/x509.php +++ b/src/helpers/x509.php @@ -71,11 +71,11 @@ public static function ocsp_response_parse($binaryOcspResp, &$status = '') } foreach ($ocsp as $key => $value) { if (is_numeric($key)) { - if ($value['type'] == '0a') { + if ($value['type'] === '0a') { $ocsp['responseStatus'] = $value['value_hex']; unset($ocsp[$key]); } - if ($value['type'] == 'a0') { + if ($value['type'] === 'a0') { $ocsp['responseBytes'] = $value; unset($ocsp[$key]); } @@ -152,7 +152,7 @@ public static function ocsp_response_parse($binaryOcspResp, &$status = '') $curr['signature'] = substr((string) $value['value_hex'], 2); unset($curr[$key]); } - if ($value['type'] == 'a0') { + if ($value['type'] === 'a0') { foreach ($value[0] as $certsK => $certsV) { if (is_numeric($certsK)) { $certs[$certsK] = $certsV['value_hex']; @@ -171,15 +171,15 @@ public static function ocsp_response_parse($binaryOcspResp, &$status = '') $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']; foreach ($curr as $key => $value) { if (is_numeric($key)) { - if ($value['type'] == 'a0') { + if ($value['type'] === 'a0') { $curr['version'] = $value[0]['value']; unset($curr[$key]); } - if ($value['type'] == 'a1' && ! array_key_exists('responderID', $curr)) { + if ($value['type'] === 'a1' && ! array_key_exists('responderID', $curr)) { $curr['responderID'] = $value; unset($curr[$key]); } - if ($value['type'] == 'a2') { + if ($value['type'] === 'a2') { $curr['responderID'] = $value; unset($curr[$key]); } @@ -191,7 +191,7 @@ public static function ocsp_response_parse($binaryOcspResp, &$status = '') $curr['responses'] = $value; unset($curr[$key]); } - if ($value['type'] == 'a1') { + if ($value['type'] === 'a1') { $curr['responseExtensions'] = $value; unset($curr[$key]); } @@ -220,7 +220,7 @@ public static function ocsp_response_parse($binaryOcspResp, &$status = '') foreach ($curr as $key => $value) { if (is_numeric($key)) { if ($value['type'] == '30') { - if ($value[0]['value_hex'] == '2b0601050507300102') { // nonce + if ($value[0]['value_hex'] === '2b0601050507300102') { // nonce $curr['nonce'] = $value[0]['value_hex']; } else { $curr[$value[0]['value_hex']] = $value[1]; @@ -362,14 +362,13 @@ public static function ocsp_request($serialNumber, $issuerNameHash, $issuerKeyHa '06092A864886F70D010105' . // OBJ_sha1WithRSAEncryption. '0500' ); - $signature = asn1::bit('00' . bin2hex((string) $signature_value)); + $signature = asn1::bit('00' . bin2hex($signature_value)); $signer_cert = self::x509_pem2der($signer_cert); $certs = asn1::expl('0', asn1::seq(bin2hex($signer_cert))); $optionalSignature = asn1::expl('0', asn1::seq($signatureAlgorithm . $signature . $certs)); } - $OCSPRequest = asn1::seq($TBSRequest . $optionalSignature); - return $OCSPRequest; + return asn1::seq($TBSRequest . $optionalSignature); } /** @@ -393,11 +392,9 @@ public static function crl_pem2der($crl): false|string return false; } $crl = substr($crl, 0, $endPos); - $crl = str_replace("\n", '', $crl); - $crl = str_replace("\r", '', $crl); - $dercrl = base64_decode($crl, true); + $crl = str_replace(["\n", "\r"], '', $crl); - return $dercrl; + return base64_decode($crl, true); } /** @@ -433,7 +430,7 @@ public static function x509_pem2der($pem): string|false $x509_der = false; if ($x509_res = @openssl_x509_read($pem)) { openssl_x509_export($x509_res, $x509_pem); - $arr_x509_pem = explode("\n", (string) $x509_pem); + $arr_x509_pem = explode("\n", $x509_pem); $numarr = count($arr_x509_pem); $i = 0; $cert_pem = false; @@ -536,7 +533,7 @@ public static function readcert($cert_in, $oidprint = false) $i = 0; foreach ($curr as $key => $value) { if (is_numeric($key)) { - if ($value['type'] == 'a0') { + if ($value['type'] === 'a0') { $curr['version'] = $value[0]['value']; unset($curr[$key]); } @@ -553,9 +550,9 @@ public static function readcert($cert_in, $oidprint = false) foreach ($value as $issuerK => $issuerV) { if (is_numeric($issuerK)) { $issuerOID = $issuerV[0][0]['value_hex']; - if ($oidprint == 'oid') { + if ($oidprint === 'oid') { $issuerOID = self::oidfromhex($issuerOID); - } elseif ($oidprint == 'hex') { + } elseif ($oidprint === 'hex') { } else { $issuerOID = self::oidfromhex($issuerOID); } @@ -581,9 +578,9 @@ public static function readcert($cert_in, $oidprint = false) foreach ($value as $subjectK => $subjectV) { if (is_numeric($subjectK)) { $subjectOID = $subjectV[0][0]['value_hex']; - if ($oidprint == 'oid') { + if ($oidprint === 'oid') { $subjectOID = self::oidfromhex($subjectOID); - } elseif ($oidprint == 'hex') { + } elseif ($oidprint === 'hex') { } else { $subjectOID = self::oidfromhex($subjectOID); } @@ -619,7 +616,7 @@ public static function readcert($cert_in, $oidprint = false) unset($curr[$key]); continue; } - if ($value['type'] == 'a3') { + if ($value['type'] === 'a3') { $curr['attributes'] = $value[0]; unset($curr[$key]); } @@ -638,20 +635,20 @@ public static function readcert($cert_in, $oidprint = false) $extvalue = $value[1]; $name_hex = $value[0]['value_hex']; $value_hex = $value[1]['hexdump']; - if ($value[1]['type'] == '01' && $value[1]['value_hex'] == 'ff') { + if ($value[1]['type'] == '01' && $value[1]['value_hex'] === 'ff') { $critical = 1; $extvalue = $value[2]; } - if ($name_hex == '551d0e') { // OBJ_subject_key_identifier + if ($name_hex === '551d0e') { // OBJ_subject_key_identifier $extvalue = $value[1][0]['value_hex']; } - if ($name_hex == '551d23') { // OBJ_authority_key_identifier + if ($name_hex === '551d23') { // OBJ_authority_key_identifier foreach ($value[1][0] as $OBJ_authority_key_identifierKey => $OBJ_authority_key_identifierVal) { if (is_numeric($OBJ_authority_key_identifierKey)) { if ($OBJ_authority_key_identifierVal['type'] == '80') { $OBJ_authority_key_identifier['keyid'] = $OBJ_authority_key_identifierVal['value_hex']; } - if ($OBJ_authority_key_identifierVal['type'] == 'a1') { + if ($OBJ_authority_key_identifierVal['type'] === 'a1') { $OBJ_authority_key_identifier['issuerName'] = $OBJ_authority_key_identifierVal['value_hex']; } if ($OBJ_authority_key_identifierVal['type'] == '82') { @@ -661,7 +658,7 @@ public static function readcert($cert_in, $oidprint = false) } $extvalue = $OBJ_authority_key_identifier; } - if ($name_hex == '2b06010505070101') { // OBJ_info_access + if ($name_hex === '2b06010505070101') { // OBJ_info_access foreach ($value[1][0] as $OBJ_info_accessK => $OBJ_info_accessV) { if (is_numeric($OBJ_info_accessK)) { $OBJ_info_accessHEX = $OBJ_info_accessV[0]['value_hex']; @@ -672,7 +669,7 @@ public static function readcert($cert_in, $oidprint = false) } $extvalue = $OBJ_info_access; } - if ($name_hex == '551d1f') { // OBJ_crl_distribution_points 551d1f + if ($name_hex === '551d1f') { // OBJ_crl_distribution_points 551d1f foreach ($value[1][0] as $OBJ_crl_distribution_pointsK => $OBJ_crl_distribution_pointsV) { if (is_numeric($OBJ_crl_distribution_pointsK)) { $OBJ_crl_distribution_points[] = hex2bin((string) $OBJ_crl_distribution_pointsV[0][0][0]['value_hex']); @@ -680,16 +677,16 @@ public static function readcert($cert_in, $oidprint = false) } $extvalue = $OBJ_crl_distribution_points; } - if ($name_hex == '551d0f') { // OBJ_key_usage + if ($name_hex === '551d0f') { // OBJ_key_usage // $extvalue = self::parse_keyUsage($extvalue[0]['value']); } - if ($name_hex == '551d13') { // OBJ_basic_constraints + if ($name_hex === '551d13') { // OBJ_basic_constraints $bc['ca'] = '0'; $bc['pathLength'] = ''; foreach ($extvalue[0] as $bck => $bcv) { if (is_numeric($bck)) { if ($bcv['type'] == '01') { - if ($bcv['value_hex'] == 'ff') { + if ($bcv['value_hex'] === 'ff') { $bc['ca'] = '1'; } } @@ -700,7 +697,7 @@ public static function readcert($cert_in, $oidprint = false) } $extvalue = $bc; } - if ($name_hex == '551d25') { // OBJ_ext_key_usage 551d1f + if ($name_hex === '551d25') { // OBJ_ext_key_usage 551d1f foreach ($extvalue[0] as $OBJ_ext_key_usageK => $OBJ_ext_key_usageV) { if (is_numeric($OBJ_ext_key_usageK)) { $OBJ_ext_key_usageHEX = $OBJ_ext_key_usageV['value_hex']; @@ -719,9 +716,9 @@ public static function readcert($cert_in, $oidprint = false) 'value' => $extvalue, ]; $extNameOID = $value[0]['value_hex']; - if ($oidprint == 'oid') { + if ($oidprint === 'oid') { $extNameOID = self::oidfromhex($extNameOID); - } elseif ($oidprint == 'hex') { + } elseif ($oidprint === 'hex') { } else { $extNameOID = self::oidfromhex($extNameOID); } @@ -786,7 +783,7 @@ private static function opensslSubjHash($hex_subjSequence): array * * @return array parsed crl */ - private static function parsecrl(array $crl, $oidprint = false) + private static function parsecrl(array $crl, bool $oidprint = false): false|array { if ($derCrl = self::crl_pem2der($crl)) { $derCrl = bin2hex($derCrl); @@ -856,7 +853,7 @@ private static function parsecrl(array $crl, $oidprint = false) unset($curr[$key]); continue; } - if ($value['type'] == 'a0') { + if ($value['type'] === 'a0') { $curr['crlExtensions'] = $curr[$key]; unset($curr[$key]); } @@ -890,25 +887,25 @@ private static function parsecrl(array $crl, $oidprint = false) foreach ($curr as $key => $value) { if (is_numeric($key)) { $attributes_name = self::oidfromhex($value[0]['value_hex']); - if ($oidprint == 'oid') { + if ($oidprint === 'oid') { $attributes_name = self::oidfromhex($value[0]['value_hex']); } - if ($oidprint == 'hex') { + if ($oidprint === 'hex') { $attributes_name = $value[0]['value_hex']; } $attributes_oid = self::oidfromhex($value[0]['value_hex']); if ($value['type'] == '30') { $crlExtensionsValue = $value[1][0]; - if ($attributes_oid == '2.5.29.20') { // OBJ_crl_number + if ($attributes_oid === '2.5.29.20') { // OBJ_crl_number $crlExtensionsValue = $crlExtensionsValue['value']; } - if ($attributes_oid == '2.5.29.35') { // OBJ_authority_key_identifier + if ($attributes_oid === '2.5.29.35') { // OBJ_authority_key_identifier foreach ($crlExtensionsValue as $authority_key_identifierValueK => $authority_key_identifierV) { if (is_numeric($authority_key_identifierValueK)) { if ($authority_key_identifierV['type'] == '80') { $authority_key_identifier['keyIdentifier'] = $authority_key_identifierV['value_hex']; } - if ($authority_key_identifierV['type'] == 'a1') { + if ($authority_key_identifierV['type'] === 'a1') { $authority_key_identifier['authorityCertIssuer'] = $authority_key_identifierV['value_hex']; } if ($authority_key_identifierV['type'] == '82') { @@ -928,9 +925,9 @@ private static function parsecrl(array $crl, $oidprint = false) foreach ($curr as $key => $value) { if (is_numeric($key)) { if ($value['type'] == '31') { - if ($oidprint == 'oid') { + if ($oidprint === 'oid') { $subjOID = self::oidfromhex($curr[$key][0][0]['value_hex']); - } elseif ($oidprint == 'hex') { + } elseif ($oidprint === 'hex') { $subjOID = $curr[$key][0][0]['value_hex']; } else { $subjOID = self::oidfromhex($curr[$key][0][0]['value_hex']); @@ -942,7 +939,7 @@ private static function parsecrl(array $crl, $oidprint = false) unset($curr['depth']); unset($curr['type']); unset($curr['typeName']); - if ($key == 'hexdump') { + if ($key === 'hexdump') { $curr['sha1'] = hash('sha1', pack('H*', $value)); } } @@ -960,9 +957,6 @@ private static function parsecrl(array $crl, $oidprint = false) if (count($differ) == 0) { $differ = array_diff_key($arrModel['TBSCertList'], $crl['TBSCertList']); if (count($differ) > 0) { - foreach ($differ as $key => $val) { - } - return false; } } else { @@ -996,7 +990,7 @@ private static function oidfromhex($hex): string $result = false; foreach ($split as $val) { $dec = hexdec($val); - if ($i == 0) { + if ($i === 0) { if ($dec >= 128) { $nex = (128 * ($dec - 128)) - 80; if ($dec > 129) { @@ -1013,7 +1007,7 @@ private static function oidfromhex($hex): string $result = "1.{$first}."; } if ($dec < 40) { - $first = $dec - 0; + $first = $dec; $result = "0.{$first}."; } } else { diff --git a/src/pdfvalue/PDFValue.php b/src/pdfvalue/PDFValue.php index 2955be1..c3f99bb 100644 --- a/src/pdfvalue/PDFValue.php +++ b/src/pdfvalue/PDFValue.php @@ -22,6 +22,7 @@ namespace ddn\sapp\pdfvalue; use ArrayAccess; +use Exception; use ReturnTypeWillChange; use Stringable; @@ -80,7 +81,7 @@ public function offsetUnset($offset): void unset($this->value[$offset]); } - public function push($v): bool + public function push(mixed $v): bool { /*if (get_class($v) !== get_class($this)) throw new Exception('invalid object to concat to this one');*/ @@ -105,11 +106,8 @@ public function get_keys(): false|array /** * Returns the difference between this and other object (false means "cannot compare", null means "equal" and any value means "different": things in this object that are different from the other) */ - public function diff($other) + public function diff(object $other): mixed { - if (! is_a($other, static::class)) { - return false; - } if ($this->value === $other->value) { return null; } @@ -129,7 +127,7 @@ public function diff($other) * * @return pdfvalue an object of type PDFValue*, depending on the */ - protected static function _convert($value) + protected static function _convert($value): self { switch (gettype($value)) { case 'integer': @@ -160,7 +158,7 @@ protected static function _convert($value) // If not an object, it is a list $list = []; foreach ($value as $v) { - array_push($list, self::_convert($v)); + $list[] = self::_convert($v); } $value = new PDFValueList($list); } diff --git a/src/pdfvalue/PDFValueList.php b/src/pdfvalue/PDFValueList.php index ba4a302..9e0205d 100644 --- a/src/pdfvalue/PDFValueList.php +++ b/src/pdfvalue/PDFValueList.php @@ -33,7 +33,7 @@ public function __toString(): string return '[' . implode(' ', $this->value) . ']'; } - public function diff($other): false|null|self + public function diff(object $other): mixed { $different = parent::diff($other); if (($different === false) || ($different === null)) { @@ -58,7 +58,7 @@ public function val($list = false) if ($list === true) { $result = []; foreach ($this->value as $v) { - if (is_a($v, 'ddn\\sapp\\pdfvalue\\PDFValueSimple')) { + if (is_a($v, PDFValueSimple::class)) { $v = explode(' ', (string) $v->val()); } else { $v = [$v->val()]; @@ -87,7 +87,7 @@ public function get_object_referenced(): false|array if ($plain_text_val === $rebuilt) { // Any content is a reference foreach ($matches[2] as $id) { - array_push($ids, intval($id)); + $ids[] = (int) $id; } } } else { @@ -104,7 +104,7 @@ public function get_object_referenced(): false|array * - if it is a list object, the lists are merged; * - otherwise the object is converted to a PDFValue* object and it is appended to the list */ - public function push($v): bool + public function push(mixed $v): bool { if (is_object($v) && ($v::class === static::class)) { // If a list is pushed to another list, the elements are merged @@ -115,7 +115,7 @@ public function push($v): bool } foreach ($v as $e) { $e = self::_convert($e); - array_push($this->value, $e); + $this->value[] = $e; } return true; diff --git a/src/pdfvalue/PDFValueObject.php b/src/pdfvalue/PDFValueObject.php index b225f73..e5342c7 100644 --- a/src/pdfvalue/PDFValueObject.php +++ b/src/pdfvalue/PDFValueObject.php @@ -43,7 +43,7 @@ public function __toString(): string foreach ($this->value as $k => $v) { $v = '' . $v; if ($v === '') { - array_push($result, "/{$k}"); + $result[] = "/{$k}"; continue; } match ($v[0]) { @@ -55,7 +55,7 @@ public function __toString(): string return '<<' . implode('', $result) . '>>'; } - public function diff($other): false|null|self + public function diff(object $other): mixed { $different = parent::diff($other); if (($different === false) || ($different === null)) { @@ -67,7 +67,7 @@ public function diff($other): false|null|self foreach ($this->value as $k => $v) { if (isset($other->value[$k])) { - if (is_a($this->value[$k], "ddn\sapp\pdfvalue\PDFValue")) { + if (is_a($this->value[$k], PDFValue::class)) { $different = $this->value[$k]->diff($other->value[$k]); if ($different === false) { $result[$k] = $v; @@ -91,7 +91,7 @@ public function diff($other): false|null|self return $result; } - public static function fromarray($parts): false|self + public static function fromarray(array $parts): false|self { $k = array_keys($parts); $intkeys = false; @@ -105,8 +105,8 @@ public static function fromarray($parts): false|self if ($intkeys) { return false; } - foreach ($parts as $k => $v) { - $result[$k] = self::_convert($v); + foreach ($parts as $k2 => $v) { + $result[$k2] = self::_convert($v); } return new self($result); @@ -118,7 +118,7 @@ public static function fromstring($str): false|self $field = null; $value = null; $parts = explode(' ', (string) $str); - for ($i = 0; $i < count($parts); $i++) { + for ($i = 0, $iMax = count($parts); $i < $iMax; $i++) { if ($field === null) { $field = $parts[$i]; if ($field === '') { diff --git a/src/pdfvalue/PDFValueSimple.php b/src/pdfvalue/PDFValueSimple.php index f119c13..63e6b19 100644 --- a/src/pdfvalue/PDFValueSimple.php +++ b/src/pdfvalue/PDFValueSimple.php @@ -46,7 +46,7 @@ public function get_object_referenced(): false|int return false; } - return intval($matches[1]); + return (int) $matches[1]; } public function get_int(): false|int @@ -55,6 +55,6 @@ public function get_int(): false|int return false; } - return intval($this->value); + return (int) $this->value; } } From 7e7dcd8f7c2a7d461e193666c846e4f063e8e9f2 Mon Sep 17 00:00:00 2001 From: Marcin Jakubowski Date: Thu, 31 Oct 2024 22:39:58 +0100 Subject: [PATCH 05/11] throw exception instead of homegrown error report --- src/PDFDoc.php | 63 ++++++----- src/PDFDocWithContents.php | 22 ++-- src/PDFException.php | 9 ++ src/PDFObject.php | 22 ++-- src/PDFSignatureObject.php | 4 +- src/PDFUtilFnc.php | 93 ++++++++-------- src/helpers/CMS.php | 176 ++++++++++++++---------------- src/helpers/contentgeneration.php | 8 +- src/helpers/fpdfhelpers.php | 42 +++---- src/helpers/helpers.php | 17 --- 10 files changed, 216 insertions(+), 240 deletions(-) create mode 100644 src/PDFException.php diff --git a/src/PDFDoc.php b/src/PDFDoc.php index bcbf5e3..fe37edb 100644 --- a/src/PDFDoc.php +++ b/src/PDFDoc.php @@ -37,7 +37,6 @@ use function ddn\sapp\helpers\_add_image; use function ddn\sapp\helpers\get_random_string; use function ddn\sapp\helpers\p_debug; -use function ddn\sapp\helpers\p_error; use function ddn\sapp\helpers\p_warning; use function ddn\sapp\helpers\references_in_object; use function ddn\sapp\helpers\timestamp_to_pdfdatestring; @@ -175,7 +174,7 @@ public static function from_string(string $buffer, ?int $depth = null): false|se if ($trailer !== false) { if ($trailer['Encrypt'] !== false) { // TODO: include encryption (maybe borrowing some code: http://www.fpdf.org/en/script/script37.php) - p_error('encrypted documents are not fully supported; maybe you cannot get the expected results'); + throw new PDFException('encrypted documents are not fully supported; maybe you cannot get the expected results'); } } @@ -343,7 +342,7 @@ public function clear_signature_certificate(): void * * @return valid true if the certificate can be used to sign the document, false otherwise */ - public function set_signature_certificate($certfile, ?string $certpass = null) + public function set_signature_certificate($certfile, ?string $certpass = null): bool { // First we read the certificate if (is_array($certfile)) { @@ -352,10 +351,10 @@ public function set_signature_certificate($certfile, ?string $certpass = null) // If a password is provided, we'll try to decode the private key if (openssl_pkey_get_private($certificate['pkey']) === false) { - return p_error('invalid private key'); + throw new PDFException('invalid private key'); } if (! openssl_x509_check_private_key($certificate['cert'], $certificate['pkey'])) { - return p_error("private key doesn't corresponds to certificate"); + throw new PDFException("private key doesn't corresponds to certificate"); } if (is_string($certificate['extracerts'] ?? null)) { @@ -367,10 +366,10 @@ public function set_signature_certificate($certfile, ?string $certpass = null) } else { $certfilecontent = file_get_contents($certfile); if ($certfilecontent === false) { - return p_error("could not read file {$certfile}"); + throw new PDFException("could not read file {$certfile}"); } if (openssl_pkcs12_read($certfilecontent, $certificate, $certpass) === false) { - return p_error("could not get the certificates from file {$certfile}"); + throw new PDFException("could not get the certificates from file {$certfile}"); } } @@ -538,7 +537,7 @@ public function to_pdf_file_b(bool $rebuild = false): Buffer if ($_signature === false) { $this->pop_state(); - return p_error('could not generate the signed document'); + throw new PDFException('could not generate the signed document'); } } @@ -689,18 +688,18 @@ public function to_pdf_file_s(bool $rebuild = false) * * @return written true if the file has been correcly written to the file; false otherwise */ - public function to_pdf_file($filename, bool $rebuild = false) + public function to_pdf_file($filename, bool $rebuild = false): bool { $pdf_content = $this->to_pdf_file_b($rebuild); $file = fopen($filename, 'wb'); if ($file === false) { - return p_error('failed to create the file'); + throw new PDFException('failed to create the file'); } if (fwrite($file, $pdf_content->get_raw()) !== $pdf_content->size()) { fclose($file); - return p_error('failed to write to file'); + throw new PDFException('failed to write to file'); } fclose($file); @@ -957,11 +956,11 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, return p_warning("failed to open the image {$imagesize}"); } if (($page_to_appear < 0) || ($page_to_appear > $this->get_page_count() - 1)) { - return p_error('invalid page number'); + throw new PDFException('invalid page number'); } $pagesize = $this->get_page_size($page_to_appear); if ($pagesize === false) { - return p_error('failed to get page size'); + throw new PDFException('failed to get page size'); } $pagesize = explode(' ', (string) $pagesize[0]->val()); @@ -979,7 +978,7 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, if (is_array($size)) { if (count($size) != 2) { - return p_error('invalid size'); + throw new PDFException('invalid size'); } $width = $size[0]; $height = $size[1]; @@ -992,7 +991,7 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, $width = $i_w * $size; $height = $i_h * $size; } else { - return p_error('invalid size format'); + throw new PDFException('invalid size format'); } } } @@ -1005,12 +1004,12 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, } if (! $this->set_signature_certificate($certfile, $password)) { - return p_error('the certificate or the signature is not valid'); + throw new PDFException('the certificate or the signature is not valid'); } $docsigned = $this->to_pdf_file_s(); if ($docsigned === false) { - return p_error('failed to sign the document'); + throw new PDFException('failed to sign the document'); } return self::from_string($docsigned); @@ -1057,18 +1056,18 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false $root = $this->_pdf_trailer_object['Root']; if (($root === false) || (($root = $root->get_object_referenced()) === false)) { - return p_error('could not find the root object from the trailer'); + throw new PDFException('could not find the root object from the trailer'); } $root_obj = $this->get_object($root); if ($root_obj === false) { - return p_error('invalid root object'); + throw new PDFException('invalid root object'); } // Now the object corresponding to the page number in which to appear $page_obj = $this->get_page($pagetoappear); if ($page_obj === false) { - return p_error('invalid page'); + throw new PDFException('invalid page'); } // The objects to update @@ -1197,7 +1196,7 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false $result = _add_image($this->create_object(...), $imagefilename, $bbox[0], $bbox[1], $bbox[2], $bbox[3], $page_rotation->val()); if ($result === false) { - return p_error('could not add the image'); + throw new PDFException('could not add the image'); } $layer_n2['Resources'] = $result['resources']; @@ -1221,7 +1220,7 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false } if (! $newannots->push(new PDFValueReference($annotation_object->get_oid()))) { - return p_error('Could not update the page where the signature has to appear'); + throw new PDFException('Could not update the page where the signature has to appear'); } $page_obj['Annots'] = new PDFValueReference($newannots->get_oid()); @@ -1248,7 +1247,7 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false // Add the annotation object to the interactive form if (! $acroform['Fields']->push(new PDFValueReference($annotation_object->get_oid()))) { - return p_error('could not create the signature field'); + throw new PDFException('could not create the signature field'); } // Store the objects @@ -1267,18 +1266,18 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false * * @return ok true if the date could be set; false otherwise */ - protected function update_mod_date(DateTime $date = null) + protected function update_mod_date(DateTime $date = null): bool { // First of all, we are searching for the root object (which should be in the trailer) $root = $this->_pdf_trailer_object['Root']; if (($root === false) || (($root = $root->get_object_referenced()) === false)) { - return p_error('could not find the root object from the trailer'); + throw new PDFException('could not find the root object from the trailer'); } $root_obj = $this->get_object($root); if ($root_obj === false) { - return p_error('invalid root object'); + throw new PDFException('invalid root object'); } if ($date === null) { @@ -1302,12 +1301,12 @@ protected function update_mod_date(DateTime $date = null) // Update the information object (not really needed) $info = $this->_pdf_trailer_object['Info']; if (($info === false) || (($info = $info->get_object_referenced()) === false)) { - return p_error('could not find the info object from the trailer'); + throw new PDFException('could not find the info object from the trailer'); } $info_obj = $this->get_object($info); if ($info_obj === false) { - return p_error('invalid info object'); + throw new PDFException('invalid info object'); } $info_obj['ModDate'] = new PDFValueString(timestamp_to_pdfdatestring($date)); @@ -1374,7 +1373,7 @@ protected function _get_page_info(int $oid, array $info = []): array { $object = $this->get_object($oid); if ($object === false) { - return p_error('could not get information about the page'); + throw new PDFException('could not get information about the page'); } $page_ids = []; @@ -1395,7 +1394,7 @@ protected function _get_page_info(int $oid, array $info = []): array array_push($page_ids, ...$ids); } } else { - return p_error('could not get the pages'); + throw new PDFException('could not get the pages'); } break; case 'Page': @@ -1426,14 +1425,14 @@ protected function _acquire_pages_info() { $root = $this->_pdf_trailer_object['Root']; if (($root === false) || (($root = $root->get_object_referenced()) === false)) { - return p_error('could not find the root object from the trailer'); + throw new PDFException('could not find the root object from the trailer'); } $root = $this->get_object($root); if ($root !== false) { $pages = $root['Pages']; if (($pages === false) || (($pages = $pages->get_object_referenced()) === false)) { - return p_error('could not find the pages for the document'); + throw new PDFException('could not find the pages for the document'); } $this->_pages_info = $this->_get_page_info($pages); diff --git a/src/PDFDocWithContents.php b/src/PDFDocWithContents.php index 483771e..309e04b 100644 --- a/src/PDFDocWithContents.php +++ b/src/PDFDocWithContents.php @@ -26,7 +26,6 @@ use ddn\sapp\pdfvalue\PDFValueReference; use function ddn\sapp\helpers\_add_image; use function ddn\sapp\helpers\get_random_string; -use function ddn\sapp\helpers\p_error; use function ddn\sapp\helpers\p_warning; class PDFDocWithContents extends PDFDoc @@ -59,7 +58,7 @@ class PDFDocWithContents extends PDFDoc * @param params an array of values [ "font" => , "size" => , * "color" => <#hexcolor>, "angle" => ] */ - public function add_text(int $page_to_appear, $text, $x, $y, $params = []) + public function add_text(int $page_to_appear, $text, $x, $y, $params = []): void { // TODO: maybe we can create a function that "adds content to a page", and that // function will search for the content field and merge the resources, if @@ -77,13 +76,13 @@ public function add_text(int $page_to_appear, $text, $x, $y, $params = []) $page_obj = $this->get_page($page_to_appear); if ($page_obj === false) { - return p_error('invalid page'); + throw new PDFException('invalid page'); } $resources_obj = $this->get_indirect_object($page_obj['Resources']); if (! in_array($params['font'], self::T_STANDARD_FONTS, true)) { - return p_error('only standard fonts are allowed Times-Roman, Helvetica, Courier, Symbol, ZapfDingbats'); + throw new PDFException('only standard fonts are allowed Times-Roman, Helvetica, Courier, Symbol, ZapfDingbats'); } $font_id = 'F' . get_random_string(4); @@ -98,7 +97,7 @@ public function add_text(int $page_to_appear, $text, $x, $y, $params = []) $data = $contents_obj->get_stream(false); if ($data === false) { - return p_error('could not interpret the contents of the page'); + throw new PDFException('could not interpret the contents of the page'); } // Get the page height, to change the coordinates system (up to down) @@ -134,13 +133,13 @@ public function add_text(int $page_to_appear, $text, $x, $y, $params = []) [$r, $g, $b] = sscanf($color, '#%02x%02x%02x'); break; default: - p_error('please use html-like colors (e.g. #ffbbaa)'); + throw new PDFException('please use html-like colors (e.g. #ffbbaa)'); } if ($r !== null) { $text_command = " q {$r} {$g} {$b} rg {$text_command} Q"; } // Color RGB } else { - p_error('please use html-like colors (e.g. #ffbbaa)'); + throw new PDFException('please use html-like colors (e.g. #ffbbaa)'); } if ($angle !== 0) { @@ -169,7 +168,7 @@ public function add_text(int $page_to_appear, $text, $x, $y, $params = []) * @param w the width of the image * @param w the height of the image */ - public function add_image($page_obj, $filename, $x = 0, $y = 0, $w = 0, $h = 0) + public function add_image($page_obj, $filename, $x = 0, $y = 0, $w = 0, $h = 0): bool { // TODO: maybe we can create a function that "adds content to a page", and that // function will search for the content field and merge the resources, if @@ -182,7 +181,7 @@ public function add_image($page_obj, $filename, $x = 0, $y = 0, $w = 0, $h = 0) } if ($page_obj === false) { - return p_error('invalid page'); + throw new PDFException('invalid page'); } // Get the page height, to change the coordinates system (up to down) @@ -191,8 +190,7 @@ public function add_image($page_obj, $filename, $x = 0, $y = 0, $w = 0, $h = 0) $result = _add_image($filename, $x, $pagesize_h - $y, $w, $h); - return p_error('this function still needs work'); - + throw new PDFException('this function still needs work'); // Get the resources for the page $resources_obj = $this->get_indirect_object($page_obj['Resources']); if (! isset($resources_obj['ProcSet'])) { @@ -211,7 +209,7 @@ public function add_image($page_obj, $filename, $x = 0, $y = 0, $w = 0, $h = 0) $data = $contents_obj->get_stream(false); if ($data === false) { - return p_error('could not interpret the contents of the page'); + throw new PDFException('could not interpret the contents of the page'); } // Append the command to draw the image diff --git a/src/PDFException.php b/src/PDFException.php new file mode 100644 index 0000000..d0d31bb --- /dev/null +++ b/src/PDFException.php @@ -0,0 +1,9 @@ +_stream), $params); default: - return p_error('unknown compression method ' . $this->_value['Filter']); + throw new PDFException('unknown compression method ' . $this->_value['Filter']); } } @@ -194,13 +193,10 @@ public function set_stream($stream, $raw = true): void return; } if (isset($this->_value['Filter'])) { - switch ($this->_value['Filter']) { - case '/FlateDecode': - $stream = gzcompress((string) $stream); - break; - default: - p_error('unknown compression method ' . $this->_value['Filter']); - } + $stream = match ($this->_value['Filter']) { + '/FlateDecode' => gzcompress((string) $stream), + default => throw new PDFException('unknown compression method ' . $this->_value['Filter']), + }; } $this->_value['Length'] = strlen((string) $stream); $this->_stream = $stream; @@ -278,21 +274,21 @@ protected static function FlateDecode($_stream, array $params) case 15: break; default: - return p_error('other predictor than PNG is not supported in this version'); + throw new PDFException('other predictor than PNG is not supported in this version'); } switch ($params['Colors']->get_int()) { case 1: break; default: - return p_error('other color count than 1 is not supported in this version'); + throw new PDFException('other color count than 1 is not supported in this version'); } switch ($params['BitsPerComponent']->get_int()) { case 8: break; default: - return p_error('other bit count than 8 is not supported in this version'); + throw new PDFException('other bit count than 8 is not supported in this version'); } $decoded = new Buffer(); @@ -331,7 +327,7 @@ protected static function FlateDecode($_stream, array $params) } break; default: - return p_error('Unsupported stream'); + throw new PDFException('Unsupported stream'); } // Store and prepare the previous row diff --git a/src/PDFSignatureObject.php b/src/PDFSignatureObject.php index 2d4dddc..1372462 100644 --- a/src/PDFSignatureObject.php +++ b/src/PDFSignatureObject.php @@ -23,7 +23,6 @@ use ddn\sapp\pdfvalue\PDFValueSimple; use ddn\sapp\pdfvalue\PDFValueString; -use function ddn\sapp\helpers\p_error; use function ddn\sapp\helpers\timestamp_to_pdfdatestring; // This is an special object that has a set of fields @@ -80,8 +79,7 @@ public function set_certificate($certificate): void public function set_signature_ltv($signature_ltv_data): void { - p_error(get_debug_type($signature_ltv_data)); - + throw new PDFException(get_debug_type($signature_ltv_data)); $this->_signature_ltv_data = $signature_ltv_data; } diff --git a/src/PDFUtilFnc.php b/src/PDFUtilFnc.php index 0e0def9..0b34d69 100644 --- a/src/PDFUtilFnc.php +++ b/src/PDFUtilFnc.php @@ -24,7 +24,7 @@ use ddn\sapp\helpers\Buffer; use ddn\sapp\helpers\LoadHelpers; use ddn\sapp\helpers\StreamReader; -use function ddn\sapp\helpers\p_error; +use ddn\sapp\pdfvalue\PDFValue; use function ddn\sapp\helpers\p_warning; if (! defined(LoadHelpers::class)) { @@ -35,11 +35,11 @@ class PDFUtilFnc { - public static function get_trailer(&$_buffer, int $trailer_pos) + public static function get_trailer(&$_buffer, int $trailer_pos): PDFValue|false|null { // Search for the trailer structure if (preg_match('/trailer\s*(.*)\s*startxref/ms', (string) $_buffer, $matches, 0, $trailer_pos) !== 1) { - return p_error('trailer not found'); + throw new PDFException('trailer not found'); } $trailer_str = $matches[1]; @@ -50,7 +50,7 @@ public static function get_trailer(&$_buffer, int $trailer_pos) try { $trailer_obj = $parser->parsestr($trailer_str); } catch (Exception) { - return p_error('trailer is not valid'); + throw new PDFException('trailer is not valid'); } return $trailer_obj; @@ -115,7 +115,7 @@ public static function build_xref_1_5($offsets): array * This function obtains the xref from the cross reference streams (7.5.8 Cross-Reference Streams) * which started in PDF 1.5. */ - public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null) + public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null): false|array { if ($depth !== null) { if ($depth <= 0) { @@ -127,21 +127,21 @@ public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null $xref_o = self::find_object_at_pos($_buffer, null, $xref_pos, []); if ($xref_o === false) { - return p_error("cross reference object not found when parsing xref at position {$xref_pos}", [false, false, false]); + throw new PDFException("cross reference object not found when parsing xref at position {$xref_pos}", [false, false, false]); } if (! (isset($xref_o['Type'])) || ($xref_o['Type']->val() !== 'XRef')) { - return p_error('invalid xref table', [false, false, false]); + throw new PDFException('invalid xref table', [false, false, false]); } $stream = $xref_o->get_stream(false); if ($stream === null) { - return p_error("cross reference stream not found when parsing xref at position {$xref_pos}", [false, false, false]); + throw new PDFException("cross reference stream not found when parsing xref at position {$xref_pos}", [false, false, false]); } $W = $xref_o['W']->val(true); if (count($W) !== 3) { - return p_error('invalid cross reference object', [false, false, false]); + throw new PDFException('invalid cross reference object', [false, false, false]); } $W[0] = (int) $W[0]; @@ -150,7 +150,7 @@ public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null $Size = $xref_o['Size']->get_int(); if ($Size === false) { - return p_error('could not get the size of the xref table', [false, false, false]); + throw new PDFException('could not get the size of the xref table', [false, false, false]); } $Index = [0, $Size]; @@ -159,7 +159,7 @@ public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null } if (count($Index) % 2 !== 0) { - return p_error('invalid indexes of xref table', [false, false, false]); + throw new PDFException('invalid indexes of xref table', [false, false, false]); } // Get the previous xref table, to build up on it @@ -173,7 +173,7 @@ public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null $Prev = $xref_o['Prev']; $Prev = $Prev->get_int(); if ($Prev === false) { - return p_error('invalid reference to a previous xref table', [false, false, false]); + throw new PDFException('invalid reference to a previous xref table', [false, false, false]); } // When dealing with 1.5 cross references, we do not allow to use other than cross references @@ -222,7 +222,7 @@ public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null $f3 = $fmt_function[2]($stream_v->nextchars($W[2])); if (($f1 === false) || ($f2 === false) || ($f3 === false)) { - return p_error('invalid stream for xref table', [false, false, false]); + throw new PDFException('invalid stream for xref table', [false, false, false]); } switch ($f1) { @@ -250,7 +250,7 @@ public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null ]; break; default: - p_error("do not know about entry of type {$f1} in xref table"); + throw new PDFException("do not know about entry of type {$f1} in xref table"); } $object_i++; @@ -261,7 +261,7 @@ public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null return [$xref_table, $xref_o->get_value(), '1.5']; } - public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) + public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null): false|array { if ($depth !== null) { if ($depth <= 0) { @@ -280,7 +280,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) $separator = "\r\n"; $xref_line = strtok($xref_substr, $separator); if ($xref_line !== 'xref') { - return p_error("xref tag not found at position {$xref_pos}", [false, false, false]); + throw new PDFException("xref tag not found at position {$xref_pos}", [false, false, false]); } // Now parse the lines and build the xref table @@ -293,7 +293,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) if (preg_match('/([0-9]+) ([0-9]+)$/', $xref_line, $matches) === 1) { if ($obj_count > 0) { // If still expecting objects, we'll assume that the xref is malformed - return p_error("malformed xref at position {$xref_pos}", [false, false, false]); + throw new PDFException("malformed xref at position {$xref_pos}", [false, false, false]); } $obj_id = (int) $matches[1]; $obj_count = (int) $matches[2]; @@ -304,7 +304,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) if (preg_match('/^([0-9]+) ([0-9]+) (.)\s*/', $xref_line, $matches) === 1) { // If no object expected, we'll assume that the xref is malformed if ($obj_count === 0) { - return p_error("unexpected entry for xref: {$xref_line}", [false, false, false]); + throw new PDFException("unexpected entry for xref: {$xref_line}", [false, false, false]); } $obj_offset = (int) $matches[1]; @@ -340,7 +340,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) break; default: // If it is not one of the expected, let's skip the object - p_error("invalid entry for xref: {$xref_line}", [false, false, false]); + throw new PDFException("invalid entry for xref: {$xref_line}", [false, false, false]); } } @@ -350,8 +350,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) } // If the entry is not recongised, show the error - p_error("invalid xref entry {$xref_line}"); - $xref_line = strtok($separator); + throw new PDFException("invalid xref entry {$xref_line}"); } // Get the trailer object @@ -361,7 +360,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) if (isset($trailer_obj['Prev'])) { $xref_prev_pos = $trailer_obj['Prev']->val(); if (! is_numeric($xref_prev_pos)) { - return p_error("invalid trailer {$trailer_obj}", [false, false, false]); + throw new PDFException("invalid trailer {$trailer_obj}", [false, false, false]); } $xref_prev_pos = (int) $xref_prev_pos; @@ -369,7 +368,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null) [$prev_table, $prev_trailer, $prev_min_pdf_version] = self::get_xref_1_4($_buffer, $xref_prev_pos, $depth); if ($prev_min_pdf_version !== $min_pdf_version) { - return p_error('mixed type of xref tables are not supported', [false, false, false]); + throw new PDFException('mixed type of xref tables are not supported', [false, false, false]); } if ($prev_table !== false) { @@ -398,7 +397,7 @@ public static function get_xref(string &$_buffer, ?int $xref_pos, ?int $depth = return [$xref_table, $trailer_obj, $min_pdf_version]; } - public static function acquire_structure(string &$_buffer, ?int $depth = null) + public static function acquire_structure(string &$_buffer, ?int $depth = null): false|array { // Get the first line and acquire the PDF version of the document $separator = "\r\n"; @@ -408,11 +407,11 @@ public static function acquire_structure(string &$_buffer, ?int $depth = null) } if (preg_match('/^%PDF-[0-9]+\.[0-9]+$/', $pdf_version, $matches) !== 1) { - return p_error('PDF version string not found'); + throw new PDFException('PDF version string not found'); } if (preg_match_all('/startxref\s*([0-9]+)\s*%%EOF($|[\r\n])/ms', (string) $_buffer, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE) === false) { - return p_error('failed to get structure'); + throw new PDFException('failed to get structure'); } $_versions = []; @@ -427,11 +426,11 @@ public static function acquire_structure(string &$_buffer, ?int $depth = null) // Now get the trailing part and make sure that it has the proper form $startxref_pos = strrpos((string) $_buffer, 'startxref'); if ($startxref_pos === false) { - return p_error('startxref not found'); + throw new PDFException('startxref not found'); } if (preg_match('/startxref\s*([0-9]+)\s*%%EOF\s*$/ms', (string) $_buffer, $matches, 0, $startxref_pos) !== 1) { - return p_error('startxref and %%EOF not found'); + throw new PDFException('startxref and %%EOF not found'); } $xref_pos = (int) $matches[1]; @@ -453,11 +452,11 @@ public static function acquire_structure(string &$_buffer, ?int $depth = null) // We are providing a lot of information to be able to inspect the problems of a PDF file if ($xref_table === false) { // TODO: Maybe we could include a "recovery" method for this: if xref is not at pos $xref_pos, we could search for xref by hand - return p_error('could not find the xref table'); + throw new PDFException('could not find the xref table'); } if ($trailer_object === false) { - return p_error('could not find the trailer object'); + throw new PDFException('could not find the trailer object'); } return [ @@ -480,7 +479,7 @@ public static function acquire_structure(string &$_buffer, ?int $depth = null) * * @return obj the PDFObject obtained from the file or false if could not be found */ - public static function find_object_at_pos(&$_buffer, ?int $oid, int $object_offset, $xref_table) + public static function find_object_at_pos(&$_buffer, ?int $oid, int $object_offset, $xref_table): false|PDFObject { $object = self::object_from_string($_buffer, $oid, $object_offset, $offset_end); @@ -507,18 +506,18 @@ public static function find_object_at_pos(&$_buffer, ?int $oid, int $object_offs if ($length === false) { $length_object_id = $object['Length']->get_object_referenced(); if ($length_object_id === false) { - return p_error("could not get stream for object {$oid}"); + throw new PDFException("could not get stream for object {$oid}"); } $length_object = self::find_object($_buffer, $xref_table, $length_object_id); if ($length_object === false) { - return p_error("could not get object {$oid}"); + throw new PDFException("could not get object {$oid}"); } $length = $length_object->get_value()?->get_int(); } if ($length === false) { - return p_error("could not get stream length for object {$oid}"); + throw new PDFException("could not get stream length for object {$oid}"); } $object->set_stream(substr((string) $_buffer, $_stream_pending, $length), true); @@ -536,7 +535,7 @@ public static function find_object_at_pos(&$_buffer, ?int $oid, int $object_offs * * @return obj the PDFObject obtained from the file or false if could not be found */ - public static function find_object(&$_buffer, $xref_table, int $oid) + public static function find_object(&$_buffer, $xref_table, int $oid): false|PDFObject { if ($oid === 0) { return false; @@ -559,15 +558,15 @@ public static function find_object(&$_buffer, $xref_table, int $oid) /** * Function that searches for an object in an object stream */ - public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm_oid, $objpos, int $oid) + public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm_oid, $objpos, int $oid): PDFObject { $objstm = self::find_object($_buffer, $xref_table, $objstm_oid); if ($objstm === false) { - return p_error("could not get object stream {$objstm_oid}"); + throw new PDFException("could not get object stream {$objstm_oid}"); } if ((($objstm['Extends'] ?? false) !== false)) { // TODO: support them - return p_error('not supporting extended object streams at this time'); + throw new PDFException('not supporting extended object streams at this time'); } $First = $objstm['First'] ?? false; @@ -575,11 +574,11 @@ public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm $Type = $objstm['Type'] ?? false; if (($First === false) || ($N === false) || ($Type === false)) { - return p_error("invalid object stream {$objstm_oid}"); + throw new PDFException("invalid object stream {$objstm_oid}"); } if ($Type->val() !== 'ObjStm') { - return p_error("object {$objstm_oid} is not an object stream"); + throw new PDFException("object {$objstm_oid} is not an object stream"); } $First = $First->get_int(); @@ -591,12 +590,12 @@ public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm $stream = substr((string) $stream, $First); if (count($index) % 2 !== 0) { - return p_error("invalid index for object stream {$objstm_oid}"); + throw new PDFException("invalid index for object stream {$objstm_oid}"); } $objpos = $objpos * 2; if ($objpos > count($index)) { - return p_error("object {$oid} not found in object stream {$objstm_oid}"); + throw new PDFException("object {$oid} not found in object stream {$objstm_oid}"); } $offset = (int) $index[$objpos + 1]; @@ -621,11 +620,11 @@ public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm /** * Function that parses an object */ - public static function object_from_string(string $buffer, ?int $expected_obj_id, int $offset = 0, ?int &$offset_end = 0) + public static function object_from_string(string $buffer, ?int $expected_obj_id, int $offset = 0, ?int &$offset_end = 0): PDFObject { if (preg_match('/([0-9]+)\s+([0-9+])\s+obj(\s+)/ms', (string) $buffer, $matches, 0, $offset) !== 1) { // p_debug_var(substr($buffer)) - return p_error("object is not valid: {$expected_obj_id}"); + throw new PDFException("object is not valid: {$expected_obj_id}"); } $found_obj_header = $matches[0]; @@ -637,7 +636,7 @@ public static function object_from_string(string $buffer, ?int $expected_obj_id, } if ($found_obj_id !== $expected_obj_id) { - return p_error("pdf structure is corrupt: found obj {$found_obj_id} while searching for obj {$expected_obj_id} (at {$offset})"); + throw new PDFException("pdf structure is corrupt: found obj {$found_obj_id} while searching for obj {$expected_obj_id} (at {$offset})"); } // The object starts after the header @@ -650,7 +649,7 @@ public static function object_from_string(string $buffer, ?int $expected_obj_id, $obj_parsed = $parser->parse($stream); if ($obj_parsed === false) { - return p_error("object {$expected_obj_id} could not be parsed"); + throw new PDFException("object {$expected_obj_id} could not be parsed"); } switch ($parser->current_token()) { @@ -661,7 +660,7 @@ public static function object_from_string(string $buffer, ?int $expected_obj_id, // There is an stream break; default: - return p_error('malformed object'); + throw new PDFException('malformed object'); } $offset_end = $stream->getpos(); diff --git a/src/helpers/CMS.php b/src/helpers/CMS.php index 59861f2..0a0cfe0 100644 --- a/src/helpers/CMS.php +++ b/src/helpers/CMS.php @@ -10,6 +10,8 @@ // License : GNU GPLv3 */ +use ddn\sapp\PDFException; + /** * @class cms * Manage CMS(Cryptographic Message Syntax) Signature for SAPP PDF @@ -54,9 +56,7 @@ public function sendReq(array $reqData) } } if ($code != '200') { - p_error(" response error! Code=\"{$code}\", Status=\"" . trim($status ?? '') . '"'); - - return false; + throw new PDFException(sprintf('response error! Code="{$code}", Status="%s"', trim($status ?? ''))); } $contentTypeHeader = ''; $headers = explode("\n", $header); @@ -68,12 +68,10 @@ public function sendReq(array $reqData) } } if ($contentTypeHeader != $reqData['resp_contentType']) { - p_error(" response content type not {$reqData['resp_contentType']}, but: \"{$contentTypeHeader}\""); - - return false; + throw new PDFException("response content type not {$reqData['resp_contentType']}, but: \"{$contentTypeHeader}\""); } if (empty($body)) { - p_error(' error empty response!'); + throw new PDFException('error empty response!'); } return $body; // binary response @@ -102,14 +100,12 @@ public function pkcs7_sign($binaryData) ]; $hashAlgorithm = $this->signature_data['hashAlgorithm']; if (! array_key_exists($hashAlgorithm, $hexOidHashAlgos)) { - p_error('not support hash algorithm!'); - - return false; + throw new PDFException('not support hash algorithm!'); } p_debug("hash algorithm is \"{$hashAlgorithm}\""); $x509 = new x509(); if (! $certParse = $x509::readcert($this->signature_data['signcert'])) { - p_error('certificate error! check certificate'); + throw new PDFException('certificate error! check certificate'); } $hexEmbedCerts[] = bin2hex($x509::get_cert($this->signature_data['signcert'])); $appendLTV = ''; @@ -218,9 +214,7 @@ public function pkcs7_sign($binaryData) ); $pkey = $this->signature_data['privkey']; if (! openssl_private_encrypt(hex2bin($toencrypt), $encryptedDigest, $pkey, OPENSSL_PKCS1_PADDING)) { - p_error("openssl_private_encrypt error! can't encrypt"); - - return false; + throw new PDFException("openssl_private_encrypt error! can't encrypt"); } $hexencryptedDigest = bin2hex($encryptedDigest); $timeStamp = ''; @@ -288,17 +282,17 @@ protected function createTimestamp($data, string $hashAlg = 'sha1') p_debug(' sending TSA query to "' . $tsaData['host'] . '"...'); if (! $binaryTsaResp = $this->sendReq($reqData)) { - p_error(' TSA query send FAILED!'); - } else { - p_debug(' TSA query send OK'); - p_debug(' Parsing Timestamp response...'); - if (! $tsaResp = $this->tsa_parseResp($binaryTsaResp)) { - p_error(' parsing FAILED!'); - } - p_debug(' parsing OK'); - $TSTInfo = $tsaResp['TimeStampResp']['timeStampToken']['hexdump']; + throw new PDFException('TSA query send FAILED!'); } + p_debug(' TSA query send OK'); + p_debug(' Parsing Timestamp response...'); + if (! $tsaResp = $this->tsa_parseResp($binaryTsaResp)) { + throw new PDFException('parsing FAILED!'); + } + p_debug(' parsing OK'); + $TSTInfo = $tsaResp['TimeStampResp']['timeStampToken']['hexdump']; + return $TSTInfo; } @@ -335,58 +329,59 @@ protected function LTVvalidation($parsedCert): false|array p_debug(" OK got address:\"{$crlURIorFILE}\""); } if (empty($ocspURI) && empty($crlURIorFILE)) { - p_error(" can't get OCSP/CRL address! Process terminated."); - } else { // Perform if either ocspURI/crlURIorFILE exists - p_debug(' getting Issuer...'); - p_debug(' looking for issuer address from AIA attribute...'); - $issuerURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.2'][0]; - $issuerURIorFILE = trim($issuerURIorFILE ?? ''); - if (empty($issuerURIorFILE)) { - p_debug(' Failed!'); - } else { - p_debug(" OK got address \"{$issuerURIorFILE}\"..."); - p_debug(" load issuer from \"{$issuerURIorFILE}\"..."); - if ($issuerCert = @file_get_contents($issuerURIorFILE)) { - p_debug(' OK. size ' . round(strlen($issuerCert) / 1024, 2) . 'Kb'); - p_debug(' reading issuer certificate...'); - if ($issuer_certDER = x509::get_cert($issuerCert)) { - p_debug(' OK'); - p_debug(' check if issuer is cert issuer...'); - $certIssuer_parse = x509::readcert($issuer_certDER, 'oid'); // Parsing Issuer cert - $certSigner_signatureField = $certSigner_parse['signatureValue']; - if (openssl_public_decrypt(hex2bin((string) $certSigner_signatureField), $decrypted, x509::x509_der2pem($issuer_certDER), OPENSSL_PKCS1_PADDING)) { - p_debug(' OK issuer is cert issuer.'); - $ltvResult['issuer'] = $issuer_certDER; - } else { - p_warning(' FAILED! issuer is not cert issuer.'); - } + throw new PDFException("can't get OCSP/CRL address! Process terminated."); + } + + // Perform if either ocspURI/crlURIorFILE exists + p_debug(' getting Issuer...'); + p_debug(' looking for issuer address from AIA attribute...'); + $issuerURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.2'][0]; + $issuerURIorFILE = trim($issuerURIorFILE ?? ''); + if (empty($issuerURIorFILE)) { + p_debug(' Failed!'); + } else { + p_debug(" OK got address \"{$issuerURIorFILE}\"..."); + p_debug(" load issuer from \"{$issuerURIorFILE}\"..."); + if ($issuerCert = @file_get_contents($issuerURIorFILE)) { + p_debug(' OK. size ' . round(strlen($issuerCert) / 1024, 2) . 'Kb'); + p_debug(' reading issuer certificate...'); + if ($issuer_certDER = x509::get_cert($issuerCert)) { + p_debug(' OK'); + p_debug(' check if issuer is cert issuer...'); + $certIssuer_parse = x509::readcert($issuer_certDER, 'oid'); // Parsing Issuer cert + $certSigner_signatureField = $certSigner_parse['signatureValue']; + if (openssl_public_decrypt(hex2bin((string) $certSigner_signatureField), $decrypted, x509::x509_der2pem($issuer_certDER), OPENSSL_PKCS1_PADDING)) { + p_debug(' OK issuer is cert issuer.'); + $ltvResult['issuer'] = $issuer_certDER; } else { - p_warning(' FAILED!'); + p_warning(' FAILED! issuer is not cert issuer.'); } } else { - p_warning(' FAILED!.'); + p_warning(' FAILED!'); } + } else { + p_warning(' FAILED!.'); } + } - if (! $ltvResult['issuer']) { - p_debug(' search for issuer in extracerts.....'); - if (array_key_exists('extracerts', $this->signature_data) && ($this->signature_data['extracerts'] !== null) && (count($this->signature_data['extracerts']) > 0)) { - $i = 0; - foreach ($this->signature_data['extracerts'] as $extracert) { - p_debug(" extracerts[{$i}] ..."); - $certSigner_signatureField = $certSigner_parse['signatureValue']; - if (openssl_public_decrypt(hex2bin((string) $certSigner_signatureField), $decrypted, $extracert, OPENSSL_PKCS1_PADDING)) { - p_debug(' OK got issuer.'); - $certIssuer_parse = x509::readcert($extracert, 'oid'); // Parsing Issuer cert - $ltvResult['issuer'] = x509::get_cert($extracert); - } else { - p_debug(' FAIL!'); - } - $i++; + if (! $ltvResult['issuer']) { + p_debug(' search for issuer in extracerts.....'); + if (array_key_exists('extracerts', $this->signature_data) && ($this->signature_data['extracerts'] !== null) && (count($this->signature_data['extracerts']) > 0)) { + $i = 0; + foreach ($this->signature_data['extracerts'] as $extracert) { + p_debug(" extracerts[{$i}] ..."); + $certSigner_signatureField = $certSigner_parse['signatureValue']; + if (openssl_public_decrypt(hex2bin((string) $certSigner_signatureField), $decrypted, $extracert, OPENSSL_PKCS1_PADDING)) { + p_debug(' OK got issuer.'); + $certIssuer_parse = x509::readcert($extracert, 'oid'); // Parsing Issuer cert + $ltvResult['issuer'] = x509::get_cert($extracert); + } else { + p_debug(' FAIL!'); } - } else { - p_error(' FAILED! no extracerts available'); + $i++; } + } else { + throw new PDFException('FAILED! no extracerts available'); } } @@ -453,34 +448,33 @@ protected function LTVvalidation($parsedCert): false|array $nextUpdateTime = strtotime($nextUpdate); $nowz = time(); if (($nowz - $thisUpdateTime) < 0) { // 0 sec after valid - p_error(' FAILED! not yet valid! valid at ' . date('d/m/Y H:i:s', $thisUpdateTime)); - } elseif (($nextUpdateTime - $nowz) < 1) { // not accept if crl 1 sec remain to expired - p_error(' FAILED! Expired crl at ' . date('d/m/Y H:i:s', $nextUpdateTime) . ' and now ' . date('d/m/Y H:i:s', $nowz) . '!'); - } else { - p_debug(' OK CRL still valid until ' . date('d/m/Y H:i:s', $nextUpdateTime)); - $crlCertValid = true; - p_debug(' check if cert not revoked...'); - if (array_key_exists('revokedCertificates', $crl_parse['TBSCertList'])) { - $certSigner_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; - if (array_key_exists($certSigner_serialNumber, $crl_parse['TBSCertList']['revokedCertificates']['lists'])) { - $crlCertValid = false; - p_error(' FAILED! Certificate Revoked!'); - } - } - if ($crlCertValid == true) { - p_debug(' OK. VALID'); - $crlHex = current(unpack('H*', (string) $crlread['der'])); - $ltvResult['crl'] = $crlHex; + throw new PDFException('FAILED! not yet valid! valid at ' . date('d/m/Y H:i:s', $thisUpdateTime)); + } + if (($nextUpdateTime - $nowz) < 1) { // not accept if crl 1 sec remain to expired + throw new PDFException(' FAILED! Expired crl at ' . date('d/m/Y H:i:s', $nextUpdateTime) . ' and now ' . date('d/m/Y H:i:s', $nowz) . '!'); + } + p_debug(' OK CRL still valid until ' . date('d/m/Y H:i:s', $nextUpdateTime)); + $crlCertValid = true; + p_debug(' check if cert not revoked...'); + if (array_key_exists('revokedCertificates', $crl_parse['TBSCertList'])) { + $certSigner_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; + if (array_key_exists($certSigner_serialNumber, $crl_parse['TBSCertList']['revokedCertificates']['lists'])) { + throw new PDFException(' FAILED! Certificate Revoked!'); } } + if ($crlCertValid == true) { + p_debug(' OK. VALID'); + $crlHex = current(unpack('H*', (string) $crlread['der'])); + $ltvResult['crl'] = $crlHex; + } } else { - p_error(' FAILED! Wrong CRL.'); + throw new PDFException(' FAILED! Wrong CRL.'); } } else { - p_error(" FAILED! can't read crl"); + throw new PDFException(" FAILED! can't read crl"); } } else { - p_error(" FAILED! can't get crl"); + throw new PDFException(" FAILED! can't get crl"); } } } @@ -505,9 +499,7 @@ protected function LTVvalidation($parsedCert): false|array private function tsa_parseResp($binaryTsaRespData) { if (! @$ar = asn1::parse(bin2hex($binaryTsaRespData), 3)) { - p_error(" can't parse invalid tsa Response."); - - return false; + throw new PDFException(" can't parse invalid tsa Response."); } $curr = $ar; foreach ($curr as $key => $value) { @@ -559,7 +551,7 @@ private function tsa_parseResp($binaryTsaRespData) if (@$ar['TimeStampResp']['timeStampToken']['content']['hexdump'] != '') { return $ar; } - return false; + return false; } } diff --git a/src/helpers/contentgeneration.php b/src/helpers/contentgeneration.php index 1257f6a..f078cd3 100644 --- a/src/helpers/contentgeneration.php +++ b/src/helpers/contentgeneration.php @@ -174,10 +174,10 @@ function is_base64($string): bool * "alpha": true if the image has alpha * "command": pdf command to draw the image */ -function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, $angle = 0, $keep_proportions = true) +function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, $angle = 0, $keep_proportions = true): array { if (empty($filename)) { - return p_error('invalid image name or stream'); + throw new PDFException('invalid image name or stream'); } if ($filename[0] === '@') { @@ -189,7 +189,7 @@ function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, $filecontent = @file_get_contents($filename); if ($filecontent === false) { - return p_error('failed to get the image'); + throw new PDFException('failed to get the image'); } } } @@ -211,7 +211,7 @@ function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, $info = _parsepng($filecontent); break; default: - return p_error('unsupported mime type'); + throw new PDFException('unsupported mime type'); } // Generate a new identifier for the image diff --git a/src/helpers/fpdfhelpers.php b/src/helpers/fpdfhelpers.php index 91dfd53..6cf6d3c 100644 --- a/src/helpers/fpdfhelpers.php +++ b/src/helpers/fpdfhelpers.php @@ -35,15 +35,17 @@ namespace ddn\sapp\helpers; -function _parsejpg($filecontent) +use ddn\sapp\PDFException; + +function _parsejpg($filecontent): array { // Extract info from a JPEG file $a = getimagesizefromstring($filecontent); if (! $a) { - return p_error('Missing or incorrect image'); + throw new PDFException('Missing or incorrect image'); } if ($a[2] != 2) { - return p_error('Not a JPEG image'); + throw new PDFException('Not a JPEG image'); } if (! isset($a['channels']) || $a['channels'] == 3) { $colspace = 'DeviceRGB'; @@ -65,7 +67,7 @@ function _parsejpg($filecontent) ]; } -function _parsepng($filecontent) +function _parsepng($filecontent): array { // Extract info from a PNG file $f = new StreamReader($filecontent); @@ -73,23 +75,23 @@ function _parsepng($filecontent) return _parsepngstream($f); } -function _parsepngstream(&$f) +function _parsepngstream(&$f): array { // Check signature if (($res = _readstream($f, 8)) != chr(137) . 'PNG' . chr(13) . chr(10) . chr(26) . chr(10)) { - return p_error("Not a PNG image {$res}"); + throw new PDFException("Not a PNG image {$res}"); } // Read header chunk _readstream($f, 4); if (_readstream($f, 4) !== 'IHDR') { - return p_error('Incorrect PNG image'); + throw new PDFException('Incorrect PNG image'); } $w = _readint($f); $h = _readint($f); $bpc = ord(_readstream($f, 1)); if ($bpc > 8) { - return p_error('16-bit depth not supported'); + throw new PDFException('16-bit depth not supported'); } $ct = ord(_readstream($f, 1)); if ($ct == 0 || $ct == 4) { @@ -99,16 +101,16 @@ function _parsepngstream(&$f) } elseif ($ct == 3) { $colspace = 'Indexed'; } else { - return p_error('Unknown color type'); + throw new PDFException('Unknown color type'); } if (ord(_readstream($f, 1)) != 0) { - return p_error('Unknown compression method'); + throw new PDFException('Unknown compression method'); } if (ord(_readstream($f, 1)) != 0) { - return p_error('Unknown filter method'); + throw new PDFException('Unknown filter method'); } if (ord(_readstream($f, 1)) != 0) { - return p_error('Interlacing not supported'); + throw new PDFException('Interlacing not supported'); } _readstream($f, 4); $dp = '/Predictor 15 /Colors ' . ($colspace === 'DeviceRGB' ? 3 : 1) . ' /BitsPerComponent ' . $bpc . ' /Columns ' . $w; @@ -150,7 +152,7 @@ function _parsepngstream(&$f) } while ($n); if ($colspace === 'Indexed' && empty($pal)) { - return p_error('Missing palette in image'); + throw new PDFException('Missing palette in image'); } $info = [ 'w' => $w, @@ -165,11 +167,11 @@ function _parsepngstream(&$f) if ($ct >= 4) { // Extract alpha channel if (! function_exists('gzuncompress')) { - return p_error('Zlib not available, can\'t handle alpha channel'); + throw new PDFException('Zlib not available, can\'t handle alpha channel'); } $data = gzuncompress($data); if ($data === false) { - return p_error('failed to uncompress the image'); + throw new PDFException('failed to uncompress the image'); } $color = ''; $alpha = ''; @@ -210,21 +212,21 @@ function _parsepngstream(&$f) return $info; } -function _readstream($f, $n) +function _readstream($f, $n): string { $res = ''; while ($n > 0 && ! $f->eos()) { $s = $f->nextchars($n); if ($s === false) { - return p_error('Error while reading the stream'); + throw new PDFException('Error while reading the stream'); } $n -= strlen((string) $s); $res .= $s; } if ($n > 0) { - return p_error('Unexpected end of stream'); + throw new PDFException('Unexpected end of stream'); } return $res; @@ -247,12 +249,12 @@ function _readstream($f, $n) { $s = fread($f,$n); if($s===false) - return p_error('Error while reading stream'); + throw new PDFException('Error while reading stream'); $n -= strlen($s); $res .= $s; } if($n>0) - return p_error('Unexpected end of stream'); + throw new PDFException('Unexpected end of stream'); return $res; } diff --git a/src/helpers/helpers.php b/src/helpers/helpers.php index 292f690..235a944 100644 --- a/src/helpers/helpers.php +++ b/src/helpers/helpers.php @@ -161,23 +161,6 @@ function p_warning(string $e, mixed $retval = false) return $retval; } -/** - * Function that writes a string to stderr and returns a value (to ease coding like return p_error(...)) - * - * @param e the error message - * @param retval the value to return (default: false) - * - */ -function p_error(string $e, mixed $retval = false) -{ - // If the debug level is less than 1, suppress error messages - if (_DEBUG_LEVEL >= 1) { - p_stderr($e, 'Error'); - } - - return $retval; -} - /** * Obtains a random string from a printable character set: alphanumeric, extended with * common symbols, an extended with less common symbols. From 99b55a9734b7fb4e6a13469134543e8b2b050bae Mon Sep 17 00:00:00 2001 From: Marcin Jakubowski Date: Thu, 31 Oct 2024 22:54:04 +0100 Subject: [PATCH 06/11] modernization and cleanup --- src/PDFDoc.php | 22 ++++----- src/PDFDocWithContents.php | 2 +- src/PDFObject.php | 3 -- src/PDFObjectParser.php | 6 +-- src/PDFSignatureObject.php | 1 - src/PDFUtilFnc.php | 48 +++++++++--------- src/helpers/CMS.php | 74 ++++++++++++++-------------- src/helpers/DependencyTreeObject.php | 2 +- src/helpers/asn1.php | 12 ++--- src/helpers/contentgeneration.php | 2 +- src/helpers/fpdfhelpers.php | 10 ++-- src/helpers/x509.php | 53 ++++++++------------ src/pdfvalue/PDFValueObject.php | 1 - src/pdfvalue/PDFValueSimple.php | 7 +-- 14 files changed, 110 insertions(+), 133 deletions(-) diff --git a/src/PDFDoc.php b/src/PDFDoc.php index fe37edb..1fe2a60 100644 --- a/src/PDFDoc.php +++ b/src/PDFDoc.php @@ -360,7 +360,7 @@ public function set_signature_certificate($certfile, ?string $certpass = null): if (is_string($certificate['extracerts'] ?? null)) { $certificate['extracerts'] = array_filter(explode("-----END CERTIFICATE-----\n", $certificate['extracerts'])); foreach ($certificate['extracerts'] as &$extracerts) { - $extracerts = $extracerts . "-----END CERTIFICATE-----\n"; + $extracerts .= "-----END CERTIFICATE-----\n"; } } } else { @@ -382,11 +382,11 @@ public function set_signature_certificate($certfile, ?string $certpass = null): /** * Function that stores the ltv configuration to use, when signing the document * - * @param $ocspURI OCSP Url to validate cert file - * @param $crlURIorFILE Crl filename/url to validate cert - * @param $issuerURIorFILE issuer filename/url + * @param $ocspURI OCSP|null Url to validate cert file + * @param $crlURIorFILE Crl|null filename/url to validate cert + * @param $issuerURIorFILE issuer|null filename/url */ - public function set_ltv($ocspURI = null, $crlURIorFILE = null, $issuerURIorFILE = null): void + public function set_ltv(OCSP $ocspURI = null, Crl $crlURIorFILE = null, issuer $issuerURIorFILE = null): void { $this->_signature_ltv_data['ocspURI'] = $ocspURI; $this->_signature_ltv_data['crlURIorFILE'] = $crlURIorFILE; @@ -397,10 +397,10 @@ public function set_ltv($ocspURI = null, $crlURIorFILE = null, $issuerURIorFILE * Function that stores the tsa configuration to use, when signing the document * * @param $tsaurl Link to tsa service - * @param $tsauser the user for tsa service - * @param $tsapass the password for tsa service + * @param $tsauser the|null user for tsa service + * @param $tsapass the|null password for tsa service */ - public function set_tsa($tsa, $tsauser = null, $tsapass = null): void + public function set_tsa($tsa, the $tsauser = null, the $tsapass = null): void { $this->_signature_tsa['host'] = $tsa; if ($tsauser && $tsapass) { @@ -970,8 +970,8 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, $p_y = (int) $pagesize[1]; // Add the position for the image - $p_x = $p_x + $px; - $p_y = $p_y + $py; + $p_x += $px; + $p_y += $py; $i_w = $imagesize[0]; $i_h = $imagesize[1]; @@ -1266,7 +1266,7 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false * * @return ok true if the date could be set; false otherwise */ - protected function update_mod_date(DateTime $date = null): bool + protected function update_mod_date(?DateTime $date = null): bool { // First of all, we are searching for the root object (which should be in the trailer) $root = $this->_pdf_trailer_object['Root']; diff --git a/src/PDFDocWithContents.php b/src/PDFDocWithContents.php index 309e04b..ca4ab4b 100644 --- a/src/PDFDocWithContents.php +++ b/src/PDFDocWithContents.php @@ -188,7 +188,7 @@ public function add_image($page_obj, $filename, $x = 0, $y = 0, $w = 0, $h = 0): $pagesize = $this->get_page_size($page_obj); $pagesize_h = (float) ('' . $pagesize[3]) - (float) ('' . $pagesize[1]); - $result = _add_image($filename, $x, $pagesize_h - $y, $w, $h); + _add_image($filename, $x, $pagesize_h - $y, $w, $h); throw new PDFException('this function still needs work'); // Get the resources for the page diff --git a/src/PDFObject.php b/src/PDFObject.php index f5232de..4d5008a 100644 --- a/src/PDFObject.php +++ b/src/PDFObject.php @@ -294,14 +294,11 @@ protected static function FlateDecode($_stream, array $params) $decoded = new Buffer(); $columns = $params['Columns']->get_int(); - $row_len = $columns + 1; $stream_len = strlen((string) $_stream); // The previous row is zero $data_prev = str_pad('', $columns, chr(0)); - $row_i = 0; $pos_i = 0; - $data = str_pad('', $columns, chr(0)); while ($pos_i < $stream_len) { $filter_byte = ord($_stream[$pos_i++]); diff --git a/src/PDFObjectParser.php b/src/PDFObjectParser.php index 3b8a711..5b7f21e 100644 --- a/src/PDFObjectParser.php +++ b/src/PDFObjectParser.php @@ -136,7 +136,7 @@ public function current_token() /** * Parses the document */ - public function parse(StreamReader &$stream): PDFValue|false|null + public function parse(StreamReader $stream): PDFValue|false|null { // $str, $offset = 0) { $this->start($stream); //$str, $offset); $this->nexttoken(); @@ -144,7 +144,7 @@ public function parse(StreamReader &$stream): PDFValue|false|null return $this->_parse_value(); } - public function parsestr($str, int $offset = 0): PDFValue|false|null + public function parsestr(string $str, int $offset = 0): PDFValue|false|null { $stream = new StreamReader($str); $stream->goto($offset); @@ -204,7 +204,7 @@ protected function _c_is_separator(): bool { $DSEPS = ['<<', '>>']; - return (($this->_c === false) || (str_contains("%<>()[]{}/ \n\r\t", (string) $this->_c)) || ((array_search($this->_c . $this->_n, $DSEPS)) !== false)); + return (($this->_c === false) || (str_contains("%<>()[]{}/ \n\r\t", (string) $this->_c)) || (in_array($this->_c . $this->_n, $DSEPS) !== false)); } /** diff --git a/src/PDFSignatureObject.php b/src/PDFSignatureObject.php index 1372462..a4db47e 100644 --- a/src/PDFSignatureObject.php +++ b/src/PDFSignatureObject.php @@ -80,7 +80,6 @@ public function set_certificate($certificate): void public function set_signature_ltv($signature_ltv_data): void { throw new PDFException(get_debug_type($signature_ltv_data)); - $this->_signature_ltv_data = $signature_ltv_data; } public function set_signature_tsa($signature_tsa): void diff --git a/src/PDFUtilFnc.php b/src/PDFUtilFnc.php index 0b34d69..0f5485d 100644 --- a/src/PDFUtilFnc.php +++ b/src/PDFUtilFnc.php @@ -35,7 +35,7 @@ class PDFUtilFnc { - public static function get_trailer(&$_buffer, int $trailer_pos): PDFValue|false|null + public static function get_trailer($_buffer, int $trailer_pos): PDFValue|false|null { // Search for the trailer structure if (preg_match('/trailer\s*(.*)\s*startxref/ms', (string) $_buffer, $matches, 0, $trailer_pos) !== 1) { @@ -122,7 +122,7 @@ public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null return false; } - $depth = $depth - 1; + --$depth; } $xref_o = self::find_object_at_pos($_buffer, null, $xref_pos, []); @@ -163,7 +163,6 @@ public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null } // Get the previous xref table, to build up on it - $trailer_obj = null; $xref_table = []; if (($depth === null) || ($depth > 0)) { @@ -177,7 +176,7 @@ public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null } // When dealing with 1.5 cross references, we do not allow to use other than cross references - [$xref_table, $trailer_obj] = self::get_xref_1_5($_buffer, $Prev, $depth); + [$xref_table] = self::get_xref_1_5($_buffer, $Prev, $depth); // p_debug_var($xref_table); } } @@ -186,17 +185,17 @@ public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null $stream_v = new StreamReader($stream); // Get the format function to un pack the values - $get_fmt_function = function ($f) { + $get_fmt_function = static function ($f) { if ($f === false) { return false; } return match ($f) { - 0 => fn ($v): int => 0, - 1 => fn ($v) => unpack('C', str_pad($v, 1, chr(0), STR_PAD_LEFT))[1], - 2 => fn ($v) => unpack('n', str_pad($v, 2, chr(0), STR_PAD_LEFT))[1], - 3, 4 => fn ($v) => unpack('N', str_pad($v, 4, chr(0), STR_PAD_LEFT))[1], - 5, 6, 7, 8 => fn ($v) => unpack('J', str_pad($v, 8, chr(0), STR_PAD_LEFT))[1], + 0 => static fn ($v): int => 0, + 1 => static fn ($v) => unpack('C', str_pad($v, 1, chr(0), STR_PAD_LEFT))[1], + 2 => static fn ($v) => unpack('n', str_pad($v, 2, chr(0), STR_PAD_LEFT))[1], + 3, 4 => static fn ($v) => unpack('N', str_pad($v, 4, chr(0), STR_PAD_LEFT))[1], + 5, 6, 7, 8 => static fn ($v) => unpack('J', str_pad($v, 8, chr(0), STR_PAD_LEFT))[1], default => false, }; }; @@ -268,7 +267,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null): false| return false; } - $depth = $depth - 1; + --$depth; } $trailer_pos = strpos((string) $_buffer, 'trailer', $xref_pos); @@ -334,7 +333,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null): false| // in the actual offset. // TODO: consider creating a "generation table" $xref_table[$obj_id] = $obj_offset; - if ($obj_generation != 0) { + if ($obj_generation !== 0) { p_warning('Objects of non-zero generation are not fully checked... please double check your document and (if possible) please send examples via issues to https://github.com/dealfonso/sapp/issues/'); } break; @@ -344,7 +343,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null): false| } } - --$obj_count; + $obj_count--; $obj_id++; continue; } @@ -387,7 +386,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null): false| public static function get_xref(string &$_buffer, ?int $xref_pos, ?int $depth = null): array { // Each xref is immediately followed by a trailer - $trailer_pos = strpos((string) $_buffer, 'trailer', $xref_pos); + $trailer_pos = strpos($_buffer, 'trailer', $xref_pos); if ($trailer_pos === false) { [$xref_table, $trailer_obj, $min_pdf_version] = self::get_xref_1_5($_buffer, $xref_pos, $depth); } else { @@ -410,7 +409,7 @@ public static function acquire_structure(string &$_buffer, ?int $depth = null): throw new PDFException('PDF version string not found'); } - if (preg_match_all('/startxref\s*([0-9]+)\s*%%EOF($|[\r\n])/ms', (string) $_buffer, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE) === false) { + if (preg_match_all('/startxref\s*([0-9]+)\s*%%EOF($|[\r\n])/ms', $_buffer, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE) === false) { throw new PDFException('failed to get structure'); } @@ -424,12 +423,12 @@ public static function acquire_structure(string &$_buffer, ?int $depth = null): } // Now get the trailing part and make sure that it has the proper form - $startxref_pos = strrpos((string) $_buffer, 'startxref'); + $startxref_pos = strrpos($_buffer, 'startxref'); if ($startxref_pos === false) { throw new PDFException('startxref not found'); } - if (preg_match('/startxref\s*([0-9]+)\s*%%EOF\s*$/ms', (string) $_buffer, $matches, 0, $startxref_pos) !== 1) { + if (preg_match('/startxref\s*([0-9]+)\s*%%EOF\s*$/ms', $_buffer, $matches, 0, $startxref_pos) !== 1) { throw new PDFException('startxref and %%EOF not found'); } @@ -582,7 +581,7 @@ public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm } $First = $First->get_int(); - $N = $N->get_int(); + assert($N instanceof PDFValue); $stream = $objstm->get_stream(false); $index = substr((string) $stream, 0, $First); @@ -593,15 +592,14 @@ public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm throw new PDFException("invalid index for object stream {$objstm_oid}"); } - $objpos = $objpos * 2; + $objpos *= 2; if ($objpos > count($index)) { throw new PDFException("object {$oid} not found in object stream {$objstm_oid}"); } $offset = (int) $index[$objpos + 1]; - $next = 0; $offsets = []; - for ($i = 1; ($i < count($index)); $i = $i + 2) { + for ($i = 1; ($i < count($index)); $i += 2) { $offsets[] = (int) $index[$i]; } @@ -622,7 +620,7 @@ public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm */ public static function object_from_string(string $buffer, ?int $expected_obj_id, int $offset = 0, ?int &$offset_end = 0): PDFObject { - if (preg_match('/([0-9]+)\s+([0-9+])\s+obj(\s+)/ms', (string) $buffer, $matches, 0, $offset) !== 1) { + if (preg_match('/([0-9]+)\s+([0-9+])\s+obj(\s+)/ms', $buffer, $matches, 0, $offset) !== 1) { // p_debug_var(substr($buffer)) throw new PDFException("object is not valid: {$expected_obj_id}"); } @@ -640,7 +638,7 @@ public static function object_from_string(string $buffer, ?int $expected_obj_id, } // The object starts after the header - $offset = $offset + strlen($found_obj_header); + $offset += strlen($found_obj_header); // Parse the object $parser = new PDFObjectParser(); @@ -693,7 +691,7 @@ public static function build_xref(array $offsets): string if ($k[$i] === $c_k + 1) { $count++; } else { - $result = $result . "{$i_k} {$count}\n{$references}"; + $result .= "{$i_k} {$count}\n{$references}"; $count = 1; $i_k = $k[$i]; $references = ''; @@ -701,7 +699,7 @@ public static function build_xref(array $offsets): string $references .= sprintf("%010d 00000 n \n", $offsets[$k[$i]]); $c_k = $k[$i]; } - $result = $result . "{$i_k} {$count}\n{$references}"; + $result .= "{$i_k} {$count}\n{$references}"; return "xref\n{$result}"; } diff --git a/src/helpers/CMS.php b/src/helpers/CMS.php index 0a0cfe0..a7b89de 100644 --- a/src/helpers/CMS.php +++ b/src/helpers/CMS.php @@ -28,7 +28,7 @@ class CMS * @return string response body * @public */ - public function sendReq(array $reqData) + public function sendReq(array $reqData): string { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $reqData['uri']); @@ -42,40 +42,41 @@ public function sendReq(array $reqData) } $tsResponse = curl_exec($ch); - if ($tsResponse) { - $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); - curl_close($ch); - $header = substr($tsResponse, 0, $header_size); - $body = substr($tsResponse, $header_size); - // Get the HTTP response code - $headers = explode("\n", $header); - foreach ($headers as $r) { - if (stripos($r, 'HTTP/') === 0) { - [, $code, $status] = explode(' ', $r, 3); - break; - } - } - if ($code != '200') { - throw new PDFException(sprintf('response error! Code="{$code}", Status="%s"', trim($status ?? ''))); - } - $contentTypeHeader = ''; - $headers = explode("\n", $header); - foreach ($headers as $r) { - // Match the header name up to ':', compare lower case - if (stripos($r, 'Content-Type' . ':') === 0) { - [, $headervalue] = explode(':', $r, 2); - $contentTypeHeader = trim($headervalue); - } - } - if ($contentTypeHeader != $reqData['resp_contentType']) { - throw new PDFException("response content type not {$reqData['resp_contentType']}, but: \"{$contentTypeHeader}\""); + if (! $tsResponse) { + throw new PDFException('empty curl response'); + } + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + curl_close($ch); + $header = substr($tsResponse, 0, $header_size); + $body = substr($tsResponse, $header_size); + // Get the HTTP response code + $headers = explode("\n", $header); + foreach ($headers as $r) { + if (stripos($r, 'HTTP/') === 0) { + [, $code, $status] = explode(' ', $r, 3); + break; } - if (empty($body)) { - throw new PDFException('error empty response!'); + } + if ($code != '200') { + throw new PDFException(sprintf('response error! Code="{$code}", Status="%s"', trim($status ?? ''))); + } + $contentTypeHeader = ''; + $headers = explode("\n", $header); + foreach ($headers as $r) { + // Match the header name up to ':', compare lower case + if (stripos($r, 'Content-Type' . ':') === 0) { + [, $headervalue] = explode(':', $r, 2); + $contentTypeHeader = trim($headervalue); } - - return $body; // binary response } + if ($contentTypeHeader != $reqData['resp_contentType']) { + throw new PDFException("response content type not {$reqData['resp_contentType']}, but: \"{$contentTypeHeader}\""); + } + if (empty($body)) { + throw new PDFException('error empty response!'); + } + + return $body; // binary response } /** @@ -86,7 +87,7 @@ public function sendReq(array $reqData) * @return string hex + padding 0 * @public */ - public function pkcs7_sign($binaryData) + public function pkcs7_sign(string $binaryData): string { $hexOidHashAlgos = [ 'md2' => '06082A864886F70D0202', @@ -268,9 +269,8 @@ public function pkcs7_sign($binaryData) * * @return string hex TSTinfo. */ - protected function createTimestamp($data, string $hashAlg = 'sha1') + protected function createTimestamp(string $data, string $hashAlg = 'sha1') { - $TSTInfo = false; $tsaQuery = x509::tsa_query($data, $hashAlg); $tsaData = $this->signature_data['tsa']; $reqData = [ @@ -306,7 +306,7 @@ protected function createTimestamp($data, string $hashAlg = 'sha1') * * @return array */ - protected function LTVvalidation($parsedCert): false|array + protected function LTVvalidation(array $parsedCert): false|array { $ltvResult['issuer'] = false; $ltvResult['ocsp'] = false; @@ -496,7 +496,7 @@ protected function LTVvalidation($parsedCert): false|array * * @return array asn.1 hex structure of tsa response */ - private function tsa_parseResp($binaryTsaRespData) + private function tsa_parseResp(string $binaryTsaRespData) { if (! @$ar = asn1::parse(bin2hex($binaryTsaRespData), 3)) { throw new PDFException(" can't parse invalid tsa Response."); diff --git a/src/helpers/DependencyTreeObject.php b/src/helpers/DependencyTreeObject.php index e0ea1ab..62df2ef 100644 --- a/src/helpers/DependencyTreeObject.php +++ b/src/helpers/DependencyTreeObject.php @@ -58,7 +58,7 @@ public function addchild(int $oid, self $o): void p_warning("object {$o->oid} is already a child of other object"); } - $o->is_child = $o->is_child + 1; + ++$o->is_child; } /** diff --git a/src/helpers/asn1.php b/src/helpers/asn1.php index efeeacf..b5f5254 100644 --- a/src/helpers/asn1.php +++ b/src/helpers/asn1.php @@ -21,7 +21,7 @@ public static function __callStatic($func, $params) $func = strtolower((string) $func); $asn1Tag = self::asn1Tag($func); if ($asn1Tag !== false) { - $num = $asn1Tag; //valu of array + $num = $asn1Tag; //value of array $hex = $params[0]; $val = $hex; if (in_array($func, ['printable', 'utf8', 'ia5', 'visible', 't61'])) { // ($string) @@ -31,7 +31,7 @@ public static function __callStatic($func, $params) $val = (strlen((string) $val) % 2 !== 0) ? "0{$val}" : (string) ($val); } if ($func === 'expl') { //expl($num, $hex) - $num = $num . $params[0]; + $num .= $params[0]; $val = $params[1]; } if ($func === 'impl') { //impl($num="0") @@ -86,7 +86,7 @@ public static function __callStatic($func, $params) * * @return array asn.1 structure recursively to specific depth */ - public static function parse($hex, $maxDepth = 5): array + public static function parse(string $hex, int $maxDepth = 5): array { $result = []; static $currentDepth = 0; @@ -135,7 +135,7 @@ public static function parse($hex, $maxDepth = 5): array * * @return string asn.1 tag name */ - protected static function type($id) + protected static function type(string $id): string { $asn1_Types = [ '00' => 'ASN1_EOC', @@ -177,7 +177,7 @@ protected static function type($id) * * @return array asn.1 structure */ - protected static function oneParse($hex): array|false + protected static function oneParse(string $hex): array|false { if ($hex === '') { return false; @@ -234,7 +234,7 @@ protected static function oneParse($hex): array|false * * @return string hex of asn.1 TLV tag length */ - protected static function asn1_header($str): string + protected static function asn1_header(string $str): string { $len = strlen($str) / 2; $ret = dechex($len); diff --git a/src/helpers/contentgeneration.php b/src/helpers/contentgeneration.php index f078cd3..0f55cfe 100644 --- a/src/helpers/contentgeneration.php +++ b/src/helpers/contentgeneration.php @@ -174,7 +174,7 @@ function is_base64($string): bool * "alpha": true if the image has alpha * "command": pdf command to draw the image */ -function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, $angle = 0, $keep_proportions = true): array +function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, $angle = 0, bool $keep_proportions = true): array { if (empty($filename)) { throw new PDFException('invalid image name or stream'); diff --git a/src/helpers/fpdfhelpers.php b/src/helpers/fpdfhelpers.php index 6cf6d3c..a567c9d 100644 --- a/src/helpers/fpdfhelpers.php +++ b/src/helpers/fpdfhelpers.php @@ -130,11 +130,11 @@ function _parsepngstream(&$f): array // Read transparency info $t = _readstream($f, $n); if ($ct == 0) { - $trns = [ord(substr((string) $t, 1, 1))]; + $trns = [ord($t[1])]; } elseif ($ct == 2) { - $trns = [ord(substr((string) $t, 1, 1)), ord(substr((string) $t, 3, 1)), ord(substr((string) $t, 5, 1))]; + $trns = [ord($t[1]), ord($t[3]), ord($t[5])]; } else { - $pos = strpos((string) $t, chr(0)); + $pos = strpos($t, chr(0)); if ($pos !== false) { $trns = [$pos]; } @@ -232,10 +232,10 @@ function _readstream($f, $n): string return $res; } -function _readint(&$f) +function _readint($f) { // Read a 4-byte integer from stream - $a = unpack('Ni', (string) _readstream($f, 4)); + $a = unpack('Ni', _readstream($f, 4)); return $a['i']; } diff --git a/src/helpers/x509.php b/src/helpers/x509.php index 6d8ad9f..9ef1ef2 100644 --- a/src/helpers/x509.php +++ b/src/helpers/x509.php @@ -60,7 +60,7 @@ public static function tsa_query($binaryData, $hashAlg = 'sha256'): false|string * * @return array ocsp response structure */ - public static function ocsp_response_parse($binaryOcspResp, &$status = '') + public static function ocsp_response_parse(string $binaryOcspResp, &$status = '') { $hex = current(unpack('H*', $binaryOcspResp)); $parse = asn1::parse($hex, 10); @@ -300,15 +300,9 @@ public static function ocsp_response_parse($binaryOcspResp, &$status = '') if (count($differ) == 0) { $differ = array_diff_key($arrModel['responseBytes'], $ocsp['responseBytes']); if (count($differ) > 0) { - foreach ($differ as $key => $val) { - } - return false; } } else { - foreach ($differ as $key => $val) { - } - return false; } @@ -321,15 +315,14 @@ public static function ocsp_response_parse($binaryOcspResp, &$status = '') * @param string $serialNumber serial number to check * @param string $issuerNameHash sha1 hex form of issuer subject hash * @param string $issuerKeyHash sha1 hex form of issuer subject public info hash - * @param string $signer_cert cert to sign ocsp request - * @param string $signer_key privkey to sign ocsp request - * @param string $subjectName hex form of asn1 subject + * @param bool|string $signer_cert cert to sign ocsp request + * @param bool|string $signer_key privkey to sign ocsp request + * @param bool|string $subjectName hex form of asn1 subject * * @return string hex form ocsp request */ - public static function ocsp_request($serialNumber, $issuerNameHash, $issuerKeyHash, $signer_cert = false, $signer_key = false, $subjectName = false) + public static function ocsp_request(string $serialNumber, string $issuerNameHash, string $issuerKeyHash, bool|string $signer_cert = false, bool|string $signer_key = false, bool|string $subjectName = false) { - $Request = false; $hashAlgorithm = asn1::seq( '06052B0E03021A' . // OBJ_sha1 '0500' @@ -378,7 +371,7 @@ public static function ocsp_request($serialNumber, $issuerNameHash, $issuerKeyHa * * @return string der crl form */ - public static function crl_pem2der($crl): false|string + public static function crl_pem2der(string $crl): false|string { $begin = '-----BEGIN X509 CRL-----'; $end = '-----END X509 CRL-----'; @@ -404,7 +397,7 @@ public static function crl_pem2der($crl): false|string * * @return array der crl and parsed crl */ - public static function crl_read($crl): false|array + public static function crl_read(string $crl): false|array { if (! $crlparse = self::parsecrl($crl)) { // if cant read, thats not crl return false; @@ -425,7 +418,7 @@ public static function crl_read($crl): false|array * * @return string der form cert */ - public static function x509_pem2der($pem): string|false + public static function x509_pem2der(string $pem): string|false { $x509_der = false; if ($x509_res = @openssl_x509_read($pem)) { @@ -453,7 +446,7 @@ public static function x509_pem2der($pem): string|false * * @return string pem form cert */ - public static function x509_der2pem($der_cert): string + public static function x509_der2pem(string $der_cert): string { $x509_pem = "-----BEGIN CERTIFICATE-----\r\n"; $x509_pem .= chunk_split(base64_encode($der_cert), 64); @@ -469,7 +462,7 @@ public static function x509_der2pem($der_cert): string * * @return string der form cert */ - public static function get_cert($certin): string|false + public static function get_cert(string $certin): string|false { if ($rsccert = @openssl_x509_read($certin)) { openssl_x509_export($rsccert, $cert); @@ -490,11 +483,11 @@ public static function get_cert($certin): string|false * parse x.509 DER/PEM Certificate structure * * @param string $certin pem/der form cert - * @param string $oidprint show oid as oid number or hex + * @param bool|string $oidprint show oid as oid number or hex * * @return array cert structure */ - public static function readcert($cert_in, $oidprint = false) + public static function readcert($cert_in, bool|string $oidprint = false) { if (! $der = self::get_cert($cert_in)) { return false; @@ -530,7 +523,6 @@ public static function readcert($cert_in, $oidprint = false) $ar['cert'] = $curr; $ar['cert']['sha1Fingerprint'] = hash('sha1', $der); $curr = $ar['cert']['tbsCertificate']; - $i = 0; foreach ($curr as $key => $value) { if (is_numeric($key)) { if ($value['type'] === 'a0') { @@ -574,7 +566,6 @@ public static function readcert($cert_in, $oidprint = false) continue; } if ($value['type'] == '30' && ! array_key_exists('subject', $curr)) { - $asn1SubjectToHash = ''; foreach ($value as $subjectK => $subjectV) { if (is_numeric($subjectK)) { $subjectOID = $subjectV[0][0]['value_hex']; @@ -620,7 +611,6 @@ public static function readcert($cert_in, $oidprint = false) $curr['attributes'] = $value[0]; unset($curr[$key]); } - $i++; } else { $tbsCertificateTag[$key] = $value; } @@ -634,7 +624,6 @@ public static function readcert($cert_in, $oidprint = false) $critical = 0; $extvalue = $value[1]; $name_hex = $value[0]['value_hex']; - $value_hex = $value[1]['hexdump']; if ($value[1]['type'] == '01' && $value[1]['value_hex'] === 'ff') { $critical = 1; $extvalue = $value[2]; @@ -743,7 +732,7 @@ public static function readcert($cert_in, $oidprint = false) * * @return array subject hash old and new */ - private static function opensslSubjHash($hex_subjSequence): array + private static function opensslSubjHash(string $hex_subjSequence): array { $parse = asn1::parse($hex_subjSequence, 3); $hex_subjSequence_new = ''; @@ -760,14 +749,14 @@ private static function opensslSubjHash($hex_subjSequence): array $tohash = pack('H*', $hex_subjSequence_new); $openssl_subjHash_new = hash('sha1', $tohash); $openssl_subjHash_new = substr($openssl_subjHash_new, 0, 8); - $openssl_subjHash_new = str_split($openssl_subjHash_new, 2); - $openssl_subjHash_new = array_reverse($openssl_subjHash_new); - $openssl_subjHash_new = implode('', $openssl_subjHash_new); + $openssl_subjHash_new2 = str_split($openssl_subjHash_new, 2); + $openssl_subjHash_new2 = array_reverse($openssl_subjHash_new2); + $openssl_subjHash_new = implode('', $openssl_subjHash_new2); $openssl_subjHash_old = hash('md5', hex2bin($hex_subjSequence)); $openssl_subjHash_old = substr($openssl_subjHash_old, 0, 8); - $openssl_subjHash_old = str_split($openssl_subjHash_old, 2); - $openssl_subjHash_old = array_reverse($openssl_subjHash_old); - $openssl_subjHash_old = implode('', $openssl_subjHash_old); + $openssl_subjHash_old2 = str_split($openssl_subjHash_old, 2); + $openssl_subjHash_old2 = array_reverse($openssl_subjHash_old2); + $openssl_subjHash_old = implode('', $openssl_subjHash_old2); return [ 'old' => $openssl_subjHash_old, @@ -960,7 +949,7 @@ private static function parsecrl(array $crl, bool $oidprint = false): false|arra return false; } } else { - foreach ($differ as $key => $val) { + foreach ($differ as $val) { } return false; @@ -976,7 +965,7 @@ private static function parsecrl(array $crl, bool $oidprint = false): false|arra * * @return string oid number */ - private static function oidfromhex($hex): string + private static function oidfromhex(string $hex): string { $split = str_split($hex, 2); $i = 0; diff --git a/src/pdfvalue/PDFValueObject.php b/src/pdfvalue/PDFValueObject.php index e5342c7..3e3b90a 100644 --- a/src/pdfvalue/PDFValueObject.php +++ b/src/pdfvalue/PDFValueObject.php @@ -116,7 +116,6 @@ public static function fromstring($str): false|self { $result = []; $field = null; - $value = null; $parts = explode(' ', (string) $str); for ($i = 0, $iMax = count($parts); $i < $iMax; $i++) { if ($field === null) { diff --git a/src/pdfvalue/PDFValueSimple.php b/src/pdfvalue/PDFValueSimple.php index 63e6b19..048c8b7 100644 --- a/src/pdfvalue/PDFValueSimple.php +++ b/src/pdfvalue/PDFValueSimple.php @@ -23,16 +23,11 @@ class PDFValueSimple extends PDFValue { - public function __construct($v) - { - parent::__construct($v); - } - public function push($v): bool { if ($v::class === static::class) { // Can push - $this->value = $this->value . ' ' . $v->val(); + $this->value .= ' ' . $v->val(); return true; } From 4c65c488a49ca218170f6023a8252729c982e602 Mon Sep 17 00:00:00 2001 From: Marcin Jakubowski Date: Thu, 31 Oct 2024 23:10:35 +0100 Subject: [PATCH 07/11] modernization and cleanup --- pdfcompare.php | 2 +- pdfdeflate.php | 4 +- pdfrebuild.php | 4 +- pdfsign.php | 2 +- pdfsigni.php | 6 +- pdfsignlts.php | 2 +- pdfsigntsa.php | 2 +- pdfsignx.php | 2 +- phpstan.neon.dist | 2 +- rector.php | 2 + src/PDFDoc.php | 140 ++++++++++--------- src/PDFDocWithContents.php | 11 +- src/PDFObject.php | 14 +- src/PDFObjectParser.php | 42 ++++-- src/PDFSignatureObject.php | 11 +- src/PDFUtilFnc.php | 127 +++++++++-------- src/helpers/Buffer.php | 5 +- src/helpers/CMS.php | 200 +++++++++++++++------------ src/helpers/DependencyTreeObject.php | 39 +++--- src/helpers/LoadHelpers.php | 2 +- src/helpers/StreamReader.php | 2 +- src/helpers/UUID.php | 11 +- src/helpers/asn1.php | 63 +++++---- src/helpers/contentgeneration.php | 29 ++-- src/helpers/fpdfhelpers.php | 28 +++- src/helpers/helpers.php | 15 +- src/helpers/x509.php | 169 +++++++++++++++++----- src/pdfvalue/PDFValue.php | 18 ++- src/pdfvalue/PDFValueList.php | 14 +- src/pdfvalue/PDFValueObject.php | 28 ++-- 30 files changed, 609 insertions(+), 387 deletions(-) diff --git a/pdfcompare.php b/pdfcompare.php index 9b33bc0..e38eb9c 100644 --- a/pdfcompare.php +++ b/pdfcompare.php @@ -25,7 +25,7 @@ use ddn\sapp\pdfvalue\PDFValueObject; use function ddn\sapp\helpers\p_error; -require_once('vendor/autoload.php'); +require_once 'vendor/autoload.php'; if ($argc !== 3) fwrite(STDERR, sprintf("usage: %s ", $argv[0])); diff --git a/pdfdeflate.php b/pdfdeflate.php index 34c457f..46890c6 100644 --- a/pdfdeflate.php +++ b/pdfdeflate.php @@ -21,9 +21,9 @@ use ddn\sapp\PDFDoc; -require_once('vendor/autoload.php'); +require_once 'vendor/autoload.php'; -if (($argc < 2) or ($argc > 3)) +if ($argc < 2 or $argc > 3) fwrite(STDERR, sprintf("usage: %s [oid]", $argv[0])); else { if (!file_exists($argv[1])) diff --git a/pdfrebuild.php b/pdfrebuild.php index 609828f..0ec0bcd 100644 --- a/pdfrebuild.php +++ b/pdfrebuild.php @@ -21,9 +21,9 @@ use ddn\sapp\PDFDoc; -require_once('vendor/autoload.php'); +require_once 'vendor/autoload.php'; -if (($argc < 2) || ($argc > 3)) +if ($argc < 2 || $argc > 3) fwrite(STDERR, sprintf("usage: %s []", $argv[0])); else { if (!file_exists($argv[1])) diff --git a/pdfsign.php b/pdfsign.php index ed7db05..6b61416 100644 --- a/pdfsign.php +++ b/pdfsign.php @@ -21,7 +21,7 @@ use ddn\sapp\PDFDoc; -require_once('vendor/autoload.php'); +require_once 'vendor/autoload.php'; if ($argc !== 3) fwrite(STDERR, sprintf("usage: %s ", $argv[0])); diff --git a/pdfsigni.php b/pdfsigni.php index 39c5a1d..bd3da0e 100755 --- a/pdfsigni.php +++ b/pdfsigni.php @@ -24,7 +24,7 @@ use function ddn\sapp\helpers\p_error; -require_once('vendor/autoload.php'); +require_once 'vendor/autoload.php'; if ($argc !== 4) { fwrite(STDERR, sprintf("usage: %s ", $argv[0])); @@ -83,8 +83,8 @@ $ratio_y = $p_h / $i_h; $ratio = min($ratio_x, $ratio_y); -$i_w = ($i_w * $ratio) / 3; -$i_h = ($i_h * $ratio) / 3; +$i_w = $i_w * $ratio / 3; +$i_h = $i_h * $ratio / 3; $p_x = $p_w / 3; $p_y = $p_h / 3; // Set the image appearance and the certificate file diff --git a/pdfsignlts.php b/pdfsignlts.php index f5e9692..0b1ebb6 100644 --- a/pdfsignlts.php +++ b/pdfsignlts.php @@ -22,7 +22,7 @@ use ddn\sapp\PDFDoc; -require_once('vendor/autoload.php'); +require_once 'vendor/autoload.php'; if ($argc < 3) fwrite(STDERR, sprintf("usage: %s \n diff --git a/pdfsigntsa.php b/pdfsigntsa.php index e4e848c..1c56893 100644 --- a/pdfsigntsa.php +++ b/pdfsigntsa.php @@ -18,7 +18,7 @@ use ddn\sapp\PDFDoc; -require_once('vendor/autoload.php'); +require_once 'vendor/autoload.php'; if ($argc < 3) fwrite(STDERR, sprintf("usage: %s \n diff --git a/pdfsignx.php b/pdfsignx.php index 0150e3b..b401aeb 100755 --- a/pdfsignx.php +++ b/pdfsignx.php @@ -22,7 +22,7 @@ use ddn\sapp\PDFDoc; -require_once('vendor/autoload.php'); +require_once 'vendor/autoload.php'; if ($argc !== 4) fwrite(STDERR, sprintf("usage: %s ", $argv[0])); diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 876584b..02fe261 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -12,7 +12,7 @@ parameters: ignoreErrors: - "#Implicit array creation is not allowed#" - - "#Call to an undefined static method .*asn1::#" +# - "#Call to an undefined static method .*asn1::#" - "#Construct empty\\(\\) is not allowed. Use more strict comparison.#" # - # identifier: missingType.iterableValue diff --git a/rector.php b/rector.php index 7f7f29d..fbedc1c 100644 --- a/rector.php +++ b/rector.php @@ -16,4 +16,6 @@ // ->withDeadCodeLevel(1) // ->withPreparedSets(deadCode: true, codeQuality: true, codingStyle: true, typeDeclarations: true) ->withPreparedSets(typeDeclarations: true) + ->withPreparedSets(codeQuality: true) + ->withPreparedSets(codingStyle: true) ; diff --git a/src/PDFDoc.php b/src/PDFDoc.php index 1fe2a60..6881963 100644 --- a/src/PDFDoc.php +++ b/src/PDFDoc.php @@ -117,6 +117,7 @@ public function push_state(): void foreach ($this->_pdf_objects as $oid => $object) { $cloned_objects[$oid] = clone $object; } + $this->_backup_state[] = [ 'max_oid' => $this->_max_oid, 'pdf_objects' => $cloned_objects, @@ -172,10 +173,9 @@ public static function from_string(string $buffer, ?int $depth = null): false|se $pdfdoc->_revisions = $revisions; $pdfdoc->_buffer = $buffer; - if ($trailer !== false) { - if ($trailer['Encrypt'] !== false) { // TODO: include encryption (maybe borrowing some code: http://www.fpdf.org/en/script/script37.php) - throw new PDFException('encrypted documents are not fully supported; maybe you cannot get the expected results'); - } + if ($trailer !== false && $trailer['Encrypt'] !== false) { + // TODO: include encryption (maybe borrowing some code: http://www.fpdf.org/en/script/script37.php) + throw new PDFException('encrypted documents are not fully supported; maybe you cannot get the expected results'); } $oids = array_keys($xref_table); @@ -196,6 +196,7 @@ public function get_revision($rev_i): string if ($rev_i === null) { $rev_i = count($this->_revisions) - 1; } + if ($rev_i < 0) { $rev_i = count($this->_revisions) + $rev_i - 1; } @@ -282,7 +283,7 @@ public function get_indirect_object($reference) */ public function get_object(int $oid, bool $original_version = false) { - if ($original_version === true) { + if ($original_version) { // Prioritizing the original version $object = PDFUtilFnc::find_object($this->_buffer, $this->_xref_table, $oid); if ($object === false) { @@ -353,6 +354,7 @@ public function set_signature_certificate($certfile, ?string $certpass = null): if (openssl_pkey_get_private($certificate['pkey']) === false) { throw new PDFException('invalid private key'); } + if (! openssl_x509_check_private_key($certificate['cert'], $certificate['pkey'])) { throw new PDFException("private key doesn't corresponds to certificate"); } @@ -366,10 +368,11 @@ public function set_signature_certificate($certfile, ?string $certpass = null): } else { $certfilecontent = file_get_contents($certfile); if ($certfilecontent === false) { - throw new PDFException("could not read file {$certfile}"); + throw new PDFException('could not read file ' . $certfile); } + if (openssl_pkcs12_read($certfilecontent, $certificate, $certpass) === false) { - throw new PDFException("could not get the certificates from file {$certfile}"); + throw new PDFException('could not get the certificates from file ' . $certfile); } } @@ -386,7 +389,7 @@ public function set_signature_certificate($certfile, ?string $certpass = null): * @param $crlURIorFILE Crl|null filename/url to validate cert * @param $issuerURIorFILE issuer|null filename/url */ - public function set_ltv(OCSP $ocspURI = null, Crl $crlURIorFILE = null, issuer $issuerURIorFILE = null): void + public function set_ltv(?string $ocspURI = null, ?string $crlURIorFILE = null, ?string $issuerURIorFILE = null): void { $this->_signature_ltv_data['ocspURI'] = $ocspURI; $this->_signature_ltv_data['crlURIorFILE'] = $crlURIorFILE; @@ -396,11 +399,11 @@ public function set_ltv(OCSP $ocspURI = null, Crl $crlURIorFILE = null, issuer $ /** * Function that stores the tsa configuration to use, when signing the document * - * @param $tsaurl Link to tsa service - * @param $tsauser the|null user for tsa service - * @param $tsapass the|null password for tsa service + * @param $tsaurl string Link to tsa service + * @param $tsauser ?string user for tsa service + * @param $tsapass ?string password for tsa service */ - public function set_tsa($tsa, the $tsauser = null, the $tsapass = null): void + public function set_tsa(string $tsa, ?string $tsauser = null, ?string $tsapass = null): void { $this->_signature_tsa['host'] = $tsa; if ($tsauser && $tsapass) { @@ -460,6 +463,7 @@ public function set_version($version): bool if (preg_match("/PDF-1.\[0-9\]/", (string) $version) !== 1) { return false; } + $this->_pdf_version_string = $version; return true; @@ -495,10 +499,8 @@ public function add_object(PDFObject $pdf_object): bool { $oid = $pdf_object->get_oid(); - if (isset($this->_pdf_objects[$oid])) { - if ($this->_pdf_objects[$oid]->get_generation() > $pdf_object->get_generation()) { - return false; - } + if (isset($this->_pdf_objects[$oid]) && $this->_pdf_objects[$oid]->get_generation() > $pdf_object->get_generation()) { + return false; } $this->_pdf_objects[$oid] = $pdf_object; @@ -521,7 +523,7 @@ public function add_object(PDFObject $pdf_object): bool public function to_pdf_file_b(bool $rebuild = false): Buffer { // We made no updates, so return the original doc - if (($rebuild === false) && (count($this->_pdf_objects) === 0) && ($this->_certificate === null) && ($this->_appearance === null)) { + if ($rebuild === false && count($this->_pdf_objects) === 0 && $this->_certificate === null && $this->_appearance === null) { return new Buffer($this->_buffer); } @@ -532,7 +534,7 @@ public function to_pdf_file_b(bool $rebuild = false): Buffer $this->update_mod_date(); $_signature = null; - if (($this->_appearance !== null) || ($this->_certificate !== null)) { + if ($this->_appearance !== null || $this->_certificate !== null) { $_signature = $this->_generate_signature_in_document(); if ($_signature === false) { $this->pop_state(); @@ -545,9 +547,9 @@ public function to_pdf_file_b(bool $rebuild = false): Buffer [$_doc_to_xref, $_obj_offsets] = $this->_generate_content_to_xref($rebuild); $xref_offset = $_doc_to_xref->size(); - if ($_signature !== null) { + if ($_signature instanceof PDFSignatureObject) { $_obj_offsets[$_signature->get_oid()] = $_doc_to_xref->size(); - $xref_offset += strlen((string) $_signature->to_pdf_entry()); + $xref_offset += strlen($_signature->to_pdf_entry()); } $doc_version_string = str_replace('PDF-', '', $this->_pdf_version_string); @@ -560,11 +562,9 @@ public function to_pdf_file_b(bool $rebuild = false): Buffer if ($doc_version_string > $target_version) { $target_version = $doc_version_string; } - } else { + } elseif ($doc_version_string < $target_version) { // i.e. xref+trailer - if ($doc_version_string < $target_version) { - $target_version = $doc_version_string; - } + $target_version = $doc_version_string; } if ($target_version >= '1.5') { @@ -602,15 +602,15 @@ public function to_pdf_file_b(bool $rebuild = false): Buffer if (isset($trailer['Filter'])) { unset($trailer['Filter']); } + $trailer->set_stream($xref['stream'], false); // If creating an incremental modification, point to the previous xref table if ($rebuild === false) { $trailer['Prev'] = $this->_xref_position; - } else { // If rebuilding the document, remove the references to previous xref tables, because it will be only one - if (isset($trailer['Prev'])) { - unset($trailer['Prev']); - } + } elseif (isset($trailer['Prev'])) { + // If rebuilding the document, remove the references to previous xref tables, because it will be only one + unset($trailer['Prev']); } // And generate the part of the document related to the xref @@ -636,18 +636,21 @@ public function to_pdf_file_b(bool $rebuild = false): Buffer // Generate the part of the document related to the xref $_doc_from_xref = new Buffer($xref_content); - $_doc_from_xref->data("trailer\n{$this->_pdf_trailer_object}"); + $_doc_from_xref->data( + 'trailer +' . $this->_pdf_trailer_object + ); $_doc_from_xref->data("\nstartxref\n{$xref_offset}\n%%EOF\n"); } - if ($_signature !== null) { + if ($_signature instanceof PDFSignatureObject) { // In case that the document is signed, calculate the signature $_signature->set_sizes($_doc_to_xref->size(), $_doc_from_xref->size()); $_signature['Contents'] = new PDFValueSimple(''); $_signable_document = new Buffer($_doc_to_xref->get_raw() . $_signature->to_pdf_entry() . $_doc_from_xref->get_raw()); $certificate = $_signature->get_certificate(); - $extracerts = (array_key_exists('extracerts', $certificate)) ? $certificate['extracerts'] : null; + $extracerts = $certificate['extracerts'] ?? null; $cms = new CMS(); $cms->signature_data['hashAlgorithm'] = 'sha256'; $cms->signature_data['privkey'] = $certificate['pkey']; @@ -696,11 +699,13 @@ public function to_pdf_file($filename, bool $rebuild = false): bool if ($file === false) { throw new PDFException('failed to create the file'); } + if (fwrite($file, $pdf_content->get_raw()) !== $pdf_content->size()) { fclose($file); throw new PDFException('failed to write to file'); } + fclose($file); return true; @@ -718,6 +723,7 @@ public function get_page(int $i): PDFObject|false if ($i < 0) { return false; } + if ($i >= count($this->_pages_info)) { return false; } @@ -740,6 +746,7 @@ public function get_page_size(int $i): false|array if ($i < 0) { return false; } + if ($i > count($this->_pages_info)) { return false; } @@ -755,7 +762,7 @@ public function get_page_size(int $i): false|array } // The page has not been found - if (($pageinfo === false) || (! isset($pageinfo['size']))) { + if ($pageinfo === false || ! isset($pageinfo['size'])) { return false; } @@ -832,6 +839,7 @@ public function get_object_tree(): array if ($references === false) { continue; } + if (! is_array($references)) { $references = [$references]; } @@ -843,6 +851,7 @@ public function get_object_tree(): array $r_object_o = $this->get_object($r_object); $objects[$r_object] = new DependencyTreeObject($r_object, $r_object_o['Type']); } + $object->addchild($r_object, $objects[$r_object]); } } @@ -859,10 +868,8 @@ public function get_object_tree(): array // Remove those objects that are child of other objects from the top of the tree foreach ($objects as $oid => $t_object) { - if (($t_object->is_child > 0) || (in_array($t_object->info, ['/XRef', '/ObjStm'], true))) { - if (! in_array($oid, $xref_children, true)) { - unset($objects[$oid]); - } + if (($t_object->is_child > 0 || in_array($t_object->info, ['/XRef', '/ObjStm'], true)) && ! in_array($oid, $xref_children, true)) { + unset($objects[$oid]); } } @@ -893,6 +900,7 @@ public function get_signatures(): array if (! is_array($o_value) || ! isset($o_value['Type'])) { continue; } + if ($o_value['Type']->val() !== 'Sig') { continue; } @@ -911,8 +919,9 @@ public function get_signatures(): array $cert ); - $signature += openssl_x509_parse($cert[0] ?? '') ?: []; - } catch (Throwable) { + $signature += openssl_x509_parse($cert[0] ?? ''); + } catch (Throwable $e) { + throw new PDFException('failed to read certificate', 0, $e); } $signatures[] = $signature; @@ -953,11 +962,13 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, if ($imagefilename !== null) { $imagesize = @getimagesize($imagefilename); if ($imagesize === false) { - return p_warning("failed to open the image {$imagesize}"); + return p_warning('failed to open the image ' . $imagesize); } - if (($page_to_appear < 0) || ($page_to_appear > $this->get_page_count() - 1)) { + + if ($page_to_appear < 0 || $page_to_appear > $this->get_page_count() - 1) { throw new PDFException('invalid page number'); } + $pagesize = $this->get_page_size($page_to_appear); if ($pagesize === false) { throw new PDFException('failed to get page size'); @@ -980,20 +991,17 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, if (count($size) != 2) { throw new PDFException('invalid size'); } + $width = $size[0]; $height = $size[1]; + } elseif ($size === null) { + $width = $i_w; + $height = $i_h; + } elseif (is_float($size) || is_int($size)) { + $width = $i_w * $size; + $height = $i_h * $size; } else { - if ($size === null) { - $width = $i_w; - $height = $i_h; - } else { - if (is_float($size) || is_int($size)) { - $width = $i_w * $size; - $height = $i_h * $size; - } else { - throw new PDFException('invalid size format'); - } - } + throw new PDFException('invalid size format'); } $i_w = $width ?? $imagesize[0]; @@ -1055,7 +1063,7 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false // First of all, we are searching for the root object (which should be in the trailer) $root = $this->_pdf_trailer_object['Root']; - if (($root === false) || (($root = $root->get_object_referenced()) === false)) { + if ($root === false || ($root = $root->get_object_referenced()) === false) { throw new PDFException('could not find the root object from the trailer'); } @@ -1081,7 +1089,7 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false $annots = &$page_obj['Annots']; $page_rotation = $page_obj['Rotate'] ?? new PDFValueSimple(0); - if ((($referenced = $annots->get_object_referenced()) !== false) && (! is_array($referenced))) { + if (($referenced = $annots->get_object_referenced()) !== false && ! is_array($referenced)) { // It is an indirect object, so we need to update that object $newannots = $this->create_object( $this->get_object($referenced)->get_value() @@ -1121,7 +1129,7 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false $CMS->signature_data['ltv'] = $this->_signature_ltv_data; $res = $CMS->pkcs7_sign('0'); $len = strlen($res); - p_debug(" Signature Length is \"{$len}\" Bytes"); + p_debug(sprintf(' Signature Length is "%d" Bytes', $len)); p_debug(" ########## FINISHED SIGNATURE LENGTH CHECK #########\n\n"); PDFSignatureObject::$__SIGNATURE_MAX_LENGTH = $len; @@ -1132,6 +1140,7 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false if ($this->_signature_tsa !== null) { $signature->set_signature_tsa($this->_signature_tsa); } + if ($this->_signature_ltv_data !== null) { $signature->set_signature_ltv($this->_signature_ltv_data); } @@ -1232,7 +1241,7 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false } $acroform = &$root_obj['AcroForm']; - if ((($referenced = $acroform->get_object_referenced()) !== false) && (! is_array($referenced))) { + if (($referenced = $acroform->get_object_referenced()) !== false && ! is_array($referenced)) { $acroform = $this->get_object($referenced); $updated_objects[] = $acroform; } else { @@ -1271,7 +1280,7 @@ protected function update_mod_date(?DateTime $date = null): bool // First of all, we are searching for the root object (which should be in the trailer) $root = $this->_pdf_trailer_object['Root']; - if (($root === false) || (($root = $root->get_object_referenced()) === false)) { + if ($root === false || ($root = $root->get_object_referenced()) === false) { throw new PDFException('could not find the root object from the trailer'); } @@ -1280,14 +1289,14 @@ protected function update_mod_date(?DateTime $date = null): bool throw new PDFException('invalid root object'); } - if ($date === null) { + if (! $date instanceof DateTime) { $date = new DateTime(); } // Update the xmp metadata if exists if (isset($root_obj['Metadata'])) { $metadata = $root_obj['Metadata']; - if ((($referenced = $metadata->get_object_referenced()) !== false) && (! is_array($referenced))) { + if (($referenced = $metadata->get_object_referenced()) !== false && ! is_array($referenced)) { $metadata = $this->get_object($referenced); $metastream = $metadata->get_stream(); $metastream = preg_replace('/([^<]*)<\/xmp:ModifyDate>/', '' . $date->format('c') . '', (string) $metastream); @@ -1300,7 +1309,7 @@ protected function update_mod_date(?DateTime $date = null): bool // Update the information object (not really needed) $info = $this->_pdf_trailer_object['Info']; - if (($info === false) || (($info = $info->get_object_referenced()) === false)) { + if ($info === false || ($info = $info->get_object_referenced()) === false) { throw new PDFException('could not find the info object from the trailer'); } @@ -1327,7 +1336,7 @@ protected function update_mod_date(?DateTime $date = null): bool protected function _generate_content_to_xref($rebuild = false): array { if ($rebuild === true) { - $result = new Buffer("%{$this->_pdf_version_string}" . __EOL); + $result = new Buffer('%' . $this->_pdf_version_string . __EOL); } else { $result = new Buffer($this->_buffer); } @@ -1386,16 +1395,19 @@ protected function _get_page_info(int $oid, array $info = []): array if (isset($object['MediaBox'])) { $info['size'] = $object['MediaBox']->val(); } + foreach ($kids as $kid) { $ids = $this->_get_page_info($kid, $info); if ($ids === false) { return false; } + array_push($page_ids, ...$ids); } } else { throw new PDFException('could not get the pages'); } + break; case 'Page': if (isset($object['MediaBox'])) { @@ -1421,17 +1433,17 @@ protected function _get_page_info(int $oid, array $info = []): array * * @return list an ordered list of the id of the page objects, or false if could not be found */ - protected function _acquire_pages_info() + protected function _acquire_pages_info(): array { $root = $this->_pdf_trailer_object['Root']; - if (($root === false) || (($root = $root->get_object_referenced()) === false)) { + if ($root === false || ($root = $root->get_object_referenced()) === false) { throw new PDFException('could not find the root object from the trailer'); } $root = $this->get_object($root); if ($root !== false) { $pages = $root['Pages']; - if (($pages === false) || (($pages = $pages->get_object_referenced()) === false)) { + if ($pages === false || ($pages = $pages->get_object_referenced()) === false) { throw new PDFException('could not find the pages for the document'); } @@ -1439,5 +1451,7 @@ protected function _acquire_pages_info() } else { p_warning('root object does not exist, so cannot get information about pages'); } + + return []; } } diff --git a/src/PDFDocWithContents.php b/src/PDFDocWithContents.php index ca4ab4b..e495a12 100644 --- a/src/PDFDocWithContents.php +++ b/src/PDFDocWithContents.php @@ -109,14 +109,14 @@ public function add_text(int $page_to_appear, $text, $x, $y, $params = []): void $c = cos($angle); $s = sin($angle); $cx = $x; - $cy = ($pagesize_h - $y); + $cy = $pagesize_h - $y; if ($angle !== 0) { $rotate_command = sprintf('%.5F %.5F %.5F %.5F %.2F %.2F cm 1 0 0 1 %.2F %.2F cm', $c, $s, -$s, $c, $cx, $cy, -$cx, -$cy); } $text_command = 'BT '; - $text_command .= "/{$font_id} " . $params['size'] . ' Tf '; + $text_command .= sprintf('/%s ', $font_id) . $params['size'] . ' Tf '; $text_command .= sprintf('%.2f %.2f Td ', $x, $pagesize_h - $y); // Ubicar en x, y $text_command .= sprintf('(%s) Tj ', $text); $text_command .= 'ET '; @@ -135,15 +135,16 @@ public function add_text(int $page_to_appear, $text, $x, $y, $params = []): void default: throw new PDFException('please use html-like colors (e.g. #ffbbaa)'); } + if ($r !== null) { - $text_command = " q {$r} {$g} {$b} rg {$text_command} Q"; + $text_command = sprintf(' q %d %s %s rg %s Q', $r, $g, $b, $text_command); } // Color RGB } else { throw new PDFException('please use html-like colors (e.g. #ffbbaa)'); } if ($angle !== 0) { - $text_command = " q {$rotate_command} {$text_command} Q"; + $text_command = sprintf(' q %s %s Q', $rotate_command, $text_command); } $data .= $text_command; @@ -196,10 +197,12 @@ public function add_image($page_obj, $filename, $x = 0, $y = 0, $w = 0, $h = 0): if (! isset($resources_obj['ProcSet'])) { $resources_obj['ProcSet'] = new PDFValueList(['/PDF']); } + $resources_obj['ProcSet']->push(['/ImageB', '/ImageC', '/ImageI']); if (! isset($resources_obj['XObject'])) { $resources_obj['XObject'] = new PDFValueObject(); } + $resources_obj['XObject'][$info['i']] = new PDFValueReference($images_objects[0]->get_oid()); // TODO: get the contents object in which to add the image. diff --git a/src/PDFObject.php b/src/PDFObject.php index 4d5008a..0fa41db 100644 --- a/src/PDFObject.php +++ b/src/PDFObject.php @@ -72,16 +72,19 @@ public function __construct( foreach ($value as $field => $v) { $obj[$field] = $v; } + $value = $obj; } + $this->_value = $value; $this->_generation = $generation; } public function __toString(): string { - return "{$this->_oid} 0 obj\n" . - "{$this->_value}\n" . + return $this->_oid . ' 0 obj +' . + $this->_value . PHP_EOL . ( $this->_stream === null ? '' : "stream\n" . @@ -119,7 +122,7 @@ public function get_generation(): int */ public function to_pdf_entry(): string { - return "{$this->_oid} 0 obj" . __EOL . + return $this->_oid . ' 0 obj' . __EOL . $this->_value . __EOL . ( $this->_stream === null ? '' : @@ -160,6 +163,7 @@ public function get_stream($raw = true) if ($raw === true) { return $this->_stream; } + if (isset($this->_value['Filter'])) { switch ($this->_value['Filter']) { case '/FlateDecode': @@ -192,12 +196,14 @@ public function set_stream($stream, $raw = true): void return; } + if (isset($this->_value['Filter'])) { $stream = match ($this->_value['Filter']) { '/FlateDecode' => gzcompress((string) $stream), default => throw new PDFException('unknown compression method ' . $this->_value['Filter']), }; } + $this->_value['Length'] = strlen((string) $stream); $this->_stream = $stream; } @@ -317,11 +323,13 @@ protected static function FlateDecode($_stream, array $params) for ($i = 1; $i < $columns; $i++) { $data[$i] = ($data[$i] + $data[$i - 1]) % 256; } + break; case 2: for ($i = 0; $i < $columns; $i++) { $data[$i] = chr((ord($data[$i]) + ord($data_prev[$i])) % 256); } + break; default: throw new PDFException('Unsupported stream'); diff --git a/src/PDFObjectParser.php b/src/PDFObjectParser.php index 5b7f21e..bf1a114 100644 --- a/src/PDFObjectParser.php +++ b/src/PDFObjectParser.php @@ -109,7 +109,7 @@ class PDFObjectParser implements Stringable protected $_t = false; - protected $_tt = self::T_NOTOKEN; + protected int $_tt = self::T_NOTOKEN; /** * Simple output of the object @@ -118,7 +118,7 @@ class PDFObjectParser implements Stringable */ public function __toString(): string { - return 'pos: ' . $this->_buffer->getpos() . ", c: {$this->_c}, n: {$this->_n}, t: {$this->_t}, tt: " . + return 'pos: ' . $this->_buffer->getpos() . sprintf(', c: %s, n: %s, t: %s, tt: ', $this->_c, $this->_n, $this->_t) . self::T_NAMES[$this->_tt] . ', b: ' . $this->_buffer->substratpos(50) . "\n"; } @@ -126,9 +126,9 @@ public function __toString(): string /** * Retrieves the current token type (one of T_* constants) * - * @return token the current token + * @return int the current token */ - public function current_token() + public function current_token(): int { return $this->_tt; } @@ -191,6 +191,7 @@ protected function start(StreamReader $buffer): bool|null if ($this->_buffer->size() === 0) { return false; } + $this->_n = $this->_buffer->currentchar(); $this->nextchar(); @@ -204,7 +205,7 @@ protected function _c_is_separator(): bool { $DSEPS = ['<<', '>>']; - return (($this->_c === false) || (str_contains("%<>()[]{}/ \n\r\t", (string) $this->_c)) || (in_array($this->_c . $this->_n, $DSEPS) !== false)); + return $this->_c === false || str_contains("%<>()[]{}/ \n\r\t", (string) $this->_c) || in_array($this->_c . $this->_n, $DSEPS, true); } /** @@ -221,13 +222,14 @@ protected function _parse_hex_string() } $this->nextchar(); // This char is "<" - while (($this->_c !== '>') && (str_contains("0123456789abcdefABCDEF \t\r\n\f", $this->_c))) { + while ($this->_c !== '>' && str_contains("0123456789abcdefABCDEF \t\r\n\f", $this->_c)) { $token .= $this->_c; if ($this->nextchar() === false) { break; } } - if (($this->_c !== false) && (! str_contains(">0123456789abcdefABCDEF \t\r\n\f", $this->_c))) { + + if ($this->_c !== false && ! str_contains(">0123456789abcdefABCDEF \t\r\n\f", $this->_c)) { throw new Exception('invalid hex string'); } @@ -251,15 +253,16 @@ protected function _parse_string() $n_parenthesis = 1; while ($this->_c !== false) { $this->nextchar(); - if (($this->_c === ')') && (! strlen($token) || ($token[strlen($token) - 1] !== '\\'))) { + if ($this->_c === ')' && (! strlen($token) || $token[strlen($token) - 1] !== '\\')) { $n_parenthesis--; if ($n_parenthesis === 0) { break; } } else { - if (($this->_c === '(') && (! strlen($token) || ($token[strlen($token) - 1] !== '\\'))) { + if ($this->_c === '(' && (! strlen($token) || $token[strlen($token) - 1] !== '\\')) { $n_parenthesis++; } + $token .= $this->_c; } } @@ -267,22 +270,23 @@ protected function _parse_string() if ($this->_c !== ')') { throw new Exception('Invalid string'); } + $this->nextchar(); return $token; } - protected function token() + protected function token(): array { if ($this->_c === false) { - return [false, false]; + return [false, 0]; } $token = false; while ($this->_c !== false) { // Skip the spaces - while ((str_contains("\t\n\r ", (string) $this->_c)) && ($this->nextchar() !== false)) { + while (str_contains("\t\n\r ", (string) $this->_c) && $this->nextchar() !== false) { } $token_type = self::T_NOTOKEN; @@ -296,6 +300,7 @@ protected function token() $token .= $this->_c; $this->nextchar(); } + $token_type = self::T_COMMENT; break; case '<': @@ -308,6 +313,7 @@ protected function token() $token = $this->_parse_hex_string(); $token_type = self::T_HEX_STRING; } + break; case '(': $token = $this->_parse_string(); @@ -320,6 +326,7 @@ protected function token() $token = '>>'; $token_type = self::T_DICT_END; } + break; case '[': $token = $this->_c; @@ -342,9 +349,11 @@ protected function token() break; } } + $token_type = self::T_FIELD; break; } + if ($token === false) { $token = ''; @@ -366,6 +375,8 @@ protected function token() return [$token, $token_type]; } + + return [false, 0]; } protected function _parse_obj(): PDFValueObject|false @@ -388,7 +399,7 @@ protected function _parse_obj(): PDFValueObject|false return new PDFValueObject($object); default: - throw new Exception("Invalid token: {$this}"); + throw new Exception('Invalid token: ' . $this); } } @@ -420,6 +431,7 @@ protected function _parse_list(): PDFValueList if ($value !== false) { $list[] = $value; } + break; } } @@ -463,7 +475,7 @@ protected function _parse_value(): PDFValue|false|null $simple_value = $this->_t; $this->nexttoken(); - while (($this->_t !== false) && ($this->_tt == self::T_SIMPLE)) { + while ($this->_t !== false && $this->_tt === self::T_SIMPLE) { $simple_value .= ' ' . $this->_t; $this->nexttoken(); } @@ -477,7 +489,7 @@ protected function _parse_value(): PDFValue|false|null return new PDFValueSimple($simple_value); default: - throw new Exception("Invalid token: {$this}"); + throw new Exception('Invalid token: ' . $this); } } diff --git a/src/PDFSignatureObject.php b/src/PDFSignatureObject.php index a4db47e..950fb24 100644 --- a/src/PDFSignatureObject.php +++ b/src/PDFSignatureObject.php @@ -36,7 +36,7 @@ class PDFSignatureObject extends PDFObject // is not known. 68 digits enable 20 digits for the size of the document public static $__BYTERANGE_SIZE = 68; - protected int $_prev_content_size; + protected int $_prev_content_size = 0; protected $_post_content_size = null; @@ -54,8 +54,6 @@ class PDFSignatureObject extends PDFObject */ public function __construct(int $oid) { - $this->_prev_content_size = 0; - $this->_post_content_size = null; parent::__construct($oid, [ 'Filter' => '/Adobe.PPKLite', 'Type' => '/Sig', @@ -120,12 +118,15 @@ public function set_metadata($name = null, $reason = null, $location = null, $co if ($name !== null) { $this->_value['Name'] = new PDFValueString($name); } + if ($reason !== null) { $this->_value['Reason'] = new PDFValueString($reason); } + if ($location !== null) { $this->_value['Location'] = new PDFValueString($location); } + if ($contact !== null) { $this->_value['ContactInfo'] = new PDFValueString($contact); } @@ -172,8 +173,8 @@ public function to_pdf_entry(): string $contents_size = strlen('' . $this->_value['Contents']); $byterange_str = '[ 0 ' . - ($this->_prev_content_size + $offset) . ' ' . - ($starting_second_part) . ' ' . + $this->_prev_content_size + $offset . ' ' . + $starting_second_part . ' ' . ($this->_post_content_size !== null ? $this->_post_content_size + ($signature_size - $contents_size - $offset) : 0) . ' ]'; $this->_value['ByteRange'] = diff --git a/src/PDFUtilFnc.php b/src/PDFUtilFnc.php index 0f5485d..7494327 100644 --- a/src/PDFUtilFnc.php +++ b/src/PDFUtilFnc.php @@ -25,6 +25,7 @@ use ddn\sapp\helpers\LoadHelpers; use ddn\sapp\helpers\StreamReader; use ddn\sapp\pdfvalue\PDFValue; +use Exception; use function ddn\sapp\helpers\p_warning; if (! defined(LoadHelpers::class)) { @@ -49,8 +50,8 @@ public static function get_trailer($_buffer, int $trailer_pos): PDFValue|false|n $parser = new PDFObjectParser(); try { $trailer_obj = $parser->parsestr($trailer_str); - } catch (Exception) { - throw new PDFException('trailer is not valid'); + } catch (Exception $exception) { + throw new PDFException('trailer is not valid', 0, $exception); } return $trailer_obj; @@ -61,6 +62,7 @@ public static function build_xref_1_5($offsets): array if (isset($offsets[0])) { unset($offsets[0]); } + $k = array_keys($offsets); sort($k); @@ -75,13 +77,15 @@ public static function build_xref_1_5($offsets): array $i_k = $k[$i]; $count = 0; } + if ($k[$i] === $c_k + 1) { $count++; } else { - $indexes[] = "{$i_k} {$count}"; + $indexes[] = sprintf('%s %d', $i_k, $count); $count = 1; $i_k = $k[$i]; } + $c_offset = $offsets[$k[$i]]; if (is_array($c_offset)) { @@ -94,12 +98,15 @@ public static function build_xref_1_5($offsets): array } else { $result .= pack('C', 1); } + $result .= pack('N', $c_offset); $result .= pack('C', 0); } + $c_k = $k[$i]; } - $indexes[] = "{$i_k} {$count}"; + + $indexes[] = sprintf('%s %d', $i_k, $count); $indexes = implode(' ', $indexes); // p_debug(show_bytes($result, 6)); @@ -127,16 +134,16 @@ public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null $xref_o = self::find_object_at_pos($_buffer, null, $xref_pos, []); if ($xref_o === false) { - throw new PDFException("cross reference object not found when parsing xref at position {$xref_pos}", [false, false, false]); + throw new PDFException('cross reference object not found when parsing xref at position ' . $xref_pos, [false, false, false]); } - if (! (isset($xref_o['Type'])) || ($xref_o['Type']->val() !== 'XRef')) { + if (! isset($xref_o['Type']) || $xref_o['Type']->val() !== 'XRef') { throw new PDFException('invalid xref table', [false, false, false]); } $stream = $xref_o->get_stream(false); if ($stream === null) { - throw new PDFException("cross reference stream not found when parsing xref at position {$xref_pos}", [false, false, false]); + throw new PDFException('cross reference stream not found when parsing xref at position ' . $xref_pos, [false, false, false]); } $W = $xref_o['W']->val(true); @@ -165,20 +172,17 @@ public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null // Get the previous xref table, to build up on it $xref_table = []; - if (($depth === null) || ($depth > 0)) { - // If still want to get more versions, let's check whether there is a previous xref table or not - - if (isset($xref_o['Prev'])) { - $Prev = $xref_o['Prev']; - $Prev = $Prev->get_int(); - if ($Prev === false) { - throw new PDFException('invalid reference to a previous xref table', [false, false, false]); - } - - // When dealing with 1.5 cross references, we do not allow to use other than cross references - [$xref_table] = self::get_xref_1_5($_buffer, $Prev, $depth); - // p_debug_var($xref_table); + // If still want to get more versions, let's check whether there is a previous xref table or not + if (($depth === null || $depth > 0) && isset($xref_o['Prev'])) { + $Prev = $xref_o['Prev']; + $Prev = $Prev->get_int(); + if ($Prev === false) { + throw new PDFException('invalid reference to a previous xref table', [false, false, false]); } + + // When dealing with 1.5 cross references, we do not allow to use other than cross references + [$xref_table] = self::get_xref_1_5($_buffer, $Prev, $depth); + // p_debug_var($xref_table); } // p_debug("xref table found at $xref_pos (oid: " . $xref_o->get_oid() . ")"); @@ -215,12 +219,12 @@ public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null $object_i = $Index[$index_i++]; $object_count = $Index[$index_i++]; - while (($stream_v->currentchar() !== false) && ($object_count > 0)) { - $f1 = $W[0] != 0 ? ($fmt_function[0]($stream_v->nextchars($W[0]))) : 1; + while ($stream_v->currentchar() !== false && $object_count > 0) { + $f1 = $W[0] != 0 ? $fmt_function[0]($stream_v->nextchars($W[0])) : 1; $f2 = $fmt_function[1]($stream_v->nextchars($W[1])); $f3 = $fmt_function[2]($stream_v->nextchars($W[2])); - if (($f1 === false) || ($f2 === false) || ($f3 === false)) { + if ($f1 === false || $f2 === false || $f3 === false) { throw new PDFException('invalid stream for xref table', [false, false, false]); } @@ -249,7 +253,7 @@ public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null ]; break; default: - throw new PDFException("do not know about entry of type {$f1} in xref table"); + throw new PDFException(sprintf('do not know about entry of type %s in xref table', $f1)); } $object_i++; @@ -260,7 +264,7 @@ public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null return [$xref_table, $xref_o->get_value(), '1.5']; } - public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null): false|array + public static function get_xref_1_4(&$_buffer, string $xref_pos, $depth = null): false|array { if ($depth !== null) { if ($depth <= 0) { @@ -279,7 +283,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null): false| $separator = "\r\n"; $xref_line = strtok($xref_substr, $separator); if ($xref_line !== 'xref') { - throw new PDFException("xref tag not found at position {$xref_pos}", [false, false, false]); + throw new PDFException('xref tag not found at position ' . $xref_pos, [false, false, false]); } // Now parse the lines and build the xref table @@ -289,21 +293,22 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null): false| while (($xref_line = strtok($separator)) !== false) { // The first type of entry contains the id of the next object and the amount of continuous objects defined - if (preg_match('/([0-9]+) ([0-9]+)$/', $xref_line, $matches) === 1) { + if (preg_match('/(\d+) (\d+)$/', $xref_line, $matches) === 1) { if ($obj_count > 0) { // If still expecting objects, we'll assume that the xref is malformed - throw new PDFException("malformed xref at position {$xref_pos}", [false, false, false]); + throw new PDFException('malformed xref at position ' . $xref_pos, [false, false, false]); } + $obj_id = (int) $matches[1]; $obj_count = (int) $matches[2]; continue; } // The other type of entry contains the offset of the object, the generation and the command (which is "f" for "free" or "n" for "new") - if (preg_match('/^([0-9]+) ([0-9]+) (.)\s*/', $xref_line, $matches) === 1) { + if (preg_match('/^(\d+) (\d+) (.)\s*/', $xref_line, $matches) === 1) { // If no object expected, we'll assume that the xref is malformed if ($obj_count === 0) { - throw new PDFException("unexpected entry for xref: {$xref_line}", [false, false, false]); + throw new PDFException('unexpected entry for xref: ' . $xref_line, [false, false, false]); } $obj_offset = (int) $matches[1]; @@ -336,10 +341,11 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null): false| if ($obj_generation !== 0) { p_warning('Objects of non-zero generation are not fully checked... please double check your document and (if possible) please send examples via issues to https://github.com/dealfonso/sapp/issues/'); } + break; default: // If it is not one of the expected, let's skip the object - throw new PDFException("invalid entry for xref: {$xref_line}", [false, false, false]); + throw new PDFException('invalid entry for xref: ' . $xref_line, [false, false, false]); } } @@ -349,7 +355,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null): false| } // If the entry is not recongised, show the error - throw new PDFException("invalid xref entry {$xref_line}"); + throw new PDFException('invalid xref entry ' . $xref_line); } // Get the trailer object @@ -359,7 +365,7 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null): false| if (isset($trailer_obj['Prev'])) { $xref_prev_pos = $trailer_obj['Prev']->val(); if (! is_numeric($xref_prev_pos)) { - throw new PDFException("invalid trailer {$trailer_obj}", [false, false, false]); + throw new PDFException('invalid trailer ' . $trailer_obj, [false, false, false]); } $xref_prev_pos = (int) $xref_prev_pos; @@ -371,10 +377,10 @@ public static function get_xref_1_4(&$_buffer, $xref_pos, $depth = null): false| } if ($prev_table !== false) { - foreach ($prev_table as $obj_id => $obj_offset) { // Not modifying the objects, but to make sure that it does not consume additional memory + foreach ($prev_table as $obj_id2 => $obj_offset) { // Not modifying the objects, but to make sure that it does not consume additional memory // If there not exists a new version, we'll acquire it - if (! isset($xref_table[$obj_id])) { - $xref_table[$obj_id] = $obj_offset; + if (! isset($xref_table[$obj_id2])) { + $xref_table[$obj_id2] = $obj_offset; } } } @@ -405,7 +411,7 @@ public static function acquire_structure(string &$_buffer, ?int $depth = null): return false; } - if (preg_match('/^%PDF-[0-9]+\.[0-9]+$/', $pdf_version, $matches) !== 1) { + if (preg_match('/^%PDF-\d+\.\d+$/', $pdf_version, $matches) !== 1) { throw new PDFException('PDF version string not found'); } @@ -505,18 +511,19 @@ public static function find_object_at_pos(&$_buffer, ?int $oid, int $object_offs if ($length === false) { $length_object_id = $object['Length']->get_object_referenced(); if ($length_object_id === false) { - throw new PDFException("could not get stream for object {$oid}"); + throw new PDFException('could not get stream for object ' . $oid); } + $length_object = self::find_object($_buffer, $xref_table, $length_object_id); if ($length_object === false) { - throw new PDFException("could not get object {$oid}"); + throw new PDFException('could not get object ' . $oid); } $length = $length_object->get_value()?->get_int(); } if ($length === false) { - throw new PDFException("could not get stream length for object {$oid}"); + throw new PDFException('could not get stream length for object ' . $oid); } $object->set_stream(substr((string) $_buffer, $_stream_pending, $length), true); @@ -539,6 +546,7 @@ public static function find_object(&$_buffer, $xref_table, int $oid): false|PDFO if ($oid === 0) { return false; } + if (! isset($xref_table[$oid])) { return false; } @@ -549,6 +557,7 @@ public static function find_object(&$_buffer, $xref_table, int $oid): false|PDFO if (! is_array($object_offset)) { return self::find_object_at_pos($_buffer, $oid, $object_offset, $xref_table); } + $object = self::find_object_in_objstm($_buffer, $xref_table, $object_offset['stmoid'], $object_offset['pos'], $oid); return $object; @@ -561,10 +570,10 @@ public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm { $objstm = self::find_object($_buffer, $xref_table, $objstm_oid); if ($objstm === false) { - throw new PDFException("could not get object stream {$objstm_oid}"); + throw new PDFException('could not get object stream ' . $objstm_oid); } - if ((($objstm['Extends'] ?? false) !== false)) { // TODO: support them + if (($objstm['Extends'] ?? false) !== false) { // TODO: support them throw new PDFException('not supporting extended object streams at this time'); } @@ -572,12 +581,12 @@ public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm $N = $objstm['N'] ?? false; $Type = $objstm['Type'] ?? false; - if (($First === false) || ($N === false) || ($Type === false)) { - throw new PDFException("invalid object stream {$objstm_oid}"); + if ($First === false || $N === false || $Type === false) { + throw new PDFException('invalid object stream ' . $objstm_oid); } if ($Type->val() !== 'ObjStm') { - throw new PDFException("object {$objstm_oid} is not an object stream"); + throw new PDFException(sprintf('object %d is not an object stream', $objstm_oid)); } $First = $First->get_int(); @@ -586,31 +595,33 @@ public static function find_object_in_objstm(&$_buffer, $xref_table, int $objstm $stream = $objstm->get_stream(false); $index = substr((string) $stream, 0, $First); $index = explode(' ', trim($index)); + $stream = substr((string) $stream, $First); if (count($index) % 2 !== 0) { - throw new PDFException("invalid index for object stream {$objstm_oid}"); + throw new PDFException('invalid index for object stream ' . $objstm_oid); } $objpos *= 2; if ($objpos > count($index)) { - throw new PDFException("object {$oid} not found in object stream {$objstm_oid}"); + throw new PDFException(sprintf('object %d not found in object stream %d', $oid, $objstm_oid)); } $offset = (int) $index[$objpos + 1]; $offsets = []; - for ($i = 1; ($i < count($index)); $i += 2) { + $counter = count($index); + for ($i = 1; $i < $counter; $i += 2) { $offsets[] = (int) $index[$i]; } $offsets[] = strlen($stream); sort($offsets); - for ($i = 0; ($i < count($offsets)) && ($offset >= $offsets[$i]); $i++) { + for ($i = 0; $i < count($offsets) && $offset >= $offsets[$i]; $i++) { } $next = $offsets[$i]; - $object_def_str = "{$oid} 0 obj " . substr($stream, $offset, $next - $offset) . ' endobj'; + $object_def_str = $oid . ' 0 obj ' . substr($stream, $offset, $next - $offset) . ' endobj'; return self::object_from_string($object_def_str, $oid); } @@ -622,7 +633,7 @@ public static function object_from_string(string $buffer, ?int $expected_obj_id, { if (preg_match('/([0-9]+)\s+([0-9+])\s+obj(\s+)/ms', $buffer, $matches, 0, $offset) !== 1) { // p_debug_var(substr($buffer)) - throw new PDFException("object is not valid: {$expected_obj_id}"); + throw new PDFException('object is not valid: ' . $expected_obj_id); } $found_obj_header = $matches[0]; @@ -634,7 +645,7 @@ public static function object_from_string(string $buffer, ?int $expected_obj_id, } if ($found_obj_id !== $expected_obj_id) { - throw new PDFException("pdf structure is corrupt: found obj {$found_obj_id} while searching for obj {$expected_obj_id} (at {$offset})"); + throw new PDFException(sprintf('pdf structure is corrupt: found obj %d while searching for obj %d (at %d)', $found_obj_id, $expected_obj_id, $offset)); } // The object starts after the header @@ -647,7 +658,7 @@ public static function object_from_string(string $buffer, ?int $expected_obj_id, $obj_parsed = $parser->parse($stream); if ($obj_parsed === false) { - throw new PDFException("object {$expected_obj_id} could not be parsed"); + throw new PDFException(sprintf('object %d could not be parsed', $expected_obj_id)); } switch ($parser->current_token()) { @@ -688,19 +699,23 @@ public static function build_xref(array $offsets): string if ($k[$i] === 0) { continue; } + if ($k[$i] === $c_k + 1) { $count++; } else { - $result .= "{$i_k} {$count}\n{$references}"; + $result .= sprintf('%s %d%s%s', $i_k, $count, PHP_EOL, $references); $count = 1; $i_k = $k[$i]; $references = ''; } + $references .= sprintf("%010d 00000 n \n", $offsets[$k[$i]]); $c_k = $k[$i]; } - $result .= "{$i_k} {$count}\n{$references}"; - return "xref\n{$result}"; + $result .= sprintf('%s %d%s%s', $i_k, $count, PHP_EOL, $references); + + return 'xref +' . $result; } } diff --git a/src/helpers/Buffer.php b/src/helpers/Buffer.php index 4602f06..f5cb8de 100644 --- a/src/helpers/Buffer.php +++ b/src/helpers/Buffer.php @@ -57,7 +57,7 @@ public function __construct($string = null) */ public function __toString(): string { - if (strlen((string) $this->_buffer) < (__CONVENIENT_MAX_BUFFER_DUMP * 2)) { + if (strlen((string) $this->_buffer) < __CONVENIENT_MAX_BUFFER_DUMP * 2) { return (string) debug_var($this); } @@ -164,9 +164,10 @@ public function show_bytes($columns, $offset = 0, $length = null): string $result = ''; $length = min($length, $this->_bufferlen); for ($i = $offset; $i < $length;) { - for ($j = 0; ($j < $columns) && ($i < $length); $i++, $j++) { + for ($j = 0; $j < $columns && $i < $length; $i++, $j++) { $result .= sprintf('%02x ', ord($this->_buffer[$i])); } + $result .= "\n"; } diff --git a/src/helpers/CMS.php b/src/helpers/CMS.php index a7b89de..b4a666f 100644 --- a/src/helpers/CMS.php +++ b/src/helpers/CMS.php @@ -35,16 +35,18 @@ public function sendReq(array $reqData): string curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_HEADER, 1); - curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: {$reqData['req_contentType']}", 'User-Agent: SAPP PDF']); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: ' . $reqData['req_contentType'], 'User-Agent: SAPP PDF']); curl_setopt($ch, CURLOPT_POSTFIELDS, $reqData['data']); if (($reqData['user'] ?? null) && ($reqData['password'] ?? null)) { curl_setopt($ch, CURLOPT_USERPWD, $reqData['user'] . ':' . $reqData['password']); } + $tsResponse = curl_exec($ch); if (! $tsResponse) { throw new PDFException('empty curl response'); } + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); curl_close($ch); $header = substr($tsResponse, 0, $header_size); @@ -57,22 +59,26 @@ public function sendReq(array $reqData): string break; } } - if ($code != '200') { + + if ($code !== '200') { throw new PDFException(sprintf('response error! Code="{$code}", Status="%s"', trim($status ?? ''))); } + $contentTypeHeader = ''; $headers = explode("\n", $header); foreach ($headers as $r) { // Match the header name up to ':', compare lower case - if (stripos($r, 'Content-Type' . ':') === 0) { + if (stripos($r, 'Content-Type:') === 0) { [, $headervalue] = explode(':', $r, 2); $contentTypeHeader = trim($headervalue); } } + if ($contentTypeHeader != $reqData['resp_contentType']) { - throw new PDFException("response content type not {$reqData['resp_contentType']}, but: \"{$contentTypeHeader}\""); + throw new PDFException(sprintf('response content type not %s, but: "%s"', $reqData['resp_contentType'], $contentTypeHeader)); } - if (empty($body)) { + + if ($body === '' || $body === '0') { throw new PDFException('error empty response!'); } @@ -103,11 +109,13 @@ public function pkcs7_sign(string $binaryData): string if (! array_key_exists($hashAlgorithm, $hexOidHashAlgos)) { throw new PDFException('not support hash algorithm!'); } - p_debug("hash algorithm is \"{$hashAlgorithm}\""); + + p_debug(sprintf('hash algorithm is "%s"', $hashAlgorithm)); $x509 = new x509(); if (! $certParse = $x509::readcert($this->signature_data['signcert'])) { throw new PDFException('certificate error! check certificate'); } + $hexEmbedCerts[] = bin2hex($x509::get_cert($this->signature_data['signcert'])); $appendLTV = ''; $ltvData = $this->signature_data['ltv']; @@ -118,18 +126,18 @@ public function pkcs7_sign(string $binaryData): string $LTVvalidationEnd = false; $isRootCA = false; - if ($certParse['tbsCertificate']['issuer']['hexdump'] == $certParse['tbsCertificate']['subject']['hexdump']) { // check whether root ca - if (openssl_public_decrypt(hex2bin((string) $certParse['signatureValue']), $decrypted, x509::x509_der2pem($x509::get_cert($this->signature_data['signcert'])), OPENSSL_PKCS1_PADDING)) { - p_debug("***** \"{$certParse['tbsCertificate']['subject']['2.5.4.3'][0]}\" is a ROOT CA. No validation performed ***"); - $isRootCA = true; - } + // check whether root ca + if ($certParse['tbsCertificate']['issuer']['hexdump'] == $certParse['tbsCertificate']['subject']['hexdump'] && openssl_public_decrypt(hex2bin((string) $certParse['signatureValue']), $decrypted, x509::x509_der2pem($x509::get_cert($this->signature_data['signcert'])), OPENSSL_PKCS1_PADDING)) { + p_debug(sprintf('***** "%s" is a ROOT CA. No validation performed ***', $certParse['tbsCertificate']['subject']['2.5.4.3'][0])); + $isRootCA = true; } + if ($isRootCA == false) { $i = 0; $LTVvalidation = true; $certtoCheck = $certParse; while ($LTVvalidation !== false) { - p_debug("========= {$i} checking \"{$certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0]}\"==============="); + p_debug(sprintf('========= %d checking "%s"===============', $i, $certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0])); $LTVvalidation = $this->LTVvalidation($certtoCheck); $i++; if ($LTVvalidation) { @@ -141,12 +149,11 @@ public function pkcs7_sign(string $binaryData): string $hexEmbedCerts[] = bin2hex((string) $LTVvalidation['issuer']); } - if ($certtoCheck['tbsCertificate']['issuer']['hexdump'] == $certtoCheck['tbsCertificate']['subject']['hexdump']) { // check whether root ca - if (openssl_public_decrypt(hex2bin((string) $certtoCheck['signatureValue']), $decrypted, $x509::x509_der2pem($curr_issuer), OPENSSL_PKCS1_PADDING)) { - p_debug("========= FINISH Reached ROOT CA \"{$certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0]}\"==============="); - $LTVvalidationEnd = true; - break; - } + // check whether root ca + if ($certtoCheck['tbsCertificate']['issuer']['hexdump'] == $certtoCheck['tbsCertificate']['subject']['hexdump'] && openssl_public_decrypt(hex2bin((string) $certtoCheck['signatureValue']), $decrypted, $x509::x509_der2pem($curr_issuer), OPENSSL_PKCS1_PADDING)) { + p_debug(sprintf('========= FINISH Reached ROOT CA "%s"===============', $certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0])); + $LTVvalidationEnd = true; + break; } } } @@ -154,7 +161,7 @@ public function pkcs7_sign(string $binaryData): string if ($LTVvalidationEnd) { p_debug(" LTV Validation SUCCESS\n"); $ocsp = ''; - if (! empty($LTVvalidation_ocsp)) { + if ($LTVvalidation_ocsp !== '' && $LTVvalidation_ocsp !== '0') { $ocsp = asn1::expl( 1, asn1::seq( @@ -162,8 +169,9 @@ public function pkcs7_sign(string $binaryData): string ) ); } + $crl = ''; - if (! empty($LTVvalidation_crl)) { + if ($LTVvalidation_crl !== '' && $LTVvalidation_crl !== '0') { $crl = asn1::expl( 0, asn1::seq( @@ -171,6 +179,7 @@ public function pkcs7_sign(string $binaryData): string ) ); } + $appendLTV = asn1::seq( '06092A864886F72F010108' . // adbe-revocationInfoArchival (1.2.840.113583.1.1.8) asn1::set( @@ -184,6 +193,7 @@ public function pkcs7_sign(string $binaryData): string p_warning(" LTV Validation FAILED!\n"); } } + foreach ($this->signature_data['extracerts'] ?? [] as $extracert) { $hex_extracert = bin2hex($x509::x509_pem2der($extracert)); if (! in_array($hex_extracert, $hexEmbedCerts, true)) { @@ -191,6 +201,7 @@ public function pkcs7_sign(string $binaryData): string } } } + $messageDigest = hash($hashAlgorithm, $binaryData); $authenticatedAttributes = asn1::seq( '06092A864886F70D010903' . //OBJ_pkcs9_contentType 1.2.840.113549.1.9.3 @@ -217,6 +228,7 @@ public function pkcs7_sign(string $binaryData): string if (! openssl_private_encrypt(hex2bin($toencrypt), $encryptedDigest, $pkey, OPENSSL_PKCS1_PADDING)) { throw new PDFException("openssl_private_encrypt error! can't encrypt"); } + $hexencryptedDigest = bin2hex($encryptedDigest); $timeStamp = ''; if (! empty($this->signature_data['tsa'])) { @@ -232,6 +244,7 @@ public function pkcs7_sign(string $binaryData): string p_warning(' Timestamping FAILED!'); } } + $issuerName = $certParse['tbsCertificate']['issuer']['hexdump']; $serialNumber = $certParse['tbsCertificate']['serialNumber']; $signerinfos = asn1::seq( @@ -240,8 +253,7 @@ public function pkcs7_sign(string $binaryData): string asn1::seq($hexOidHashAlgos[$hashAlgorithm] . '0500') . asn1::expl(0, $authenticatedAttributes) . asn1::seq( - '06092A864886F70D010101' . //OBJ_rsaEncryption - '0500' + '06092A864886F70D0101010500' ) . asn1::oct($hexencryptedDigest) . $timeStamp @@ -281,7 +293,7 @@ protected function createTimestamp(string $data, string $hashAlg = 'sha1') ] + $tsaData; p_debug(' sending TSA query to "' . $tsaData['host'] . '"...'); - if (! $binaryTsaResp = $this->sendReq($reqData)) { + if (($binaryTsaResp = $this->sendReq($reqData)) === '' || ($binaryTsaResp = $this->sendReq($reqData)) === '0') { throw new PDFException('TSA query send FAILED!'); } @@ -290,6 +302,7 @@ protected function createTimestamp(string $data, string $hashAlg = 'sha1') if (! $tsaResp = $this->tsa_parseResp($binaryTsaResp)) { throw new PDFException('parsing FAILED!'); } + p_debug(' parsing OK'); $TSTInfo = $tsaResp['TimeStampResp']['timeStampToken']['hexdump']; @@ -315,20 +328,22 @@ protected function LTVvalidation(array $parsedCert): false|array p_debug(' getting OCSP & CRL address...'); p_debug(' reading AIA OCSP attribute...'); $ocspURI = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.1'][0]; - if (empty(trim((string) $ocspURI))) { + if (trim((string) $ocspURI) === '' || trim((string) $ocspURI) === '0') { p_warning(' FAILED!'); } else { - p_debug(" OK got address:\"{$ocspURI}\""); + p_debug(sprintf(' OK got address:"%s"', $ocspURI)); } + $ocspURI = trim((string) $ocspURI); p_debug(' reading CRL CDP attribute...'); $crlURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['2.5.29.31']['value'][0]; - if (empty(trim($crlURIorFILE ?? ''))) { + if (trim($crlURIorFILE ?? '') === '' || trim($crlURIorFILE ?? '') === '0') { p_warning(' FAILED!'); } else { - p_debug(" OK got address:\"{$crlURIorFILE}\""); + p_debug(sprintf(' OK got address:"%s"', $crlURIorFILE)); } - if (empty($ocspURI) && empty($crlURIorFILE)) { + + if (($ocspURI === '' || $ocspURI === '0') && empty($crlURIorFILE)) { throw new PDFException("can't get OCSP/CRL address! Process terminated."); } @@ -337,11 +352,11 @@ protected function LTVvalidation(array $parsedCert): false|array p_debug(' looking for issuer address from AIA attribute...'); $issuerURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.2'][0]; $issuerURIorFILE = trim($issuerURIorFILE ?? ''); - if (empty($issuerURIorFILE)) { + if ($issuerURIorFILE === '' || $issuerURIorFILE === '0') { p_debug(' Failed!'); } else { - p_debug(" OK got address \"{$issuerURIorFILE}\"..."); - p_debug(" load issuer from \"{$issuerURIorFILE}\"..."); + p_debug(sprintf(' OK got address "%s"...', $issuerURIorFILE)); + p_debug(sprintf(' load issuer from "%s"...', $issuerURIorFILE)); if ($issuerCert = @file_get_contents($issuerURIorFILE)) { p_debug(' OK. size ' . round(strlen($issuerCert) / 1024, 2) . 'Kb'); p_debug(' reading issuer certificate...'); @@ -366,10 +381,10 @@ protected function LTVvalidation(array $parsedCert): false|array if (! $ltvResult['issuer']) { p_debug(' search for issuer in extracerts.....'); - if (array_key_exists('extracerts', $this->signature_data) && ($this->signature_data['extracerts'] !== null) && (count($this->signature_data['extracerts']) > 0)) { + if (array_key_exists('extracerts', $this->signature_data) && $this->signature_data['extracerts'] !== null && count($this->signature_data['extracerts']) > 0) { $i = 0; foreach ($this->signature_data['extracerts'] as $extracert) { - p_debug(" extracerts[{$i}] ..."); + p_debug(sprintf(' extracerts[%d] ...', $i)); $certSigner_signatureField = $certSigner_parse['signatureValue']; if (openssl_public_decrypt(hex2bin((string) $certSigner_signatureField), $decrypted, $extracert, OPENSSL_PKCS1_PADDING)) { p_debug(' OK got issuer.'); @@ -378,6 +393,7 @@ protected function LTVvalidation(array $parsedCert): false|array } else { p_debug(' FAIL!'); } + $i++; } } else { @@ -386,7 +402,7 @@ protected function LTVvalidation(array $parsedCert): false|array } if ($ltvResult['issuer']) { - if (! empty($ocspURI)) { + if ($ocspURI !== '' && $ocspURI !== '0') { p_debug(' OCSP start...'); $ocspReq_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; $ocspReq_issuerNameHash = $certIssuer_parse['tbsCertificate']['subject']['sha1']; @@ -401,8 +417,8 @@ protected function LTVvalidation(array $parsedCert): false|array 'req_contentType' => 'application/ocsp-request', 'resp_contentType' => 'application/ocsp-response', ]; - p_debug(" OCSP send request to \"{$ocspURI}\"..."); - if ($ocspResp = $this->sendReq($reqData)) { + p_debug(sprintf(' OCSP send request to "%s"...', $ocspURI)); + if (($ocspResp = $this->sendReq($reqData)) !== '' && ($ocspResp = $this->sendReq($reqData)) !== '0') { p_debug(' OK.'); p_debug(' OCSP parsing response...'); if ($ocsp_parse = x509::ocsp_response_parse($ocspResp, $return)) { @@ -417,7 +433,7 @@ protected function LTVvalidation(array $parsedCert): false|array p_warning(' FAILED! cert not valid, status:"' . strtoupper((string) $certStatus) . '"'); } } else { - p_warning(" FAILED! Ocsp server status \"{$return}\""); + p_warning(sprintf(' FAILED! Ocsp server status "%s"', $return)); } } else { p_warning(' FAILED!'); @@ -427,61 +443,65 @@ protected function LTVvalidation(array $parsedCert): false|array } } - if (! $ltvResult['ocsp']) {// CRL not processed if OCSP validation already success - if (! empty($crlURIorFILE)) { - p_debug(' processing CRL validation since OCSP not done/failed...'); - p_debug(" getting crl from \"{$crlURIorFILE}\"..."); - if ($crl = @file_get_contents($crlURIorFILE)) { - p_debug(' OK. size ' . round(strlen($crl) / 1024, 2) . 'Kb'); - p_debug(' reading crl...'); - if ($crlread = x509::crl_read($crl)) { + // CRL not processed if OCSP validation already success + if (! $ltvResult['ocsp'] && ! empty($crlURIorFILE)) { + p_debug(' processing CRL validation since OCSP not done/failed...'); + p_debug(sprintf(' getting crl from "%s"...', $crlURIorFILE)); + if ($crl = @file_get_contents($crlURIorFILE)) { + p_debug(' OK. size ' . round(strlen($crl) / 1024, 2) . 'Kb'); + p_debug(' reading crl...'); + if ($crlread = x509::crl_read($crl)) { + p_debug(' OK'); + p_debug(' verify crl signature...'); + $crl_signatureField = $crlread['parse']['signature']; + if (openssl_public_decrypt(hex2bin((string) $crl_signatureField), $decrypted, x509::x509_der2pem($ltvResult['issuer']), OPENSSL_PKCS1_PADDING)) { p_debug(' OK'); - p_debug(' verify crl signature...'); - $crl_signatureField = $crlread['parse']['signature']; - if (openssl_public_decrypt(hex2bin((string) $crl_signatureField), $decrypted, x509::x509_der2pem($ltvResult['issuer']), OPENSSL_PKCS1_PADDING)) { - p_debug(' OK'); - p_debug(' check CRL validity...'); - $crl_parse = $crlread['parse']; - $thisUpdate = str_pad((string) $crl_parse['TBSCertList']['thisUpdate'], 15, '20', STR_PAD_LEFT); - $thisUpdateTime = strtotime($thisUpdate); - $nextUpdate = str_pad((string) $crl_parse['TBSCertList']['nextUpdate'], 15, '20', STR_PAD_LEFT); - $nextUpdateTime = strtotime($nextUpdate); - $nowz = time(); - if (($nowz - $thisUpdateTime) < 0) { // 0 sec after valid - throw new PDFException('FAILED! not yet valid! valid at ' . date('d/m/Y H:i:s', $thisUpdateTime)); - } - if (($nextUpdateTime - $nowz) < 1) { // not accept if crl 1 sec remain to expired - throw new PDFException(' FAILED! Expired crl at ' . date('d/m/Y H:i:s', $nextUpdateTime) . ' and now ' . date('d/m/Y H:i:s', $nowz) . '!'); - } - p_debug(' OK CRL still valid until ' . date('d/m/Y H:i:s', $nextUpdateTime)); - $crlCertValid = true; - p_debug(' check if cert not revoked...'); - if (array_key_exists('revokedCertificates', $crl_parse['TBSCertList'])) { - $certSigner_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; - if (array_key_exists($certSigner_serialNumber, $crl_parse['TBSCertList']['revokedCertificates']['lists'])) { - throw new PDFException(' FAILED! Certificate Revoked!'); - } - } - if ($crlCertValid == true) { - p_debug(' OK. VALID'); - $crlHex = current(unpack('H*', (string) $crlread['der'])); - $ltvResult['crl'] = $crlHex; + p_debug(' check CRL validity...'); + $crl_parse = $crlread['parse']; + $thisUpdate = str_pad((string) $crl_parse['TBSCertList']['thisUpdate'], 15, '20', STR_PAD_LEFT); + $thisUpdateTime = strtotime($thisUpdate); + $nextUpdate = str_pad((string) $crl_parse['TBSCertList']['nextUpdate'], 15, '20', STR_PAD_LEFT); + $nextUpdateTime = strtotime($nextUpdate); + $nowz = time(); + if ($nowz - $thisUpdateTime < 0) { // 0 sec after valid + throw new PDFException('FAILED! not yet valid! valid at ' . date('d/m/Y H:i:s', $thisUpdateTime)); + } + + if ($nextUpdateTime - $nowz < 1) { // not accept if crl 1 sec remain to expired + throw new PDFException(' FAILED! Expired crl at ' . date('d/m/Y H:i:s', $nextUpdateTime) . ' and now ' . date('d/m/Y H:i:s', $nowz) . '!'); + } + + p_debug(' OK CRL still valid until ' . date('d/m/Y H:i:s', $nextUpdateTime)); + $crlCertValid = true; + p_debug(' check if cert not revoked...'); + if (array_key_exists('revokedCertificates', $crl_parse['TBSCertList'])) { + $certSigner_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; + if (array_key_exists($certSigner_serialNumber, $crl_parse['TBSCertList']['revokedCertificates']['lists'])) { + throw new PDFException(' FAILED! Certificate Revoked!'); } - } else { - throw new PDFException(' FAILED! Wrong CRL.'); + } + + if ($crlCertValid) { + p_debug(' OK. VALID'); + $crlHex = current(unpack('H*', (string) $crlread['der'])); + $ltvResult['crl'] = $crlHex; } } else { - throw new PDFException(" FAILED! can't read crl"); + throw new PDFException(' FAILED! Wrong CRL.'); } } else { - throw new PDFException(" FAILED! can't get crl"); + throw new PDFException(" FAILED! can't read crl"); } + } else { + throw new PDFException(" FAILED! can't get crl"); } } } + if (! $ltvResult['issuer']) { return false; } + if (! $ltvResult['ocsp'] && ! $ltvResult['crl']) { return false; } @@ -501,6 +521,7 @@ private function tsa_parseResp(string $binaryTsaRespData) if (! @$ar = asn1::parse(bin2hex($binaryTsaRespData), 3)) { throw new PDFException(" can't parse invalid tsa Response."); } + $curr = $ar; foreach ($curr as $key => $value) { if ($value['type'] == '30') { @@ -508,6 +529,7 @@ private function tsa_parseResp(string $binaryTsaRespData) unset($curr[$key]); } } + $ar = $curr; $curr = $ar['TimeStampResp']; foreach ($curr as $key => $value) { @@ -515,14 +537,13 @@ private function tsa_parseResp(string $binaryTsaRespData) if ($value['type'] == '30' && ! array_key_exists('status', $curr)) { $curr['status'] = $curr[$key]; unset($curr[$key]); - } else { - if ($value['type'] == '30') { - $curr['timeStampToken'] = $curr[$key]; - unset($curr[$key]); - } + } elseif ($value['type'] == '30') { + $curr['timeStampToken'] = $curr[$key]; + unset($curr[$key]); } } } + $ar['TimeStampResp'] = $curr; $curr = $ar['TimeStampResp']['timeStampToken']; foreach ($curr as $key => $value) { @@ -531,22 +552,23 @@ private function tsa_parseResp(string $binaryTsaRespData) $curr['contentType'] = $curr[$key]; unset($curr[$key]); } + if ($value['type'] === 'a0') { $curr['content'] = $curr[$key]; unset($curr[$key]); } } } + $ar['TimeStampResp']['timeStampToken'] = $curr; $curr = $ar['TimeStampResp']['timeStampToken']['content']; foreach ($curr as $key => $value) { - if (is_numeric($key)) { - if ($value['type'] == '30') { - $curr['TSTInfo'] = $curr[$key]; - unset($curr[$key]); - } + if (is_numeric($key) && $value['type'] == '30') { + $curr['TSTInfo'] = $curr[$key]; + unset($curr[$key]); } } + $ar['TimeStampResp']['timeStampToken']['content'] = $curr; if (@$ar['TimeStampResp']['timeStampToken']['content']['hexdump'] != '') { return $ar; diff --git a/src/helpers/DependencyTreeObject.php b/src/helpers/DependencyTreeObject.php index 62df2ef..e54d84e 100644 --- a/src/helpers/DependencyTreeObject.php +++ b/src/helpers/DependencyTreeObject.php @@ -31,15 +31,14 @@ */ class DependencyTreeObject implements Stringable { - public int $is_child; + public int $is_child = 0; private array $children = []; public function __construct( private int $oid, - public mixed $info = null, + public mixed $info = null ) { - $this->is_child = 0; } public function __toString(): string @@ -55,7 +54,7 @@ public function addchild(int $oid, self $o): void { $this->children[$oid] = $o; if ($o->is_child !== 0) { - p_warning("object {$o->oid} is already a child of other object"); + p_warning(sprintf('object %d is already a child of other object', $o->oid)); } ++$o->is_child; @@ -67,7 +66,7 @@ public function addchild(int $oid, self $o): void public function children(): Generator { if (isset($this->children)) { - foreach ($this->children as $oid => $object) { + foreach (array_keys($this->children) as $oid) { yield $oid; } } @@ -79,21 +78,20 @@ public function children(): Generator protected function _getstr(?string $spaces = '', int $mychcount = 0): string { // $info = $this->oid . ($this->info?" ($this->info)":"") . (($this->is_child > 1)?" $this->is_child":""); - $info = $this->oid . ($this->info !== null ? " ({$this->info})" : ''); + $info = $this->oid . ($this->info !== null ? sprintf(' (%s)', $this->info) : ''); if ($spaces === null) { - $lines = ["{$spaces} " . json_decode('"\u2501"', false, 512, JSON_THROW_ON_ERROR) . " {$info}"]; + $lines = [$spaces . ' ' . json_decode('"\u2501"', false, 512, JSON_THROW_ON_ERROR) . ' ' . $info]; + } elseif ($mychcount === 0) { + $lines = [$spaces . ' ' . json_decode('"\u2514\u2500"', false, 512, JSON_THROW_ON_ERROR) . ' ' . $info]; } else { - if ($mychcount === 0) { - $lines = ["{$spaces} " . json_decode('"\u2514\u2500"', false, 512, JSON_THROW_ON_ERROR) . " {$info}"]; - } else { - $lines = ["{$spaces} " . json_decode('"\u251c\u2500"', false, 512, JSON_THROW_ON_ERROR) . " {$info}"]; - } + $lines = [$spaces . ' ' . json_decode('"\u251c\u2500"', false, 512, JSON_THROW_ON_ERROR) . ' ' . $info]; } + if (isset($this->children)) { $chcount = count($this->children); foreach ($this->children as $child) { $chcount--; - if (($spaces === null) || ($mychcount === 0)) { + if ($spaces === null || $mychcount === 0) { $lines[] = $child->_getstr($spaces . ' ', $chcount); } else { $lines[] = $child->_getstr($spaces . ' ' . json_decode('"\u2502"', false, 512, JSON_THROW_ON_ERROR), $chcount); @@ -123,11 +121,7 @@ protected function _getstr(?string $spaces = '', int $mychcount = 0): string function references_in_object(PDFObject $object): array { $type = $object['Type']; - if ($type !== false) { - $type = $type->val(); - } else { - $type = ''; - } + $type = $type !== false ? $type->val() : ''; $references = []; @@ -137,13 +131,11 @@ function references_in_object(PDFObject $object): array continue; } - if (array_key_exists($type, BLACKLIST)) { - if (in_array($key, BLACKLIST[$type], true)) { - continue; - } + if (array_key_exists($type, BLACKLIST) && in_array($key, BLACKLIST[$type], true)) { + continue; } - if (is_a($object[$key], PDFValueObject::class)) { + if ($object[$key] instanceof PDFValueObject) { $r_objects = references_in_object($object[$key]); } else { // Function get_object_referenced checks whether the value (or values in a list) have the form of object references, and if they have the form @@ -159,6 +151,7 @@ function references_in_object(PDFObject $object): array $r_objects = [$r_objects]; } } + // p_debug($key . "=>" . implode(",",$r_objects)); array_push($references, ...$r_objects); diff --git a/src/helpers/LoadHelpers.php b/src/helpers/LoadHelpers.php index 60c2a62..e1ef4d0 100644 --- a/src/helpers/LoadHelpers.php +++ b/src/helpers/LoadHelpers.php @@ -3,7 +3,7 @@ namespace ddn\sapp\helpers; foreach (glob(__DIR__ . '/*.php') as $i) { - include_once($i); + include_once $i; } class LoadHelpers diff --git a/src/helpers/StreamReader.php b/src/helpers/StreamReader.php index 3c6feb1..50eabba 100644 --- a/src/helpers/StreamReader.php +++ b/src/helpers/StreamReader.php @@ -45,7 +45,7 @@ public function __construct(?string $string = null, int $offset = 0) } $this->_buffer = $string; - $this->_bufferlen = strlen((string) $string); + $this->_bufferlen = strlen($string); $this->goto($offset); } diff --git a/src/helpers/UUID.php b/src/helpers/UUID.php index 1c8de34..327e48d 100644 --- a/src/helpers/UUID.php +++ b/src/helpers/UUID.php @@ -35,12 +35,12 @@ public static function v3($namespace, string $name): false|string // 16 bits for "time_hi_and_version", // four most significant bits holds version number 3 - (hexdec(substr($hash, 12, 4)) & 0x0fff) | 0x3000, + hexdec(substr($hash, 12, 4)) & 0x0fff | 0x3000, // 16 bits, 8 bits for "clk_seq_hi_res", // 8 bits for "clk_seq_low", // two most significant bits holds zero and one for variant DCE1.1 - (hexdec(substr($hash, 16, 4)) & 0x3fff) | 0x8000, + hexdec(substr($hash, 16, 4)) & 0x3fff | 0x8000, // 48 bits for "node" substr($hash, 20, 12) @@ -106,12 +106,12 @@ public static function v5($namespace, string $name): false|string // 16 bits for "time_hi_and_version", // four most significant bits holds version number 5 - (hexdec(substr($hash, 12, 4)) & 0x0fff) | 0x5000, + hexdec(substr($hash, 12, 4)) & 0x0fff | 0x5000, // 16 bits, 8 bits for "clk_seq_hi_res", // 8 bits for "clk_seq_low", // two most significant bits holds zero and one for variant DCE1.1 - (hexdec(substr($hash, 16, 4)) & 0x3fff) | 0x8000, + hexdec(substr($hash, 16, 4)) & 0x3fff | 0x8000, // 48 bits for "node" substr($hash, 20, 12) @@ -121,8 +121,7 @@ public static function v5($namespace, string $name): false|string public static function is_valid($uuid): bool { return preg_match( - '/^\{?[0-9a-f]{8}\-?[0-9a-f]{4}\-?[0-9a-f]{4}\-?' . - '[0-9a-f]{4}\-?[0-9a-f]{12}\}?$/i', + '/^\{?[0-9a-f]{8}\-?[0-9a-f]{4}\-?[0-9a-f]{4}\-?[0-9a-f]{4}\-?[0-9a-f]{12}\}?$/i', (string) $uuid ) === 1; } diff --git a/src/helpers/asn1.php b/src/helpers/asn1.php index b5f5254..83d5cae 100644 --- a/src/helpers/asn1.php +++ b/src/helpers/asn1.php @@ -24,22 +24,26 @@ public static function __callStatic($func, $params) $num = $asn1Tag; //value of array $hex = $params[0]; $val = $hex; - if (in_array($func, ['printable', 'utf8', 'ia5', 'visible', 't61'])) { // ($string) + if (in_array($func, ['printable', 'utf8', 'ia5', 'visible', 't61'], true)) { // ($string) $val = bin2hex((string) $hex); } + if ($func === 'int') { - $val = (strlen((string) $val) % 2 !== 0) ? "0{$val}" : (string) ($val); + $val = strlen((string) $val) % 2 !== 0 ? '0' . $val : (string) $val; } + if ($func === 'expl') { //expl($num, $hex) $num .= $params[0]; $val = $params[1]; } + if ($func === 'impl') { //impl($num="0") - $val = (! $val) ? '00' : $val; - $val = (strlen((string) $val) % 2 !== 0) ? "0{$val}" : $val; + $val = $val ?: '00'; + $val = strlen((string) $val) % 2 !== 0 ? '0' . $val : $val; return $num . $val; } + if ($func === 'other') { //OTHER($id, $hex, $chr = false) $id = $params[0]; $hex = $params[1]; @@ -49,8 +53,9 @@ public static function __callStatic($func, $params) $str = bin2hex((string) $hex); } - return ($id) . self::asn1_header($str) . $str; + return $id . self::asn1_header($str) . $str; } + if ($func === 'utime') { $time = $params[0]; //yymmddhhiiss $oldTz = date_default_timezone_get(); @@ -59,22 +64,27 @@ public static function __callStatic($func, $params) date_default_timezone_set($oldTz); $val = bin2hex($time . 'Z'); } + if ($func === 'gtime') { if (! $time = strtotime((string) $params[0])) { // echo "asn1::GTIME function strtotime cant recognize time!! please check at input=\"{$params[0]}\""; return false; } + $oldTz = date_default_timezone_get(); // date_default_timezone_set("UTC"); $time = date('YmdHis', $time); date_default_timezone_set($oldTz); $val = bin2hex($time . 'Z'); } + $hdr = self::asn1_header($val); return $num . $hdr . $val; } + // echo "asn1 \"$func\" not exists!"; + return null; } @@ -101,25 +111,20 @@ public static function parse(string $hex, int $maxDepth = 5): array $info['type'] = $k; $info['typeName'] = self::type($k); $info['value_hex'] = $v; - if (($currentDepth <= $maxDepth)) { + if ($currentDepth <= $maxDepth) { if ($k !== '06') { if (in_array($k, ['13', '18'], true)) { $info['value'] = hex2bin((string) $info['value_hex']); + } elseif (in_array($k, ['03', '02', 'a04'], true)) { + $info['value'] = $v; } else { - if (in_array($k, ['03', '02', 'a04'], true)) { - $info['value'] = $v; - } else { - $currentDepth++; - $parse_recursive = self::parse($v, $maxDepth); - $currentDepth--; - } + $currentDepth++; + $parse_recursive = self::parse($v, $maxDepth); + $currentDepth--; } } - if ($parse_recursive) { - $result[] = array_merge($info, $parse_recursive); - } else { - $result[] = $info; - } + + $result[] = $parse_recursive ? array_merge($info, $parse_recursive) : $info; } } } @@ -182,30 +187,34 @@ protected static function oneParse(string $hex): array|false if ($hex === '') { return false; } + if (! @ctype_xdigit($hex) || @strlen($hex) % 2 !== 0) { echo "input:\"{$hex}\" not hex string!.\n"; return false; } + $stop = false; while ($stop == false) { $asn1_type = substr($hex, 0, 2); $tlv_tagLength = hexdec(substr($hex, 2, 2)); if ($tlv_tagLength > 127) { $tlv_lengthLength = $tlv_tagLength - 128; - $tlv_valueLength = substr($hex, 4, ($tlv_lengthLength * 2)); + $tlv_valueLength = substr($hex, 4, $tlv_lengthLength * 2); } else { $tlv_lengthLength = 0; - $tlv_valueLength = substr($hex, 2, 2 + ($tlv_lengthLength * 2)); + $tlv_valueLength = substr($hex, 2, 2 + $tlv_lengthLength * 2); } + if ($tlv_lengthLength > 4) { // limit tlv_lengthLength to FFFF return false; } + $tlv_valueLength = hexdec($tlv_valueLength); - $totalTlLength = 2 + 2 + ($tlv_lengthLength * 2); + $totalTlLength = 2 + 2 + $tlv_lengthLength * 2; $tlv_value = substr($hex, $totalTlLength, $tlv_valueLength * 2); - $remain = substr($hex, $totalTlLength + ($tlv_valueLength * 2)); - $newhexdump = substr($hex, 0, $totalTlLength + ($tlv_valueLength * 2)); + $remain = substr($hex, $totalTlLength + $tlv_valueLength * 2); + $newhexdump = substr($hex, 0, $totalTlLength + $tlv_valueLength * 2); $result[] = [ 'tlv_tagLength' => strlen(dechex($tlv_tagLength)) % 2 === 0 ? dechex($tlv_tagLength) : '0' . dechex($tlv_tagLength), 'tlv_lengthLength' => $tlv_lengthLength, @@ -214,7 +223,7 @@ protected static function oneParse(string $hex): array|false 'typ' => $asn1_type, 'tlv_value' => $tlv_value, ]; - if ($remain == '') { // if remains string was empty & contents also empty, function return FALSE + if ($remain === '') { // if remains string was empty & contents also empty, function return FALSE $stop = true; } else { $hex = $remain; @@ -223,6 +232,7 @@ protected static function oneParse(string $hex): array|false return $result; } + // =====End ASN.1 Parser section===== // =====Begin ASN.1 Builder section===== @@ -239,8 +249,9 @@ protected static function asn1_header(string $str): string $len = strlen($str) / 2; $ret = dechex($len); if (strlen($ret) % 2 !== 0) { - $ret = "0{$ret}"; + $ret = '0' . $ret; } + $headerLength = strlen($ret) / 2; if ($len > 127) { $ret = '8' . $headerLength . $ret; @@ -275,9 +286,11 @@ private static function asn1Tag($name): string|false if (array_key_exists($name, $functionList)) { return $functionList[$name]; } + // echo "func \"$name\" not available"; return false; } + // =====End ASN.1 Builder section===== } diff --git a/src/helpers/contentgeneration.php b/src/helpers/contentgeneration.php index 0f55cfe..165c15d 100644 --- a/src/helpers/contentgeneration.php +++ b/src/helpers/contentgeneration.php @@ -21,6 +21,7 @@ namespace ddn\sapp\helpers; +use ddn\sapp\PDFException; use ddn\sapp\pdfvalue\PDFValueList; use ddn\sapp\pdfvalue\PDFValueObject; use ddn\sapp\pdfvalue\PDFValueReference; @@ -83,7 +84,7 @@ function _create_image_objects($info, $object_factory): array $image['ColorSpace']->push([ '/Indexed', '/DeviceRGB', - (strlen((string) $info['pal']) / 3) - 1, + strlen((string) $info['pal']) / 3 - 1, new PDFValueReference($streamobject->get_oid()), ]); $objects[] = $streamobject; @@ -124,6 +125,7 @@ function _create_image_objects($info, $object_factory): array foreach ($smasks as $smask) { $objects[] = $smask; } + $image['SMask'] = new PDFValueReference($smask->get_oid()); } @@ -147,11 +149,7 @@ function is_base64($string): bool } // Encode the string again - if (base64_encode($decoded) != $string) { - return false; - } - - return true; + return base64_encode($decoded) == $string; } /** @@ -182,15 +180,13 @@ function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, if ($filename[0] === '@') { $filecontent = substr((string) $filename, 1); + } elseif (is_base64($filename)) { + $filecontent = base64_decode((string) $filename, true); } else { - if (is_base64($filename)) { - $filecontent = base64_decode((string) $filename, true); - } else { - $filecontent = @file_get_contents($filename); - - if ($filecontent === false) { - throw new PDFException('failed to get the image'); - } + $filecontent = @file_get_contents($filename); + + if ($filecontent === false) { + throw new PDFException('failed to get the image'); } } @@ -220,6 +216,7 @@ function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, if ($w === null) { $w = -96; } + if ($h === null) { $h = -96; } @@ -227,12 +224,15 @@ function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, if ($w < 0) { $w = -$info['w'] * 72 / $w; } + if ($h < 0) { $h = -$info['h'] * 72 / $h; } + if ($w == 0) { $w = $h * $info['w'] / $info['h']; } + if ($h == 0) { $h = $w * $info['h'] / $info['w']; } @@ -282,6 +282,7 @@ function _add_image($object_factory, $filename, $x = 0, $y = 0, $w = 0, $h = 0, $data .= rx($angle); $data .= tx(-0.5, -0.5); } + $data .= sprintf(' /%s Do Q', $info['i']); $resources = new PDFValueObject([ diff --git a/src/helpers/fpdfhelpers.php b/src/helpers/fpdfhelpers.php index a567c9d..12942b0 100644 --- a/src/helpers/fpdfhelpers.php +++ b/src/helpers/fpdfhelpers.php @@ -44,9 +44,11 @@ function _parsejpg($filecontent): array if (! $a) { throw new PDFException('Missing or incorrect image'); } + if ($a[2] != 2) { throw new PDFException('Not a JPEG image'); } + if (! isset($a['channels']) || $a['channels'] == 3) { $colspace = 'DeviceRGB'; } elseif ($a['channels'] == 4) { @@ -54,6 +56,7 @@ function _parsejpg($filecontent): array } else { $colspace = 'DeviceGray'; } + $bpc = $a['bits'] ?? 8; $data = $filecontent; @@ -78,8 +81,8 @@ function _parsepng($filecontent): array function _parsepngstream(&$f): array { // Check signature - if (($res = _readstream($f, 8)) != chr(137) . 'PNG' . chr(13) . chr(10) . chr(26) . chr(10)) { - throw new PDFException("Not a PNG image {$res}"); + if (($res = _readstream($f, 8)) !== chr(137) . 'PNG' . chr(13) . chr(10) . chr(26) . chr(10)) { + throw new PDFException('Not a PNG image ' . $res); } // Read header chunk @@ -87,12 +90,14 @@ function _parsepngstream(&$f): array if (_readstream($f, 4) !== 'IHDR') { throw new PDFException('Incorrect PNG image'); } + $w = _readint($f); $h = _readint($f); $bpc = ord(_readstream($f, 1)); if ($bpc > 8) { throw new PDFException('16-bit depth not supported'); } + $ct = ord(_readstream($f, 1)); if ($ct == 0 || $ct == 4) { $colspace = 'DeviceGray'; @@ -103,15 +108,19 @@ function _parsepngstream(&$f): array } else { throw new PDFException('Unknown color type'); } + if (ord(_readstream($f, 1)) != 0) { throw new PDFException('Unknown compression method'); } - if (ord(_readstream($f, 1)) != 0) { + + if (ord(_readstream($f, 1)) !== 0) { throw new PDFException('Unknown filter method'); } - if (ord(_readstream($f, 1)) != 0) { + + if (ord(_readstream($f, 1)) !== 0) { throw new PDFException('Interlacing not supported'); } + _readstream($f, 4); $dp = '/Predictor 15 /Colors ' . ($colspace === 'DeviceRGB' ? 3 : 1) . ' /BitsPerComponent ' . $bpc . ' /Columns ' . $w; @@ -139,6 +148,7 @@ function _parsepngstream(&$f): array $trns = [$pos]; } } + _readstream($f, 4); } elseif ($type === 'IDAT') { // Read image data block @@ -151,9 +161,10 @@ function _parsepngstream(&$f): array } } while ($n); - if ($colspace === 'Indexed' && empty($pal)) { + if ($colspace === 'Indexed' && ($pal === '' || $pal === '0')) { throw new PDFException('Missing palette in image'); } + $info = [ 'w' => $w, 'h' => $h, @@ -167,12 +178,14 @@ function _parsepngstream(&$f): array if ($ct >= 4) { // Extract alpha channel if (! function_exists('gzuncompress')) { - throw new PDFException('Zlib not available, can\'t handle alpha channel'); + throw new PDFException("Zlib not available, can't handle alpha channel"); } + $data = gzuncompress($data); if ($data === false) { throw new PDFException('failed to uncompress the image'); } + $color = ''; $alpha = ''; if ($ct == 4) { @@ -198,6 +211,7 @@ function _parsepngstream(&$f): array $alpha .= preg_replace('/.{3}(.)/s', '$1', $line); } } + unset($data); $data = gzcompress($color); $info['smask'] = gzcompress($alpha); @@ -207,6 +221,7 @@ function _parsepngstream(&$f): array $this->PDFVersion = '1.4'; */ } + $info['data'] = $data; return $info; @@ -221,6 +236,7 @@ function _readstream($f, $n): string if ($s === false) { throw new PDFException('Error while reading the stream'); } + $n -= strlen((string) $s); $res .= $s; } diff --git a/src/helpers/helpers.php b/src/helpers/helpers.php index 235a944..3cafa92 100644 --- a/src/helpers/helpers.php +++ b/src/helpers/helpers.php @@ -103,8 +103,9 @@ function varval($e) $a = []; foreach ($e as $k => $v) { $v = varval($v); - $a[] = "{$k} => {$v}"; + $a[] = sprintf('%s => %s', $k, $v); } + $retval = '[ ' . implode(', ', $a) . ' ]'; } @@ -123,8 +124,9 @@ function p_stderr(string &$e, string $tag = 'Error', int $level = 1): void { $dinfo = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT); $dinfo = $dinfo[$level]; - $e = sprintf("{$tag} info at %s:%d: %s", $dinfo['file'], $dinfo['line'], varval($e)); - fwrite(STDERR, "{$e}\n"); + + $e = sprintf('%s info at %s:%d: %s', $tag, $dinfo['file'], $dinfo['line'], varval($e)); + fwrite(STDERR, $e . PHP_EOL); } /** @@ -183,9 +185,11 @@ function get_random_string($length = 8, $extended = false, $hard = false): strin if ($extended === true) { $codeAlphabet .= "!\"#$%&'()*+,-./:;<=>?@[\\]_{}"; } + if ($hard === true) { $codeAlphabet .= '^`|~'; } + $max = strlen($codeAlphabet); for ($i = 0; $i < $length; $i++) { $token .= $codeAlphabet[random_int(0, $max - 1)]; @@ -225,6 +229,7 @@ function show_bytes($str, $columns = null): string if ($columns === null) { $columns = strlen((string) $str); } + $c = $columns; for ($i = 0, $iMax = strlen((string) $str); $i < $iMax; $i++) { $result .= sprintf('%02x ', ord($str[$i])); @@ -247,7 +252,7 @@ function show_bytes($str, $columns = null): string */ function timestamp_to_pdfdatestring(?DateTimeInterface $date = null): string { - if ($date === null) { + if (! $date instanceof DateTimeInterface) { $date = new DateTime(); } @@ -266,5 +271,5 @@ function timestamp_to_pdfdatestring(?DateTimeInterface $date = null): string */ function get_pdf_formatted_date(int $time) { - return substr_replace(date('YmdHisO', $time), '\'', (0 - 2), 0) . '\''; + return substr_replace(date('YmdHisO', $time), "'", 0 - 2, 0) . "'"; } diff --git a/src/helpers/x509.php b/src/helpers/x509.php index 9ef1ef2..557a9a4 100644 --- a/src/helpers/x509.php +++ b/src/helpers/x509.php @@ -39,6 +39,7 @@ public static function tsa_query($binaryData, $hashAlg = 'sha256'): false|string if (! array_key_exists($hashAlg, $hexOidHashAlgos)) { return false; } + $hash = hash($hashAlg, (string) $binaryData); $tsReqData = asn1::seq( asn1::int(1) . @@ -69,12 +70,14 @@ public static function ocsp_response_parse(string $binaryOcspResp, &$status = '' } else { return false; } + foreach ($ocsp as $key => $value) { if (is_numeric($key)) { if ($value['type'] === '0a') { $ocsp['responseStatus'] = $value['value_hex']; unset($ocsp[$key]); } + if ($value['type'] === 'a0') { $ocsp['responseBytes'] = $value; unset($ocsp[$key]); @@ -86,6 +89,7 @@ public static function ocsp_response_parse(string $binaryOcspResp, &$status = '' unset($ocsp['value_hex']); } } + //OCSPResponseStatus ::= ENUMERATED // successful (0), --Response has valid confirmations // malformedRequest (1), --Illegal confirmation request @@ -104,9 +108,11 @@ public static function ocsp_response_parse(string $binaryOcspResp, &$status = '' return false; } + if (! @$curr = $ocsp['responseBytes']) { return false; } + foreach ($curr as $key => $value) { if (is_numeric($key)) { if ($value['type'] == '30') { @@ -120,6 +126,7 @@ public static function ocsp_response_parse(string $binaryOcspResp, &$status = '' unset($curr['depth']); } } + $ocsp['responseBytes'] = $curr; $curr = $ocsp['responseBytes']['response']; foreach ($curr as $key => $value) { @@ -134,6 +141,7 @@ public static function ocsp_response_parse(string $binaryOcspResp, &$status = '' unset($curr['depth']); } } + $ocsp['responseBytes']['response'] = $curr; $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']; foreach ($curr as $key => $value) { @@ -143,21 +151,25 @@ public static function ocsp_response_parse(string $binaryOcspResp, &$status = '' unset($curr[$key]); continue; } + if ($value['type'] == '30' && ! array_key_exists('signatureAlgorithm', $curr)) { $curr['signatureAlgorithm'] = $value[0]['value_hex']; unset($curr[$key]); continue; } + if ($value['type'] == '03') { $curr['signature'] = substr((string) $value['value_hex'], 2); unset($curr[$key]); } + if ($value['type'] === 'a0') { foreach ($value[0] as $certsK => $certsV) { if (is_numeric($certsK)) { $certs[$certsK] = $certsV['value_hex']; } } + $curr['certs'] = $certs; unset($curr[$key]); } @@ -167,6 +179,7 @@ public static function ocsp_response_parse(string $binaryOcspResp, &$status = '' unset($curr['depth']); } } + $ocsp['responseBytes']['response']['BasicOCSPResponse'] = $curr; $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']; foreach ($curr as $key => $value) { @@ -175,22 +188,27 @@ public static function ocsp_response_parse(string $binaryOcspResp, &$status = '' $curr['version'] = $value[0]['value']; unset($curr[$key]); } + if ($value['type'] === 'a1' && ! array_key_exists('responderID', $curr)) { $curr['responderID'] = $value; unset($curr[$key]); } + if ($value['type'] === 'a2') { $curr['responderID'] = $value; unset($curr[$key]); } + if ($value['type'] == '18') { $curr['producedAt'] = $value['value']; unset($curr[$key]); } + if ($value['type'] == '30') { $curr['responses'] = $value; unset($curr[$key]); } + if ($value['type'] === 'a1') { $curr['responseExtensions'] = $value; unset($curr[$key]); @@ -201,6 +219,7 @@ public static function ocsp_response_parse(string $binaryOcspResp, &$status = '' unset($curr['depth']); } } + $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData'] = $curr; $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions']; foreach ($curr as $key => $value) { @@ -215,6 +234,7 @@ public static function ocsp_response_parse(string $binaryOcspResp, &$status = '' unset($curr['depth']); } } + $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions'] = $curr; $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions']['lists']; foreach ($curr as $key => $value) { @@ -225,6 +245,7 @@ public static function ocsp_response_parse(string $binaryOcspResp, &$status = '' } else { $curr[$value[0]['value_hex']] = $value[1]; } + unset($curr[$key]); } } else { @@ -233,6 +254,7 @@ public static function ocsp_response_parse(string $binaryOcspResp, &$status = '' unset($curr['depth']); } } + $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responseExtensions']['lists'] = $curr; $curr = $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses']; $i = 0; @@ -246,19 +268,24 @@ public static function ocsp_response_parse(string $binaryOcspResp, &$status = '' if ($certIDv['type'] == '30') { $certID['hashAlgorithm'] = $certIDv[0]['value_hex']; } + if ($certIDv['type'] == '04' && ! array_key_exists('issuerNameHash', $certID)) { $certID['issuerNameHash'] = $certIDv['value_hex']; } + if ($certIDv['type'] == '04') { $certID['issuerKeyHash'] = $certIDv['value_hex']; } + if ($certIDv['type'] == '02') { $certID['serialNumber'] = $certIDv['value_hex']; } } } + $cert['certID'] = $certID; } + if ($SingleResponseK == 1) { if ($SingleResponseV['type'] == '82') { $certStatus = 'unknown'; @@ -267,19 +294,24 @@ public static function ocsp_response_parse(string $binaryOcspResp, &$status = '' } else { $certStatus = 'revoked'; } + $cert['certStatus'] = $certStatus; } + if ($SingleResponseK == 2) { $cert['thisUpdate'] = $SingleResponseV['value']; } + if ($SingleResponseK == 3) { $cert['nextUpdate'] = $SingleResponseV[0]['value']; } + if ($SingleResponseK == 4) { $cert['singleExtensions'] = $SingleResponseV; } } } + $curr[$i] = $cert; } else { unset($curr[$key]); @@ -288,6 +320,7 @@ public static function ocsp_response_parse(string $binaryOcspResp, &$status = '' unset($curr['depth']); } } + $ocsp['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses'] = $curr; $arrModel = [ 'responseStatus' => '', @@ -299,7 +332,7 @@ public static function ocsp_response_parse(string $binaryOcspResp, &$status = '' $differ = array_diff_key($arrModel, $ocsp); if (count($differ) == 0) { $differ = array_diff_key($arrModel['responseBytes'], $ocsp['responseBytes']); - if (count($differ) > 0) { + if ($differ !== []) { return false; } } else { @@ -324,8 +357,7 @@ public static function ocsp_response_parse(string $binaryOcspResp, &$status = '' public static function ocsp_request(string $serialNumber, string $issuerNameHash, string $issuerKeyHash, bool|string $signer_cert = false, bool|string $signer_key = false, bool|string $subjectName = false) { $hashAlgorithm = asn1::seq( - '06052B0E03021A' . // OBJ_sha1 - '0500' + '06052B0E03021A0500' ); $issuerNameHash = asn1::oct($issuerNameHash); $issuerKeyHash = asn1::oct($issuerKeyHash); @@ -337,6 +369,7 @@ public static function ocsp_request(string $serialNumber, string $issuerNameHash } else { $requestorName = false; } + $requestList = asn1::seq($Request); // add more request into sequence $rand = microtime(true) * random_int(0, mt_getrandmax()); $nonce = md5(base64_encode($rand) . $rand); @@ -351,9 +384,9 @@ public static function ocsp_request(string $serialNumber, string $issuerNameHash if (! openssl_sign(hex2bin($TBSRequest), $signature_value, $signer_key)) { return false; } + $signatureAlgorithm = asn1::seq( - '06092A864886F70D010105' . // OBJ_sha1WithRSAEncryption. - '0500' + '06092A864886F70D0101050500' ); $signature = asn1::bit('00' . bin2hex($signature_value)); $signer_cert = self::x509_pem2der($signer_cert); @@ -379,11 +412,13 @@ public static function crl_pem2der(string $crl): false|string if ($beginPos === false) { return false; } + $crl = substr($crl, $beginPos + strlen($begin)); $endPos = stripos($crl, $end); if ($endPos === false) { return false; } + $crl = substr($crl, 0, $endPos); $crl = str_replace(["\n", "\r"], '', $crl); @@ -402,9 +437,11 @@ public static function crl_read(string $crl): false|array if (! $crlparse = self::parsecrl($crl)) { // if cant read, thats not crl return false; } + if (! $dercrl = self::crl_pem2der($crl)) { // if not pem, thats already der $dercrl = $crl; } + $res['der'] = $dercrl; $res['parse'] = $crlparse; @@ -428,11 +465,13 @@ public static function x509_pem2der(string $pem): string|false $i = 0; $cert_pem = false; foreach ($arr_x509_pem as $val) { - if ($i > 0 && $i < ($numarr - 2)) { + if ($i > 0 && $i < $numarr - 2) { $cert_pem .= $val; } + $i++; } + $x509_der = base64_decode($cert_pem, true); } @@ -469,12 +508,14 @@ public static function get_cert(string $certin): string|false return self::x509_pem2der($cert); } + $pem = @self::x509_der2pem($certin); if ($rsccert = @openssl_x509_read($pem)) { openssl_x509_export($rsccert, $cert); return self::x509_pem2der($cert); } + return false; } @@ -492,6 +533,7 @@ public static function readcert($cert_in, bool|string $oidprint = false) if (! $der = self::get_cert($cert_in)) { return false; } + $hex = bin2hex($der); $curr = asn1::parse($hex, 10); foreach ($curr as $key => $value) { @@ -500,6 +542,7 @@ public static function readcert($cert_in, bool|string $oidprint = false) unset($curr[$key]); } } + $ar = $curr; $curr = $ar['cert']; foreach ($curr as $key => $value) { @@ -508,10 +551,12 @@ public static function readcert($cert_in, bool|string $oidprint = false) $curr['tbsCertificate'] = $curr[$key]; unset($curr[$key]); } + if ($value['type'] == '30') { $curr['signatureAlgorithm'] = self::oidfromhex($value[0]['value_hex']); unset($curr[$key]); } + if ($value['type'] == '03') { $curr['signatureValue'] = substr((string) $value['value'], 2); unset($curr[$key]); @@ -520,6 +565,7 @@ public static function readcert($cert_in, bool|string $oidprint = false) unset($curr[$key]); } } + $ar['cert'] = $curr; $ar['cert']['sha1Fingerprint'] = hash('sha1', $der); $curr = $ar['cert']['tbsCertificate']; @@ -529,15 +575,18 @@ public static function readcert($cert_in, bool|string $oidprint = false) $curr['version'] = $value[0]['value']; unset($curr[$key]); } + if ($value['type'] == '02') { $curr['serialNumber'] = $value['value']; unset($curr[$key]); } + if ($value['type'] == '30' && ! array_key_exists('signature', $curr)) { $curr['signature'] = $value[0]['value_hex']; unset($curr[$key]); continue; } + if ($value['type'] == '30' && ! array_key_exists('issuer', $curr)) { foreach ($value as $issuerK => $issuerV) { if (is_numeric($issuerK)) { @@ -548,9 +597,11 @@ public static function readcert($cert_in, bool|string $oidprint = false) } else { $issuerOID = self::oidfromhex($issuerOID); } + $issuer[$issuerOID][] = hex2bin((string) $issuerV[0][1]['value_hex']); } } + $hexdump = $value['hexdump']; $issuer['sha1'] = hash('sha1', hex2bin((string) $hexdump)); $issuer['opensslHash'] = self::opensslSubjHash($hexdump); @@ -559,12 +610,14 @@ public static function readcert($cert_in, bool|string $oidprint = false) unset($curr[$key]); continue; } + if ($value['type'] == '30' && ! array_key_exists('validity', $curr)) { $curr['validity']['notBefore'] = hex2bin((string) $value[0]['value_hex']); $curr['validity']['notAfter'] = hex2bin((string) $value[1]['value_hex']); unset($curr[$key]); continue; } + if ($value['type'] == '30' && ! array_key_exists('subject', $curr)) { foreach ($value as $subjectK => $subjectV) { if (is_numeric($subjectK)) { @@ -575,9 +628,11 @@ public static function readcert($cert_in, bool|string $oidprint = false) } else { $subjectOID = self::oidfromhex($subjectOID); } + $subject[$subjectOID][] = hex2bin((string) $subjectV[0][1]['value_hex']); } } + $hexdump = $value['hexdump']; $subject['sha1'] = hash('sha1', hex2bin((string) $hexdump)); $subject['opensslHash'] = self::opensslSubjHash($hexdump); @@ -586,12 +641,14 @@ public static function readcert($cert_in, bool|string $oidprint = false) unset($curr[$key]); continue; } + if ($value['type'] == '30' && ! array_key_exists('subjectPublicKeyInfo', $curr)) { foreach ($value as $subjectPublicKeyInfoK => $subjectPublicKeyInfoV) { if (is_numeric($subjectPublicKeyInfoK)) { if ($subjectPublicKeyInfoV['type'] == '30') { $subjectPublicKeyInfo['algorithm'] = self::oidfromhex($subjectPublicKeyInfoV[0]['value_hex']); } + if ($subjectPublicKeyInfoV['type'] == '03') { $subjectPublicKeyInfo['subjectPublicKey'] = substr((string) $subjectPublicKeyInfoV['value'], 2); } @@ -599,14 +656,16 @@ public static function readcert($cert_in, bool|string $oidprint = false) unset($curr[$key]); } } + $subjectPublicKeyInfo['hex'] = $value['hexdump']; $subjectPublicKey_parse = asn1::parse($subjectPublicKeyInfo['subjectPublicKey']); - $subjectPublicKeyInfo['keyLength'] = (strlen(substr((string) $subjectPublicKey_parse[0][0]['value'], 2)) / 2) * 8; + $subjectPublicKeyInfo['keyLength'] = strlen(substr((string) $subjectPublicKey_parse[0][0]['value'], 2)) / 2 * 8; $subjectPublicKeyInfo['sha1'] = hash('sha1', pack('H*', $subjectPublicKeyInfo['subjectPublicKey'])); $curr['subjectPublicKeyInfo'] = $subjectPublicKeyInfo; unset($curr[$key]); continue; } + if ($value['type'] === 'a3') { $curr['attributes'] = $value[0]; unset($curr[$key]); @@ -615,6 +674,7 @@ public static function readcert($cert_in, bool|string $oidprint = false) $tbsCertificateTag[$key] = $value; } } + $ar['cert']['tbsCertificate'] = $curr; if (array_key_exists('attributes', $ar['cert']['tbsCertificate'])) { $curr = $ar['cert']['tbsCertificate']['attributes']; @@ -628,25 +688,31 @@ public static function readcert($cert_in, bool|string $oidprint = false) $critical = 1; $extvalue = $value[2]; } + if ($name_hex === '551d0e') { // OBJ_subject_key_identifier $extvalue = $value[1][0]['value_hex']; } + if ($name_hex === '551d23') { // OBJ_authority_key_identifier foreach ($value[1][0] as $OBJ_authority_key_identifierKey => $OBJ_authority_key_identifierVal) { if (is_numeric($OBJ_authority_key_identifierKey)) { if ($OBJ_authority_key_identifierVal['type'] == '80') { $OBJ_authority_key_identifier['keyid'] = $OBJ_authority_key_identifierVal['value_hex']; } + if ($OBJ_authority_key_identifierVal['type'] === 'a1') { $OBJ_authority_key_identifier['issuerName'] = $OBJ_authority_key_identifierVal['value_hex']; } + if ($OBJ_authority_key_identifierVal['type'] == '82') { $OBJ_authority_key_identifier['issuerSerial'] = $OBJ_authority_key_identifierVal['value_hex']; } } } + $extvalue = $OBJ_authority_key_identifier; } + if ($name_hex === '2b06010505070101') { // OBJ_info_access foreach ($value[1][0] as $OBJ_info_accessK => $OBJ_info_accessV) { if (is_numeric($OBJ_info_accessK)) { @@ -656,36 +722,42 @@ public static function readcert($cert_in, bool|string $oidprint = false) $OBJ_info_access[$OBJ_info_accessNAME][] = hex2bin((string) $OBJ_info_accessV[1]['value_hex']); } } + $extvalue = $OBJ_info_access; } + if ($name_hex === '551d1f') { // OBJ_crl_distribution_points 551d1f foreach ($value[1][0] as $OBJ_crl_distribution_pointsK => $OBJ_crl_distribution_pointsV) { if (is_numeric($OBJ_crl_distribution_pointsK)) { $OBJ_crl_distribution_points[] = hex2bin((string) $OBJ_crl_distribution_pointsV[0][0][0]['value_hex']); } } + $extvalue = $OBJ_crl_distribution_points; } + if ($name_hex === '551d0f') { // OBJ_key_usage // $extvalue = self::parse_keyUsage($extvalue[0]['value']); } + if ($name_hex === '551d13') { // OBJ_basic_constraints $bc['ca'] = '0'; $bc['pathLength'] = ''; foreach ($extvalue[0] as $bck => $bcv) { if (is_numeric($bck)) { - if ($bcv['type'] == '01') { - if ($bcv['value_hex'] === 'ff') { - $bc['ca'] = '1'; - } + if ($bcv['type'] == '01' && $bcv['value_hex'] === 'ff') { + $bc['ca'] = '1'; } + if ($bcv['type'] == '02') { $bc['pathLength'] = $bcv['value']; } } } + $extvalue = $bc; } + if ($name_hex === '551d25') { // OBJ_ext_key_usage 551d1f foreach ($extvalue[0] as $OBJ_ext_key_usageK => $OBJ_ext_key_usageV) { if (is_numeric($OBJ_ext_key_usageK)) { @@ -695,8 +767,10 @@ public static function readcert($cert_in, bool|string $oidprint = false) $OBJ_ext_key_usage[] = $OBJ_ext_key_usageNAME; } } + $extvalue = $OBJ_ext_key_usage; } + $extsVal = [ 'name_hex' => $value[0]['value_hex'], 'name_oid' => self::oidfromhex($value[0]['value_hex']), @@ -711,12 +785,14 @@ public static function readcert($cert_in, bool|string $oidprint = false) } else { $extNameOID = self::oidfromhex($extNameOID); } + $curr[$extNameOID] = $extsVal; unset($curr[$key]); } } else { unset($curr[$key]); } + unset($ar['cert']['tbsCertificate']['attributes']); $ar['cert']['tbsCertificate']['attributes'] = $curr; } @@ -746,16 +822,21 @@ private static function opensslSubjHash(string $hex_subjSequence): array ); } } + $tohash = pack('H*', $hex_subjSequence_new); $openssl_subjHash_new = hash('sha1', $tohash); $openssl_subjHash_new = substr($openssl_subjHash_new, 0, 8); + $openssl_subjHash_new2 = str_split($openssl_subjHash_new, 2); $openssl_subjHash_new2 = array_reverse($openssl_subjHash_new2); + $openssl_subjHash_new = implode('', $openssl_subjHash_new2); $openssl_subjHash_old = hash('md5', hex2bin($hex_subjSequence)); $openssl_subjHash_old = substr($openssl_subjHash_old, 0, 8); + $openssl_subjHash_old2 = str_split($openssl_subjHash_old, 2); $openssl_subjHash_old2 = array_reverse($openssl_subjHash_old2); + $openssl_subjHash_old = implode('', $openssl_subjHash_old2); return [ @@ -774,11 +855,8 @@ private static function opensslSubjHash(string $hex_subjSequence): array */ private static function parsecrl(array $crl, bool $oidprint = false): false|array { - if ($derCrl = self::crl_pem2der($crl)) { - $derCrl = bin2hex($derCrl); - } else { - $derCrl = bin2hex($crl); - } + $derCrl = ($derCrl = self::crl_pem2der($crl)) ? bin2hex($derCrl) : bin2hex($crl); + $curr = asn1::parse($derCrl, 7); foreach ($curr as $key => $value) { if ($value['type'] == '30') { @@ -786,10 +864,12 @@ private static function parsecrl(array $crl, bool $oidprint = false): false|arra unset($curr[$key]); } } + $ar = $curr; if (! array_key_exists('crl', $ar)) { return false; } + $curr = $ar['crl']; foreach ($curr as $key => $value) { if (is_numeric($key)) { @@ -797,10 +877,12 @@ private static function parsecrl(array $crl, bool $oidprint = false): false|arra $curr['TBSCertList'] = $curr[$key]; unset($curr[$key]); } + if ($value['type'] == '30') { $curr['signatureAlgorithm'] = self::oidfromhex($value[0]['value_hex']); unset($curr[$key]); } + if ($value['type'] == '03') { $curr['signature'] = substr((string) $value['value'], 2); unset($curr[$key]); @@ -809,6 +891,7 @@ private static function parsecrl(array $crl, bool $oidprint = false): false|arra unset($curr[$key]); } } + $ar['crl'] = $curr; $curr = $ar['crl']['TBSCertList']; foreach ($curr as $key => $value) { @@ -817,31 +900,37 @@ private static function parsecrl(array $crl, bool $oidprint = false): false|arra $curr['version'] = $curr[$key]['value']; unset($curr[$key]); } + if ($value['type'] == '30' && ! array_key_exists('signature', $curr)) { $curr['signature'] = $value[0]['value_hex']; unset($curr[$key]); continue; } + if ($value['type'] == '30' && ! array_key_exists('issuer', $curr)) { $curr['issuer'] = $value; unset($curr[$key]); continue; } + if ($value['type'] == '17' && ! array_key_exists('thisUpdate', $curr)) { $curr['thisUpdate'] = hex2bin((string) $value['value_hex']); unset($curr[$key]); continue; } + if ($value['type'] == '17' && ! array_key_exists('nextUpdate', $curr)) { $curr['nextUpdate'] = hex2bin((string) $value['value_hex']); unset($curr[$key]); continue; } + if ($value['type'] == '30' && ! array_key_exists('revokedCertificates', $curr)) { $curr['revokedCertificates'] = $value; unset($curr[$key]); continue; } + if ($value['type'] === 'a0') { $curr['crlExtensions'] = $curr[$key]; unset($curr[$key]); @@ -850,6 +939,7 @@ private static function parsecrl(array $crl, bool $oidprint = false): false|arra unset($curr[$key]); } } + $ar['crl']['TBSCertList'] = $curr; if (array_key_exists('revokedCertificates', $curr)) { $curr = $ar['crl']['TBSCertList']['revokedCertificates']; @@ -867,9 +957,11 @@ private static function parsecrl(array $crl, bool $oidprint = false): false|arra unset($curr['typeName']); } } + $curr['lists'] = $lists; $ar['crl']['TBSCertList']['revokedCertificates'] = $curr; } + if (array_key_exists('crlExtensions', $ar['crl']['TBSCertList'])) { $curr = $ar['crl']['TBSCertList']['crlExtensions'][0]; unset($ar['crl']['TBSCertList']['crlExtensions']); @@ -879,37 +971,46 @@ private static function parsecrl(array $crl, bool $oidprint = false): false|arra if ($oidprint === 'oid') { $attributes_name = self::oidfromhex($value[0]['value_hex']); } + if ($oidprint === 'hex') { $attributes_name = $value[0]['value_hex']; } + $attributes_oid = self::oidfromhex($value[0]['value_hex']); if ($value['type'] == '30') { $crlExtensionsValue = $value[1][0]; if ($attributes_oid === '2.5.29.20') { // OBJ_crl_number $crlExtensionsValue = $crlExtensionsValue['value']; } + if ($attributes_oid === '2.5.29.35') { // OBJ_authority_key_identifier foreach ($crlExtensionsValue as $authority_key_identifierValueK => $authority_key_identifierV) { if (is_numeric($authority_key_identifierValueK)) { if ($authority_key_identifierV['type'] == '80') { $authority_key_identifier['keyIdentifier'] = $authority_key_identifierV['value_hex']; } + if ($authority_key_identifierV['type'] === 'a1') { $authority_key_identifier['authorityCertIssuer'] = $authority_key_identifierV['value_hex']; } + if ($authority_key_identifierV['type'] == '82') { $authority_key_identifier['authorityCertSerialNumber'] = $authority_key_identifierV['value_hex']; } } } + $crlExtensionsValue = $authority_key_identifier; } + $attribute_list = $crlExtensionsValue; } + $ar['crl']['TBSCertList']['crlExtensions'][$attributes_name] = $attribute_list; } } } + $curr = $ar['crl']['TBSCertList']['issuer']; foreach ($curr as $key => $value) { if (is_numeric($key)) { @@ -921,6 +1022,7 @@ private static function parsecrl(array $crl, bool $oidprint = false): false|arra } else { $subjOID = self::oidfromhex($curr[$key][0][0]['value_hex']); } + $curr[$subjOID][] = hex2bin((string) $curr[$key][0][1]['value_hex']); unset($curr[$key]); } @@ -933,6 +1035,7 @@ private static function parsecrl(array $crl, bool $oidprint = false): false|arra } } } + $ar['crl']['TBSCertList']['issuer'] = $curr; $arrModel['TBSCertList']['version'] = ''; $arrModel['TBSCertList']['signature'] = ''; @@ -945,7 +1048,7 @@ private static function parsecrl(array $crl, bool $oidprint = false): false|arra $differ = array_diff_key($arrModel, $crl); if (count($differ) == 0) { $differ = array_diff_key($arrModel['TBSCertList'], $crl['TBSCertList']); - if (count($differ) > 0) { + if ($differ !== []) { return false; } } else { @@ -974,6 +1077,7 @@ private static function oidfromhex(string $hex): string $mplx[$i] = ($dec - 128) * 128; $i++; } + $i = 0; $nex = false; $result = false; @@ -981,38 +1085,37 @@ private static function oidfromhex(string $hex): string $dec = hexdec($val); if ($i === 0) { if ($dec >= 128) { - $nex = (128 * ($dec - 128)) - 80; + $nex = 128 * ($dec - 128) - 80; if ($dec > 129) { - $nex = (128 * ($dec - 128)) - 80; + $nex = 128 * ($dec - 128) - 80; } + $result = '2.'; } + if ($dec >= 80 && $dec < 128) { $first = $dec - 80; - $result = "2.{$first}."; + $result = sprintf('2.%s.', $first); } + if ($dec >= 40 && $dec < 80) { $first = $dec - 40; - $result = "1.{$first}."; + $result = sprintf('1.%s.', $first); } + if ($dec < 40) { $first = $dec; - $result = "0.{$first}."; + $result = sprintf('0.%s.', $first); } + } elseif ($dec > 127) { + $nex = $nex == false ? $mplx[$i] : $nex * 128 + $mplx[$i]; } else { - if ($dec > 127) { - if ($nex == false) { - $nex = $mplx[$i]; - } else { - $nex = ($nex * 128) + $mplx[$i]; - } - } else { - $result .= ($dec + $nex) . '.'; - if ($dec <= 127) { - $nex = 0; - } + $result .= $dec + $nex . '.'; + if ($dec <= 127) { + $nex = 0; } } + $i++; } diff --git a/src/pdfvalue/PDFValue.php b/src/pdfvalue/PDFValue.php index c3f99bb..57601b0 100644 --- a/src/pdfvalue/PDFValue.php +++ b/src/pdfvalue/PDFValue.php @@ -58,6 +58,7 @@ public function offsetGet($offset) if (! is_array($this->value)) { return false; } + if (! isset($this->value[$offset])) { return false; } @@ -70,14 +71,16 @@ public function offsetSet($offset, $value): void if (! is_array($this->value)) { return; } + $this->value[$offset] = $value; } public function offsetUnset($offset): void { - if ((! is_array($this->value)) || (! isset($this->value[$offset]))) { + if (! is_array($this->value) || ! isset($this->value[$offset])) { throw new Exception('invalid offset'); } + unset($this->value[$offset]); } @@ -137,16 +140,15 @@ protected static function _convert($value): self case 'string': if ($value[0] === '/') { $value = new PDFValueType(substr($value, 1)); + } elseif (preg_match("/\s/ms", $value) === 1) { + $value = new PDFValueString($value); } else { - if (preg_match("/\s/ms", $value) === 1) { - $value = new PDFValueString($value); - } else { - $value = new PDFValueSimple($value); - } + $value = new PDFValueSimple($value); } + break; case 'array': - if (count($value) === 0) { + if ($value === []) { // An empty list is assumed to be a list $value = new PDFValueList(); } else { @@ -160,9 +162,11 @@ protected static function _convert($value): self foreach ($value as $v) { $list[] = self::_convert($v); } + $value = new PDFValueList($list); } } + break; } diff --git a/src/pdfvalue/PDFValueList.php b/src/pdfvalue/PDFValueList.php index 9e0205d..9e9fa52 100644 --- a/src/pdfvalue/PDFValueList.php +++ b/src/pdfvalue/PDFValueList.php @@ -36,7 +36,7 @@ public function __toString(): string public function diff(object $other): mixed { $different = parent::diff($other); - if (($different === false) || ($different === null)) { + if ($different === false || $different === null) { return $different; } @@ -58,16 +58,14 @@ public function val($list = false) if ($list === true) { $result = []; foreach ($this->value as $v) { - if (is_a($v, PDFValueSimple::class)) { - $v = explode(' ', (string) $v->val()); - } else { - $v = [$v->val()]; - } + $v = is_a($v, PDFValueSimple::class) ? explode(' ', (string) $v->val()) : [$v->val()]; + array_push($result, ...$v); } return $result; } + return parent::val(); } @@ -106,13 +104,15 @@ public function get_object_referenced(): false|array */ public function push(mixed $v): bool { - if (is_object($v) && ($v::class === static::class)) { + if (is_object($v) && $v::class === static::class) { // If a list is pushed to another list, the elements are merged $v = $v->val(); } + if (! is_array($v)) { $v = [$v]; } + foreach ($v as $e) { $e = self::_convert($e); $this->value[] = $e; diff --git a/src/pdfvalue/PDFValueObject.php b/src/pdfvalue/PDFValueObject.php index 3e3b90a..78a8a82 100644 --- a/src/pdfvalue/PDFValueObject.php +++ b/src/pdfvalue/PDFValueObject.php @@ -29,6 +29,7 @@ public function __construct($value = []) foreach ($value as $k => $v) { $result[$k] = self::_convert($v); } + parent::__construct($result); } @@ -43,12 +44,13 @@ public function __toString(): string foreach ($this->value as $k => $v) { $v = '' . $v; if ($v === '') { - $result[] = "/{$k}"; + $result[] = '/' . $k; continue; } + match ($v[0]) { - '/', '[', '(', '<' => array_push($result, "/{$k}{$v}"), - default => array_push($result, "/{$k} {$v}"), + '/', '[', '(', '<' => array_push($result, sprintf('/%s%s', $k, $v)), + default => array_push($result, sprintf('/%s %s', $k, $v)), }; } @@ -58,7 +60,7 @@ public function __toString(): string public function diff(object $other): mixed { $different = parent::diff($other); - if (($different === false) || ($different === null)) { + if ($different === false || $different === null) { return $different; } @@ -72,11 +74,9 @@ public function diff(object $other): mixed if ($different === false) { $result[$k] = $v; $differences++; - } else { - if ($different !== null) { - $result[$k] = $different; - $differences++; - } + } elseif ($different !== null) { + $result[$k] = $different; + $differences++; } } } else { @@ -84,6 +84,7 @@ public function diff(object $other): mixed $differences++; } } + if ($differences === 0) { return null; } @@ -102,9 +103,11 @@ public static function fromarray(array $parts): false|self break; } } + if ($intkeys) { return false; } + foreach ($parts as $k2 => $v) { $result[$k2] = self::_convert($v); } @@ -123,19 +126,24 @@ public static function fromstring($str): false|self if ($field === '') { return false; } + if ($field[0] !== '/') { return false; } + $field = substr($field, 1); if ($field === '') { return false; } + continue; } + $value = $parts[$i]; $result[$field] = $value; $field = null; } + // If there is no pair of values, there is no valid if ($field !== null) { return false; @@ -164,8 +172,10 @@ public function offsetSet($offset, $value): void if (isset($this->value[$offset])) { unset($this->value[$offset]); } + // return null; } + $this->value[$offset] = self::_convert($value); // return $this->value[$offset]; } From 618b9621f9c45140728c9db82380376c75af2658 Mon Sep 17 00:00:00 2001 From: Marcin Jakubowski Date: Thu, 31 Oct 2024 23:13:54 +0100 Subject: [PATCH 08/11] modernization and cleanup --- phpstan.neon.dist | 4 ++-- src/helpers/asn1.php | 2 +- src/helpers/contentgeneration.php | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 02fe261..b13a6da 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 0 + level: 1 paths: - src/ parallel: @@ -12,7 +12,7 @@ parameters: ignoreErrors: - "#Implicit array creation is not allowed#" -# - "#Call to an undefined static method .*asn1::#" + - "#Call to an undefined static method .*asn1::#" - "#Construct empty\\(\\) is not allowed. Use more strict comparison.#" # - # identifier: missingType.iterableValue diff --git a/src/helpers/asn1.php b/src/helpers/asn1.php index 83d5cae..5d3979c 100644 --- a/src/helpers/asn1.php +++ b/src/helpers/asn1.php @@ -38,7 +38,7 @@ public static function __callStatic($func, $params) } if ($func === 'impl') { //impl($num="0") - $val = $val ?: '00'; + $val ??= '00'; $val = strlen((string) $val) % 2 !== 0 ? '0' . $val : $val; return $num . $val; diff --git a/src/helpers/contentgeneration.php b/src/helpers/contentgeneration.php index 165c15d..f644833 100644 --- a/src/helpers/contentgeneration.php +++ b/src/helpers/contentgeneration.php @@ -122,6 +122,7 @@ function _create_image_objects($info, $object_factory): array // In principle, it may return multiple objects $smasks = _create_image_objects($smaskinfo, $object_factory); + assert($smasks !== []); foreach ($smasks as $smask) { $objects[] = $smask; } From ebde76b8106ffea019cfc59a741f66c320450508 Mon Sep 17 00:00:00 2001 From: Marcin Jakubowski Date: Thu, 31 Oct 2024 23:37:36 +0100 Subject: [PATCH 09/11] introduce psr-compatible logger --- pdfcompare.php | 51 +++++------ pdfdeflate.php | 77 +++++++++-------- pdfrebuild.php | 22 ++--- pdfsign.php | 29 +++---- pdfsigni.php | 7 +- pdfsignlts.php | 47 +++++----- pdfsigntsa.php | 41 +++++---- pdfsignx.php | 31 ++++--- rector.php | 14 ++- src/AlmostOriginalLogger.php | 16 ++++ src/PDFDoc.php | 119 ++++++++++++------------- src/PDFDocWithContents.php | 5 +- src/PDFObject.php | 6 +- src/PDFObjectParser.php | 2 +- src/PDFSignatureObject.php | 8 +- src/PDFUtilFnc.php | 4 +- src/helpers/Buffer.php | 2 +- src/helpers/CMS.php | 125 ++++++++++++++------------- src/helpers/DependencyTreeObject.php | 3 - src/helpers/asn1.php | 2 +- src/helpers/helpers.php | 2 +- src/helpers/x509.php | 3 +- 22 files changed, 324 insertions(+), 292 deletions(-) create mode 100644 src/AlmostOriginalLogger.php diff --git a/pdfcompare.php b/pdfcompare.php index e38eb9c..0f4c416 100644 --- a/pdfcompare.php +++ b/pdfcompare.php @@ -19,37 +19,32 @@ along with this program. If not, see . */ +use ddn\sapp\AlmostOriginalLogger; use ddn\sapp\PDFDoc; -use function ddn\sapp\helpers\p_debug_var; -use function ddn\sapp\helpers\p_debug; -use ddn\sapp\pdfvalue\PDFValueObject; -use function ddn\sapp\helpers\p_error; require_once 'vendor/autoload.php'; -if ($argc !== 3) +if ($argc !== 3) { fwrite(STDERR, sprintf("usage: %s ", $argv[0])); -else { - if (!file_exists($argv[1])) { - fwrite(STDERR, "failed to open file " . $argv[1]); - die(); - } - if (!file_exists($argv[2])) { - fwrite(STDERR, "failed to open file " . $argv[2]); - die(); - } - - $doc1 = PDFDoc::from_string(file_get_contents($argv[1])); - if ($doc1 === false) - fwrite(STDERR, "failed to parse file " . $argv[1]); - - $doc2 = PDFDoc::from_string(file_get_contents($argv[2])); - if ($doc2 === false) - fwrite(STDERR, "failed to parse file " . $argv[2]); - - $differences = $doc1->compare($doc2); - foreach ($differences as $oid => $obj) { - p_error(get_debug_type($oid)); - print($obj->to_pdf_entry()); - } + exit(1); +} + +if (!file_exists($argv[1])) { + fwrite(STDERR, "failed to open file " . $argv[1]); + exit(1); +} +if (!file_exists($argv[2])) { + fwrite(STDERR, "failed to open file " . $argv[2]); + exit(1); +} + +$doc1 = PDFDoc::from_string(file_get_contents($argv[1])); +$doc1->setLogger(new AlmostOriginalLogger()); + +$doc2 = PDFDoc::from_string(file_get_contents($argv[2])); +$doc2->setLogger(new AlmostOriginalLogger()); + +$differences = $doc1->compare($doc2); +foreach ($differences as $oid => $obj) { + print($obj->to_pdf_entry()); } diff --git a/pdfdeflate.php b/pdfdeflate.php index 46890c6..d26ec3d 100644 --- a/pdfdeflate.php +++ b/pdfdeflate.php @@ -19,60 +19,61 @@ along with this program. If not, see . */ +use ddn\sapp\AlmostOriginalLogger; use ddn\sapp\PDFDoc; require_once 'vendor/autoload.php'; -if ($argc < 2 or $argc > 3) +if ($argc < 2 or $argc > 3) { fwrite(STDERR, sprintf("usage: %s [oid]", $argv[0])); -else { - if (!file_exists($argv[1])) +} else { + if (!file_exists($argv[1])) { fwrite(STDERR, "failed to open file " . $argv[1]); - else { + } else { $doc = PDFDoc::from_string(file_get_contents($argv[1])); + $doc->setLogger(new AlmostOriginalLogger()); $toid = null; - if ($argc === 3) + if ($argc === 3) { $toid = (int)$argv[2]; + } - if ($doc === false) - fwrite(STDERR, "failed to parse file " . $argv[1]); - else { - foreach ($doc->get_object_iterator() as $oid => $object) { - if ($toid !== null) { - if ($oid != $toid) { - continue; - } + foreach ($doc->get_object_iterator() as $oid => $object) { + if ($toid !== null) { + if ($oid != $toid) { + continue; } + } - if ($object === false) - continue; - if ($object["Filter"] == "/FlateDecode") { - if ($object["Subtype"] != "/Image") { - $stream = $object->get_stream(false); - if ($stream !== false) { - unset($object["Filter"]); - $object->set_stream($stream, false); - $doc->add_object($object); - } + if ($object === false) { + continue; + } + if ($object["Filter"] == "/FlateDecode") { + if ($object["Subtype"] != "/Image") { + $stream = $object->get_stream(false); + if ($stream !== false) { + unset($object["Filter"]); + $object->set_stream($stream, false); + $doc->add_object($object); } } - // Not needed because we are rebuilding the document - if ($object["Type"] == "/ObjStm") { - $object->set_stream("", false); - $doc->add_object($object); - } - // Do not want images to be uncompressed - if ($object["Subtype"] == "/Image") { - $object->set_stream(""); - $doc->add_object($object); - } - if ($toid != null) { - print($object->get_stream(false)); - } } - if ($toid === null) - echo $doc->to_pdf_file_s(true); + // Not needed because we are rebuilding the document + if ($object["Type"] == "/ObjStm") { + $object->set_stream("", false); + $doc->add_object($object); + } + // Do not want images to be uncompressed + if ($object["Subtype"] == "/Image") { + $object->set_stream(""); + $doc->add_object($object); + } + if ($toid != null) { + print($object->get_stream(false)); + } + } + if ($toid === null) { + echo $doc->to_pdf_file_s(true); } } } diff --git a/pdfrebuild.php b/pdfrebuild.php index 0ec0bcd..6e7961e 100644 --- a/pdfrebuild.php +++ b/pdfrebuild.php @@ -19,25 +19,25 @@ along with this program. If not, see . */ +use ddn\sapp\AlmostOriginalLogger; use ddn\sapp\PDFDoc; +use Psr\Log\AbstractLogger; require_once 'vendor/autoload.php'; -if ($argc < 2 || $argc > 3) +if ($argc < 2 || $argc > 3) { fwrite(STDERR, sprintf("usage: %s []", $argv[0])); -else { - if (!file_exists($argv[1])) +} else { + if (!file_exists($argv[1])) { fwrite(STDERR, "failed to open file " . $argv[1]); - else { + } else { $obj = PDFDoc::from_string(file_get_contents($argv[1])); + $obj->setLogger(new AlmostOriginalLogger()); - if ($obj === false) - fwrite(STDERR, "failed to parse file " . $argv[1]); - else { - if ($argc == 3) - file_put_contents($argv[2], $obj->to_pdf_file_s(true)); - else - echo $obj->to_pdf_file_s(true); + if ($argc == 3) { + file_put_contents($argv[2], $obj->to_pdf_file_s(true)); + } else { + echo $obj->to_pdf_file_s(true); } } } diff --git a/pdfsign.php b/pdfsign.php index 6b61416..37d203e 100644 --- a/pdfsign.php +++ b/pdfsign.php @@ -19,16 +19,17 @@ along with this program. If not, see . */ +use ddn\sapp\AlmostOriginalLogger; use ddn\sapp\PDFDoc; require_once 'vendor/autoload.php'; -if ($argc !== 3) +if ($argc !== 3) { fwrite(STDERR, sprintf("usage: %s ", $argv[0])); -else { - if (!file_exists($argv[1])) +} else { + if (!file_exists($argv[1])) { fwrite(STDERR, "failed to open file " . $argv[1]); - else { + } else { // Silently prompt for the password fwrite(STDERR, "Password: "); system('stty -echo'); @@ -38,18 +39,16 @@ $file_content = file_get_contents($argv[1]); $obj = PDFDoc::from_string($file_content); - - if ($obj === false) - fwrite(STDERR, "failed to parse file " . $argv[1]); - else { - if (!$obj->set_signature_certificate($argv[2], $password)) { - fwrite(STDERR, "the certificate is not valid"); + $obj->setLogger(new AlmostOriginalLogger()); + + if (!$obj->set_signature_certificate($argv[2], $password)) { + fwrite(STDERR, "the certificate is not valid"); + } else { + $docsigned = $obj->to_pdf_file_s(); + if ($docsigned === false) { + fwrite(STDERR, "could not sign the document"); } else { - $docsigned = $obj->to_pdf_file_s(); - if ($docsigned === false) - fwrite(STDERR, "could not sign the document"); - else - echo $docsigned; + echo $docsigned; } } } diff --git a/pdfsigni.php b/pdfsigni.php index bd3da0e..414ee9e 100755 --- a/pdfsigni.php +++ b/pdfsigni.php @@ -20,9 +20,9 @@ along with this program. If not, see . */ +use ddn\sapp\AlmostOriginalLogger; use ddn\sapp\PDFDoc; - -use function ddn\sapp\helpers\p_error; +use ddn\sapp\PDFException; require_once 'vendor/autoload.php'; @@ -46,6 +46,7 @@ $file_content = file_get_contents($argv[1]); $obj = PDFDoc::from_string($file_content); +$obj->setLogger(new AlmostOriginalLogger()); if ($obj === false) { fwrite(STDERR, "failed to parse file " . $argv[1]); @@ -65,7 +66,7 @@ $pagesize = $obj->get_page_size(0); if ($pagesize === false) { - return p_error("failed to get page size"); + return throw new PDFException("failed to get page size"); } $pagesize = explode(" ", $pagesize[0]->val()); diff --git a/pdfsignlts.php b/pdfsignlts.php index 0b1ebb6..af4cce5 100644 --- a/pdfsignlts.php +++ b/pdfsignlts.php @@ -20,18 +20,25 @@ along with this program. If not, see . */ +use ddn\sapp\AlmostOriginalLogger; use ddn\sapp\PDFDoc; require_once 'vendor/autoload.php'; -if ($argc < 3) - fwrite(STDERR, sprintf("usage: %s \n +if ($argc < 3) { + fwrite( + STDERR, + sprintf( + "usage: %s \n tsaUrl - optional TSA server url to timestamp pdf document. -", $argv[0])); -else { - if (!file_exists($argv[1])) +", + $argv[0] + ) + ); +} else { + if (!file_exists($argv[1])) { fwrite(STDERR, "failed to open file " . $argv[1]); - else { + } else { // Silently prompt for the password fwrite(STDERR, "Password: "); system('stty -echo'); @@ -51,22 +58,20 @@ $file_content = file_get_contents($argv[1]); $obj = PDFDoc::from_string($file_content); + $obj->setLogger(new AlmostOriginalLogger()); - if ($obj === false) - fwrite(STDERR, "failed to parse file " . $argv[1]); - else { - if (!$obj->set_signature_certificate($argv[2], $password)) - fwrite(STDERR, "the certificate is not valid"); - else { - if ($tsa != 'no') { - $obj->set_tsa($tsa); - } - $obj->set_ltv(); - $docsigned = $obj->to_pdf_file_s(); - if ($docsigned === false) - fwrite(STDERR, "could not sign the document"); - else - echo $docsigned; + if (!$obj->set_signature_certificate($argv[2], $password)) { + fwrite(STDERR, "the certificate is not valid"); + } else { + if ($tsa != 'no') { + $obj->set_tsa($tsa); + } + $obj->set_ltv(); + $docsigned = $obj->to_pdf_file_s(); + if ($docsigned === false) { + fwrite(STDERR, "could not sign the document"); + } else { + echo $docsigned; } } } diff --git a/pdfsigntsa.php b/pdfsigntsa.php index 1c56893..c5378aa 100644 --- a/pdfsigntsa.php +++ b/pdfsigntsa.php @@ -16,18 +16,25 @@ along with this program. If not, see . */ +use ddn\sapp\AlmostOriginalLogger; use ddn\sapp\PDFDoc; require_once 'vendor/autoload.php'; -if ($argc < 3) - fwrite(STDERR, sprintf("usage: %s \n +if ($argc < 3) { + fwrite( + STDERR, + sprintf( + "usage: %s \n tsaUrl - optional TSA server url to timestamp pdf document. -", $argv[0])); -else { - if (!file_exists($argv[1])) +", + $argv[0] + ) + ); +} else { + if (!file_exists($argv[1])) { fwrite(STDERR, "failed to open file " . $argv[1]); - else { + } else { // Silently prompt for the password fwrite(STDERR, "Password: "); system('stty -echo'); @@ -47,19 +54,17 @@ $file_content = file_get_contents($argv[1]); $obj = PDFDoc::from_string($file_content); + $obj->setLogger(new AlmostOriginalLogger()); - if ($obj === false) - fwrite(STDERR, "failed to parse file " . $argv[1]); - else { - if (!$obj->set_signature_certificate($argv[2], $password)) - fwrite(STDERR, "the certificate is not valid"); - else { - $obj->set_tsa($tsa); - $docsigned = $obj->to_pdf_file_s(); - if ($docsigned === false) - fwrite(STDERR, "could not sign the document"); - else - echo $docsigned; + if (!$obj->set_signature_certificate($argv[2], $password)) { + fwrite(STDERR, "the certificate is not valid"); + } else { + $obj->set_tsa($tsa); + $docsigned = $obj->to_pdf_file_s(); + if ($docsigned === false) { + fwrite(STDERR, "could not sign the document"); + } else { + echo $docsigned; } } } diff --git a/pdfsignx.php b/pdfsignx.php index b401aeb..6c9ea4c 100755 --- a/pdfsignx.php +++ b/pdfsignx.php @@ -20,16 +20,17 @@ along with this program. If not, see . */ +use ddn\sapp\AlmostOriginalLogger; use ddn\sapp\PDFDoc; require_once 'vendor/autoload.php'; -if ($argc !== 4) +if ($argc !== 4) { fwrite(STDERR, sprintf("usage: %s ", $argv[0])); -else { - if (!file_exists($argv[1])) +} else { + if (!file_exists($argv[1])) { fwrite(STDERR, "failed to open file " . $argv[1]); - else { + } else { // Silently prompt for the password fwrite(STDERR, "Password: "); system('stty -echo'); @@ -39,19 +40,17 @@ $file_content = file_get_contents($argv[1]); $obj = PDFDoc::from_string($file_content); - - if ($obj === false) - fwrite(STDERR, "failed to parse file " . $argv[1]); - else { - $signedDoc = $obj->sign_document($argv[3], $password, 0, $argv[2]); - if ($signedDoc === false) { - fwrite(STDERR, "failed to sign the document"); + $obj->setLogger(new AlmostOriginalLogger()); + + $signedDoc = $obj->sign_document($argv[3], $password, 0, $argv[2]); + if ($signedDoc === false) { + fwrite(STDERR, "failed to sign the document"); + } else { + $docsigned = $signedDoc->to_pdf_file_s(); + if ($docsigned === false) { + fwrite(STDERR, "could not sign the document"); } else { - $docsigned = $signedDoc->to_pdf_file_s(); - if ($docsigned === false) - fwrite(STDERR, "could not sign the document"); - else - echo $docsigned; + echo $docsigned; } } } diff --git a/rector.php b/rector.php index fbedc1c..2bec622 100644 --- a/rector.php +++ b/rector.php @@ -3,6 +3,11 @@ declare(strict_types=1); use Rector\Config\RectorConfig; +use Rector\DeadCode\Rector\For_\RemoveDeadIfForeachForRector; +use Rector\DeadCode\Rector\For_\RemoveDeadLoopRector; +use Rector\DeadCode\Rector\If_\RemoveAlwaysTrueIfConditionRector; +use Rector\DeadCode\Rector\Stmt\RemoveUnreachableStatementRector; +use Rector\DeadCode\Rector\Switch_\RemoveDuplicatedCaseInSwitchRector; return RectorConfig::configure() ->withPaths([ @@ -18,4 +23,11 @@ ->withPreparedSets(typeDeclarations: true) ->withPreparedSets(codeQuality: true) ->withPreparedSets(codingStyle: true) - ; + ->withPreparedSets(deadCode: true) + ->withSkip([ + RemoveDeadIfForeachForRector::class, + RemoveDeadLoopRector::class, + RemoveDuplicatedCaseInSwitchRector::class, + RemoveUnreachableStatementRector::class, + RemoveAlwaysTrueIfConditionRector::class, + ]); diff --git a/src/AlmostOriginalLogger.php b/src/AlmostOriginalLogger.php new file mode 100644 index 0000000..44db1e3 --- /dev/null +++ b/src/AlmostOriginalLogger.php @@ -0,0 +1,16 @@ +logger = $logger; + } + /** * Retrieve the number of pages in the document (not considered those pages that could be added by the user using this object or derived ones) * @@ -151,11 +159,11 @@ public function pop_state(): bool * otherwise only the object ids from the latest $depth versions will be considered * (if it is an incremental updated document) */ - public static function from_string(string $buffer, ?int $depth = null): false|self + public static function from_string(string $buffer, ?int $depth = null): self { $structure = PDFUtilFnc::acquire_structure($buffer, $depth); if ($structure === false) { - return false; + throw new PDFException('acquire_structure failed'); } $trailer = $structure['trailer']; @@ -191,7 +199,7 @@ public static function from_string(string $buffer, ?int $depth = null): false|se return $pdfdoc; } - public function get_revision($rev_i): string + public function get_revision(?int $rev_i): string { if ($rev_i === null) { $rev_i = count($this->_revisions) - 1; @@ -201,7 +209,7 @@ public function get_revision($rev_i): string $rev_i = count($this->_revisions) + $rev_i - 1; } - return substr((string) $this->_buffer, 0, $this->_revisions[$rev_i]); + return substr((string)$this->_buffer, 0, $this->_revisions[$rev_i]); } /** @@ -355,7 +363,7 @@ public function set_signature_certificate($certfile, ?string $certpass = null): throw new PDFException('invalid private key'); } - if (! openssl_x509_check_private_key($certificate['cert'], $certificate['pkey'])) { + if (!openssl_x509_check_private_key($certificate['cert'], $certificate['pkey'])) { throw new PDFException("private key doesn't corresponds to certificate"); } @@ -419,8 +427,6 @@ public function set_tsa(string $tsa, ?string $tsauser = null, ?string $tsapass = * @param $reason * @param $location * @param $contact - * - * @return void */ public function set_metadata_props($name = null, $reason = null, $location = null, $contact = null): void { @@ -460,7 +466,7 @@ public function get_version() */ public function set_version($version): bool { - if (preg_match("/PDF-1.\[0-9\]/", (string) $version) !== 1) { + if (preg_match("/PDF-1.\[0-9\]/", (string)$version) !== 1) { return false; } @@ -568,7 +574,7 @@ public function to_pdf_file_b(bool $rebuild = false): Buffer } if ($target_version >= '1.5') { - p_debug('generating xref using cross-reference streams'); + $this->logger?->debug('generating xref using cross-reference streams'); // Create a new object for the trailer $trailer = $this->create_object( @@ -582,7 +588,7 @@ public function to_pdf_file_b(bool $rebuild = false): Buffer $xref = PDFUtilFnc::build_xref_1_5($_obj_offsets); // Set the parameters for the trailer - $trailer['Index'] = explode(' ', (string) $xref['Index']); + $trailer['Index'] = explode(' ', (string)$xref['Index']); $trailer['W'] = $xref['W']; $trailer['Size'] = $this->_max_oid + 1; $trailer['Type'] = '/XRef'; @@ -617,7 +623,7 @@ public function to_pdf_file_b(bool $rebuild = false): Buffer $_doc_from_xref = new Buffer($trailer->to_pdf_entry()); $_doc_from_xref->data('startxref' . __EOL . $xref_offset . __EOL . '%%EOF' . __EOL); } else { - p_debug('generating xref using classic xref...trailer'); + $this->logger?->debug('generating xref using classic xref...trailer'); $xref_content = PDFUtilFnc::build_xref($_obj_offsets); // Update the trailer @@ -636,10 +642,7 @@ public function to_pdf_file_b(bool $rebuild = false): Buffer // Generate the part of the document related to the xref $_doc_from_xref = new Buffer($xref_content); - $_doc_from_xref->data( - 'trailer -' . $this->_pdf_trailer_object - ); + $_doc_from_xref->data("trailer\n" . $this->_pdf_trailer_object); $_doc_from_xref->data("\nstartxref\n{$xref_offset}\n%%EOF\n"); } @@ -651,7 +654,7 @@ public function to_pdf_file_b(bool $rebuild = false): Buffer $_signable_document = new Buffer($_doc_to_xref->get_raw() . $_signature->to_pdf_entry() . $_doc_from_xref->get_raw()); $certificate = $_signature->get_certificate(); $extracerts = $certificate['extracerts'] ?? null; - $cms = new CMS(); + $cms = new CMS($this->logger); $cms->signature_data['hashAlgorithm'] = 'sha256'; $cms->signature_data['privkey'] = $certificate['pkey']; $cms->signature_data['extracerts'] = $extracerts; @@ -762,7 +765,7 @@ public function get_page_size(int $i): false|array } // The page has not been found - if ($pageinfo === false || ! isset($pageinfo['size'])) { + if ($pageinfo === false || !isset($pageinfo['size'])) { return false; } @@ -823,7 +826,7 @@ public function get_object_tree(): array // foreach ($this->get_object_iterator() as $oid => $o) { // Create the object in the dependency tree and add it to the list of objects - if (! array_key_exists($oid, $objects)) { + if (!array_key_exists($oid, $objects)) { $objects[$oid] = new DependencyTreeObject($oid, $o['Type']); } @@ -840,14 +843,14 @@ public function get_object_tree(): array continue; } - if (! is_array($references)) { + if (!is_array($references)) { $references = [$references]; } } - // p_debug("$oid references " . implode(", ", $references)); + // $this->logger?->debug("$oid references " . implode(", ", $references)); foreach ($references as $r_object) { - if (! array_key_exists($r_object, $objects)) { + if (!array_key_exists($r_object, $objects)) { $r_object_o = $this->get_object($r_object); $objects[$r_object] = new DependencyTreeObject($r_object, $r_object_o['Type']); } @@ -868,7 +871,7 @@ public function get_object_tree(): array // Remove those objects that are child of other objects from the top of the tree foreach ($objects as $oid => $t_object) { - if (($t_object->is_child > 0 || in_array($t_object->info, ['/XRef', '/ObjStm'], true)) && ! in_array($oid, $xref_children, true)) { + if (($t_object->is_child > 0 || in_array($t_object->info, ['/XRef', '/ObjStm'], true)) && !in_array($oid, $xref_children, true)) { unset($objects[$oid]); } } @@ -897,7 +900,7 @@ public function get_signatures(): array } $o_value = $o->get_value()->val(); - if (! is_array($o_value) || ! isset($o_value['Type'])) { + if (!is_array($o_value) || !isset($o_value['Type'])) { continue; } @@ -914,7 +917,7 @@ public function get_signatures(): array openssl_pkcs7_read( "-----BEGIN CERTIFICATE-----\n" - . chunk_split(base64_encode(hex2bin((string) $signature['content'])), 64, "\n") + . chunk_split(base64_encode(hex2bin((string)$signature['content'])), 64, "\n") . "-----END CERTIFICATE-----\n", $cert ); @@ -974,11 +977,11 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, throw new PDFException('failed to get page size'); } - $pagesize = explode(' ', (string) $pagesize[0]->val()); + $pagesize = explode(' ', (string)$pagesize[0]->val()); // Get the bounding box for the image - $p_x = (int) $pagesize[0]; - $p_y = (int) $pagesize[1]; + $p_x = (int)$pagesize[0]; + $p_y = (int)$pagesize[1]; // Add the position for the image $p_x += $px; @@ -1011,7 +1014,7 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, $this->set_signature_appearance($page_to_appear, [$p_x, $p_y, $p_x + $i_w, $p_y + $i_h], $imagefilename); } - if (! $this->set_signature_certificate($certfile, $password)) { + if (!$this->set_signature_certificate($certfile, $password)) { throw new PDFException('the certificate or the signature is not valid'); } @@ -1082,14 +1085,14 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false $updated_objects = []; // Add the annotation to the page - if (! isset($page_obj['Annots'])) { + if (!isset($page_obj['Annots'])) { $page_obj['Annots'] = new PDFValueList(); } $annots = &$page_obj['Annots']; $page_rotation = $page_obj['Rotate'] ?? new PDFValueSimple(0); - if (($referenced = $annots->get_object_referenced()) !== false && ! is_array($referenced)) { + if (($referenced = $annots->get_object_referenced()) !== false && !is_array($referenced)) { // It is an indirect object, so we need to update that object $newannots = $this->create_object( $this->get_object($referenced)->get_value() @@ -1119,8 +1122,8 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false $signature = null; if ($this->_certificate !== null) { // Perform signature test to get signature size to define __SIGNATURE_MAX_LENGTH - p_debug(" ########## PERFORM SIGNATURE LENGTH CHECK ##########\n"); - $CMS = new CMS(); + $this->logger?->debug(" ########## PERFORM SIGNATURE LENGTH CHECK ##########\n"); + $CMS = new CMS($this->logger); $CMS->signature_data['signcert'] = $this->_certificate['cert']; $CMS->signature_data['extracerts'] = $this->_certificate['extracerts'] ?? null; $CMS->signature_data['hashAlgorithm'] = 'sha256'; @@ -1129,8 +1132,8 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false $CMS->signature_data['ltv'] = $this->_signature_ltv_data; $res = $CMS->pkcs7_sign('0'); $len = strlen($res); - p_debug(sprintf(' Signature Length is "%d" Bytes', $len)); - p_debug(" ########## FINISHED SIGNATURE LENGTH CHECK #########\n\n"); + $this->logger?->debug(sprintf(' Signature Length is "%d" Bytes', $len)); + $this->logger?->debug(" ########## FINISHED SIGNATURE LENGTH CHECK #########\n\n"); PDFSignatureObject::$__SIGNATURE_MAX_LENGTH = $len; $signature = $this->create_object([], PDFSignatureObject::class, false); @@ -1158,8 +1161,8 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false // Get the page height, to change the coordinates system (up to down) $pagesize = $this->get_page_size($pagetoappear); - $pagesize = explode(' ', (string) $pagesize[0]->val()); - $pagesize_h = (float) ('' . $pagesize[3]) - (float) ('' . $pagesize[1]); + $pagesize = explode(' ', (string)$pagesize[0]->val()); + $pagesize_h = (float)('' . $pagesize[3]) - (float)('' . $pagesize[1]); $bbox = [0, 0, $recttoappear[2] - $recttoappear[0], $recttoappear[3] - $recttoappear[1]]; $form_object = $this->create_object([ @@ -1228,7 +1231,7 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false $annotation_object['Rect'] = [$recttoappear[0], $pagesize_h - $recttoappear[1], $recttoappear[2], $pagesize_h - $recttoappear[3]]; } - if (! $newannots->push(new PDFValueReference($annotation_object->get_oid()))) { + if (!$newannots->push(new PDFValueReference($annotation_object->get_oid()))) { throw new PDFException('Could not update the page where the signature has to appear'); } @@ -1236,12 +1239,12 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false $updated_objects[] = $page_obj; // AcroForm may be an indirect object - if (! isset($root_obj['AcroForm'])) { + if (!isset($root_obj['AcroForm'])) { $root_obj['AcroForm'] = new PDFValueObject(); } $acroform = &$root_obj['AcroForm']; - if (($referenced = $acroform->get_object_referenced()) !== false && ! is_array($referenced)) { + if (($referenced = $acroform->get_object_referenced()) !== false && !is_array($referenced)) { $acroform = $this->get_object($referenced); $updated_objects[] = $acroform; } else { @@ -1250,12 +1253,12 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false // Add the annotation to the interactive form $acroform['SigFlags'] = 3; - if (! isset($acroform['Fields'])) { + if (!isset($acroform['Fields'])) { $acroform['Fields'] = new PDFValueList(); } // Add the annotation object to the interactive form - if (! $acroform['Fields']->push(new PDFValueReference($annotation_object->get_oid()))) { + if (!$acroform['Fields']->push(new PDFValueReference($annotation_object->get_oid()))) { throw new PDFException('could not create the signature field'); } @@ -1289,19 +1292,19 @@ protected function update_mod_date(?DateTime $date = null): bool throw new PDFException('invalid root object'); } - if (! $date instanceof DateTime) { + if (!$date instanceof DateTime) { $date = new DateTime(); } // Update the xmp metadata if exists if (isset($root_obj['Metadata'])) { $metadata = $root_obj['Metadata']; - if (($referenced = $metadata->get_object_referenced()) !== false && ! is_array($referenced)) { + if (($referenced = $metadata->get_object_referenced()) !== false && !is_array($referenced)) { $metadata = $this->get_object($referenced); $metastream = $metadata->get_stream(); - $metastream = preg_replace('/([^<]*)<\/xmp:ModifyDate>/', '' . $date->format('c') . '', (string) $metastream); - $metastream = preg_replace('/([^<]*)<\/xmp:MetadataDate>/', '' . $date->format('c') . '', (string) $metastream); - $metastream = preg_replace('/([^<]*)<\/xmpMM:InstanceID>/', 'uuid:' . UUID::v4() . '', (string) $metastream); + $metastream = preg_replace('/([^<]*)<\/xmp:ModifyDate>/', '' . $date->format('c') . '', (string)$metastream); + $metastream = preg_replace('/([^<]*)<\/xmp:MetadataDate>/', '' . $date->format('c') . '', (string)$metastream); + $metastream = preg_replace('/([^<]*)<\/xmpMM:InstanceID>/', 'uuid:' . UUID::v4() . '', (string)$metastream); $metadata->set_stream($metastream, false); $this->add_object($metadata); } diff --git a/src/PDFDocWithContents.php b/src/PDFDocWithContents.php index e495a12..f40d3ec 100644 --- a/src/PDFDocWithContents.php +++ b/src/PDFDocWithContents.php @@ -111,7 +111,8 @@ public function add_text(int $page_to_appear, $text, $x, $y, $params = []): void $cx = $x; $cy = $pagesize_h - $y; - if ($angle !== 0) { + $rotate_command = ''; + if ($angle != 0) { $rotate_command = sprintf('%.5F %.5F %.5F %.5F %.2F %.2F cm 1 0 0 1 %.2F %.2F cm', $c, $s, -$s, $c, $cx, $cy, -$cx, -$cy); } @@ -143,7 +144,7 @@ public function add_text(int $page_to_appear, $text, $x, $y, $params = []): void throw new PDFException('please use html-like colors (e.g. #ffbbaa)'); } - if ($angle !== 0) { + if ($angle != 0) { $text_command = sprintf(' q %s %s Q', $rotate_command, $text_command); } diff --git a/src/PDFObject.php b/src/PDFObject.php index 0fa41db..b527d30 100644 --- a/src/PDFObject.php +++ b/src/PDFObject.php @@ -46,9 +46,9 @@ class PDFObject implements ArrayAccess, Stringable protected static $_xref_table_version; - protected $_stream = null; + protected $_stream; - protected $_value = null; + protected $_value; protected int $_generation; @@ -218,8 +218,6 @@ public function set_stream($stream, $raw = true): void * * @param field the field to set the value * @param value the value to set - * - * @return void */ public function offsetSet($offset, $value): void { diff --git a/src/PDFObjectParser.php b/src/PDFObjectParser.php index bf1a114..e40eea6 100644 --- a/src/PDFObjectParser.php +++ b/src/PDFObjectParser.php @@ -101,7 +101,7 @@ class PDFObjectParser implements Stringable self::T_COMMENT, ]; - protected $_buffer = null; + protected $_buffer; protected $_c = false; diff --git a/src/PDFSignatureObject.php b/src/PDFSignatureObject.php index 950fb24..1eefdc0 100644 --- a/src/PDFSignatureObject.php +++ b/src/PDFSignatureObject.php @@ -38,14 +38,14 @@ class PDFSignatureObject extends PDFObject protected int $_prev_content_size = 0; - protected $_post_content_size = null; + protected $_post_content_size; // A placeholder for the certificate to use to sign the document - protected $_certificate = null; + protected $_certificate; - protected $_signature_ltv_data = null; + protected $_signature_ltv_data; - protected $_signature_tsa = null; + protected $_signature_tsa; /** * Constructs the object and sets the default values needed to sign diff --git a/src/PDFUtilFnc.php b/src/PDFUtilFnc.php index 7494327..9bbb445 100644 --- a/src/PDFUtilFnc.php +++ b/src/PDFUtilFnc.php @@ -558,9 +558,7 @@ public static function find_object(&$_buffer, $xref_table, int $oid): false|PDFO return self::find_object_at_pos($_buffer, $oid, $object_offset, $xref_table); } - $object = self::find_object_in_objstm($_buffer, $xref_table, $object_offset['stmoid'], $object_offset['pos'], $oid); - - return $object; + return self::find_object_in_objstm($_buffer, $xref_table, $object_offset['stmoid'], $object_offset['pos'], $oid); } /** diff --git a/src/helpers/Buffer.php b/src/helpers/Buffer.php index f5cb8de..3f657c4 100644 --- a/src/helpers/Buffer.php +++ b/src/helpers/Buffer.php @@ -39,7 +39,7 @@ class Buffer implements Stringable protected int $_bufferlen; - public function __construct($string = null) + public function __construct(?string $string = null) { if ($string === null) { $string = ''; diff --git a/src/helpers/CMS.php b/src/helpers/CMS.php index b4a666f..0d8c421 100644 --- a/src/helpers/CMS.php +++ b/src/helpers/CMS.php @@ -11,6 +11,7 @@ */ use ddn\sapp\PDFException; +use Psr\Log\LoggerInterface; /** * @class cms @@ -20,10 +21,14 @@ class CMS { public $signature_data; + public function __construct( + private ?LoggerInterface $logger = null, + ) { + } + /** * send tsa/ocsp query with curl * - * @param array $reqData * * @return string response body * @public @@ -88,7 +93,6 @@ public function sendReq(array $reqData): string /** * Perform PKCS7 Signing * - * @param string $binaryData * * @return string hex + padding 0 * @public @@ -110,7 +114,7 @@ public function pkcs7_sign(string $binaryData): string throw new PDFException('not support hash algorithm!'); } - p_debug(sprintf('hash algorithm is "%s"', $hashAlgorithm)); + $this->logger?->debug(sprintf('hash algorithm is "%s"', $hashAlgorithm)); $x509 = new x509(); if (! $certParse = $x509::readcert($this->signature_data['signcert'])) { throw new PDFException('certificate error! check certificate'); @@ -120,7 +124,7 @@ public function pkcs7_sign(string $binaryData): string $appendLTV = ''; $ltvData = $this->signature_data['ltv']; if (! empty($ltvData)) { - p_debug(' LTV Validation start...'); + $this->logger?->debug(' LTV Validation start...'); $LTVvalidation_ocsp = ''; $LTVvalidation_crl = ''; $LTVvalidationEnd = false; @@ -128,7 +132,7 @@ public function pkcs7_sign(string $binaryData): string $isRootCA = false; // check whether root ca if ($certParse['tbsCertificate']['issuer']['hexdump'] == $certParse['tbsCertificate']['subject']['hexdump'] && openssl_public_decrypt(hex2bin((string) $certParse['signatureValue']), $decrypted, x509::x509_der2pem($x509::get_cert($this->signature_data['signcert'])), OPENSSL_PKCS1_PADDING)) { - p_debug(sprintf('***** "%s" is a ROOT CA. No validation performed ***', $certParse['tbsCertificate']['subject']['2.5.4.3'][0])); + $this->logger?->debug(sprintf('***** "%s" is a ROOT CA. No validation performed ***', $certParse['tbsCertificate']['subject']['2.5.4.3'][0])); $isRootCA = true; } @@ -137,7 +141,7 @@ public function pkcs7_sign(string $binaryData): string $LTVvalidation = true; $certtoCheck = $certParse; while ($LTVvalidation !== false) { - p_debug(sprintf('========= %d checking "%s"===============', $i, $certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0])); + $this->logger?->debug(sprintf('========= %d checking "%s"===============', $i, $certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0])); $LTVvalidation = $this->LTVvalidation($certtoCheck); $i++; if ($LTVvalidation) { @@ -151,7 +155,7 @@ public function pkcs7_sign(string $binaryData): string // check whether root ca if ($certtoCheck['tbsCertificate']['issuer']['hexdump'] == $certtoCheck['tbsCertificate']['subject']['hexdump'] && openssl_public_decrypt(hex2bin((string) $certtoCheck['signatureValue']), $decrypted, $x509::x509_der2pem($curr_issuer), OPENSSL_PKCS1_PADDING)) { - p_debug(sprintf('========= FINISH Reached ROOT CA "%s"===============', $certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0])); + $this->logger?->debug(sprintf('========= FINISH Reached ROOT CA "%s"===============', $certtoCheck['tbsCertificate']['subject']['2.5.4.3'][0])); $LTVvalidationEnd = true; break; } @@ -159,7 +163,7 @@ public function pkcs7_sign(string $binaryData): string } if ($LTVvalidationEnd) { - p_debug(" LTV Validation SUCCESS\n"); + $this->logger?->debug(" LTV Validation SUCCESS\n"); $ocsp = ''; if ($LTVvalidation_ocsp !== '' && $LTVvalidation_ocsp !== '0') { $ocsp = asn1::expl( @@ -232,9 +236,9 @@ public function pkcs7_sign(string $binaryData): string $hexencryptedDigest = bin2hex($encryptedDigest); $timeStamp = ''; if (! empty($this->signature_data['tsa'])) { - p_debug(' Timestamping process start...'); + $this->logger?->debug(' Timestamping process start...'); if ($TSTInfo = $this->createTimestamp($encryptedDigest, $hashAlgorithm)) { - p_debug(' Timestamping SUCCESS.'); + $this->logger?->debug(' Timestamping SUCCESS.'); $TimeStampToken = asn1::seq( '060B2A864886F70D010910020E' . // OBJ_id_smime_aa_timeStampToken 1.2.840.113549.1.9.16.2.14 asn1::set($TSTInfo) @@ -292,21 +296,20 @@ protected function createTimestamp(string $data, string $hashAlg = 'sha1') 'resp_contentType' => 'application/timestamp-reply', ] + $tsaData; - p_debug(' sending TSA query to "' . $tsaData['host'] . '"...'); + $this->logger?->debug(' sending TSA query to "' . $tsaData['host'] . '"...'); if (($binaryTsaResp = $this->sendReq($reqData)) === '' || ($binaryTsaResp = $this->sendReq($reqData)) === '0') { throw new PDFException('TSA query send FAILED!'); } - p_debug(' TSA query send OK'); - p_debug(' Parsing Timestamp response...'); + $this->logger?->debug(' TSA query send OK'); + $this->logger?->debug(' Parsing Timestamp response...'); if (! $tsaResp = $this->tsa_parseResp($binaryTsaResp)) { throw new PDFException('parsing FAILED!'); } - p_debug(' parsing OK'); - $TSTInfo = $tsaResp['TimeStampResp']['timeStampToken']['hexdump']; + $this->logger?->debug(' parsing OK'); - return $TSTInfo; + return $tsaResp['TimeStampResp']['timeStampToken']['hexdump']; } /** @@ -325,22 +328,22 @@ protected function LTVvalidation(array $parsedCert): false|array $ltvResult['ocsp'] = false; $ltvResult['crl'] = false; $certSigner_parse = $parsedCert; - p_debug(' getting OCSP & CRL address...'); - p_debug(' reading AIA OCSP attribute...'); + $this->logger?->debug(' getting OCSP & CRL address...'); + $this->logger?->debug(' reading AIA OCSP attribute...'); $ocspURI = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.1'][0]; if (trim((string) $ocspURI) === '' || trim((string) $ocspURI) === '0') { p_warning(' FAILED!'); } else { - p_debug(sprintf(' OK got address:"%s"', $ocspURI)); + $this->logger?->debug(sprintf(' OK got address:"%s"', $ocspURI)); } $ocspURI = trim((string) $ocspURI); - p_debug(' reading CRL CDP attribute...'); + $this->logger?->debug(' reading CRL CDP attribute...'); $crlURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['2.5.29.31']['value'][0]; if (trim($crlURIorFILE ?? '') === '' || trim($crlURIorFILE ?? '') === '0') { p_warning(' FAILED!'); } else { - p_debug(sprintf(' OK got address:"%s"', $crlURIorFILE)); + $this->logger?->debug(sprintf(' OK got address:"%s"', $crlURIorFILE)); } if (($ocspURI === '' || $ocspURI === '0') && empty($crlURIorFILE)) { @@ -348,25 +351,25 @@ protected function LTVvalidation(array $parsedCert): false|array } // Perform if either ocspURI/crlURIorFILE exists - p_debug(' getting Issuer...'); - p_debug(' looking for issuer address from AIA attribute...'); + $this->logger?->debug(' getting Issuer...'); + $this->logger?->debug(' looking for issuer address from AIA attribute...'); $issuerURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.2'][0]; $issuerURIorFILE = trim($issuerURIorFILE ?? ''); if ($issuerURIorFILE === '' || $issuerURIorFILE === '0') { - p_debug(' Failed!'); + $this->logger?->debug(' Failed!'); } else { - p_debug(sprintf(' OK got address "%s"...', $issuerURIorFILE)); - p_debug(sprintf(' load issuer from "%s"...', $issuerURIorFILE)); + $this->logger?->debug(sprintf(' OK got address "%s"...', $issuerURIorFILE)); + $this->logger?->debug(sprintf(' load issuer from "%s"...', $issuerURIorFILE)); if ($issuerCert = @file_get_contents($issuerURIorFILE)) { - p_debug(' OK. size ' . round(strlen($issuerCert) / 1024, 2) . 'Kb'); - p_debug(' reading issuer certificate...'); + $this->logger?->debug(' OK. size ' . round(strlen($issuerCert) / 1024, 2) . 'Kb'); + $this->logger?->debug(' reading issuer certificate...'); if ($issuer_certDER = x509::get_cert($issuerCert)) { - p_debug(' OK'); - p_debug(' check if issuer is cert issuer...'); + $this->logger?->debug(' OK'); + $this->logger?->debug(' check if issuer is cert issuer...'); $certIssuer_parse = x509::readcert($issuer_certDER, 'oid'); // Parsing Issuer cert $certSigner_signatureField = $certSigner_parse['signatureValue']; if (openssl_public_decrypt(hex2bin((string) $certSigner_signatureField), $decrypted, x509::x509_der2pem($issuer_certDER), OPENSSL_PKCS1_PADDING)) { - p_debug(' OK issuer is cert issuer.'); + $this->logger?->debug(' OK issuer is cert issuer.'); $ltvResult['issuer'] = $issuer_certDER; } else { p_warning(' FAILED! issuer is not cert issuer.'); @@ -380,18 +383,18 @@ protected function LTVvalidation(array $parsedCert): false|array } if (! $ltvResult['issuer']) { - p_debug(' search for issuer in extracerts.....'); + $this->logger?->debug(' search for issuer in extracerts.....'); if (array_key_exists('extracerts', $this->signature_data) && $this->signature_data['extracerts'] !== null && count($this->signature_data['extracerts']) > 0) { $i = 0; foreach ($this->signature_data['extracerts'] as $extracert) { - p_debug(sprintf(' extracerts[%d] ...', $i)); + $this->logger?->debug(sprintf(' extracerts[%d] ...', $i)); $certSigner_signatureField = $certSigner_parse['signatureValue']; if (openssl_public_decrypt(hex2bin((string) $certSigner_signatureField), $decrypted, $extracert, OPENSSL_PKCS1_PADDING)) { - p_debug(' OK got issuer.'); + $this->logger?->debug(' OK got issuer.'); $certIssuer_parse = x509::readcert($extracert, 'oid'); // Parsing Issuer cert $ltvResult['issuer'] = x509::get_cert($extracert); } else { - p_debug(' FAIL!'); + $this->logger?->debug(' FAIL!'); } $i++; @@ -403,13 +406,13 @@ protected function LTVvalidation(array $parsedCert): false|array if ($ltvResult['issuer']) { if ($ocspURI !== '' && $ocspURI !== '0') { - p_debug(' OCSP start...'); + $this->logger?->debug(' OCSP start...'); $ocspReq_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; $ocspReq_issuerNameHash = $certIssuer_parse['tbsCertificate']['subject']['sha1']; $ocspReq_issuerKeyHash = $certIssuer_parse['tbsCertificate']['subjectPublicKeyInfo']['sha1']; - p_debug(' OCSP create request...'); + $this->logger?->debug(' OCSP create request...'); if ($ocspReq = x509::ocsp_request($ocspReq_serialNumber, $ocspReq_issuerNameHash, $ocspReq_issuerKeyHash)) { - p_debug(' OK.'); + $this->logger?->debug(' OK.'); $ocspBinReq = pack('H*', $ocspReq); $reqData = [ 'data' => $ocspBinReq, @@ -417,16 +420,16 @@ protected function LTVvalidation(array $parsedCert): false|array 'req_contentType' => 'application/ocsp-request', 'resp_contentType' => 'application/ocsp-response', ]; - p_debug(sprintf(' OCSP send request to "%s"...', $ocspURI)); + $this->logger?->debug(sprintf(' OCSP send request to "%s"...', $ocspURI)); if (($ocspResp = $this->sendReq($reqData)) !== '' && ($ocspResp = $this->sendReq($reqData)) !== '0') { - p_debug(' OK.'); - p_debug(' OCSP parsing response...'); + $this->logger?->debug(' OK.'); + $this->logger?->debug(' OCSP parsing response...'); if ($ocsp_parse = x509::ocsp_response_parse($ocspResp, $return)) { - p_debug(' OK.'); - p_debug(' OCSP check cert validity...'); + $this->logger?->debug(' OK.'); + $this->logger?->debug(' OCSP check cert validity...'); $certStatus = $ocsp_parse['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses'][0]['certStatus']; if ($certStatus === 'valid') { - p_debug(' OK. VALID.'); + $this->logger?->debug(' OK. VALID.'); $ocspRespHex = $ocsp_parse['hexdump']; $ltvResult['ocsp'] = $ocspRespHex; } else { @@ -445,18 +448,18 @@ protected function LTVvalidation(array $parsedCert): false|array // CRL not processed if OCSP validation already success if (! $ltvResult['ocsp'] && ! empty($crlURIorFILE)) { - p_debug(' processing CRL validation since OCSP not done/failed...'); - p_debug(sprintf(' getting crl from "%s"...', $crlURIorFILE)); + $this->logger?->debug(' processing CRL validation since OCSP not done/failed...'); + $this->logger?->debug(sprintf(' getting crl from "%s"...', $crlURIorFILE)); if ($crl = @file_get_contents($crlURIorFILE)) { - p_debug(' OK. size ' . round(strlen($crl) / 1024, 2) . 'Kb'); - p_debug(' reading crl...'); + $this->logger?->debug(' OK. size ' . round(strlen($crl) / 1024, 2) . 'Kb'); + $this->logger?->debug(' reading crl...'); if ($crlread = x509::crl_read($crl)) { - p_debug(' OK'); - p_debug(' verify crl signature...'); + $this->logger?->debug(' OK'); + $this->logger?->debug(' verify crl signature...'); $crl_signatureField = $crlread['parse']['signature']; if (openssl_public_decrypt(hex2bin((string) $crl_signatureField), $decrypted, x509::x509_der2pem($ltvResult['issuer']), OPENSSL_PKCS1_PADDING)) { - p_debug(' OK'); - p_debug(' check CRL validity...'); + $this->logger?->debug(' OK'); + $this->logger?->debug(' check CRL validity...'); $crl_parse = $crlread['parse']; $thisUpdate = str_pad((string) $crl_parse['TBSCertList']['thisUpdate'], 15, '20', STR_PAD_LEFT); $thisUpdateTime = strtotime($thisUpdate); @@ -468,32 +471,32 @@ protected function LTVvalidation(array $parsedCert): false|array } if ($nextUpdateTime - $nowz < 1) { // not accept if crl 1 sec remain to expired - throw new PDFException(' FAILED! Expired crl at ' . date('d/m/Y H:i:s', $nextUpdateTime) . ' and now ' . date('d/m/Y H:i:s', $nowz) . '!'); + throw new PDFException('FAILED! Expired crl at ' . date('d/m/Y H:i:s', $nextUpdateTime) . ' and now ' . date('d/m/Y H:i:s', $nowz) . '!'); } - p_debug(' OK CRL still valid until ' . date('d/m/Y H:i:s', $nextUpdateTime)); + $this->logger?->debug(' OK CRL still valid until ' . date('d/m/Y H:i:s', $nextUpdateTime)); $crlCertValid = true; - p_debug(' check if cert not revoked...'); + $this->logger?->debug(' check if cert not revoked...'); if (array_key_exists('revokedCertificates', $crl_parse['TBSCertList'])) { $certSigner_serialNumber = $certSigner_parse['tbsCertificate']['serialNumber']; if (array_key_exists($certSigner_serialNumber, $crl_parse['TBSCertList']['revokedCertificates']['lists'])) { - throw new PDFException(' FAILED! Certificate Revoked!'); + throw new PDFException('FAILED! Certificate Revoked!'); } } if ($crlCertValid) { - p_debug(' OK. VALID'); + $this->logger?->debug(' OK. VALID'); $crlHex = current(unpack('H*', (string) $crlread['der'])); $ltvResult['crl'] = $crlHex; } } else { - throw new PDFException(' FAILED! Wrong CRL.'); + throw new PDFException('FAILED! Wrong CRL.'); } } else { - throw new PDFException(" FAILED! can't read crl"); + throw new PDFException("FAILED! can't read crl"); } } else { - throw new PDFException(" FAILED! can't get crl"); + throw new PDFException("FAILED! can't get crl"); } } } @@ -519,7 +522,7 @@ protected function LTVvalidation(array $parsedCert): false|array private function tsa_parseResp(string $binaryTsaRespData) { if (! @$ar = asn1::parse(bin2hex($binaryTsaRespData), 3)) { - throw new PDFException(" can't parse invalid tsa Response."); + throw new PDFException("can't parse invalid tsa Response."); } $curr = $ar; diff --git a/src/helpers/DependencyTreeObject.php b/src/helpers/DependencyTreeObject.php index e54d84e..95b2976 100644 --- a/src/helpers/DependencyTreeObject.php +++ b/src/helpers/DependencyTreeObject.php @@ -115,9 +115,6 @@ protected function _getstr(?string $spaces = '', int $mychcount = 0): string 'Annot' => ['P'], ]; -/** - * @return array - */ function references_in_object(PDFObject $object): array { $type = $object['Type']; diff --git a/src/helpers/asn1.php b/src/helpers/asn1.php index 5d3979c..08ca1bd 100644 --- a/src/helpers/asn1.php +++ b/src/helpers/asn1.php @@ -203,7 +203,7 @@ protected static function oneParse(string $hex): array|false $tlv_valueLength = substr($hex, 4, $tlv_lengthLength * 2); } else { $tlv_lengthLength = 0; - $tlv_valueLength = substr($hex, 2, 2 + $tlv_lengthLength * 2); + $tlv_valueLength = substr($hex, 2, 2); } if ($tlv_lengthLength > 4) { // limit tlv_lengthLength to FFFF diff --git a/src/helpers/helpers.php b/src/helpers/helpers.php index 3cafa92..93a5d3d 100644 --- a/src/helpers/helpers.php +++ b/src/helpers/helpers.php @@ -271,5 +271,5 @@ function timestamp_to_pdfdatestring(?DateTimeInterface $date = null): string */ function get_pdf_formatted_date(int $time) { - return substr_replace(date('YmdHisO', $time), "'", 0 - 2, 0) . "'"; + return substr_replace(date('YmdHisO', $time), "'", -2, 0) . "'"; } diff --git a/src/helpers/x509.php b/src/helpers/x509.php index 557a9a4..341e9d6 100644 --- a/src/helpers/x509.php +++ b/src/helpers/x509.php @@ -489,9 +489,8 @@ public static function x509_der2pem(string $der_cert): string { $x509_pem = "-----BEGIN CERTIFICATE-----\r\n"; $x509_pem .= chunk_split(base64_encode($der_cert), 64); - $x509_pem .= "-----END CERTIFICATE-----\r\n"; - return $x509_pem; + return $x509_pem . "-----END CERTIFICATE-----\r\n"; } /** From ad0f5a739173e2970a6ad534f38f7c9e47e8d5e2 Mon Sep 17 00:00:00 2001 From: Marcin Jakubowski Date: Fri, 1 Nov 2024 00:06:31 +0100 Subject: [PATCH 10/11] move binaries to dedicated folder and modernize them --- pdfcompare.php => bin/pdfcompare.php | 5 +- bin/pdfdeflate.php | 82 +++++++++++++++++++++++++ pdfrebuild.php => bin/pdfrebuild.php | 24 ++++---- pdfsign.php => bin/pdfsign.php | 45 +++++++------- pdfsigni.php => bin/pdfsigni.php | 31 ++++------ bin/pdfsignlts.php | 82 +++++++++++++++++++++++++ bin/pdfsigntsa.php | 73 ++++++++++++++++++++++ pdfsignx.php => bin/pdfsignx.php | 52 ++++++++-------- stty.bat => bin/stty.bat | 0 ecs.php | 1 + pdfdeflate.php | 79 ------------------------ pdfsignlts.php | 78 ----------------------- pdfsigntsa.php | 71 --------------------- phpstan.neon.dist | 1 + rector.php | 1 + src/AlmostOriginalLogger.php | 3 +- src/PDFDoc.php | 92 +++++++++++++++------------- src/PDFObject.php | 20 +++--- src/helpers/Buffer.php | 6 +- 19 files changed, 379 insertions(+), 367 deletions(-) rename pdfcompare.php => bin/pdfcompare.php (94%) create mode 100644 bin/pdfdeflate.php rename pdfrebuild.php => bin/pdfrebuild.php (70%) rename pdfsign.php => bin/pdfsign.php (56%) rename pdfsigni.php => bin/pdfsigni.php (84%) mode change 100755 => 100644 create mode 100644 bin/pdfsignlts.php create mode 100644 bin/pdfsigntsa.php rename pdfsignx.php => bin/pdfsignx.php (53%) mode change 100755 => 100644 rename stty.bat => bin/stty.bat (100%) delete mode 100644 pdfdeflate.php delete mode 100644 pdfsignlts.php delete mode 100644 pdfsigntsa.php diff --git a/pdfcompare.php b/bin/pdfcompare.php similarity index 94% rename from pdfcompare.php rename to bin/pdfcompare.php index 0f4c416..00c01b5 100644 --- a/pdfcompare.php +++ b/bin/pdfcompare.php @@ -22,7 +22,7 @@ use ddn\sapp\AlmostOriginalLogger; use ddn\sapp\PDFDoc; -require_once 'vendor/autoload.php'; +require_once __DIR__ . '/../vendor/autoload.php'; if ($argc !== 3) { fwrite(STDERR, sprintf("usage: %s ", $argv[0])); @@ -33,6 +33,7 @@ fwrite(STDERR, "failed to open file " . $argv[1]); exit(1); } + if (!file_exists($argv[2])) { fwrite(STDERR, "failed to open file " . $argv[2]); exit(1); @@ -45,6 +46,6 @@ $doc2->setLogger(new AlmostOriginalLogger()); $differences = $doc1->compare($doc2); -foreach ($differences as $oid => $obj) { +foreach ($differences as $obj) { print($obj->to_pdf_entry()); } diff --git a/bin/pdfdeflate.php b/bin/pdfdeflate.php new file mode 100644 index 0000000..61aa6cc --- /dev/null +++ b/bin/pdfdeflate.php @@ -0,0 +1,82 @@ +. +*/ + +use ddn\sapp\AlmostOriginalLogger; +use ddn\sapp\PDFDoc; + +require_once __DIR__ . '/../vendor/autoload.php'; + +if ($argc < 2 || $argc > 3) { + fwrite(STDERR, sprintf("usage: %s [oid]", $argv[0])); + exit(1); +} + +if (!file_exists($argv[1])) { + fwrite(STDERR, "failed to open file " . $argv[1]); + exit(1); +} + +$doc = PDFDoc::from_string(file_get_contents($argv[1])); +$doc->setLogger(new AlmostOriginalLogger()); + +$toid = null; +if ($argc === 3) { + $toid = (int)$argv[2]; +} + +foreach ($doc->get_object_iterator() as $oid => $object) { + if ($toid !== null && $oid !== $toid) { + continue; + } + + if ($object === false) { + continue; + } + + if ($object["Filter"] === "/FlateDecode" && $object["Subtype"] !== "/Image") { + $stream = $object->get_stream(false); + if ($stream !== false) { + unset($object["Filter"]); + $object->set_stream($stream, false); + $doc->add_object($object); + } + } + + // Not needed because we are rebuilding the document + if ($object["Type"] === "/ObjStm") { + $object->set_stream("", false); + $doc->add_object($object); + } + + // Do not want images to be uncompressed + if ($object["Subtype"] === "/Image") { + $object->set_stream(""); + $doc->add_object($object); + } + + if ($toid !== null) { + print($object->get_stream(false)); + } +} + +if ($toid === null) { + echo $doc->to_pdf_file_s(true); +} diff --git a/pdfrebuild.php b/bin/pdfrebuild.php similarity index 70% rename from pdfrebuild.php rename to bin/pdfrebuild.php index 6e7961e..ea0cb01 100644 --- a/pdfrebuild.php +++ b/bin/pdfrebuild.php @@ -21,23 +21,23 @@ use ddn\sapp\AlmostOriginalLogger; use ddn\sapp\PDFDoc; -use Psr\Log\AbstractLogger; -require_once 'vendor/autoload.php'; +require_once __DIR__ . '/../vendor/autoload.php'; if ($argc < 2 || $argc > 3) { fwrite(STDERR, sprintf("usage: %s []", $argv[0])); + exit(1); +} + +if (!file_exists($argv[1])) { + fwrite(STDERR, "failed to open file " . $argv[1]); } else { - if (!file_exists($argv[1])) { - fwrite(STDERR, "failed to open file " . $argv[1]); + $obj = PDFDoc::from_string(file_get_contents($argv[1])); + $obj->setLogger(new AlmostOriginalLogger()); + + if ($argc === 3) { + file_put_contents($argv[2], $obj->to_pdf_file_s(true)); } else { - $obj = PDFDoc::from_string(file_get_contents($argv[1])); - $obj->setLogger(new AlmostOriginalLogger()); - - if ($argc == 3) { - file_put_contents($argv[2], $obj->to_pdf_file_s(true)); - } else { - echo $obj->to_pdf_file_s(true); - } + echo $obj->to_pdf_file_s(true); } } diff --git a/pdfsign.php b/bin/pdfsign.php similarity index 56% rename from pdfsign.php rename to bin/pdfsign.php index 37d203e..8215735 100644 --- a/pdfsign.php +++ b/bin/pdfsign.php @@ -22,34 +22,35 @@ use ddn\sapp\AlmostOriginalLogger; use ddn\sapp\PDFDoc; -require_once 'vendor/autoload.php'; +require_once __DIR__ . '/../vendor/autoload.php'; if ($argc !== 3) { fwrite(STDERR, sprintf("usage: %s ", $argv[0])); + exit(1); +} + +if (!file_exists($argv[1])) { + fwrite(STDERR, "failed to open file " . $argv[1]); } else { - if (!file_exists($argv[1])) { - fwrite(STDERR, "failed to open file " . $argv[1]); + // Silently prompt for the password + fwrite(STDERR, "Password: "); + system('stty -echo'); + $password = trim(fgets(STDIN)); + system('stty echo'); + fwrite(STDERR, "\n"); + + $file_content = file_get_contents($argv[1]); + $obj = PDFDoc::from_string($file_content); + $obj->setLogger(new AlmostOriginalLogger()); + + if (!$obj->set_signature_certificate($argv[2], $password)) { + fwrite(STDERR, "the certificate is not valid"); } else { - // Silently prompt for the password - fwrite(STDERR, "Password: "); - system('stty -echo'); - $password = trim(fgets(STDIN)); - system('stty echo'); - fwrite(STDERR, "\n"); - - $file_content = file_get_contents($argv[1]); - $obj = PDFDoc::from_string($file_content); - $obj->setLogger(new AlmostOriginalLogger()); - - if (!$obj->set_signature_certificate($argv[2], $password)) { - fwrite(STDERR, "the certificate is not valid"); + $docsigned = $obj->to_pdf_file_s(); + if ($docsigned === false) { + fwrite(STDERR, "could not sign the document"); } else { - $docsigned = $obj->to_pdf_file_s(); - if ($docsigned === false) { - fwrite(STDERR, "could not sign the document"); - } else { - echo $docsigned; - } + echo $docsigned; } } } diff --git a/pdfsigni.php b/bin/pdfsigni.php old mode 100755 new mode 100644 similarity index 84% rename from pdfsigni.php rename to bin/pdfsigni.php index 414ee9e..6c882f4 --- a/pdfsigni.php +++ b/bin/pdfsigni.php @@ -24,46 +24,38 @@ use ddn\sapp\PDFDoc; use ddn\sapp\PDFException; -require_once 'vendor/autoload.php'; +require_once __DIR__ . '/../vendor/autoload.php'; if ($argc !== 4) { fwrite(STDERR, sprintf("usage: %s ", $argv[0])); exit(1); } + if (!file_exists($argv[1])) { fwrite(STDERR, "failed to open file " . $argv[1]); exit(1); } // Silently prompt for the password -//fwrite(STDERR, "Password: "); -//system('stty -echo'); -//$password = trim(fgets(STDIN)); -//system('stty echo'); -//fwrite(STDERR, "\n"); - -$password=''; +fwrite(STDERR, "Password: "); +system('stty -echo'); +$password = trim(fgets(STDIN)); +system('stty echo'); +fwrite(STDERR, "\n"); $file_content = file_get_contents($argv[1]); $obj = PDFDoc::from_string($file_content); $obj->setLogger(new AlmostOriginalLogger()); -if ($obj === false) { - fwrite(STDERR, "failed to parse file " . $argv[1]); - - exit(1); -} - $position = []; $image = $argv[2]; -$imagesize = @getimagesize($image); +$imagesize = getimagesize($image); if ($imagesize === false) { - fwrite(STDERR, "failed to open the image $image"); + fwrite(STDERR, 'failed to open the image ' . $image); exit(1); } - $pagesize = $obj->get_page_size(0); if ($pagesize === false) { return throw new PDFException("failed to get page size"); @@ -77,8 +69,7 @@ $p_y = (int)("" . $pagesize[1]); $p_w = (int)("" . $pagesize[2]) - $p_x; $p_h = (int)("" . $pagesize[3]) - $p_y; -$i_w = $imagesize[0]; -$i_h = $imagesize[1]; +[$i_w, $i_h] = $imagesize; $ratio_x = $p_w / $i_w; $ratio_y = $p_h / $i_h; @@ -94,7 +85,7 @@ fwrite(STDERR, "the certificate is not valid"); } else { $docsigned = $obj->to_pdf_file_s(); - if ($docsigned === false) { + if ($docsigned == false) { fwrite(STDERR, "could not sign the document"); } else { echo $docsigned; diff --git a/bin/pdfsignlts.php b/bin/pdfsignlts.php new file mode 100644 index 0000000..bdfa9b0 --- /dev/null +++ b/bin/pdfsignlts.php @@ -0,0 +1,82 @@ +#!/usr/bin/env php +. +*/ + +use ddn\sapp\AlmostOriginalLogger; +use ddn\sapp\PDFDoc; + +require_once __DIR__ . '/../vendor/autoload.php'; + +if ($argc < 3) { + fwrite( + STDERR, + sprintf( + "usage: %s \n +tsaUrl - optional TSA server url to timestamp pdf document. +", + $argv[0] + ) + ); + exit(1); +} + +if (!file_exists($argv[1])) { + fwrite(STDERR, "failed to open file " . $argv[1]); + exit(1); +} + +// Silently prompt for the password +fwrite(STDERR, "Password: "); +system('stty -echo'); +$password = trim(fgets(STDIN)); +system('stty echo'); +fwrite(STDERR, "\n"); + +$tsa = $argv[3] ?? null; +if ($tsa === null || $tsa === '' || $tsa === '0') { + // Silently prompt for the timestamp autority + fwrite(STDERR, 'TSA("http://timestamp.digicert.com") type "no" to bypass tsa: '); + system('stty -echo'); + $tsa = trim(fgets(STDIN)) ?: "http://timestamp.digicert.com"; + system('stty echo'); + fwrite(STDERR, "\n"); +} + +$file_content = file_get_contents($argv[1]); +$obj = PDFDoc::from_string($file_content); +$obj->setLogger(new AlmostOriginalLogger()); + +if (!$obj->set_signature_certificate($argv[2], $password)) { + fwrite(STDERR, "the certificate is not valid"); + exit(1); +} + +if ($tsa !== 'no') { + $obj->set_tsa($tsa); +} + +$obj->set_ltv(); +$docsigned = $obj->to_pdf_file_s(); +if ($docsigned == false) { + fwrite(STDERR, "could not sign the document"); +} else { + echo $docsigned; +} diff --git a/bin/pdfsigntsa.php b/bin/pdfsigntsa.php new file mode 100644 index 0000000..5d6a959 --- /dev/null +++ b/bin/pdfsigntsa.php @@ -0,0 +1,73 @@ +#!/usr/bin/env php +. +*/ + +use ddn\sapp\AlmostOriginalLogger; +use ddn\sapp\PDFDoc; + +require_once __DIR__ . '/../vendor/autoload.php'; + +if ($argc < 3) { + fwrite( + STDERR, + sprintf( + "usage: %s \n +tsaUrl - optional TSA server url to timestamp pdf document. +", + $argv[0] + ) + ); + exit(1); +} + +if (!file_exists($argv[1])) { + fwrite(STDERR, "failed to open file " . $argv[1]); + exit(1); +} + +// Silently prompt for the password +fwrite(STDERR, "Password: "); +system('stty -echo'); +$password = trim(fgets(STDIN)); +system('stty echo'); +fwrite(STDERR, "\n"); + +$tsa = $argv[3] ?? null; +if ($tsa === null || $tsa === '' || $tsa === '0') { + // Silently prompt for the timestamp autority + fwrite(STDERR, 'TSA("http://timestamp.digicert.com"): '); + system('stty -echo'); + $tsa = trim(fgets(STDIN)) ?: "http://timestamp.digicert.com"; + system('stty echo'); + fwrite(STDERR, "\n"); +} + +$file_content = file_get_contents($argv[1]); +$obj = PDFDoc::from_string($file_content); +$obj->setLogger(new AlmostOriginalLogger()); + +if (!$obj->set_signature_certificate($argv[2], $password)) { + fwrite(STDERR, "the certificate is not valid"); +} else { + $obj->set_tsa($tsa); + $docsigned = $obj->to_pdf_file_s(); + if ($docsigned == false) { + fwrite(STDERR, "could not sign the document"); + } else { + echo $docsigned; + } +} diff --git a/pdfsignx.php b/bin/pdfsignx.php old mode 100755 new mode 100644 similarity index 53% rename from pdfsignx.php rename to bin/pdfsignx.php index 6c9ea4c..6f5ca7d --- a/pdfsignx.php +++ b/bin/pdfsignx.php @@ -23,35 +23,37 @@ use ddn\sapp\AlmostOriginalLogger; use ddn\sapp\PDFDoc; -require_once 'vendor/autoload.php'; +require_once __DIR__ . '/../vendor/autoload.php'; if ($argc !== 4) { fwrite(STDERR, sprintf("usage: %s ", $argv[0])); + exit(1); +} + +if (!file_exists($argv[1])) { + fwrite(STDERR, "failed to open file " . $argv[1]); + exit(1); +} + +// Silently prompt for the password +fwrite(STDERR, "Password: "); +system('stty -echo'); +$password = trim(fgets(STDIN)); +system('stty echo'); +fwrite(STDERR, "\n"); + +$file_content = file_get_contents($argv[1]); +$obj = PDFDoc::from_string($file_content); +$obj->setLogger(new AlmostOriginalLogger()); + +$signedDoc = $obj->sign_document($argv[3], $password, 0, $argv[2]); +if ($signedDoc === false) { + fwrite(STDERR, "failed to sign the document"); } else { - if (!file_exists($argv[1])) { - fwrite(STDERR, "failed to open file " . $argv[1]); + $docsigned = $signedDoc->to_pdf_file_s(); + if ($docsigned == false) { + fwrite(STDERR, "could not sign the document"); } else { - // Silently prompt for the password - fwrite(STDERR, "Password: "); - system('stty -echo'); - $password = trim(fgets(STDIN)); - system('stty echo'); - fwrite(STDERR, "\n"); - - $file_content = file_get_contents($argv[1]); - $obj = PDFDoc::from_string($file_content); - $obj->setLogger(new AlmostOriginalLogger()); - - $signedDoc = $obj->sign_document($argv[3], $password, 0, $argv[2]); - if ($signedDoc === false) { - fwrite(STDERR, "failed to sign the document"); - } else { - $docsigned = $signedDoc->to_pdf_file_s(); - if ($docsigned === false) { - fwrite(STDERR, "could not sign the document"); - } else { - echo $docsigned; - } - } + echo $docsigned; } } diff --git a/stty.bat b/bin/stty.bat similarity index 100% rename from stty.bat rename to bin/stty.bat diff --git a/ecs.php b/ecs.php index 90ced41..3f38b4e 100644 --- a/ecs.php +++ b/ecs.php @@ -8,6 +8,7 @@ return ECSConfig::configure() ->withPaths([ __DIR__ . '/src', + __DIR__ . '/bin', ]) // add a single rule diff --git a/pdfdeflate.php b/pdfdeflate.php deleted file mode 100644 index d26ec3d..0000000 --- a/pdfdeflate.php +++ /dev/null @@ -1,79 +0,0 @@ -. -*/ - -use ddn\sapp\AlmostOriginalLogger; -use ddn\sapp\PDFDoc; - -require_once 'vendor/autoload.php'; - -if ($argc < 2 or $argc > 3) { - fwrite(STDERR, sprintf("usage: %s [oid]", $argv[0])); -} else { - if (!file_exists($argv[1])) { - fwrite(STDERR, "failed to open file " . $argv[1]); - } else { - $doc = PDFDoc::from_string(file_get_contents($argv[1])); - $doc->setLogger(new AlmostOriginalLogger()); - - $toid = null; - if ($argc === 3) { - $toid = (int)$argv[2]; - } - - foreach ($doc->get_object_iterator() as $oid => $object) { - if ($toid !== null) { - if ($oid != $toid) { - continue; - } - } - - if ($object === false) { - continue; - } - if ($object["Filter"] == "/FlateDecode") { - if ($object["Subtype"] != "/Image") { - $stream = $object->get_stream(false); - if ($stream !== false) { - unset($object["Filter"]); - $object->set_stream($stream, false); - $doc->add_object($object); - } - } - } - // Not needed because we are rebuilding the document - if ($object["Type"] == "/ObjStm") { - $object->set_stream("", false); - $doc->add_object($object); - } - // Do not want images to be uncompressed - if ($object["Subtype"] == "/Image") { - $object->set_stream(""); - $doc->add_object($object); - } - if ($toid != null) { - print($object->get_stream(false)); - } - } - if ($toid === null) { - echo $doc->to_pdf_file_s(true); - } - } -} diff --git a/pdfsignlts.php b/pdfsignlts.php deleted file mode 100644 index af4cce5..0000000 --- a/pdfsignlts.php +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env php -. -*/ - -use ddn\sapp\AlmostOriginalLogger; -use ddn\sapp\PDFDoc; - -require_once 'vendor/autoload.php'; - -if ($argc < 3) { - fwrite( - STDERR, - sprintf( - "usage: %s \n -tsaUrl - optional TSA server url to timestamp pdf document. -", - $argv[0] - ) - ); -} else { - if (!file_exists($argv[1])) { - fwrite(STDERR, "failed to open file " . $argv[1]); - } else { - // Silently prompt for the password - fwrite(STDERR, "Password: "); - system('stty -echo'); - $password = trim(fgets(STDIN)); - system('stty echo'); - fwrite(STDERR, "\n"); - - $tsa = $argv[3] ?? null; - if (empty($tsa)) { - // Silently prompt for the timestamp autority - fwrite(STDERR, "TSA(\"http://timestamp.digicert.com\") type \"no\" to bypass tsa: "); - system('stty -echo'); - $tsa = trim(fgets(STDIN)) ?: "http://timestamp.digicert.com"; - system('stty echo'); - fwrite(STDERR, "\n"); - } - - $file_content = file_get_contents($argv[1]); - $obj = PDFDoc::from_string($file_content); - $obj->setLogger(new AlmostOriginalLogger()); - - if (!$obj->set_signature_certificate($argv[2], $password)) { - fwrite(STDERR, "the certificate is not valid"); - } else { - if ($tsa != 'no') { - $obj->set_tsa($tsa); - } - $obj->set_ltv(); - $docsigned = $obj->to_pdf_file_s(); - if ($docsigned === false) { - fwrite(STDERR, "could not sign the document"); - } else { - echo $docsigned; - } - } - } -} diff --git a/pdfsigntsa.php b/pdfsigntsa.php deleted file mode 100644 index c5378aa..0000000 --- a/pdfsigntsa.php +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env php -. -*/ - -use ddn\sapp\AlmostOriginalLogger; -use ddn\sapp\PDFDoc; - -require_once 'vendor/autoload.php'; - -if ($argc < 3) { - fwrite( - STDERR, - sprintf( - "usage: %s \n -tsaUrl - optional TSA server url to timestamp pdf document. -", - $argv[0] - ) - ); -} else { - if (!file_exists($argv[1])) { - fwrite(STDERR, "failed to open file " . $argv[1]); - } else { - // Silently prompt for the password - fwrite(STDERR, "Password: "); - system('stty -echo'); - $password = trim(fgets(STDIN)); - system('stty echo'); - fwrite(STDERR, "\n"); - - $tsa = $argv[3] ?? null; - if (empty($tsa)) { - // Silently prompt for the timestamp autority - fwrite(STDERR, "TSA(\"http://timestamp.digicert.com\"): "); - system('stty -echo'); - $tsa = trim(fgets(STDIN)) ?: "http://timestamp.digicert.com"; - system('stty echo'); - fwrite(STDERR, "\n"); - } - - $file_content = file_get_contents($argv[1]); - $obj = PDFDoc::from_string($file_content); - $obj->setLogger(new AlmostOriginalLogger()); - - if (!$obj->set_signature_certificate($argv[2], $password)) { - fwrite(STDERR, "the certificate is not valid"); - } else { - $obj->set_tsa($tsa); - $docsigned = $obj->to_pdf_file_s(); - if ($docsigned === false) { - fwrite(STDERR, "could not sign the document"); - } else { - echo $docsigned; - } - } - } -} diff --git a/phpstan.neon.dist b/phpstan.neon.dist index b13a6da..188bd40 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -2,6 +2,7 @@ parameters: level: 1 paths: - src/ + - bin/ parallel: jobSize: 2 diff --git a/rector.php b/rector.php index 2bec622..d18757c 100644 --- a/rector.php +++ b/rector.php @@ -12,6 +12,7 @@ return RectorConfig::configure() ->withPaths([ __DIR__ . '/src', + __DIR__ . '/bin', ]) // uncomment to reach your current PHP version ->withPhpSets(php80: true) diff --git a/src/AlmostOriginalLogger.php b/src/AlmostOriginalLogger.php index 44db1e3..639ee4a 100644 --- a/src/AlmostOriginalLogger.php +++ b/src/AlmostOriginalLogger.php @@ -3,10 +3,11 @@ namespace ddn\sapp; use Psr\Log\AbstractLogger; +use Stringable; class AlmostOriginalLogger extends AbstractLogger { - public function log($level, string|\Stringable $message, array $context = []): void + public function log($level, string|Stringable $message, array $context = []): void { $dinfo = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT); $dinfo = $dinfo[1]; diff --git a/src/PDFDoc.php b/src/PDFDoc.php index 9c32b7c..8255045 100644 --- a/src/PDFDoc.php +++ b/src/PDFDoc.php @@ -33,9 +33,9 @@ use ddn\sapp\pdfvalue\PDFValueReference; use ddn\sapp\pdfvalue\PDFValueSimple; use ddn\sapp\pdfvalue\PDFValueString; +use Generator; use Psr\Log\LoggerInterface; use Throwable; - use function ddn\sapp\helpers\_add_image; use function ddn\sapp\helpers\get_random_string; use function ddn\sapp\helpers\p_warning; @@ -44,7 +44,7 @@ // Loading the functions -if (!defined(LoadHelpers::class)) { +if (! defined(LoadHelpers::class)) { new LoadHelpers(); } @@ -60,15 +60,15 @@ class PDFDoc extends Buffer protected $_pdf_trailer_object; - protected $_xref_position = 0; + protected int $_xref_position = 0; - protected $_xref_table = []; + protected array $_xref_table = []; - protected $_max_oid = 0; + protected int $_max_oid = 0; - protected $_buffer = ''; + protected ?string $_buffer = ''; - protected $_backup_state = []; + protected array $_backup_state = []; protected $_certificate; @@ -139,7 +139,7 @@ public function push_state(): void */ public function pop_state(): bool { - if (count($this->_backup_state) > 0) { + if ($this->_backup_state !== []) { $state = array_pop($this->_backup_state); $this->_max_oid = $state['max_oid']; $this->_pdf_objects = $state['pdf_objects']; @@ -209,7 +209,7 @@ public function get_revision(?int $rev_i): string $rev_i = count($this->_revisions) + $rev_i - 1; } - return substr((string)$this->_buffer, 0, $this->_revisions[$rev_i]); + return substr((string) $this->_buffer, 0, $this->_revisions[$rev_i]); } /** @@ -232,11 +232,11 @@ public function build_objects_from_xref(): void * @param allobjects the iterator obtains any possible object, according to the oids; otherwise, only will return the * objects that appear in the current version of the xref * - * @return oid=>obj the objects + * @return Generator */ - public function get_object_iterator($allobjects = false) + public function get_object_iterator(bool $allobjects = false) { - if ($allobjects === true) { + if ($allobjects) { for ($i = 0; $i <= $this->_max_oid; $i++) { yield $i => $this->get_object($i); } @@ -363,7 +363,7 @@ public function set_signature_certificate($certfile, ?string $certpass = null): throw new PDFException('invalid private key'); } - if (!openssl_x509_check_private_key($certificate['cert'], $certificate['pkey'])) { + if (! openssl_x509_check_private_key($certificate['cert'], $certificate['pkey'])) { throw new PDFException("private key doesn't corresponds to certificate"); } @@ -466,7 +466,7 @@ public function get_version() */ public function set_version($version): bool { - if (preg_match("/PDF-1.\[0-9\]/", (string)$version) !== 1) { + if (preg_match("/PDF-1.\[0-9\]/", (string) $version) !== 1) { return false; } @@ -588,7 +588,7 @@ public function to_pdf_file_b(bool $rebuild = false): Buffer $xref = PDFUtilFnc::build_xref_1_5($_obj_offsets); // Set the parameters for the trailer - $trailer['Index'] = explode(' ', (string)$xref['Index']); + $trailer['Index'] = explode(' ', (string) $xref['Index']); $trailer['W'] = $xref['W']; $trailer['Size'] = $this->_max_oid + 1; $trailer['Type'] = '/XRef'; @@ -651,6 +651,7 @@ public function to_pdf_file_b(bool $rebuild = false): Buffer $_signature->set_sizes($_doc_to_xref->size(), $_doc_from_xref->size()); $_signature['Contents'] = new PDFValueSimple(''); + assert($_signature instanceof PDFSignatureObject); $_signable_document = new Buffer($_doc_to_xref->get_raw() . $_signature->to_pdf_entry() . $_doc_from_xref->get_raw()); $certificate = $_signature->get_certificate(); $extracerts = $certificate['extracerts'] ?? null; @@ -668,6 +669,7 @@ public function to_pdf_file_b(bool $rebuild = false): Buffer $_signature['Contents'] = new PDFValueHexString($signature_contents); // Add this object to the content previous to this document xref + assert($_signature instanceof PDFSignatureObject); $_doc_to_xref->data($_signature->to_pdf_entry()); } @@ -682,7 +684,7 @@ public function to_pdf_file_b(bool $rebuild = false): Buffer * * @return buffer a buffer that contains a pdf document */ - public function to_pdf_file_s(bool $rebuild = false) + public function to_pdf_file_s(bool $rebuild = false): ?string { return $this->to_pdf_file_b($rebuild)->get_raw(); } @@ -765,7 +767,7 @@ public function get_page_size(int $i): false|array } // The page has not been found - if ($pageinfo === false || !isset($pageinfo['size'])) { + if ($pageinfo === false || ! isset($pageinfo['size'])) { return false; } @@ -826,7 +828,7 @@ public function get_object_tree(): array // foreach ($this->get_object_iterator() as $oid => $o) { // Create the object in the dependency tree and add it to the list of objects - if (!array_key_exists($oid, $objects)) { + if (! array_key_exists($oid, $objects)) { $objects[$oid] = new DependencyTreeObject($oid, $o['Type']); } @@ -843,14 +845,14 @@ public function get_object_tree(): array continue; } - if (!is_array($references)) { + if (! is_array($references)) { $references = [$references]; } } // $this->logger?->debug("$oid references " . implode(", ", $references)); foreach ($references as $r_object) { - if (!array_key_exists($r_object, $objects)) { + if (! array_key_exists($r_object, $objects)) { $r_object_o = $this->get_object($r_object); $objects[$r_object] = new DependencyTreeObject($r_object, $r_object_o['Type']); } @@ -871,7 +873,7 @@ public function get_object_tree(): array // Remove those objects that are child of other objects from the top of the tree foreach ($objects as $oid => $t_object) { - if (($t_object->is_child > 0 || in_array($t_object->info, ['/XRef', '/ObjStm'], true)) && !in_array($oid, $xref_children, true)) { + if (($t_object->is_child > 0 || in_array($t_object->info, ['/XRef', '/ObjStm'], true)) && ! in_array($oid, $xref_children, true)) { unset($objects[$oid]); } } @@ -900,7 +902,7 @@ public function get_signatures(): array } $o_value = $o->get_value()->val(); - if (!is_array($o_value) || !isset($o_value['Type'])) { + if (! is_array($o_value) || ! isset($o_value['Type'])) { continue; } @@ -917,7 +919,7 @@ public function get_signatures(): array openssl_pkcs7_read( "-----BEGIN CERTIFICATE-----\n" - . chunk_split(base64_encode(hex2bin((string)$signature['content'])), 64, "\n") + . chunk_split(base64_encode(hex2bin((string) $signature['content'])), 64, "\n") . "-----END CERTIFICATE-----\n", $cert ); @@ -977,11 +979,11 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, throw new PDFException('failed to get page size'); } - $pagesize = explode(' ', (string)$pagesize[0]->val()); + $pagesize = explode(' ', (string) $pagesize[0]->val()); // Get the bounding box for the image - $p_x = (int)$pagesize[0]; - $p_y = (int)$pagesize[1]; + $p_x = (int) $pagesize[0]; + $p_y = (int) $pagesize[1]; // Add the position for the image $p_x += $px; @@ -1014,7 +1016,7 @@ public function sign_document($certfile, $password = null, $page_to_appear = 0, $this->set_signature_appearance($page_to_appear, [$p_x, $p_y, $p_x + $i_w, $p_y + $i_h], $imagefilename); } - if (!$this->set_signature_certificate($certfile, $password)) { + if (! $this->set_signature_certificate($certfile, $password)) { throw new PDFException('the certificate or the signature is not valid'); } @@ -1081,18 +1083,19 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false throw new PDFException('invalid page'); } + assert($page_obj instanceof PDFObject); // The objects to update $updated_objects = []; // Add the annotation to the page - if (!isset($page_obj['Annots'])) { + if (! isset($page_obj['Annots'])) { $page_obj['Annots'] = new PDFValueList(); } $annots = &$page_obj['Annots']; $page_rotation = $page_obj['Rotate'] ?? new PDFValueSimple(0); - if (($referenced = $annots->get_object_referenced()) !== false && !is_array($referenced)) { + if (($referenced = $annots->get_object_referenced()) !== false && ! is_array($referenced)) { // It is an indirect object, so we need to update that object $newannots = $this->create_object( $this->get_object($referenced)->get_value() @@ -1161,8 +1164,8 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false // Get the page height, to change the coordinates system (up to down) $pagesize = $this->get_page_size($pagetoappear); - $pagesize = explode(' ', (string)$pagesize[0]->val()); - $pagesize_h = (float)('' . $pagesize[3]) - (float)('' . $pagesize[1]); + $pagesize = explode(' ', (string) $pagesize[0]->val()); + $pagesize_h = (float) ('' . $pagesize[3]) - (float) ('' . $pagesize[1]); $bbox = [0, 0, $recttoappear[2] - $recttoappear[0], $recttoappear[3] - $recttoappear[1]]; $form_object = $this->create_object([ @@ -1217,6 +1220,7 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false $container_form_object['Resources']['XObject']['n0'] = new PDFValueReference($layer_n0->get_oid()); $container_form_object['Resources']['XObject']['n2'] = new PDFValueReference($layer_n2->get_oid()); + assert($container_form_object instanceof PDFObject); $form_object['Resources'] = new PDFValueObject([ 'XObject' => [ 'FRM' => new PDFValueReference($container_form_object->get_oid()), @@ -1231,7 +1235,7 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false $annotation_object['Rect'] = [$recttoappear[0], $pagesize_h - $recttoappear[1], $recttoappear[2], $pagesize_h - $recttoappear[3]]; } - if (!$newannots->push(new PDFValueReference($annotation_object->get_oid()))) { + if (! $newannots->push(new PDFValueReference($annotation_object->get_oid()))) { throw new PDFException('Could not update the page where the signature has to appear'); } @@ -1239,12 +1243,12 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false $updated_objects[] = $page_obj; // AcroForm may be an indirect object - if (!isset($root_obj['AcroForm'])) { + if (! isset($root_obj['AcroForm'])) { $root_obj['AcroForm'] = new PDFValueObject(); } $acroform = &$root_obj['AcroForm']; - if (($referenced = $acroform->get_object_referenced()) !== false && !is_array($referenced)) { + if (($referenced = $acroform->get_object_referenced()) !== false && ! is_array($referenced)) { $acroform = $this->get_object($referenced); $updated_objects[] = $acroform; } else { @@ -1253,12 +1257,12 @@ protected function _generate_signature_in_document(): PDFSignatureObject|false // Add the annotation to the interactive form $acroform['SigFlags'] = 3; - if (!isset($acroform['Fields'])) { + if (! isset($acroform['Fields'])) { $acroform['Fields'] = new PDFValueList(); } // Add the annotation object to the interactive form - if (!$acroform['Fields']->push(new PDFValueReference($annotation_object->get_oid()))) { + if (! $acroform['Fields']->push(new PDFValueReference($annotation_object->get_oid()))) { throw new PDFException('could not create the signature field'); } @@ -1292,19 +1296,19 @@ protected function update_mod_date(?DateTime $date = null): bool throw new PDFException('invalid root object'); } - if (!$date instanceof DateTime) { + if (! $date instanceof DateTime) { $date = new DateTime(); } // Update the xmp metadata if exists if (isset($root_obj['Metadata'])) { $metadata = $root_obj['Metadata']; - if (($referenced = $metadata->get_object_referenced()) !== false && !is_array($referenced)) { + if (($referenced = $metadata->get_object_referenced()) !== false && ! is_array($referenced)) { $metadata = $this->get_object($referenced); $metastream = $metadata->get_stream(); - $metastream = preg_replace('/([^<]*)<\/xmp:ModifyDate>/', '' . $date->format('c') . '', (string)$metastream); - $metastream = preg_replace('/([^<]*)<\/xmp:MetadataDate>/', '' . $date->format('c') . '', (string)$metastream); - $metastream = preg_replace('/([^<]*)<\/xmpMM:InstanceID>/', 'uuid:' . UUID::v4() . '', (string)$metastream); + $metastream = preg_replace('/([^<]*)<\/xmp:ModifyDate>/', '' . $date->format('c') . '', (string) $metastream); + $metastream = preg_replace('/([^<]*)<\/xmp:MetadataDate>/', '' . $date->format('c') . '', (string) $metastream); + $metastream = preg_replace('/([^<]*)<\/xmpMM:InstanceID>/', 'uuid:' . UUID::v4() . '', (string) $metastream); $metadata->set_stream($metastream, false); $this->add_object($metadata); } @@ -1336,9 +1340,9 @@ protected function update_mod_date(?DateTime $date = null): bool * * @return xref_data [ the text corresponding to the objects, array of offsets for each object ] */ - protected function _generate_content_to_xref($rebuild = false): array + protected function _generate_content_to_xref(bool $rebuild = false): array { - if ($rebuild === true) { + if ($rebuild) { $result = new Buffer('%' . $this->_pdf_version_string . __EOL); } else { $result = new Buffer($this->_buffer); @@ -1351,7 +1355,7 @@ protected function _generate_content_to_xref($rebuild = false): array // The objects $offset = $result->size(); - if ($rebuild === true) { + if ($rebuild) { for ($i = 0; $i <= $this->_max_oid; $i++) { if (($object = $this->get_object($i)) === false) { continue; diff --git a/src/PDFObject.php b/src/PDFObject.php index b527d30..0277d3f 100644 --- a/src/PDFObject.php +++ b/src/PDFObject.php @@ -158,9 +158,9 @@ public function get_value() * * @return stream a string that contains the stream of the object */ - public function get_stream($raw = true) + public function get_stream(bool $raw = true): Buffer|false { - if ($raw === true) { + if ($raw) { return $this->_stream; } @@ -189,9 +189,9 @@ public function get_stream($raw = true) * * @param stream the stream for the object */ - public function set_stream($stream, $raw = true): void + public function set_stream($stream, bool $raw = true): void { - if ($raw === true) { + if ($raw) { $this->_stream = $stream; return; @@ -219,7 +219,7 @@ public function set_stream($stream, $raw = true): void * @param field the field to set the value * @param value the value to set */ - public function offsetSet($offset, $value): void + public function offsetSet(mixed $offset, mixed $value): void { $this->_value[$offset] = $value; } @@ -232,7 +232,7 @@ public function offsetSet($offset, $value): void * * @return exists true if the field exists; false otherwise */ - public function offsetExists($offset): bool + public function offsetExists(mixed $offset): bool { return $this->_value->offsetExists($offset); } @@ -245,7 +245,7 @@ public function offsetExists($offset): bool * @return value the value of the field */ #[ReturnTypeWillChange] - public function offsetGet($offset) + public function offsetGet(mixed $offset) { return $this->_value[$offset]; } @@ -255,17 +255,17 @@ public function offsetGet($offset) * * @param field the field to unset the value */ - public function offsetUnset($offset): void + public function offsetUnset(mixed $offset): void { $this->_value->offsetUnset($offset); } - public function push($v) + public function push(mixed $v): bool { return $this->_value->push($v); } - protected static function FlateDecode($_stream, array $params) + protected static function FlateDecode($_stream, array $params): Buffer|string|null { switch ($params['Predictor']->get_int()) { case 1: diff --git a/src/helpers/Buffer.php b/src/helpers/Buffer.php index 3f657c4..5ac55da 100644 --- a/src/helpers/Buffer.php +++ b/src/helpers/Buffer.php @@ -35,7 +35,7 @@ */ class Buffer implements Stringable { - protected $_buffer = ''; + protected ?string $_buffer; protected int $_bufferlen; @@ -46,7 +46,7 @@ public function __construct(?string $string = null) } $this->_buffer = $string; - $this->_bufferlen = strlen((string) $string); + $this->_bufferlen = strlen($string); } /** @@ -98,7 +98,7 @@ public function size(): int * * @return buffer the raw data */ - public function get_raw() + public function get_raw(): ?string { return $this->_buffer; } From 0e7cb215224485758675979a750dc737a0b3be9c Mon Sep 17 00:00:00 2001 From: Marcin Jakubowski Date: Fri, 1 Nov 2024 00:25:55 +0100 Subject: [PATCH 11/11] code cleanup --- bin/pdfcompare.php | 12 ++--- bin/pdfdeflate.php | 22 ++++----- bin/pdfrebuild.php | 6 +-- bin/pdfsign.php | 14 +++--- bin/pdfsigni.php | 26 +++++----- bin/pdfsignlts.php | 14 +++--- bin/pdfsigntsa.php | 14 +++--- bin/pdfsignx.php | 12 ++--- ecs.php | 11 ++++- phpstan.neon.dist | 2 +- src/PDFDoc.php | 19 +++----- src/PDFDocWithContents.php | 5 +- src/PDFObject.php | 13 ++--- src/PDFUtilFnc.php | 10 ---- src/helpers/CMS.php | 65 ++++++++++++------------- src/helpers/DependencyTreeObject.php | 5 +- src/helpers/helpers.php | 71 +--------------------------- src/helpers/x509.php | 1 - 18 files changed, 113 insertions(+), 209 deletions(-) diff --git a/bin/pdfcompare.php b/bin/pdfcompare.php index 00c01b5..42c9a89 100644 --- a/bin/pdfcompare.php +++ b/bin/pdfcompare.php @@ -25,17 +25,17 @@ require_once __DIR__ . '/../vendor/autoload.php'; if ($argc !== 3) { - fwrite(STDERR, sprintf("usage: %s ", $argv[0])); + fwrite(STDERR, sprintf('usage: %s ', $argv[0])); exit(1); } -if (!file_exists($argv[1])) { - fwrite(STDERR, "failed to open file " . $argv[1]); +if (! file_exists($argv[1])) { + fwrite(STDERR, 'failed to open file ' . $argv[1]); exit(1); } -if (!file_exists($argv[2])) { - fwrite(STDERR, "failed to open file " . $argv[2]); +if (! file_exists($argv[2])) { + fwrite(STDERR, 'failed to open file ' . $argv[2]); exit(1); } @@ -47,5 +47,5 @@ $differences = $doc1->compare($doc2); foreach ($differences as $obj) { - print($obj->to_pdf_entry()); + print $obj->to_pdf_entry(); } diff --git a/bin/pdfdeflate.php b/bin/pdfdeflate.php index 61aa6cc..ab6f23e 100644 --- a/bin/pdfdeflate.php +++ b/bin/pdfdeflate.php @@ -25,12 +25,12 @@ require_once __DIR__ . '/../vendor/autoload.php'; if ($argc < 2 || $argc > 3) { - fwrite(STDERR, sprintf("usage: %s [oid]", $argv[0])); + fwrite(STDERR, sprintf('usage: %s [oid]', $argv[0])); exit(1); } -if (!file_exists($argv[1])) { - fwrite(STDERR, "failed to open file " . $argv[1]); +if (! file_exists($argv[1])) { + fwrite(STDERR, 'failed to open file ' . $argv[1]); exit(1); } @@ -39,7 +39,7 @@ $toid = null; if ($argc === 3) { - $toid = (int)$argv[2]; + $toid = (int) $argv[2]; } foreach ($doc->get_object_iterator() as $oid => $object) { @@ -51,29 +51,29 @@ continue; } - if ($object["Filter"] === "/FlateDecode" && $object["Subtype"] !== "/Image") { + if ($object['Filter'] === '/FlateDecode' && $object['Subtype'] !== '/Image') { $stream = $object->get_stream(false); if ($stream !== false) { - unset($object["Filter"]); + unset($object['Filter']); $object->set_stream($stream, false); $doc->add_object($object); } } // Not needed because we are rebuilding the document - if ($object["Type"] === "/ObjStm") { - $object->set_stream("", false); + if ($object['Type'] === '/ObjStm') { + $object->set_stream('', false); $doc->add_object($object); } // Do not want images to be uncompressed - if ($object["Subtype"] === "/Image") { - $object->set_stream(""); + if ($object['Subtype'] === '/Image') { + $object->set_stream(''); $doc->add_object($object); } if ($toid !== null) { - print($object->get_stream(false)); + print $object->get_stream(false); } } diff --git a/bin/pdfrebuild.php b/bin/pdfrebuild.php index ea0cb01..a5eb57d 100644 --- a/bin/pdfrebuild.php +++ b/bin/pdfrebuild.php @@ -25,12 +25,12 @@ require_once __DIR__ . '/../vendor/autoload.php'; if ($argc < 2 || $argc > 3) { - fwrite(STDERR, sprintf("usage: %s []", $argv[0])); + fwrite(STDERR, sprintf('usage: %s []', $argv[0])); exit(1); } -if (!file_exists($argv[1])) { - fwrite(STDERR, "failed to open file " . $argv[1]); +if (! file_exists($argv[1])) { + fwrite(STDERR, 'failed to open file ' . $argv[1]); } else { $obj = PDFDoc::from_string(file_get_contents($argv[1])); $obj->setLogger(new AlmostOriginalLogger()); diff --git a/bin/pdfsign.php b/bin/pdfsign.php index 8215735..c59dfaf 100644 --- a/bin/pdfsign.php +++ b/bin/pdfsign.php @@ -25,15 +25,15 @@ require_once __DIR__ . '/../vendor/autoload.php'; if ($argc !== 3) { - fwrite(STDERR, sprintf("usage: %s ", $argv[0])); + fwrite(STDERR, sprintf('usage: %s ', $argv[0])); exit(1); } -if (!file_exists($argv[1])) { - fwrite(STDERR, "failed to open file " . $argv[1]); +if (! file_exists($argv[1])) { + fwrite(STDERR, 'failed to open file ' . $argv[1]); } else { // Silently prompt for the password - fwrite(STDERR, "Password: "); + fwrite(STDERR, 'Password: '); system('stty -echo'); $password = trim(fgets(STDIN)); system('stty echo'); @@ -43,12 +43,12 @@ $obj = PDFDoc::from_string($file_content); $obj->setLogger(new AlmostOriginalLogger()); - if (!$obj->set_signature_certificate($argv[2], $password)) { - fwrite(STDERR, "the certificate is not valid"); + if (! $obj->set_signature_certificate($argv[2], $password)) { + fwrite(STDERR, 'the certificate is not valid'); } else { $docsigned = $obj->to_pdf_file_s(); if ($docsigned === false) { - fwrite(STDERR, "could not sign the document"); + fwrite(STDERR, 'could not sign the document'); } else { echo $docsigned; } diff --git a/bin/pdfsigni.php b/bin/pdfsigni.php index 6c882f4..fcc40c5 100644 --- a/bin/pdfsigni.php +++ b/bin/pdfsigni.php @@ -27,17 +27,17 @@ require_once __DIR__ . '/../vendor/autoload.php'; if ($argc !== 4) { - fwrite(STDERR, sprintf("usage: %s ", $argv[0])); + fwrite(STDERR, sprintf('usage: %s ', $argv[0])); exit(1); } -if (!file_exists($argv[1])) { - fwrite(STDERR, "failed to open file " . $argv[1]); +if (! file_exists($argv[1])) { + fwrite(STDERR, 'failed to open file ' . $argv[1]); exit(1); } // Silently prompt for the password -fwrite(STDERR, "Password: "); +fwrite(STDERR, 'Password: '); system('stty -echo'); $password = trim(fgets(STDIN)); system('stty echo'); @@ -58,17 +58,17 @@ $pagesize = $obj->get_page_size(0); if ($pagesize === false) { - return throw new PDFException("failed to get page size"); + return throw new PDFException('failed to get page size'); } -$pagesize = explode(" ", $pagesize[0]->val()); +$pagesize = explode(' ', $pagesize[0]->val()); // Calculate the position of the image according to its size and the size of the page; // the idea is to keep the aspect ratio and center the image in the page with a size // of 1/3 of the size of the page. -$p_x = (int)("" . $pagesize[0]); -$p_y = (int)("" . $pagesize[1]); -$p_w = (int)("" . $pagesize[2]) - $p_x; -$p_h = (int)("" . $pagesize[3]) - $p_y; +$p_x = (int) ('' . $pagesize[0]); +$p_y = (int) ('' . $pagesize[1]); +$p_w = (int) ('' . $pagesize[2]) - $p_x; +$p_h = (int) ('' . $pagesize[3]) - $p_y; [$i_w, $i_h] = $imagesize; $ratio_x = $p_w / $i_w; @@ -81,12 +81,12 @@ $p_y = $p_h / 3; // Set the image appearance and the certificate file $obj->set_signature_appearance(0, [$p_x, $p_y, $p_x + $i_w, $p_y + $i_h], $image); -if (!$obj->set_signature_certificate($argv[3], $password)) { - fwrite(STDERR, "the certificate is not valid"); +if (! $obj->set_signature_certificate($argv[3], $password)) { + fwrite(STDERR, 'the certificate is not valid'); } else { $docsigned = $obj->to_pdf_file_s(); if ($docsigned == false) { - fwrite(STDERR, "could not sign the document"); + fwrite(STDERR, 'could not sign the document'); } else { echo $docsigned; } diff --git a/bin/pdfsignlts.php b/bin/pdfsignlts.php index bdfa9b0..3143573 100644 --- a/bin/pdfsignlts.php +++ b/bin/pdfsignlts.php @@ -38,13 +38,13 @@ exit(1); } -if (!file_exists($argv[1])) { - fwrite(STDERR, "failed to open file " . $argv[1]); +if (! file_exists($argv[1])) { + fwrite(STDERR, 'failed to open file ' . $argv[1]); exit(1); } // Silently prompt for the password -fwrite(STDERR, "Password: "); +fwrite(STDERR, 'Password: '); system('stty -echo'); $password = trim(fgets(STDIN)); system('stty echo'); @@ -55,7 +55,7 @@ // Silently prompt for the timestamp autority fwrite(STDERR, 'TSA("http://timestamp.digicert.com") type "no" to bypass tsa: '); system('stty -echo'); - $tsa = trim(fgets(STDIN)) ?: "http://timestamp.digicert.com"; + $tsa = trim(fgets(STDIN)) ?: 'http://timestamp.digicert.com'; system('stty echo'); fwrite(STDERR, "\n"); } @@ -64,8 +64,8 @@ $obj = PDFDoc::from_string($file_content); $obj->setLogger(new AlmostOriginalLogger()); -if (!$obj->set_signature_certificate($argv[2], $password)) { - fwrite(STDERR, "the certificate is not valid"); +if (! $obj->set_signature_certificate($argv[2], $password)) { + fwrite(STDERR, 'the certificate is not valid'); exit(1); } @@ -76,7 +76,7 @@ $obj->set_ltv(); $docsigned = $obj->to_pdf_file_s(); if ($docsigned == false) { - fwrite(STDERR, "could not sign the document"); + fwrite(STDERR, 'could not sign the document'); } else { echo $docsigned; } diff --git a/bin/pdfsigntsa.php b/bin/pdfsigntsa.php index 5d6a959..01fff9b 100644 --- a/bin/pdfsigntsa.php +++ b/bin/pdfsigntsa.php @@ -34,13 +34,13 @@ exit(1); } -if (!file_exists($argv[1])) { - fwrite(STDERR, "failed to open file " . $argv[1]); +if (! file_exists($argv[1])) { + fwrite(STDERR, 'failed to open file ' . $argv[1]); exit(1); } // Silently prompt for the password -fwrite(STDERR, "Password: "); +fwrite(STDERR, 'Password: '); system('stty -echo'); $password = trim(fgets(STDIN)); system('stty echo'); @@ -51,7 +51,7 @@ // Silently prompt for the timestamp autority fwrite(STDERR, 'TSA("http://timestamp.digicert.com"): '); system('stty -echo'); - $tsa = trim(fgets(STDIN)) ?: "http://timestamp.digicert.com"; + $tsa = trim(fgets(STDIN)) ?: 'http://timestamp.digicert.com'; system('stty echo'); fwrite(STDERR, "\n"); } @@ -60,13 +60,13 @@ $obj = PDFDoc::from_string($file_content); $obj->setLogger(new AlmostOriginalLogger()); -if (!$obj->set_signature_certificate($argv[2], $password)) { - fwrite(STDERR, "the certificate is not valid"); +if (! $obj->set_signature_certificate($argv[2], $password)) { + fwrite(STDERR, 'the certificate is not valid'); } else { $obj->set_tsa($tsa); $docsigned = $obj->to_pdf_file_s(); if ($docsigned == false) { - fwrite(STDERR, "could not sign the document"); + fwrite(STDERR, 'could not sign the document'); } else { echo $docsigned; } diff --git a/bin/pdfsignx.php b/bin/pdfsignx.php index 6f5ca7d..59679cd 100644 --- a/bin/pdfsignx.php +++ b/bin/pdfsignx.php @@ -26,17 +26,17 @@ require_once __DIR__ . '/../vendor/autoload.php'; if ($argc !== 4) { - fwrite(STDERR, sprintf("usage: %s ", $argv[0])); + fwrite(STDERR, sprintf('usage: %s ', $argv[0])); exit(1); } -if (!file_exists($argv[1])) { - fwrite(STDERR, "failed to open file " . $argv[1]); +if (! file_exists($argv[1])) { + fwrite(STDERR, 'failed to open file ' . $argv[1]); exit(1); } // Silently prompt for the password -fwrite(STDERR, "Password: "); +fwrite(STDERR, 'Password: '); system('stty -echo'); $password = trim(fgets(STDIN)); system('stty echo'); @@ -48,11 +48,11 @@ $signedDoc = $obj->sign_document($argv[3], $password, 0, $argv[2]); if ($signedDoc === false) { - fwrite(STDERR, "failed to sign the document"); + fwrite(STDERR, 'failed to sign the document'); } else { $docsigned = $signedDoc->to_pdf_file_s(); if ($docsigned == false) { - fwrite(STDERR, "could not sign the document"); + fwrite(STDERR, 'could not sign the document'); } else { echo $docsigned; } diff --git a/ecs.php b/ecs.php index 3f38b4e..a981a56 100644 --- a/ecs.php +++ b/ecs.php @@ -3,6 +3,8 @@ declare(strict_types=1); use PhpCsFixer\Fixer\Import\NoUnusedImportsFixer; +use Symplify\CodingStandard\Fixer\Commenting\ParamReturnAndVarTagMalformsFixer; +use Symplify\CodingStandard\Fixer\Commenting\RemoveUselessDefaultCommentFixer; use Symplify\EasyCodingStandard\Config\ECSConfig; return ECSConfig::configure() @@ -20,7 +22,12 @@ controlStructures: true, psr12: true, comments: true, -// docblocks: true, + docblocks: true, spaces: true, + cleanCode: true, namespaces: true, - ); + ) + ->withSkip([ + RemoveUselessDefaultCommentFixer::class, + ParamReturnAndVarTagMalformsFixer::class, + ]); diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 188bd40..0416964 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 1 + level: 2 paths: - src/ - bin/ diff --git a/src/PDFDoc.php b/src/PDFDoc.php index 8255045..69b803b 100644 --- a/src/PDFDoc.php +++ b/src/PDFDoc.php @@ -38,7 +38,6 @@ use Throwable; use function ddn\sapp\helpers\_add_image; use function ddn\sapp\helpers\get_random_string; -use function ddn\sapp\helpers\p_warning; use function ddn\sapp\helpers\references_in_object; use function ddn\sapp\helpers\timestamp_to_pdfdatestring; @@ -191,11 +190,11 @@ public static function from_string(string $buffer, ?int $depth = null): self $pdfdoc->_max_oid = array_pop($oids); if ($trailer === false) { - p_warning('invalid trailer object'); - } else { - $pdfdoc->_acquire_pages_info(); + throw new PDFException('invalid trailer object'); } + $pdfdoc->_acquire_pages_info(); + return $pdfdoc; } @@ -407,7 +406,6 @@ public function set_ltv(?string $ocspURI = null, ?string $crlURIorFILE = null, ? /** * Function that stores the tsa configuration to use, when signing the document * - * @param $tsaurl string Link to tsa service * @param $tsauser ?string user for tsa service * @param $tsapass ?string password for tsa service */ @@ -422,11 +420,6 @@ public function set_tsa(string $tsa, ?string $tsauser = null, ?string $tsapass = /** * Function to set the metadata properties for the certificate options - * - * @param $name - * @param $reason - * @param $location - * @param $contact */ public function set_metadata_props($name = null, $reason = null, $location = null, $contact = null): void { @@ -962,12 +955,12 @@ public function get_signature_count(): int * - if array [ width, height ], it will be the width and the height for the image to be included as a signature appearance (if * one of these values is null, it will fallback to the actual width or height of the image) */ - public function sign_document($certfile, $password = null, $page_to_appear = 0, $imagefilename = null, $px = 0, $py = 0, $size = null) + public function sign_document($certfile, $password = null, $page_to_appear = 0, $imagefilename = null, $px = 0, $py = 0, $size = null): self { if ($imagefilename !== null) { $imagesize = @getimagesize($imagefilename); if ($imagesize === false) { - return p_warning('failed to open the image ' . $imagesize); + throw new PDFException('failed to open the image ' . $imagesize); } if ($page_to_appear < 0 || $page_to_appear > $this->get_page_count() - 1) { @@ -1456,7 +1449,7 @@ protected function _acquire_pages_info(): array $this->_pages_info = $this->_get_page_info($pages); } else { - p_warning('root object does not exist, so cannot get information about pages'); + $this->logger?->warning('root object does not exist, so cannot get information about pages'); } return []; diff --git a/src/PDFDocWithContents.php b/src/PDFDocWithContents.php index f40d3ec..f6aa626 100644 --- a/src/PDFDocWithContents.php +++ b/src/PDFDocWithContents.php @@ -26,7 +26,6 @@ use ddn\sapp\pdfvalue\PDFValueReference; use function ddn\sapp\helpers\_add_image; use function ddn\sapp\helpers\get_random_string; -use function ddn\sapp\helpers\p_warning; class PDFDocWithContents extends PDFDoc { @@ -63,7 +62,7 @@ public function add_text(int $page_to_appear, $text, $x, $y, $params = []): void // TODO: maybe we can create a function that "adds content to a page", and that // function will search for the content field and merge the resources, if // needed - p_warning('This function still needs work'); + $this->logger?->warning('This function still needs work'); $default = [ 'font' => 'Helvetica', @@ -175,7 +174,7 @@ public function add_image($page_obj, $filename, $x = 0, $y = 0, $w = 0, $h = 0): // TODO: maybe we can create a function that "adds content to a page", and that // function will search for the content field and merge the resources, if // needed - p_warning('This function still needs work'); + $this->logger?->warning('This function still needs work'); // Check that the page is valid if (is_int($page_obj)) { diff --git a/src/PDFObject.php b/src/PDFObject.php index 0277d3f..a5a12ef 100644 --- a/src/PDFObject.php +++ b/src/PDFObject.php @@ -23,11 +23,11 @@ use ArrayAccess; use ddn\sapp\helpers\Buffer; +use ddn\sapp\pdfvalue\PDFValue; use ddn\sapp\pdfvalue\PDFValueObject; use ddn\sapp\pdfvalue\PDFValueSimple; use ReturnTypeWillChange; use Stringable; -use function ddn\sapp\helpers\p_warning; // Loading the functions @@ -50,17 +50,11 @@ class PDFObject implements ArrayAccess, Stringable protected $_value; - protected int $_generation; - public function __construct( protected int $_oid, - $value = null, - int $generation = 0, + PDFValue|array|null $value = null, + protected int $_generation = 0, ) { - if ($generation !== 0) { - p_warning('Objects of non-zero generation are not fully checked... please double check your document and (if possible) please send examples via issues to https://github.com/dealfonso/sapp/issues/'); - } - // If the value is null, we suppose that we are creating an empty object if ($value === null) { $value = new PDFValueObject(); @@ -77,7 +71,6 @@ public function __construct( } $this->_value = $value; - $this->_generation = $generation; } public function __toString(): string diff --git a/src/PDFUtilFnc.php b/src/PDFUtilFnc.php index 9bbb445..8ab5033 100644 --- a/src/PDFUtilFnc.php +++ b/src/PDFUtilFnc.php @@ -26,7 +26,6 @@ use ddn\sapp\helpers\StreamReader; use ddn\sapp\pdfvalue\PDFValue; use Exception; -use function ddn\sapp\helpers\p_warning; if (! defined(LoadHelpers::class)) { new LoadHelpers(); @@ -236,12 +235,6 @@ public static function get_xref_1_5(&$_buffer, int $xref_pos, ?int $depth = null case 1: // Add object $xref_table[$object_i] = $f2; - /* - TODO: consider creating a generation table, but for the purpose of the xref there is no matter... if the document if well-formed. - */ - if ($f3 !== 0) { - p_warning('Objects of non-zero generation are not fully checked... please double check your document and (if possible) please send examples via issues to https://github.com/dealfonso/sapp/issues/'); - } break; case 2: @@ -338,9 +331,6 @@ public static function get_xref_1_4(&$_buffer, string $xref_pos, $depth = null): // in the actual offset. // TODO: consider creating a "generation table" $xref_table[$obj_id] = $obj_offset; - if ($obj_generation !== 0) { - p_warning('Objects of non-zero generation are not fully checked... please double check your document and (if possible) please send examples via issues to https://github.com/dealfonso/sapp/issues/'); - } break; default: diff --git a/src/helpers/CMS.php b/src/helpers/CMS.php index 0d8c421..1cda93b 100644 --- a/src/helpers/CMS.php +++ b/src/helpers/CMS.php @@ -29,7 +29,6 @@ public function __construct( /** * send tsa/ocsp query with curl * - * * @return string response body * @public */ @@ -93,7 +92,6 @@ public function sendReq(array $reqData): string /** * Perform PKCS7 Signing * - * * @return string hex + padding 0 * @public */ @@ -194,7 +192,7 @@ public function pkcs7_sign(string $binaryData): string ) ); } else { - p_warning(" LTV Validation FAILED!\n"); + $this->logger?->warning("LTV Validation FAILED!\n"); } } @@ -245,7 +243,7 @@ public function pkcs7_sign(string $binaryData): string ); $timeStamp = asn1::expl(1, $TimeStampToken); } else { - p_warning(' Timestamping FAILED!'); + $this->logger?->warning('Timestamping FAILED!'); } } @@ -316,9 +314,6 @@ protected function createTimestamp(string $data, string $hashAlg = 'sha1') * Perform OCSP/CRL Validation * * @param array $parsedCert parsed certificate - * @param string $ocspURI - * @param string $crlURIorFILE - * @param string $issuerURIorFILE * * @return array */ @@ -332,18 +327,18 @@ protected function LTVvalidation(array $parsedCert): false|array $this->logger?->debug(' reading AIA OCSP attribute...'); $ocspURI = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.1'][0]; if (trim((string) $ocspURI) === '' || trim((string) $ocspURI) === '0') { - p_warning(' FAILED!'); + $this->logger?->warning('FAILED!'); } else { - $this->logger?->debug(sprintf(' OK got address:"%s"', $ocspURI)); + $this->logger?->debug(sprintf('OK got address:"%s"', $ocspURI)); } $ocspURI = trim((string) $ocspURI); $this->logger?->debug(' reading CRL CDP attribute...'); $crlURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['2.5.29.31']['value'][0]; if (trim($crlURIorFILE ?? '') === '' || trim($crlURIorFILE ?? '') === '0') { - p_warning(' FAILED!'); + $this->logger?->warning('FAILED!'); } else { - $this->logger?->debug(sprintf(' OK got address:"%s"', $crlURIorFILE)); + $this->logger?->debug(sprintf('OK got address:"%s"', $crlURIorFILE)); } if (($ocspURI === '' || $ocspURI === '0') && empty($crlURIorFILE)) { @@ -356,29 +351,29 @@ protected function LTVvalidation(array $parsedCert): false|array $issuerURIorFILE = @$certSigner_parse['tbsCertificate']['attributes']['1.3.6.1.5.5.7.1.1']['value']['1.3.6.1.5.5.7.48.2'][0]; $issuerURIorFILE = trim($issuerURIorFILE ?? ''); if ($issuerURIorFILE === '' || $issuerURIorFILE === '0') { - $this->logger?->debug(' Failed!'); + $this->logger?->debug('Failed!'); } else { - $this->logger?->debug(sprintf(' OK got address "%s"...', $issuerURIorFILE)); + $this->logger?->debug(sprintf('OK got address "%s"...', $issuerURIorFILE)); $this->logger?->debug(sprintf(' load issuer from "%s"...', $issuerURIorFILE)); if ($issuerCert = @file_get_contents($issuerURIorFILE)) { - $this->logger?->debug(' OK. size ' . round(strlen($issuerCert) / 1024, 2) . 'Kb'); + $this->logger?->debug('OK. size ' . round(strlen($issuerCert) / 1024, 2) . 'Kb'); $this->logger?->debug(' reading issuer certificate...'); if ($issuer_certDER = x509::get_cert($issuerCert)) { - $this->logger?->debug(' OK'); + $this->logger?->debug('OK'); $this->logger?->debug(' check if issuer is cert issuer...'); $certIssuer_parse = x509::readcert($issuer_certDER, 'oid'); // Parsing Issuer cert $certSigner_signatureField = $certSigner_parse['signatureValue']; if (openssl_public_decrypt(hex2bin((string) $certSigner_signatureField), $decrypted, x509::x509_der2pem($issuer_certDER), OPENSSL_PKCS1_PADDING)) { - $this->logger?->debug(' OK issuer is cert issuer.'); + $this->logger?->debug('OK issuer is cert issuer.'); $ltvResult['issuer'] = $issuer_certDER; } else { - p_warning(' FAILED! issuer is not cert issuer.'); + $this->logger?->warning('FAILED! issuer is not cert issuer.'); } } else { - p_warning(' FAILED!'); + $this->logger?->warning('FAILED!'); } } else { - p_warning(' FAILED!.'); + $this->logger?->warning('FAILED!.'); } } @@ -387,14 +382,14 @@ protected function LTVvalidation(array $parsedCert): false|array if (array_key_exists('extracerts', $this->signature_data) && $this->signature_data['extracerts'] !== null && count($this->signature_data['extracerts']) > 0) { $i = 0; foreach ($this->signature_data['extracerts'] as $extracert) { - $this->logger?->debug(sprintf(' extracerts[%d] ...', $i)); + $this->logger?->debug(sprintf('extracerts[%d] ...', $i)); $certSigner_signatureField = $certSigner_parse['signatureValue']; if (openssl_public_decrypt(hex2bin((string) $certSigner_signatureField), $decrypted, $extracert, OPENSSL_PKCS1_PADDING)) { - $this->logger?->debug(' OK got issuer.'); + $this->logger?->debug(' OK got issuer.'); $certIssuer_parse = x509::readcert($extracert, 'oid'); // Parsing Issuer cert $ltvResult['issuer'] = x509::get_cert($extracert); } else { - $this->logger?->debug(' FAIL!'); + $this->logger?->debug(' FAIL!'); } $i++; @@ -412,7 +407,7 @@ protected function LTVvalidation(array $parsedCert): false|array $ocspReq_issuerKeyHash = $certIssuer_parse['tbsCertificate']['subjectPublicKeyInfo']['sha1']; $this->logger?->debug(' OCSP create request...'); if ($ocspReq = x509::ocsp_request($ocspReq_serialNumber, $ocspReq_issuerNameHash, $ocspReq_issuerKeyHash)) { - $this->logger?->debug(' OK.'); + $this->logger?->debug('OK.'); $ocspBinReq = pack('H*', $ocspReq); $reqData = [ 'data' => $ocspBinReq, @@ -422,27 +417,27 @@ protected function LTVvalidation(array $parsedCert): false|array ]; $this->logger?->debug(sprintf(' OCSP send request to "%s"...', $ocspURI)); if (($ocspResp = $this->sendReq($reqData)) !== '' && ($ocspResp = $this->sendReq($reqData)) !== '0') { - $this->logger?->debug(' OK.'); + $this->logger?->debug('OK.'); $this->logger?->debug(' OCSP parsing response...'); if ($ocsp_parse = x509::ocsp_response_parse($ocspResp, $return)) { - $this->logger?->debug(' OK.'); + $this->logger?->debug('OK.'); $this->logger?->debug(' OCSP check cert validity...'); $certStatus = $ocsp_parse['responseBytes']['response']['BasicOCSPResponse']['tbsResponseData']['responses'][0]['certStatus']; if ($certStatus === 'valid') { - $this->logger?->debug(' OK. VALID.'); + $this->logger?->debug('OK. VALID.'); $ocspRespHex = $ocsp_parse['hexdump']; $ltvResult['ocsp'] = $ocspRespHex; } else { - p_warning(' FAILED! cert not valid, status:"' . strtoupper((string) $certStatus) . '"'); + $this->logger?->warning('FAILED! cert not valid, status:"' . strtoupper((string) $certStatus) . '"'); } } else { - p_warning(sprintf(' FAILED! Ocsp server status "%s"', $return)); + $this->logger?->warning(sprintf('FAILED! Ocsp server status "%s"', $return)); } } else { - p_warning(' FAILED!'); + $this->logger?->warning('FAILED!'); } } else { - p_warning(' FAILED!'); + $this->logger?->warning(' FAILED!'); } } @@ -451,14 +446,14 @@ protected function LTVvalidation(array $parsedCert): false|array $this->logger?->debug(' processing CRL validation since OCSP not done/failed...'); $this->logger?->debug(sprintf(' getting crl from "%s"...', $crlURIorFILE)); if ($crl = @file_get_contents($crlURIorFILE)) { - $this->logger?->debug(' OK. size ' . round(strlen($crl) / 1024, 2) . 'Kb'); + $this->logger?->debug('OK. size ' . round(strlen($crl) / 1024, 2) . 'Kb'); $this->logger?->debug(' reading crl...'); if ($crlread = x509::crl_read($crl)) { - $this->logger?->debug(' OK'); + $this->logger?->debug('OK'); $this->logger?->debug(' verify crl signature...'); $crl_signatureField = $crlread['parse']['signature']; if (openssl_public_decrypt(hex2bin((string) $crl_signatureField), $decrypted, x509::x509_der2pem($ltvResult['issuer']), OPENSSL_PKCS1_PADDING)) { - $this->logger?->debug(' OK'); + $this->logger?->debug('OK'); $this->logger?->debug(' check CRL validity...'); $crl_parse = $crlread['parse']; $thisUpdate = str_pad((string) $crl_parse['TBSCertList']['thisUpdate'], 15, '20', STR_PAD_LEFT); @@ -474,7 +469,7 @@ protected function LTVvalidation(array $parsedCert): false|array throw new PDFException('FAILED! Expired crl at ' . date('d/m/Y H:i:s', $nextUpdateTime) . ' and now ' . date('d/m/Y H:i:s', $nowz) . '!'); } - $this->logger?->debug(' OK CRL still valid until ' . date('d/m/Y H:i:s', $nextUpdateTime)); + $this->logger?->debug('OK CRL still valid until ' . date('d/m/Y H:i:s', $nextUpdateTime)); $crlCertValid = true; $this->logger?->debug(' check if cert not revoked...'); if (array_key_exists('revokedCertificates', $crl_parse['TBSCertList'])) { @@ -485,7 +480,7 @@ protected function LTVvalidation(array $parsedCert): false|array } if ($crlCertValid) { - $this->logger?->debug(' OK. VALID'); + $this->logger?->debug('OK. VALID'); $crlHex = current(unpack('H*', (string) $crlread['der'])); $ltvResult['crl'] = $crlHex; } diff --git a/src/helpers/DependencyTreeObject.php b/src/helpers/DependencyTreeObject.php index 95b2976..3ca346a 100644 --- a/src/helpers/DependencyTreeObject.php +++ b/src/helpers/DependencyTreeObject.php @@ -53,11 +53,8 @@ public function __toString(): string public function addchild(int $oid, self $o): void { $this->children[$oid] = $o; - if ($o->is_child !== 0) { - p_warning(sprintf('object %d is already a child of other object', $o->oid)); - } - ++$o->is_child; + $o->is_child++; } /** diff --git a/src/helpers/helpers.php b/src/helpers/helpers.php index 93a5d3d..0c2e88b 100644 --- a/src/helpers/helpers.php +++ b/src/helpers/helpers.php @@ -69,24 +69,6 @@ function debug_var(...$vars): ?string return implode("\n", $result); } -/** - * Function that writes the representation of some vars to - * - * @param vars comma separated list of variables to output - */ -function p_debug_var(...$vars): void -{ - // If the debug level is less than 3, suppress debug messages - if (_DEBUG_LEVEL < 3) { - return; - } - - foreach ($vars as $var) { - $e = var_dump_to_string($var); - p_stderr($e, 'Debug'); - } -} - /** * Function that converts an array into a string, but also recursively converts its values * just in case that they are also arrays. In case that it is not an array, it returns its @@ -112,57 +94,6 @@ function varval($e) return $retval; } -/** - * Function that writes a string to stderr, including some information about the call stack - * - * @param e the string to write to stderr - * @param tag the tag to prepend to the string and the debug information - * @param level the depth level to output (0 will refer to the function that called p_stderr - * call itself, 1 to the function that called to the function that called p_stderr) - */ -function p_stderr(string &$e, string $tag = 'Error', int $level = 1): void -{ - $dinfo = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT); - $dinfo = $dinfo[$level]; - - $e = sprintf('%s info at %s:%d: %s', $tag, $dinfo['file'], $dinfo['line'], varval($e)); - fwrite(STDERR, $e . PHP_EOL); -} - -/** - * Function that writes a string to stderr and returns a value (to ease coding like return p_debug(...)) - * - * @param e the debug message - * @param retval the value to return (default: false) - * - */ -function p_debug(string $e, mixed $retval = false) -{ - // If the debug level is less than 3, suppress debug messages - if (_DEBUG_LEVEL >= 3) { - p_stderr($e, 'Debug'); - } - - return $retval; -} - -/** - * Function that writes a string to stderr and returns a value (to ease coding like return p_warning(...)) - * - * @param e the debug message - * @param retval the value to return (default: false) - * - */ -function p_warning(string $e, mixed $retval = false) -{ - // If the debug level is less than 2, suppress warning messages - if (_DEBUG_LEVEL >= 2) { - p_stderr($e, 'Warning'); - } - - return $retval; -} - /** * Obtains a random string from a printable character set: alphanumeric, extended with * common symbols, an extended with less common symbols. @@ -269,7 +200,7 @@ function timestamp_to_pdfdatestring(?DateTimeInterface $date = null): string * @return string escaped date string. * @since 5.9.152 (2012-03-23) */ -function get_pdf_formatted_date(int $time) +function get_pdf_formatted_date(int $time): string { return substr_replace(date('YmdHisO', $time), "'", -2, 0) . "'"; } diff --git a/src/helpers/x509.php b/src/helpers/x509.php index 341e9d6..0b33563 100644 --- a/src/helpers/x509.php +++ b/src/helpers/x509.php @@ -522,7 +522,6 @@ public static function get_cert(string $certin): string|false /** * parse x.509 DER/PEM Certificate structure * - * @param string $certin pem/der form cert * @param bool|string $oidprint show oid as oid number or hex * * @return array cert structure