File Coverage

blib/lib/PHP/Session/Serializer/PHP.pm
Criterion Covered Total %
statement 133 144 92.3
branch 52 64 81.2
condition 5 9 55.5
subroutine 30 33 90.9
pod 0 24 0.0
total 220 274 80.2


line stmt bran cond sub pod time code
1             package PHP::Session::Serializer::PHP;
2              
3 13     13   72 use strict;
  13         21  
  13         609  
4 13     13   191 use vars qw($VERSION);
  13         41  
  13         63550  
5             $VERSION = 0.26;
6              
7 0     0   0 sub _croak { require Carp; Carp::croak(@_) }
  0         0  
8              
9             sub new {
10 49     49 0 83 my $class = shift;
11 49         455 bless {
12             buffer => undef,
13             data => {},
14             state => undef,
15             stack => [],
16             array => [], # array-ref of hash-ref
17             }, $class;
18             }
19              
20             # encoder starts here
21              
22             sub encode {
23 14     14 0 24 my($self, $data) = @_;
24 14         24 my $body;
25 14         48 for my $key (keys %$data) {
26 22 100       65 if (defined $data->{$key}) {
27 20         63 $body .= "$key|" . $self->do_encode($data->{$key});
28             } else {
29 2         7 $body .= "!$key|";
30             }
31             }
32 14         86 return $body;
33             }
34              
35             sub do_encode {
36 42     42 0 112 my($self, $value) = @_;
37 42 50       130 if (! defined $value) {
    100          
    100          
    50          
    50          
38 0         0 return $self->encode_null($value);
39             }
40             elsif (! ref $value) {
41 35 100       73 if (is_int($value)) {
    100          
42 4         46 return $self->encode_int($value);
43             }
44             elsif (is_float($value)) {
45 2         8 return $self->encode_double($value);
46             }
47             else {
48 29         65 return $self->encode_string($value);
49             }
50             }
51             elsif (ref $value eq 'HASH') {
52 3         9 return $self->encode_array($value);
53             }
54             elsif (ref $value eq 'ARRAY') {
55 0         0 return $self->encode_array($value);
56             }
57             elsif (ref $value eq 'PHP::Session::Object') {
58 4         7 return $self->encode_object($value);
59             }
60             else {
61 0         0 _croak("Can't encode ", ref($value));
62             }
63             }
64              
65             sub encode_null {
66 0     0 0 0 my($self, $value) = @_;
67 0         0 return 'N;';
68             }
69              
70             sub encode_int {
71 4     4 0 9 my($self, $value) = @_;
72 4         25 return sprintf 'i:%d;', $value;
73             }
74              
75             sub encode_double {
76 2     2 0 3 my($self, $value) = @_;
77 2         12 return sprintf "d:%s;", $value; # XXX hack
78             }
79              
80             sub encode_string {
81 29     29 0 63 my($self, $value) = @_;
82 29         475 return sprintf 's:%d:"%s";', length($value), $value;
83             }
84              
85             sub encode_array {
86 3     3 0 5 my($self, $value) = @_;
87 3 50       14 my %array = ref $value eq 'HASH' ? %$value : map { $_ => $value->[$_] } 0..$#{$value};
  0         0  
  0         0  
88 3         13 return sprintf 'a:%d:{%s}', scalar(keys %array), join('', map $self->do_encode($_), %array);
89             }
90              
91             sub encode_object {
92 4     4 0 4 my($self, $value) = @_;
93 4         20 my %impl = %$value;
94 4         8 my $class = delete $impl{_class};
95 4         15 return sprintf 'O:%d:"%s":%d:{%s}', length($class), $class, scalar(keys %impl),
96             join('', map $self->do_encode($_), %impl);
97             }
98              
99             sub is_int {
100 35     35 0 96 local $_ = shift;
101 35         172 /^-?(0|[1-9]\d{0,8})$/;
102             }
103              
104             sub is_float {
105 31     31 0 44 local $_ = shift;
106 31         92 /^-?(0|[1-9]\d{0,8})\.\d+$/;
107             }
108              
109             # decoder starts here
110              
111             sub decode {
112 35     35 0 67 my($self, $data) = @_;
113 35         151 $self->{buffer} = $data;
114 35         95 $self->change_state('VarName');
115 35   100     239 while (defined $self->{buffer} && length $self->{buffer}) {
116 6438         15029 $self->{state}->parse($self);
117             }
118 35         183 return $self->{data};
119             }
120              
121             sub change_state {
122 7081     7081 0 9178 my($self, $state) = @_;
123 7081         42815 $self->{state} = "PHP::Session::Serializer::PHP::State::$state"; # optimization
124             # $self->{state} = PHP::Session::Serializer::PHP::State->new($state);
125              
126             }
127              
128             sub set {
129 73     73 0 172 my($self, $key, $value) = @_;
130 73         294 $self->{data}->{$key} = $value;
131             }
132              
133             sub push_stack {
134 6056     6056 0 9542 my($self, $stuff) = @_;
135 6056         5628 push @{$self->{stack}}, $stuff;
  6056         14686  
136             }
137              
138             sub pop_stack {
139 2542     2542 0 2586 my $self = shift;
140 2542         2331 pop @{$self->{stack}};
  2542         5281  
141             }
142              
143             sub extract_stack {
144 309     309 0 362 my($self, $num) = @_;
145 309 100       525 return $num ? splice(@{$self->{stack}}, -$num) : ();
  258         1491  
146             }
147              
148             # array: [ [ $length, $consuming, $class ], [ $length, $consuming, $class ] .. ]
149              
150             sub start_array {
151 309     309 0 539 my($self, $length, $class) = @_;
152 309         298 unshift @{$self->{array}}, [ $length, 0, $class ];
  309         1070  
153             }
154              
155             sub in_array {
156 3937     3937 0 9660 my $self = shift;
157 3937         3595 return scalar @{$self->{array}};
  3937         10560  
158             }
159              
160             sub consume_array {
161 3514     3514 0 3724 my $self = shift;
162 3514         6799 $self->{array}->[0]->[1]++;
163             }
164              
165             sub finished_array {
166 3565     3565 0 3710 my $self = shift;
167 3565         11289 return $self->{array}->[0]->[0] * 2 == $self->{array}->[0]->[1];
168             }
169              
170             sub elements_count {
171 309     309 0 349 my $self = shift;
172 309         878 return $self->{array}->[0]->[0];
173             }
174              
175             sub process_value {
176 3628     3628 0 5528 my($self, $value, $empty_skip) = @_;
177 3628 100       6091 if ($self->in_array()) {
178 3565 100       6236 unless ($empty_skip) {
179 3514         5687 $self->push_stack($value);
180 3514         6024 $self->consume_array();
181             }
182 3565 100       5577 if ($self->finished_array()) {
183             # just finished array
184 309         326 my $array = shift @{$self->{array}}; # shift it
  309         505  
185 309         749 my @values = $self->extract_stack($array->[0] * 2);
186 309         624 my $class = $array->[2];
187 309 100       476 if (defined $class) {
188             # object
189 65         686 my $real_value = bless {
190             _class => $class,
191             @values,
192             }, 'PHP::Session::Object';
193 65         159 $self->process_value($real_value);
194             } else {
195             # array is hash
196 244         1492 $self->process_value({ @values });
197             }
198 309         637 $self->change_state('ArrayEnd');
199 309         719 $self->{state}->parse($self);
200             } else {
201             # not yet finished
202 3256         5815 $self->change_state('VarType');
203             }
204             }
205             else {
206             # not in array
207 63         199 my $varname = $self->pop_stack;
208 63         160 $self->set($varname => $value);
209 63         486 $self->change_state('VarName');
210             }
211             }
212              
213             sub weird {
214 0     0 0 0 my $self = shift;
215 0         0 _croak("weird data: $self->{buffer}");
216             }
217              
218             package PHP::Session::Serializer::PHP::State::VarName;
219              
220             sub parse {
221 73     73   119 my($self, $decoder) = @_;
222 73 50       756 $decoder->{buffer} =~ s/^(!?)(.*?)\|// or $decoder->weird;
223 73 100       234 if ($1) {
224 10         74 $decoder->set($2 => undef);
225             } else {
226 63         152 $decoder->push_stack($2);
227 63         148 $decoder->change_state('VarType');
228             }
229             }
230              
231             package PHP::Session::Serializer::PHP::State::VarType;
232              
233             my @re = (
234             's:(\d+):', # string
235             'i:(-?\d+);', # integer
236             'd:(-?\d+(?:\.\d+)?);', # double
237             'a:(\d+):', # array
238             'O:(\d+):', # object
239             '(N);', # null
240             'b:([01]);', # boolean
241             '[Rr]:(\d+);', # reference count?
242             );
243              
244             sub parse {
245 3577     3577   5044 my($self, $decoder) = @_;
246 3577         8292 my $re = join "|", @re;
247 3577 50       31440 $decoder->{buffer} =~ s/^(?:$re)// or $decoder->weird;
248 3577 100       9228 if (defined $1) { # string
    100          
    100          
    100          
    100          
    100          
    100          
    50          
249 2414         4274 $decoder->push_stack($1);
250 2414         4582 $decoder->change_state('String');
251             }
252             elsif (defined $2) { # integer
253 730         1743 $decoder->process_value($2);
254             }
255             elsif (defined $3) { # double
256 5         20 $decoder->process_value($3);
257             }
258             elsif (defined $4) { # array
259 244         462 $decoder->start_array($4);
260 244         486 $decoder->change_state('ArrayStart');
261             }
262             elsif (defined $5) { # object
263 65         132 $decoder->push_stack($5);
264 65         126 $decoder->change_state('ClassName');
265             }
266             elsif (defined $6) { # null
267 33         64 $decoder->process_value(undef);
268             }
269             elsif (defined $7) { # boolean
270 74         128 $decoder->process_value($7);
271             }
272             elsif (defined $8) { # reference
273 12         23 $decoder->process_value($8);
274             }
275             }
276              
277             package PHP::Session::Serializer::PHP::State::String;
278              
279             sub parse {
280 2414     2414   3132 my($self, $decoder) = @_;
281 2414         3735 my $length = $decoder->pop_stack();
282              
283             # .{$length} has a limit on length
284             # $decoder->{buffer} =~ s/^"(.{$length})";//s or $decoder->weird;
285 2414         6144 my $value = substr($decoder->{buffer}, 0, $length + 3, "");
286 2414 50 33     17578 $value =~ s/^"// and $value =~ s/";$// or $decoder->weird;
287 2414         4903 $decoder->process_value($value);
288             }
289              
290             package PHP::Session::Serializer::PHP::State::ArrayStart;
291              
292             sub parse {
293 309     309   439 my($self, $decoder) = @_;
294 309 50       1692 $decoder->{buffer} =~ s/^{// or $decoder->weird;
295 309 100       553 if ($decoder->elements_count) {
296 258         436 $decoder->change_state('VarType');
297             } else {
298 51         111 $decoder->process_value(undef, 1);
299             }
300             }
301              
302             package PHP::Session::Serializer::PHP::State::ArrayEnd;
303              
304             sub parse {
305 309     309   424 my($self, $decoder) = @_;
306 309 50       1686 $decoder->{buffer} =~ s/^}// or $decoder->weird;
307 309 100       645 my $next_state = $decoder->in_array() ? 'VarType' : 'VarName';
308 309         591 $decoder->change_state($next_state);
309             }
310              
311             package PHP::Session::Serializer::PHP::State::ClassName;
312              
313             sub parse {
314 65     65   98 my($self, $decoder) = @_;
315 65         127 my $length = $decoder->pop_stack();
316             # $decoder->{buffer} =~ s/^"(.{$length})":(\d+):// or $decoder->weird;
317 65         185 my $value = substr($decoder->{buffer}, 0, $length + 3, "");
318 65 50 33     523 $value =~ s/^"// and $value =~ s/":$// or $decoder->weird;
319 65 50       477 $decoder->{buffer} =~ s/^(\d+):// or $decoder->weird;
320 65         157 $decoder->start_array($1, $value); # $length, $class
321 65         129 $decoder->change_state('ArrayStart');
322             }
323              
324              
325             1;
326             __END__