Login | ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/oidinfo_api/trunk/oid_utils.inc.phps
Revision: 16
Committed: Sun May 23 21:07:22 2021 UTC (4 months ago) by daniel-marschall
File size: 31045 byte(s)

File Contents

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