File Coverage

blib/lib/Data/Radius/DictionaryParser.pm
Criterion Covered Total %
statement 110 124 88.7
branch 39 50 78.0
condition 8 13 61.5
subroutine 14 14 100.0
pod 0 4 0.0
total 171 205 83.4


line stmt bran cond sub pod time code
1             package Data::Radius::DictionaryParser;
2              
3 7     7   99 use v5.10;
  7         23  
4 7     7   39 use strict;
  7         12  
  7         227  
5 7     7   30 use warnings;
  7         12  
  7         393  
6              
7 7     7   1607 use IO::File ();
  7         19416  
  7         174  
8 7     7   40 use File::Spec ();
  7         22  
  7         129  
9 7     7   31 use File::Basename ();
  7         12  
  7         190  
10              
11 7     7   36 use Digest::MD5 qw(md5_hex);
  7         13  
  7         10273  
12              
13             # parser state
14             my $begin_vendor = undef;
15             my $begin_tlv = undef;
16             # map id to name, {vendor => {id => name}}
17             my %dict_id = ();
18             # map name to id
19             my %dict_attr = ();
20             my %dict_const_name = ();
21             my %dict_const_value = ();
22             my %dict_vendor_name = ();
23             my %dict_vendor_id = ();
24              
25             my %included_files = ();
26              
27             sub new {
28 9     9 0 24 my $class = shift;
29 9         39 cleanup();
30 9         56 return bless {}, $class;
31             }
32              
33             sub _create_dict_from_current_state {
34 9     9   20 my ($self) = @_;
35              
36 9         7975 return Data::Radius::Dictionary->new(
37             attr_id => { %dict_id },
38             attr_name => { %dict_attr },
39             const_name => { %dict_const_name },
40             const_value => { %dict_const_value },
41             vnd_name => { %dict_vendor_name },
42             vnd_id => { %dict_vendor_id },
43             );
44             }
45              
46             sub parse_str_array {
47 1     1 0 2 my ($self, $str_array) = @_;
48              
49 1         2 my $synthetic_fname = md5_hex( @{$str_array} );
  1         21  
50 1 50       5 return if ( $included_files{$synthetic_fname} );
51 1         2 $included_files{$synthetic_fname} = 1;
52              
53             # since it's not file on disk, all $INCLUDEs are built relative to CWD
54 1   50     4 my $include_dir = $ENV{PWD} // '/';
55              
56 1         2 for my $line ( @{$str_array} ) {
  1         3  
57 2         4 $self->_parse_line($line, $include_dir);
58             }
59              
60 1         4 return $self->_create_dict_from_current_state;
61             }
62              
63             sub parse_file {
64 8     8 0 20 my ($self, $file) = @_;
65 8         43 $self->_load_file($file);
66 8         43 return $self->_create_dict_from_current_state;
67             }
68              
69             sub _load_file {
70 173     173   395 my ($self, $file) = @_;
71              
72 173 50       518 return undef if ( $included_files{$file} );
73              
74 173   33     1018 my $fh = IO::File->new($file, 'r')
75             || warn sprintf('Failed to open file "%s": %s', $file, $!);
76              
77 173         19965 $included_files{$file} = 1;
78              
79             # INCLUDEs must be treated relatively to current file
80 173         6297 my $include_dir = File::Basename::dirname($file);
81              
82 173         6231 while ( my $line = $fh->getline ) {
83 24376         42702 $self->_parse_line($line, $include_dir);
84             }
85              
86 173         1105 $fh->close;
87              
88 173         3616 return 1;
89             }
90              
91             sub cleanup {
92 9     9 0 24 my ($self) = @_;
93              
94 9         21 $begin_vendor = undef;
95 9         18 $begin_tlv = undef;
96              
97 9         31 %dict_id = ();
98 9         433 %dict_attr = ();
99 9         44 %dict_const_name = ();
100 9         31 %dict_const_value = ();
101 9         23 %dict_vendor_name = ();
102 9         20 %dict_vendor_id = ();
103 9         29 %included_files = ();
104              
105 9         34 return undef;
106             }
107              
108             sub _parse_line {
109 24378     24378   40918 my ($self, $line, $include_dir) = @_;
110 24378   33     39738 $include_dir //= $ENV{PWD};
111              
112 24378         33170 my ($cmd, $name, $id, $type, $vendor, $has_tag, $has_options, $encrypt);
113              
114 24378         41484 $line =~ s/#.*$//;
115 24378 100       66538 return undef if ( $line =~ /^\s*$/ );
116 19148         27283 chomp $line;
117              
118 19148         61216 ($cmd, $name, $id, $type, $vendor) = split(/\s+/, $line);
119 19148         30408 $cmd = lc($cmd);
120 19148         24044 $has_options = 0;
121 19148         23061 $has_tag = 0;
122 19148         23741 $encrypt = undef;
123              
124 19148 100       36026 if ($cmd eq 'attribute') {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
125             # 'vendor' part can be an options - in FreeRADIUS dictionary format
126 9006 100       15376 if ($vendor) {
127             # there could be combination of both options:
128 2466 100       4876 if ($vendor =~ /has_tag/) {
129 273         344 $has_tag = 1;
130 273         365 $has_options = 1;
131             }
132 2466 100       4598 if ($vendor =~ /encrypt=(\d)/) {
133             #TODO encryption methods not supported now
134 140         327 $encrypt = $1;
135 140         199 $has_options = 1;
136             }
137              
138 2466 100       4017 if ($has_options) {
139 406         556 $vendor = undef;
140             }
141             }
142              
143 9006   100     25871 $vendor ||= $begin_vendor;
144              
145 9006 50       18299 if (exists $dict_attr{ $name }) {
146 0         0 warn "Duplicated attribute name $name";
147             }
148              
149 9006         40504 my $a_info = {
150             id => $id,
151             name => $name,
152             type => $type,
153             vendor => $vendor,
154             has_tag => $has_tag,
155             encrypt => $encrypt,
156             };
157              
158 9006         21842 $dict_attr{ $name } = $a_info;
159              
160 9006 100       13628 if ($begin_tlv) {
161 371         715 $a_info->{parent} = $begin_tlv;
162              
163 371         576 my $parent = $dict_attr{ $begin_tlv };
164 371         745 $parent->{tlv_attr_name}{ $name } = $a_info;
165 371         888 $parent->{tlv_attr_id}{ $id } = $a_info;
166             }
167             else {
168 8635   100     24416 $dict_id{ $vendor // '' }{ $id } = $a_info;
169             }
170             }
171             elsif ($cmd eq 'value') {
172             # VALUE NAS-Port-Type Ethernet 15
173 9646         15453 my ($v_name, $v_val) = ($id, $type);
174              
175 9646 50       18205 if (! exists $dict_attr{ $name }) {
176 0         0 warn "Value for unknown attribute $name";
177 0         0 next;
178             }
179              
180 9646         22376 $dict_const_name{$name}{$v_val} = $v_name;
181 9646         24351 $dict_const_value{$name}{$v_name} = $v_val;
182             }
183             elsif ($cmd eq 'vendor') {
184             # VENDOR Mikrotik 14988
185 121         404 $dict_vendor_name{ $name } = $id;
186 121         344 $dict_vendor_id{ $id } = $name;
187             }
188             elsif ($cmd eq 'begin-vendor') {
189             # BEGIN-VENDOR Huawei
190 63 50       178 if (! exists $dict_vendor_name{ $name }) {
191 0         0 warn "BEGIN-VENDOR $name - vendor id is unknown";
192             }
193             # set default vendor for all attributes below
194 63         112 $begin_vendor = $name;
195             }
196             elsif ($cmd eq 'end-vendor') {
197             # END-VENDOR Laurel
198 63 50       175 if (! $begin_vendor) {
199 0         0 warn "END-VENDOR found without BEGIN-VENDOR";
200 0         0 next;
201             }
202 63         107 $begin_vendor = undef;
203             }
204             elsif ($cmd eq 'begin-tlv') {
205 42 50       108 if ($begin_tlv) {
206             # no support for 2nd level
207 0         0 warn "Nested BEGIN-TLV found";
208             }
209              
210             # BEGIN-TLV WiMAX-PPAC
211             # must be defined attribute with type 'tlv' first
212 42 50       129 if (! exists $dict_attr{ $name }) {
213 0         0 warn "Begin-tlv for unknown attribute $name";
214 0         0 next;
215             }
216 42 50       113 if ($dict_attr{ $name }{type} ne 'tlv') {
217 0         0 warn "Begin-tlv for attribute $name of non-tlv type";
218 0         0 next;
219             }
220 42         65 $begin_tlv = $name;
221             }
222             elsif ($cmd eq 'end-tlv') {
223             # END-TLV WiMAX-PPAC
224 42 50       180 if (! $begin_tlv) {
225 0         0 warn "END-TLV found without BEGIN-TLV";
226 0         0 next;
227             }
228 42         115 $begin_tlv = undef;
229             }
230             elsif ($cmd eq '$include') {
231             # $INCLUDE mikrotik
232             # $INCLUDE /absolute/path/to/mikrotik
233              
234             # clear modifiers
235 165         316 ($begin_vendor, $begin_tlv) = ();
236              
237 165 100       1782 if (File::Spec->file_name_is_absolute($name)) {
238 4         9 $self->_load_file($name);
239             }
240             else {
241 161         2075 $self->_load_file( File::Spec->catfile($include_dir, $name) );
242             }
243             }
244             else {
245 0         0 warn "Unknown command: $cmd";
246             }
247              
248 19148         70358 return undef;
249             }
250              
251              
252             1;