Subversion Repositories oidplus

Rev

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