Subversion Repositories oidinfo_api

Rev

Rev 66 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * OID-base.com API for PHP
  5.  * Copyright 2019 - 2026 Daniel Marschall, ViaThinkSoft
  6.  * Version 2026-03-26
  7.  *
  8.  * Licensed under the Apache License, Version 2.0 (the "License");
  9.  * you may not use this file except in compliance with the License.
  10.  * You may obtain a copy of the License at
  11.  *
  12.  *     http://www.apache.org/licenses/LICENSE-2.0
  13.  *
  14.  * Unless required by applicable law or agreed to in writing, software
  15.  * distributed under the License is distributed on an "AS IS" BASIS,
  16.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17.  * See the License for the specific language governing permissions and
  18.  * limitations under the License.
  19.  */
  20.  
  21. // TODO: should description and information be CDATA or not?
  22.  
  23. // error_reporting(E_ALL | E_NOTICE | E_STRICT | E_DEPRECATED);
  24.  
  25. if(!defined('STDIN'))  define('STDIN',  fopen('php://stdin',  'rb'));
  26. if(!defined('STDOUT')) define('STDOUT', fopen('php://stdout', 'wb'));
  27. if(!defined('STDERR')) define('STDERR', fopen('php://stderr', 'wb'));
  28.  
  29. if (file_exists(__DIR__ . '/oid_utils.inc.phps')) require_once __DIR__ . '/oid_utils.inc.phps';
  30. if (file_exists(__DIR__ . '/oid_utils.inc.php'))  require_once __DIR__ . '/oid_utils.inc.php';
  31. if (file_exists(__DIR__ . '/xml_utils.inc.phps')) require_once __DIR__ . '/xml_utils.inc.phps';
  32. if (file_exists(__DIR__ . '/xml_utils.inc.php'))  require_once __DIR__ . '/xml_utils.inc.php';
  33. if (file_exists(__DIR__ . '/../includes/oid_utils.inc.php'))  require_once __DIR__ . '/../includes/oid_utils.inc.php';
  34. if (file_exists(__DIR__ . '/../includes/xml_utils.inc.php'))  require_once __DIR__ . '/../includes/xml_utils.inc.php';
  35. if (file_exists(__DIR__ . '/../../includes/oid_utils.inc.php'))  require_once __DIR__ . '/../../includes/oid_utils.inc.php';
  36. if (file_exists(__DIR__ . '/../../includes/xml_utils.inc.php'))  require_once __DIR__ . '/../../includes/xml_utils.inc.php';
  37. if (file_exists(__DIR__ . '/../../../includes/oid_utils.inc.php'))  require_once __DIR__ . '/../../../includes/oid_utils.inc.php';
  38. if (file_exists(__DIR__ . '/../../../includes/xml_utils.inc.php'))  require_once __DIR__ . '/../../../includes/xml_utils.inc.php';
  39.  
  40. class OIDInfoException extends Exception {
  41. }
  42.  
  43. class OIDInfoAPI {
  44.  
  45.         # --- PART 0: Constants
  46.  
  47.         // First digit of the ping result
  48.         // "-" = error
  49.         // "0" = OID does not exist
  50.         // "1" = OID does exist, but is not approved yet
  51.         // "2" = OID does exist and is accessible
  52.         private const PING_IDX_EXISTS = 0;
  53.  
  54.         // Second digit of the ping result
  55.         // "-" = error
  56.         // "0" = The OID may not be created
  57.         // "1" = OID is not an illegal OID, and none of its ascendant is a leaf and its parent OID is not frozen
  58.         private const PING_IDX_MAY_CREATE = 1;
  59.  
  60.         public const SOFT_CORRECT_BEHAVIOR_NONE = 0;
  61.         public const SOFT_CORRECT_BEHAVIOR_LOWERCASE_BEGINNING = 1;
  62.         public const SOFT_CORRECT_BEHAVIOR_ALL_POSSIBLE = 2;
  63.  
  64.         public const DEFAULT_ILLEGALITY_RULE_FILE = __DIR__ . '/oid_illegality_rules';
  65.  
  66.         # --- Part 1: "Ping API" for checking if OIDs are available or allowed to create
  67.  
  68.         public $verbosePingProviders = array('https://misc.daniel-marschall.de/oid-repository/ping_oid.php?oid={OID}');
  69.  
  70.         private $pingCache = array();
  71.  
  72.         public $pingCacheMaxAge = 3600;
  73.  
  74.         public function clearPingCache() {
  75.                 $this->pingCache = array();
  76.         }
  77.  
  78.         public function checkOnlineExists($oid) {
  79.                 if (!self::strictCheckSyntax($oid)) return false;
  80.  
  81.                 $pingResult = $this->pingOID($oid);
  82.                 return $pingResult[self::PING_IDX_EXISTS] >= 1;
  83.         }
  84.  
  85.         public function checkOnlineAvailable($oid) {
  86.                 if (!self::strictCheckSyntax($oid)) return false;
  87.  
  88.                 $pingResult = $this->pingOID($oid);
  89.                 return $pingResult[self::PING_IDX_EXISTS] == 2;
  90.         }
  91.  
  92.         public function checkOnlineAllowed($oid) {
  93.                 if (!self::strictCheckSyntax($oid)) return false;
  94.  
  95.                 $pingResult = $this->pingOID($oid);
  96.                 return $pingResult[self::PING_IDX_MAY_CREATE] == 1;
  97.         }
  98.  
  99.         public function checkOnlineMayCreate($oid) {
  100.                 if (!self::strictCheckSyntax($oid)) return false;
  101.  
  102.                 $pingResult = $this->pingOID($oid);
  103.  
  104.                 // OID is either illegal, or one of their parents are leaf or frozen
  105.                 # if (!checkOnlineAllowed($oid)) return false;
  106.                 if ($pingResult[self::PING_IDX_MAY_CREATE] == 0) return false;
  107.  
  108.                 // The OID exists already, so we don't need to create it again
  109.                 # if ($this->checkOnlineExists($oid)) return false;
  110.                 if ($pingResult[self::PING_IDX_EXISTS] >= 1) return false;
  111.  
  112.                 return true;
  113.         }
  114.  
  115.         protected function pingOID($oid) {
  116.                 if (isset($this->pingCache[$oid])) {
  117.                         $cacheAge = $this->pingCache[$oid][0] - time();
  118.                         if ($cacheAge <= $this->pingCacheMaxAge) {
  119.                                 return $this->pingCache[$oid][1];
  120.                         }
  121.                 }
  122.  
  123.                 if (count($this->verbosePingProviders) == 0) {
  124.                         throw new OIDInfoException("No verbose ping provider available!");
  125.                 }
  126.  
  127.                 $res = false;
  128.                 foreach ($this->verbosePingProviders as $url) {
  129.                         $url = str_replace('{OID}', $oid, $url);
  130.                         $cn = @file_get_contents($url);
  131.                         if ($cn === false) continue;
  132.                         $loc_res = trim($cn);
  133.                         if (strpos($loc_res, '-') === false) {
  134.                                 $res = $loc_res;
  135.                                 break;
  136.                         }
  137.                 }
  138.                 if ($res === false) {
  139.                         throw new OIDInfoException("Could not ping OID $oid status!");
  140.                 }
  141.  
  142.                 // if ($this->pingCacheMaxAge >= 0) {
  143.                         $this->pingCache[$oid] = array(time(), $res);
  144.                 //}
  145.  
  146.                 return $res;
  147.         }
  148.  
  149.         # --- PART 2: Syntax checking
  150.  
  151.         public static function strictCheckSyntax($oid) {
  152.                 return oid_valid_dotnotation($oid, false, false, 1);
  153.         }
  154.  
  155.         // Returns false if $oid has wrong syntax
  156.         // Return an OID without leading dot or zeroes, if the syntax is acceptable
  157.         public static function trySanitizeOID($oid) {
  158.                 // Allow leading dots and leading zeroes, but remove then afterwards
  159.                 $ok = oid_valid_dotnotation($oid, true, true, 1);
  160.                 if ($ok === false) return false;
  161.                 return sanitizeOID($oid, substr($oid,0,1) == '.');
  162.         }
  163.  
  164.         # --- PART 3: XML file creation
  165.  
  166.         protected static function eMailValid($email) {
  167.                 # TODO: use isemail project
  168.  
  169.                 if (empty($email)) return false;
  170.  
  171.                 if (strpos($email, '@') === false) return false;
  172.  
  173.                 $ary = explode('@', $email, 2);
  174.                 if (!isset($ary[1])) return false;
  175.                 if (strpos($ary[1], '.') === false) return false;
  176.  
  177.                 return true;
  178.         }
  179.  
  180.         public function softCorrectEMail($email, $params) {
  181.                 $params = $this->init_params($params);
  182.  
  183.                 $email = str_replace(' ', '', $email);
  184.                 $email = str_replace('&', '@', $email);
  185.                 $email = str_replace('(at)', '@', $email);
  186.                 $email = str_replace('[at]', '@', $email);
  187.                 $email = str_replace('(dot)', '.', $email);
  188.                 $email = str_replace('[dot]', '.', $email);
  189.                 $email = trim($email);
  190.  
  191.                 if (!$params['allow_illegal_email'] && !self::eMailValid($email)) {
  192.                         return '';
  193.                 }
  194.  
  195.                 return $email;
  196.         }
  197.  
  198.         public function softCorrectPhone($phone, $params) {
  199.                 $params = $this->init_params($params);
  200.  
  201.                 // TODO: if no "+", add "+1" , but only if address is in USA
  202.                 // TODO: or use param to fixate country if it is not known
  203.                 /*
  204.                 NOTE: with german phone numbers, this will cause trouble, even if we assume "+49"
  205.                         06223 / 1234
  206.                         shall be
  207.                         +49 6223 1234
  208.                         and not
  209.                         +49 06223 1234
  210.                 */
  211.  
  212.                 $phone = str_replace('-', ' ', $phone);
  213.                 $phone = str_replace('.', ' ', $phone);
  214.                 $phone = str_replace('/', ' ', $phone);
  215.                 $phone = str_replace('(', ' ', $phone);
  216.                 $phone = str_replace(')', ' ', $phone);
  217.  
  218.                 // HL7 registry has included this accidently
  219.                 $phone = str_replace('&quot;', '', $phone);
  220.  
  221.                 return trim($phone);
  222.         }
  223.  
  224.         private static function strip_to_xhtml_light($str, $allow_strong_text=false) {
  225.                 // <strong> is allowed in the XSD, but not <b>
  226.                 $str = str_ireplace('<b>', '<strong>', $str);
  227.                 $str = str_ireplace('</b>', '</strong>', $str);
  228.  
  229.                 if (!$allow_strong_text) {
  230.                         // <strong> is only used for very important things like the word "deprecated". It should therefore not used for anything else
  231.                         $str = str_ireplace('<strong>', '', $str);
  232.                         $str = str_ireplace('</strong>', '', $str);
  233.                 }
  234.  
  235.                 $str = preg_replace('@<\s*script.+<\s*/script.*>@isU', '', $str);
  236.                 $str = preg_replace('@<\s*style.+<\s*/style.*>@isU', '', $str);
  237.  
  238.                 return preg_replace_callback(
  239.                         '@<(\s*/{0,1}\d*)([^\s/>]+)(\s*[^>]*)>@i',
  240.                         function ($treffer) {
  241.                                 // see https://oid-base.com/xhtml-light.xsd
  242.                                 $whitelist = array('a', 'br', 'code', 'em', 'font', 'img', 'li', 'strong', 'sub', 'sup', 'ul');
  243.  
  244.                                 $pre = $treffer[1];
  245.                                 $tag = $treffer[2];
  246.                                 $attrib = $treffer[3];
  247.                                 if (in_array($tag, $whitelist)) {
  248.                                         return '<'.$pre.$tag.$attrib.'>';
  249.                                 } else {
  250.                                         return '';
  251.                                 }
  252.                         }, $str);
  253.         }
  254.  
  255.         const OIDINFO_CORRECT_DESC_OPTIONAL_ENDING_DOT = 0;
  256.         const OIDINFO_CORRECT_DESC_ENFORCE_ENDING_DOT = 1;
  257.         const OIDINFO_CORRECT_DESC_DISALLOW_ENDING_DOT = 2;
  258.  
  259.         public function correctDesc($desc, $params, $ending_dot_policy=self::OIDINFO_CORRECT_DESC_OPTIONAL_ENDING_DOT, $enforce_xhtml_light=false) {
  260.                 $params = $this->init_params($params);
  261.  
  262.                 $desc = trim($desc);
  263.  
  264.                 $desc = preg_replace('@<!\\[CDATA\\[(.+)\\]\\]>@ismU', '\\1', $desc);
  265.  
  266.                 if (substr_count($desc, '>') != substr_count($desc, '<')) {
  267.                         $params['allow_html'] = false;
  268.                 }
  269.  
  270.                 $desc = str_replace("\r", '', $desc);
  271.  
  272.                 if (!$params['allow_html']) {
  273.                         // htmlentities_numeric() does this for us
  274.                         /*
  275.                         $desc = str_replace('&', '&amp;', $desc);
  276.                         $desc = str_replace('<', '&lt;', $desc);
  277.                         $desc = str_replace('>', '&gt;', $desc);
  278.                         $desc = str_replace('"', '&quot;', $desc);
  279.                         $desc = str_replace("'", '&#39;', $desc); // &apos; is not HTML. It is XML
  280.                         */
  281.  
  282.                         $desc = str_replace("\n", '<br />', $desc);
  283.                 } else {
  284.                         // Some problems we had with HL7 registry
  285.                         $desc = preg_replace('@&lt;(/{0,1}(p|i|b|u|ul|li))&gt;@ismU', '<\\1>', $desc);
  286.                         # preg_match_all('@&lt;[^ :\\@]+&gt;@ismU', $desc, $m);
  287.                         # if (count($m[0]) > 0) print_r($m);
  288.  
  289.                         $desc = preg_replace('@<i>(.+)&lt;i/&gt;@ismU', '<i>\\1</i>', $desc);
  290.                         $desc = str_replace('<p><p>', '</p><p>', $desc);
  291.  
  292.                         // <p> are not supported by oid-base.com
  293.                         $desc = str_replace('<p>', '<br /><br />', $desc);
  294.                         $desc = str_replace('</p>', '', $desc);
  295.                 }
  296.  
  297.                 // DM 28.01.2025, since Unicode chars are not allowed by the parser
  298.                 $desc = htmlentities_numeric($desc, $params['allow_html']);
  299.  
  300.                 // Escape unicode characters as numeric &#...;
  301.                 // The XML 1.0 standard does only has a few entities, but nothing like e.g. &euro; , so we prefer numeric
  302.                 if (!$params['allow_html']) $desc = htmlentities($desc);
  303.                 $desc = html_named_to_numeric_entities($desc);
  304.  
  305.                 // Remove HTML tags which are not allowed
  306.                 if ($params['allow_html'] && (!$params['ignore_xhtml_light']) && $enforce_xhtml_light) {
  307.                         // oid-base.com does only allow a few HTML tags
  308.                         // see https://oid-base.com/xhtml-light.xsd
  309.                         $desc = self::strip_to_xhtml_light($desc, true);
  310.                 }
  311.  
  312.                 // Solve some XML problems...
  313.                 $desc = preg_replace('@<\s*br\s*>@ismU', '<br/>', $desc); // auto close <br>
  314.                 $desc = preg_replace('@(href\s*=\s*)(["\'])(.*)&([^#].*)(\2)@ismU', '\1\2\3&amp;\4\5', $desc); // fix "&" inside href-URLs to &amp;
  315.                 // TODO: what do we do if there are more XHTML errors (e.g. additional open tags) which would make the XML invalid?
  316.  
  317.                 // "Trim" <br/>
  318.                 $count = 0;
  319.                 do { $desc = preg_replace('@^\s*<\s*br\s*/{0,1}\s*>@isU', '', $desc, -1, $count); } while ($count > 0); // left trim
  320.                 do { $desc = preg_replace('@<\s*br\s*/{0,1}\s*>\s*$@isU', '', $desc, -1, $count); } while ($count > 0); // right trim
  321.  
  322.                 // Correct double-encoded stuff
  323.                 if (!isset($params['tolerant_htmlentities']) || $params['tolerant_htmlentities']) {
  324.                         do {
  325.                                 $old_desc = $desc;
  326.                                 # Full list of entities: https://www.freeformatter.com/html-entities.html
  327.                                 # Max: 8 chars ( &thetasym; )
  328.                                 # Min: 2 chars ( lt,gt,ni,or,ne,le,ge,Mu,Nu,Xi,Pi,mu,nu,xi,pi )
  329.                                 $desc = preg_replace('@(&|&amp;)(#|&#35;)(\d+);@ismU', '&#\3;', $desc);
  330.                                 $desc = preg_replace('@(&|&amp;)([a-zA-Z0-9]{2,8});@ismU', '&\2;', $desc);
  331.                         } while ($old_desc != $desc);
  332.                 }
  333.  
  334.                 // TODO: use the complete list of oid-base.com
  335.                 // TODO: Make this step optional using $params
  336.                 /*
  337.                 Array
  338.                 (
  339.                     [0] => Root OID for
  340.                     [1] => OID for
  341.                     [2] => OID identifying
  342.                     [3] => Top arc for
  343.                     [4] => Arc for
  344.                     [5] => arc root
  345.                     [6] => Node for
  346.                     [7] => Leaf node for
  347.                     [8] => This OID describes
  348.                     [9] => [tT]his oid
  349.                     [10] => This arc describes
  350.                     [11] => This identifies
  351.                     [12] => Identifies a
  352.                     [13] => [Oo]bject [Ii]dentifier
  353.                     [14] => Identifier for
  354.                     [15] => This [Ii]dentifier is for
  355.                     [16] => Identifiers used by
  356.                     [17] => identifier$
  357.                     [18] => This branch
  358.                     [19] => Branch for
  359.                     [20] => Child tree for
  360.                     [21] => Child for
  361.                     [22] => Subtree for
  362.                     [23] => Sub-OID
  363.                     [24] => Tree for
  364.                     [25] => Child object
  365.                     [26] => Parent OID
  366.                     [27] =>  root for
  367.                     [28] => Assigned for
  368.                     [29] => Used to identify
  369.                     [30] => Used in
  370.                     [31] => Used for
  371.                     [32] => For use by
  372.                     [33] => Entry for
  373.                     [34] => This is for
  374.                     [35] =>  ["]?OID["]?
  375.                     [36] => ^OID
  376.                     [37] =>  OID$
  377.                     [38] =>  oid
  378.                     [39] =>  oid$
  379.                     [40] =>  OIDs
  380.                 )
  381.                 $x = 'Root OID for ; OID for ; OID identifying ; Top arc for ; Arc for ; arc root; Node for ; Leaf node for ; This OID describes ; [tT]his oid ; This arc describes ; This identifies ; Identifies a ; [Oo]bject [Ii]dentifier; Identifier for ; This [Ii]dentifier is for ; Identifiers used by ; identifier$; This branch ; Branch for ; Child tree for ; Child for ; Subtree for ; Sub-OID; Tree for ; Child object; Parent OID;  root for ; Assigned for ; Used to identify ; Used in ; Used for ; For use by ; Entry for ; This is for ;  ["]?OID["]? ; ^OID ;  OID$;  oid ;  oid$;  OIDs';
  382.                 $ary = explode('; ', $x);
  383.                 print_r($ary);
  384.                 */
  385.                 $desc = preg_replace("@^Root OID for the @i",                   '', $desc);
  386.                 $desc = preg_replace("@^Root OID for @i",                       '', $desc);
  387.                 $desc = preg_replace("@^OID root for the @i",                   '', $desc);
  388.                 $desc = preg_replace("@^OID root for @i",                       '', $desc);
  389.                 $desc = preg_replace("@^This OID will be used for @i",          '', $desc);
  390.                 $desc = preg_replace("@^This will be a generic OID for the @i", '', $desc);
  391.                 $desc = preg_replace("@^OID for @i",                            '', $desc);
  392.                 $desc = preg_replace("@ Root OID$@i",                           '', $desc);
  393.                 $desc = preg_replace("@ OID$@i",                                '', $desc);
  394.                 $desc = preg_replace("@ OID Namespace$@i",                      '', $desc);
  395.                 $desc = preg_replace("@^OID for @i",                            '', $desc);
  396.                 $desc = preg_replace("@OID root$@i",                            '', $desc);
  397.                 $desc = preg_replace("@Object Identifier$@i",                   '', $desc);
  398.                 $desc = preg_replace("@^The @i",                                '', $desc);
  399.  
  400.                 $desc = rtrim($desc);
  401.                 if ($ending_dot_policy == self::OIDINFO_CORRECT_DESC_ENFORCE_ENDING_DOT) {
  402.                         if (($desc != '') && (substr($desc, -1)) != '.') $desc .= '.';
  403.                 } else if ($ending_dot_policy == self::OIDINFO_CORRECT_DESC_DISALLOW_ENDING_DOT) {
  404.                         $desc = preg_replace('@\\.$@', '', $desc);
  405.                 }
  406.  
  407.                 // Required for XML importer of oid-base.com (E-Mail 09.12.2021)
  408.                 return str_replace('&amp;', '&amp;amp;', $desc);
  409.         }
  410.  
  411.         public function xmlAddHeader($firstName, $lastName, $email) {
  412.                 // TODO: encode
  413.  
  414.                 $firstName = htmlentities_numeric($firstName);
  415.                 if (empty($firstName)) {
  416.                         throw new OIDInfoException("Please supply a first name");
  417.                 }
  418.  
  419.                 $lastName  = htmlentities_numeric($lastName);
  420.                 if (empty($lastName)) {
  421.                         throw new OIDInfoException("Please supply a last name");
  422.                 }
  423.  
  424.                 $email     = htmlentities_numeric($email);
  425.                 if (empty($email)) {
  426.                         throw new OIDInfoException("Please supply an email address");
  427.                 }
  428.  
  429. //              $out  = "<!DOCTYPE oid-database>\n\n";
  430.                 $out  = '<?xml version="1.0" encoding="UTF-8" ?>'."\n";
  431.                 $out .= '<oid-database xmlns="https://oid-base.com"'."\n";
  432.                 $out .= '              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'."\n";
  433.                 $out .= '              xsi:schemaLocation="https://oid-base.com '."\n";
  434.                 $out .= '                                  https://oid-base.com/oid.xsd">'."\n";
  435.                 $out .= "\t<submitter>\n";
  436.                 $out .= "\t\t<first-name>$firstName</first-name>\n";
  437.                 $out .= "\t\t<last-name>$lastName</last-name>\n";
  438.                 $out .= "\t\t<email>$email</email>\n";
  439.                 $out .= "\t</submitter>\n";
  440.  
  441.                 if (!self::eMailValid($email)) {
  442.                         throw new OIDInfoException("eMail address '$email' is invalid");
  443.                 }
  444.  
  445.                 return $out;
  446.         }
  447.  
  448.         public function xmlAddFooter() {
  449.                 return "</oid-database>\n";
  450.         }
  451.  
  452.         /*
  453.                 -- CODE TEMPLATE --
  454.  
  455.                 $params['allow_html'] = false; // Allow HTML in <description> and <information>
  456.                 $params['allow_illegal_email'] = true; // We should allow it, because we don't know if the user has some kind of human-readable anti-spam technique
  457.                 $params['soft_correct_behavior'] = OIDInfoAPI::SOFT_CORRECT_BEHAVIOR_NONE;
  458.                 $params['do_online_check'] = false; // Flag to disable this online check, because it generates a lot of traffic and runtime.
  459.                 $params['do_illegality_check'] = true;
  460.                 $params['do_simpleping_check'] = true;
  461.                 $params['auto_extract_name'] = '';
  462.                 $params['auto_extract_url'] = '';
  463.                 $params['always_output_comment'] = false; // Also output comment if there was an error (e.g. OID already existing)
  464.                 $params['creation_allowed_check'] = true;
  465.                 $params['tolerant_htmlentities'] = true;
  466.                 $params['ignore_xhtml_light'] = false;
  467.  
  468.                 $elements['synonymous-identifier'] = ''; // string or array
  469.                 $elements['description'] = '';
  470.                 $elements['information'] = '';
  471.  
  472.                 $elements['first-registrant']['first-name'] = '';
  473.                 $elements['first-registrant']['last-name'] = '';
  474.                 $elements['first-registrant']['address'] = '';
  475.                 $elements['first-registrant']['email'] = '';
  476.                 $elements['first-registrant']['phone'] = '';
  477.                 $elements['first-registrant']['fax'] = '';
  478.                 $elements['first-registrant']['creation-date'] = '';
  479.  
  480.                 $elements['current-registrant']['first-name'] = '';
  481.                 $elements['current-registrant']['last-name'] = '';
  482.                 $elements['current-registrant']['address'] = '';
  483.                 $elements['current-registrant']['email'] = '';
  484.                 $elements['current-registrant']['phone'] = '';
  485.                 $elements['current-registrant']['fax'] = '';
  486.                 $elements['current-registrant']['modification-date'] = '';
  487.  
  488.                 $oid = '1.2.3';
  489.  
  490.                 $comment = 'test';
  491.  
  492.                 echo $oa->createXMLEntry($oid, $elements, $params, $comment);
  493.         */
  494.         private function init_params($params) {
  495.                 // Backward compatibility
  496.                 if (!isset($params['do_csv_check']))           $params['do_simpleping_check'] = true;
  497.  
  498.                 // Set default behavior
  499.                 if (!isset($params['allow_html']))             $params['allow_html'] = false; // Allow HTML in <description> and <information>
  500.                 if (!isset($params['allow_illegal_email']))    $params['allow_illegal_email'] = true; // We should allow it, because we don't know if the user has some kind of human-readable anti-spam technique
  501.                 if (!isset($params['soft_correct_behavior']))  $params['soft_correct_behavior'] = self::SOFT_CORRECT_BEHAVIOR_NONE;
  502.                 if (!isset($params['do_online_check']))        $params['do_online_check'] = false; // Flag to disable this online check, because it generates a lot of traffic and runtime.
  503.                 if (!isset($params['do_illegality_check']))    $params['do_illegality_check'] = true;
  504.                 if (!isset($params['do_simpleping_check']))    $params['do_simpleping_check'] = true;
  505.                 if (!isset($params['auto_extract_name']))      $params['auto_extract_name'] = '';
  506.                 if (!isset($params['auto_extract_url']))       $params['auto_extract_url'] = '';
  507.                 if (!isset($params['always_output_comment']))  $params['always_output_comment'] = false; // Also output comment if there was an error (e.g. OID already existing)
  508.                 if (!isset($params['creation_allowed_check'])) $params['creation_allowed_check'] = true;
  509.                 if (!isset($params['tolerant_htmlentities']))  $params['tolerant_htmlentities'] = true;
  510.                 if (!isset($params['ignore_xhtml_light']))     $params['ignore_xhtml_light'] = false;
  511.                 if (!isset($params['show_errors']))            $params['show_errors'] = true;
  512.  
  513.                 return $params;
  514.         }
  515.         public function createXMLEntry($oid, $elements, $params, $comment='') {
  516.                 $params = $this->init_params($params);
  517.  
  518.                 $out = '';
  519.                 if (!empty($comment)) $out .= "\t\t<!-- $comment -->\n";
  520.  
  521.                 if ($params['always_output_comment']) {
  522.                         $err = $out;
  523.                 } else {
  524.                         $err = false;
  525.                 }
  526.  
  527.                 if (isset($elements['dotted_oid'])) {
  528.                         throw new OIDInfoException("'dotted_oid' in the \$elements array is not supported. Please use the \$oid argument.");
  529.                 }
  530.                 if (isset($elements['value'])) {
  531.                         // TODO: WHAT SHOULD WE DO WITH THAT?
  532.                         throw new OIDInfoException("'value' in the \$elements array is currently not supported.");
  533.                 }
  534.  
  535.                 $bak_oid = $oid;
  536.                 $oid = self::trySanitizeOID($oid);
  537.                 if ($oid === false) {
  538.                         if ($params['show_errors']) fwrite(STDOUT/*STDERR*/,"<!-- ERROR: Ignored '$bak_oid', because it is not a valid OID -->\n");
  539.                         return $err;
  540.                 }
  541.  
  542.                 if ($params['creation_allowed_check']) {
  543.                         if (!$this->oidMayCreate($oid, $params['do_online_check'], $params['do_simpleping_check'], $params['do_illegality_check'])) {
  544.                                 if ($params['show_errors']) fwrite(STDOUT/*STDERR*/,"<!-- ERROR: Creation of $oid disallowed -->\n");
  545.                                 return $err;
  546.                         }
  547.                 } else {
  548.                         if ($params['do_illegality_check'] && ($this->illegalOid($oid))) {
  549.                                 if ($params['show_errors']) fwrite(STDOUT/*STDERR*/,"<!-- ERROR: Creation of $oid disallowed -->\n");
  550.                                 return $err;
  551.                         }
  552.                 }
  553.  
  554.                 if (!isset($elements['description'])) $elements['description'] = '';
  555.                 if (!isset($elements['information'])) $elements['information'] = '';
  556.  
  557.                 $elements['description'] = $this->correctDesc($elements['description'], $params, self::OIDINFO_CORRECT_DESC_DISALLOW_ENDING_DOT, true);
  558.                 $elements['information'] = $this->correctDesc($elements['information'], $params, self::OIDINFO_CORRECT_DESC_ENFORCE_ENDING_DOT, true);
  559.  
  560.                 // Request by O.D. 26 March 2026
  561.                 $elements['description'] = trim(preg_replace('@<\s*br\s*/{0,1}\s*>@isU', ' ', $elements['description']));
  562.  
  563.                 // Request by O.D. 26 August 2019
  564.                 $elements['description'] = trim($elements['description']);
  565.                 $m = array();
  566.                 if (preg_match('@^[a-z]@', $elements['description'], $m)) {
  567.                         $ending_dot_policy = self::OIDINFO_CORRECT_DESC_DISALLOW_ENDING_DOT; // for description
  568.                         if (strpos($elements['description'], ' ') === false) { // <-- added by DM
  569.                                 $elements['description'] = '"' . $elements['description'] . '"';
  570.                         }
  571.                 }
  572.                 // End request by O.D. 26. August 2019
  573.  
  574.                 if (($params['auto_extract_name'] != '') || ($params['auto_extract_url'] != '')) {
  575.                         if (!empty($elements['information'])) $elements['information'] .= '<br /><br />';
  576.                         if (($params['auto_extract_name'] != '') || ($params['auto_extract_url'] != '')) {
  577.                                 $elements['information'] .= 'Automatically extracted from <a href="'.$params['auto_extract_url'].'">'.$params['auto_extract_name'].'</a>.';
  578.                         } else if ($params['auto_extract_name'] != '') {
  579.                                 $elements['information'] .= 'Automatically extracted from '.$params['auto_extract_name'];
  580.                         } else if ($params['auto_extract_url'] != '') {
  581.                                 $hr_url = $params['auto_extract_url'];
  582.                                 // $hr_url = preg_replace('@^https{0,1}://@ismU', '', $hr_url);
  583.                                 $hr_url = preg_replace('@^http://@ismU', '', (string)$hr_url);
  584.                                 $elements['information'] .= 'Automatically extracted from <a href="'.$params['auto_extract_url'].'">'.$hr_url.'</a>.';
  585.                         }
  586.                 }
  587.  
  588.                 // Validate ASN.1 ID
  589.                 if (isset($elements['synonymous-identifier'])) {
  590.                         if (!is_array($elements['synonymous-identifier'])) {
  591.                                 $elements['synonymous-identifier'] = array($elements['synonymous-identifier']);
  592.                         }
  593.                         foreach ($elements['synonymous-identifier'] as &$synid) {
  594.                                 if ($synid == '') {
  595.                                         $synid = null;
  596.                                         continue;
  597.                                 }
  598.  
  599.                                 $behavior = $params['soft_correct_behavior'];
  600.  
  601.                                 if ($behavior == self::SOFT_CORRECT_BEHAVIOR_NONE) {
  602.                                         if (!oid_id_is_valid($synid)) $synid = null;
  603.                                 } else if ($behavior == self::SOFT_CORRECT_BEHAVIOR_LOWERCASE_BEGINNING) {
  604.                                         if ($synid!='') $synid[0] = strtolower(substr($synid,0,1));
  605.                                         if (!oid_id_is_valid($synid)) $synid = null;
  606.                                 } else if ($behavior == self::SOFT_CORRECT_BEHAVIOR_ALL_POSSIBLE) {
  607.                                         $synid = oid_soft_correct_id($synid);
  608.                                         // if (!oid_id_is_valid($synid)) $synid = null;
  609.                                 } else {
  610.                                         throw new OIDInfoException("Unexpected soft-correction behavior for ASN.1 IDs");
  611.                                 }
  612.                         }
  613.                         unset($synid);
  614.                 }
  615.  
  616.                 // ATTENTION: the XML-generator will always add <dotted-oid> , but what will happen if additionally an
  617.                 // asn1-path (<value>) is given? (the resulting OIDs might be inconsistent/mismatch)
  618.                 if (isset($elements['value']) && (!asn1_path_valid($elements['value']))) {
  619.                         unset($elements['value']);
  620.                 }
  621.  
  622.                 // Validate IRI (currently not supported by oid-base.com, but the tag name is reserved)
  623.                 if (isset($elements['iri'])) {
  624.                         if (!is_array($elements['iri'])) {
  625.                                 $elements['iri'] = array($elements['iri']);
  626.                         }
  627.                         foreach ($elements['iri'] as &$iri) {
  628.                                 // Do not allow Numeric-only. It would only be valid in an IRI path, but not in a single identifier
  629.                                 if (!iri_arc_valid($iri, false)) $iri = null;
  630.                         }
  631.                         unset($iri);
  632.                 }
  633.  
  634.                 if (isset($elements['first-registrant']['phone']))
  635.                 $elements['first-registrant']['phone']   = $this->softCorrectPhone($elements['first-registrant']['phone'], $params);
  636.  
  637.                 if (isset($elements['current-registrant']['phone']))
  638.                 $elements['current-registrant']['phone'] = $this->softCorrectPhone($elements['current-registrant']['phone'], $params);
  639.  
  640.                 if (isset($elements['first-registrant']['fax']))
  641.                 $elements['first-registrant']['fax']     = $this->softCorrectPhone($elements['first-registrant']['fax'], $params);
  642.  
  643.                 if (isset($elements['current-registrant']['fax']))
  644.                 $elements['current-registrant']['fax']   = $this->softCorrectPhone($elements['current-registrant']['fax'], $params);
  645.  
  646.                 if (isset($elements['first-registrant']['email']))
  647.                 $elements['first-registrant']['email']   = $this->softCorrectEMail($elements['first-registrant']['email'], $params);
  648.  
  649.                 if (isset($elements['current-registrant']['email']))
  650.                 $elements['current-registrant']['email'] = $this->softCorrectEMail($elements['current-registrant']['email'], $params);
  651.  
  652.                 // TODO: if name is empty, but address has 1 line, take it as firstname (but remove hyperlink)
  653.  
  654.                 $ignore_current_registrant = false;
  655.                 if (isset($elements['current-registrant'])) {
  656.                         $test_keys = array_keys($elements['current-registrant']);
  657.                         if ((count($test_keys) == 1) && ($test_keys[0] == 'modification-date')) {
  658.                                 // Modification dates without information about the current RA which are rejected by the OID repository
  659.                                 $ignore_current_registrant = true;
  660.                         }
  661.                 }
  662.  
  663.                 $out_loc = '';
  664.                 foreach ($elements as $name => $val) {
  665.                         if (($name == 'first-registrant') || ($name == 'current-registrant')) {
  666.                                 if (($name != 'current-registrant') || !$ignore_current_registrant) {
  667.                                         $out_loc2 = '';
  668.                                         foreach ($val as $name2 => $val2) {
  669.                                                 if (is_null($val2)) continue;
  670.                                                 if (empty($val2)) continue;
  671.  
  672.                                                 if (!is_array($val2)) $val2 = array($val2);
  673.  
  674.                                                 foreach ($val2 as $val3) {
  675.                                                         // if (is_null($val3)) continue;
  676.                                                         if (empty($val3)) continue;
  677.  
  678.                                                         if ($name2 == 'address') {
  679.                                                                 // $val3 = htmlentities_numeric($val3);
  680.                                                                 $val3 = $this->correctDesc($val3, $params, self::OIDINFO_CORRECT_DESC_DISALLOW_ENDING_DOT, true);
  681.                                                         } else {
  682.                                                                 // $val3 = htmlentities_numeric($val3);
  683.                                                                 $val3 = $this->correctDesc($val3, $params, self::OIDINFO_CORRECT_DESC_DISALLOW_ENDING_DOT, false);
  684.                                                         }
  685.                                                         $out_loc2 .= "\t\t\t<$name2>".$val3."</$name2>\n";
  686.                                                 }
  687.                                         }
  688.  
  689.                                         if (!empty($out_loc2)) {
  690.                                                 $out_loc .= "\t\t<$name>\n";
  691.                                                 $out_loc .= $out_loc2;
  692.                                                 $out_loc .= "\t\t</$name>\n";
  693.                                         }
  694.                                 }
  695.                         } else {
  696.                                 // if (is_null($val)) continue;
  697.                                 if (empty($val) && ($name != 'description')) continue; // description is mandatory, according to https://oid-base.com/oid.xsd
  698.  
  699.                                 if (!is_array($val)) $val = array($val);
  700.  
  701.                                 foreach ($val as $val2) {
  702.                                         // if (is_null($val2)) continue;
  703.                                         if (empty($val2) && ($name != 'description')) continue; // description is mandatory, according to https://oid-base.com/oid.xsd
  704.  
  705.                                         if (($name != 'description') && ($name != 'information')) { // don't correctDesc description/information, because we already did it above.
  706.                                                 // $val2 = htmlentities_numeric($val2);
  707.                                                 $val2 = $this->correctDesc($val2, $params, self::OIDINFO_CORRECT_DESC_OPTIONAL_ENDING_DOT, false);
  708.                                         }
  709.                                         $out_loc .= "\t\t<$name>".$val2."</$name>\n";
  710.                                 }
  711.                         }
  712.                 }
  713.  
  714.                 if (!empty($out)) {
  715.                         $out = "\t<oid>\n"."\t\t".trim($out)."\n";
  716.                 } else {
  717.                         $out = "\t<oid>\n";
  718.                 }
  719.                 $out .= "\t\t<dot-notation>$oid</dot-notation>\n";
  720.                 $out .= $out_loc;
  721.                 $out .= "\t</oid>\n";
  722.  
  723.                 return $out;
  724.         }
  725.  
  726.         # --- PART 4: Offline check if OIDs are illegal
  727.  
  728.         protected $illegality_rules = array();
  729.  
  730.         public function clearIllegalityRules() {
  731.                 $this->illegality_rules = array();
  732.         }
  733.  
  734.         public function loadIllegalityRuleFile($file) {
  735.                 if (!file_exists($file)) {
  736.                         throw new OIDInfoException("Error: File '$file' does not exist");
  737.                 }
  738.  
  739.                 $lines = file($file);
  740.  
  741.                 if ($lines === false) {
  742.                         throw new OIDInfoException("Error: Could not load '$file'");
  743.                 }
  744.  
  745.                 $signature = trim(array_shift($lines));
  746.                 if (($signature != '[1.3.6.1.4.1.37476.3.1.5.1]') && ($signature != '[1.3.6.1.4.1.37476.3.1.5.2]')) {
  747.                         throw new OIDInfoException("'$file' does not seem to a valid illegality rule file (file format OID does not match. Signature $signature unexpected)");
  748.                 }
  749.  
  750.                 foreach ($lines as $line) {
  751.                         // Remove comments
  752.                         $ary  = explode('--', $line, 2);
  753.                         $rule = trim($ary[0]);
  754.                         $explanation = strip_tags(trim($ary[1]??''));
  755.  
  756.                         if ($rule !== '') $this->addIllegalityRule($rule, $explanation);
  757.                 }
  758.         }
  759.  
  760.         public function addIllegalityRule($rule, $explanation='') {
  761.                 $test = $rule;
  762.                 $test = preg_replace('@\\.\\(!\\d+\\)@ismU', '.0', $test); // added in ver 2
  763.                 $test = preg_replace('@\\.\\(\\d+\\+\\)@ismU', '.0', $test);
  764.                 $test = preg_replace('@\\.\\(\\d+\\-\\)@ismU', '.0', $test);
  765.                 $test = preg_replace('@\\.\\(\\d+\\-\\d+\\)@ismU', '.0', $test);
  766.                 $test = preg_replace('@\\.\\*@ismU', '.0', $test);
  767.  
  768.                 if (!oid_valid_dotnotation($test, false, false, 1)) {
  769.                         throw new OIDInfoException("Illegal illegality rule '$rule'.");
  770.                 }
  771.  
  772.                 $this->illegality_rules[] = array($rule, $explanation);
  773.         }
  774.  
  775.         private static function bigint_cmp($a, $b) {
  776.                 if (function_exists('bccomp')) {
  777.                         return bccomp($a, $b);
  778.                 }
  779.  
  780.                 if (function_exists('gmp_cmp')) {
  781.                         return gmp_cmp($a, $b);
  782.                 }
  783.  
  784.                 if ($a > $b) return 1;
  785.                 if ($a < $b) return -1;
  786.                 return 0;
  787.         }
  788.  
  789.         public function illegalOID($oid, &$illegal_root='', &$explanation='') {
  790.                 $bak = $oid;
  791.                 $oid = self::trySanitizeOID($oid);
  792.                 if ($oid === false) {
  793.                         $illegal_root = $bak;
  794.                         return true; // is illegal
  795.                 }
  796.  
  797.                 $rules = $this->illegality_rules;
  798.  
  799.                 foreach ($rules as list($rule, $expl)) {
  800.                         $rule = str_replace(array('(', ')'), '', $rule);
  801.  
  802.                         $oarr = explode('.', $oid);
  803.                         $rarr = explode('.', $rule);
  804.  
  805.                         if (count($oarr) < count($rarr)) continue;
  806.  
  807.                         $rulefit = true;
  808.  
  809.                         $illrootary = array();
  810.  
  811.                         $vararcs = 0;
  812.                         $varsfit = 0;
  813.                         for ($i=0; $i<count($rarr); $i++) {
  814.                                 $oelem = $oarr[$i];
  815.                                 $relem = $rarr[$i];
  816.  
  817.                                 $illrootary[] = $oelem;
  818.  
  819.                                 if ($relem == '*') $relem = '0+';
  820.  
  821.                                 $startchar = substr($relem, 0, 1);
  822.                                 $endchar = substr($relem, -1, 1);
  823.                                 if ($startchar == '!') { // added in ver 2
  824.                                         $vararcs++;
  825.                                         $relem = substr($relem, 1, strlen($relem)-1); // cut away first char
  826.                                         $cmp = self::bigint_cmp($oelem, $relem) != 0;
  827.                                         if ($cmp) $varsfit++;
  828.                                 } else if ($endchar == '+') {
  829.                                         $vararcs++;
  830.                                         $relem = substr($relem, 0, strlen($relem)-1); // cut away last char
  831.                                         $cmp = self::bigint_cmp($oelem, $relem) >= 0;
  832.                                         if ($cmp) $varsfit++;
  833.                                 } else if ($endchar == '-') {
  834.                                         $vararcs++;
  835.                                         $relem = substr($relem, 0, strlen($relem)-1); // cut away last char
  836.                                         $cmp = self::bigint_cmp($oelem, $relem) <= 0;
  837.                                         if ($cmp) $varsfit++;
  838.                                 } else if (strpos($relem, '-') !== false) {
  839.                                         $vararcs++;
  840.                                         $limarr = explode('-', $relem);
  841.                                         $limmin = $limarr[0];
  842.                                         $limmax = $limarr[1];
  843.                                         $cmp_min = self::bigint_cmp($oelem, $limmin) >= 0;
  844.                                         $cmp_max = self::bigint_cmp($oelem, $limmax) <= 0;
  845.                                         if ($cmp_min && $cmp_max) $varsfit++;
  846.                                 } else {
  847.                                         if ($relem != $oelem) {
  848.                                                 $rulefit = false;
  849.                                                 break;
  850.                                         }
  851.                                 }
  852.                         }
  853.  
  854.                         if ($rulefit && ($vararcs == $varsfit)) {
  855.                                 // echo "$oid is illegal because of rule $rule ($explanation)\n";
  856.                                 $illegal_root = implode('.', $illrootary);
  857.                                 $explanation = $expl;
  858.                                 return true; // is illegal
  859.                         }
  860.                 }
  861.  
  862.                 $illegal_root = '';
  863.                 $explanation = '';
  864.                 return false; // not illegal
  865.         }
  866.  
  867.         # --- PART 5: Misc functions
  868.  
  869.         function __construct() {
  870.                 if (file_exists(self::DEFAULT_ILLEGALITY_RULE_FILE)) {
  871.                         $this->loadIllegalityRuleFile(self::DEFAULT_ILLEGALITY_RULE_FILE);
  872.                 }
  873.         }
  874.  
  875.         public static function getPublicURL($oid) {
  876.                 return "https://oid-base.com/get/$oid";
  877.         }
  878.  
  879.         public function oidExisting($oid, $onlineCheck=true, $useSimplePingProvider=true) {
  880.                 $bak_oid = $oid;
  881.                 $oid = self::trySanitizeOID($oid);
  882.                 if ($oid === false) {
  883.                         throw new OIDInfoException("'$bak_oid' is not a valid OID");
  884.                 }
  885.  
  886.                 $canuseSimplePingProvider = $useSimplePingProvider && $this->simplePingProviderAvailable();
  887.                 if ($canuseSimplePingProvider) {
  888.                         if ($this->simplePingProviderCheckOID($oid)) return true;
  889.                 }
  890.                 if ($onlineCheck) {
  891.                         return $this->checkOnlineExists($oid);
  892.                 }
  893.                 if ((!$canuseSimplePingProvider) && (!$onlineCheck)) {
  894.                         throw new OIDInfoException("No simple or verbose checking method chosen/available");
  895.                 }
  896.                 return false;
  897.         }
  898.  
  899.         public function oidMayCreate($oid, $onlineCheck=true, $useSimplePingProvider=true, $illegalityCheck=true) {
  900.                 $bak_oid = $oid;
  901.                 $oid = self::trySanitizeOID($oid);
  902.                 if ($oid === false) {
  903.                         throw new OIDInfoException("'$bak_oid' is not a valid OID");
  904.                 }
  905.  
  906.                 if ($illegalityCheck && $this->illegalOID($oid)) return false;
  907.  
  908.                 $canuseSimplePingProvider = $useSimplePingProvider && $this->simplePingProviderAvailable();
  909.                 if ($canuseSimplePingProvider) {
  910.                         if ($this->simplePingProviderCheckOID($oid)) return false;
  911.                 }
  912.                 if ($onlineCheck) {
  913.                         return $this->checkOnlineMayCreate($oid);
  914.                 }
  915.                 if ((!$canuseSimplePingProvider) && (!$onlineCheck)) {
  916.                         throw new OIDInfoException("No simple or verbose checking method chosen/available");
  917.                 }
  918.                 return true;
  919.         }
  920.  
  921.         # --- PART 6: Simple Ping Providers
  922.         # TODO: Question ... can't these provider concepts (SPP and VPP) not somehow be combined?
  923.  
  924.         protected $simplePingProviders = array();
  925.  
  926.         public function addSimplePingProvider($addr) {
  927.                 if (!isset($this->simplePingProviders[$addr])) {
  928.                         if (strtolower(substr($addr, -4, 4)) == '.csv') {
  929.                                 $this->simplePingProviders[$addr] = new CSVSimplePingProvider($addr);
  930.                         } else {
  931.                                 $this->simplePingProviders[$addr] = new OIDSimplePingProvider($addr);
  932.                                 // $this->simplePingProviders[$addr]->connect();
  933.                         }
  934.                 }
  935.                 return $this->simplePingProviders[$addr];
  936.         }
  937.  
  938.         public function removeSimplePingProvider($addr) {
  939.                 $this->simplePingProviders[$addr]->disconnect();
  940.                 unset($this->simplePingProviders[$addr]);
  941.         }
  942.  
  943.         public function removeAllSimplePingProviders() {
  944.                 foreach ($this->simplePingProviders as $addr => $obj) {
  945.                         $this->removeSimplePingProvider($addr);
  946.                 }
  947.         }
  948.  
  949.         public function listSimplePingProviders() {
  950.                 $out = array();
  951.                 foreach ($this->simplePingProviders as $addr => $obj) {
  952.                         $out[] = $addr;
  953.                 }
  954.                 return $out;
  955.         }
  956.  
  957.         public function simplePingProviderCheckOID($oid) {
  958.                 if (!$this->simplePingProviderAvailable()) {
  959.                         throw new OIDInfoException("No simple ping providers available.");
  960.                 }
  961.  
  962.                 $one_null = false;
  963.                 foreach ($this->simplePingProviders as /*$addr =>*/ $obj) {
  964.                         $res = $obj->queryOID($oid);
  965.                         if ($res) return true;
  966.                         if ($res !== false) $one_null = true;
  967.                 }
  968.  
  969.                 return $one_null ? null : false;
  970.         }
  971.  
  972.         public function simplePingProviderAvailable() {
  973.                 return count($this->simplePingProviders) >= 1;
  974.         }
  975.  
  976. }
  977.  
  978. interface IOIDSimplePingProvider {
  979.         public function queryOID($oid);
  980.         public function disconnect();
  981.         public function connect();
  982. }
  983.  
  984. class CSVSimplePingProvider implements IOIDSimplePingProvider {
  985.         protected $csvfile = '';
  986.         protected $lines = array();
  987.         protected $filemtime = 0;
  988.  
  989.         public function queryOID($oid) {
  990.                 $this->reloadCSV();
  991.                 return in_array($oid, $this->lines);
  992.         }
  993.  
  994.         public function disconnect() {
  995.                 // Nothing
  996.         }
  997.  
  998.         public function connect() {
  999.                 // Nothing
  1000.         }
  1001.  
  1002.         // TODO: This cannot handle big CSVs. We need to introduce the old code of "2016-09-02_old_oidinfo_api_with_csv_reader.zip" here.
  1003.         protected function reloadCSV() {
  1004.                 if (!file_exists($this->csvfile)) {
  1005.                         throw new OIDInfoException("File '".$this->csvfile."' does not exist");
  1006.                 }
  1007.                 $filemtime = filemtime($this->csvfile);
  1008.                 if ($filemtime != $this->filemtime) {
  1009.                         $this->lines = file($this->csvfile);
  1010.                         $this->filemtime = $filemtime;
  1011.                 }
  1012.         }
  1013.  
  1014.         function __construct($csvfile) {
  1015.                 $this->csvfile = $csvfile;
  1016.                 $this->reloadCSV();
  1017.         }
  1018. }
  1019.  
  1020.  
  1021. class OIDSimplePingProvider implements IOIDSimplePingProvider {
  1022.         protected $addr = '';
  1023.         protected $connected = false;
  1024.         protected $socket = null;
  1025.  
  1026.         const SPP_MAX_CONNECTION_ATTEMPTS = 3; // TODO: Put into an OIDInfoAPI class...?
  1027.  
  1028.         const DEFAULT_PORT = 49500;
  1029.  
  1030.         protected function spp_reader_init() {
  1031.                 $this->spp_reader_uninit();
  1032.  
  1033.                 $ary = explode(':', $this->addr); // TODO: does not work with an IPv6 address
  1034.                 $host = $ary[0];
  1035.                 $service_port = $ary[1] ?? self::DEFAULT_PORT;
  1036.  
  1037.                 $is_ip = filter_var($host, FILTER_VALIDATE_IP) !== false;
  1038.                 if (!$is_ip) {
  1039.                         $address = @gethostbyname($host);
  1040.                         if ($address === $host) {
  1041.                                 $msg = "gethostbyname() failed.\n"; // TODO: exceptions? (also all "echos" below)
  1042.                                 throw new Exception($msg);
  1043.                                 // echo $msg;
  1044.                                 // return false;
  1045.                         }
  1046.                 } else {
  1047.                         $address = $host;
  1048.                 }
  1049.  
  1050.                 $this->socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
  1051.                 if ($this->socket === false) {
  1052.                         $msg = "socket_create() failed: " . socket_strerror(socket_last_error()) . "\n";
  1053.                         throw new Exception($msg);
  1054.                         // echo $msg;
  1055.                         // return false;
  1056.                 }
  1057.                 $result = @socket_connect($this->socket, $address, $service_port);
  1058.                 if ($result === false) {
  1059.                         $msg = "socket_connect() failed: " . socket_strerror(socket_last_error($this->socket)) . "\n";
  1060.                         throw new Exception($msg);
  1061.                         // echo $msg;
  1062.                         // return false;
  1063.                 }
  1064.  
  1065.                 $this->connected = true;
  1066.         }
  1067.  
  1068.         protected function spp_reader_avail($oid, $failcount=0) {
  1069.                 $in = "$oid\n";
  1070.  
  1071.                 if ($failcount >= self::SPP_MAX_CONNECTION_ATTEMPTS) {
  1072.                         $msg = "Query $oid: CONNECTION TO SIMPLE PING PROVIDER FAILED!\n";
  1073.                         throw new Exception($msg);
  1074.                         // echo $msg;
  1075.                         // return null;
  1076.                 }
  1077.  
  1078.                 if (!$this->connected) {
  1079.                         $this->spp_reader_init();
  1080.                 }
  1081.  
  1082.                 socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ["sec"=>5,"usec"=>0]);
  1083.  
  1084.                 $s = @socket_send($this->socket, $in, strlen($in), 0);
  1085.                 if ($s != strlen($in)) {
  1086.                         // echo "Query $oid: Sending failed\n";
  1087.                         $this->spp_reader_init();
  1088.                         if (!$this->socket) return null;
  1089.                         return $this->spp_reader_avail($oid, $failcount+1);
  1090.                 }
  1091.  
  1092.                 $out = @socket_read($this->socket, 2048, PHP_NORMAL_READ);
  1093.                 if (trim($out) == '1') {
  1094.                         return true;
  1095.                 } else if (trim($out) == '0') {
  1096.                         return false;
  1097.                 } else {
  1098.                         // echo "Query $oid: Receiving failed\n";
  1099.                         $this->spp_reader_init();
  1100.                         if (!$this->socket) return null;
  1101.                         return $this->spp_reader_avail($oid, $failcount+1);
  1102.                 }
  1103.         }
  1104.  
  1105.         protected function spp_reader_uninit() {
  1106.                 if (!$this->connected) return;
  1107.                 @socket_close($this->socket);
  1108.                 $this->connected = false;
  1109.         }
  1110.  
  1111.         public function queryOID($oid) {
  1112.                 if (trim($oid) === 'bye') return null;
  1113.                 return $this->spp_reader_avail($oid);
  1114.         }
  1115.  
  1116.         public function disconnect() {
  1117.                 $this->spp_reader_uninit();
  1118.         }
  1119.  
  1120.         public function connect() {
  1121.                 $this->spp_reader_init();
  1122.         }
  1123.  
  1124.         function __construct($addr='localhost:49500') {
  1125.                 $this->addr = $addr;
  1126.         }
  1127.  
  1128. }
  1129.