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         9  
  5         162  
39 5     5   24 use warnings FATAL => 'all';
  5         11  
  5         164  
40 5     5   26 use experimental 'signatures';
  5         10  
  5         25  
41              
42 5     5   628 use constant INTERNAL_KEY_PREFIX => 'int_';
  5         16  
  5         333  
43              
44 5     5   32 use base 'Exporter';
  5         60  
  5         4718  
45             our @EXPORT = qw(parse_raw_wg_config INTERNAL_KEY_PREFIX);
46              
47             our $VERSION = "0.3.3";
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 43     43 1 68 sub parse_raw_wg_config($file_content, $on_every_value, $on_new_section, $skip = 0, $wg_meta_prefix = '#+', $wg_disabled_prefix = '#-') {
  43         74  
  43         64  
  43         70  
  43         73  
  43         70  
  43         70  
  43         58  
97 43         99 my $IDENT_KEY = '';
98 43         65 my $IS_ACTIVE_COUNTER = 0;
99 43         62 my $IS_ROOT = 1;
100 43         69 my $SECTION_TYPE = 'Root';
101 43         90 my $IS_WG_META = 0;
102              
103 43         82 my $parsed_config = {};
104 43         75 my @peer_order;
105             my @root_order;
106              
107 43         73 my $section_data = {};
108 43         65 my @section_order;
109 43         68 my $generic_autokey = 0;
110 43         64 my $line_count = 0;
111              
112             my $section_handler = sub {
113 160 100   160   310 if ($IS_ROOT) {
114 43         96 $parsed_config = $section_data;
115 43         106 $section_data = {};
116             }
117             else {
118 117 100       257 my $is_disabled = $IS_ACTIVE_COUNTER == 1 ? 1 : 0;
119 117         218 my $identifier = &{$on_new_section}($section_data->{$IDENT_KEY}, $SECTION_TYPE, $is_disabled);
  117         275  
120 117 50       290 die "`$identifier` is already present" if exists($parsed_config->{$identifier});
121 117         343 $section_data->{INTERNAL_KEY_PREFIX . 'order'} = [ @section_order ];
122 117         226 $section_data->{'disabled'} = $is_disabled;
123 117         247 $section_data->{INTERNAL_KEY_PREFIX . 'type'} = $SECTION_TYPE;
124 117         739 $parsed_config->{$identifier} = { %$section_data };
125 117         270 push @peer_order, $identifier;
126 117         380 $section_data = {};
127             }
128              
129 160         344 @section_order = ();
130 160         273 $IDENT_KEY = 'PublicKey';
131 160         223 $IS_ACTIVE_COUNTER--;
132 160         248 $IS_ROOT = 0;
133 43         209 };
134              
135 43         344 for my $line (split "\n", $file_content) {
136 733         1143 $line_count++;
137 733 50       1335 next if $line_count <= $skip;
138              
139             # Strip-of any leading or trailing whitespace
140 733         3798 $line =~ s/^\s+|\s+$//g;
141              
142 733 100       1598 if ((substr $line, 0, 2) eq $wg_disabled_prefix) {
143 7         13 $line = substr $line, 2;
144 7 100       15 $IS_ACTIVE_COUNTER = 2 if $IS_ACTIVE_COUNTER != 1;
145             }
146 733 100       1370 if ((substr $line, 0, 2) eq $wg_meta_prefix) {
147             # Also slice-off wg-meta prefixes
148 89         280 $line = substr $line, 2;
149 89         139 $IS_WG_META = 1;
150             }
151             else {
152 644         892 $IS_WG_META = 0;
153             }
154              
155             # skip empty lines
156 733 100       1369 next unless $line;
157              
158             # Simply decide if we are in an interface or peer section
159 635 100       1309 if ((substr $line, 0, 11) eq '[Interface]') {
160 43         107 &$section_handler();
161 43         69 $SECTION_TYPE = 'Interface';
162 43         68 $IDENT_KEY = 'PrivateKey';
163 43         90 next;
164             }
165 592 100       1185 if ((substr $line, 0, 6) eq '[Peer]') {
166 74         214 &$section_handler();
167 74         105 $SECTION_TYPE = 'Peer';
168 74         109 $IDENT_KEY = 'PublicKey';
169 74         140 next;
170             }
171 518         798 my ($definitive_key, $definitive_value, $discard);
172 518 100       905 unless ((substr $line, 0, 1) eq '#') {
173 500         943 my ($raw_key, $raw_value) = _split_and_trim($line, '=');
174 500         1395 ($definitive_key, $definitive_value, $discard) = &$on_every_value($raw_key, $raw_value, $IS_WG_META);
175 500 100       1121 next if $discard == 1;
176              
177             # Update identity key if changed
178 483 100       1035 $IDENT_KEY = $definitive_key if $raw_key eq $IDENT_KEY;
179             }
180             else {
181             # Handle "normal" comments
182 18         52 $definitive_key = "comment_$generic_autokey";
183 18         41 $definitive_value = $line;
184             }
185 501         987 $section_data->{$definitive_key} = $definitive_value;
186 501 50       1095 $IS_ROOT ? push @root_order, $definitive_key : push @section_order, $definitive_key;
187 501         950 $generic_autokey++;
188             }
189             # and finalize
190 43         167 &$section_handler();
191 43         82 $parsed_config->{INTERNAL_KEY_PREFIX . 'section_order'} = \@peer_order;
192 43         84 $parsed_config->{INTERNAL_KEY_PREFIX . 'root_order'} = \@root_order;
193              
194 43         378 return $parsed_config;
195             }
196              
197 500     500   691 sub _split_and_trim($line, $separator) {
  500         749  
  500         739  
  500         643  
198 500         4466 return map {s/^\s+|\s+$//g;
  1000         5211  
199 1000         2738 $_} split $separator, $line, 2;
200             }
201              
202             1;