Subversion Repositories oidplus

Rev

Go to most recent revision | Blame | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. // OID-Utilities for PHP
  4. // (C) 2011-2018 Daniel Marschall, ViaThinkSoft
  5. // Licensed under GNU LGPL 3.0
  6. // Last revision: 2018-01-05
  7.  
  8. // All functions in this library are compatible with leading zeroes (not recommended) and leading dots
  9.  
  10. // TODO: change the function names, so that they have a uniform naming schema, and rename "oid identifier" into "asn.1 alphanumeric identifier"
  11. // TODO: Function for finding a shared ancestor, e.g. oid_shared_ancestor('2.999.1.2.3', '2.999.4.5') == '2.999'
  12.  
  13. define('OID_DOT_FORBIDDEN', 0);
  14. define('OID_DOT_OPTIONAL',  1);
  15. define('OID_DOT_REQUIRED',  2);
  16.  
  17. /**
  18.  * Checks if an OID has a valid dot notation.
  19.  * @author  Daniel Marschall, ViaThinkSoft
  20.  * @version 2014-12-09
  21.  * @param   $oid (string)<br />
  22.  *              An OID in dot notation.
  23.  * @param   $allow_leading_zeroes (bool)<br />
  24.  *              true of leading zeroes are allowed or not.
  25.  * @param   $allow_leading_dot (bool)<br />
  26.  *              true of leading dots are allowed or not.
  27.  * @return  (bool) true if the dot notation is valid.
  28.  **/
  29. function oid_valid_dotnotation($oid, $allow_leading_zeroes=true, $allow_leading_dot=false, $min_len=0) {
  30.         $regex = oid_validation_regex($allow_leading_zeroes, $allow_leading_dot, $min_len);
  31.  
  32.         return preg_match($regex, $oid, $m) ? true : false;
  33. }
  34.  
  35. /**
  36.  * Returns a full regular expression to validate an OID in dot-notation
  37.  * @author  Daniel Marschall, ViaThinkSoft
  38.  * @version 2014-12-09
  39.  * @param   $allow_leading_zeroes (bool)<br />
  40.  *              true of leading zeroes are allowed or not.
  41.  * @param   $allow_leading_dot (bool)<br />
  42.  *              true of leading dots are allowed or not.
  43.  * @return  (string) The regular expression
  44.  **/
  45. function oid_validation_regex($allow_leading_zeroes=true, $allow_leading_dot=false, $min_len=0) {
  46.         $leading_dot_policy = $allow_leading_dot ? OID_DOT_OPTIONAL : OID_DOT_FORBIDDEN;
  47.  
  48.         $part_regex = oid_part_regex($min_len, $allow_leading_zeroes, $leading_dot_policy);
  49.  
  50.         return '@^'.$part_regex.'$@';
  51. }
  52.  
  53. /**
  54.  * Returns a partial regular expression which matches valid OIDs in dot notation.
  55.  * It can be inserted into regular expressions.
  56.  * @author  Daniel Marschall, ViaThinkSoft
  57.  * @version 2014-12-09
  58.  * @param   $min_len (int)<br />
  59.  *              0="." and greater will be recognized, but not ""<br />
  60.  *              1=".2" and greater will be recognized<br />
  61.  *              2=".2.999" and greater will be recognized (default)<br />
  62.  *              etc.
  63.  * @param   $allow_leading_zeroes (bool)<br />
  64.  *              true:  ".2.0999" will be recognized<br />
  65.  *              false: ".2.0999" won't be recognized (default)
  66.  * @param   $leading_dot_policy (int)<br />
  67.  *              0 (OID_DOT_FORBIDDEN): forbidden<br />
  68.  *              1 (OID_DOT_OPTIONAL) : optional (default)<br />
  69.  *              2 (OID_DOT_REQUIRED) : enforced
  70.  * @return  (string) A regular expression which matches OIDs in dot notation
  71.  **/
  72. function oid_part_regex($min_len=2, $allow_leading_zeroes=false, $leading_dot_policy=OID_DOT_OPTIONAL) {
  73.         switch ($leading_dot_policy) {
  74.                 case 0: // forbidden
  75.                         $lead_dot = '';
  76.                         break;
  77.                 case 1: // optional
  78.                         $lead_dot = '\\.{0,1}';
  79.                         break;
  80.                 case 2: // enforced
  81.                         $lead_dot = '\\.';
  82.                         break;
  83.                 default:
  84.                         assert(false);
  85.                         break;
  86.         }
  87.  
  88.         $lead_zero            = $allow_leading_zeroes ? '0*' : '';
  89.         $zero_till_thirtynine = '(([0-9])|([1-3][0-9]))'; // second arc is limited to 0..39 if root arc is 0..1
  90.         $singledot_option     = ($min_len == 0) && ($leading_dot_policy != OID_DOT_FORBIDDEN) ? '|\\.' : '';
  91.         $only_root_option     = ($min_len <= 1) ? '|('.$lead_dot.$lead_zero.'[0-2])' : '';
  92.  
  93.         $regex = '
  94.         (
  95.                 (
  96.                         (
  97.                                 ('.$lead_dot.$lead_zero.'[0-1])
  98.                                 \\.'.$lead_zero.$zero_till_thirtynine.'
  99.                                 (\\.'.$lead_zero.'(0|[1-9][0-9]*)){'.max(0, $min_len-2).',}
  100.                         )|(
  101.                                 ('.$lead_dot.$lead_zero.'[2])
  102.                                 (\\.'.$lead_zero.'(0|[1-9][0-9]*)){'.max(0, $min_len-1).',}
  103.                         )
  104.                         '.$only_root_option.'
  105.                         '.$singledot_option.'
  106.                 )
  107.         )';
  108.  
  109.         // Remove the indentations which are used to maintain this large regular expression in a human friendly way
  110.         $regex = str_replace("\n", '', $regex);
  111.         $regex = str_replace("\r", '', $regex);
  112.         $regex = str_replace("\t", '', $regex);
  113.         $regex = str_replace(' ',  '', $regex);
  114.  
  115.         return $regex;
  116. }
  117.  
  118. /**
  119.  * Searches all OIDs in $text and outputs them as array.
  120.  * @author  Daniel Marschall, ViaThinkSoft
  121.  * @version 2014-12-09
  122.  * @param   $text (string)<br />
  123.  *              The text to be parsed
  124.  * @param   $min_len (int)<br />
  125.  *              0="." and greater will be recognized, but not ""<br />
  126.  *              1=".2" and greater will be recognized<br />
  127.  *              2=".2.999" and greater will be recognized (default)<br />
  128.  *              etc.
  129.  * @param   $allow_leading_zeroes (bool)<br />
  130.  *              true:  ".2.0999" will be recognized<br />
  131.  *              false: ".2.0999" won't be recognized (default)
  132.  * @param   $leading_dot_policy (int)<br />
  133.  *              0 (OID_DOT_FORBIDDEN): forbidden<br />
  134.  *              1 (OID_DOT_OPTIONAL) : optional (default)<br />
  135.  *              2 (OID_DOT_REQUIRED) : enforced
  136.  * @param   $requires_whitespace_delimiters (bool)<br />
  137.  *              true:  "2.999" will be recognized, as well as " 2.999 " (default)<br />
  138.  *              false: "2.999!" will be reconigzed, as well as "2.999.c" (this might be used in in documentations with templates)
  139.  * @return  (array<string>) An array of OIDs in dot notation
  140.  **/
  141. function parse_oids($text, $min_len=2, $allow_leading_zeroes=false, $leading_dot_policy=OID_DOT_OPTIONAL, $requires_whitespace_delimiters=true) {
  142.         $regex = oid_detection_regex($min_len, $allow_leading_zeroes, $leading_dot_policy, $requires_whitespace_delimiters);
  143.  
  144.         preg_match_all($regex, $text, $matches);
  145.         return $matches[1];
  146. }
  147.  
  148. /**
  149.  * Returns a full regular expression for detecting OIDs in dot notation inside a text.
  150.  * @author  Daniel Marschall, ViaThinkSoft
  151.  * @version 2014-12-09
  152.  * @param   $min_len (int)<br />
  153.  *              0="." and greater will be recognized, but not ""<br />
  154.  *              1=".2" and greater will be recognized<br />
  155.  *              2=".2.999" and greater will be recognized (default)<br />
  156.  *              etc.
  157.  * @param   $allow_leading_zeroes (bool)<br />
  158.  *              true:  ".2.0999" will be recognized<br />
  159.  *              false: ".2.0999" won't be recognized (default)
  160.  * @param   $leading_dot_policy (int)<br />
  161.  *              0 (OID_DOT_FORBIDDEN): forbidden<br />
  162.  *              1 (OID_DOT_OPTIONAL) : optional (default)<br />
  163.  *              2 (OID_DOT_REQUIRED) : enforced
  164.  * @param   $requires_whitespace_delimiters (bool)<br />
  165.  *              true:  "2.999" will be recognized, as well as " 2.999 " (default)<br />
  166.  *              false: "2.999!" will be reconigzed, as well as "2.999.c" (this might be used in in documentations with templates)
  167.  * @return  (string) The regular expression
  168.  **/
  169. function oid_detection_regex($min_len=2, $allow_leading_zeroes=false, $leading_dot_policy=OID_DOT_OPTIONAL, $requires_whitespace_delimiters=true) {
  170.         if ($requires_whitespace_delimiters) {
  171.                 // A fully qualified regular expression which can be used by preg_match()
  172.                 $begin_condition = '(?<=^|\\s)';
  173.                 $end_condition   = '(?=\\s|$)';
  174.         } else {
  175.                 // A partial expression which can be used inside another regular expression
  176.                 $begin_condition = '(?<![\d])';
  177.                 $end_condition   = '(?![\d])';
  178.         }
  179.  
  180.         $part_regex = oid_part_regex($min_len, $allow_leading_zeroes, $leading_dot_policy);
  181.  
  182.         return '@'.$begin_condition.$part_regex.$end_condition.'@';
  183. }
  184.  
  185. /**
  186.  * Returns the parent of an OID in dot notation or the OID itself, if it is the root.<br />
  187.  * Leading dots and leading zeroes are tolerated.
  188.  * @author  Daniel Marschall, ViaThinkSoft
  189.  * @version 2014-12-16
  190.  * @param   $oid (string)<br />
  191.  *              An OID in dot notation.
  192.  * @return  (string) The parent OID in dot notation.
  193.  **/
  194. function oid_up($oid) {
  195.         $oid = sanitizeOID($oid, 'auto');
  196.         if ($oid === false) return false;
  197.  
  198.         $p = strrpos($oid, '.');
  199.         if ($p === false) return $oid;
  200.         if ($p == 0) return '.';
  201.  
  202.         return substr($oid, 0, $p);
  203. }
  204.  
  205. /**
  206.  * Outputs the depth of an OID.
  207.  * @author  Daniel Marschall, ViaThinkSoft
  208.  * @version 2014-12-09
  209.  * @param   $oid (string) An OID in dot notation (with or without leading dot)
  210.  * @return  (int) The depth of the OID, e.g. 2.999 and .2.999 has the length 2.
  211.  **/
  212. function oid_len($oid) {
  213.         if ($oid == '') return 0;
  214.         if ($oid[0] == '.') $oid = substr($oid, 1);
  215.         return substr_count($oid, '.')+1;
  216. }
  217. function oid_depth($oid) {
  218.         return oid_len($oid);
  219. }
  220.  
  221. /**
  222.  * Lists all parents of an OID.
  223.  * This function tolerates leading dots. The parent of '.' stays '.'.
  224.  * The OID will not be checked for validity!
  225.  * @author  Daniel Marschall, ViaThinkSoft
  226.  * @version 2014-12-17
  227.  * @param   $oid (string)<br />
  228.  *              An OID in dot notation.
  229.  * @return  (array<string>) An array with all parent OIDs.
  230.  **/
  231. function oid_parents($oid) {
  232.         $parents = array();
  233.  
  234.         while (oid_len($oid) > 1) {
  235.                 $oid = oid_up($oid);
  236.                 $parents[] = $oid;
  237.         }
  238.  
  239.         if (substr($oid, 0, 1) == '.') $parents[] = '.';
  240.  
  241.         return $parents;
  242. }
  243.  
  244. /*
  245. assert(oid_parents('.1.2.999') == array('.1.2', '.1', '.'));
  246. assert(oid_parents('1.2.999') == array('1.2', '1'));
  247. assert(oid_parents('.') == array('.'));
  248. assert(oid_parents('') == array());
  249. */
  250.  
  251. /**
  252.  * Sorts an array containing OIDs in dot notation.
  253.  * @author  Daniel Marschall, ViaThinkSoft
  254.  * @version 2014-12-09
  255.  * @param   $ary (array<string>)<br />
  256.  *              An array of OIDs in dot notation.<br />
  257.  *              This array will be changed by this method.
  258.  * @param   $output_with_leading_dot (bool)<br />
  259.  *              true: The array will be normalized to OIDs with a leading dot.
  260.  *              false: The array will be normalized to OIDs without a leading dot. (default)
  261.  * @return  Nothing
  262.  **/
  263. function oidSort(&$ary, $output_with_leading_dot=false) {
  264.         $out = array();
  265.  
  266.         $none = $output_with_leading_dot ? '.' : '';
  267.  
  268.         $d = array();
  269.         foreach ($ary as &$oid) {
  270.                 if (($oid == '') || ($oid == '.')) {
  271.                         $out[] = $none;
  272.                 } else {
  273.                         $oid = sanitizeOID($oid, 'auto'); // strike leading zeroes
  274.                         $bry = explode('.', $oid, 2);
  275.                         $firstarc = $bry[0];
  276.                         $rest     = (isset($bry[1])) ? $bry[1] : '';
  277.                         $d[$firstarc][] = $rest;
  278.                 }
  279.         }
  280.         unset($oid);
  281.         ksort($d);
  282.  
  283.         foreach ($d as $firstarc => &$data) {
  284.                 oidSort($data);
  285.                 foreach ($data as &$rest) {
  286.                         $out[] = ($output_with_leading_dot ? '.' : '')."$firstarc" . (($rest != $none) ? ".$rest" : '');
  287.                 }
  288.         }
  289.         unset($data);
  290.  
  291.         $ary = $out;
  292. }
  293.  
  294. /**
  295.  * Removes leading zeroes from an OID in dot notation.
  296.  * @author  Daniel Marschall, ViaThinkSoft
  297.  * @version 2015-08-17
  298.  * @param   $oid (string)<br />
  299.  *              An OID in dot notation.
  300.  * @param   $leading_dot (bool)<br />
  301.  *              true: The OID is valid, if it contains a leading dot.<br />
  302.  *              false (default): The OID is valid, if it does not contain a leading dot.
  303.  *              'auto: Allow both
  304.  * @return  (mixed) The OID without leading dots, or <code>false</code> if the OID is syntactically wrong.
  305.  **/
  306. $oid_sanitize_cache = array();
  307. function sanitizeOID($oid, $leading_dot=false) {
  308.         if ($leading_dot) $leading_dot = substr($oid,0,1) == '.';
  309.  
  310.         // We are using a cache, since this function is used very often by OID+
  311.         global $oid_sanitize_cache;
  312.         $v = ($leading_dot ? 'T' : 'F').$oid;
  313.         if (isset($oid_sanitize_cache[$v])) return $oid_sanitize_cache[$v];
  314.  
  315.         if ($leading_dot) {
  316.                 if ($oid == '.') return '';
  317.         } else {
  318.                 if ($oid == '') return '';
  319.         }
  320.  
  321.         $out = '';
  322.         $ary = explode('.', $oid);
  323.         foreach ($ary as $n => &$a) {
  324.                 if (($leading_dot) && ($n == 0)) {
  325.                         if ($a != '') return false;
  326.                         continue;
  327.                 }
  328.  
  329.                 if (!ctype_digit($a)) return false; // does contain something other than digits
  330.  
  331.                 // strike leading zeroes
  332.                 $a = preg_replace("@^0+@", '', $a);
  333.                 if ($a == '') $a = 0;
  334.  
  335.                 if (($leading_dot) || ($n != 0)) $out .= '.';
  336.                 $out .= $a;
  337.         }
  338.         unset($a);
  339.         unset($ary);
  340.  
  341.         $oid_sanitize_cache[$v] = $out;
  342.         return $out;
  343. }
  344.  
  345. /**
  346.  * Shows the top arc of an OID.
  347.  * This function tolerates leading dots.
  348.  * @author  Daniel Marschall, ViaThinkSoft
  349.  * @version 2014-12-16
  350.  * @param   $oid (string)<br />
  351.  *              An OID in dot notation.
  352.  * @return  (mixed) The top arc of the OID or empty string if it is already the root ('.')
  353.  **/
  354. function oid_toparc($oid) {
  355.         $leadingdot = substr($oid,0,1) == '.';
  356.  
  357.         $oid = sanitizeOID($oid, $leadingdot);
  358.         if ($oid === false) return false;
  359.  
  360.         if (!$leadingdot) $oid = '.'.$oid;
  361.  
  362.         $p = strrpos($oid, '.');
  363.         if ($p === false) return false;
  364.         $r = substr($oid, $p+1);
  365.  
  366.         if ($leadingdot) {
  367.         #       if ($r == '') return '.';
  368.                 return $r;
  369.         } else {
  370.                 return substr($r, 1);
  371.         }
  372. }
  373.  
  374. /**
  375.  * Calculates the distance between two OIDs.
  376.  * This function tolerates leading dots and leading zeroes.
  377.  * @author  Daniel Marschall, ViaThinkSoft
  378.  * @version 2014-12-20
  379.  * @param   $a (string)<br />
  380.  *              An OID.
  381.  * @param   $b (string)<br />
  382.  *              An OID.
  383.  * @return  (string) false if both OIDs do not have a child-parent or parent-child relation, e.g. oid_distance('2.999.1.2.3', '2.999.4.5') = false, or if one of the OIDs is syntactially invalid<br />
  384.  *              >0 if $a is more specific than $b , e.g. oid_distance('2.999.1.2', '2.999') = 2<br />
  385.  *              <0 if $a is more common than $b , e.g. oid_distance('2.999', '2.999.1.2') = -2
  386.  **/
  387. function oid_distance($a, $b) {
  388.         $a = sanitizeOID($a, $a[0] == '.');
  389.         if ($a === false) return false;
  390.         $b = sanitizeOID($b, $a[0] == '.');
  391.         if ($b === false) return false;
  392.  
  393.         $ary = explode('.', $a);
  394.         $bry = explode('.', $b);
  395.  
  396.         $min_len = min(count($ary), count($bry));
  397.  
  398.         for ($i=0; $i<$min_len; $i++) {
  399.                 if ($ary[$i] != $bry[$i]) return false;
  400.         }
  401.  
  402.         return count($ary) - count($bry);
  403. }
  404.  
  405. /*
  406. assert(oid_distance('2.999.1.2.3', '2.999.4.5') === false);
  407. assert(oid_distance('2.999.1.2', '2.999') === 2);
  408. assert(oid_distance('2.999', '2.999.1.2') === -2);
  409. */
  410.  
  411. /**
  412.  * Adds a leading dot to an OID.
  413.  * Leading zeroes are tolerated.
  414.  * @author  Daniel Marschall, ViaThinkSoft
  415.  * @version 2014-12-20
  416.  * @param   $oid (string)<br />
  417.  *              An OID.
  418.  * @return  (string) The OID with a leading dot or false if the OID is syntactially wrong.
  419.  **/
  420. function oid_add_leading_dot($oid) {
  421.         $oid = sanitizeOID($oid, 'auto');
  422.         if ($oid === false) return false;
  423.  
  424.         if ($oid[0] != '.') $oid = '.'.$oid;
  425.         return $oid;
  426. }
  427.  
  428. /**
  429.  * Removes a leading dot to an OID.
  430.  * Leading zeroes are tolerated.
  431.  * @author  Daniel Marschall, ViaThinkSoft
  432.  * @version 2014-12-20
  433.  * @param   $oid (string)<br />
  434.  *              An OID.
  435.  * @return  (string) The OID without a leading dot or false if the OID is syntactially wrong.
  436.  **/
  437. function oid_remove_leading_dot($oid) {
  438.         $oid = sanitizeOID($oid, 'auto');
  439.         if ($oid === false) return false;
  440.  
  441.         if (substr($oid,0,1) == '.') $oid = substr($oid, 1);
  442.         return $oid;
  443. }
  444.  
  445.  
  446. # === IRI NOTATION FUNCTIONS ===
  447.  
  448. if (!function_exists('mb_ord')) {
  449.         # http://stackoverflow.com/a/24755772/3544341
  450.         function mb_ord($char, $encoding = 'UTF-8') {
  451.                 if ($encoding === 'UCS-4BE') {
  452.                         list(, $ord) = (strlen($char) === 4) ? @unpack('N', $char) : @unpack('n', $char);
  453.                         return $ord;
  454.                 } else {
  455.                         return mb_ord(mb_convert_encoding($char, 'UCS-4BE', $encoding), 'UCS-4BE');
  456.                 }
  457.         }
  458. }
  459.  
  460. function iri_char_valid($c, $firstchar, $lastchar) {
  461.         // see Rec. ITU-T X.660, clause 7.5
  462.  
  463.         if (($firstchar || $lastchar) && ($c == '-')) return false;
  464.  
  465.         if ($c == '-') return true;
  466.         if ($c == '.') return true;
  467.         if ($c == '_') return true;
  468.         if ($c == '~') return true;
  469.         if (($c >= '0') && ($c <= '9') && (!$firstchar)) return true;
  470.         if (($c >= 'A') && ($c <= 'Z')) return true;
  471.         if (($c >= 'a') && ($c <= 'z')) return true;
  472.  
  473.         $v = mb_ord($c);
  474.         if (($v >= 0x000000A0) && ($v <= 0x0000DFFE)) return true;
  475.         if (($v >= 0x0000F900) && ($v <= 0x0000FDCF)) return true;
  476.         if (($v >= 0x0000FDF0) && ($v <= 0x0000FFEF)) return true;
  477.         if (($v >= 0x00010000) && ($v <= 0x0001FFFD)) return true;
  478.         if (($v >= 0x00020000) && ($v <= 0x0002FFFD)) return true;
  479.         if (($v >= 0x00030000) && ($v <= 0x0003FFFD)) return true;
  480.         if (($v >= 0x00040000) && ($v <= 0x0004FFFD)) return true;
  481.         if (($v >= 0x00050000) && ($v <= 0x0005FFFD)) return true;
  482.         if (($v >= 0x00060000) && ($v <= 0x0006FFFD)) return true;
  483.         if (($v >= 0x00070000) && ($v <= 0x0007FFFD)) return true;
  484.         if (($v >= 0x00080000) && ($v <= 0x0008FFFD)) return true;
  485.         if (($v >= 0x00090000) && ($v <= 0x0009FFFD)) return true;
  486.         if (($v >= 0x000A0000) && ($v <= 0x000AFFFD)) return true;
  487.         if (($v >= 0x000B0000) && ($v <= 0x000BFFFD)) return true;
  488.         if (($v >= 0x000C0000) && ($v <= 0x000CFFFD)) return true;
  489.         if (($v >= 0x000D0000) && ($v <= 0x000DFFFD)) return true;
  490.         if (($v >= 0x000E1000) && ($v <= 0x000EFFFD)) return true;
  491.  
  492.         // Note: Rec. ITU-T X.660, clause 7.5.3 would also forbid ranges which are marked in ISO/IEC 10646 as "(This position shall not be used)"
  493.         // But tool implementers should be tolerate them, since these limitations can be removed in future.
  494.  
  495.         return false;
  496. }
  497.  
  498. function iri_arc_valid($arc, $allow_only_numeric=true) {
  499.         if ($arc == '') return false;
  500.  
  501.         if ($allow_only_numeric && preg_match('@^(\\d+)$@', $arc, $m)) return true; # numeric arc
  502.  
  503.         if (substr($arc, 2, 2) == '--') return false; // see Rec. ITU-T X.660, clause 7.5.4
  504.  
  505.         $len = strlen($arc);
  506.         for ($i=0; $i<$len; $i++) {
  507.                 if (!iri_char_valid($arc[$i], $i==0, $i==$len-1)) return false;
  508.         }
  509.  
  510.         return true;
  511. }
  512.  
  513. /**
  514.  * Checks if an IRI identifier is valid or not.
  515.  * @author  Daniel Marschall, ViaThinkSoft
  516.  * @version 2014-12-17
  517.  * @param   $iri (string)<br />
  518.  *              An OID in IRI notation, e.g. /Example/test
  519.  * @return  (bool) true if the IRI identifier is valid.
  520.  **/
  521. function iri_valid($iri) {
  522.         if ($iri == '/') return true; // OK?
  523.  
  524.         if (substr($iri, 0, 1) != '/') return false;
  525.  
  526.         $ary = explode('/', $iri);
  527.         array_shift($ary);
  528.         foreach ($ary as $a) {
  529.                 if (!iri_arc_valid($a)) return false;
  530.         }
  531.  
  532.         return true;
  533. }
  534.  
  535. /*
  536. assert(iri_arc_valid('ABCDEF'));
  537. assert(!iri_arc_valid('-ABCDEF'));
  538. assert(!iri_arc_valid('ABCDEF-'));
  539. assert(!iri_arc_valid(' ABCDEF'));
  540. assert(!iri_arc_valid('2 ABCDEF'));
  541. assert(!iri_arc_valid(''));
  542.  
  543. assert(!iri_valid(''));
  544. assert(iri_valid('/'));
  545. assert(iri_valid('/hello/world'));
  546. assert(iri_valid('/123/world'));
  547. assert(!iri_valid('/hello/0world'));
  548. assert(!iri_valid('/hello/xo--test'));
  549. assert(!iri_valid('/hello/-super-/sd'));
  550. */
  551.  
  552. /**
  553.  * Returns an associative array in the form 'ASN.1' => '/2/1' .
  554.  * @author  Daniel Marschall, ViaThinkSoft
  555.  * @version 2018-01-05
  556.  * @see http://itu.int/go/X660
  557.  * @return  (array) An associative array in the form 'ASN.1' => '/2/1' .
  558.  **/
  559. function iri_get_long_arcs() {
  560.         $iri_long_arcs = array();
  561.         $iri_long_arcs['ASN.1'] = '/2/1';
  562.         $iri_long_arcs['Country'] = '/2/16';
  563.         $iri_long_arcs['International-Organizations'] = '/2/23';
  564.         $iri_long_arcs['UUID'] = '/2/25';
  565.         $iri_long_arcs['Tag-Based'] = '/2/27';
  566.         $iri_long_arcs['BIP'] = '/2/41';
  567.         $iri_long_arcs['Telebiometrics'] = '/2/42';
  568.         $iri_long_arcs['Cybersecurity'] = '/2/48';
  569.         $iri_long_arcs['Alerting'] = '/2/49';
  570.         $iri_long_arcs['OIDResolutionSystem'] = '/2/50';
  571.         $iri_long_arcs['GS1'] = '/2/51';
  572.         $iri_long_arcs['Example'] = '/2/999'; // English
  573.         $iri_long_arcs['Exemple'] = '/2/999'; // French
  574.         $iri_long_arcs['Ejemplo'] = '/2/999'; // Spanish
  575.         $iri_long_arcs["\u{0627}\u{0644}\u{0645}\u{062B}\u{0627}\u{0644}"] = '/2/999'; // Arabic
  576.         $iri_long_arcs["\u{8303}\u{4F8B}"] = '/2/999'; // Chinese
  577.         $iri_long_arcs["\u{041F}\u{0440}\u{0438}\u{043C}\u{0435}\u{0440}"] = '/2/999'; // Russian
  578.         $iri_long_arcs["\u{C608}\u{C81C}"] = '/2/999'; // Korean
  579.         $iri_long_arcs["\u{4F8B}"] = '/2/999'; // Japanese
  580.         $iri_long_arcs['Beispiel'] = '/2/999'; // German
  581.         return $iri_long_arcs;
  582. }
  583.  
  584. /**
  585.  * Tries to shorten/simplify an IRI by applying "long arcs", e.g. /2/999/123 -> /Example/123 .
  586.  * @author  Daniel Marschall, ViaThinkSoft
  587.  * @version 2014-12-28
  588.  * @param   $iri (string)<br />
  589.  *              An OID in IRI notation, e.g. /Example/test
  590.  * @return  (string) The modified IRI.
  591.  **/
  592. function iri_add_longarcs($iri) {
  593.         $iri_long_arcs = iri_get_long_arcs();
  594.  
  595.         // TODO: $iri valid?
  596.  
  597.         $ary = explode('/', $iri);
  598.  
  599.         $ary_number_iri = $ary;
  600.         if ($ary_number_iri[1] == 'Joint-ISO-ITU-T') $ary_number_iri[1] = '2';
  601.         /*
  602.         if ($ary_number_iri[1] == '2') {
  603.                 // TODO: /2/Example -> /2/999 -> /Example
  604.         } else {
  605.                 // Currently, only long arcs inside .2 are defined
  606.                 // return $iri;
  607.         }
  608.         */
  609.         $number_iri = implode('/', $ary_number_iri);
  610.  
  611.         foreach ($iri_long_arcs as $cur_longarc => $cur_iri) {
  612.                 // TODO: $cur_iri valid?
  613.  
  614.                 if (strpos($number_iri.'/', $cur_iri.'/') === 0) {
  615.                         $cnt = substr_count($cur_iri, '/');
  616.                         for ($i=1; $i<$cnt; $i++) {
  617.                                 array_shift($ary);
  618.                         }
  619.                         $ary[0] = '';
  620.                         $ary[1] = $cur_longarc;
  621.                         $iri = implode('/', $ary);
  622.                         break;
  623.                 }
  624.         }
  625.  
  626.         return $iri;
  627. }
  628.  
  629. # === FUNCTIONS FOR OIDS IN ASN.1 NOTATION ===
  630.  
  631. /**
  632.  * Checks if an ASN.1 identifier is valid.
  633.  * @author  Daniel Marschall, ViaThinkSoft
  634.  * @version 2014-12-09
  635.  * @param   $id (string)<br />
  636.  *              An ASN.1 identifier, e.g. "example". Not "example(99)" or "99" and not a path like "{ 2 999 }"
  637.  *              Note: Use asn1_path_valid() for validating a whole ASN.1 notation path.
  638.  * @return  (bool) true, if the identifier is valid: It begins with an lowercase letter and contains only 0-9, a-z, A-Z and "-"
  639.  **/
  640. # TODO: umbenennen in asn1_alpha_id_valid
  641. function oid_id_is_valid($id) {
  642.         return preg_match('/^([a-z][a-zA-Z0-9-]*)$/', $id);
  643. }
  644.  
  645. /**
  646.  * Checks if the ASN.1 notation of an OID is valid.
  647.  * This function does not tolerate leading zeros.
  648.  * This function will fail (return false) if there are unresolved symbols, e.g. {iso test} is not valid while { iso 123 } is valid.
  649.  * @author  Daniel Marschall, ViaThinkSoft
  650.  * @version 2014-12-17
  651.  * @param   $asn (string)<br />
  652.  *              An OID in ASN.1 notation.
  653.  * @return  (bools) true if the identifier is valid.
  654.  **/
  655. function asn1_path_valid($asn1) {
  656.         return asn1_to_dot($asn1) != false;
  657. }
  658.  
  659. /**
  660.  * Returns an array of standardized ASN.1 alphanumeric identifiers which do not require a numeric identifier, e.g. { 2 example }
  661.  * The array has the form '0.0.a' -> '0.0.0'
  662.  * @author  Daniel Marschall, ViaThinkSoft
  663.  * @version 2014-12-28
  664.  * @see http://www.oid-info.com/name-forms.htm
  665.  * @return  (array) Assoziative array of standardized ASN.1 alphanumeric identifiers
  666.  **/
  667. function asn1_get_standardized_array() {
  668.  
  669.         // Taken from oid-info.com
  670.         // http://www.oid-info.com/name-forms.htm
  671.         $standardized = array();
  672.         $standardized['itu-t'] = '0';
  673.         $standardized['ccitt'] = '0';
  674.         $standardized['iso'] = '1';
  675.         $standardized['joint-iso-itu-t'] = '2';
  676.         $standardized['joint-iso-ccitt'] = '2';
  677.         $standardized['0.recommendation'] = '0.0';
  678.         $standardized['0.0.a'] = '0.0.0';
  679.         $standardized['0.0.b'] = '0.0.1';
  680.         $standardized['0.0.c'] = '0.0.2';
  681.         $standardized['0.0.d'] = '0.0.3';
  682.         $standardized['0.0.e'] = '0.0.4';
  683.         $standardized['0.0.f'] = '0.0.5';
  684.         $standardized['0.0.g'] = '0.0.6';
  685.         $standardized['0.0.h'] = '0.0.7';
  686.         $standardized['0.0.i'] = '0.0.8';
  687.         $standardized['0.0.j'] = '0.0.9';
  688.         $standardized['0.0.k'] = '0.0.10';
  689.         $standardized['0.0.l'] = '0.0.11';
  690.         $standardized['0.0.m'] = '0.0.12';
  691.         $standardized['0.0.n'] = '0.0.13';
  692.         $standardized['0.0.o'] = '0.0.14';
  693.         $standardized['0.0.p'] = '0.0.15';
  694.         $standardized['0.0.q'] = '0.0.16';
  695.         $standardized['0.0.r'] = '0.0.17';
  696.         $standardized['0.0.s'] = '0.0.18';
  697.         $standardized['0.0.t'] = '0.0.19';
  698.         $standardized['0.0.u'] = '0.0.20';
  699.         $standardized['0.0.v'] = '0.0.21';
  700.         $standardized['0.0.w'] = '0.0.22';
  701.         $standardized['0.0.x'] = '0.0.23';
  702.         $standardized['0.0.y'] = '0.0.24';
  703.         $standardized['0.0.z'] = '0.0.25';
  704.         $standardized['0.question'] = '0.1';
  705.         $standardized['0.administration'] = '0.2';
  706.         $standardized['0.network-operator'] = '0.3';
  707.         $standardized['0.identified-organization'] = '0.4';
  708.         $standardized['1.standard'] = '1.0';
  709.         $standardized['1.registration-authority'] = '1.1';
  710.         $standardized['1.member-body'] = '1.2';
  711.         $standardized['1.identified-organization'] = '1.3';
  712.         return $standardized;
  713. }
  714.  
  715. /**
  716.  * Converts an OID in ASN.1 notation into an OID in dot notation and tries to resolve well-known identifiers.<br />
  717.  * e.g. {joint-iso-itu-t(2) example(999) 1 2 3} --> 2.999.1.2.3<br />
  718.  * e.g. {iso 3} --> 1.3
  719.  * This function does not tolerate leading zeros.
  720.  * This function will fail (return false) if there are unresolved symbols, e.g. {iso test} will not be resolved to 1.test
  721.  * @author  Daniel Marschall, ViaThinkSoft
  722.  * @version 2014-12-17
  723.  * @param   $asn (string)<br />
  724.  *              An OID in ASN.1 notation.
  725.  * @return  (string) An OID in dot notation without leading dot or false if the path is invalid.
  726.  **/
  727. function asn1_to_dot($asn) {
  728.         $standardized = asn1_get_standardized_array();
  729.  
  730.         // Clean up
  731.         $asn = preg_replace('@^\\{(.+)\\}$@', '\\1', $asn, -1, $count);
  732.         if ($count == 0) return false; // { and } are required. The asn.1 path will NOT be trimmed by this function
  733.  
  734.         // If identifier is set, apply it (no check if it overrides a standardized identifier)
  735.         $asn = preg_replace('|\s*([a-z][a-zA-Z0-9-]*)\s*\((\d+)\)|', ' \\2', $asn);
  736.         $asn = trim($asn);
  737.  
  738.         // Set dots
  739.         $asn = preg_replace('|\s+|', '.', $asn);
  740.  
  741.         // Apply standardized identifiers (case sensitive)
  742.         $asn .= '.';
  743.         foreach ($standardized as $s => $r) {
  744.                 $asn = preg_replace("|^$s|", $r, $asn);
  745.         }
  746.         $asn = substr($asn, 0, strlen($asn)-1);
  747.  
  748.         // Check if all numbers are OK
  749.         // -> every arc must be resolved
  750.         // -> numeric arcs must not have a leading zero
  751.         // -> invalid stuff will be recognized, e.g. a "(1)" without an identifier in front of it
  752.         $ary = explode('.', $asn);
  753.         foreach ($ary as $a) {
  754.                 if (!preg_match('@^(0|([1-9]\\d*))$@', $a, $m)) return false;
  755.         }
  756.  
  757.         return $asn;
  758. }
  759.  
  760. /*
  761. assert(asn1_to_dot('{2 999 (1)}') == false);
  762. assert(asn1_to_dot('{2 999 test}') == false);
  763. assert(asn1_to_dot('{2 999 1}') == '2.999.1');
  764. assert(asn1_to_dot(' {2 999 1} ') == false);
  765. assert(asn1_to_dot('2 999 1') == false);
  766. assert(asn1_to_dot('{2 999 01}') == false);
  767. assert(asn1_to_dot('{  0   question 123  }') == '0.1.123');
  768. assert(asn1_to_dot('{  iso  }') == '1');
  769. assert(asn1_to_dot('{  iso(1)  }') == '1');
  770. assert(asn1_to_dot('{  iso(2)  }') == '2');
  771. assert(asn1_to_dot('{  iso 3 }') == '1.3');
  772. */
  773.  
  774. /**
  775.  * "Soft corrects" an invalid ASN.1 identifier.<br />
  776.  * Attention, by "soft correcting" the ID, it is not authoritative anymore, and might not be able to be resolved by ORS.
  777.  * @author  Daniel Marschall, ViaThinkSoft
  778.  * @version 2014-12-09
  779.  * @param   $id (string)<br />
  780.  *              An ASN.1 identifier.
  781.  * @param   $append_id_prefix (bool)<br />
  782.  *              true (default): If the identifier doesn't start with a-Z, the problem will be solved by prepending "id-" to the identifier.<br />
  783.  *              false: If the identifier doesn't start with a-Z, then the problem cannot be solved (method returns empty string).
  784.  * @return  (string) The "soft corrected" ASN.1 identifier.<br />
  785.  *              Invalid characters will be removed.<br />
  786.  *              Uncorrectable start elements (0-9 or "-") will be either removed or solved by prepending "id-" (see <code>$append_id_prefix</code>)<br />
  787.  *              If the identifier begins with an upper case letter, the letter will be converted into lower case.
  788.  **/
  789. function oid_soft_correct_id($id, $append_id_prefix = true) {
  790.         // Convert "_" to "-"
  791.         $id = str_replace('_', '-', $id);
  792.  
  793.         // Remove invalid characters
  794.         $id = preg_replace('/[^a-zA-Z0-9-]+/', '', $id);
  795.  
  796.         // Remove uncorrectable start elements (0-9 or "-")
  797.         if ($append_id_prefix) {
  798.                 $id = preg_replace('/^([^a-zA-Z]+)/', 'id-$1', $id);
  799.         } else {
  800.                 $id = preg_replace('/^([^a-zA-Z]+)/', '', $id);
  801.         }
  802.  
  803.         // "Correct" upper case beginning letter by converting it to lower case
  804.         if (preg_match('/^[A-Z]/', $id)) {
  805.                 $id = strtolower($id[0]) . substr($id, 1);
  806.         }
  807.  
  808.         return $id;
  809. }
  810.  
  811.