Subversion Repositories oidplus

Rev

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