line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#+############################################################################## |
2
|
|
|
|
|
|
|
# # |
3
|
|
|
|
|
|
|
# File: Messaging/Message.pm # |
4
|
|
|
|
|
|
|
# # |
5
|
|
|
|
|
|
|
# Description: abstraction of a message # |
6
|
|
|
|
|
|
|
# # |
7
|
|
|
|
|
|
|
#-############################################################################## |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
# |
10
|
|
|
|
|
|
|
# module definition |
11
|
|
|
|
|
|
|
# |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
package Messaging::Message; |
14
|
5
|
|
|
5
|
|
508258
|
use strict; |
|
5
|
|
|
|
|
13
|
|
|
5
|
|
|
|
|
176
|
|
15
|
5
|
|
|
5
|
|
31
|
use warnings; |
|
5
|
|
|
|
|
13
|
|
|
5
|
|
|
|
|
431
|
|
16
|
|
|
|
|
|
|
our $VERSION = "1.5"; |
17
|
|
|
|
|
|
|
our $REVISION = sprintf("%d.%02d", q$Revision: 1.25 $ =~ /(\d+)\.(\d+)/); |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
# |
20
|
|
|
|
|
|
|
# used modules |
21
|
|
|
|
|
|
|
# |
22
|
|
|
|
|
|
|
|
23
|
5
|
|
|
5
|
|
3279
|
use Encode qw(encode decode FB_CROAK LEAVE_SRC); |
|
5
|
|
|
|
|
40151
|
|
|
5
|
|
|
|
|
484
|
|
24
|
5
|
|
|
5
|
|
5982
|
use JSON qw(); |
|
5
|
|
|
|
|
92607
|
|
|
5
|
|
|
|
|
179
|
|
25
|
5
|
|
|
5
|
|
5664
|
use MIME::Base64 qw(encode_base64 decode_base64); |
|
5
|
|
|
|
|
4305
|
|
|
5
|
|
|
|
|
499
|
|
26
|
5
|
|
|
5
|
|
4864
|
use No::Worries::Die qw(dief); |
|
5
|
|
|
|
|
149307
|
|
|
5
|
|
|
|
|
39
|
|
27
|
5
|
|
|
5
|
|
681
|
use No::Worries::Export qw(export_control); |
|
5
|
|
|
|
|
12
|
|
|
5
|
|
|
|
|
34
|
|
28
|
5
|
|
|
5
|
|
559
|
use Params::Validate qw(validate validate_pos :types); |
|
5
|
|
|
|
|
45
|
|
|
5
|
|
|
|
|
28235
|
|
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
# |
31
|
|
|
|
|
|
|
# global variables |
32
|
|
|
|
|
|
|
# |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
our( |
35
|
|
|
|
|
|
|
%_LoadedModule, # hash of successfully loaded modules |
36
|
|
|
|
|
|
|
%_CompressionModule, # known compression modules |
37
|
|
|
|
|
|
|
$_CompressionAlgos, # known compression algorithms |
38
|
|
|
|
|
|
|
$_JSON, # JSON object |
39
|
|
|
|
|
|
|
); |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
%_CompressionModule = ( |
42
|
|
|
|
|
|
|
"lz4" => "LZ4", |
43
|
|
|
|
|
|
|
"snappy" => "Snappy", |
44
|
|
|
|
|
|
|
"zlib" => "Zlib", |
45
|
|
|
|
|
|
|
); |
46
|
|
|
|
|
|
|
$_CompressionAlgos = join("|", sort(keys(%_CompressionModule))); |
47
|
|
|
|
|
|
|
$_JSON = JSON->new(); |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
#+++############################################################################ |
50
|
|
|
|
|
|
|
# # |
51
|
|
|
|
|
|
|
# helper functions # |
52
|
|
|
|
|
|
|
# # |
53
|
|
|
|
|
|
|
#---############################################################################ |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
# |
56
|
|
|
|
|
|
|
# make sure a module is loaded |
57
|
|
|
|
|
|
|
# |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
sub _require ($) { |
60
|
15
|
|
|
15
|
|
27
|
my($module) = @_; |
61
|
|
|
|
|
|
|
|
62
|
15
|
100
|
|
|
|
58
|
return if $_LoadedModule{$module}; |
63
|
2
|
|
|
|
|
156
|
eval("require $module"); ## no critic 'ProhibitStringyEval' |
64
|
2
|
50
|
|
|
|
11
|
if ($@) { |
65
|
0
|
|
|
|
|
0
|
$@ =~ s/\s+at\s.+?\sline\s+\d+\.?$//; |
66
|
0
|
|
|
|
|
0
|
dief("failed to load %s: %s", $module, $@); |
67
|
|
|
|
|
|
|
} else { |
68
|
2
|
|
|
|
|
9
|
$_LoadedModule{$module} = 1; |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
} |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
# |
73
|
|
|
|
|
|
|
# evaluate some code with fatal warnings |
74
|
|
|
|
|
|
|
# |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
sub _eval ($&;$) { |
77
|
180
|
|
|
180
|
|
7649
|
my($what, $code, $arg) = @_; |
78
|
|
|
|
|
|
|
|
79
|
180
|
|
|
|
|
209
|
eval { |
80
|
180
|
|
|
0
|
|
881
|
local $SIG{__WARN__} = sub { die($_[0]) }; |
|
0
|
|
|
|
|
0
|
|
81
|
180
|
|
|
|
|
371
|
$code->($arg); |
82
|
|
|
|
|
|
|
}; |
83
|
180
|
100
|
|
|
|
10380
|
return unless $@; |
84
|
3
|
|
|
|
|
38
|
$@ =~ s/\s+at\s.+?\sline\s+\d+\.?$//; |
85
|
3
|
|
|
|
|
13
|
dief("%s failed: %s", $what, $@); |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
# |
89
|
|
|
|
|
|
|
# helpers for body encoding and compression |
90
|
|
|
|
|
|
|
# |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
sub _maybe_base64_encode ($) { |
93
|
15
|
|
|
15
|
|
18
|
my($object) = @_; |
94
|
|
|
|
|
|
|
|
95
|
15
|
50
|
|
|
|
65
|
return unless $object->{"body"} =~ /[^\t\n\r\x20-\x7e]/; |
96
|
|
|
|
|
|
|
# only if it contains more than printable ASCII characters (plus \t \n \r) |
97
|
|
|
|
|
|
|
_eval("Base64 encoding", sub { |
98
|
15
|
|
|
15
|
|
110
|
$object->{"body"} = encode_base64($object->{"body"}, ""); |
99
|
15
|
|
|
|
|
60
|
}); |
100
|
15
|
|
|
|
|
60
|
$object->{"encoding"}{"base64"}++; |
101
|
|
|
|
|
|
|
} |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
sub _maybe_utf8_encode ($) { |
104
|
3
|
|
|
3
|
|
5
|
my($object) = @_; |
105
|
3
|
|
|
|
|
4
|
my($tmp); |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
_eval("UTF-8 encoding", sub { |
108
|
3
|
|
|
3
|
|
28
|
$tmp = encode("UTF-8", $object->{"body"}, FB_CROAK|LEAVE_SRC); |
109
|
3
|
|
|
|
|
19
|
}); |
110
|
3
|
100
|
|
|
|
18
|
return if $tmp eq $object->{"body"}; |
111
|
1
|
|
|
|
|
3
|
$object->{"body"} = $tmp; |
112
|
1
|
|
|
|
|
4
|
$object->{"encoding"}{"utf8"}++; |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
sub _do_compress ($$) { |
116
|
6
|
|
|
6
|
|
11
|
my($object, $algo) = @_; |
117
|
6
|
|
|
|
|
10
|
my($compress, $tmp); |
118
|
|
|
|
|
|
|
|
119
|
6
|
|
|
|
|
7
|
$compress = \&{"Compress::$_CompressionModule{$algo}::compress"}; |
|
6
|
|
|
|
|
30
|
|
120
|
|
|
|
|
|
|
_eval("$_CompressionModule{$algo} compression", sub { |
121
|
6
|
|
|
6
|
|
21
|
$tmp = $compress->(\$object->{"body"}); |
122
|
6
|
|
|
|
|
35
|
}); |
123
|
6
|
|
|
|
|
22
|
$object->{"body"} = $tmp; |
124
|
6
|
|
|
|
|
26
|
$object->{"encoding"}{$algo}++; |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
#+++############################################################################ |
128
|
|
|
|
|
|
|
# # |
129
|
|
|
|
|
|
|
# object oriented interface # |
130
|
|
|
|
|
|
|
# # |
131
|
|
|
|
|
|
|
#---############################################################################ |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
# |
134
|
|
|
|
|
|
|
# normal constructor |
135
|
|
|
|
|
|
|
# |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
my %new_options = ( |
138
|
|
|
|
|
|
|
"header" => { |
139
|
|
|
|
|
|
|
type => HASHREF, |
140
|
|
|
|
|
|
|
callbacks => { |
141
|
|
|
|
|
|
|
"hash of strings" => |
142
|
|
|
|
|
|
|
sub { grep(!defined($_)||ref($_), values(%{$_[0]})) == 0 }, |
143
|
|
|
|
|
|
|
}, |
144
|
|
|
|
|
|
|
optional => 1, |
145
|
|
|
|
|
|
|
}, |
146
|
|
|
|
|
|
|
"body" => { |
147
|
|
|
|
|
|
|
type => SCALAR, |
148
|
|
|
|
|
|
|
optional => 1, |
149
|
|
|
|
|
|
|
}, |
150
|
|
|
|
|
|
|
"body_ref" => { |
151
|
|
|
|
|
|
|
type => SCALARREF, |
152
|
|
|
|
|
|
|
optional => 1, |
153
|
|
|
|
|
|
|
}, |
154
|
|
|
|
|
|
|
"text" => { |
155
|
|
|
|
|
|
|
type => BOOLEAN, |
156
|
|
|
|
|
|
|
optional => 1, |
157
|
|
|
|
|
|
|
}, |
158
|
|
|
|
|
|
|
); |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
sub new : method { |
161
|
222
|
|
|
222
|
1
|
99468
|
my($class, %option, $body, $self); |
162
|
|
|
|
|
|
|
|
163
|
222
|
|
|
|
|
289
|
$class = shift(@_); |
164
|
222
|
100
|
|
|
|
3299
|
%option = validate(@_, \%new_options) if @_; |
165
|
219
|
100
|
100
|
|
|
1283
|
dief("new(): options body and body_ref are mutually exclusive") |
166
|
|
|
|
|
|
|
if exists($option{"body"}) and exists($option{"body_ref"}); |
167
|
|
|
|
|
|
|
# default message |
168
|
218
|
|
|
|
|
276
|
$body = ""; |
169
|
218
|
|
|
|
|
1293
|
$self = { "header" => {}, "body_ref" => \$body, "text" => 0 }; |
170
|
|
|
|
|
|
|
# handle options |
171
|
218
|
100
|
|
|
|
541
|
$self->{"header"} = $option{"header"} if exists($option{"header"}); |
172
|
218
|
100
|
|
|
|
577
|
$self->{"body_ref"} = $option{"body_ref"} if exists($option{"body_ref"}); |
173
|
218
|
100
|
|
|
|
410
|
$self->{"body_ref"} = \$option{"body"} if exists($option{"body"}); |
174
|
218
|
100
|
|
|
|
576
|
$self->{"text"} = $option{"text"} ? 1 : 0 if exists($option{"text"}); |
|
|
100
|
|
|
|
|
|
175
|
|
|
|
|
|
|
# so far so good! |
176
|
218
|
|
|
|
|
391
|
bless($self, $class); |
177
|
218
|
|
|
|
|
933
|
return($self); |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
# |
181
|
|
|
|
|
|
|
# normal accessors |
182
|
|
|
|
|
|
|
# |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
sub header : method { |
185
|
58
|
|
|
58
|
1
|
9933
|
my($self); |
186
|
|
|
|
|
|
|
|
187
|
58
|
|
|
|
|
99
|
$self = shift(@_); |
188
|
58
|
50
|
|
|
|
396
|
return($self->{"header"}) if @_ == 0; |
189
|
0
|
|
|
|
|
0
|
validate_pos(@_, $new_options{"header"}); |
190
|
0
|
|
|
|
|
0
|
$self->{"header"} = $_[0]; |
191
|
0
|
|
|
|
|
0
|
return($self); |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
sub body_ref : method { |
195
|
2
|
|
|
2
|
1
|
260
|
my($self); |
196
|
|
|
|
|
|
|
|
197
|
2
|
|
|
|
|
3
|
$self = shift(@_); |
198
|
2
|
100
|
|
|
|
8
|
return($self->{"body_ref"}) if @_ == 0; |
199
|
1
|
50
|
33
|
|
|
10
|
validate_pos(@_, $new_options{"body_ref"}) |
|
|
|
33
|
|
|
|
|
200
|
|
|
|
|
|
|
unless @_ == 1 and defined($_[0]) and ref($_[0]) eq "SCALAR"; |
201
|
1
|
|
|
|
|
2
|
$self->{"body_ref"} = $_[0]; |
202
|
1
|
|
|
|
|
2
|
return($self); |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
sub text : method { |
206
|
74
|
|
|
74
|
1
|
1414
|
my($self); |
207
|
|
|
|
|
|
|
|
208
|
74
|
|
|
|
|
119
|
$self = shift(@_); |
209
|
74
|
100
|
|
|
|
311
|
return($self->{"text"}) if @_ == 0; |
210
|
1
|
50
|
33
|
|
|
10
|
validate_pos(@_, $new_options{"text"}) |
|
|
|
33
|
|
|
|
|
211
|
|
|
|
|
|
|
unless @_ == 1 and (not defined($_[0]) or ref($_[0]) eq ""); |
212
|
1
|
50
|
|
|
|
4
|
$self->{"text"} = $_[0] ? 1 : 0; |
213
|
1
|
|
|
|
|
2
|
return($self); |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
# |
217
|
|
|
|
|
|
|
# extra accessors |
218
|
|
|
|
|
|
|
# |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
sub header_field : method { |
221
|
82
|
|
|
82
|
1
|
7094
|
my($self); |
222
|
|
|
|
|
|
|
|
223
|
82
|
|
|
|
|
127
|
$self = shift(@_); |
224
|
82
|
100
|
66
|
|
|
739
|
if (@_ >= 1 and defined($_[0]) and ref($_[0]) eq "") { |
|
|
|
100
|
|
|
|
|
225
|
80
|
100
|
|
|
|
403
|
return($self->{"header"}{$_[0]}) if @_ == 1; |
226
|
2
|
100
|
33
|
|
|
37
|
if (@_ == 2 and defined($_[1]) and ref($_[1]) eq "") { |
|
|
|
66
|
|
|
|
|
227
|
1
|
|
|
|
|
3
|
$self->{"header"}{$_[0]} = $_[1]; |
228
|
1
|
|
|
|
|
2
|
return($self); |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
# so far so bad :-( |
232
|
3
|
|
|
|
|
665
|
validate_pos(@_, { type => SCALAR }, { type => SCALAR, optional => 1 }); |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
sub body : method { |
236
|
50
|
|
|
50
|
1
|
215
|
my($self, $body); |
237
|
|
|
|
|
|
|
|
238
|
50
|
|
|
|
|
69
|
$self = shift(@_); |
239
|
50
|
100
|
|
|
|
125
|
return(${ $self->{"body_ref"} }) if @_ == 0; |
|
49
|
|
|
|
|
193
|
|
240
|
1
|
50
|
33
|
|
|
10
|
validate_pos(@_, $new_options{"body"}) |
|
|
|
33
|
|
|
|
|
241
|
|
|
|
|
|
|
unless @_ == 1 and defined($_[0]) and ref($_[0]) eq ""; |
242
|
1
|
|
|
|
|
2
|
$body = $_[0]; # copy |
243
|
1
|
|
|
|
|
2
|
$self->{"body_ref"} = \$body; |
244
|
1
|
|
|
|
|
2
|
return($self); |
245
|
|
|
|
|
|
|
} |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
# |
248
|
|
|
|
|
|
|
# extra methods |
249
|
|
|
|
|
|
|
# |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
sub copy : method { |
252
|
7
|
|
|
7
|
1
|
6313
|
my($self, %header, $body, $copy); |
253
|
|
|
|
|
|
|
|
254
|
7
|
|
|
|
|
11
|
$self = shift(@_); |
255
|
7
|
50
|
|
|
|
13
|
validate_pos(@_) if @_; |
256
|
7
|
|
|
|
|
8
|
%header = %{ $self->{"header"} }; # copy |
|
7
|
|
|
|
|
18
|
|
257
|
7
|
|
|
|
|
8
|
$body = ${ $self->{"body_ref"} }; # copy |
|
7
|
|
|
|
|
11
|
|
258
|
7
|
|
|
|
|
22
|
$copy = { |
259
|
|
|
|
|
|
|
"header" => \%header, |
260
|
|
|
|
|
|
|
"body_ref" => \$body, |
261
|
|
|
|
|
|
|
"text" => $self->{"text"}, |
262
|
|
|
|
|
|
|
}; |
263
|
7
|
|
|
|
|
15
|
bless($copy, ref($self)); |
264
|
7
|
|
|
|
|
39
|
return($copy); |
265
|
|
|
|
|
|
|
} |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
sub size : method { |
268
|
0
|
|
|
0
|
1
|
0
|
my($self, $size, $key, $value); |
269
|
|
|
|
|
|
|
|
270
|
0
|
|
|
|
|
0
|
$self = shift(@_); |
271
|
0
|
0
|
|
|
|
0
|
validate_pos(@_) if @_; |
272
|
0
|
|
|
|
|
0
|
$size = 1 + length(${ $self->{"body_ref"} }); |
|
0
|
|
|
|
|
0
|
|
273
|
0
|
|
|
|
|
0
|
while (($key, $value) = each(%{ $self->{"header"} })) { |
|
0
|
|
|
|
|
0
|
|
274
|
0
|
|
|
|
|
0
|
$size += 2 + length($key) + length($value); |
275
|
|
|
|
|
|
|
} |
276
|
0
|
|
|
|
|
0
|
return($size); |
277
|
|
|
|
|
|
|
} |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
#+++############################################################################ |
280
|
|
|
|
|
|
|
# # |
281
|
|
|
|
|
|
|
# (de)jsonification # |
282
|
|
|
|
|
|
|
# # |
283
|
|
|
|
|
|
|
#---############################################################################ |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
# |
286
|
|
|
|
|
|
|
# jsonify (= transform into a JSON object) |
287
|
|
|
|
|
|
|
# |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
my %jsonify_options = ( |
290
|
|
|
|
|
|
|
"compression" => { |
291
|
|
|
|
|
|
|
type => SCALAR, |
292
|
|
|
|
|
|
|
regex => qr/^($_CompressionAlgos)?!?$/o, |
293
|
|
|
|
|
|
|
optional => 1, |
294
|
|
|
|
|
|
|
}, |
295
|
|
|
|
|
|
|
); |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
sub _jsonify_text ($$$$$) { |
298
|
10
|
|
|
10
|
|
18
|
my($self, $object, $algo, $force, $len) = @_; |
299
|
|
|
|
|
|
|
|
300
|
10
|
50
|
66
|
|
|
98
|
if ($algo and $force) { |
|
|
100
|
66
|
|
|
|
|
301
|
|
|
|
|
|
|
# always compress |
302
|
0
|
|
|
|
|
0
|
_maybe_utf8_encode($object); |
303
|
0
|
|
|
|
|
0
|
_do_compress($object, $algo); |
304
|
0
|
|
|
|
|
0
|
_maybe_base64_encode($object); |
305
|
|
|
|
|
|
|
} elsif ($algo and $len > 255) { |
306
|
|
|
|
|
|
|
# maybe compress |
307
|
3
|
|
|
|
|
9
|
_maybe_utf8_encode($object); |
308
|
3
|
|
|
|
|
7
|
_do_compress($object, $algo); |
309
|
3
|
|
|
|
|
9
|
_maybe_base64_encode($object); |
310
|
3
|
50
|
|
|
|
14
|
if (length($object->{"body"}) >= $len) { |
311
|
|
|
|
|
|
|
# not worth it |
312
|
0
|
|
|
|
|
0
|
$object->{"body"} = ${ $self->{"body_ref"} }; |
|
0
|
|
|
|
|
0
|
|
313
|
0
|
|
|
|
|
0
|
delete($object->{"encoding"}); |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
} else { |
316
|
|
|
|
|
|
|
# do not compress |
317
|
|
|
|
|
|
|
} |
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
sub _jsonify_binary ($$$$$) { |
321
|
12
|
|
|
12
|
|
24
|
my($self, $object, $algo, $force, $len) = @_; |
322
|
|
|
|
|
|
|
|
323
|
12
|
50
|
66
|
|
|
60
|
if ($algo and $force) { |
|
|
100
|
66
|
|
|
|
|
324
|
|
|
|
|
|
|
# always compress |
325
|
0
|
|
|
|
|
0
|
_do_compress($object, $algo); |
326
|
0
|
|
|
|
|
0
|
_maybe_base64_encode($object); |
327
|
|
|
|
|
|
|
} elsif ($algo and $len > 255) { |
328
|
|
|
|
|
|
|
# maybe compress |
329
|
3
|
100
|
|
|
|
63
|
$len *= 4/3 if $object->{"body"} =~ /[^\t\n\r\x20-\x7e]/; |
330
|
3
|
|
|
|
|
9
|
_do_compress($object, $algo); |
331
|
3
|
|
|
|
|
9
|
_maybe_base64_encode($object); |
332
|
3
|
50
|
|
|
|
13
|
if (length($object->{"body"}) >= $len) { |
333
|
|
|
|
|
|
|
# not worth it |
334
|
0
|
|
|
|
|
0
|
$object->{"body"} = ${ $self->{"body_ref"} }; |
|
0
|
|
|
|
|
0
|
|
335
|
0
|
|
|
|
|
0
|
delete($object->{"encoding"}); |
336
|
0
|
|
|
|
|
0
|
_maybe_base64_encode($object); |
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
} else { |
339
|
|
|
|
|
|
|
# do not compress |
340
|
9
|
|
|
|
|
18
|
_maybe_base64_encode($object); |
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
sub jsonify : method { |
345
|
38
|
|
|
38
|
1
|
82
|
my($self, %option, %object, $algo, $force, $len); |
346
|
|
|
|
|
|
|
|
347
|
38
|
|
|
|
|
45
|
$self = shift(@_); |
348
|
38
|
100
|
|
|
|
161
|
%option = validate(@_, \%jsonify_options) if @_; |
349
|
38
|
100
|
66
|
|
|
230
|
if ($option{"compression"} and $option{"compression"} =~ /^(\w+)(!?)$/) { |
350
|
6
|
|
|
|
|
21
|
($algo, $force) = ($1, $2); |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
# check compression availability |
353
|
38
|
100
|
|
|
|
88
|
_require("Compress::$_CompressionModule{$algo}") if $algo; |
354
|
|
|
|
|
|
|
# build the JSON object |
355
|
38
|
100
|
|
|
|
114
|
$object{"text"} = JSON::true if $self->{"text"}; |
356
|
38
|
100
|
|
|
|
75
|
$object{"header"} = $self->{"header"} if keys(%{ $self->{"header"} }); |
|
38
|
|
|
|
|
124
|
|
357
|
38
|
|
|
|
|
40
|
$len = length(${ $self->{"body_ref"} }); |
|
38
|
|
|
|
|
60
|
|
358
|
38
|
100
|
|
|
|
112
|
return(\%object) unless $len; |
359
|
22
|
|
|
|
|
20
|
$object{"body"} = ${ $self->{"body_ref"} }; |
|
22
|
|
|
|
|
45
|
|
360
|
|
|
|
|
|
|
# handle non-empty body |
361
|
22
|
100
|
|
|
|
46
|
if ($self->{"text"}) { |
362
|
|
|
|
|
|
|
# text body |
363
|
10
|
|
|
|
|
23
|
_jsonify_text($self, \%object, $algo, $force, $len); |
364
|
|
|
|
|
|
|
} else { |
365
|
|
|
|
|
|
|
# binary body |
366
|
12
|
|
|
|
|
54
|
_jsonify_binary($self, \%object, $algo, $force, $len); |
367
|
|
|
|
|
|
|
} |
368
|
|
|
|
|
|
|
# set the encoding string |
369
|
22
|
100
|
|
|
|
49
|
$object{"encoding"} = join("+", sort(keys(%{ $object{"encoding"} }))) |
|
15
|
|
|
|
|
71
|
|
370
|
|
|
|
|
|
|
if $object{"encoding"}; |
371
|
|
|
|
|
|
|
# so far so good! |
372
|
22
|
|
|
|
|
68
|
return(\%object); |
373
|
|
|
|
|
|
|
} |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
# |
376
|
|
|
|
|
|
|
# dejsonify (= alternate constructor using the JSON object) |
377
|
|
|
|
|
|
|
# |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
my %dejsonify_options = ( |
380
|
|
|
|
|
|
|
"header" => $new_options{"header"}, |
381
|
|
|
|
|
|
|
"body" => { |
382
|
|
|
|
|
|
|
type => SCALAR, |
383
|
|
|
|
|
|
|
optional => 1, |
384
|
|
|
|
|
|
|
}, |
385
|
|
|
|
|
|
|
"text" => { |
386
|
|
|
|
|
|
|
type => OBJECT, |
387
|
|
|
|
|
|
|
callbacks => { |
388
|
|
|
|
|
|
|
"JSON::is_bool" => sub { JSON::is_bool($_[0]) }, |
389
|
|
|
|
|
|
|
}, |
390
|
|
|
|
|
|
|
optional => 1, |
391
|
|
|
|
|
|
|
}, |
392
|
|
|
|
|
|
|
"encoding" => { |
393
|
|
|
|
|
|
|
type => SCALAR, |
394
|
|
|
|
|
|
|
optional => 1, |
395
|
|
|
|
|
|
|
}, |
396
|
|
|
|
|
|
|
); |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
sub dejsonify : method { |
399
|
74
|
|
|
74
|
1
|
109
|
my($class, $object, $encoding, $self, $tmp, $len, $uncompress); |
400
|
|
|
|
|
|
|
|
401
|
74
|
|
|
|
|
100
|
$class = shift(@_); |
402
|
74
|
100
|
33
|
|
|
3663
|
validate_pos(@_, { type => HASHREF }) |
|
|
|
66
|
|
|
|
|
403
|
|
|
|
|
|
|
unless @_ == 1 and defined($_[0]) and ref($_[0]) eq "HASH"; |
404
|
73
|
|
|
|
|
1659
|
validate(@_, \%dejsonify_options); |
405
|
70
|
|
|
|
|
349
|
$object = $_[0]; |
406
|
70
|
|
100
|
|
|
256
|
$encoding = $object->{"encoding"} || ""; |
407
|
70
|
100
|
100
|
|
|
444
|
dief("invalid encoding: %s", $encoding) |
408
|
|
|
|
|
|
|
unless $encoding eq "" |
409
|
|
|
|
|
|
|
or "${encoding}+" =~ /^((base64|utf8|$_CompressionAlgos)\+)+$/o; |
410
|
69
|
100
|
|
|
|
337
|
_require("Compress::$_CompressionModule{$1}") |
411
|
|
|
|
|
|
|
if $encoding =~ /($_CompressionAlgos)/o; |
412
|
|
|
|
|
|
|
# construct the message |
413
|
69
|
|
|
|
|
230
|
$self = $class->new(); |
414
|
69
|
100
|
|
|
|
176
|
$self->{"text"} = 1 if $object->{"text"}; |
415
|
40
|
|
|
|
|
192
|
$self->{"header"} = $object->{"header"} |
416
|
69
|
100
|
100
|
|
|
283
|
if $object->{"header"} and keys(%{ $object->{"header"} }); |
417
|
69
|
100
|
|
|
|
197
|
if (exists($object->{"body"})) { |
418
|
57
|
|
|
|
|
90
|
$tmp = $object->{"body"}; |
419
|
57
|
100
|
|
|
|
151
|
if ($encoding =~ /base64/) { |
420
|
|
|
|
|
|
|
# body has been Base64 encoded, compute length to detect unexpected |
421
|
|
|
|
|
|
|
# characters (this is because MIME::Base64 silently ignores them) |
422
|
26
|
|
|
|
|
57
|
$len = length($tmp); |
423
|
26
|
100
|
|
|
|
73
|
dief("invalid Base64 data: %s", $object->{"body"}) if $len % 4; |
424
|
25
|
|
|
|
|
55
|
$len = $len * 3 / 4; |
425
|
25
|
|
|
|
|
66
|
$len -= substr($tmp, -2) =~ tr/=/=/; |
426
|
|
|
|
|
|
|
_eval("Base64 decoding", sub { |
427
|
25
|
|
|
25
|
|
472
|
$tmp = decode_base64($tmp); |
428
|
25
|
|
|
|
|
101
|
}); |
429
|
25
|
100
|
|
|
|
101
|
dief("invalid Base64 data: %s", $object->{"body"}) |
430
|
|
|
|
|
|
|
unless $len == length($tmp); |
431
|
|
|
|
|
|
|
} |
432
|
54
|
100
|
|
|
|
217
|
if ($encoding =~ /($_CompressionAlgos)/o) { |
433
|
|
|
|
|
|
|
# body has been compressed |
434
|
9
|
|
|
|
|
20
|
$uncompress = \&{"Compress::$_CompressionModule{$1}::uncompress"}; |
|
9
|
|
|
|
|
72
|
|
435
|
|
|
|
|
|
|
_eval("$_CompressionModule{$1} decompression", sub { |
436
|
9
|
|
|
9
|
|
44
|
$tmp = $uncompress->(\$tmp); |
437
|
9
|
|
|
|
|
59
|
}); |
438
|
9
|
50
|
|
|
|
44
|
dief("invalid $_CompressionModule{$1} compressed data!") |
439
|
|
|
|
|
|
|
unless defined($tmp); |
440
|
|
|
|
|
|
|
} |
441
|
54
|
100
|
|
|
|
120
|
if ($encoding =~ /utf8/) { |
442
|
|
|
|
|
|
|
# body has been UTF-8 encoded |
443
|
|
|
|
|
|
|
_eval("UTF-8 decoding", sub { |
444
|
4
|
|
|
4
|
|
29
|
$tmp = decode("UTF-8", $tmp, FB_CROAK); |
445
|
4
|
|
|
|
|
23
|
}); |
446
|
|
|
|
|
|
|
} |
447
|
54
|
|
|
|
|
113
|
$self->{"body_ref"} = \$tmp; |
448
|
|
|
|
|
|
|
} |
449
|
|
|
|
|
|
|
# so far so good! |
450
|
66
|
|
|
|
|
325
|
return($self); |
451
|
|
|
|
|
|
|
} |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
#+++############################################################################ |
454
|
|
|
|
|
|
|
# # |
455
|
|
|
|
|
|
|
# (de)stringification # |
456
|
|
|
|
|
|
|
# # |
457
|
|
|
|
|
|
|
#---############################################################################ |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
# |
460
|
|
|
|
|
|
|
# stringify (= transform into a text string) |
461
|
|
|
|
|
|
|
# |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
sub stringify : method { |
464
|
7
|
|
|
7
|
1
|
9125
|
my($self, $tmp); |
465
|
|
|
|
|
|
|
|
466
|
7
|
|
|
|
|
14
|
$self = shift(@_); |
467
|
7
|
|
|
|
|
15
|
$tmp = $self->jsonify(@_); |
468
|
|
|
|
|
|
|
_eval("JSON encoding", sub { |
469
|
7
|
|
|
7
|
|
63
|
$tmp = $_JSON->encode($tmp); |
470
|
7
|
|
|
|
|
27
|
}); |
471
|
7
|
|
|
|
|
23
|
return($tmp); |
472
|
|
|
|
|
|
|
} |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
sub stringify_ref : method { |
475
|
18
|
|
|
18
|
1
|
20
|
my($self, $tmp); |
476
|
|
|
|
|
|
|
|
477
|
18
|
|
|
|
|
20
|
$self = shift(@_); |
478
|
18
|
|
|
|
|
35
|
$tmp = $self->jsonify(@_); |
479
|
|
|
|
|
|
|
_eval("JSON encoding", sub { |
480
|
18
|
|
|
18
|
|
168
|
$tmp = $_JSON->encode($tmp); |
481
|
18
|
|
|
|
|
69
|
}); |
482
|
18
|
|
|
|
|
49
|
return(\$tmp); |
483
|
|
|
|
|
|
|
} |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
# |
486
|
|
|
|
|
|
|
# destringify (= alternate constructor using the stringified representation) |
487
|
|
|
|
|
|
|
# |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
sub destringify : method { |
490
|
36
|
|
|
36
|
1
|
59
|
my($class, $tmp); |
491
|
|
|
|
|
|
|
|
492
|
36
|
|
|
|
|
45
|
$class = shift(@_); |
493
|
36
|
50
|
33
|
|
|
209
|
validate_pos(@_, { type => SCALAR }) |
|
|
|
33
|
|
|
|
|
494
|
|
|
|
|
|
|
unless @_ == 1 and defined($_[0]) and ref($_[0]) eq ""; |
495
|
|
|
|
|
|
|
_eval("JSON decoding", sub { |
496
|
36
|
|
|
36
|
|
39
|
$tmp = $_JSON->decode(${ $_[0] }); |
|
36
|
|
|
|
|
610
|
|
497
|
36
|
|
|
|
|
138
|
}, \$_[0]); |
498
|
34
|
|
|
|
|
124
|
return($class->dejsonify($tmp)); |
499
|
|
|
|
|
|
|
} |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
sub destringify_ref : method { |
502
|
33
|
|
|
33
|
1
|
43
|
my($class, $tmp); |
503
|
|
|
|
|
|
|
|
504
|
33
|
|
|
|
|
75
|
$class = shift(@_); |
505
|
33
|
50
|
33
|
|
|
403
|
validate_pos(@_, { type => SCALARREF }) |
|
|
|
33
|
|
|
|
|
506
|
|
|
|
|
|
|
unless @_ == 1 and defined($_[0]) and ref($_[0]) eq "SCALAR"; |
507
|
|
|
|
|
|
|
_eval("JSON decoding", sub { |
508
|
33
|
|
|
33
|
|
45
|
$tmp = $_JSON->decode(${ $_[0] }); |
|
33
|
|
|
|
|
1791
|
|
509
|
33
|
|
|
|
|
224
|
}, $_[0]); |
510
|
33
|
|
|
|
|
163
|
return($class->dejsonify($tmp)); |
511
|
|
|
|
|
|
|
} |
512
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
#+++############################################################################ |
514
|
|
|
|
|
|
|
# # |
515
|
|
|
|
|
|
|
# (de)serialization # |
516
|
|
|
|
|
|
|
# # |
517
|
|
|
|
|
|
|
#---############################################################################ |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
# |
520
|
|
|
|
|
|
|
# serialize (= transform into a binary string) |
521
|
|
|
|
|
|
|
# |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
sub serialize : method { |
524
|
18
|
|
|
18
|
1
|
6490
|
my($self, $tmp); |
525
|
|
|
|
|
|
|
|
526
|
18
|
|
|
|
|
25
|
$self = shift(@_); |
527
|
18
|
|
|
|
|
39
|
$tmp = $self->stringify_ref(@_); |
528
|
|
|
|
|
|
|
_eval("UTF-8 encoding", sub { |
529
|
18
|
|
|
18
|
|
18
|
$tmp = encode("UTF-8", ${ $tmp }, FB_CROAK); |
|
18
|
|
|
|
|
75
|
|
530
|
18
|
|
|
|
|
57
|
}); |
531
|
18
|
|
|
|
|
67
|
return($tmp); |
532
|
|
|
|
|
|
|
} |
533
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
sub serialize_ref : method { |
535
|
0
|
|
|
0
|
1
|
0
|
my($self, $tmp); |
536
|
|
|
|
|
|
|
|
537
|
0
|
|
|
|
|
0
|
$self = shift(@_); |
538
|
0
|
|
|
|
|
0
|
$tmp = $self->stringify_ref(@_); |
539
|
|
|
|
|
|
|
_eval("UTF-8 encoding", sub { |
540
|
0
|
|
|
0
|
|
0
|
$tmp = encode("UTF-8", ${ $tmp }, FB_CROAK); |
|
0
|
|
|
|
|
0
|
|
541
|
0
|
|
|
|
|
0
|
}); |
542
|
0
|
|
|
|
|
0
|
return(\$tmp); |
543
|
|
|
|
|
|
|
} |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
# |
546
|
|
|
|
|
|
|
# deserialize (= alternate constructor using the serialized representation) |
547
|
|
|
|
|
|
|
# |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
sub deserialize : method { |
550
|
28
|
|
|
28
|
1
|
6822
|
my($class, $tmp); |
551
|
|
|
|
|
|
|
|
552
|
28
|
|
|
|
|
37
|
$class = shift(@_); |
553
|
28
|
50
|
33
|
|
|
185
|
validate_pos(@_, { type => SCALAR }) |
|
|
|
33
|
|
|
|
|
554
|
|
|
|
|
|
|
unless @_ == 1 and defined($_[0]) and ref($_[0]) eq ""; |
555
|
28
|
100
|
|
|
|
114
|
return($class->destringify($_[0])) unless $_[0] =~ /[^[:ascii:]]/; |
556
|
|
|
|
|
|
|
_eval("UTF-8 decoding", sub { |
557
|
4
|
|
|
4
|
|
4
|
$tmp = decode("UTF-8", ${ $_[0] }, FB_CROAK|LEAVE_SRC); |
|
4
|
|
|
|
|
25
|
|
558
|
4
|
|
|
|
|
18
|
}, \$_[0]); |
559
|
3
|
|
|
|
|
57
|
return($class->destringify($tmp)); |
560
|
|
|
|
|
|
|
} |
561
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
sub deserialize_ref : method { |
563
|
35
|
|
|
35
|
1
|
144302
|
my($class, $tmp); |
564
|
|
|
|
|
|
|
|
565
|
35
|
|
|
|
|
79
|
$class = shift(@_); |
566
|
35
|
50
|
33
|
|
|
527
|
validate_pos(@_, { type => SCALARREF }) |
|
|
|
33
|
|
|
|
|
567
|
|
|
|
|
|
|
unless @_ == 1 and defined($_[0]) and ref($_[0]) eq "SCALAR"; |
568
|
35
|
100
|
|
|
|
53
|
return($class->destringify_ref($_[0])) unless ${ $_[0] } =~ /[^[:ascii:]]/; |
|
35
|
|
|
|
|
997
|
|
569
|
|
|
|
|
|
|
_eval("UTF-8 decoding", sub { |
570
|
2
|
|
|
2
|
|
4
|
$tmp = decode("UTF-8", ${ $_[0] }, FB_CROAK|LEAVE_SRC); |
|
2
|
|
|
|
|
17
|
|
571
|
2
|
|
|
|
|
17
|
}, $_[0]); |
572
|
2
|
|
|
|
|
15
|
return($class->destringify($tmp)); |
573
|
|
|
|
|
|
|
} |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
# |
576
|
|
|
|
|
|
|
# export control |
577
|
|
|
|
|
|
|
# |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
sub import : method { |
580
|
6
|
|
|
6
|
|
40
|
my($pkg, %exported); |
581
|
|
|
|
|
|
|
|
582
|
6
|
|
|
|
|
14
|
$pkg = shift(@_); |
583
|
6
|
|
|
|
|
15
|
%exported = ("_require" => 1); |
584
|
6
|
|
|
|
|
38
|
export_control(scalar(caller()), $pkg, \%exported, @_); |
585
|
|
|
|
|
|
|
} |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
1; |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
__DATA__ |