... | ... |
@@ -1,23 +0,0 @@ |
1 |
-use strict; |
|
2 |
-use warnings; |
|
3 |
-use Test::ModuleVersion; |
|
4 |
-use FindBin; |
|
5 |
- |
|
6 |
-# Create module test |
|
7 |
-my $tm = Test::ModuleVersion->new; |
|
8 |
-$tm->before(<<'EOS'); |
|
9 |
-use 5.008007; |
|
10 |
- |
|
11 |
-=pod |
|
12 |
- |
|
13 |
-run mvt.pl to create this module version test(t/module.t). |
|
14 |
- |
|
15 |
- perl mvt.pl |
|
16 |
- |
|
17 |
-=cut |
|
18 |
- |
|
19 |
-EOS |
|
20 |
-$tm->lib(['../extlib/lib/perl5']); |
|
21 |
-$tm->test_script(output => "$FindBin::Bin/t/module.t"); |
|
22 |
- |
|
23 |
-1; |
... | ... |
@@ -0,0 +1,4 @@ |
1 |
+use Test::More 'no_plan'; |
|
2 |
+ |
|
3 |
+use_ok('Gitprep'); |
|
4 |
+ |
... | ... |
@@ -1,2908 +0,0 @@ |
1 |
-use FindBin; |
|
2 |
-use lib "$FindBin::Bin/../extlib/lib/perl5"; |
|
3 |
-use 5.008007; |
|
4 |
- |
|
5 |
-=pod |
|
6 |
- |
|
7 |
-run mvt.pl to create this module version test(t/module.t). |
|
8 |
- |
|
9 |
- perl mvt.pl |
|
10 |
- |
|
11 |
-=cut |
|
12 |
- |
|
13 |
- |
|
14 |
-# Created by Test::ModuleVersion 0.15 |
|
15 |
-use Test::More; |
|
16 |
-use strict; |
|
17 |
-use warnings; |
|
18 |
-use ExtUtils::Installed; |
|
19 |
- |
|
20 |
-sub main { |
|
21 |
- my $command = shift; |
|
22 |
- my @options = @_; |
|
23 |
- |
|
24 |
- die qq/command "$command" is unkonwn command/ |
|
25 |
- if defined $command && $command ne 'list'; |
|
26 |
- |
|
27 |
- my $list_failed; |
|
28 |
- my $lwp = 'auto'; |
|
29 |
- for my $option (@options) { |
|
30 |
- if ($option eq '--fail') { $list_failed = 1 } |
|
31 |
- elsif ($option eq '--lwp') { $lwp = 'use' } |
|
32 |
- elsif ($option eq '--no-lwp') { $lwp = 'no' } |
|
33 |
- else { die qq/list $option is unknown option/ } |
|
34 |
- } |
|
35 |
- |
|
36 |
- if (defined $command) { |
|
37 |
- my $builder = Test::More->builder; |
|
38 |
- open my $out_fh, '>', undef; |
|
39 |
- $builder->output($out_fh); |
|
40 |
- $builder->failure_output($out_fh); |
|
41 |
- $builder->todo_output($out_fh); |
|
42 |
- } |
|
43 |
- |
|
44 |
- my $modules = []; |
|
45 |
- my $failed = []; |
|
46 |
- my $require_ok; |
|
47 |
- my $version_ok; |
|
48 |
- my $version; |
|
49 |
- |
|
50 |
- plan tests => 0; |
|
51 |
- |
|
52 |
- # Print module URLs |
|
53 |
- if (defined $command) { |
|
54 |
- my $distnames = {} |
|
55 |
- |
|
56 |
- ; |
|
57 |
- my $privates = {} |
|
58 |
- |
|
59 |
- ; |
|
60 |
- my $tm = Test::ModuleVersion->new; |
|
61 |
- my @ms = $command eq 'list' && $list_failed ? @$failed |
|
62 |
- : $command eq 'list' ? @$modules |
|
63 |
- : []; |
|
64 |
- for my $m (@ms) { |
|
65 |
- my ($module, $version) = @$m; |
|
66 |
- my $mu = Test::ModuleVersion::ModuleURL->new; |
|
67 |
- $mu->distnames($distnames); |
|
68 |
- $mu->privates($privates); |
|
69 |
- $mu->lwp($lwp); |
|
70 |
- my $url = $mu->get($module, $version); |
|
71 |
- if (defined $url) { print "$url\n" } |
|
72 |
- else { print STDERR $mu->error . "\n" } |
|
73 |
- } |
|
74 |
- } |
|
75 |
-} |
|
76 |
- |
|
77 |
-use 5.008007; |
|
78 |
-package Test::ModuleVersion; |
|
79 |
-our $VERSION = '0.15'; |
|
80 |
- |
|
81 |
-package |
|
82 |
- Test::ModuleVersion::Object::Simple; |
|
83 |
- |
|
84 |
-our $VERSION = '3.0626'; |
|
85 |
- |
|
86 |
-use strict; |
|
87 |
-use warnings; |
|
88 |
-no warnings 'redefine'; |
|
89 |
- |
|
90 |
-use Carp (); |
|
91 |
- |
|
92 |
-sub import { |
|
93 |
- my ($class, @methods) = @_; |
|
94 |
- |
|
95 |
- # Caller |
|
96 |
- my $caller = caller; |
|
97 |
- |
|
98 |
- # Base |
|
99 |
- if ((my $flag = $methods[0] || '') eq '-base') { |
|
100 |
- |
|
101 |
- # Can haz? |
|
102 |
- no strict 'refs'; |
|
103 |
- no warnings 'redefine'; |
|
104 |
- *{"${caller}::has"} = sub { attr($caller, @_) }; |
|
105 |
- |
|
106 |
- # Inheritance |
|
107 |
- if (my $module = $methods[1]) { |
|
108 |
- $module =~ s/::|'/\//g; |
|
109 |
- require "$module.pm" unless $module->can('new'); |
|
110 |
- push @{"${caller}::ISA"}, $module; |
|
111 |
- } |
|
112 |
- else { |
|
113 |
- push @{"${caller}::ISA"}, $class; |
|
114 |
- } |
|
115 |
- |
|
116 |
- # strict! |
|
117 |
- strict->import; |
|
118 |
- warnings->import; |
|
119 |
- |
|
120 |
- # Modern! |
|
121 |
- feature->import(':5.10') if $] >= 5.010; |
|
122 |
- } |
|
123 |
- # Method export |
|
124 |
- else { |
|
125 |
- |
|
126 |
- # Exports |
|
127 |
- my %exports = map { $_ => 1 } qw/new attr class_attr dual_attr/; |
|
128 |
- |
|
129 |
- # Export methods |
|
130 |
- foreach my $method (@methods) { |
|
131 |
- |
|
132 |
- # Can be Exported? |
|
133 |
- Carp::croak("Cannot export '$method'.") |
|
134 |
- unless $exports{$method}; |
|
135 |
- |
|
136 |
- # Export |
|
137 |
- no strict 'refs'; |
|
138 |
- *{"${caller}::$method"} = \&{"$method"}; |
|
139 |
- } |
|
140 |
- } |
|
141 |
-} |
|
142 |
- |
|
143 |
-sub new { |
|
144 |
- my $class = shift; |
|
145 |
- bless @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {}, ref $class || $class; |
|
146 |
-} |
|
147 |
- |
|
148 |
-sub attr { |
|
149 |
- my ($self, @args) = @_; |
|
150 |
- |
|
151 |
- my $class = ref $self || $self; |
|
152 |
- |
|
153 |
- # Fix argument |
|
154 |
- unshift @args, (shift @args, undef) if @args % 2; |
|
155 |
- |
|
156 |
- for (my $i = 0; $i < @args; $i += 2) { |
|
157 |
- |
|
158 |
- # Attribute name |
|
159 |
- my $attrs = $args[$i]; |
|
160 |
- $attrs = [$attrs] unless ref $attrs eq 'ARRAY'; |
|
161 |
- |
|
162 |
- # Default |
|
163 |
- my $default = $args[$i + 1]; |
|
164 |
- |
|
165 |
- foreach my $attr (@$attrs) { |
|
166 |
- |
|
167 |
- Carp::croak("Default value of attr must be string or number " . |
|
168 |
- "or code reference (${class}::$attr)") |
|
169 |
- unless !ref $default || ref $default eq 'CODE'; |
|
170 |
- |
|
171 |
- # Code |
|
172 |
- my $code; |
|
173 |
- if (defined $default && ref $default) { |
|
174 |
- |
|
175 |
- |
|
176 |
- |
|
177 |
-$code = sub { |
|
178 |
- if(@_ == 1) { |
|
179 |
- return $_[0]->{$attr} = $default->($_[0]) unless exists $_[0]->{$attr}; |
|
180 |
- return $_[0]->{$attr}; |
|
181 |
- } |
|
182 |
- $_[0]->{$attr} = $_[1]; |
|
183 |
- $_[0]; |
|
184 |
-} |
|
185 |
- |
|
186 |
- } |
|
187 |
- elsif (defined $default && ! ref $default) { |
|
188 |
- |
|
189 |
- |
|
190 |
- |
|
191 |
-$code = sub { |
|
192 |
- if(@_ == 1) { |
|
193 |
- return $_[0]->{$attr} = $default unless exists $_[0]->{$attr}; |
|
194 |
- return $_[0]->{$attr}; |
|
195 |
- } |
|
196 |
- $_[0]->{$attr} = $_[1]; |
|
197 |
- $_[0]; |
|
198 |
-} |
|
199 |
- |
|
200 |
- |
|
201 |
- |
|
202 |
- } |
|
203 |
- else { |
|
204 |
- |
|
205 |
- |
|
206 |
- |
|
207 |
-$code = sub { |
|
208 |
- return $_[0]->{$attr} if @_ == 1; |
|
209 |
- $_[0]->{$attr} = $_[1]; |
|
210 |
- $_[0]; |
|
211 |
-} |
|
212 |
- |
|
213 |
- |
|
214 |
- |
|
215 |
- } |
|
216 |
- |
|
217 |
- no strict 'refs'; |
|
218 |
- *{"${class}::$attr"} = $code; |
|
219 |
- } |
|
220 |
- } |
|
221 |
-} |
|
222 |
- |
|
223 |
-package |
|
224 |
- Test::ModuleVersion::HTTP::Tiny; |
|
225 |
-use strict; |
|
226 |
-use warnings; |
|
227 |
-# ABSTRACT: A small, simple, correct HTTP/1.1 client |
|
228 |
-our $VERSION = '0.016'; # VERSION |
|
229 |
- |
|
230 |
-use Carp (); |
|
231 |
- |
|
232 |
- |
|
233 |
-my @attributes; |
|
234 |
-BEGIN { |
|
235 |
- @attributes = qw(agent default_headers max_redirect max_size proxy timeout); |
|
236 |
- no strict 'refs'; |
|
237 |
- for my $accessor ( @attributes ) { |
|
238 |
- *{$accessor} = sub { |
|
239 |
- @_ > 1 ? $_[0]->{$accessor} = $_[1] : $_[0]->{$accessor}; |
|
240 |
- }; |
|
241 |
- } |
|
242 |
-} |
|
243 |
- |
|
244 |
-sub new { |
|
245 |
- my($class, %args) = @_; |
|
246 |
- (my $agent = $class) =~ s{::}{-}g; |
|
247 |
- my $self = { |
|
248 |
- agent => $agent . "/" . ($class->VERSION || 0), |
|
249 |
- max_redirect => 5, |
|
250 |
- timeout => 60, |
|
251 |
- }; |
|
252 |
- for my $key ( @attributes ) { |
|
253 |
- $self->{$key} = $args{$key} if exists $args{$key} |
|
254 |
- } |
|
255 |
- |
|
256 |
- # Never override proxy argument as this breaks backwards compat. |
|
257 |
- if (!exists $self->{proxy} && (my $http_proxy = $ENV{http_proxy})) { |
|
258 |
- if ($http_proxy =~ m{\Ahttp://[^/?#:@]+:\d+/?\z}) { |
|
259 |
- $self->{proxy} = $http_proxy; |
|
260 |
- } |
|
261 |
- else { |
|
262 |
- Carp::croak(qq{Environment 'http_proxy' must be in format http://<host>:<port>/\n}); |
|
263 |
- } |
|
264 |
- } |
|
265 |
- |
|
266 |
- return bless $self, $class; |
|
267 |
-} |
|
268 |
- |
|
269 |
- |
|
270 |
-for my $sub_name ( qw/get head put post delete/ ) { |
|
271 |
- my $req_method = uc $sub_name; |
|
272 |
- no strict 'refs'; |
|
273 |
- eval <<"HERE"; |
|
274 |
- sub $sub_name { |
|
275 |
- my (\$self, \$url, \$args) = \@_; |
|
276 |
- \@_ == 2 || (\@_ == 3 && ref \$args eq 'HASH') |
|
277 |
- or Carp::croak(q/Usage: \$http->$sub_name(URL, [HASHREF])/ . "\n"); |
|
278 |
- return \$self->request('$req_method', \$url, \$args || {}); |
|
279 |
- } |
|
280 |
-HERE |
|
281 |
-} |
|
282 |
- |
|
283 |
- |
|
284 |
-sub post_form { |
|
285 |
- my ($self, $url, $data, $args) = @_; |
|
286 |
- (@_ == 3 || @_ == 4 && ref $args eq 'HASH') |
|
287 |
- or Carp::croak(q/Usage: $http->post_form(URL, DATAREF, [HASHREF])/ . "\n"); |
|
288 |
- |
|
289 |
- my $headers = {}; |
|
290 |
- while ( my ($key, $value) = each %{$args->{headers} || {}} ) { |
|
291 |
- $headers->{lc $key} = $value; |
|
292 |
- } |
|
293 |
- delete $args->{headers}; |
|
294 |
- |
|
295 |
- return $self->request('POST', $url, { |
|
296 |
- %$args, |
|
297 |
- content => $self->www_form_urlencode($data), |
|
298 |
- headers => { |
|
299 |
- %$headers, |
|
300 |
- 'content-type' => 'application/x-www-form-urlencoded' |
|
301 |
- }, |
|
302 |
- } |
|
303 |
- ); |
|
304 |
-} |
|
305 |
- |
|
306 |
- |
|
307 |
-sub mirror { |
|
308 |
- my ($self, $url, $file, $args) = @_; |
|
309 |
- @_ == 3 || (@_ == 4 && ref $args eq 'HASH') |
|
310 |
- or Carp::croak(q/Usage: $http->mirror(URL, FILE, [HASHREF])/ . "\n"); |
|
311 |
- if ( -e $file and my $mtime = (stat($file))[9] ) { |
|
312 |
- $args->{headers}{'if-modified-since'} ||= $self->_http_date($mtime); |
|
313 |
- } |
|
314 |
- my $tempfile = $file . int(rand(2**31)); |
|
315 |
- open my $fh, ">", $tempfile |
|
316 |
- or Carp::croak(qq/Error: Could not open temporary file $tempfile for downloading: $!\n/); |
|
317 |
- binmode $fh; |
|
318 |
- $args->{data_callback} = sub { print {$fh} $_[0] }; |
|
319 |
- my $response = $self->request('GET', $url, $args); |
|
320 |
- close $fh |
|
321 |
- or Carp::croak(qq/Error: Could not close temporary file $tempfile: $!\n/); |
|
322 |
- if ( $response->{success} ) { |
|
323 |
- rename $tempfile, $file |
|
324 |
- or Carp::croak(qq/Error replacing $file with $tempfile: $!\n/); |
|
325 |
- my $lm = $response->{headers}{'last-modified'}; |
|
326 |
- if ( $lm and my $mtime = $self->_parse_http_date($lm) ) { |
|
327 |
- utime $mtime, $mtime, $file; |
|
328 |
- } |
|
329 |
- } |
|
330 |
- $response->{success} ||= $response->{status} eq '304'; |
|
331 |
- unlink $tempfile; |
|
332 |
- return $response; |
|
333 |
-} |
|
334 |
- |
|
335 |
- |
|
336 |
-my %idempotent = map { $_ => 1 } qw/GET HEAD PUT DELETE OPTIONS TRACE/; |
|
337 |
- |
|
338 |
-sub request { |
|
339 |
- my ($self, $method, $url, $args) = @_; |
|
340 |
- @_ == 3 || (@_ == 4 && ref $args eq 'HASH') |
|
341 |
- or Carp::croak(q/Usage: $http->request(METHOD, URL, [HASHREF])/ . "\n"); |
|
342 |
- $args ||= {}; # we keep some state in this during _request |
|
343 |
- |
|
344 |
- # RFC 2616 Section 8.1.4 mandates a single retry on broken socket |
|
345 |
- my $response; |
|
346 |
- for ( 0 .. 1 ) { |
|
347 |
- $response = eval { $self->_request($method, $url, $args) }; |
|
348 |
- last unless $@ && $idempotent{$method} |
|
349 |
- && $@ =~ m{^(?:Socket closed|Unexpected end)}; |
|
350 |
- } |
|
351 |
- |
|
352 |
- if (my $e = "$@") { |
|
353 |
- $response = { |
|
354 |
- success => q{}, |
|
355 |
- status => 599, |
|
356 |
- reason => 'Internal Exception', |
|
357 |
- content => $e, |
|
358 |
- headers => { |
|
359 |
- 'content-type' => 'text/plain', |
|
360 |
- 'content-length' => length $e, |
|
361 |
- } |
|
362 |
- }; |
|
363 |
- } |
|
364 |
- return $response; |
|
365 |
-} |
|
366 |
- |
|
367 |
- |
|
368 |
-sub www_form_urlencode { |
|
369 |
- my ($self, $data) = @_; |
|
370 |
- (@_ == 2 && ref $data) |
|
371 |
- or Carp::croak(q/Usage: $http->www_form_urlencode(DATAREF)/ . "\n"); |
|
372 |
- (ref $data eq 'HASH' || ref $data eq 'ARRAY') |
|
373 |
- or Carp::croak("form data must be a hash or array reference"); |
|
374 |
- |
|
375 |
- my @params = ref $data eq 'HASH' ? %$data : @$data; |
|
376 |
- @params % 2 == 0 |
|
377 |
- or Carp::croak("form data reference must have an even number of terms\n"); |
|
378 |
- |
|
379 |
- my @terms; |
|
380 |
- while( @params ) { |
|
381 |
- my ($key, $value) = splice(@params, 0, 2); |
|
382 |
- if ( ref $value eq 'ARRAY' ) { |
|
383 |
- unshift @params, map { $key => $_ } @$value; |
|
384 |
- } |
|
385 |
- else { |
|
386 |
- push @terms, join("=", map { $self->_uri_escape($_) } $key, $value); |
|
387 |
- } |
|
388 |
- } |
|
389 |
- |
|
390 |
- return join("&", sort @terms); |
|
391 |
-} |
|
392 |
- |
|
393 |
-#--------------------------------------------------------------------------# |
|
394 |
-# private methods |
|
395 |
-#--------------------------------------------------------------------------# |
|
396 |
- |
|
397 |
-my %DefaultPort = ( |
|
398 |
- http => 80, |
|
399 |
- https => 443, |
|
400 |
-); |
|
401 |
- |
|
402 |
-sub _request { |
|
403 |
- my ($self, $method, $url, $args) = @_; |
|
404 |
- |
|
405 |
- my ($scheme, $host, $port, $path_query) = $self->_split_url($url); |
|
406 |
- |
|
407 |
- my $request = { |
|
408 |
- method => $method, |
|
409 |
- scheme => $scheme, |
|
410 |
- host_port => ($port == $DefaultPort{$scheme} ? $host : "$host:$port"), |
|
411 |
- uri => $path_query, |
|
412 |
- headers => {}, |
|
413 |
- }; |
|
414 |
- |
|
415 |
- my $handle = Test::ModuleVersion::HTTP::Tiny::Handle->new(timeout => $self->{timeout}); |
|
416 |
- |
|
417 |
- if ($self->{proxy}) { |
|
418 |
- $request->{uri} = "$scheme://$request->{host_port}$path_query"; |
|
419 |
- die(qq/HTTPS via proxy is not supported\n/) |
|
420 |
- if $request->{scheme} eq 'https'; |
|
421 |
- $handle->connect(($self->_split_url($self->{proxy}))[0..2]); |
|
422 |
- } |
|
423 |
- else { |
|
424 |
- $handle->connect($scheme, $host, $port); |
|
425 |
- } |
|
426 |
- |
|
427 |
- $self->_prepare_headers_and_cb($request, $args); |
|
428 |
- $handle->write_request($request); |
|
429 |
- |
|
430 |
- my $response; |
|
431 |
- do { $response = $handle->read_response_header } |
|
432 |
- until (substr($response->{status},0,1) ne '1'); |
|
433 |
- |
|
434 |
- if ( my @redir_args = $self->_maybe_redirect($request, $response, $args) ) { |
|
435 |
- $handle->close; |
|
436 |
- return $self->_request(@redir_args, $args); |
|
437 |
- } |
|
438 |
- |
|
439 |
- if ($method eq 'HEAD' || $response->{status} =~ /^[23]04/) { |
|
440 |
- # response has no message body |
|
441 |
- } |
|
442 |
- else { |
|
443 |
- my $data_cb = $self->_prepare_data_cb($response, $args); |
|
444 |
- $handle->read_body($data_cb, $response); |
|
445 |
- } |
|
446 |
- |
|
447 |
- $handle->close; |
|
448 |
- $response->{success} = substr($response->{status},0,1) eq '2'; |
|
449 |
- return $response; |
|
450 |
-} |
|
451 |
- |
|
452 |
-sub _prepare_headers_and_cb { |
|
453 |
- my ($self, $request, $args) = @_; |
|
454 |
- |
|
455 |
- for ($self->{default_headers}, $args->{headers}) { |
|
456 |
- next unless defined; |
|
457 |
- while (my ($k, $v) = each %$_) { |
|
458 |
- $request->{headers}{lc $k} = $v; |
|
459 |
- } |
|
460 |
- } |
|
461 |
- $request->{headers}{'host'} = $request->{host_port}; |
|
462 |
- $request->{headers}{'connection'} = "close"; |
|
463 |
- $request->{headers}{'user-agent'} ||= $self->{agent}; |
|
464 |
- |
|
465 |
- if (defined $args->{content}) { |
|
466 |
- $request->{headers}{'content-type'} ||= "application/octet-stream"; |
|
467 |
- if (ref $args->{content} eq 'CODE') { |
|
468 |
- $request->{headers}{'transfer-encoding'} = 'chunked' |
|
469 |
- unless $request->{headers}{'content-length'} |
|
470 |
- || $request->{headers}{'transfer-encoding'}; |
|
471 |
- $request->{cb} = $args->{content}; |
|
472 |
- } |
|
473 |
- else { |
|
474 |
- my $content = $args->{content}; |
|
475 |
- if ( $] ge '5.008' ) { |
|
476 |
- utf8::downgrade($content, 1) |
|
477 |
- or die(qq/Wide character in request message body\n/); |
|
478 |
- } |
|
479 |
- $request->{headers}{'content-length'} = length $content |
|
480 |
- unless $request->{headers}{'content-length'} |
|
481 |
- || $request->{headers}{'transfer-encoding'}; |
|
482 |
- $request->{cb} = sub { substr $content, 0, length $content, '' }; |
|
483 |
- } |
|
484 |
- $request->{trailer_cb} = $args->{trailer_callback} |
|
485 |
- if ref $args->{trailer_callback} eq 'CODE'; |
|
486 |
- } |
|
487 |
- return; |
|
488 |
-} |
|
489 |
- |
|
490 |
-sub _prepare_data_cb { |
|
491 |
- my ($self, $response, $args) = @_; |
|
492 |
- my $data_cb = $args->{data_callback}; |
|
493 |
- $response->{content} = ''; |
|
494 |
- |
|
495 |
- if (!$data_cb || $response->{status} !~ /^2/) { |
|
496 |
- if (defined $self->{max_size}) { |
|
497 |
- $data_cb = sub { |
|
498 |
- $_[1]->{content} .= $_[0]; |
|
499 |
- die(qq/Size of response body exceeds the maximum allowed of $self->{max_size}\n/) |
|
500 |
- if length $_[1]->{content} > $self->{max_size}; |
|
501 |
- }; |
|
502 |
- } |
|
503 |
- else { |
|
504 |
- $data_cb = sub { $_[1]->{content} .= $_[0] }; |
|
505 |
- } |
|
506 |
- } |
|
507 |
- return $data_cb; |
|
508 |
-} |
|
509 |
- |
|
510 |
-sub _maybe_redirect { |
|
511 |
- my ($self, $request, $response, $args) = @_; |
|
512 |
- my $headers = $response->{headers}; |
|
513 |
- my ($status, $method) = ($response->{status}, $request->{method}); |
|
514 |
- if (($status eq '303' or ($status =~ /^30[127]/ && $method =~ /^GET|HEAD$/)) |
|
515 |
- and $headers->{location} |
|
516 |
- and ++$args->{redirects} <= $self->{max_redirect} |
|
517 |
- ) { |
|
518 |
- my $location = ($headers->{location} =~ /^\//) |
|
519 |
- ? "$request->{scheme}://$request->{host_port}$headers->{location}" |
|
520 |
- : $headers->{location} ; |
|
521 |
- return (($status eq '303' ? 'GET' : $method), $location); |
|
522 |
- } |
|
523 |
- return; |
|
524 |
-} |
|
525 |
- |
|
526 |
-sub _split_url { |
|
527 |
- my $url = pop; |
|
528 |
- |
|
529 |
- # URI regex adapted from the URI module |
|
530 |
- my ($scheme, $authority, $path_query) = $url =~ m<\A([^:/?#]+)://([^/?#]*)([^#]*)> |
|
531 |
- or die(qq/Cannot parse URL: '$url'\n/); |
|
532 |
- |
|
533 |
- $scheme = lc $scheme; |
|
534 |
- $path_query = "/$path_query" unless $path_query =~ m<\A/>; |
|
535 |
- |
|
536 |
- my $host = (length($authority)) ? lc $authority : 'localhost'; |
|
537 |
- $host =~ s/\A[^@]*@//; # userinfo |
|
538 |
- my $port = do { |
|
539 |
- $host =~ s/:([0-9]*)\z// && length $1 |
|
540 |
- ? $1 |
|
541 |
- : ($scheme eq 'http' ? 80 : $scheme eq 'https' ? 443 : undef); |
|
542 |
- }; |
|
543 |
- |
|
544 |
- return ($scheme, $host, $port, $path_query); |
|
545 |
-} |
|
546 |
- |
|
547 |
-# Date conversions adapted from HTTP::Date |
|
548 |
-my $DoW = "Sun|Mon|Tue|Wed|Thu|Fri|Sat"; |
|
549 |
-my $MoY = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec"; |
|
550 |
-sub _http_date { |
|
551 |
- my ($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime($_[1]); |
|
552 |
- return sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT", |
|
553 |
- substr($DoW,$wday*4,3), |
|
554 |
- $mday, substr($MoY,$mon*4,3), $year+1900, |
|
555 |
- $hour, $min, $sec |
|
556 |
- ); |
|
557 |
-} |
|
558 |
- |
|
559 |
-sub _parse_http_date { |
|
560 |
- my ($self, $str) = @_; |
|
561 |
- require Time::Local; |
|
562 |
- my @tl_parts; |
|
563 |
- if ($str =~ /^[SMTWF][a-z]+, +(\d{1,2}) ($MoY) +(\d\d\d\d) +(\d\d):(\d\d):(\d\d) +GMT$/) { |
|
564 |
- @tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3); |
|
565 |
- } |
|
566 |
- elsif ($str =~ /^[SMTWF][a-z]+, +(\d\d)-($MoY)-(\d{2,4}) +(\d\d):(\d\d):(\d\d) +GMT$/ ) { |
|
567 |
- @tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3); |
|
568 |
- } |
|
569 |
- elsif ($str =~ /^[SMTWF][a-z]+ +($MoY) +(\d{1,2}) +(\d\d):(\d\d):(\d\d) +(?:[^0-9]+ +)?(\d\d\d\d)$/ ) { |
|
570 |
- @tl_parts = ($5, $4, $3, $2, (index($MoY,$1)/4), $6); |
|
571 |
- } |
|
572 |
- return eval { |
|
573 |
- my $t = @tl_parts ? Time::Local::timegm(@tl_parts) : -1; |
|
574 |
- $t < 0 ? undef : $t; |
|
575 |
- }; |
|
576 |
-} |
|
577 |
- |
|
578 |
-# URI escaping adapted from URI::Escape |
|
579 |
-# c.f. http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 |
|
580 |
-# perl 5.6 ready UTF-8 encoding adapted from Test::ModuleVersion::JSON::PP |
|
581 |
-my %escapes = map { chr($_) => sprintf("%%%02X", $_) } 0..255; |
|
582 |
-$escapes{' '}="+"; |
|
583 |
-my $unsafe_char = qr/[^A-Za-z0-9\-\._~]/; |
|
584 |
- |
|
585 |
-sub _uri_escape { |
|
586 |
- my ($self, $str) = @_; |
|
587 |
- if ( $] ge '5.008' ) { |
|
588 |
- utf8::encode($str); |
|
589 |
- } |
|
590 |
- else { |
|
591 |
- $str = pack("U*", unpack("C*", $str)) # UTF-8 encode a byte string |
|
592 |
- if ( length $str == do { use bytes; length $str } ); |
|
593 |
- $str = pack("C*", unpack("C*", $str)); # clear UTF-8 flag |
|
594 |
- } |
|
595 |
- $str =~ s/($unsafe_char)/$escapes{$1}/ge; |
|
596 |
- return $str; |
|
597 |
-} |
|
598 |
- |
|
599 |
-package |
|
600 |
- Test::ModuleVersion::HTTP::Tiny::Handle; # hide from PAUSE/indexers |
|
601 |
-use strict; |
|
602 |
-use warnings; |
|
603 |
- |
|
604 |
-use Errno qw[EINTR EPIPE]; |
|
605 |
-use IO::Socket qw[SOCK_STREAM]; |
|
606 |
- |
|
607 |
-sub BUFSIZE () { 32768 } |
|
608 |
- |
|
609 |
-my $Printable = sub { |
|
610 |
- local $_ = shift; |
|
611 |
- s/\r/\\r/g; |
|
612 |
- s/\n/\\n/g; |
|
613 |
- s/\t/\\t/g; |
|
614 |
- s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge; |
|
615 |
- $_; |
|
616 |
-}; |
|
617 |
- |
|
618 |
-my $Token = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]/; |
|
619 |
- |
|
620 |
-sub new { |
|
621 |
- my ($class, %args) = @_; |
|
622 |
- return bless { |
|
623 |
- rbuf => '', |
|
624 |
- timeout => 60, |
|
625 |
- max_line_size => 16384, |
|
626 |
- max_header_lines => 64, |
|
627 |
- %args |
|
628 |
- }, $class; |
|
629 |
-} |
|
630 |
- |
|
631 |
-my $ssl_verify_args = { |
|
632 |
- check_cn => "when_only", |
|
633 |
- wildcards_in_alt => "anywhere", |
|
634 |
- wildcards_in_cn => "anywhere" |
|
635 |
-}; |
|
636 |
- |
|
637 |
-sub connect { |
|
638 |
- @_ == 4 || die(q/Usage: $handle->connect(scheme, host, port)/ . "\n"); |
|
639 |
- my ($self, $scheme, $host, $port) = @_; |
|
640 |
- |
|
641 |
- if ( $scheme eq 'https' ) { |
|
642 |
- eval "require IO::Socket::SSL" |
|
643 |
- unless exists $INC{'IO/Socket/SSL.pm'}; |
|
644 |
- die(qq/IO::Socket::SSL must be installed for https support\n/) |
|
645 |
- unless $INC{'IO/Socket/SSL.pm'}; |
|
646 |
- } |
|
647 |
- elsif ( $scheme ne 'http' ) { |
|
648 |
- die(qq/Unsupported URL scheme '$scheme'\n/); |
|
649 |
- } |
|
650 |
- |
|
651 |
- $self->{fh} = 'IO::Socket::INET'->new( |
|
652 |
- PeerHost => $host, |
|
653 |
- PeerPort => $port, |
|
654 |
- Proto => 'tcp', |
|
655 |
- Type => SOCK_STREAM, |
|
656 |
- Timeout => $self->{timeout} |
|
657 |
- ) or die(qq/Could not connect to '$host:$port': $@\n/); |
|
658 |
- |
|
659 |
- binmode($self->{fh}) |
|
660 |
- or die(qq/Could not binmode() socket: '$!'\n/); |
|
661 |
- |
|
662 |
- if ( $scheme eq 'https') { |
|
663 |
- IO::Socket::SSL->start_SSL($self->{fh}); |
|
664 |
- ref($self->{fh}) eq 'IO::Socket::SSL' |
|
665 |
- or die(qq/SSL connection failed for $host\n/); |
|
666 |
- $self->{fh}->verify_hostname( $host, $ssl_verify_args ) |
|
667 |
- or die(qq/SSL certificate not valid for $host\n/); |
|
668 |
- } |
|
669 |
- |
|
670 |
- $self->{host} = $host; |
|
671 |
- $self->{port} = $port; |
|
672 |
- |
|
673 |
- return $self; |
|
674 |
-} |
|
675 |
- |
|
676 |
-sub close { |
|
677 |
- @_ == 1 || die(q/Usage: $handle->close()/ . "\n"); |
|
678 |
- my ($self) = @_; |
|
679 |
- CORE::close($self->{fh}) |
|
680 |
- or die(qq/Could not close socket: '$!'\n/); |
|
681 |
-} |
|
682 |
- |
|
683 |
-sub write { |
|
684 |
- @_ == 2 || die(q/Usage: $handle->write(buf)/ . "\n"); |
|
685 |
- my ($self, $buf) = @_; |
|
686 |
- |
|
687 |
- if ( $] ge '5.008' ) { |
|
688 |
- utf8::downgrade($buf, 1) |
|
689 |
- or die(qq/Wide character in write()\n/); |
|
690 |
- } |
|
691 |
- |
|
692 |
- my $len = length $buf; |
|
693 |
- my $off = 0; |
|
694 |
- |
|
695 |
- local $SIG{PIPE} = 'IGNORE'; |
|
696 |
- |
|
697 |
- while () { |
|
698 |
- $self->can_write |
|
699 |
- or die(qq/Timed out while waiting for socket to become ready for writing\n/); |
|
700 |
- my $r = syswrite($self->{fh}, $buf, $len, $off); |
|
701 |
- if (defined $r) { |
|
702 |
- $len -= $r; |
|
703 |
- $off += $r; |
|
704 |
- last unless $len > 0; |
|
705 |
- } |
|
706 |
- elsif ($! == EPIPE) { |
|
707 |
- die(qq/Socket closed by remote server: $!\n/); |
|
708 |
- } |
|
709 |
- elsif ($! != EINTR) { |
|
710 |
- die(qq/Could not write to socket: '$!'\n/); |
|
711 |
- } |
|
712 |
- } |
|
713 |
- return $off; |
|
714 |
-} |
|
715 |
- |
|
716 |
-sub read { |
|
717 |
- @_ == 2 || @_ == 3 || die(q/Usage: $handle->read(len [, allow_partial])/ . "\n"); |
|
718 |
- my ($self, $len, $allow_partial) = @_; |
|
719 |
- |
|
720 |
- my $buf = ''; |
|
721 |
- my $got = length $self->{rbuf}; |
|
722 |
- |
|
723 |
- if ($got) { |
|
724 |
- my $take = ($got < $len) ? $got : $len; |
|
725 |
- $buf = substr($self->{rbuf}, 0, $take, ''); |
|
726 |
- $len -= $take; |
|
727 |
- } |
|
728 |
- |
|
729 |
- while ($len > 0) { |
|
730 |
- $self->can_read |
|
731 |
- or die(q/Timed out while waiting for socket to become ready for reading/ . "\n"); |
|
732 |
- my $r = sysread($self->{fh}, $buf, $len, length $buf); |
|
733 |
- if (defined $r) { |
|
734 |
- last unless $r; |
|
735 |
- $len -= $r; |
|
736 |
- } |
|
737 |
- elsif ($! != EINTR) { |
|
738 |
- die(qq/Could not read from socket: '$!'\n/); |
|
739 |
- } |
|
740 |
- } |
|
741 |
- if ($len && !$allow_partial) { |
|
742 |
- die(qq/Unexpected end of stream\n/); |
|
743 |
- } |
|
744 |
- return $buf; |
|
745 |
-} |
|
746 |
- |
|
747 |
-sub readline { |
|
748 |
- @_ == 1 || die(q/Usage: $handle->readline()/ . "\n"); |
|
749 |
- my ($self) = @_; |
|
750 |
- |
|
751 |
- while () { |
|
752 |
- if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) { |
|
753 |
- return $1; |
|
754 |
- } |
|
755 |
- if (length $self->{rbuf} >= $self->{max_line_size}) { |
|
756 |
- die(qq/Line size exceeds the maximum allowed size of $self->{max_line_size}\n/); |
|
757 |
- } |
|
758 |
- $self->can_read |
|
759 |
- or die(qq/Timed out while waiting for socket to become ready for reading\n/); |
|
760 |
- my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf}); |
|
761 |
- if (defined $r) { |
|
762 |
- last unless $r; |
|
763 |
- } |
|
764 |
- elsif ($! != EINTR) { |
|
765 |
- die(qq/Could not read from socket: '$!'\n/); |
|
766 |
- } |
|
767 |
- } |
|
768 |
- die(qq/Unexpected end of stream while looking for line\n/); |
|
769 |
-} |
|
770 |
- |
|
771 |
-sub read_header_lines { |
|
772 |
- @_ == 1 || @_ == 2 || die(q/Usage: $handle->read_header_lines([headers])/ . "\n"); |
|
773 |
- my ($self, $headers) = @_; |
|
774 |
- $headers ||= {}; |
|
775 |
- my $lines = 0; |
|
776 |
- my $val; |
|
777 |
- |
|
778 |
- while () { |
|
779 |
- my $line = $self->readline; |
|
780 |
- |
|
781 |
- if (++$lines >= $self->{max_header_lines}) { |
|
782 |
- die(qq/Header lines exceeds maximum number allowed of $self->{max_header_lines}\n/); |
|
783 |
- } |
|
784 |
- elsif ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) { |
|
785 |
- my ($field_name) = lc $1; |
|
786 |
- if (exists $headers->{$field_name}) { |
|
787 |
- for ($headers->{$field_name}) { |
|
788 |
- $_ = [$_] unless ref $_ eq "ARRAY"; |
|
789 |
- push @$_, $2; |
|
790 |
- $val = \$_->[-1]; |
|
791 |
- } |
|
792 |
- } |
|
793 |
- else { |
|
794 |
- $val = \($headers->{$field_name} = $2); |
|
795 |
- } |
|
796 |
- } |
|
797 |
- elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) { |
|
798 |
- $val |
|
799 |
- or die(qq/Unexpected header continuation line\n/); |
|
800 |
- next unless length $1; |
|
801 |
- $$val .= ' ' if length $$val; |
|
802 |
- $$val .= $1; |
|
803 |
- } |
|
804 |
- elsif ($line =~ /\A \x0D?\x0A \z/x) { |
|
805 |
- last; |
|
806 |
- } |
|
807 |
- else { |
|
808 |
- die(q/Malformed header line: / . $Printable->($line) . "\n"); |
|
809 |
- } |
|
810 |
- } |
|
811 |
- return $headers; |
|
812 |
-} |
|
813 |
- |
|
814 |
-sub write_request { |
|
815 |
- @_ == 2 || die(q/Usage: $handle->write_request(request)/ . "\n"); |
|
816 |
- my($self, $request) = @_; |
|
817 |
- $self->write_request_header(@{$request}{qw/method uri headers/}); |
|
818 |
- $self->write_body($request) if $request->{cb}; |
|
819 |
- return; |
|
820 |
-} |
|
821 |
- |
|
822 |
-my %HeaderCase = ( |
|
823 |
- 'content-md5' => 'Content-MD5', |
|
824 |
- 'etag' => 'ETag', |
|
825 |
- 'te' => 'TE', |
|
826 |
- 'www-authenticate' => 'WWW-Authenticate', |
|
827 |
- 'x-xss-protection' => 'X-XSS-Protection', |
|
828 |
-); |
|
829 |
- |
|
830 |
-sub write_header_lines { |
|
831 |
- (@_ == 2 && ref $_[1] eq 'HASH') || die(q/Usage: $handle->write_header_lines(headers)/ . "\n"); |
|
832 |
- my($self, $headers) = @_; |
|
833 |
- |
|
834 |
- my $buf = ''; |
|
835 |
- while (my ($k, $v) = each %$headers) { |
|
836 |
- my $field_name = lc $k; |
|
837 |
- if (exists $HeaderCase{$field_name}) { |
|
838 |
- $field_name = $HeaderCase{$field_name}; |
|
839 |
- } |
|
840 |
- else { |
|
841 |
- $field_name =~ /\A $Token+ \z/xo |
|
842 |
- or die(q/Invalid HTTP header field name: / . $Printable->($field_name) . "\n"); |
|
843 |
- $field_name =~ s/\b(\w)/\u$1/g; |
|
844 |
- $HeaderCase{lc $field_name} = $field_name; |
|
845 |
- } |
|
846 |
- for (ref $v eq 'ARRAY' ? @$v : $v) { |
|
847 |
- /[^\x0D\x0A]/ |
|
848 |
- or die(qq/Invalid HTTP header field value ($field_name): / . $Printable->($_). "\n"); |
|
849 |
- $buf .= "$field_name: $_\x0D\x0A"; |
|
850 |
- } |
|
851 |
- } |
|
852 |
- $buf .= "\x0D\x0A"; |
|
853 |
- return $self->write($buf); |
|
854 |
-} |
|
855 |
- |
|
856 |
-sub read_body { |
|
857 |
- @_ == 3 || die(q/Usage: $handle->read_body(callback, response)/ . "\n"); |
|
858 |
- my ($self, $cb, $response) = @_; |
|
859 |
- my $te = $response->{headers}{'transfer-encoding'} || ''; |
|
860 |
- if ( grep { /chunked/i } ( ref $te eq 'ARRAY' ? @$te : $te ) ) { |
|
861 |
- $self->read_chunked_body($cb, $response); |
|
862 |
- } |
|
863 |
- else { |
|
864 |
- $self->read_content_body($cb, $response); |
|
865 |
- } |
|
866 |
- return; |
|
867 |
-} |
|
868 |
- |
|
869 |
-sub write_body { |
|
870 |
- @_ == 2 || die(q/Usage: $handle->write_body(request)/ . "\n"); |
|
871 |
- my ($self, $request) = @_; |
|
872 |
- if ($request->{headers}{'content-length'}) { |
|
873 |
- return $self->write_content_body($request); |
|
874 |
- } |
|
875 |
- else { |
|
876 |
- return $self->write_chunked_body($request); |
|
877 |
- } |
|
878 |
-} |
|
879 |
- |
|
880 |
-sub read_content_body { |
|
881 |
- @_ == 3 || @_ == 4 || die(q/Usage: $handle->read_content_body(callback, response, [read_length])/ . "\n"); |
|
882 |
- my ($self, $cb, $response, $content_length) = @_; |
|
883 |
- $content_length ||= $response->{headers}{'content-length'}; |
|
884 |
- |
|
885 |
- if ( $content_length ) { |
|
886 |
- my $len = $content_length; |
|
887 |
- while ($len > 0) { |
|
888 |
- my $read = ($len > BUFSIZE) ? BUFSIZE : $len; |
|
889 |
- $cb->($self->read($read, 0), $response); |
|
890 |
- $len -= $read; |
|
891 |
- } |
|
892 |
- } |
|
893 |
- else { |
|
894 |
- my $chunk; |
|
895 |
- $cb->($chunk, $response) while length( $chunk = $self->read(BUFSIZE, 1) ); |
|
896 |
- } |
|
897 |
- |
|
898 |
- return; |
|
899 |
-} |
|
900 |
- |
|
901 |
-sub write_content_body { |
|
902 |
- @_ == 2 || die(q/Usage: $handle->write_content_body(request)/ . "\n"); |
|
903 |
- my ($self, $request) = @_; |
|
904 |
- |
|
905 |
- my ($len, $content_length) = (0, $request->{headers}{'content-length'}); |
|
906 |
- while () { |
|
907 |
- my $data = $request->{cb}->(); |
|
908 |
- |
|
909 |
- defined $data && length $data |
|
910 |
- or last; |
|
911 |
- |
|
912 |
- if ( $] ge '5.008' ) { |
|
913 |
- utf8::downgrade($data, 1) |
|
914 |
- or die(qq/Wide character in write_content()\n/); |
|
915 |
- } |
|
916 |
- |
|
917 |
- $len += $self->write($data); |
|
918 |
- } |
|
919 |
- |
|
920 |
- $len == $content_length |
|
921 |
- or die(qq/Content-Length missmatch (got: $len expected: $content_length)\n/); |
|
922 |
- |
|
923 |
- return $len; |
|
924 |
-} |
|
925 |
- |
|
926 |
-sub read_chunked_body { |
|
927 |
- @_ == 3 || die(q/Usage: $handle->read_chunked_body(callback, $response)/ . "\n"); |
|
928 |
- my ($self, $cb, $response) = @_; |
|
929 |
- |
|
930 |
- while () { |
|
931 |
- my $head = $self->readline; |
|
932 |
- |
|
933 |
- $head =~ /\A ([A-Fa-f0-9]+)/x |
|
934 |
- or die(q/Malformed chunk head: / . $Printable->($head) . "\n"); |
|
935 |
- |
|
936 |
- my $len = hex($1) |
|
937 |
- or last; |
|
938 |
- |
|
939 |
- $self->read_content_body($cb, $response, $len); |
|
940 |
- |
|
941 |
- $self->read(2) eq "\x0D\x0A" |
|
942 |
- or die(qq/Malformed chunk: missing CRLF after chunk data\n/); |
|
943 |
- } |
|
944 |
- $self->read_header_lines($response->{headers}); |
|
945 |
- return; |
|
946 |
-} |
|
947 |
- |
|
948 |
-sub write_chunked_body { |
|
949 |
- @_ == 2 || die(q/Usage: $handle->write_chunked_body(request)/ . "\n"); |
|
950 |
- my ($self, $request) = @_; |
|
951 |
- |
|
952 |
- my $len = 0; |
|
953 |
- while () { |
|
954 |
- my $data = $request->{cb}->(); |
|
955 |
- |
|
956 |
- defined $data && length $data |
|
957 |
- or last; |
|
958 |
- |
|
959 |
- if ( $] ge '5.008' ) { |
|
960 |
- utf8::downgrade($data, 1) |
|
961 |
- or die(qq/Wide character in write_chunked_body()\n/); |
|
962 |
- } |
|
963 |
- |
|
964 |
- $len += length $data; |
|
965 |
- |
|
966 |
- my $chunk = sprintf '%X', length $data; |
|
967 |
- $chunk .= "\x0D\x0A"; |
|
968 |
- $chunk .= $data; |
|
969 |
- $chunk .= "\x0D\x0A"; |
|
970 |
- |
|
971 |
- $self->write($chunk); |
|
972 |
- } |
|
973 |
- $self->write("0\x0D\x0A"); |
|
974 |
- $self->write_header_lines($request->{trailer_cb}->()) |
|
975 |
- if ref $request->{trailer_cb} eq 'CODE'; |
|
976 |
- return $len; |
|
977 |
-} |
|
978 |
- |
|
979 |
-sub read_response_header { |
|
980 |
- @_ == 1 || die(q/Usage: $handle->read_response_header()/ . "\n"); |
|
981 |
- my ($self) = @_; |
|
982 |
- |
|
983 |
- my $line = $self->readline; |
|
984 |
- |
|
985 |
- $line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) [\x09\x20]+ ([^\x0D\x0A]*) \x0D?\x0A/x |
|
986 |
- or die(q/Malformed Status-Line: / . $Printable->($line). "\n"); |
|
987 |
- |
|
988 |
- my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4); |
|
989 |
- |
|
990 |
- die (qq/Unsupported HTTP protocol: $protocol\n/) |
|
991 |
- unless $version =~ /0*1\.0*[01]/; |
|
992 |
- |
|
993 |
- return { |
|
994 |
- status => $status, |
|
995 |
- reason => $reason, |
|
996 |
- headers => $self->read_header_lines, |
|
997 |
- protocol => $protocol, |
|
998 |
- }; |
|
999 |
-} |
|
1000 |
- |
|
1001 |
-sub write_request_header { |
|
1002 |
- @_ == 4 || die(q/Usage: $handle->write_request_header(method, request_uri, headers)/ . "\n"); |
|
1003 |
- my ($self, $method, $request_uri, $headers) = @_; |
|
1004 |
- |
|
1005 |
- return $self->write("$method $request_uri HTTP/1.1\x0D\x0A") |
|
1006 |
- + $self->write_header_lines($headers); |
|
1007 |
-} |
|
1008 |
- |
|
1009 |
-sub _do_timeout { |
|
1010 |
- my ($self, $type, $timeout) = @_; |
|
1011 |
- $timeout = $self->{timeout} |
|
1012 |
- unless defined $timeout && $timeout >= 0; |
|
1013 |
- |
|
1014 |
- my $fd = fileno $self->{fh}; |
|
1015 |
- defined $fd && $fd >= 0 |
|
1016 |
- or die(qq/select(2): 'Bad file descriptor'\n/); |
|
1017 |
- |
|
1018 |
- my $initial = time; |
|
1019 |
- my $pending = $timeout; |
|
1020 |
- my $nfound; |
|
1021 |
- |
|
1022 |
- vec(my $fdset = '', $fd, 1) = 1; |
|
1023 |
- |
|
1024 |
- while () { |
|
1025 |
- $nfound = ($type eq 'read') |
|
1026 |
- ? select($fdset, undef, undef, $pending) |
|
1027 |
- : select(undef, $fdset, undef, $pending) ; |
|
1028 |
- if ($nfound == -1) { |
|
1029 |
- $! == EINTR |
|
1030 |
- or die(qq/select(2): '$!'\n/); |
|
1031 |
- redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0; |
|
1032 |
- $nfound = 0; |
|
1033 |
- } |
|
1034 |
- last; |
|
1035 |
- } |
|
1036 |
- $! = 0; |
|
1037 |
- return $nfound; |
|
1038 |
-} |
|
1039 |
- |
|
1040 |
-sub can_read { |
|
1041 |
- @_ == 1 || @_ == 2 || die(q/Usage: $handle->can_read([timeout])/ . "\n"); |
|
1042 |
- my $self = shift; |
|
1043 |
- return $self->_do_timeout('read', @_) |
|
1044 |
-} |
|
1045 |
- |
|
1046 |
-sub can_write { |
|
1047 |
- @_ == 1 || @_ == 2 || die(q/Usage: $handle->can_write([timeout])/ . "\n"); |
|
1048 |
- my $self = shift; |
|
1049 |
- return $self->_do_timeout('write', @_) |
|
1050 |
-} |
|
1051 |
- |
|
1052 |
-no warnings 'once'; |
|
1053 |
-package Test::ModuleVersion::JSON::PP; |
|
1054 |
-# JSON-2.0 |
|
1055 |
- |
|
1056 |
-use 5.005; |
|
1057 |
-use strict; |
|
1058 |
-use base qw(Exporter); |
|
1059 |
-use overload (); |
|
1060 |
- |
|
1061 |
-use Carp (); |
|
1062 |
-use B (); |
|
1063 |
-#use Devel::Peek; |
|
1064 |
- |
|
1065 |
-$Test::ModuleVersion::JSON::PP::VERSION = '2.27200'; |
|
1066 |
- |
|
1067 |
-@Test::ModuleVersion::JSON::PP::EXPORT = qw(encode_json decode_json from_json to_json); |
|
1068 |
- |
|
1069 |
-# instead of hash-access, i tried index-access for speed. |
|
1070 |
-# but this method is not faster than what i expected. so it will be changed. |
|
1071 |
- |
|
1072 |
-use constant P_ASCII => 0; |
|
1073 |
-use constant P_LATIN1 => 1; |
|
1074 |
-use constant P_UTF8 => 2; |
|
1075 |
-use constant P_INDENT => 3; |
|
1076 |
-use constant P_CANONICAL => 4; |
|
1077 |
-use constant P_SPACE_BEFORE => 5; |
|
1078 |
-use constant P_SPACE_AFTER => 6; |
|
1079 |
-use constant P_ALLOW_NONREF => 7; |
|
1080 |
-use constant P_SHRINK => 8; |
|
1081 |
-use constant P_ALLOW_BLESSED => 9; |
|
1082 |
-use constant P_CONVERT_BLESSED => 10; |
|
1083 |
-use constant P_RELAXED => 11; |
|
1084 |
- |
|
1085 |
-use constant P_LOOSE => 12; |
|
1086 |
-use constant P_ALLOW_BIGNUM => 13; |
|
1087 |
-use constant P_ALLOW_BAREKEY => 14; |
|
1088 |
-use constant P_ALLOW_SINGLEQUOTE => 15; |
|
1089 |
-use constant P_ESCAPE_SLASH => 16; |
|
1090 |
-use constant P_AS_NONBLESSED => 17; |
|
1091 |
- |
|
1092 |
-use constant P_ALLOW_UNKNOWN => 18; |
|
1093 |
- |
|
1094 |
-use constant OLD_PERL => $] < 5.008 ? 1 : 0; |
|
1095 |
- |
|
1096 |
-BEGIN { |
|
1097 |
- my @xs_compati_bit_properties = qw( |
|
1098 |
- latin1 ascii utf8 indent canonical space_before space_after allow_nonref shrink |
|
1099 |
- allow_blessed convert_blessed relaxed allow_unknown |
|
1100 |
- ); |
|
1101 |
- my @pp_bit_properties = qw( |
|
1102 |
- allow_singlequote allow_bignum loose |
|
1103 |
- allow_barekey escape_slash as_nonblessed |
|
1104 |
- ); |
|
1105 |
- |
|
1106 |
- # Perl version check, Unicode handling is enable? |
|
1107 |
- # Helper module sets @Test::ModuleVersion::JSON::PP::_properties. |
|
1108 |
- if ($] < 5.008 ) { |
|
1109 |
- my $helper = $] >= 5.006 ? 'Test::ModuleVersion::JSON::PP::Compat5006' : 'Test::ModuleVersion::JSON::PP::Compat5005'; |
|
1110 |
- eval qq| require $helper |; |
|
1111 |
- if ($@) { Carp::croak $@; } |
|
1112 |
- } |
|
1113 |
- |
|
1114 |
- for my $name (@xs_compati_bit_properties, @pp_bit_properties) { |
|
1115 |
- my $flag_name = 'P_' . uc($name); |
|
1116 |
- |
|
1117 |
- eval qq/ |
|
1118 |
- sub $name { |
|
1119 |
- my \$enable = defined \$_[1] ? \$_[1] : 1; |
|
1120 |
- |
|
1121 |
- if (\$enable) { |
|
1122 |
- \$_[0]->{PROPS}->[$flag_name] = 1; |
|
1123 |
- } |
|
1124 |
- else { |
|
1125 |
- \$_[0]->{PROPS}->[$flag_name] = 0; |
|
1126 |
- } |
|
1127 |
- |
|
1128 |
- \$_[0]; |
|
1129 |
- } |
|
1130 |
- |
|
1131 |
- sub get_$name { |
|
1132 |
- \$_[0]->{PROPS}->[$flag_name] ? 1 : ''; |
|
1133 |
- } |
|
1134 |
- /; |
|
1135 |
- } |
|
1136 |
- |
|
1137 |
-} |
|
1138 |
- |
|
1139 |
- |
|
1140 |
- |
|
1141 |
-# Functions |
|
1142 |
- |
|
1143 |
-my %encode_allow_method |
|
1144 |
- = map {($_ => 1)} qw/utf8 pretty allow_nonref latin1 self_encode escape_slash |
|
1145 |
- allow_blessed convert_blessed indent indent_length allow_bignum |
|
1146 |
- as_nonblessed |
|
1147 |
- /; |
|
1148 |
-my %decode_allow_method |
|
1149 |
- = map {($_ => 1)} qw/utf8 allow_nonref loose allow_singlequote allow_bignum |
|
1150 |
- allow_barekey max_size relaxed/; |
|
1151 |
- |
|
1152 |
- |
|
1153 |
-my $JSON; # cache |
|
1154 |
- |
|
1155 |
-sub encode_json ($) { # encode |
|
1156 |
- ($JSON ||= __PACKAGE__->new->utf8)->encode(@_); |
|
1157 |
-} |
|
1158 |
- |
|
1159 |
- |
|
1160 |
-sub decode_json { # decode |
|
1161 |
- ($JSON ||= __PACKAGE__->new->utf8)->decode(@_); |
|
1162 |
-} |
|
1163 |
- |
|
1164 |
-# Obsoleted |
|
1165 |
- |
|
1166 |
-sub to_json($) { |
|
1167 |
- Carp::croak ("Test::ModuleVersion::JSON::PP::to_json has been renamed to encode_json."); |
|
1168 |
-} |
|
1169 |
- |
|
1170 |
- |
|
1171 |
-sub from_json($) { |
|
1172 |
- Carp::croak ("Test::ModuleVersion::JSON::PP::from_json has been renamed to decode_json."); |
|
1173 |
-} |
|
1174 |
- |
|
1175 |
- |
|
1176 |
-# Methods |
|
1177 |
- |
|
1178 |
-sub new { |
|
1179 |
- my $class = shift; |
|
1180 |
- my $self = { |
|
1181 |
- max_depth => 512, |
|
1182 |
- max_size => 0, |
|
1183 |
- indent => 0, |
|
1184 |
- FLAGS => 0, |
|
1185 |
- fallback => sub { encode_error('Invalid value. JSON can only reference.') }, |
|
1186 |
- indent_length => 3, |
|
1187 |
- }; |
|
1188 |
- |
|
1189 |
- bless $self, $class; |
|
1190 |
-} |
|
1191 |
- |
|
1192 |
- |
|
1193 |
-sub encode { |
|
1194 |
- return $_[0]->PP_encode_json($_[1]); |
|
1195 |
-} |
|
1196 |
- |
|
1197 |
- |
|
1198 |
-sub decode { |
|
1199 |
- return $_[0]->PP_decode_json($_[1], 0x00000000); |
|
1200 |
-} |
|
1201 |
- |
|
1202 |
- |
|
1203 |
-sub decode_prefix { |
|
1204 |
- return $_[0]->PP_decode_json($_[1], 0x00000001); |
|
1205 |
-} |
|
1206 |
- |
|
1207 |
- |
|
1208 |
-# accessor |
|
1209 |
- |
|
1210 |
- |
|
1211 |
-# pretty printing |
|
1212 |
- |
|
1213 |
-sub pretty { |
|
1214 |
- my ($self, $v) = @_; |
|
1215 |
- my $enable = defined $v ? $v : 1; |
|
1216 |
- |
|
1217 |
- if ($enable) { # indent_length(3) for JSON::XS compatibility |
|
1218 |
- $self->indent(1)->indent_length(3)->space_before(1)->space_after(1); |
|
1219 |
- } |
|
1220 |
- else { |
|
1221 |
- $self->indent(0)->space_before(0)->space_after(0); |
|
1222 |
- } |
|
1223 |
- |
|
1224 |
- $self; |
|
1225 |
-} |
|
1226 |
- |
|
1227 |
-# etc |
|
1228 |
- |
|
1229 |
-sub max_depth { |
|
1230 |
- my $max = defined $_[1] ? $_[1] : 0x80000000; |
|
1231 |
- $_[0]->{max_depth} = $max; |
|
1232 |
- $_[0]; |
|
1233 |
-} |
|
1234 |
- |
|
1235 |
- |
|
1236 |
-sub get_max_depth { $_[0]->{max_depth}; } |
|
1237 |
- |
|
1238 |
- |
|
1239 |
-sub max_size { |
|
1240 |
- my $max = defined $_[1] ? $_[1] : 0; |
|
1241 |
- $_[0]->{max_size} = $max; |
|
1242 |
- $_[0]; |
|
1243 |
-} |
|
1244 |
- |
|
1245 |
- |
|
1246 |
-sub get_max_size { $_[0]->{max_size}; } |
|
1247 |
- |
|
1248 |
- |
|
1249 |
-sub filter_json_object { |
|
1250 |
- $_[0]->{cb_object} = defined $_[1] ? $_[1] : 0; |
|
1251 |
- $_[0]->{F_HOOK} = ($_[0]->{cb_object} or $_[0]->{cb_sk_object}) ? 1 : 0; |
|
1252 |
- $_[0]; |
|
1253 |
-} |
|
1254 |
- |
|
1255 |
-sub filter_json_single_key_object { |
|
1256 |
- if (@_ > 1) { |
|
1257 |
- $_[0]->{cb_sk_object}->{$_[1]} = $_[2]; |
|
1258 |
- } |
|
1259 |
- $_[0]->{F_HOOK} = ($_[0]->{cb_object} or $_[0]->{cb_sk_object}) ? 1 : 0; |
|
1260 |
- $_[0]; |
|
1261 |
-} |
|
1262 |
- |
|
1263 |
-sub indent_length { |
|
1264 |
- if (!defined $_[1] or $_[1] > 15 or $_[1] < 0) { |
|
1265 |
- Carp::carp "The acceptable range of indent_length() is 0 to 15."; |
|
1266 |
- } |
|
1267 |
- else { |
|
1268 |
- $_[0]->{indent_length} = $_[1]; |
|
1269 |
- } |
|
1270 |
- $_[0]; |
|
1271 |
-} |
|
1272 |
- |
|
1273 |
-sub get_indent_length { |
|
1274 |
- $_[0]->{indent_length}; |
|
1275 |
-} |
|
1276 |
- |
|
1277 |
-sub sort_by { |
|
1278 |
- $_[0]->{sort_by} = defined $_[1] ? $_[1] : 1; |
|
1279 |
- $_[0]; |
|
1280 |
-} |
|
1281 |
- |
|
1282 |
-sub allow_bigint { |
|
1283 |
- Carp::carp("allow_bigint() is obsoleted. use allow_bignum() insted."); |
|
1284 |
-} |
|
1285 |
- |
|
1286 |
-############################### |
|
1287 |
- |
|
1288 |
-### |
|
1289 |
-### Perl => JSON |
|
1290 |
-### |
|
1291 |
- |
|
1292 |
- |
|
1293 |
-{ # Convert |
|
1294 |
- |
|
1295 |
- my $max_depth; |
|
1296 |
- my $indent; |
|
1297 |
- my $ascii; |
|
1298 |
- my $latin1; |
|
1299 |
- my $utf8; |
|
1300 |
- my $space_before; |
|
1301 |
- my $space_after; |
|
1302 |
- my $canonical; |
|
1303 |
- my $allow_blessed; |
|
1304 |
- my $convert_blessed; |
|
1305 |
- |
|
1306 |
- my $indent_length; |
|
1307 |
- my $escape_slash; |
|
1308 |
- my $bignum; |
|
1309 |
- my $as_nonblessed; |
|
1310 |
- |
|
1311 |
- my $depth; |
|
1312 |
- my $indent_count; |
|
1313 |
- my $keysort; |
|
1314 |
- |
|
1315 |
- |
|
1316 |
- sub PP_encode_json { |
|
1317 |
- my $self = shift; |
|
1318 |
- my $obj = shift; |
|
1319 |
- |
|
1320 |
- $indent_count = 0; |
|
1321 |
- $depth = 0; |
|
1322 |
- |
|
1323 |
- my $idx = $self->{PROPS}; |
|
1324 |
- |
|
1325 |
- ($ascii, $latin1, $utf8, $indent, $canonical, $space_before, $space_after, $allow_blessed, |
|
1326 |
- $convert_blessed, $escape_slash, $bignum, $as_nonblessed) |
|
1327 |
- = @{$idx}[P_ASCII .. P_SPACE_AFTER, P_ALLOW_BLESSED, P_CONVERT_BLESSED, |
|
1328 |
- P_ESCAPE_SLASH, P_ALLOW_BIGNUM, P_AS_NONBLESSED]; |
|
1329 |
- |
|
1330 |
- ($max_depth, $indent_length) = @{$self}{qw/max_depth indent_length/}; |
|
1331 |
- |
|
1332 |
- $keysort = $canonical ? sub { $a cmp $b } : undef; |
|
1333 |
- |
|
1334 |
- if ($self->{sort_by}) { |
|
1335 |
- $keysort = ref($self->{sort_by}) eq 'CODE' ? $self->{sort_by} |
|
1336 |
- : $self->{sort_by} =~ /\D+/ ? $self->{sort_by} |
|
1337 |
- : sub { $a cmp $b }; |
|
1338 |
- } |
|
1339 |
- |
|
1340 |
- encode_error("hash- or arrayref expected (not a simple scalar, use allow_nonref to allow this)") |
|
1341 |
- if(!ref $obj and !$idx->[ P_ALLOW_NONREF ]); |
|
1342 |
- |
|
1343 |
- my $str = $self->object_to_json($obj); |
|
1344 |
- |
|
1345 |
- $str .= "\n" if ( $indent ); # JSON::XS 2.26 compatible |
|
1346 |
- |
|
1347 |
- unless ($ascii or $latin1 or $utf8) { |
|
1348 |
- utf8::upgrade($str); |
|
1349 |
- } |
|
1350 |
- |
|
1351 |
- if ($idx->[ P_SHRINK ]) { |
|
1352 |
- utf8::downgrade($str, 1); |
|
1353 |
- } |
|
1354 |
- |
|
1355 |
- return $str; |
|
1356 |
- } |
|
1357 |
- |
|
1358 |
- |
|
1359 |
- sub object_to_json { |
|
1360 |
- my ($self, $obj) = @_; |
|
1361 |
- my $type = ref($obj); |
|
1362 |
- |
|
1363 |
- if($type eq 'HASH'){ |
|
1364 |
- return $self->hash_to_json($obj); |
|
1365 |
- } |
|
1366 |
- elsif($type eq 'ARRAY'){ |
|
1367 |
- return $self->array_to_json($obj); |
|
1368 |
- } |
|
1369 |
- elsif ($type) { # blessed object? |
|
1370 |
- if (blessed($obj)) { |
|
1371 |
- |
|
1372 |
- return $self->value_to_json($obj) if ( $obj->isa('Test::ModuleVersion::JSON::PP::Boolean') ); |
|
1373 |
- |
|
1374 |
- if ( $convert_blessed and $obj->can('TO_JSON') ) { |
|
1375 |
- my $result = $obj->TO_JSON(); |
|
1376 |
- if ( defined $result and ref( $result ) ) { |
|
1377 |
- if ( refaddr( $obj ) eq refaddr( $result ) ) { |
|
1378 |
- encode_error( sprintf( |
|
1379 |
- "%s::TO_JSON method returned same object as was passed instead of a new one", |
|
1380 |
- ref $obj |
|
1381 |
- ) ); |
|
1382 |
- } |
|
1383 |
- } |
|
1384 |
- |
|
1385 |
- return $self->object_to_json( $result ); |
|
1386 |
- } |
|
1387 |
- |
|
1388 |
- return "$obj" if ( $bignum and _is_bignum($obj) ); |
|
1389 |
- return $self->blessed_to_json($obj) if ($allow_blessed and $as_nonblessed); # will be removed. |
|
1390 |
- |
|
1391 |
- encode_error( sprintf("encountered object '%s', but neither allow_blessed " |
|
1392 |
- . "nor convert_blessed settings are enabled", $obj) |
|
1393 |
- ) unless ($allow_blessed); |
|
1394 |
- |
|
1395 |
- return 'null'; |
|
1396 |
- } |
|
1397 |
- else { |
|
1398 |
- return $self->value_to_json($obj); |
|
1399 |
- } |
|
1400 |
- } |
|
1401 |
- else{ |
|
1402 |
- return $self->value_to_json($obj); |
|
1403 |
- } |
|
1404 |
- } |
|
1405 |
- |
|
1406 |
- |
|
1407 |
- sub hash_to_json { |
|
1408 |
- my ($self, $obj) = @_; |
|
1409 |
- my @res; |
|
1410 |
- |
|
1411 |
- encode_error("json text or perl structure exceeds maximum nesting level (max_depth set too low?)") |
|
1412 |
- if (++$depth > $max_depth); |
|
1413 |
- |
|
1414 |
- my ($pre, $post) = $indent ? $self->_up_indent() : ('', ''); |
|
1415 |
- my $del = ($space_before ? ' ' : '') . ':' . ($space_after ? ' ' : ''); |
|
1416 |
- |
|
1417 |
- for my $k ( _sort( $obj ) ) { |
|
1418 |
- if ( OLD_PERL ) { utf8::decode($k) } # key for Perl 5.6 / be optimized |
|
1419 |
- push @res, string_to_json( $self, $k ) |
|
1420 |
- . $del |
|
1421 |
- . ( $self->object_to_json( $obj->{$k} ) || $self->value_to_json( $obj->{$k} ) ); |
|
1422 |
- } |
|
1423 |
- |
|
1424 |
- --$depth; |
|
1425 |
- $self->_down_indent() if ($indent); |
|
1426 |
- |
|
1427 |
- return '{' . ( @res ? $pre : '' ) . ( @res ? join( ",$pre", @res ) . $post : '' ) . '}'; |
|
1428 |
- } |
|
1429 |
- |
|
1430 |
- |
|
1431 |
- sub array_to_json { |
|
1432 |
- my ($self, $obj) = @_; |
|
1433 |
- my @res; |
|
1434 |
- |
|
1435 |
- encode_error("json text or perl structure exceeds maximum nesting level (max_depth set too low?)") |
|
1436 |
- if (++$depth > $max_depth); |
|
1437 |
- |
|
1438 |
- my ($pre, $post) = $indent ? $self->_up_indent() : ('', ''); |
|
1439 |
- |
|
1440 |
- for my $v (@$obj){ |
|
1441 |
- push @res, $self->object_to_json($v) || $self->value_to_json($v); |
|
1442 |
- } |
|
1443 |
- |
|
1444 |
- --$depth; |
|
1445 |
- $self->_down_indent() if ($indent); |
|
1446 |
- |
|
1447 |
- return '[' . ( @res ? $pre : '' ) . ( @res ? join( ",$pre", @res ) . $post : '' ) . ']'; |
|
1448 |
- } |
|
1449 |
- |
|
1450 |
- |
|
1451 |
- sub value_to_json { |
|
1452 |
- my ($self, $value) = @_; |
|
1453 |
- |
|
1454 |
- return 'null' if(!defined $value); |
|
1455 |
- |
|
1456 |
- my $b_obj = B::svref_2object(\$value); # for round trip problem |
|
1457 |
- my $flags = $b_obj->FLAGS; |
|
1458 |
- |
|
1459 |
- return $value # as is |
|
1460 |
- if $flags & ( B::SVp_IOK | B::SVp_NOK ) and !( $flags & B::SVp_POK ); # SvTYPE is IV or NV? |
|
1461 |
- |
|
1462 |
- my $type = ref($value); |
|
1463 |
- |
|
1464 |
- if(!$type){ |
|
1465 |
- return string_to_json($self, $value); |
|
1466 |
- } |
|
1467 |
- elsif( blessed($value) and $value->isa('Test::ModuleVersion::JSON::PP::Boolean') ){ |
|
1468 |
- return $$value == 1 ? 'true' : 'false'; |
|
1469 |
- } |
|
1470 |
- elsif ($type) { |
|
1471 |
- if ((overload::StrVal($value) =~ /=(\w+)/)[0]) { |
|
1472 |
- return $self->value_to_json("$value"); |
|
1473 |
- } |
|
1474 |
- |
|
1475 |
- if ($type eq 'SCALAR' and defined $$value) { |
|
1476 |
- return $$value eq '1' ? 'true' |
|
1477 |
- : $$value eq '0' ? 'false' |
|
1478 |
- : $self->{PROPS}->[ P_ALLOW_UNKNOWN ] ? 'null' |
|
1479 |
- : encode_error("cannot encode reference to scalar"); |
|
1480 |
- } |
|
1481 |
- |
|
1482 |
- if ( $self->{PROPS}->[ P_ALLOW_UNKNOWN ] ) { |
|
1483 |
- return 'null'; |
|
1484 |
- } |
|
1485 |
- else { |
|
1486 |
- if ( $type eq 'SCALAR' or $type eq 'REF' ) { |
|
1487 |
- encode_error("cannot encode reference to scalar"); |
|
1488 |
- } |
|
1489 |
- else { |
|
1490 |
- encode_error("encountered $value, but JSON can only represent references to arrays or hashes"); |
|
1491 |
- } |
|
1492 |
- } |
|
1493 |
- |
|
1494 |
- } |
|
1495 |
- else { |
|
1496 |
- return $self->{fallback}->($value) |
|
1497 |
- if ($self->{fallback} and ref($self->{fallback}) eq 'CODE'); |
|
1498 |
- return 'null'; |
|
1499 |
- } |
|
1500 |
- |
|
1501 |
- } |
|
1502 |
- |
|
1503 |
- |
|
1504 |
- my %esc = ( |
|
1505 |
- "\n" => '\n', |
|
1506 |
- "\r" => '\r', |
|
1507 |
- "\t" => '\t', |
|
1508 |
- "\f" => '\f', |
|
1509 |
- "\b" => '\b', |
|
1510 |
- "\"" => '\"', |
|
1511 |
- "\\" => '\\\\', |
|
1512 |
- "\'" => '\\\'', |
|
1513 |
- ); |
|
1514 |
- |
|
1515 |
- |
|
1516 |
- sub string_to_json { |
|
1517 |
- my ($self, $arg) = @_; |
|
1518 |
- |
|
1519 |
- $arg =~ s/([\x22\x5c\n\r\t\f\b])/$esc{$1}/g; |
|
1520 |
- $arg =~ s/\//\\\//g if ($escape_slash); |
|
1521 |
- $arg =~ s/([\x00-\x08\x0b\x0e-\x1f])/'\\u00' . unpack('H2', $1)/eg; |
|
1522 |
- |
|
1523 |
- if ($ascii) { |
|
1524 |
- $arg = JSON_PP_encode_ascii($arg); |
|
1525 |
- } |
|
1526 |
- |
|
1527 |
- if ($latin1) { |
|
1528 |
- $arg = JSON_PP_encode_latin1($arg); |
|
1529 |
- } |
|
1530 |
- |
|
1531 |
- if ($utf8) { |
|
1532 |
- utf8::encode($arg); |
|
1533 |
- } |
|
1534 |
- |
|
1535 |
- return '"' . $arg . '"'; |
|
1536 |
- } |
|
1537 |
- |
|
1538 |
- |
|
1539 |
- sub blessed_to_json { |
|
1540 |
- my $reftype = reftype($_[1]) || ''; |
|
1541 |
- if ($reftype eq 'HASH') { |
|
1542 |
- return $_[0]->hash_to_json($_[1]); |
|
1543 |
- } |
|
1544 |
- elsif ($reftype eq 'ARRAY') { |
|
1545 |
- return $_[0]->array_to_json($_[1]); |
|
1546 |
- } |
|
1547 |
- else { |
|
1548 |
- return 'null'; |
|
1549 |
- } |
|
1550 |
- } |
|
1551 |
- |
|
1552 |
- |
|
1553 |
- sub encode_error { |
|
1554 |
- my $error = shift; |
|
1555 |
- Carp::croak "$error"; |
|
1556 |
- } |
|
1557 |
- |
|
1558 |
- |
|
1559 |
- sub _sort { |
|
1560 |
- defined $keysort ? (sort $keysort (keys %{$_[0]})) : keys %{$_[0]}; |
|
1561 |
- } |
|
1562 |
- |
|
1563 |
- |
|
1564 |
- sub _up_indent { |
|
1565 |
- my $self = shift; |
|
1566 |
- my $space = ' ' x $indent_length; |
|
1567 |
- |
|
1568 |
- my ($pre,$post) = ('',''); |
|
1569 |
- |
|
1570 |
- $post = "\n" . $space x $indent_count; |
|
1571 |
- |
|
1572 |
- $indent_count++; |
|
1573 |
- |
|
1574 |
- $pre = "\n" . $space x $indent_count; |
|
1575 |
- |
|
1576 |
- return ($pre,$post); |
|
1577 |
- } |
|
1578 |
- |
|
1579 |
- |
|
1580 |
- sub _down_indent { $indent_count--; } |
|
1581 |
- |
|
1582 |
- |
|
1583 |
- sub PP_encode_box { |
|
1584 |
- { |
|
1585 |
- depth => $depth, |
|
1586 |
- indent_count => $indent_count, |
|
1587 |
- }; |
|
1588 |
- } |
|
1589 |
- |
|
1590 |
-} # Convert |
|
1591 |
- |
|
1592 |
- |
|
1593 |
-sub _encode_ascii { |
|
1594 |
- join('', |
|
1595 |
- map { |
|
1596 |
- $_ <= 127 ? |
|
1597 |
- chr($_) : |
|
1598 |
- $_ <= 65535 ? |
|
1599 |
- sprintf('\u%04x', $_) : sprintf('\u%x\u%x', _encode_surrogates($_)); |
|
1600 |
- } unpack('U*', $_[0]) |
|
1601 |
- ); |
|
1602 |
-} |
|
1603 |
- |
|
1604 |
- |
|
1605 |
-sub _encode_latin1 { |
|
1606 |
- join('', |
|
1607 |
- map { |
|
1608 |
- $_ <= 255 ? |
|
1609 |
- chr($_) : |
|
1610 |
- $_ <= 65535 ? |
|
1611 |
- sprintf('\u%04x', $_) : sprintf('\u%x\u%x', _encode_surrogates($_)); |
|
1612 |
- } unpack('U*', $_[0]) |
|
1613 |
- ); |
|
1614 |
-} |
|
1615 |
- |
|
1616 |
- |
|
1617 |
-sub _encode_surrogates { # from perlunicode |
|
1618 |
- my $uni = $_[0] - 0x10000; |
|
1619 |
- return ($uni / 0x400 + 0xD800, $uni % 0x400 + 0xDC00); |
|
1620 |
-} |
|
1621 |
- |
|
1622 |
- |
|
1623 |
-sub _is_bignum { |
|
1624 |
- $_[0]->isa('Math::BigInt') or $_[0]->isa('Math::BigFloat'); |
|
1625 |
-} |
|
1626 |
- |
|
1627 |
- |
|
1628 |
- |
|
1629 |
-# |
|
1630 |
-# JSON => Perl |
|
1631 |
-# |
|
1632 |
- |
|
1633 |
-my $max_intsize; |
|
1634 |
- |
|
1635 |
-BEGIN { |
|
1636 |
- my $checkint = 1111; |
|
1637 |
- for my $d (5..64) { |
|
1638 |
- $checkint .= 1; |
|
1639 |
- my $int = eval qq| $checkint |; |
|
1640 |
- if ($int =~ /[eE]/) { |
|
1641 |
- $max_intsize = $d - 1; |
|
1642 |
- last; |
|
1643 |
- } |
|
1644 |
- } |
|
1645 |
-} |
|
1646 |
- |
|
1647 |
-{ # PARSE |
|
1648 |
- |
|
1649 |
- my %escapes = ( # by Jeremy Muhlich <jmuhlich [at] bitflood.org> |
|
1650 |
- b => "\x8", |
|
1651 |
- t => "\x9", |
|
1652 |
- n => "\xA", |
|
1653 |
- f => "\xC", |
|
1654 |
- r => "\xD", |
|
1655 |
- '\\' => '\\', |
|
1656 |
- '"' => '"', |
|
1657 |
- '/' => '/', |
|
1658 |
- ); |
|
1659 |
- |
|
1660 |
- my $text; # json data |
|
1661 |
- my $at; # offset |
|
1662 |
- my $ch; # 1chracter |
|
1663 |
- my $len; # text length (changed according to UTF8 or NON UTF8) |
|
1664 |
- # INTERNAL |
|
1665 |
- my $depth; # nest counter |
|
1666 |
- my $encoding; # json text encoding |
|
1667 |
- my $is_valid_utf8; # temp variable |
|
1668 |
- my $utf8_len; # utf8 byte length |
|
1669 |
- # FLAGS |
|
1670 |
- my $utf8; # must be utf8 |
|
1671 |
- my $max_depth; # max nest nubmer of objects and arrays |
|
1672 |
- my $max_size; |
|
1673 |
- my $relaxed; |
|
1674 |
- my $cb_object; |
|
1675 |
- my $cb_sk_object; |
|
1676 |
- |
|
1677 |
- my $F_HOOK; |
|
1678 |
- |
|
1679 |
- my $allow_bigint; # using Math::BigInt |
|
1680 |
- my $singlequote; # loosely quoting |
|
1681 |
- my $loose; # |
|
1682 |
- my $allow_barekey; # bareKey |
|
1683 |
- |
|
1684 |
- # $opt flag |
|
1685 |
- # 0x00000001 .... decode_prefix |
|
1686 |
- # 0x10000000 .... incr_parse |
|
1687 |
- |
|
1688 |
- sub PP_decode_json { |
|
1689 |
- my ($self, $opt); # $opt is an effective flag during this decode_json. |
|
1690 |
- |
|
1691 |
- ($self, $text, $opt) = @_; |
|
1692 |
- |
|
1693 |
- ($at, $ch, $depth) = (0, '', 0); |
|
1694 |
- |
|
1695 |
- if ( !defined $text or ref $text ) { |
|
1696 |
- decode_error("malformed JSON string, neither array, object, number, string or atom"); |
|
1697 |
- } |
|
1698 |
- |
|
1699 |
- my $idx = $self->{PROPS}; |
|
1700 |
- |
|
1701 |
- ($utf8, $relaxed, $loose, $allow_bigint, $allow_barekey, $singlequote) |
|
1702 |
- = @{$idx}[P_UTF8, P_RELAXED, P_LOOSE .. P_ALLOW_SINGLEQUOTE]; |
|
1703 |
- |
|
1704 |
- if ( $utf8 ) { |
|
1705 |
- utf8::downgrade( $text, 1 ) or Carp::croak("Wide character in subroutine entry"); |
|
1706 |
- } |
|
1707 |
- else { |
|
1708 |
- utf8::upgrade( $text ); |
|
1709 |
- } |
|
1710 |
- |
|
1711 |
- $len = length $text; |
|
1712 |
- |
|
1713 |
- ($max_depth, $max_size, $cb_object, $cb_sk_object, $F_HOOK) |
|
1714 |
- = @{$self}{qw/max_depth max_size cb_object cb_sk_object F_HOOK/}; |
|
1715 |
- |
|
1716 |
- if ($max_size > 1) { |
|
1717 |
- use bytes; |
|
1718 |
- my $bytes = length $text; |
|
1719 |
- decode_error( |
|
1720 |
- sprintf("attempted decode of JSON text of %s bytes size, but max_size is set to %s" |
|
1721 |
- , $bytes, $max_size), 1 |
|
1722 |
- ) if ($bytes > $max_size); |
|
1723 |
- } |
|
1724 |
- |
|
1725 |
- # Currently no effect |
|
1726 |
- # should use regexp |
|
1727 |
- my @octets = unpack('C4', $text); |
|
1728 |
- $encoding = ( $octets[0] and $octets[1]) ? 'UTF-8' |
|
1729 |
- : (!$octets[0] and $octets[1]) ? 'UTF-16BE' |
|
1730 |
- : (!$octets[0] and !$octets[1]) ? 'UTF-32BE' |
|
1731 |
- : ( $octets[2] ) ? 'UTF-16LE' |
|
1732 |
- : (!$octets[2] ) ? 'UTF-32LE' |
|
1733 |
- : 'unknown'; |
|
1734 |
- |
|
1735 |
- white(); # remove head white space |
|
1736 |
- |
|
1737 |
- my $valid_start = defined $ch; # Is there a first character for JSON structure? |
|
1738 |
- |
|
1739 |
- my $result = value(); |
|
1740 |
- |
|
1741 |
- return undef if ( !$result && ( $opt & 0x10000000 ) ); # for incr_parse |
|
1742 |
- |
|
1743 |
- decode_error("malformed JSON string, neither array, object, number, string or atom") unless $valid_start; |
|
1744 |
- |
|
1745 |
- if ( !$idx->[ P_ALLOW_NONREF ] and !ref $result ) { |
|
1746 |
- decode_error( |
|
1747 |
- 'JSON text must be an object or array (but found number, string, true, false or null,' |
|
1748 |
- . ' use allow_nonref to allow this)', 1); |
|
1749 |
- } |
|
1750 |
- |
|
1751 |
- Carp::croak('something wrong.') if $len < $at; # we won't arrive here. |
|
1752 |
- |
|
1753 |
- my $consumed = defined $ch ? $at - 1 : $at; # consumed JSON text length |
|
1754 |
- |
|
1755 |
- white(); # remove tail white space |
|
1756 |
- |
|
1757 |
- if ( $ch ) { |
|
1758 |
- return ( $result, $consumed ) if ($opt & 0x00000001); # all right if decode_prefix |
|
1759 |
- decode_error("garbage after JSON object"); |
|
1760 |
- } |
|
1761 |
- |
|
1762 |
- ( $opt & 0x00000001 ) ? ( $result, $consumed ) : $result; |
|
1763 |
- } |
|
1764 |
- |
|
1765 |
- |
|
1766 |
- sub next_chr { |
|
1767 |
- return $ch = undef if($at >= $len); |
|
1768 |
- $ch = substr($text, $at++, 1); |
|
1769 |
- } |
|
1770 |
- |
|
1771 |
- |
|
1772 |
- sub value { |
|
1773 |
- white(); |
|
1774 |
- return if(!defined $ch); |
|
1775 |
- return object() if($ch eq '{'); |
|
1776 |
- return array() if($ch eq '['); |
|
1777 |
- return string() if($ch eq '"' or ($singlequote and $ch eq "'")); |
|
1778 |
- return number() if($ch =~ /[0-9]/ or $ch eq '-'); |
|
1779 |
- return word(); |
|
1780 |
- } |
|
1781 |
- |
|
1782 |
- sub string { |
|
1783 |
- my ($i, $s, $t, $u); |
|
1784 |
- my $utf16; |
|
1785 |
- my $is_utf8; |
|
1786 |
- |
|
1787 |
- ($is_valid_utf8, $utf8_len) = ('', 0); |
|
1788 |
- |
|
1789 |
- $s = ''; # basically UTF8 flag on |
|
1790 |
- |
|
1791 |
- if($ch eq '"' or ($singlequote and $ch eq "'")){ |
|
1792 |
- my $boundChar = $ch; |
|
1793 |
- |
|
1794 |
- OUTER: while( defined(next_chr()) ){ |
|
1795 |
- |
|
1796 |
- if($ch eq $boundChar){ |
|
1797 |
- next_chr(); |
|
1798 |
- |
|
1799 |
- if ($utf16) { |
|
1800 |
- decode_error("missing low surrogate character in surrogate pair"); |
|
1801 |
- } |
|
1802 |
- |
|
1803 |
- utf8::decode($s) if($is_utf8); |
|
1804 |
- |
|
1805 |
- return $s; |
|
1806 |
- } |
|
1807 |
- elsif($ch eq '\\'){ |
|
1808 |
- next_chr(); |
|
1809 |
- if(exists $escapes{$ch}){ |
|
1810 |
- $s .= $escapes{$ch}; |
|
1811 |
- } |
|
1812 |
- elsif($ch eq 'u'){ # UNICODE handling |
|
1813 |
- my $u = ''; |
|
1814 |
- |
|
1815 |
- for(1..4){ |
|
1816 |
- $ch = next_chr(); |
|
1817 |
- last OUTER if($ch !~ /[0-9a-fA-F]/); |
|
1818 |
- $u .= $ch; |
|
1819 |
- } |
|
1820 |
- |
|
1821 |
- # U+D800 - U+DBFF |
|
1822 |
- if ($u =~ /^[dD][89abAB][0-9a-fA-F]{2}/) { # UTF-16 high surrogate? |
|
1823 |
- $utf16 = $u; |
|
1824 |
- } |
|
1825 |
- # U+DC00 - U+DFFF |
|
1826 |
- elsif ($u =~ /^[dD][c-fC-F][0-9a-fA-F]{2}/) { # UTF-16 low surrogate? |
|
1827 |
- unless (defined $utf16) { |
|
1828 |
- decode_error("missing high surrogate character in surrogate pair"); |
|
1829 |
- } |
|
1830 |
- $is_utf8 = 1; |
|
1831 |
- $s .= JSON_PP_decode_surrogates($utf16, $u) || next; |
|
1832 |
- $utf16 = undef; |
|
1833 |
- } |
|
1834 |
- else { |
|
1835 |
- if (defined $utf16) { |
|
1836 |
- decode_error("surrogate pair expected"); |
|
1837 |
- } |
|
1838 |
- |
|
1839 |
- if ( ( my $hex = hex( $u ) ) > 127 ) { |
|
1840 |
- $is_utf8 = 1; |
|
1841 |
- $s .= JSON_PP_decode_unicode($u) || next; |
|
1842 |
- } |
|
1843 |
- else { |
|
1844 |
- $s .= chr $hex; |
|
1845 |
- } |
|
1846 |
- } |
|
1847 |
- |
|
1848 |
- } |
|
1849 |
- else{ |
|
1850 |
- unless ($loose) { |
|
1851 |
- $at -= 2; |
|
1852 |
- decode_error('illegal backslash escape sequence in string'); |
|
1853 |
- } |
|
1854 |
- $s .= $ch; |
|
1855 |
- } |
|
1856 |
- } |
|
1857 |
- else{ |
|
1858 |
- |
|
1859 |
- if ( ord $ch > 127 ) { |
|
1860 |
- if ( $utf8 ) { |
|
1861 |
- unless( $ch = is_valid_utf8($ch) ) { |
|
1862 |
- $at -= 1; |
|
1863 |
- decode_error("malformed UTF-8 character in JSON string"); |
|
1864 |
- } |
|
1865 |
- else { |
|
1866 |
- $at += $utf8_len - 1; |
|
1867 |
- } |
|
1868 |
- } |
|
1869 |
- else { |
|
1870 |
- utf8::encode( $ch ); |
|
1871 |
- } |
|
1872 |
- |
|
1873 |
- $is_utf8 = 1; |
|
1874 |
- } |
|
1875 |
- |
|
1876 |
- if (!$loose) { |
|
1877 |
- if ($ch =~ /[\x00-\x1f\x22\x5c]/) { # '/' ok |
|
1878 |
- $at--; |
|
1879 |
- decode_error('invalid character encountered while parsing JSON string'); |
|
1880 |
- } |
|
1881 |
- } |
|
1882 |
- |
|
1883 |
- $s .= $ch; |
|
1884 |
- } |
|
1885 |
- } |
|
1886 |
- } |
|
1887 |
- |
|
1888 |
- decode_error("unexpected end of string while parsing JSON string"); |
|
1889 |
- } |
|
1890 |
- |
|
1891 |
- |
|
1892 |
- sub white { |
|
1893 |
- while( defined $ch ){ |
|
1894 |
- if($ch le ' '){ |
|
1895 |
- next_chr(); |
|
1896 |
- } |
|
1897 |
- elsif($ch eq '/'){ |
|
1898 |
- next_chr(); |
|
1899 |
- if(defined $ch and $ch eq '/'){ |
|
1900 |
- 1 while(defined(next_chr()) and $ch ne "\n" and $ch ne "\r"); |
|
1901 |
- } |
|
1902 |
- elsif(defined $ch and $ch eq '*'){ |
|
1903 |
- next_chr(); |
|
1904 |
- while(1){ |
|
1905 |
- if(defined $ch){ |
|
1906 |
- if($ch eq '*'){ |
|
1907 |
- if(defined(next_chr()) and $ch eq '/'){ |
|
1908 |
- next_chr(); |
|
1909 |
- last; |
|
1910 |
- } |
|
1911 |
- } |
|
1912 |
- else{ |
|
1913 |
- next_chr(); |
|
1914 |
- } |
|
1915 |
- } |
|
1916 |
- else{ |
|
1917 |
- decode_error("Unterminated comment"); |
|
1918 |
- } |
|
1919 |
- } |
|
1920 |
- next; |
|
1921 |
- } |
|
1922 |
- else{ |
|
1923 |
- $at--; |
|
1924 |
- decode_error("malformed JSON string, neither array, object, number, string or atom"); |
|
1925 |
- } |
|
1926 |
- } |
|
1927 |
- else{ |
|
1928 |
- if ($relaxed and $ch eq '#') { # correctly? |
|
1929 |
- pos($text) = $at; |
|
1930 |
- $text =~ /\G([^\n]*(?:\r\n|\r|\n|$))/g; |
|
1931 |
- $at = pos($text); |
|
1932 |
- next_chr; |
|
1933 |
- next; |
|
1934 |
- } |
|
1935 |
- |
|
1936 |
- last; |
|
1937 |
- } |
|
1938 |
- } |
|
1939 |
- } |
|
1940 |
- |
|
1941 |
- |
|
1942 |
- sub array { |
|
1943 |
- my $a = $_[0] || []; # you can use this code to use another array ref object. |
|
1944 |
- |
|
1945 |
- decode_error('json text or perl structure exceeds maximum nesting level (max_depth set too low?)') |
|
1946 |
- if (++$depth > $max_depth); |
|
1947 |
- |
|
1948 |
- next_chr(); |
|
1949 |
- white(); |
|
1950 |
- |
|
1951 |
- if(defined $ch and $ch eq ']'){ |
|
1952 |
- --$depth; |
|
1953 |
- next_chr(); |
|
1954 |
- return $a; |
|
1955 |
- } |
|
1956 |
- else { |
|
1957 |
- while(defined($ch)){ |
|
1958 |
- push @$a, value(); |
|
1959 |
- |
|
1960 |
- white(); |
|
1961 |
- |
|
1962 |
- if (!defined $ch) { |
|
1963 |
- last; |
|
1964 |
- } |
|
1965 |
- |
|
1966 |
- if($ch eq ']'){ |
|
1967 |
- --$depth; |
|
1968 |
- next_chr(); |
|
1969 |
- return $a; |
|
1970 |
- } |
|
1971 |
- |
|
1972 |
- if($ch ne ','){ |
|
1973 |
- last; |
|
1974 |
- } |
|
1975 |
- |
|
1976 |
- next_chr(); |
|
1977 |
- white(); |
|
1978 |
- |
|
1979 |
- if ($relaxed and $ch eq ']') { |
|
1980 |
- --$depth; |
|
1981 |
- next_chr(); |
|
1982 |
- return $a; |
|
1983 |
- } |
|
1984 |
- |
|
1985 |
- } |
|
1986 |
- } |
|
1987 |
- |
|
1988 |
- decode_error(", or ] expected while parsing array"); |
|
1989 |
- } |
|
1990 |
- |
|
1991 |
- |
|
1992 |
- sub object { |
|
1993 |
- my $o = $_[0] || {}; # you can use this code to use another hash ref object. |
|
1994 |
- my $k; |
|
1995 |
- |
|
1996 |
- decode_error('json text or perl structure exceeds maximum nesting level (max_depth set too low?)') |
|
1997 |
- if (++$depth > $max_depth); |
|
1998 |
- next_chr(); |
|
1999 |
- white(); |
|
2000 |
- |
|
2001 |
- if(defined $ch and $ch eq '}'){ |
|
2002 |
- --$depth; |
|
2003 |
- next_chr(); |
|
2004 |
- if ($F_HOOK) { |
|
2005 |
- return _json_object_hook($o); |
|
2006 |
- } |
|
2007 |
- return $o; |
|
2008 |
- } |
|
2009 |
- else { |
|
2010 |
- while (defined $ch) { |
|
2011 |
- $k = ($allow_barekey and $ch ne '"' and $ch ne "'") ? bareKey() : string(); |
|
2012 |
- white(); |
|
2013 |
- |
|
2014 |
- if(!defined $ch or $ch ne ':'){ |
|
2015 |
- $at--; |
|
2016 |
- decode_error("':' expected"); |
|
2017 |
- } |
|
2018 |
- |
|
2019 |
- next_chr(); |
|
2020 |
- $o->{$k} = value(); |
|
2021 |
- white(); |
|
2022 |
- |
|
2023 |
- last if (!defined $ch); |
|
2024 |
- |
|
2025 |
- if($ch eq '}'){ |
|
2026 |
- --$depth; |
|
2027 |
- next_chr(); |
|
2028 |
- if ($F_HOOK) { |
|
2029 |
- return _json_object_hook($o); |
|
2030 |
- } |
|
2031 |
- return $o; |
|
2032 |
- } |
|
2033 |
- |
|
2034 |
- if($ch ne ','){ |
|
2035 |
- last; |
|
2036 |
- } |
|
2037 |
- |
|
2038 |
- next_chr(); |
|
2039 |
- white(); |
|
2040 |
- |
|
2041 |
- if ($relaxed and $ch eq '}') { |
|
2042 |
- --$depth; |
|
2043 |
- next_chr(); |
|
2044 |
- if ($F_HOOK) { |
|
2045 |
- return _json_object_hook($o); |
|
2046 |
- } |
|
2047 |
- return $o; |
|
2048 |
- } |
|
2049 |
- |
|
2050 |
- } |
|
2051 |
- |
|
2052 |
- } |
|
2053 |
- |
|
2054 |
- $at--; |
|
2055 |
- decode_error(", or } expected while parsing object/hash"); |
|
2056 |
- } |
|
2057 |
- |
|
2058 |
- |
|
2059 |
- sub bareKey { # doesn't strictly follow Standard ECMA-262 3rd Edition |
|
2060 |
- my $key; |
|
2061 |
- while($ch =~ /[^\x00-\x23\x25-\x2F\x3A-\x40\x5B-\x5E\x60\x7B-\x7F]/){ |
|
2062 |
- $key .= $ch; |
|
2063 |
- next_chr(); |
|
2064 |
- } |
|
2065 |
- return $key; |
|
2066 |
- } |
|
2067 |
- |
|
2068 |
- |
|
2069 |
- sub word { |
|
2070 |
- my $word = substr($text,$at-1,4); |
|
2071 |
- |
|
2072 |
- if($word eq 'true'){ |
|
2073 |
- $at += 3; |
|
2074 |
- next_chr; |
|
2075 |
- return $Test::ModuleVersion::JSON::PP::true; |
|
2076 |
- } |
|
2077 |
- elsif($word eq 'null'){ |
|
2078 |
- $at += 3; |
|
2079 |
- next_chr; |
|
2080 |
- return undef; |
|
2081 |
- } |
|
2082 |
- elsif($word eq 'fals'){ |
|
2083 |
- $at += 3; |
|
2084 |
- if(substr($text,$at,1) eq 'e'){ |
|
2085 |
- $at++; |
|
2086 |
- next_chr; |
|
2087 |
- return $Test::ModuleVersion::JSON::PP::false; |
|
2088 |
- } |
|
2089 |
- } |
|
2090 |
- |
|
2091 |
- $at--; # for decode_error report |
|
2092 |
- |
|
2093 |
- decode_error("'null' expected") if ($word =~ /^n/); |
|
2094 |
- decode_error("'true' expected") if ($word =~ /^t/); |
|
2095 |
- decode_error("'false' expected") if ($word =~ /^f/); |
|
2096 |
- decode_error("malformed JSON string, neither array, object, number, string or atom"); |
|
2097 |
- } |
|
2098 |
- |
|
2099 |
- |
|
2100 |
- sub number { |
|
2101 |
- my $n = ''; |
|
2102 |
- my $v; |
|
2103 |
- |
|
2104 |
- # According to RFC4627, hex or oct digts are invalid. |
|
2105 |
- if($ch eq '0'){ |
|
2106 |
- my $peek = substr($text,$at,1); |
|
2107 |
- my $hex = $peek =~ /[xX]/; # 0 or 1 |
|
2108 |
- |
|
2109 |
- if($hex){ |
|
2110 |
- decode_error("malformed number (leading zero must not be followed by another digit)"); |
|
2111 |
- ($n) = ( substr($text, $at+1) =~ /^([0-9a-fA-F]+)/); |
|
2112 |
- } |
|
2113 |
- else{ # oct |
|
2114 |
- ($n) = ( substr($text, $at) =~ /^([0-7]+)/); |
|
2115 |
- if (defined $n and length $n > 1) { |
|
2116 |
- decode_error("malformed number (leading zero must not be followed by another digit)"); |
|
2117 |
- } |
|
2118 |
- } |
|
2119 |
- |
|
2120 |
- if(defined $n and length($n)){ |
|
2121 |
- if (!$hex and length($n) == 1) { |
|
2122 |
- decode_error("malformed number (leading zero must not be followed by another digit)"); |
|
2123 |
- } |
|
2124 |
- $at += length($n) + $hex; |
|
2125 |
- next_chr; |
|
2126 |
- return $hex ? hex($n) : oct($n); |
|
2127 |
- } |
|
2128 |
- } |
|
2129 |
- |
|
2130 |
- if($ch eq '-'){ |
|
2131 |
- $n = '-'; |
|
2132 |
- next_chr; |
|
2133 |
- if (!defined $ch or $ch !~ /\d/) { |
|
2134 |
- decode_error("malformed number (no digits after initial minus)"); |
|
2135 |
- } |
|
2136 |
- } |
|
2137 |
- |
|
2138 |
- while(defined $ch and $ch =~ /\d/){ |
|
2139 |
- $n .= $ch; |
|
2140 |
- next_chr; |
|
2141 |
- } |
|
2142 |
- |
|
2143 |
- if(defined $ch and $ch eq '.'){ |
|
2144 |
- $n .= '.'; |
|
2145 |
- |
|
2146 |
- next_chr; |
|
2147 |
- if (!defined $ch or $ch !~ /\d/) { |
|
2148 |
- decode_error("malformed number (no digits after decimal point)"); |
|
2149 |
- } |
|
2150 |
- else { |
|
2151 |
- $n .= $ch; |
|
2152 |
- } |
|
2153 |
- |
|
2154 |
- while(defined(next_chr) and $ch =~ /\d/){ |
|
2155 |
- $n .= $ch; |
|
2156 |
- } |
|
2157 |
- } |
|
2158 |
- |
|
2159 |
- if(defined $ch and ($ch eq 'e' or $ch eq 'E')){ |
|
2160 |
- $n .= $ch; |
|
2161 |
- next_chr; |
|
2162 |
- |
|
2163 |
- if(defined($ch) and ($ch eq '+' or $ch eq '-')){ |
|
2164 |
- $n .= $ch; |
|
2165 |
- next_chr; |
|
2166 |
- if (!defined $ch or $ch =~ /\D/) { |
|
2167 |
- decode_error("malformed number (no digits after exp sign)"); |
|
2168 |
- } |
|
2169 |
- $n .= $ch; |
|
2170 |
- } |
|
2171 |
- elsif(defined($ch) and $ch =~ /\d/){ |
|
2172 |
- $n .= $ch; |
|
2173 |
- } |
|
2174 |
- else { |
|
2175 |
- decode_error("malformed number (no digits after exp sign)"); |
|
2176 |
- } |
|
2177 |
- |
|
2178 |
- while(defined(next_chr) and $ch =~ /\d/){ |
|
2179 |
- $n .= $ch; |
|
2180 |
- } |
|
2181 |
- |
|
2182 |
- } |
|
2183 |
- |
|
2184 |
- $v .= $n; |
|
2185 |
- |
|
2186 |
- if ($v !~ /[.eE]/ and length $v > $max_intsize) { |
|
2187 |
- if ($allow_bigint) { # from Adam Sussman |
|
2188 |
- require Math::BigInt; |
|
2189 |
- return Math::BigInt->new($v); |
|
2190 |
- } |
|
2191 |
- else { |
|
2192 |
- return "$v"; |
|
2193 |
- } |
|
2194 |
- } |
|
2195 |
- elsif ($allow_bigint) { |
|
2196 |
- require Math::BigFloat; |
|
2197 |
- return Math::BigFloat->new($v); |
|
2198 |
- } |
|
2199 |
- |
|
2200 |
- return 0+$v; |
|
2201 |
- } |
|
2202 |
- |
|
2203 |
- |
|
2204 |
- sub is_valid_utf8 { |
|
2205 |
- |
|
2206 |
- $utf8_len = $_[0] =~ /[\x00-\x7F]/ ? 1 |
|
2207 |
- : $_[0] =~ /[\xC2-\xDF]/ ? 2 |
|
2208 |
- : $_[0] =~ /[\xE0-\xEF]/ ? 3 |
|
2209 |
- : $_[0] =~ /[\xF0-\xF4]/ ? 4 |
|
2210 |
- : 0 |
|
2211 |
- ; |
|
2212 |
- |
|
2213 |
- return unless $utf8_len; |
|
2214 |
- |
|
2215 |
- my $is_valid_utf8 = substr($text, $at - 1, $utf8_len); |
|
2216 |
- |
|
2217 |
- return ( $is_valid_utf8 =~ /^(?: |
|
2218 |
- [\x00-\x7F] |
|
2219 |
- |[\xC2-\xDF][\x80-\xBF] |
|
2220 |
- |[\xE0][\xA0-\xBF][\x80-\xBF] |
|
2221 |
- |[\xE1-\xEC][\x80-\xBF][\x80-\xBF] |
|
2222 |
- |[\xED][\x80-\x9F][\x80-\xBF] |
|
2223 |
- |[\xEE-\xEF][\x80-\xBF][\x80-\xBF] |
|
2224 |
- |[\xF0][\x90-\xBF][\x80-\xBF][\x80-\xBF] |
|
2225 |
- |[\xF1-\xF3][\x80-\xBF][\x80-\xBF][\x80-\xBF] |
|
2226 |
- |[\xF4][\x80-\x8F][\x80-\xBF][\x80-\xBF] |
|
2227 |
- )$/x ) ? $is_valid_utf8 : ''; |
|
2228 |
- } |
|
2229 |
- |
|
2230 |
- |
|
2231 |
- sub decode_error { |
|
2232 |
- my $error = shift; |
|
2233 |
- my $no_rep = shift; |
|
2234 |
- my $str = defined $text ? substr($text, $at) : ''; |
|
2235 |
- my $mess = ''; |
|
2236 |
- my $type = $] >= 5.008 ? 'U*' |
|
2237 |
- : $] < 5.006 ? 'C*' |
|
2238 |
- : utf8::is_utf8( $str ) ? 'U*' # 5.6 |
|
2239 |
- : 'C*' |
|
2240 |
- ; |
|
2241 |
- |
|
2242 |
- for my $c ( unpack( $type, $str ) ) { # emulate pv_uni_display() ? |
|
2243 |
- $mess .= $c == 0x07 ? '\a' |
|
2244 |
- : $c == 0x09 ? '\t' |
|
2245 |
- : $c == 0x0a ? '\n' |
|
2246 |
- : $c == 0x0d ? '\r' |
|
2247 |
- : $c == 0x0c ? '\f' |
|
2248 |
- : $c < 0x20 ? sprintf('\x{%x}', $c) |
|
2249 |
- : $c == 0x5c ? '\\\\' |
|
2250 |
- : $c < 0x80 ? chr($c) |
|
2251 |
- : sprintf('\x{%x}', $c) |
|
2252 |
- ; |
|
2253 |
- if ( length $mess >= 20 ) { |
|
2254 |
- $mess .= '...'; |
|
2255 |
- last; |
|
2256 |
- } |
|
2257 |
- } |
|
2258 |
- |
|
2259 |
- unless ( length $mess ) { |
|
2260 |
- $mess = '(end of string)'; |
|
2261 |
- } |
|
2262 |
- |
|
2263 |
- Carp::croak ( |
|
2264 |
- $no_rep ? "$error" : "$error, at character offset $at (before \"$mess\")" |
|
2265 |
- ); |
|
2266 |
- |
|
2267 |
- } |
|
2268 |
- |
|
2269 |
- |
|
2270 |
- sub _json_object_hook { |
|
2271 |
- my $o = $_[0]; |
|
2272 |
- my @ks = keys %{$o}; |
|
2273 |
- |
|
2274 |
- if ( $cb_sk_object and @ks == 1 and exists $cb_sk_object->{ $ks[0] } and ref $cb_sk_object->{ $ks[0] } ) { |
|
2275 |
- my @val = $cb_sk_object->{ $ks[0] }->( $o->{$ks[0]} ); |
|
2276 |
- if (@val == 1) { |
|
2277 |
- return $val[0]; |
|
2278 |
- } |
|
2279 |
- } |
|
2280 |
- |
|
2281 |
- my @val = $cb_object->($o) if ($cb_object); |
|
2282 |
- if (@val == 0 or @val > 1) { |
|
2283 |
- return $o; |
|
2284 |
- } |
|
2285 |
- else { |
|
2286 |
- return $val[0]; |
|
2287 |
- } |
|
2288 |
- } |
|
2289 |
- |
|
2290 |
- |
|
2291 |
- sub PP_decode_box { |
|
2292 |
- { |
|
2293 |
- text => $text, |
|
2294 |
- at => $at, |
|
2295 |
- ch => $ch, |
|
2296 |
- len => $len, |
|
2297 |
- depth => $depth, |
|
2298 |
- encoding => $encoding, |
|
2299 |
- is_valid_utf8 => $is_valid_utf8, |
|
2300 |
- }; |
|
2301 |
- } |
|
2302 |
- |
|
2303 |
-} # PARSE |
|
2304 |
- |
|
2305 |
- |
|
2306 |
-sub _decode_surrogates { # from perlunicode |
|
2307 |
- my $uni = 0x10000 + (hex($_[0]) - 0xD800) * 0x400 + (hex($_[1]) - 0xDC00); |
|
2308 |
- my $un = pack('U*', $uni); |
|
2309 |
- utf8::encode( $un ); |
|
2310 |
- return $un; |
|
2311 |
-} |
|
2312 |
- |
|
2313 |
- |
|
2314 |
-sub _decode_unicode { |
|
2315 |
- my $un = pack('U', hex shift); |
|
2316 |
- utf8::encode( $un ); |
|
2317 |
- return $un; |
|
2318 |
-} |
|
2319 |
- |
|
2320 |
-# |
|
2321 |
-# Setup for various Perl versions (the code from Test::ModuleVersion::JSON::PP58) |
|
2322 |
-# |
|
2323 |
- |
|
2324 |
-BEGIN { |
|
2325 |
- |
|
2326 |
- unless ( defined &utf8::is_utf8 ) { |
|
2327 |
- require Encode; |
|
2328 |
- *utf8::is_utf8 = *Encode::is_utf8; |
|
2329 |
- } |
|
2330 |
- |
|
2331 |
- if ( $] >= 5.008 ) { |
|
2332 |
- *Test::ModuleVersion::JSON::PP::JSON_PP_encode_ascii = \&_encode_ascii; |
|
2333 |
- *Test::ModuleVersion::JSON::PP::JSON_PP_encode_latin1 = \&_encode_latin1; |
|
2334 |
- *Test::ModuleVersion::JSON::PP::JSON_PP_decode_surrogates = \&_decode_surrogates; |
|
2335 |
- *Test::ModuleVersion::JSON::PP::JSON_PP_decode_unicode = \&_decode_unicode; |
|
2336 |
- } |
|
2337 |
- |
|
2338 |
- if ($] >= 5.008 and $] < 5.008003) { # join() in 5.8.0 - 5.8.2 is broken. |
|
2339 |
- package Test::ModuleVersion::JSON::PP; |
|
2340 |
- require subs; |
|
2341 |
- subs->import('join'); |
|
2342 |
- eval q| |
|
2343 |
- sub join { |
|
2344 |
- return '' if (@_ < 2); |
|
2345 |
- my $j = shift; |
|
2346 |
- my $str = shift; |
|
2347 |
- for (@_) { $str .= $j . $_; } |
|
2348 |
- return $str; |
|
2349 |
- } |
|
2350 |
- |; |
|
2351 |
- } |
|
2352 |
- |
|
2353 |
- |
|
2354 |
- sub Test::ModuleVersion::JSON::PP::incr_parse { |
|
2355 |
- local $Carp::CarpLevel = 1; |
|
2356 |
- ( $_[0]->{_incr_parser} ||= Test::ModuleVersion::JSON::PP::IncrParser->new )->incr_parse( @_ ); |
|
2357 |
- } |
|
2358 |
- |
|
2359 |
- |
|
2360 |
- sub Test::ModuleVersion::JSON::PP::incr_skip { |
|
2361 |
- ( $_[0]->{_incr_parser} ||= Test::ModuleVersion::JSON::PP::IncrParser->new )->incr_skip; |
|
2362 |
- } |
|
2363 |
- |
|
2364 |
- |
|
2365 |
- sub Test::ModuleVersion::JSON::PP::incr_reset { |
|
2366 |
- ( $_[0]->{_incr_parser} ||= Test::ModuleVersion::JSON::PP::IncrParser->new )->incr_reset; |
|
2367 |
- } |
|
2368 |
- |
|
2369 |
- eval q{ |
|
2370 |
- sub Test::ModuleVersion::JSON::PP::incr_text : lvalue { |
|
2371 |
- $_[0]->{_incr_parser} ||= Test::ModuleVersion::JSON::PP::IncrParser->new; |
|
2372 |
- |
|
2373 |
- if ( $_[0]->{_incr_parser}->{incr_parsing} ) { |
|
2374 |
- Carp::croak("incr_text can not be called when the incremental parser already started parsing"); |
|
2375 |
- } |
|
2376 |
- $_[0]->{_incr_parser}->{incr_text}; |
|
2377 |
- } |
|
2378 |
- } if ( $] >= 5.006 ); |
|
2379 |
- |
|
2380 |
-} # Setup for various Perl versions (the code from Test::ModuleVersion::JSON::PP58) |
|
2381 |
- |
|
2382 |
- |
|
2383 |
-############################### |
|
2384 |
-# Utilities |
|
2385 |
-# |
|
2386 |
- |
|
2387 |
-BEGIN { |
|
2388 |
- eval 'require Scalar::Util'; |
|
2389 |
- unless($@){ |
|
2390 |
- *Test::ModuleVersion::JSON::PP::blessed = \&Scalar::Util::blessed; |
|
2391 |
- *Test::ModuleVersion::JSON::PP::reftype = \&Scalar::Util::reftype; |
|
2392 |
- *Test::ModuleVersion::JSON::PP::refaddr = \&Scalar::Util::refaddr; |
|
2393 |
- } |
|
2394 |
- else{ # This code is from Sclar::Util. |
|
2395 |
- # warn $@; |
|
2396 |
- eval 'sub UNIVERSAL::a_sub_not_likely_to_be_here { ref($_[0]) }'; |
|
2397 |
- *Test::ModuleVersion::JSON::PP::blessed = sub { |
|
2398 |
- local($@, $SIG{__DIE__}, $SIG{__WARN__}); |
|
2399 |
- ref($_[0]) ? eval { $_[0]->a_sub_not_likely_to_be_here } : undef; |
|
2400 |
- }; |
|
2401 |
- my %tmap = qw( |
|
2402 |
- B::NULL SCALAR |
|
2403 |
- B::HV HASH |
|
2404 |
- B::AV ARRAY |
|
2405 |
- B::CV CODE |
|
2406 |
- B::IO IO |
|
2407 |
- B::GV GLOB |
|
2408 |
- B::REGEXP REGEXP |
|
2409 |
- ); |
|
2410 |
- *Test::ModuleVersion::JSON::PP::reftype = sub { |
|
2411 |
- my $r = shift; |
|
2412 |
- |
|
2413 |
- return undef unless length(ref($r)); |
|
2414 |
- |
|
2415 |
- my $t = ref(B::svref_2object($r)); |
|
2416 |
- |
|
2417 |
- return |
|
2418 |
- exists $tmap{$t} ? $tmap{$t} |
|
2419 |
- : length(ref($$r)) ? 'REF' |
|
2420 |
- : 'SCALAR'; |
|
2421 |
- }; |
|
2422 |
- *Test::ModuleVersion::JSON::PP::refaddr = sub { |
|
2423 |
- return undef unless length(ref($_[0])); |
|
2424 |
- |
|
2425 |
- my $addr; |
|
2426 |
- if(defined(my $pkg = blessed($_[0]))) { |
|
2427 |
- $addr .= bless $_[0], 'Scalar::Util::Fake'; |
|
2428 |
- bless $_[0], $pkg; |
|
2429 |
- } |
|
2430 |
- else { |
|
2431 |
- $addr .= $_[0] |
|
2432 |
- } |
|
2433 |
- |
|
2434 |
- $addr =~ /0x(\w+)/; |
|
2435 |
- local $^W; |
|
2436 |
- #no warnings 'portable'; |
|
2437 |
- hex($1); |
|
2438 |
- } |
|
2439 |
- } |
|
2440 |
-} |
|
2441 |
- |
|
2442 |
- |
|
2443 |
-# shamely copied and modified from JSON::XS code. |
|
2444 |
- |
|
2445 |
-$Test::ModuleVersion::JSON::PP::true = do { bless \(my $dummy = 1), "Test::ModuleVersion::JSON::PP::Boolean" }; |
|
2446 |
-$Test::ModuleVersion::JSON::PP::false = do { bless \(my $dummy = 0), "Test::ModuleVersion::JSON::PP::Boolean" }; |
|
2447 |
- |
|
2448 |
-sub is_bool { defined $_[0] and UNIVERSAL::isa($_[0], "Test::ModuleVersion::JSON::PP::Boolean"); } |
|
2449 |
- |
|
2450 |
-sub true { $Test::ModuleVersion::JSON::PP::true } |
|
2451 |
-sub false { $Test::ModuleVersion::JSON::PP::false } |
|
2452 |
-sub null { undef; } |
|
2453 |
- |
|
2454 |
-############################### |
|
2455 |
- |
|
2456 |
-package Test::ModuleVersion::JSON::PP::Boolean; |
|
2457 |
- |
|
2458 |
-use overload ( |
|
2459 |
- "0+" => sub { ${$_[0]} }, |
|
2460 |
- "++" => sub { $_[0] = ${$_[0]} + 1 }, |
|
2461 |
- "--" => sub { $_[0] = ${$_[0]} - 1 }, |
|
2462 |
- fallback => 1, |
|
2463 |
-); |
|
2464 |
- |
|
2465 |
- |
|
2466 |
-############################### |
|
2467 |
- |
|
2468 |
-package Test::ModuleVersion::JSON::PP::IncrParser; |
|
2469 |
- |
|
2470 |
-use strict; |
|
2471 |
- |
|
2472 |
-use constant INCR_M_WS => 0; # initial whitespace skipping |
|
2473 |
-use constant INCR_M_STR => 1; # inside string |
|
2474 |
-use constant INCR_M_BS => 2; # inside backslash |
|
2475 |
-use constant INCR_M_JSON => 3; # outside anything, count nesting |
|
2476 |
-use constant INCR_M_C0 => 4; |
|
2477 |
-use constant INCR_M_C1 => 5; |
|
2478 |
- |
|
2479 |
-$Test::ModuleVersion::JSON::PP::IncrParser::VERSION = '1.01'; |
|
2480 |
- |
|
2481 |
-my $unpack_format = $] < 5.006 ? 'C*' : 'U*'; |
|
2482 |
- |
|
2483 |
-sub new { |
|
2484 |
- my ( $class ) = @_; |
|
2485 |
- |
|
2486 |
- bless { |
|
2487 |
- incr_nest => 0, |
|
2488 |
- incr_text => undef, |
|
2489 |
- incr_parsing => 0, |
|
2490 |
- incr_p => 0, |
|
2491 |
- }, $class; |
|
2492 |
-} |
|
2493 |
- |
|
2494 |
- |
|
2495 |
-sub incr_parse { |
|
2496 |
- my ( $self, $coder, $text ) = @_; |
|
2497 |
- |
|
2498 |
- $self->{incr_text} = '' unless ( defined $self->{incr_text} ); |
|
2499 |
- |
|
2500 |
- if ( defined $text ) { |
|
2501 |
- if ( utf8::is_utf8( $text ) and !utf8::is_utf8( $self->{incr_text} ) ) { |
|
2502 |
- utf8::upgrade( $self->{incr_text} ) ; |
|
2503 |
- utf8::decode( $self->{incr_text} ) ; |
|
2504 |
- } |
|
2505 |
- $self->{incr_text} .= $text; |
|
2506 |
- } |
|
2507 |
- |
|
2508 |
- |
|
2509 |
- my $max_size = $coder->get_max_size; |
|
2510 |
- |
|
2511 |
- if ( defined wantarray ) { |
|
2512 |
- |
|
2513 |
- $self->{incr_mode} = INCR_M_WS unless defined $self->{incr_mode}; |
|
2514 |
- |
|
2515 |
- if ( wantarray ) { |
|
2516 |
- my @ret; |
|
2517 |
- |
|
2518 |
- $self->{incr_parsing} = 1; |
|
2519 |
- |
|
2520 |
- do { |
|
2521 |
- push @ret, $self->_incr_parse( $coder, $self->{incr_text} ); |
|
2522 |
- |
|
2523 |
- unless ( !$self->{incr_nest} and $self->{incr_mode} == INCR_M_JSON ) { |
|
2524 |
- $self->{incr_mode} = INCR_M_WS if $self->{incr_mode} != INCR_M_STR; |
|
2525 |
- } |
|
2526 |
- |
|
2527 |
- } until ( length $self->{incr_text} >= $self->{incr_p} ); |
|
2528 |
- |
|
2529 |
- $self->{incr_parsing} = 0; |
|
2530 |
- |
|
2531 |
- return @ret; |
|
2532 |
- } |
|
2533 |
- else { # in scalar context |
|
2534 |
- $self->{incr_parsing} = 1; |
|
2535 |
- my $obj = $self->_incr_parse( $coder, $self->{incr_text} ); |
|
2536 |
- $self->{incr_parsing} = 0 if defined $obj; # pointed by Martin J. Evans |
|
2537 |
- return $obj ? $obj : undef; # $obj is an empty string, parsing was completed. |
|
2538 |
- } |
|
2539 |
- |
|
2540 |
- } |
|
2541 |
- |
|
2542 |
-} |
|
2543 |
- |
|
2544 |
- |
|
2545 |
-sub _incr_parse { |
|
2546 |
- my ( $self, $coder, $text, $skip ) = @_; |
|
2547 |
- my $p = $self->{incr_p}; |
|
2548 |
- my $restore = $p; |
|
2549 |
- |
|
2550 |
- my @obj; |
|
2551 |
- my $len = length $text; |
|
2552 |
- |
|
2553 |
- if ( $self->{incr_mode} == INCR_M_WS ) { |
|
2554 |
- while ( $len > $p ) { |
|
2555 |
- my $s = substr( $text, $p, 1 ); |
|
2556 |
- $p++ and next if ( 0x20 >= unpack($unpack_format, $s) ); |
|
2557 |
- $self->{incr_mode} = INCR_M_JSON; |
|
2558 |
- last; |
|
2559 |
- } |
|
2560 |
- } |
|
2561 |
- |
|
2562 |
- while ( $len > $p ) { |
|
2563 |
- my $s = substr( $text, $p++, 1 ); |
|
2564 |
- |
|
2565 |
- if ( $s eq '"' ) { |
|
2566 |
- if (substr( $text, $p - 2, 1 ) eq '\\' ) { |
|
2567 |
- next; |
|
2568 |
- } |
|
2569 |
- |
|
2570 |
- if ( $self->{incr_mode} != INCR_M_STR ) { |
|
2571 |
- $self->{incr_mode} = INCR_M_STR; |
|
2572 |
- } |
|
2573 |
- else { |
|
2574 |
- $self->{incr_mode} = INCR_M_JSON; |
|
2575 |
- unless ( $self->{incr_nest} ) { |
|
2576 |
- last; |
|
2577 |
- } |
|
2578 |
- } |
|
2579 |
- } |
|
2580 |
- |
|
2581 |
- if ( $self->{incr_mode} == INCR_M_JSON ) { |
|
2582 |
- |
|
2583 |
- if ( $s eq '[' or $s eq '{' ) { |
|
2584 |
- if ( ++$self->{incr_nest} > $coder->get_max_depth ) { |
|
2585 |
- Carp::croak('json text or perl structure exceeds maximum nesting level (max_depth set too low?)'); |
|
2586 |
- } |
|
2587 |
- } |
|
2588 |
- elsif ( $s eq ']' or $s eq '}' ) { |
|
2589 |
- last if ( --$self->{incr_nest} <= 0 ); |
|
2590 |
- } |
|
2591 |
- elsif ( $s eq '#' ) { |
|
2592 |
- while ( $len > $p ) { |
|
2593 |
- last if substr( $text, $p++, 1 ) eq "\n"; |
|
2594 |
- } |
|
2595 |
- } |
|
2596 |
- |
|
2597 |
- } |
|
2598 |
- |
|
2599 |
- } |
|
2600 |
- |
|
2601 |
- $self->{incr_p} = $p; |
|
2602 |
- |
|
2603 |
- return if ( $self->{incr_mode} == INCR_M_STR and not $self->{incr_nest} ); |
|
2604 |
- return if ( $self->{incr_mode} == INCR_M_JSON and $self->{incr_nest} > 0 ); |
|
2605 |
- |
|
2606 |
- return '' unless ( length substr( $self->{incr_text}, 0, $p ) ); |
|
2607 |
- |
|
2608 |
- local $Carp::CarpLevel = 2; |
|
2609 |
- |
|
2610 |
- $self->{incr_p} = $restore; |
|
2611 |
- $self->{incr_c} = $p; |
|
2612 |
- |
|
2613 |
- my ( $obj, $tail ) = $coder->PP_decode_json( substr( $self->{incr_text}, 0, $p ), 0x10000001 ); |
|
2614 |
- |
|
2615 |
- $self->{incr_text} = substr( $self->{incr_text}, $p ); |
|
2616 |
- $self->{incr_p} = 0; |
|
2617 |
- |
|
2618 |
- return $obj or ''; |
|
2619 |
-} |
|
2620 |
- |
|
2621 |
- |
|
2622 |
-sub incr_text { |
|
2623 |
- if ( $_[0]->{incr_parsing} ) { |
|
2624 |
- Carp::croak("incr_text can not be called when the incremental parser already started parsing"); |
|
2625 |
- } |
|
2626 |
- $_[0]->{incr_text}; |
|
2627 |
-} |
|
2628 |
- |
|
2629 |
- |
|
2630 |
-sub incr_skip { |
|
2631 |
- my $self = shift; |
|
2632 |
- $self->{incr_text} = substr( $self->{incr_text}, $self->{incr_c} ); |
|
2633 |
- $self->{incr_p} = 0; |
|
2634 |
-} |
|
2635 |
- |
|
2636 |
- |
|
2637 |
-sub incr_reset { |
|
2638 |
- my $self = shift; |
|
2639 |
- $self->{incr_text} = undef; |
|
2640 |
- $self->{incr_p} = 0; |
|
2641 |
- $self->{incr_mode} = 0; |
|
2642 |
- $self->{incr_nest} = 0; |
|
2643 |
- $self->{incr_parsing} = 0; |
|
2644 |
-} |
|
2645 |
- |
|
2646 |
-package |
|
2647 |
- Test::ModuleVersion::ModuleURL; |
|
2648 |
-our @ISA = ('Test::ModuleVersion::Object::Simple'); |
|
2649 |
-use strict; |
|
2650 |
-use warnings; |
|
2651 |
-sub has { __PACKAGE__->Test::ModuleVersion::Object::Simple::attr(@_) } |
|
2652 |
- |
|
2653 |
-has distnames => sub { {} }; |
|
2654 |
-has privates => sub { {} }; |
|
2655 |
-has 'error'; |
|
2656 |
-has lwp => 'auto'; |
|
2657 |
- |
|
2658 |
-sub get { |
|
2659 |
- my ($self, $module, $version, $opts) = @_; |
|
2660 |
- |
|
2661 |
- $opts ||= {}; |
|
2662 |
- my $distnames = $self->distnames; |
|
2663 |
- my $privates = $self->privates; |
|
2664 |
- my $lwp = $self->lwp; |
|
2665 |
- |
|
2666 |
- # Module |
|
2667 |
- my $module_dist = $module; |
|
2668 |
- $module_dist = $distnames->{$module} if defined $distnames->{$module}; |
|
2669 |
- $module_dist =~ s/::/-/g; |
|
2670 |
- |
|
2671 |
- my $url; |
|
2672 |
- if ($url = $privates->{$module}) { |
|
2673 |
- $url =~ s/%M/"$module_dist-$version"/e; |
|
2674 |
- } |
|
2675 |
- else { |
|
2676 |
- |
|
2677 |
- # Get dounload URL using metaCPAN api |
|
2678 |
- my $metacpan_api = 'http://api.metacpan.org/v0'; |
|
2679 |
- my $search = "release/_search?q=name:$module_dist-$version" |
|
2680 |
- . "&fields=download_url,name"; |
|
2681 |
- my $module_info = "$metacpan_api/$search"; |
|
2682 |
- my $res = {}; |
|
2683 |
- my $agent; |
|
2684 |
- if ($lwp eq 'use' || $lwp eq 'auto' && eval { require LWP::UserAgent; LWP::UserAgent->VERSION(5.802) }) |
|
2685 |
- { |
|
2686 |
- require LWP::UserAgent; |
|
2687 |
- $agent = 'LWP::UserAgent'; |
|
2688 |
- my $ua = LWP::UserAgent->new( |
|
2689 |
- parse_head => 0, |
|
2690 |
- env_proxy => 1, |
|
2691 |
- agent => "Test::ModuleVersion/$VERSION", |
|
2692 |
- timeout => 30 |
|
2693 |
- ); |
|
2694 |
- my $r = $ua->get($module_info); |
|
2695 |
- $agent = 'LWP::UserAgent'; |
|
2696 |
- $res->{success} = $r->is_success; |
|
2697 |
- $res->{status_line} = $r->status_line; |
|
2698 |
- $res->{content} = $r->content; |
|
2699 |
- } |
|
2700 |
- else { |
|
2701 |
- $agent = 'HTTP::Tiny'; |
|
2702 |
- my $ua = Test::ModuleVersion::HTTP::Tiny->new; |
|
2703 |
- my $r = $ua->get($module_info); |
|
2704 |
- $res->{success} = $r->{success}; |
|
2705 |
- $res->{status_line} = "$r->{status} $r->{reason}"; |
|
2706 |
- $res->{content} = $r->{content}; |
|
2707 |
- } |
|
2708 |
- |
|
2709 |
- my $error; |
|
2710 |
- if ($res->{success} && !$ENV{TEST_MODULEVERSION_REQUEST_FAIL}) { |
|
2711 |
- my $release = Test::ModuleVersion::JSON::PP::decode_json $res->{content}; |
|
2712 |
- $url = $release->{hits}{hits}[0]{fields}{download_url}; |
|
2713 |
- $error = "$module_dist-$version is unknown" unless defined $url; |
|
2714 |
- } |
|
2715 |
- else { |
|
2716 |
- $error = "Request to metaCPAN fail($res->{status_line}):$agent:$module_info"; |
|
2717 |
- } |
|
2718 |
- $self->error($error); |
|
2719 |
- } |
|
2720 |
- |
|
2721 |
- return $url; |
|
2722 |
-} |
|
2723 |
- |
|
2724 |
- |
|
2725 |
-package Test::ModuleVersion; |
|
2726 |
-our @ISA = ('Test::ModuleVersion::Object::Simple'); |
|
2727 |
-use strict; |
|
2728 |
-use warnings; |
|
2729 |
-use ExtUtils::Installed; |
|
2730 |
-use Carp 'croak'; |
|
2731 |
-use Data::Dumper; |
|
2732 |
- |
|
2733 |
-sub has { __PACKAGE__->Test::ModuleVersion::Object::Simple::attr(@_) } |
|
2734 |
-has before => ''; |
|
2735 |
-has distnames => sub { {} }; |
|
2736 |
-has default_ignore => sub { ['Perl', 'Test::ModuleVersion'] }; |
|
2737 |
-has lib => sub { [] }; |
|
2738 |
-has modules => sub { [] }; |
|
2739 |
-has privates => sub { {} }; |
|
2740 |
- |
|
2741 |
-sub detect { |
|
2742 |
- my ($self, %opts) = @_; |
|
2743 |
- my $ignore = $opts{ignore} || []; |
|
2744 |
- |
|
2745 |
- # Detect installed modules |
|
2746 |
- my $ei = ExtUtils::Installed->new; |
|
2747 |
- my @modules; |
|
2748 |
- for my $module (sort $ei->modules) { |
|
2749 |
- next if grep { $module eq $_ } @$ignore; |
|
2750 |
- eval "require $module"; |
|
2751 |
- no strict 'refs'; |
|
2752 |
- my $version = ${"${module}::VERSION"}; |
|
2753 |
- push @modules, [$module => $version] if length $version; |
|
2754 |
- } |
|
2755 |
- |
|
2756 |
- return \@modules; |
|
2757 |
-} |
|
2758 |
- |
|
2759 |
-sub test_script { |
|
2760 |
- my ($self, %opts) = @_; |
|
2761 |
- |
|
2762 |
- # Code |
|
2763 |
- my $code; |
|
2764 |
- |
|
2765 |
- # Library path |
|
2766 |
- my $libs = ref $self->lib ? $self->lib : [$self->lib]; |
|
2767 |
- $code .= "use FindBin;\n"; |
|
2768 |
- $code .= qq|use lib "\$FindBin::Bin/$_";\n| for @$libs; |
|
2769 |
- |
|
2770 |
- # Before |
|
2771 |
- $code .= $self->before . "\n"; |
|
2772 |
- |
|
2773 |
- # Reffer this module |
|
2774 |
- $code .= "# Created by Test::ModuleVersion $Test::ModuleVersion::VERSION\n"; |
|
2775 |
- |
|
2776 |
- # Test code |
|
2777 |
- $code .= <<'EOS'; |
|
2778 |
-use Test::More; |
|
2779 |
-use strict; |
|
2780 |
-use warnings; |
|
2781 |
-use ExtUtils::Installed; |
|
2782 |
-EOS |
|
2783 |
- |
|
2784 |
- # Main |
|
2785 |
- $code .= <<'EOS'; |
|
2786 |
- |
|
2787 |
-sub main { |
|
2788 |
- my $command = shift; |
|
2789 |
- my @options = @_; |
|
2790 |
- |
|
2791 |
- die qq/command "$command" is unkonwn command/ |
|
2792 |
- if defined $command && $command ne 'list'; |
|
2793 |
- |
|
2794 |
- my $list_failed; |
|
2795 |
- my $lwp = 'auto'; |
|
2796 |
- for my $option (@options) { |
|
2797 |
- if ($option eq '--fail') { $list_failed = 1 } |
|
2798 |
- elsif ($option eq '--lwp') { $lwp = 'use' } |
|
2799 |
- elsif ($option eq '--no-lwp') { $lwp = 'no' } |
|
2800 |
- else { die qq/list $option is unknown option/ } |
|
2801 |
- } |
|
2802 |
- |
|
2803 |
- if (defined $command) { |
|
2804 |
- my $builder = Test::More->builder; |
|
2805 |
- open my $out_fh, '>', undef; |
|
2806 |
- $builder->output($out_fh); |
|
2807 |
- $builder->failure_output($out_fh); |
|
2808 |
- $builder->todo_output($out_fh); |
|
2809 |
- } |
|
2810 |
- |
|
2811 |
- my $modules = []; |
|
2812 |
- my $failed = []; |
|
2813 |
- my $require_ok; |
|
2814 |
- my $version_ok; |
|
2815 |
- my $version; |
|
2816 |
- |
|
2817 |
- plan tests => <%%%%%% test_count %%%%%%>; |
|
2818 |
- |
|
2819 |
-EOS |
|
2820 |
- |
|
2821 |
- # Module and version check |
|
2822 |
- my $test_count = 0; |
|
2823 |
- for my $m (@{$self->modules}) { |
|
2824 |
- my ($module, $version) = @$m; |
|
2825 |
- $code .= " # $module\n" |
|
2826 |
- . " \$require_ok = require_ok('$module');\n" |
|
2827 |
- . " \$version_ok = is(\$${module}::VERSION, '$version', '$module version: $version');\n" |
|
2828 |
- . " push \@\$modules, ['$module' => '$version'];\n" |
|
2829 |
- . " push \@\$failed, ['$module' => '$version'] unless \$require_ok && \$version_ok;\n\n"; |
|
2830 |
- $test_count += 2; |
|
2831 |
- } |
|
2832 |
- |
|
2833 |
- # Print module URLs |
|
2834 |
- $code .= <<'EOS'; |
|
2835 |
- # Print module URLs |
|
2836 |
- if (defined $command) { |
|
2837 |
- my $distnames = <%%%%%% distnames %%%%%%> |
|
2838 |
- ; |
|
2839 |
- my $privates = <%%%%%% privates %%%%%%> |
|
2840 |
- ; |
|
2841 |
- my $tm = Test::ModuleVersion->new; |
|
2842 |
- my @ms = $command eq 'list' && $list_failed ? @$failed |
|
2843 |
- : $command eq 'list' ? @$modules |
|
2844 |
- : []; |
|
2845 |
- for my $m (@ms) { |
|
2846 |
- my ($module, $version) = @$m; |
|
2847 |
- my $mu = Test::ModuleVersion::ModuleURL->new; |
|
2848 |
- $mu->distnames($distnames); |
|
2849 |
- $mu->privates($privates); |
|
2850 |
- $mu->lwp($lwp); |
|
2851 |
- my $url = $mu->get($module, $version); |
|
2852 |
- if (defined $url) { print "$url\n" } |
|
2853 |
- else { print STDERR $mu->error . "\n" } |
|
2854 |
- } |
|
2855 |
- } |
|
2856 |
-} |
|
2857 |
- |
|
2858 |
-EOS |
|
2859 |
- |
|
2860 |
- # Embbed Test::ModuleVersion |
|
2861 |
- $code .= $self->_source . "\n"; |
|
2862 |
- |
|
2863 |
- # Run |
|
2864 |
- $code .= "package main;\n" |
|
2865 |
- . "main(\@ARGV);\n"; |
|
2866 |
- |
|
2867 |
- # Test count |
|
2868 |
- $code =~ s/<%%%%%% test_count %%%%%%>/$test_count/e; |
|
2869 |
- |
|
2870 |
- # Distribution names |
|
2871 |
- my $distnames_code = Data::Dumper->new([$self->distnames])->Terse(1)->Indent(2)->Dump; |
|
2872 |
- $code =~ s/<%%%%%% distnames %%%%%%>/$distnames_code/e; |
|
2873 |
- |
|
2874 |
- # Private repositories |
|
2875 |
- my $privates_code = Data::Dumper->new([$self->privates])->Terse(1)->Indent(2)->Dump; |
|
2876 |
- $code =~ s/<%%%%%% privates %%%%%%>/$privates_code/e; |
|
2877 |
- |
|
2878 |
- if (my $file = $opts{output}) { |
|
2879 |
- open my $fh, '>', $file |
|
2880 |
- or die qq/Can't open file "$file": $!/; |
|
2881 |
- print $fh $code; |
|
2882 |
- } |
|
2883 |
- return $code; |
|
2884 |
-} |
|
2885 |
- |
|
2886 |
-sub _source { |
|
2887 |
- my $self = shift; |
|
2888 |
- |
|
2889 |
- # Source |
|
2890 |
- my $class = __PACKAGE__; |
|
2891 |
- $class =~ s/::/\//g; |
|
2892 |
- $class .= '.pm'; |
|
2893 |
- my $path = $INC{$class}; |
|
2894 |
- open my $fh, '<', $path |
|
2895 |
- or croak qq/Can't open "$path": $!/; |
|
2896 |
- my $source; |
|
2897 |
- while (my $line = <$fh>) { |
|
2898 |
- last if $line =~ /^=head1/; |
|
2899 |
- $source .= $line; |
|
2900 |
- } |
|
2901 |
- return $source; |
|
2902 |
-} |
|
2903 |
- |
|
2904 |
-1; |
|
2905 |
- |
|
2906 |
- |
|
2907 |
-package main; |
|
2908 |
-main(@ARGV); |