Subversion Repositories oidplus

Rev

Rev 50 | 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
 
3
// OID-Utilities for PHP
9 daniel-mar 4
// (C) 2011-2019 Daniel Marschall, ViaThinkSoft
5
// Licensed under the terms of the Apache 2.0 license
50 daniel-mar 6
// Last revision: 2019-03-25
2 daniel-mar 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) {
12 daniel-mar 388
        if (substr($a,0,1) == '.') $a = substr($a,1);
389
        if (substr($b,0,1) == '.') $b = substr($b,1);
390
 
391
        $a = sanitizeOID($a, false);
2 daniel-mar 392
        if ($a === false) return false;
12 daniel-mar 393
        $b = sanitizeOID($b, false);
2 daniel-mar 394
        if ($b === false) return false;
395
 
396
        $ary = explode('.', $a);
397
        $bry = explode('.', $b);
398
 
399
        $min_len = min(count($ary), count($bry));
400
 
401
        for ($i=0; $i<$min_len; $i++) {
402
                if ($ary[$i] != $bry[$i]) return false;
403
        }
404
 
405
        return count($ary) - count($bry);
406
}
407
 
408
/*
409
assert(oid_distance('2.999.1.2.3', '2.999.4.5') === false);
410
assert(oid_distance('2.999.1.2', '2.999') === 2);
411
assert(oid_distance('2.999', '2.999.1.2') === -2);
412
*/
413
 
414
/**
415
 * Adds a leading dot to an OID.
416
 * Leading zeroes are tolerated.
417
 * @author  Daniel Marschall, ViaThinkSoft
418
 * @version 2014-12-20
419
 * @param   $oid (string)<br />
420
 *              An OID.
421
 * @return  (string) The OID with a leading dot or false if the OID is syntactially wrong.
422
 **/
423
function oid_add_leading_dot($oid) {
424
        $oid = sanitizeOID($oid, 'auto');
425
        if ($oid === false) return false;
426
 
427
        if ($oid[0] != '.') $oid = '.'.$oid;
428
        return $oid;
429
}
430
 
431
/**
432
 * Removes a leading dot to an OID.
433
 * Leading zeroes are tolerated.
434
 * @author  Daniel Marschall, ViaThinkSoft
435
 * @version 2014-12-20
436
 * @param   $oid (string)<br />
437
 *              An OID.
438
 * @return  (string) The OID without a leading dot or false if the OID is syntactially wrong.
439
 **/
440
function oid_remove_leading_dot($oid) {
441
        $oid = sanitizeOID($oid, 'auto');
442
        if ($oid === false) return false;
443
 
444
        if (substr($oid,0,1) == '.') $oid = substr($oid, 1);
445
        return $oid;
446
}
447
 
448
 
70 daniel-mar 449
# === OID-IRI NOTATION FUNCTIONS ===
2 daniel-mar 450
 
451
if (!function_exists('mb_ord')) {
452
        # http://stackoverflow.com/a/24755772/3544341
453
        function mb_ord($char, $encoding = 'UTF-8') {
454
                if ($encoding === 'UCS-4BE') {
455
                        list(, $ord) = (strlen($char) === 4) ? @unpack('N', $char) : @unpack('n', $char);
456
                        return $ord;
457
                } else {
458
                        return mb_ord(mb_convert_encoding($char, 'UCS-4BE', $encoding), 'UCS-4BE');
459
                }
460
        }
461
}
462
 
