From 21fbd0742446ebcbd198d48b4e521e23eb4c8b34 Mon Sep 17 00:00:00 2001 From: Tiago Peczenyj Date: Sun, 10 Dec 2023 17:13:29 +0100 Subject: [PATCH] finish TO_JSON method --- README.pod | 126 +++++++++++++++++++++++++++++++++ lib/GDPR/IAB/TCFv2.pm | 89 ++++++++++++++++++++--- lib/GDPR/IAB/TCFv2/BitUtils.pm | 4 +- 3 files changed, 208 insertions(+), 11 deletions(-) diff --git a/README.pod b/README.pod index d895920..6300f3a 100644 --- a/README.pod +++ b/README.pod @@ -100,8 +100,63 @@ Will die if can't decode the string. 'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA.argAC0gAAAAAAAAAAAA' ); + or + + use GDPR::IAB::TCFv2; + + my $consent = GDPR::IAB::TCFv2->Parse( + 'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA.argAC0gAAAAAAAAAAAA', + json => { + verbose => 0, + compact => 1, + use_epoch => 0, + boolean_values => [ 0, 1 ], + date_format => '%Y%m%d', # yyymmdd + }, + ); + +Parse may receive an optional hash parameter C with the following properties: + +=over + +=item * + +C changes the json encoding. By default we omit some false values such as C to create +a compact json representation. With C we will present everything. See L for more details. + +=item * + +C changes the json encoding. All fields that are a mapping of something to a boolean will be changed to an array +of all elements keys where the value is true. This affects the following fields: C, +C, C, C and C. See L for more details. + +=item * + +C changes the json encode. By default we format the C and C are converted to string using +L. With C we will return the unix epoch in seconds. +See L for more details. + +=item * + +C if present, expects an arrayref if two elements: the C and the C values to be used in json encoding. +If omit, we will try to use C and C if the package L is available, else we will fallback to C<0> and C<1>. + +=item * + +C if present accepts two kinds of value: an C (to be used on C) or a code reference to a subroutine that +will be called with two arguments: epoch in seconds and nanoseconds. If omitted the format L will be used +except if the option C is true. + +=back + =head1 METHODS +=head2 tc_string + +Returns the original consent string. + +The consent object L will call this method on string interpolations. + =head2 version Version number of the encoding format. The value is 2 for this format. @@ -280,6 +335,77 @@ For the avoidance of doubt: In case a vendor has declared flexibility for a purpose and there is no legal basis restriction signal it must always apply the default legal basis under which the purpose was registered aside from being registered as flexible. That means if a vendor declared a purpose as legitimate interest and also declared that purpose as flexible it may not apply a "consent" signal without a legal basis restriction signal to require consent. +=head2 TO_JSON + +Will serialize the consent object into a hash reference. The objective is to be used by L package. + +With option C, the encoder will call this method. + + use strict; + use warnings; + use feature qw; + + use JSON; + use DateTime; + use DateTimeX::TO_JSON formatter => 'DateTime::Format::RFC3339'; + use GDPR::IAB::TCFv2; + + my $consent = GDPR::IAB::TCFv2->Parse( + 'COyiILmOyiILmADACHENAPCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAAAAAA', + json => { + compact => 1, + date_format => sub { # can be omitted, with DateTimeX::TO_JSON + my ( $epoch, $ns ) = @_; + + return DateTime->from_epoch( epoch => $epoch ) + ->set_nanosecond($ns); + }, + }, + ); + + my $json = JSON->new->convert_blessed; + my $encoded = $json->pretty->encode($consent); + + say $encoded; + + # outputs: + + { + "tc_string":"COyiILmOyiILmADACHENAPCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAAAAAA", + "consent_language":"EN", + "purposes_consent":[], + "vendor_legitimate_interests":[], + "cmp_id":3, + "purpose_one_treatment":false, + "special_features_opt_in":[], + "last_updated":"2020-04-27T20:27:54.200000000Z", + "use_non_standard_stacks":false, + "policy_version":2, + "version":2, + "vendor_consents":[ + 23, + 42, + 126, + 127, + 128, + 587, + 613, + 626 + ], + "is_service_specific":false, + "created":"2020-04-27T20:27:54.200000000Z", + "consent_screen":7, + "vendor_list_version":15, + "cmp_version":2, + "purposes_legitimate_interest":[], + "publisher_country_code":"AA" + } + +If L is installed, the C method will use C and C as boolean value. + +By default it returns a compacted format where we omit the C on fields like C and we convert the dates +using L. This behaviour can be changed by extra option in the L constructor. + =head1 FUNCTIONS =head2 looksLikeIsConsentVersion2 diff --git a/lib/GDPR/IAB/TCFv2.pm b/lib/GDPR/IAB/TCFv2.pm index 9a696f7..0b11067 100644 --- a/lib/GDPR/IAB/TCFv2.pm +++ b/lib/GDPR/IAB/TCFv2.pm @@ -671,25 +671,47 @@ Will die if can't decode the string. my $consent = GDPR::IAB::TCFv2->Parse( 'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA.argAC0gAAAAAAAAAAAA', - verbose => 1, - use_epoch => 0, + json => { + verbose => 0, + compact => 1, + use_epoch => 0, + boolean_values => [ 0, 1 ], + date_format => '%Y%m%d', # yyymmdd + }, ); -Parse may receive optional parameters such as +Parse may receive an optional hash parameter C with the following properties: =over =item * -C changes the json encode. By default we omit some false values such as C to create +C changes the json encoding. By default we omit some false values such as C to create a compact json representation. With C we will present everything. See L for more details. =item * +C changes the json encoding. All fields that are a mapping of something to a boolean will be changed to an array +of all elements keys where the value is true. This affects the following fields: C, +C, C, C and C. See L for more details. + +=item * + C changes the json encode. By default we format the C and C are converted to string using L. With C we will return the unix epoch in seconds. See L for more details. +=item * + +C if present, expects an arrayref if two elements: the C and the C values to be used in json encoding. +If omit, we will try to use C and C if the package L is available, else we will fallback to C<0> and C<1>. + +=item * + +C if present accepts two kinds of value: an C (to be used on C) or a code reference to a subroutine that +will be called with two arguments: epoch in seconds and nanoseconds. If omitted the format L will be used +except if the option C is true. + =back =head1 METHODS @@ -884,18 +906,65 @@ Will serialize the consent object into a hash reference. The objective is to be With option C, the encoder will call this method. - use GDPR::IAB::TCFv2; + use strict; + use warnings; + use feature qw; + use JSON; + use DateTime; + use DateTimeX::TO_JSON formatter => 'DateTime::Format::RFC3339'; + use GDPR::IAB::TCFv2; my $consent = GDPR::IAB::TCFv2->Parse( - 'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA.argAC0gAAAAAAAAAAAA', - verbose => 0, - use_epoch => 0, + 'COyiILmOyiILmADACHENAPCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAAAAAA', + json => { + compact => 1, + date_format => sub { # can be omitted, with DateTimeX::TO_JSON + my ( $epoch, $ns ) = @_; + + return DateTime->from_epoch( epoch => $epoch ) + ->set_nanosecond($ns); + }, + }, ); - use feature qw; + my $json = JSON->new->convert_blessed; + my $encoded = $json->pretty->encode($consent); + + say $encoded; + + # outputs: - say JSON->new->allow_blessed(1)->convert_blessed(1)->pretty(1)->encode($consent); + { + "tc_string":"COyiILmOyiILmADACHENAPCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAAAAAA", + "consent_language":"EN", + "purposes_consent":[], + "vendor_legitimate_interests":[], + "cmp_id":3, + "purpose_one_treatment":false, + "special_features_opt_in":[], + "last_updated":"2020-04-27T20:27:54.200000000Z", + "use_non_standard_stacks":false, + "policy_version":2, + "version":2, + "vendor_consents":[ + 23, + 42, + 126, + 127, + 128, + 587, + 613, + 626 + ], + "is_service_specific":false, + "created":"2020-04-27T20:27:54.200000000Z", + "consent_screen":7, + "vendor_list_version":15, + "cmp_version":2, + "purposes_legitimate_interest":[], + "publisher_country_code":"AA" + } If L is installed, the C method will use C and C as boolean value. diff --git a/lib/GDPR/IAB/TCFv2/BitUtils.pm b/lib/GDPR/IAB/TCFv2/BitUtils.pm index 3d84737..99e8893 100644 --- a/lib/GDPR/IAB/TCFv2/BitUtils.pm +++ b/lib/GDPR/IAB/TCFv2/BitUtils.pm @@ -5,6 +5,8 @@ use integer; use bytes; use Math::BigInt; +use Carp qw(croak confess); + require Exporter; use base qw; @@ -30,7 +32,7 @@ our @EXPORT_OK = qw= length($data); return substr( $data, $offset, 1 ) == 1; }