Subversion Repositories oidplus

Rev

Rev 1086 | Rev 1130 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * OIDplus 2.0
  5.  * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
  6.  *
  7.  * Licensed under the Apache License, Version 2.0 (the "License");
  8.  * you may not use this file except in compliance with the License.
  9.  * You may obtain a copy of the License at
  10.  *
  11.  *     http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing, software
  14.  * distributed under the License is distributed on an "AS IS" BASIS,
  15.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16.  * See the License for the specific language governing permissions and
  17.  * limitations under the License.
  18.  */
  19.  
  20. namespace ViaThinkSoft\OIDplus;
  21.  
  22. // phpcs:disable PSR1.Files.SideEffects
  23. \defined('INSIDE_OIDPLUS') or die;
  24. // phpcs:enable PSR1.Files.SideEffects
  25.  
  26. class OIDplusOIDIP {
  27.  
  28.         protected $XML_SCHEMA_URN;
  29.         protected $XML_SCHEMA_URL;
  30.         protected $JSON_SCHEMA_URN;
  31.         protected $JSON_SCHEMA_URL;
  32.  
  33.         /**
  34.          * @throws OIDplusException
  35.          */
  36.         public function __construct() {
  37.                 // NOTES:
  38.                 // - XML_SCHEMA_URN must be equal to the string in the .xsd file!
  39.                 // - the schema file names and draft version are also written in OIDplusPagePublicWhois.class.php
  40.                 $this->XML_SCHEMA_URN  = 'urn:ietf:id:draft-viathinksoft-oidip-05';
  41.                 $this->XML_SCHEMA_URL  = OIDplus::webpath(__DIR__,OIDplus::PATH_ABSOLUTE).'whois/draft-viathinksoft-oidip-05.xsd';
  42.                 $this->JSON_SCHEMA_URN = 'urn:ietf:id:draft-viathinksoft-oidip-05';
  43.                 $this->JSON_SCHEMA_URL = OIDplus::webpath(__DIR__,OIDplus::PATH_ABSOLUTE).'whois/draft-viathinksoft-oidip-05.json';
  44.         }
  45.  
  46.         /**
  47.          * @param $query
  48.          * @return array|void
  49.          * @throws OIDplusException
  50.          */
  51.         public function oidipQuery($query) {
  52.  
  53.                 $out_type = null;
  54.                 $out_content = '';
  55.  
  56.                 // Split input into query and arguments
  57.                 $chunks = explode('$', $query);
  58.                 $query = array_shift($chunks);
  59.                 $original_query = $query;
  60.                 $arguments = array();
  61.                 foreach ($chunks as $chunk) {
  62.                         if (strtolower(substr($chunk,0,5)) !== 'auth=') $original_query .= '$'.$chunk;
  63.  
  64.                         if (strpos($chunk,'=') === false) continue;
  65.                         $tmp = explode('=',$chunk,2);
  66.  
  67.                         //if (!preg_match('@^[a-z0-9]+$@', $tmp[0], $m)) continue; // be strict with the names. They must be lowercase (TODO: show "Service error" message?)
  68.                         $tmp[0] = strtolower($tmp[0]); // be tolerant instead
  69.  
  70.                         $arguments[$tmp[0]] = $tmp[1];
  71.                 }
  72.  
  73.                 $query = str_replace('oid:.', 'oid:', $query); // allow leading dot
  74.  
  75.                 if (isset($arguments['format'])) {
  76.                         $format = $arguments['format'];
  77.                 } else {
  78.                         $format = 'text'; // default
  79.                 }
  80.  
  81.                 if (isset($arguments['auth'])) {
  82.                         $authTokens = explode(',', $arguments['auth']);
  83.                 } else {
  84.                         $authTokens = array();
  85.                 }
  86.  
  87.                 // $lang= is not implemented, since we don't know in which language the OID descriptions are written by the site operator
  88.                 /*
  89.                 if (isset($arguments['lang'])) {
  90.                         $languages = explode(',', $arguments['lang']);
  91.                 } else {
  92.                         $languages = array();
  93.                 }
  94.                 */
  95.  
  96.                 $unimplemented_format = ($format != 'text') && ($format != 'json') && ($format != 'xml');
  97.                 if ($unimplemented_format) {
  98.                         $format = 'text';
  99.                 }
  100.  
  101.                 // Step 1: Collect data
  102.  
  103.                 $out = array();
  104.  
  105.                 // ATTENTION: THE ORDER IS IMPORTANT FOR THE XML VALIDATION!
  106.                 // The order of the RFC is the same as in the XSD
  107.                 $out[] = $this->_oidip_attr('query', $original_query);
  108.  
  109.                 $query = OIDplus::prefilterQuery($query, false);
  110.  
  111.                 if ($unimplemented_format) {
  112.                         $out[] = $this->_oidip_attr('result', 'Service error');
  113.                         $out[] = $this->_oidip_attr('message', 'Format is not implemented');
  114.                         $out[] = $this->_oidip_attr('lang', 'en-US');
  115.                 } else {
  116.  
  117.                         $distance = null;
  118.                         $found = null;
  119.  
  120.                         $obj = OIDplusObject::parse($query);
  121.  
  122.                         $only_wellknown_ids_found = false;
  123.                         $continue = false;
  124.  
  125.                         if (!$obj) {
  126.                                 // Object type not known or invalid syntax of $query
  127.                                 $out[] = $this->_oidip_attr('result', 'Not found'); // DO NOT TRANSLATE!
  128.                                 $continue = false;
  129.                         } else {
  130.  
  131.                                 $query = $obj->nodeId(); // normalize
  132.  
  133.                                 $obj = null;
  134.                                 $distance = 0;
  135.  
  136.                                 $init_query = $query;
  137.                                 while (true) {
  138.  
  139.                                         $obj_test = OIDplusObject::findFitting($query);
  140.                                         if ($obj_test) {
  141.                                                 $obj = $obj_test;
  142.                                         } else {
  143.                                                 $alts = OIDplusPagePublicObjects::getAlternativesForQuery($query);
  144.                                                 foreach ($alts as $alt) {
  145.                                                         $obj_test = OIDplusObject::findFitting($alt);
  146.                                                         if ($obj_test) {
  147.                                                                 $query = $alt;
  148.                                                                 $obj = $obj_test;
  149.                                                         }
  150.                                                 }
  151.                                         }
  152.                                         if ($obj) {
  153.                                                 if ($distance > 0) {
  154.                                                         $out[] = $this->_oidip_attr('result', 'Not found; superior object found'); // DO NOT TRANSLATE!
  155.                                                         $out[] = $this->_oidip_attr('distance', $distance); // DO NOT TRANSLATE
  156.                                                 } else {
  157.                                                         $out[] = $this->_oidip_attr('result', 'Found'); // DO NOT TRANSLATE!
  158.                                                 }
  159.                                                 $continue = true;
  160.                                                 break;
  161.                                         }
  162.  
  163.                                         if (substr($query,0,4) === 'oid:') {
  164.                                                 $query_prev = $query;
  165.                                                 $query = 'oid:'.oid_up(explode(':',$query,2)[1]);
  166.                                                 if ($query == $query_prev) break;
  167.                                                 $distance++;
  168.                                         } else {
  169.                                                 // getParent() will find the parent which DOES exist in the DB.
  170.                                                 // It does not need to be the direct parent (like ->one_up() does)
  171.                                                 $obj = OIDplusObject::parse($query)->getParent(); // For objects, we assume that they are parents of each other
  172.                                                 if ($obj) {
  173.                                                         $distance = $obj->distance($query);
  174.                                                         assert(OIDplusObject::findFitting($query));
  175.  
  176.                                                         $query = $obj->nodeId();
  177.                                                 }
  178.  
  179.                                                 if ($distance > 0) {
  180.                                                         $out[] = $this->_oidip_attr('result', 'Not found; superior object found'); // DO NOT TRANSLATE!
  181.                                                         $out[] = $this->_oidip_attr('distance', $distance); // DO NOT TRANSLATE
  182.                                                 }
  183.                                                 $continue = true;
  184.  
  185.                                                 break;
  186.                                         }
  187.                                 }
  188.  
  189.                                 if ((substr($query,0,4) === 'oid:') && (!$obj)) {
  190.                                         $query = $init_query;
  191.                                         $distance = 0;
  192.                                         while (true) {
  193.                                                 // Checks if there is any identifier (so it is a well-known OID)
  194.                                                 $res_test = OIDplus::db()->query("select * from ###asn1id where oid = ? union select * from ###iri where oid = ?", array($query, $query));
  195.                                                 if ($res_test->any()) {
  196.                                                         $obj = OIDplusObject::parse($query);
  197.                                                         if ($distance > 0) {
  198.                                                                 $out[] = $this->_oidip_attr('result', 'Not found; superior object found'); // DO NOT TRANSLATE!
  199.                                                                 $out[] = $this->_oidip_attr('distance', $distance); // DO NOT TRANSLATE
  200.                                                         } else {
  201.                                                                 $out[] = $this->_oidip_attr('result', 'Found'); // DO NOT TRANSLATE!
  202.                                                         }
  203.                                                         $only_wellknown_ids_found = true; // Information partially available
  204.                                                         $continue = true;
  205.                                                         break;
  206.                                                 }
  207.                                                 $query_prev = $query;
  208.                                                 $query = 'oid:'.oid_up(explode(':',$query,2)[1]);
  209.                                                 if ($query == $query_prev) break;
  210.                                                 $distance++;
  211.                                         }
  212.                                 }
  213.  
  214.                                 if (!$obj) {
  215.                                         $out[] = $this->_oidip_attr('result', 'Not found'); // DO NOT TRANSLATE!
  216.                                         $continue = false;
  217.                                 }
  218.  
  219.                                 $found = $distance == 0;
  220.                         }
  221.  
  222.                         if ($continue) {
  223.                                 $out[] = '';
  224.  
  225.                                 // ATTENTION: THE ORDER IS IMPORTANT FOR THE XML VALIDATION!
  226.                                 // The order of the RFC is the same as in the XSD
  227.                                 $out[] = $this->_oidip_attr('object', $query); // DO NOT TRANSLATE!
  228.                                 if (!$this->allowObjectView($obj, $authTokens)) {
  229.                                         $out[] = $this->_oidip_attr('status', 'Information unavailable'); // DO NOT TRANSLATE!
  230.                                         $out[] = $this->_oidip_attr('attribute', 'confidential'); // DO NOT TRANSLATE!
  231.                                 } else {
  232.                                         if ($only_wellknown_ids_found) {
  233.                                                 $out[] = $this->_oidip_attr('status', 'Information partially available'); // DO NOT TRANSLATE!
  234.                                         } else {
  235.                                                 $out[] = $this->_oidip_attr('status', 'Information available'); // DO NOT TRANSLATE!
  236.                                         }
  237.  
  238.                                         // $this->_oidip_attr('lang', ...); // not implemented (since we don't know the language of the texts written by the page operator)
  239.  
  240.                                         if ($obj) {
  241.                                                 $out[] = $this->_oidip_attr('name', $obj->getTitle()); // DO NOT TRANSLATE!
  242.  
  243.                                                 $cont = $obj->getDescription();
  244.                                                 $cont = preg_replace('@<a[^>]+href\s*=\s*["\']([^\'"]+)["\'][^>]*>(.+)<\s*/\s*a\s*>@ismU', '\2 (\1)', $cont);
  245.                                                 $cont = preg_replace('@<br.*>@', "\n", $cont);
  246.                                                 $cont = preg_replace('@\\n+@', "\n", $cont);
  247.                                                 $out[] = $this->_oidip_attr('description', trim(html_entity_decode(strip_tags($cont)))); // DO NOT TRANSLATE!
  248.                                         }
  249.  
  250.                                         // $this->_oidip_attr('information', ...); Not used. Contains additional information, e.g. Management Information Base (MIB) definitions.
  251.  
  252.                                         if ($only_wellknown_ids_found) {
  253.                                                 if (substr($query,0,4) === 'oid:') {
  254.                                                         // Since it is well-known, oid-info.com will most likely have it described
  255.                                                         $out[] = $this->_oidip_attr('url', 'https://oid-rep.orange-labs.fr/get/'.$obj->nodeId(false));
  256.                                                 }
  257.                                         } else {
  258.                                                 $out[] = $this->_oidip_attr('url', OIDplus::webpath(null,OIDplus::PATH_ABSOLUTE).'?goto='.urlencode($obj->nodeId(true)));
  259.                                         }
  260.  
  261.                                         if (substr($query,0,4) === 'oid:') {
  262.                                                 assert($obj instanceof OIDplusOid); //assert(get_class($obj) === "ViaThinkSoft\OIDplus\OIDplusOid");
  263.  
  264.                                                 $out[] = $this->_oidip_attr('asn1-notation', $obj->getAsn1Notation(false)); // DO NOT TRANSLATE!
  265.                                                 $out[] = $this->_oidip_attr('iri-notation', $obj->getIriNotation(false)); // DO NOT TRANSLATE!
  266.  
  267.                                                 $res_asn = OIDplus::db()->query("select * from ###asn1id where oid = ?", array($obj->nodeId()));
  268.                                                 while ($row_asn = $res_asn->fetch_object()) {
  269.                                                         $out[] = $this->_oidip_attr('identifier', $row_asn->name); // DO NOT TRANSLATE!
  270.                                                 }
  271.  
  272.                                                 $res_asn = OIDplus::db()->query("select * from ###asn1id where standardized = ? and oid = ?", array(true, $obj->nodeId()));
  273.                                                 while ($row_asn = $res_asn->fetch_object()) {
  274.                                                         $out[] = $this->_oidip_attr('standardized-id', $row_asn->name); // DO NOT TRANSLATE!
  275.                                                 }
  276.  
  277.                                                 $res_iri = OIDplus::db()->query("select * from ###iri where oid = ?", array($obj->nodeId()));
  278.                                                 while ($row_iri = $res_iri->fetch_object()) {
  279.                                                         $out[] = $this->_oidip_attr('unicode-label', $row_iri->name); // DO NOT TRANSLATE!
  280.                                                 }
  281.  
  282.                                                 $res_iri = OIDplus::db()->query("select * from ###iri where longarc = ? and oid = ?", array(true, $obj->nodeId()));
  283.                                                 while ($row_iri = $res_iri->fetch_object()) {
  284.                                                         $out[] = $this->_oidip_attr('long-arc', $row_iri->name); // DO NOT TRANSLATE!
  285.                                                 }
  286.                                         }
  287.  
  288.                                         // $this->_oidip_attr('oidip-service', ...); Not used.
  289.  
  290.                                         // $this->_oidip_attr('oidip-pubkey', ...); Not used.
  291.  
  292.                                         if ($obj->implementsFeature('1.3.6.1.4.1.37476.2.5.2.3.4')) {
  293.                                                 // Also ask $obj for extra attributes:
  294.                                                 // This way we could add various additional information, e.g. IPv4/6 range analysis, interpretation of GUID, etc.
  295.                                                 $obj->whoisObjectAttributes($obj->nodeId(), $out); /** @phpstan-ignore-line */
  296.                                         }
  297.  
  298.                                         foreach (OIDplus::getAllPlugins() as $plugin) {
  299.                                                 if ($plugin->implementsFeature('1.3.6.1.4.1.37476.2.5.2.3.4')) {
  300.                                                         $plugin->whoisObjectAttributes($obj->nodeId(), $out); /** @phpstan-ignore-line */
  301.                                                 }
  302.                                         }
  303.  
  304.                                         if ($obj->isConfidential()) { // yes, we use isConfidential() instead of $this->allowObjectView()!
  305.                                                 $out[] = $this->_oidip_attr('attribute', 'confidential'); // DO NOT TRANSLATE!
  306.                                         }
  307.  
  308.                                         if (substr($query,0,4) === 'oid:') {
  309.                                                 $sParent = 'oid:'.oid_up(explode(':',$query,2)[1]);
  310.  
  311.                                                 $objTest = OIDplusObject::parse($sParent);
  312.                                                 if ($this->allowObjectView($objTest, $authTokens)) {
  313.                                                         $out[] = $this->_oidip_attr('parent', $sParent.$this->show_asn1_appendix($sParent)); // DO NOT TRANSLATE!
  314.                                                 } else {
  315.                                                         $out[] = $this->_oidip_attr('parent', $sParent); // DO NOT TRANSLATE!
  316.                                                 }
  317.                                         } else {
  318.                                                 $objParent = $obj->getParent();
  319.                                                 $sParent = $objParent ? $objParent->nodeId() : '';
  320.                                                 if (!empty($sParent) && (!$this->is_root($sParent))) {
  321.                                                         $out[] = $this->_oidip_attr('parent', $sParent); // DO NOT TRANSLATE!
  322.                                                 }
  323.                                         }
  324.  
  325.                                         $res_children = OIDplus::db()->query("select * from ###objects where parent = ? order by ".OIDplus::db()->natOrder('id'), array($obj->nodeId()));
  326.                                         while ($row_children = $res_children->fetch_object()) {
  327.                                                 $objTest = OIDplusObject::parse($row_children->id);
  328.                                                 if ($this->allowObjectView($objTest, $authTokens)) {
  329.                                                         $out[] = $this->_oidip_attr('subordinate', $row_children->id.$this->show_asn1_appendix($row_children->id)); // DO NOT TRANSLATE!
  330.                                                 } else {
  331.                                                         $out[] = $this->_oidip_attr('subordinate', $row_children->id); // DO NOT TRANSLATE!
  332.                                                 }
  333.                                         }
  334.  
  335.                                         if ($obj) {
  336.                                                 if ($tim = $obj->getCreatedTime()) $out[] = $this->_oidip_attr('created', date('Y-m-d H:i:s', strtotime($tim))); // DO NOT TRANSLATE!
  337.                                                 if ($tim = $obj->getUpdatedTime()) $out[] = $this->_oidip_attr('updated', date('Y-m-d H:i:s', strtotime($tim))); // DO NOT TRANSLATE!
  338.                                         }
  339.  
  340.                                         $out[] = '';
  341.  
  342.                                         // ATTENTION: THE ORDER IS IMPORTANT FOR THE XML VALIDATION!
  343.                                         // The order of the RFC is the same as in the XSD
  344.                                         $res_ra = OIDplus::db()->query("select * from ###ra where email = ?", array($obj ? $obj->getRaMail() : ''));
  345.                                         if ($row_ra = $res_ra->fetch_object()) {
  346.                                                 $out[] = $this->_oidip_attr('ra', (!empty($row_ra->ra_name) ? $row_ra->ra_name : (!empty($row_ra->email) ? $row_ra->email : /*_L*/('Unknown')))); // DO NOT TRANSLATE!
  347.  
  348.                                                 if (!$this->allowRAView($row_ra, $authTokens)) {
  349.                                                         $out[] = $this->_oidip_attr('ra-status', 'Information partially available'); // DO NOT TRANSLATE!
  350.                                                 } else {
  351.                                                         $out[] = $this->_oidip_attr('ra-status', 'Information available'); // DO NOT TRANSLATE!
  352.                                                 }
  353.  
  354.                                                 // $this->_oidip_attr('ra-lang', ...); // not implemented (since we don't know the language of the texts written by the page operator)
  355.  
  356.                                                 $tmp = array();
  357.                                                 if (!empty($row_ra->office)) $tmp[] = $row_ra->office;
  358.                                                 if (!empty($row_ra->organization)) $tmp[] = $row_ra->organization;
  359.                                                 $tmp = implode(', ', $tmp);
  360.  
  361.                                                 $out[] = $this->_oidip_attr('ra-contact-name', $row_ra->personal_name.(!empty($tmp) ? " ($tmp)" : '')); // DO NOT TRANSLATE!
  362.                                                 if (!$this->allowRAView($row_ra, $authTokens)) {
  363.                                                         if (!empty($row_ra->street) || !empty($row_ra->zip_town) || !empty($row_ra->country)) {
  364.                                                                 $out[] = $this->_oidip_attr('ra-address', /*_L*/('(redacted)')); // DO NOT TRANSLATE!
  365.                                                         }
  366.                                                         $out[] = $this->_oidip_attr('ra-phone', (!empty($row_ra->phone) ? /*_L*/('(redacted)') : '')); // DO NOT TRANSLATE!
  367.                                                         $out[] = $this->_oidip_attr('ra-mobile', (!empty($row_ra->mobile) ? /*_L*/('(redacted)') : '')); // DO NOT TRANSLATE!
  368.                                                         $out[] = $this->_oidip_attr('ra-fax', (!empty($row_ra->fax) ? /*_L*/('(redacted)') : '')); // DO NOT TRANSLATE!
  369.                                                 } else {
  370.                                                         $address = array();
  371.                                                         if (!empty($row_ra->street))   $address[] = $row_ra->street; // DO NOT TRANSLATE!
  372.                                                         if (!empty($row_ra->zip_town)) $address[] = $row_ra->zip_town; // DO NOT TRANSLATE!
  373.                                                         if (!empty($row_ra->country))  $address[] = $row_ra->country; // DO NOT TRANSLATE!
  374.                                                         if (count($address) > 0) $out[] = $this->_oidip_attr('ra-address', implode("\n",$address));
  375.                                                         $out[] = $this->_oidip_attr('ra-phone', $row_ra->phone); // DO NOT TRANSLATE!
  376.                                                         $out[] = $this->_oidip_attr('ra-mobile', $row_ra->mobile); // DO NOT TRANSLATE!
  377.                                                         $out[] = $this->_oidip_attr('ra-fax', $row_ra->fax); // DO NOT TRANSLATE!
  378.                                                 }
  379.                                                 $out[] = $this->_oidip_attr('ra-email', $obj->getRaMail()); // DO NOT TRANSLATE!
  380.  
  381.                                                 // $this->_oidip_attr('ra-url', ...); Not used.
  382.  
  383.                                                 $ra = new OIDplusRA($obj->getRaMail());
  384.                                                 if ($ra->implementsFeature('1.3.6.1.4.1.37476.2.5.2.3.4')) {
  385.                                                         $ra->whoisRaAttributes($obj->getRaMail(), $out); /** @phpstan-ignore-line */
  386.                                                 }
  387.  
  388.                                                 foreach (OIDplus::getAllPlugins() as $plugin) {
  389.                                                         if ($plugin->implementsFeature('1.3.6.1.4.1.37476.2.5.2.3.4')) {
  390.                                                                 $plugin->whoisRaAttributes($obj->getRaMail(), $out); /** @phpstan-ignore-line */
  391.                                                         }
  392.                                                 }
  393.  
  394.                                                 // yes, we use row_ra->privacy() instead of $this->allowRAView(), becuase $this->allowRAView=true if auth token is given; and we want to inform the person that they content they are viewing is confidential!
  395.                                                 if ($row_ra->privacy) {
  396.                                                         $out[] = $this->_oidip_attr('ra-attribute', 'confidential'); // DO NOT TRANSLATE!
  397.                                                 }
  398.  
  399.                                                 if ($row_ra->registered) $out[] = $this->_oidip_attr('ra-created', date('Y-m-d H:i:s', strtotime($row_ra->registered))); // DO NOT TRANSLATE!
  400.                                                 if ($row_ra->updated)    $out[] = $this->_oidip_attr('ra-updated', date('Y-m-d H:i:s', strtotime($row_ra->updated))); // DO NOT TRANSLATE!
  401.                                         } else {
  402.                                                 $out[] = $this->_oidip_attr('ra', ($obj && !empty($obj->getRaMail()) ? $obj->getRaMail() : /*_L*/('Unknown'))); // DO NOT TRANSLATE!
  403.                                                 if ($obj) {
  404.                                                         foreach (OIDplus::getAllPlugins() as $plugin) {
  405.                                                                 if ($plugin->implementsFeature('1.3.6.1.4.1.37476.2.5.2.3.4')) {
  406.                                                                         $plugin->whoisRaAttributes($obj->getRaMail(), $out); /** @phpstan-ignore-line */
  407.                                                                 }
  408.                                                         }
  409.                                                 }
  410.                                                 $out[] = $this->_oidip_attr('ra-status', 'Information unavailable'); // DO NOT TRANSLATE!
  411.                                         }
  412.                                 }
  413.                         }
  414.                 }
  415.  
  416.                 // Upgrade legacy $out to extended $out format
  417.  
  418.                 $this->_oidip_newout_format($out);
  419.  
  420.                 // Trim all values
  421.  
  422.                 foreach ($out as &$tmp) {
  423.                         $tmp['value'] = trim($tmp['value']);
  424.                 }
  425.  
  426.                 // Step 2: Format output
  427.  
  428.                 if ($format == 'text') {
  429.                         $out_type = 'text/plain; charset=UTF-8';
  430.  
  431.                         $longest_key = 0;
  432.                         foreach ($out as $data) {
  433.                                 $longest_key = max($longest_key, strlen(trim($data['name'])));
  434.                         }
  435.  
  436.                         ob_start();
  437.  
  438.                         //$out_content .= '% ' . str_repeat('*', OIDplus::config()->getValue('webwhois_output_format_max_line_length', 80)-2)."\r\n";
  439.  
  440.                         foreach ($out as $data) {
  441.                                 if ($data['name'] == '') {
  442.                                         $out_content .= "\r\n";
  443.                                         continue;
  444.                                 }
  445.  
  446.                                 $key = $data['name'];
  447.                                 $value = $data['value'];
  448.  
  449.                                 // Normalize line-breaks to \r\n, otherwise mb_wordwrap won't work correctly
  450.                                 $value = str_replace("\r\n", "\n", $value);
  451.                                 $value = str_replace("\r", "\n", $value);
  452.                                 $value = str_replace("\n", "\r\n", $value);
  453.  
  454.                                 $value = mb_wordwrap($value, OIDplus::config()->getValue('webwhois_output_format_max_line_length', 80) - $longest_key - strlen(':') - OIDplus::config()->getValue('webwhois_output_format_spacer', 2), "\r\n");
  455.                                 $value = str_replace("\r\n", "\r\n$key:".str_repeat(' ', $longest_key-strlen($key)) . str_repeat(' ', OIDplus::config()->getValue('webwhois_output_format_spacer', 2)), $value);
  456.  
  457.                                 if (!empty($value)) {
  458.                                         $out_content .= $key.':' . str_repeat(' ', $longest_key-strlen($key)) . str_repeat(' ', OIDplus::config()->getValue('webwhois_output_format_spacer', 2)) . $value . "\r\n";
  459.                                 }
  460.                         }
  461.  
  462.                         //$out_content .= '% ' . str_repeat('*', OIDplus::config()->getValue('webwhois_output_format_max_line_length', 80)-2)."\r\n";
  463.  
  464.                         $cont = ob_get_contents();
  465.                         ob_end_clean();
  466.  
  467.                         $out_content .= $cont;
  468.  
  469.                         if (OIDplus::getPkiStatus()) {
  470.                                 $signature = '';
  471.                                 if (@openssl_sign($cont, $signature, OIDplus::getSystemPrivateKey())) {
  472.                                         $signature = base64_encode($signature);
  473.                                         $signature = mb_wordwrap($signature, OIDplus::config()->getValue('webwhois_output_format_max_line_length', 80) - strlen('% '), "\r\n", true);
  474.                                         $signature = "% -----BEGIN RSA SIGNATURE-----\r\n".
  475.                                                      preg_replace('/^/m', '% ', $signature)."\r\n".
  476.                                                      "% -----END RSA SIGNATURE-----\r\n";
  477.                                         $out_content .= $signature;
  478.                                 }
  479.                         }
  480.                 }
  481.  
  482.                 if ($format == 'json') {
  483.                         $ary = array();
  484.  
  485.                         $current_section = array();
  486.                         $ary[] = &$current_section;
  487.  
  488.                         foreach ($out as $data) {
  489.                                 if ($data['name'] == '') {
  490.                                         unset($current_section);
  491.                                         $current_section = array();
  492.                                         $ary[] = &$current_section;
  493.                                 } else {
  494.                                         $key = $data['name'];
  495.                                         $val = trim($data['value']);
  496.                                         if (!empty($val)) {
  497.                                                 if (!isset($current_section[$key])) {
  498.                                                         $current_section[$key] = $val;
  499.                                                 } elseif (is_array($current_section[$key])) {
  500.                                                         $current_section[$key][] = $val;
  501.                                                 } else {
  502.                                                         $current_section[$key] = array($current_section[$key], $val);
  503.                                                 }
  504.                                         }
  505.                                 }
  506.                         }
  507.  
  508.                         // Change $ary=[["query",...], ["object",...], ["ra",...]]
  509.                         // to     $bry=["querySection"=>["query",...], "objectsection"=>["object",...], "raSection"=>["ra",...]]
  510.                         $bry = array();
  511.                         foreach ($ary as $cry) {
  512.                                 $dry = array_keys($cry);
  513.                                 if (count($dry) == 0) continue;
  514.                                 $bry[$dry[0].'Section'] = $cry; /** @phpstan-ignore-line */ // PHPStan thinks that count($dry) is always 0
  515.                         }
  516.  
  517.                         // Remove 'ra-', 'ra1-', ... field prefixes from JSON (the prefix is only for the text view)
  518.                         foreach ($bry as $section_name => &$cry) { /** @phpstan-ignore-line */ // PHPStan thinks that $bry is empty
  519.                                 if (preg_match('@^(ra\d*)\\Section@', $section_name, $m)) {
  520.                                         $ra_id = str_replace('Section', '', $section_name);
  521.                                         $dry = array();
  522.                                         foreach ($cry as $name => $val) {
  523.                                                 if ($name == $ra_id) $name = 'ra'; // First field is always 'ra' even in 'ra1Section' etc.
  524.                                                 $name = preg_replace('@^('.preg_quote($ra_id,'@').')\\-@', '', $name);
  525.                                                 $dry[$name] = $val;
  526.                                         }
  527.                                         $cry = $dry;
  528.                                 }
  529.                         }
  530.  
  531.                         $ary = array(
  532.                                 // We use the URN here, because $id of the schema also uses the URN
  533.                                 '$schema' => $this->JSON_SCHEMA_URN,
  534.  
  535.                                 // we need this NAMED root, otherwise PHP will name the sections "0", "1", "2" if the array is not sequencial (e.g. because "signature" is added)
  536.                                 'oidip' => $bry
  537.                         );
  538.  
  539.                         $json = json_encode($ary);
  540.  
  541.                         if (OIDplus::getPkiStatus()) {
  542.                                 try {
  543.                                         require_once __DIR__.'/whois/json/security.inc.php';
  544.                                         $json = oidplus_json_sign($json, OIDplus::getSystemPrivateKey(), OIDplus::getSystemPublicKey());
  545.                                 } catch (\Exception $e) {
  546.                                         // die($e->getMessage());
  547.                                 }
  548.                         }
  549.  
  550.                         // Good JSON schema validator here: https://www.jsonschemavalidator.net
  551.                         $out_type = 'application/json; charset=UTF-8';
  552.                         $out_content .= $json;
  553.                 }
  554.  
  555.                 if ($format == 'xml') {
  556.                         $xml_oidip = '<oidip><section>';
  557.                         foreach ($out as $data) {
  558.                                 if ($data['name'] == '') {
  559.                                         $xml_oidip .= '</section><section>';
  560.                                 } else {
  561.                                         if ($data['xmlns'] != '') {
  562.                                                 $key = $data['xmlns'].':'.$data['name'];
  563.                                         } else {
  564.                                                 $key = $data['name'];
  565.                                         }
  566.                                         $val = trim($data['value']);
  567.                                         if (!empty($val)) {
  568.                                                 if (strpos($val,"\n") !== false) {
  569.                                                         $val = str_replace(']]>', ']]]]><![CDATA[>', $val); // Escape ']]>'
  570.                                                         $xml_oidip .= "<$key><![CDATA[$val]]></$key>";
  571.                                                 } else {
  572.                                                         $xml_oidip .= "<$key>".htmlspecialchars($val, ENT_XML1)."</$key>";
  573.                                                 }
  574.                                         }
  575.                                 }
  576.                         }
  577.                         $xml_oidip .= '</section></oidip>';
  578.  
  579.                         $xml_oidip = preg_replace('@<section><(.+)>(.+)</section>@ismU', '<\\1Section><\\1>\\2</\\1Section>', $xml_oidip);
  580.  
  581.                         // Remove 'ra-', 'ra1-', ... field prefixes from XML (the prefix is only for the text view)
  582.                         $xml_oidip = preg_replace('@<(/{0,1})ra\\d*-@', '<\\1', $xml_oidip);
  583.  
  584.                         // <ra1Section><ra1>...</ra1> => <ra1Section><ra>...</ra>
  585.                         $xml_oidip = preg_replace('@<ra(\\d+)Section><ra\\1>(.+)</ra\\1>@U', '<ra\\1Section><ra>\\2</ra>', $xml_oidip);
  586.  
  587.                         /* Debug:
  588.                         $out_type = 'application/xml; charset=UTF-8';
  589.                         $out_content .= $xml_oidip;
  590.                         die();
  591.                         */
  592.  
  593.                         // Very good XSD validator here: https://www.liquid-technologies.com/online-xsd-validator
  594.                         // Also good: https://www.utilities-online.info/xsdvalidation (but does not accept &amp; or &quot; results in "Premature end of data in tag description line ...")
  595.                         // Note:
  596.                         // - These do not support XSD 1.1
  597.                         // - You need to host http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/xmldsig-core-schema.xsd yourself, otherwise there will be a timeout in the validation!!!
  598.  
  599.                         $extra_schemas = array();
  600.                         foreach ($out as $data) {
  601.                                 if (isset($data['xmlns']) && ($data['xmlns'] != '') && !isset($extra_schemas[$data['xmlns']])) {
  602.                                         $extra_schemas[$data['xmlns']] = array($data['xmlschema'], $data['xmlschemauri']);
  603.                                 }
  604.                         }
  605.  
  606.                         $xml  = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?'.'>'."\n";
  607.                         $xml .= '<root xmlns="'.$this->XML_SCHEMA_URN.'"'."\n";
  608.                         $xml .= '      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'."\n";
  609.                         foreach ($extra_schemas as $xmlns => list($schema,$schemauri)) {
  610.                                 $xml .= '      xmlns:'.$xmlns.'="'.$schema.'"'."\n";
  611.                         }
  612.                         $xml .= '      xsi:schemaLocation="'.$this->XML_SCHEMA_URN.' '.$this->XML_SCHEMA_URL;
  613.                         foreach ($extra_schemas as $xmlns => list($schema,$schemauri)) {
  614.                                 $xml .= ' '.$schema.' '.$schemauri;
  615.                         }
  616.                         $xml .= '">'."\n";
  617.                         $xml .= $xml_oidip."\n";
  618.                         $xml .= '</root>';
  619.  
  620.                         if (!OIDplus::getPkiStatus()) {
  621.                                 $xml .= '<!-- Cannot add signature: OIDplus PKI is not initialized (OpenSSL missing?) -->';
  622.                         } else if (!class_exists('DOMDocument')) {
  623.                                 $xml .= '<!-- Cannot add signature: "PHP-XML" extension not installed -->';
  624.                         } else {
  625.                                 try {
  626.                                         require_once __DIR__.'/whois/xml/security.inc.php';
  627.                                         $xml = oidplus_xml_sign($xml, OIDplus::getSystemPrivateKey(), OIDplus::getSystemPublicKey());
  628.                                 } catch (\Exception $e) {
  629.                                         $xml .= '<!-- Cannot add signature: '.$e.' -->';
  630.                                 }
  631.                         }
  632.  
  633.                         $out_type = 'application/xml; charset=UTF-8';
  634.                         $out_content .= $xml;
  635.                 }
  636.  
  637.                 return array($out_content, $out_type);
  638.         }
  639.  
  640.         /**
  641.          * @param $id
  642.          * @return string
  643.          * @throws OIDplusException
  644.          */
  645.         protected function show_asn1_appendix($id) {
  646.                 if (substr($id,0,4) === 'oid:') {
  647.                         $appendix_asn1ids = array();
  648.                         $res_asn = OIDplus::db()->query("select * from ###asn1id where oid = ?", array($id));
  649.                         while ($row_asn = $res_asn->fetch_object()) {
  650.                                 $appendix_asn1ids[] = $row_asn->name;
  651.                         }
  652.  
  653.                         $appendix = implode(', ', $appendix_asn1ids);
  654.                         if (!empty($appendix)) $appendix = " ($appendix)";
  655.                 } else {
  656.                         $appendix = '';
  657.                 }
  658.                 return $appendix;
  659.         }
  660.  
  661.         /**
  662.          * @param $id
  663.          * @return bool
  664.          */
  665.         protected function is_root($id) {
  666.                 return empty(explode(':',$id,2)[1]);
  667.         }
  668.  
  669.         /**
  670.          * @param $content
  671.          * @param $authTokens
  672.          * @return bool
  673.          */
  674.         protected function authTokenAccepted($content, $authTokens) {
  675.                 foreach ($authTokens as $token) {
  676.                         if (OIDplusPagePublicWhois::genWhoisAuthToken($content) == $token) return true;
  677.                 }
  678.                 return false;
  679.         }
  680.  
  681.         /**
  682.          * @param $obj
  683.          * @param $authTokens
  684.          * @return bool
  685.          * @throws OIDplusException
  686.          */
  687.         protected function allowObjectView($obj, $authTokens) {
  688.                 // Master auth token (TODO: Have an object-master-token and a ra-master-token?)
  689.                 $authToken = trim(OIDplus::config()->getValue('whois_auth_token'));
  690.                 if (empty($authToken)) $authToken = false;
  691.                 if ($authToken && in_array($authToken, $authTokens)) return true;
  692.  
  693.                 // Per-OID auth tokens
  694.  
  695.                 $curid = $obj->nodeId();
  696.                 while ($test_obj = OIDplusObject::findFitting($curid)) {
  697.                         // Example: You have an auth Token for 2.999.1.2.3
  698.                         // This allows you to view 2.999.1.2.3 and all of its children,
  699.                         // as long as they are not confidential (then you need their auth token).
  700.                         // 2, 2.999, 2.999.1 and 2.999.1.2 are visible,
  701.                         // (because their existence is now obvious).
  702.                         if ($test_obj->isConfidential() && !$this->authTokenAccepted($curid, $authTokens)) return false;
  703.                         $objParentTest = $test_obj->getParent();
  704.                         if (!$objParentTest) break;
  705.                         $curid = $objParentTest->nodeId();
  706.                 }
  707.  
  708.                 // Allow
  709.                 return true;
  710.         }
  711.  
  712.         /**
  713.          * @param $row_ra
  714.          * @param $authTokens
  715.          * @return bool
  716.          * @throws OIDplusException
  717.          */
  718.         protected function allowRAView($row_ra, $authTokens) {
  719.                 // Master auth token (TODO: Have an object-master-token and a ra-master-token?)
  720.                 $authToken = trim(OIDplus::config()->getValue('whois_auth_token'));
  721.                 if (empty($authToken)) $authToken = false;
  722.                 if ($authToken && in_array($authToken, $authTokens)) return true;
  723.  
  724.                 // Per-RA auth tokens
  725.                 if ($row_ra->privacy && !$this->authTokenAccepted('ra:'.$row_ra->ra_name, $authTokens)) return false;
  726.  
  727.                 // Allow
  728.                 return true;
  729.         }
  730.  
  731.         /**
  732.          * @param $name
  733.          * @param $value
  734.          * @return array
  735.          */
  736.         protected function _oidip_attr($name, $value) {
  737.                 return array(
  738.                         'xmlns' => '',
  739.                         'xmlschema' => '',
  740.                         'xmlschemauri' => '',
  741.                         'name' => $name,
  742.                         'value' => $value
  743.                 );
  744.         }
  745.  
  746.         /**
  747.          * @param $out
  748.          * @return void
  749.          */
  750.         protected function _oidip_newout_format(&$out) {
  751.                 foreach ($out as &$line) {
  752.                         if (is_string($line)) {
  753.                                 $ary = explode(':', $line, 2);
  754.                                 $key = trim($ary[0]);
  755.                                 $value = isset($ary[1]) ? trim($ary[1]) : '';
  756.  
  757.                                 $line = array(
  758.                                         'xmlns' => '',
  759.                                         'xmlschema' => '',
  760.                                         'xmlschemauri' => '',
  761.                                         'name' => $key,
  762.                                         'value' => $value
  763.                                 );
  764.                         } else if (is_array($line)) {
  765.                                 if (!isset($line['xmlns']))        $line['xmlns'] = '';
  766.                                 if (!isset($line['xmlschema']))    $line['xmlschema'] = '';
  767.                                 if (!isset($line['xmlschemauri'])) $line['xmlschemauri'] = '';
  768.                                 if (!isset($line['name']))         $line['name'] = '';
  769.                                 if (!isset($line['value']))        $line['value'] = '';
  770.                         } else {
  771.                                 assert(false);
  772.                         }
  773.                 }
  774.         }
  775.  
  776. }
  777.