463
function iri_char_valid($c, $firstchar, $lastchar) {
464
        // see Rec. ITU-T X.660, clause 7.5
465
 
466
        if (($firstchar || $lastchar) && ($c == '-')) return false;
467
 
468
        if ($c == '-') return true;
469
        if ($c == '.') return true;
470
        if ($c == '_') return true;
471
        if ($c == '~') return true;
472
        if (($c >= '0') && ($c <= '9') && (!$firstchar)) return true;
473
        if (($c >= 'A') && ($c <= 'Z')) return true;
474
        if (($c >= 'a') && ($c <= 'z')) return true;
475
 
476
        $v = mb_ord($c);
12 daniel-mar 477
 
2 daniel-mar 478
        if (($v >= 0x000000A0) && ($v <= 0x0000DFFE)) return true;
479
        if (($v >= 0x0000F900) && ($v <= 0x0000FDCF)) return true;
480
        if (($v >= 0x0000FDF0) && ($v <= 0x0000FFEF)) return true;
481
        if (($v >= 0x00010000) && ($v <= 0x0001FFFD)) return true;
482
        if (($v >= 0x00020000) && ($v <= 0x0002FFFD)) return true;
483
        if (($v >= 0x00030000) && ($v <= 0x0003FFFD)) return true;
484
        if (($v >= 0x00040000) && ($v <= 0x0004FFFD)) return true;
485
        if (($v >= 0x00050000) && ($v <= 0x0005FFFD)) return true;
486
        if (($v >= 0x00060000) && ($v <= 0x0006FFFD)) return true;
487
        if (($v >= 0x00070000) && ($v <= 0x0007FFFD)) return true;
488
        if (($v >= 0x00080000) && ($v <= 0x0008FFFD)) return true;
489
        if (($v >= 0x00090000) && ($v <= 0x0009FFFD)) return true;
490
        if (($v >= 0x000A0000) && ($v <= 0x000AFFFD)) return true;
491
        if (($v >= 0x000B0000) && ($v <= 0x000BFFFD)) return true;
492
        if (($v >= 0x000C0000) && ($v <= 0x000CFFFD)) return true;
493
        if (($v >= 0x000D0000) && ($v <= 0x000DFFFD)) return true;
494
        if (($v >= 0x000E1000) && ($v <= 0x000EFFFD)) return true;
495
 
496
        // 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)"
497
        // But tool implementers should be tolerate them, since these limitations can be removed in future.
498
 
499
        return false;
500
}
501
 
9 daniel-mar 502
function iri_arc_valid($arc, $allow_numeric=true) {
2 daniel-mar 503
        if ($arc == '') return false;
504
 
9 daniel-mar 505
        if ($allow_numeric && preg_match('@^(\\d+)$@', $arc, $m)) return true; # numeric arc
2 daniel-mar 506
 
13 daniel-mar 507
        // Question: Should we strip RTL/LTR characters?
508
 
12 daniel-mar 509
        if (mb_substr($arc, 2, 2) == '--') return false; // see Rec. ITU-T X.660, clause 7.5.4
2 daniel-mar 510
 
12 daniel-mar 511
        $array = array();
512
        preg_match_all('/./u', $arc, $array, PREG_SET_ORDER);
513
        $len = count($array);
514
        foreach ($array as $i => $char) {
515
                if (!iri_char_valid($char[0], $i==0, $i==$len-1)) return false;
2 daniel-mar 516
        }
517
 
518
        return true;
519
}
520
 
521
/**
522
 * Checks if an IRI identifier is valid or not.
523
 * @author  Daniel Marschall, ViaThinkSoft
524
 * @version 2014-12-17
525
 * @param   $iri (string)<br />
70 daniel-mar 526
 *              An OID in OID-IRI notation, e.g. /Example/test
2 daniel-mar 527
 * @return  (bool) true if the IRI identifier is valid.
528
 **/
529
function iri_valid($iri) {
530
        if ($iri == '/') return true; // OK?
531
 
532
        if (substr($iri, 0, 1) != '/') return false;
533
 
534
        $ary = explode('/', $iri);
535
        array_shift($ary);
536
        foreach ($ary as $a) {
537
                if (!iri_arc_valid($a)) return false;
538
        }
539
 
540
        return true;
541
}
542
 
543
/*
544
assert(iri_arc_valid('ABCDEF'));
545
assert(!iri_arc_valid('-ABCDEF'));
546
assert(!iri_arc_valid('ABCDEF-'));
547
assert(!iri_arc_valid(' ABCDEF'));
548
assert(!iri_arc_valid('2 ABCDEF'));
549
assert(!iri_arc_valid(''));
550
 
551
assert(!iri_valid(''));
552
assert(iri_valid('/'));
553
assert(iri_valid('/hello/world'));
554
assert(iri_valid('/123/world'));
555
assert(!iri_valid('/hello/0world'));
556
assert(!iri_valid('/hello/xo--test'));
557
assert(!iri_valid('/hello/-super-/sd'));
558
*/
559
 
560
/**
561
 * Returns an associative array in the form 'ASN.1' => '/2/1' .
562
 * @author  Daniel Marschall, ViaThinkSoft
563
 * @version 2018-01-05
564
 * @see http://itu.int/go/X660
565
 * @return  (array) An associative array in the form 'ASN.1' => '/2/1' .
566
 **/
