File Coverage

lib/Wireguard/WGmeta/Parser/Conf.pm
Criterion Covered Total %
statement 96 96 100.0
branch 25 28 89.2
condition n/a
subroutine 8 8 100.0
pod 1 1 100.0
total 130 133 97.7


line stmt bran cond sub pod time code
1             =pod
2              
3             =head1 NAME
4              
5             WGmeta::Parser::Conf - Parser for Wireguard configurations
6              
7             =head1 SYNOPSIS
8              
9             use Wireguard::WGmeta::Parser::Conf;
10             use Wireguard::WGmeta::Util;
11              
12             # Parse a wireguard configuration file
13             my $config_contents = read_file('/path/to/config.conf', 'interface_name');
14              
15             # Define callbacks
16             my $on_every_value_callback = sub($attribute, $value, $is_wg_meta){
17             # do you magic
18             return $attribute, $value;
19             };
20             my $on_every_section_callback = sub($identifier, $section_type, $is_disabled){
21             # do you magic
22             return $identifier;
23             };
24              
25             # And finally parse the configuration
26             my $parsed_config = parse_raw_wg_config($config_contents, $on_every_value_callback, $on_every_section_callback);
27              
28              
29             =head1 DESCRIPTION
30              
31             Parser for Wireguard I<.conf> files with support for custom attributes. A possible implementation is present in L.
32              
33             =head1 METHODS
34              
35             =cut
36              
37             package Wireguard::WGmeta::Parser::Conf;
38 5     5   33 use strict;
  5         10  
  5         156  
39 5     5   26 use warnings FATAL => 'all';
  5         8  
  5         174  
40 5     5   34 use experimental 'signatures';
  5         10  
  5         26  
41              
42 5     5   499 use constant INTERNAL_KEY_PREFIX => 'int_';
  5         11  
  5         323  
43              
44 5     5   32 use base 'Exporter';
  5         89  
  5         4619  
