LJSUP-17669: Login.bml form refactoring
[livejournal.git] / cgi-bin / LJ / JSON.pm
blob8eda1c84b84d91d2ad8095daf6b86a122a1be11b
1 package LJ::JSON;
2 use strict;
4 my $wrap;
6 sub to_json {
7 my ($class, @args) = @_;
9 return $wrap->encode(@args);
12 sub from_json {
13 my ($class, $dump) = @_;
15 return unless $dump;
16 return $wrap->decode($dump);
19 sub class {
20 return ref $wrap;
23 sub true { $wrap->true };
24 sub false { $wrap->false };
26 sub to_boolean {
27 my ( $class, $what ) = @_;
28 return $what ? $wrap->true : $wrap->false;
31 sub to_number {
32 my ( $class, $what ) = @_;
34 # not using int deliberately because we may be handling floats here
35 return $what + 0;
38 foreach my $class (qw(LJ::JSON::XS LJ::JSON::JSONv2 LJ::JSON::JSONv1)) {
39 if ($class->can_load) {
40 $wrap = $class->new;
41 last;
44 die unless $wrap;
48 package LJ::JSON::Wrapper;
50 use Encode qw();
52 sub traverse {
53 my ($class, $what, $sub) = @_;
55 my $type = ref $what;
57 # simple scalar
58 if ($type eq '') {
59 return $sub->($what);
62 # hashref
63 if ($type eq 'HASH') {
64 my %ret;
65 foreach my $k (keys %$what) {
66 $ret{$sub->($k)} = $class->traverse($what->{$k}, $sub);
68 return \%ret;
71 # arrayref
72 if ($type eq 'ARRAY') {
73 my @ret;
74 foreach my $v (@$what) {
75 push @ret, $class->traverse($v, $sub);
77 return \@ret;
80 # unknown type; let the subclass decode it to a scalar
81 # (base class function defaults to plain stringification)
82 return $sub->($class->decode_unknown_type($what));
85 sub traverse_fix_encoding {
86 my ($class, $what) = @_;
88 return $class->traverse($what, sub {
89 my ($scalar) = @_;
91 return $scalar unless Encode::is_utf8($scalar);
93 # if the string does indeed contain wide characters (which happens
94 # in case the source string literals contained chars specified as
95 # '\u041c'), encode stuff as utf8
96 if ($scalar =~ /[^\x01-\xff]/) {
97 return Encode::encode("utf8", $scalar);
100 return Encode::encode("iso-8859-1", $scalar);
104 sub decode_unknown_type {
105 my ($class, $what) = @_;
107 return "$what";
110 sub clean_after_encode {
111 my ($class, $encoded) = @_;
113 unless (Encode::is_utf8($encoded)) {
114 $encoded = Encode::decode('utf8', $encoded);
117 # Perl 5.10 do not understand \x{00ad} sequence as Unicode char in the regexp s/...|\x{00ad}|.../, therefore we used char class.
118 # Dangerous symbols, that were tested on the Chrome and were a reason of its crush: \r \n \x{2028} \x{2029}
119 $encoded =~ s/[\r\n\x{0000}\x{0085}\x{00ad}\x{2028}\x{2029}\x{0600}-\x{0604}\x{070f}\x{17b4}\x{17b5}\x{200c}-\x{200f}\x{202a}-\x{202f}\x{2060}-\x{206f}\x{feff}\x{fff0}-\x{ffff}]//gs;
121 return Encode::encode('utf8', $encoded);
124 package LJ::JSON::XS;
126 our @ISA;
127 BEGIN { @ISA = qw(LJ::JSON::Wrapper JSON::XS); }
129 sub can_load {
130 eval { require JSON::XS; JSON::XS->import; };
131 return !$@;
134 sub new {
135 my ($class) = @_;
136 return $class->SUPER::new->latin1;
139 sub encode {
140 my $class = shift;
141 my $encoded = $class->SUPER::encode(@_);
142 return $class->clean_after_encode($encoded);
145 sub decode {
146 my ($class, $dump) = @_;
148 my $decoded = $class->SUPER::decode($dump);
149 $decoded = $class->traverse_fix_encoding($decoded);
150 return $decoded;
153 sub decode_unknown_type {
154 my ($class, $what) = @_;
156 # booleans get converted to undef for false and 1 for true
157 return $what ? 1 : undef if JSON::XS::is_bool($what);
159 # otherwise, stringify
160 return "$what";
165 package LJ::JSON::JSONv2;
167 our @ISA;
168 BEGIN { @ISA = qw(LJ::JSON::Wrapper JSON); }
170 sub can_load {
171 eval { require JSON };
172 return !$@ && $JSON::VERSION ge 2;
175 sub new {
176 my ($class) = @_;
177 return $class->SUPER::new->latin1;
180 sub decode {
181 my ($class, $dump) = @_;
183 my $decoded = $class->SUPER::decode($dump);
184 $decoded = $class->traverse_fix_encoding($decoded);
185 return $decoded;
188 sub decode_unknown_type {
189 my ($class, $what) = @_;
191 # booleans get converted to undef for false and 1 for true
192 return $what ? 1 : undef if JSON::is_bool($what);
194 # otherwise, stringify
195 return "$what";
200 package LJ::JSON::JSONv1;
202 our @ISA;
203 BEGIN { @ISA = qw(LJ::JSON::Wrapper JSON); }
205 sub can_load {
206 eval { require JSON };
207 return !$@ && $JSON::VERSION ge 1;
210 *encode = \&JSON::objToJson;
211 *decode = \&JSON::jsonToObj;