567
function iri_get_long_arcs() {
568
        $iri_long_arcs = array();
569
        $iri_long_arcs['ASN.1'] = '/2/1';
570
        $iri_long_arcs['Country'] = '/2/16';
571
        $iri_long_arcs['International-Organizations'] = '/2/23';
572
        $iri_long_arcs['UUID'] = '/2/25';
573
        $iri_long_arcs['Tag-Based'] = '/2/27';
574
        $iri_long_arcs['BIP'] = '/2/41';
575
        $iri_long_arcs['Telebiometrics'] = '/2/42';
576
        $iri_long_arcs['Cybersecurity'] = '/2/48';
577
        $iri_long_arcs['Alerting'] = '/2/49';
578
        $iri_long_arcs['OIDResolutionSystem'] = '/2/50';
579
        $iri_long_arcs['GS1'] = '/2/51';
580
        $iri_long_arcs['Example'] = '/2/999'; // English
581
        $iri_long_arcs['Exemple'] = '/2/999'; // French
582
        $iri_long_arcs['Ejemplo'] = '/2/999'; // Spanish
583
        $iri_long_arcs["\u{0627}\u{0644}\u{0645}\u{062B}\u{0627}\u{0644}"] = '/2/999'; // Arabic
584
        $iri_long_arcs["\u{8303}\u{4F8B}"] = '/2/999'; // Chinese
585
        $iri_long_arcs["\u{041F}\u{0440}\u{0438}\u{043C}\u{0435}\u{0440}"] = '/2/999'; // Russian
586
        $iri_long_arcs["\u{C608}\u{C81C}"] = '/2/999'; // Korean
587
        $iri_long_arcs["\u{4F8B}"] = '/2/999'; // Japanese
588
        $iri_long_arcs['Beispiel'] = '/2/999'; // German
589
        return $iri_long_arcs;
590
}
591
 
592
/**
593
 * Tries to shorten/simplify an IRI by applying "long arcs", e.g. /2/999/123 -> /Example/123 .
594
 * @author  Daniel Marschall, ViaThinkSoft
595
 * @version 2014-12-28
596
 * @param   $iri (string)<br />
70 daniel-mar 597
 *              An OID in OID-IRI notation, e.g. /Example/test
2 daniel-mar 598
 * @return  (string) The modified IRI.
599
 **/
600
function iri_add_longarcs($iri) {
601
        $iri_long_arcs = iri_get_long_arcs();
602
 
603
        // TODO: $iri valid?
604
 
605
        $ary = explode('/', $iri);
606
 
607
        $ary_number_iri = $ary;
608
        if ($ary_number_iri[1] == 'Joint-ISO-ITU-T') $ary_number_iri[1] = '2';
609
        /*
610
        if ($ary_number_iri[1] == '2') {
611
                // TODO: /2/Example -> /2/999 -> /Example
612
        } else {
613
                // Currently, only long arcs inside .2 are defined
614
                // return $iri;
615
        }
616
        */
617
        $number_iri = implode('/', $ary_number_iri);
618
 
619
        foreach ($iri_long_arcs as $cur_longarc => $cur_iri) {
620
                // TODO: $cur_iri valid?
621
 
622
                if (strpos($number_iri.'/', $cur_iri.'/') === 0) {
623
                        $cnt = substr_count($cur_iri, '/');
624
                        for ($i=1; $i<$cnt; $i++) {
625
                                array_shift($ary);
626
                        }
627
                        $ary[0] = '';
628
                        $ary[1] = $cur_longarc;
629
                        $iri = implode('/', $ary);
630
                        break;
631
                }
632
        }
633
 
634
        return $iri;
635
}
636
 
637
# === FUNCTIONS FOR OIDS IN ASN.1 NOTATION ===
638
 
639
/**
640
 * Checks if an ASN.1 identifier is valid.
641
 * @author  Daniel Marschall, ViaThinkSoft
642
 * @version 2014-12-09
643
 * @param   $id (string)<br />
644
 *              An ASN.1 identifier, e.g. "example". Not "example(99)" or "99" and not a path like "{ 2 999 }"
645
 *              Note: Use asn1_path_valid() for validating a whole ASN.1 notation path.
646
 * @return  (bool) true, if the identifier is valid: It begins with an lowercase letter and contains only 0-9, a-z, A-Z and "-"
647
 **/
648
# TODO: umbenennen in asn1_alpha_id_valid
649
function oid_id_is_valid($id) {
650
        return preg_match('/^([a-z][a-zA-Z0-9-]*)$/', $id);
651
}
652
 