45             our @EXPORT = qw(parse_raw_wg_config INTERNAL_KEY_PREFIX);
46              
47             our $VERSION = "0.3.4";
48              
49             =head3 parse_raw_wg_config($file_content, $on_every_value, $on_new_section [, $skip, $wg_meta_prefix, $wg_disabled_prefix])
50              
51             Parses a Wireguard configuration
52              
53             =over 1
54              
55             =item *
56              
57             C<$file_content> Content of Wireguard configuration. Warning, if have to ensure that its a valid file!
58              
59             =item *
60              
61             C<$on_every_value> A reference to a callback function, fired at every key/value pair. Expected signature:
62              
63             my $on_every_value_callback = sub($attribute, $value, $is_wg_meta){
64             # do you magic
65             return $attribute, $value;
66             };
67              
68             =item *
69              
70             C<$on_new_section> Callback for every section. Expected signature:
71              
72             my $on_every_section_callback = sub($identifier, $section_type, $is_disabled){
73             # do you magic
74             return $identifier;
75             };
76              
77             =item *
78              
79             C<[$skip = 0]> When you want to skip some lines at the beginning
80              
81             =item *
82              
83             C<[$wg_meta_prefix = '#+']> wg-meta prefix. Must start with '#' or ';'
84              
85             =item *
86              
87             C<[$disabled_prefix = '#-']> disabled prefix. Must start with '#' or ';'
88              
89             =back
90              
91             B
92              
93             A reference to a hash similar as described in L.
94              
95             =cut
96 42     42 1 68 sub parse_raw_wg_config($file_content, $on_every_value, $on_new_section, $skip = 0, $wg_meta_prefix = '#+', $wg_disabled_prefix = '#-') {
  42         64  
  42         64  
  42         61  
  42         62  
  42         64  
  42         62  
  42         65  
97 42         62 my $IDENT_KEY = '';
98 42         61 my $IS_ACTIVE_COUNTER = 0;
99 42         59 my $IS_ROOT = 1;
100 42         68 my $SECTION_TYPE = 'Root';
101 42         59 my $IS_WG_META = 0;
102              
103 42         74 my $parsed_config = {};
104 42         65 my @peer_order;
105             my @root_order;
106              
107 42         79 my $section_data = {};
108 42         66 my @section_order;
109 42         68 my $generic_autokey = 0;
110 42         59 my $line_count = 0;
111              
112             my $section_handler = sub {
113 157 100   157   303 if ($IS_ROOT) {
114 42         84 $parsed_config = $section_data;
115 42         94 $section_data = {};
116             }
117             else {
118 115 100       212 my $is_disabled = $IS_ACTIVE_COUNTER == 1 ? 1 : 0;
119 115         201 my $identifier = &{$on_new_section}($section_data->{$IDENT_KEY}, $SECTION_TYPE, $is_disabled);
  115         259  
120 115 50       304 die "`$identifier` is already present" if exists($parsed_config->{$identifier});
121 115         322 $section_data->{INTERNAL_KEY_PREFIX . 'order'} = [ @section_order ];
122 115         199 $section_data->{'disabled'} = $is_disabled;
123 115         209 $section_data->{INTERNAL_KEY_PREFIX . 'type'} = $SECTION_TYPE;
124 115         641 $parsed_config->{$identifier} = { %$section_data };
125 115         251 push @peer_order, $identifier;
126 115         386 $section_data = {};
127             }
128              
129 157         316 @section_order = ();
130 157         285 $IDENT_KEY = 'PublicKey';
131 157         217 $IS_ACTIVE_COUNTER--;
132 157         245 $IS_ROOT = 0;
133 42         176 };
134              
135 42         322 for my $line (split "\n", $file_content) {
136 736         966 $line_count++;
137 736 50       1357 next if $line_count <= $skip;
138              
139             # Strip-of any leading or trailing whitespace
140 736         3608 $line =~ s/^\s+|\s+$//g;
141              
142 736 100       1639 if ((substr $line, 0, 2) eq $wg_disabled_prefix) {
143 7         16 $line = substr $line, 2;
144 7 100       14 $IS_ACTIVE_COUNTER = 2 if $IS_ACTIVE_COUNTER != 1;
145             }
146 736 100       1338 if ((substr $line, 0, 2) eq $wg_meta_prefix) {
147             # Also slice-off wg-meta prefixes
148 105         255 $line = substr $line, 2;
149 105         153 $IS_WG_META = 1;
150             }
151             else {
152 631         860 $IS_WG_META = 0;
153             }
154              
155             # skip empty lines
156 736 100       1305 next unless $line;
157              
158             # Simply decide if we are in an interface or peer section
159 640 100       1235 if ((substr $line, 0, 11) eq '[Interface]') {
160 42         105 &$section_handler();
161 42         56 $SECTION_TYPE = 'Interface';
162 42         71 $IDENT_KEY = 'PrivateKey';
163 42         85 next;
164             }
165 598 100       1099 if ((substr $line, 0, 6) eq '[Peer]') {
166 73         195 &$section_handler();
167 73         98 $SECTION_TYPE = 'Peer';
168 73         120 $IDENT_KEY = 'PublicKey';
169 73         138 next;
170             }
171 525         790 my ($definitive_key, $definitive_value, $discard);
172 525 100       867 unless ((substr $line, 0, 1) eq '#') {
173 508         879 my ($raw_key, $raw_value) = _split_and_trim($line, '=');
174 508         1353 ($definitive_key, $definitive_value, $discard) = &$on_every_value($raw_key, $raw_value, $IS_WG_META);
175 508 100       1149 next if $discard == 1;
176              
177             # Update identity key if changed
178 491 100       1039 $IDENT_KEY = $definitive_key if $raw_key eq $IDENT_KEY;
179             }
180             else {
181             # Handle "normal" comments
182 17         51 $definitive_key = INTERNAL_KEY_PREFIX . "_comment_$generic_autokey";
183 17         30 $definitive_value = $line;
184             }
185 508         927 $section_data->{$definitive_key} = $definitive_value;
186 508 50       1042 $IS_ROOT ? push @root_order, $definitive_key : push @section_order, $definitive_key;
187 508         960 $generic_autokey++;
188             }
189             # and finalize
190 42         162 &$section_handler();
191 42         87 $parsed_config->{INTERNAL_KEY_PREFIX . 'section_order'} = \@peer_order;
192 42         79 $parsed_config->{INTERNAL_KEY_PREFIX . 'root_order'} = \@root_order;
193              
194 42         346 return $parsed_config;
195             }
196              
197 508     508   661 sub _split_and_trim($line, $separator) {
  508         726  
  508         658  
  508         642  
198 508         4175 return map {s/^\s+|\s+$//g;
  1016         4951  
199 1016         2675 $_} split $separator, $line, 2;
200             }
201              
202             1;