Subversion Repositories oidplus

Rev

Rev 319 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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