653
/**
654
 * Checks if the ASN.1 notation of an OID is valid.
655
 * This function does not tolerate leading zeros.
656
 * This function will fail (return false) if there are unresolved symbols, e.g. {iso test} is not valid while { iso 123 } is valid.
657
 * @author  Daniel Marschall, ViaThinkSoft
658
 * @version 2014-12-17
659
 * @param   $asn (string)<br />
660
 *              An OID in ASN.1 notation.
661
 * @return  (bools) true if the identifier is valid.
662
 **/
663
function asn1_path_valid($asn1) {
664
        return asn1_to_dot($asn1) != false;
665
}
666
 
667
/**
668
 * Returns an array of standardized ASN.1 alphanumeric identifiers which do not require a numeric identifier, e.g. { 2 example }
50 daniel-mar 669
 * The array has the form '0.0.a' -> '0.0.1'
2 daniel-mar 670
 * @author  Daniel Marschall, ViaThinkSoft
50 daniel-mar 671
 * @version 2019-03-25
2 daniel-mar 672
 * @see http://www.oid-info.com/name-forms.htm
50 daniel-mar 673
 * @return  (array) Associative array of standardized ASN.1 alphanumeric identifiers
2 daniel-mar 674
 **/
675
function asn1_get_standardized_array() {
676
 
677
        // Taken from oid-info.com
678
        // http://www.oid-info.com/name-forms.htm
679
        $standardized = array();
680
        $standardized['itu-t'] = '0';
681
        $standardized['ccitt'] = '0';
682
        $standardized['iso'] = '1';
683
        $standardized['joint-iso-itu-t'] = '2';
684
        $standardized['joint-iso-ccitt'] = '2';
685
        $standardized['0.recommendation'] = '0.0';
50 daniel-mar 686
        $standardized['0.0.a'] = '0.0.1';
687
        $standardized['0.0.b'] = '0.0.2';
688
        $standardized['0.0.c'] = '0.0.3';
689
        $standardized['0.0.d'] = '0.0.4';
690
        $standardized['0.0.e'] = '0.0.5';
691
        $standardized['0.0.f'] = '0.0.6';
692
        $standardized['0.0.g'] = '0.0.7';
693
        $standardized['0.0.h'] = '0.0.8';
694
        $standardized['0.0.i'] = '0.0.9';
695
        $standardized['0.0.j'] = '0.0.10';
696
        $standardized['0.0.k'] = '0.0.11';
697
        $standardized['0.0.l'] = '0.0.12';
698
        $standardized['0.0.m'] = '0.0.13';
699
        $standardized['0.0.n'] = '0.0.14';
700
        $standardized['0.0.o'] = '0.0.15';
701
        $standardized['0.0.p'] = '0.0.16';
702
        $standardized['0.0.q'] = '0.0.17';
703
        $standardized['0.0.r'] = '0.0.18';
704
        $standardized['0.0.s'] = '0.0.19';
705
        $standardized['0.0.t'] = '0.0.20';
706
        $standardized['0.0.u'] = '0.0.21';
707
        $standardized['0.0.v'] = '0.0.22';
708
        $standardized['0.0.w'] = '0.0.23'; // actually, this OID does not exist
709
        $standardized['0.0.x'] = '0.0.24';
710
        $standardized['0.0.y'] = '0.0.25';
711
        $standardized['0.0.z'] = '0.0.26';
2 daniel-mar 712
        $standardized['0.question'] = '0.1';
713
        $standardized['0.administration'] = '0.2';
714
        $standardized['0.network-operator'] = '0.3';
715
        $standardized['0.identified-organization'] = '0.4';
716
        $standardized['1.standard'] = '1.0';
717
        $standardized['1.registration-authority'] = '1.1';
718
        $standardized['1.member-body'] = '1.2';
719
        $standardized['1.identified-organization'] = '1.3';
720
        return $standardized;
721
}
722
 
723
/**
724
 * Converts an OID in ASN.1 notation into an OID in dot notation and tries to resolve well-known identifiers.<br />
725
 * e.g. {joint-iso-itu-t(2) example(999) 1 2 3} --> 2.999.1.2.3<br />
726
 * e.g. {iso 3} --> 1.3
727
 * This function does not tolerate leading zeros.
728
 * This function will fail (return false) if there are unresolved symbols, e.g. {iso test} will not be resolved to 1.test
729
 * @author  Daniel Marschall, ViaThinkSoft
730
 * @version 2014-12-17
731
 * @param   $asn (string)<br />
732
 *              An OID in ASN.1 notation.
733
 * @return  (string) An OID in dot notation without leading dot or false if the path is invalid.
734
 **/
735
function asn1_to_dot($asn) {
736
        $standardized = asn1_get_standardized_array();
737
 
738
        // Clean up
739
        $asn = preg_replace('@^\\{(.+)\\}$@', '\\1', $asn, -1, $count);
740
        if ($count == 0) return false; // { and } are required. The asn.1 path will NOT be trimmed by this function
741
 
742
        // If identifier is set, apply it (no check if it overrides a standardized identifier)
743
        $asn = preg_replace('|\s*([a-z][a-zA-Z0-9-]*)\s*\((\d+)\)|', ' \\2', $asn);
744
        $asn = trim($asn);
745
 
746
        // Set dots
747
        $asn = preg_replace('|\s+|', '.', $asn);
748
 
749
        // Apply standardized identifiers (case sensitive)
750
        $asn .= '.';
751
        foreach ($standardized as $s => $r) {
752
                $asn = preg_replace("|^$s|", $r, $asn);
753
        }
754
        $asn = substr($asn, 0, strlen($asn)-1);
755
 
756
        // Check if all numbers are OK
757
        // -> every arc must be resolved
758
        // -> numeric arcs must not have a leading zero
759
        // -> invalid stuff will be recognized, e.g. a "(1)" without an identifier in front of it
760
        $ary = explode('.', $asn);
761
        foreach ($ary as $a) {
762
                if (!preg_match('@^(0|([1-9]\\d*))$@', $a, $m)) return false;
763
        }
764
 
765
        return $asn;
766
}
767
 
768
/*
769
assert(asn1_to_dot('{2 999 (1)}') == false);
770
assert(asn1_to_dot('{2 999 test}') == false);
771
assert(asn1_to_dot('{2 999 1}') == '2.999.1');
772
assert(asn1_to_dot(' {2 999 1} ') == false);
773
assert(asn1_to_dot('2 999 1') == false);
774
assert(asn1_to_dot('{2 999 01}') == false);
775
assert(asn1_to_dot('{  0   question 123  }') == '0.1.123');
776
assert(asn1_to_dot('{  iso  }') == '1');
777
assert(asn1_to_dot('{  iso(1)  }') == '1');
778
assert(asn1_to_dot('{  iso(2)  }') == '2');
779
assert(asn1_to_dot('{  iso 3 }') == '1.3');
780
*/
781
 
782
/**
783
 * "Soft corrects" an invalid ASN.1 identifier.<br />
784
 * Attention, by "soft correcting" the ID, it is not authoritative anymore, and might not be able to be resolved by ORS.
785
 * @author  Daniel Marschall, ViaThinkSoft
786
 * @version 2014-12-09
787
 * @param   $id (string)<br />
788
 *              An ASN.1 identifier.
789
 * @param   $append_id_prefix (bool)<br />
790
 *              true (default): If the identifier doesn't start with a-Z, the problem will be solved by prepending "id-" to the identifier.<br />
791
 *              false: If the identifier doesn't start with a-Z, then the problem cannot be solved (method returns empty string).
792
 * @return  (string) The "soft corrected" ASN.1 identifier.<br />
793
 *              Invalid characters will be removed.<br />
794
 *              Uncorrectable start elements (0-9 or "-") will be either removed or solved by prepending "id-" (see <code>$append_id_prefix</code>)<br />
795
 *              If the identifier begins with an upper case letter, the letter will be converted into lower case.
796
 **/
797
function oid_soft_correct_id($id, $append_id_prefix = true) {
798
        // Convert "_" to "-"
799
        $id = str_replace('_', '-', $id);
800
 
801
        // Remove invalid characters
802
        $id = preg_replace('/[^a-zA-Z0-9-]+/', '', $id);
803
 
804
        // Remove uncorrectable start elements (0-9 or "-")
805
        if ($append_id_prefix) {
806
                $id = preg_replace('/^([^a-zA-Z]+)/', 'id-$1', $id);
807
        } else {
808
                $id = preg_replace('/^([^a-zA-Z]+)/', '', $id);
809
        }
810
 
811
        // "Correct" upper case beginning letter by converting it to lower case
812
        if (preg_match('/^[A-Z]/', $id)) {
813
                $id = strtolower($id[0]) . substr($id, 1);
814
        }
815
 
816
        return $id;
817
}
818