GDPR-IAB-TCFv2

 view release on metacpan or  search on metacpan

lib/GDPR/IAB/TCFv2/BitField.pm  view on Meta::CPAN

sub TO_JSON {
  my ($self, $filter_id) = @_;

  if (defined $filter_id) {
    my $val = $self->contains($filter_id);

    if (!!$self->{options}->{json}->{compact}) {
      return $val ? [$filter_id] : [];
    }

    my ($false, $true) = @{$self->{options}->{json}->{boolean_values}};
    my $bool_val = $val ? $true : $false;

    if (!!$self->{options}->{json}->{verbose}) {
      return {$filter_id => $bool_val};
    }

    return $val ? {$filter_id => $true} : {};
  }

  if (!!$self->{options}->{json}->{compact}) {
    return $self->all;
  }

  my $bitstring = unpack("B*", substr($self->{data}, $self->{offset} >> 3));
  $bitstring = substr($bitstring, $self->{offset} & 7, $self->{max_id});

  my ($false, $true) = @{$self->{options}->{json}->{boolean_values}};

  if (!!$self->{options}->{json}->{verbose}) {
    my %map = map { $_ => $false } 1 .. $self->{max_id};
    while ($bitstring =~ /1/g) {
      $map{pos($bitstring)} = $true;
    }
    return \%map;
  }

  my %map;

lib/GDPR/IAB/TCFv2/Parser.pm  view on Meta::CPAN


  croak 'missing gdpr consent string' unless defined $tc_string && length $tc_string;

  my $segments = _decode_tc_string_segments($tc_string);

  my $strict = !!$opts{strict};

  my %options = (json => $opts{json} // {}, strict => $strict, reference_time => $opts{reference_time},);

  $options{json}->{date_format}    ||= DATE_FORMAT_ISO_8601;
  $options{json}->{boolean_values} ||= [_json_false(), _json_true()];

  if (exists $opts{prefetch}) {
    my $prefetch = $opts{prefetch};

    $prefetch = [$prefetch] if ref($prefetch) ne ref([]);

    $options{prefetch} = $prefetch;
  }

  my $self = {

lib/GDPR/IAB/TCFv2/Parser.pm  view on Meta::CPAN


  return {map { @{$_} } grep { $verbose || $_->[1] } @data};
}

sub TO_JSON {
  my $self = shift;

  my %args      = (@_ && ref $_[-1] eq 'HASH') ? %{pop @_} : @_;
  my $filter_id = $args{vendor_id} // $self->{options}->{json}->{vendor_id};

  my ($false, $true) = @{$self->{options}->{json}->{boolean_values}};

  my $created      = $self->_format_date($self->created);
  my $last_updated = $self->_format_date($self->last_updated);

  my $purpose_consents = $self->_format_json_subsection(
    map { [
      $_ => (
          $filter_id
        ? $self->is_vendor_allowed_for_any_basis($filter_id, $_)
        : $self->_safe_is_purpose_consent_allowed($_)

lib/GDPR/IAB/TCFv2/Parser.pm  view on Meta::CPAN

or

    use GDPR::IAB::TCFv2::Parser;

    my $consent = GDPR::IAB::TCFv2::Parser->Parse(
        'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA.argAC0gAAAAAAAAAAAA',
        json => {
            verbose        => 0,
            compact        => 1,
            use_epoch      => 0,
            boolean_values => [ 0, 1 ],
            date_format    => '%Y%m%d',    # yyymmdd
        },
        strict => 1,
        prefetch => 284,
    );

Parse may receive an optional hash with the following parameters:

=over

lib/GDPR/IAB/TCFv2/Parser.pm  view on Meta::CPAN

C<purpose/consents>, C<purpose/legitimate_interests>, C<vendor/consents> and C<vendor/legitimate_interests>. See L</TO_JSON> for more details.

=item *

C<use_epoch> changes the json encode. By default we format the C<created> and C<last_updated> are converted to string using
L<ISO_8601|https://en.wikipedia.org/wiki/ISO_8601>. With C<use_epoch> we will return the unix epoch in seconds.
See L</TO_JSON> for more details.

=item *

C<boolean_values> if present, expects an arrayref if two elements: the C<false> and the C<true> values to be used in json encoding.
If omit, we will try to use C<JSON::false> and C<JSON::true> if the package L<JSON> is available, else we will fallback to C<0> and C<1>.

=item *

C<date_format> if present accepts two kinds of value: an C<string> (to be used on C<POSIX::strftime>) or a code reference to a subroutine that
will be called with two arguments: epoch in seconds and nanoseconds. If omitted the format L<ISO_8601|https://en.wikipedia.org/wiki/ISO_8601> will be used
except if the option C<use_epoch> is true.

=item *

lib/GDPR/IAB/TCFv2/PublisherTC.pm  view on Meta::CPAN

      consents             => $self->_format_json_subsection(\%custom_purpose_consents, $self->{num_custom_purposes}),
      legitimate_interests =>
        $self->_format_json_subsection(\%custom_purpose_legitimate_interests, $self->{num_custom_purposes}),
    },
  };
}

sub _format_json_subsection {
  my ($self, $data, $max) = @_;

  my ($false, $true) = @{$self->{options}->{json}->{boolean_values}};

  if (!!$self->{options}->{json}->{compact}) {
    return [grep { $data->{$_} } 1 .. $max,];
  }

  my $verbose = !!$self->{options}->{json}->{verbose};

  return $data if $verbose;

  return {map { $_ => $true } grep { $data->{$_} } keys %{$data}};

lib/GDPR/IAB/TCFv2/RangeSection.pm  view on Meta::CPAN

sub TO_JSON {
  my ($self, $filter_id) = @_;

  if (defined $filter_id) {
    my $val = $self->contains($filter_id);

    if (!!$self->{options}->{json}->{compact}) {
      return $val ? [$filter_id] : [];
    }

    my ($false, $true) = @{$self->{options}->{json}->{boolean_values}};
    my $bool_val = $val ? $true : $false;

    if (!!$self->{options}->{json}->{verbose}) {
      return {$filter_id => $bool_val};
    }

    return $val ? {$filter_id => $true} : {};
  }

  return $self->all if !!$self->{options}->{json}->{compact};

  my ($false, $true) = @{$self->{options}->{json}->{boolean_values}};

  my %map;
  if (!!$self->{options}->{json}->{verbose}) {
    %map = map { $_ => $false } 1 .. $self->{max_id};
  }

  # Direct hash-element assignment per range; the previous form
  # `%map = ( %map, map { $_ => $true } ... )` re-copied the running
  # hash into a list every iteration, accidentally O(n^2) over the
  # number of vendor IDs already accumulated.

t/02-json-bitfield.t  view on Meta::CPAN


subtest "bitfield" => sub {
  subtest "should convert data to json using compact flag and 0/1 as booleans" => sub {
    subtest "should convert data to json using yyyymmdd as date format" => sub {
      my $consent = GDPR::IAB::TCFv2->Parse(
        'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA',
        json => {
          verbose        => 0,
          compact        => 1,
          use_epoch      => 0,
          boolean_values => [0, 1],
          date_format    => '%Y%m%d',    # yyymmdd
        },
      );


      my $got      = $consent->TO_JSON();
      my $expected = _fixture_bitfield_compact(created => 20081207, last_updated => 20120110,);
      is_deeply $got, $expected, "must return the json hashref";

      done_testing;
    };

    subtest "should convert data to json using epoch date format" => sub {
      my $consent = GDPR::IAB::TCFv2->Parse(
        'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA',
        json => {verbose => 0, compact => 1, use_epoch => 1, boolean_values => [0, 1],},
      );


      my $got      = $consent->TO_JSON();
      my $expected = _fixture_bitfield_compact(created => 1228644257, last_updated => 1326215413,);

      is_deeply $got, $expected, "must return the json hashref";

      done_testing;
    };

    done_testing;
  };

  subtest "should convert data to json using default (non-compact) and 0/1 as booleans" => sub {

    subtest "default non verbose, date as iso 8601" => sub {
      my $consent = GDPR::IAB::TCFv2->Parse(
        'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA',
        json => {verbose => 0, compact => 0, use_epoch => 0, boolean_values => [0, 1],},
      );

      ok $consent->vendor_consent(27);

      my $got      = $consent->TO_JSON();
      my $expected = _fixture_bitfield_default();
      is_deeply $got, $expected, "must return the json hashref";

      done_testing;
    };

    subtest "default verbose, date as iso 8601" => sub {
      my $consent = GDPR::IAB::TCFv2->Parse(
        'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA',
        json => {verbose => 1, compact => 0, use_epoch => 0, boolean_values => [0, 1],},
      );

      ok $consent->vendor_consent(27);

      my $got      = $consent->TO_JSON();
      my $expected = _fixture_bitfield_verbose();
      is_deeply $got, $expected, "must return the json hashref";

      done_testing;
    };

    done_testing;
  };

  subtest "publisher section" => sub {
    subtest "publisher section without publisher_tc" => sub {
      my $consent = GDPR::IAB::TCFv2->Parse('COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA',
        json => {verbose => 0, compact => 1, use_epoch => 0, boolean_values => [0, 1],},);

      my $got      = $consent->TO_JSON;
      my $expected = {"publisher" => {"restrictions" => {"7" => {"32" => 1}}}};

      is_deeply $got->{publisher}, $expected->{publisher}, "must return the same publisher restriction section";

      done_testing;
    };
    subtest "publisher section with publisher_tc" => sub {
      subtest "without custom purposes" => sub {
        my $consent = GDPR::IAB::TCFv2->Parse(
          'COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA.argAC0gAAAAAAAAAAAA',
          json => {verbose => 0, compact => 1, use_epoch => 0, boolean_values => [0, 1],},
        );

        my $got      = $consent->TO_JSON;
        my $expected = {
          "publisher" => {
            "consents"             => [2, 4, 6, 8, 9, 10],
            "legitimate_interests" => [2, 4, 5, 7, 10],
            "custom_purposes"      => {"consents" => [], "legitimate_interests" => [],},
            "restrictions"         => {"7"        => {"32" => 1}}
          }
        };

        is_deeply $got->{publisher}, $expected->{publisher}, "must return the same publisher restriction section";

        done_testing;
      };

      subtest "with custom purposes" => sub {
        my $consent = GDPR::IAB::TCFv2->Parse(
          'COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA.YAAAAAAAAXA',
          json => {verbose => 0, compact => 1, use_epoch => 0, boolean_values => [0, 1],},
        );

        my $got      = $consent->TO_JSON;
        my $expected = {
          "publisher" => {
            "consents"             => [],
            "legitimate_interests" => [],
            "custom_purposes"      => {"consents" => [1, 2], "legitimate_interests" => [1],},
            "restrictions"         => {"7"        => {"32" => 1}}
          }

t/02-json-bitfield.t  view on Meta::CPAN


      done_testing;
    };

    done_testing;
  };

  subtest "TO_JSON method should return the same hashref " => sub {
    my $consent = GDPR::IAB::TCFv2->Parse(
      'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA',
      json => {verbose => 0, compact => 1, use_epoch => 0, boolean_values => [0, 1],},
    );


    my $got1 = $consent->TO_JSON();
    my $got2 = $consent->TO_JSON();

    is_deeply $got1, $got2, "must return the same hashref";

    done_testing;
  };

t/03-json-range.t  view on Meta::CPAN


subtest "range" => sub {
  subtest "should convert data to json using compact flag and 0/1 as booleans" => sub {
    subtest "should convert data to json using yyyymmdd as date format" => sub {
      my $consent = GDPR::IAB::TCFv2->Parse(
        'COyfVVoOyfVVoADACHENAwCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAnSAMABgAFkAgQCDASeAmYBOgAA',
        json => {
          verbose        => 0,
          compact        => 1,
          use_epoch      => 0,
          boolean_values => [0, 1],
          date_format    => '%Y%m%d',    # yyymmdd
        },
      );

      my $got      = $consent->TO_JSON();
      my $expected = _fixture_range_compact(created => 20200427, last_updated => 20200427,);

      is_deeply $got, $expected, "must return the json hashref";

      done_testing;
    };

    subtest "should convert data to json using epoch date format" => sub {
      my $consent = GDPR::IAB::TCFv2->Parse(
        'COyfVVoOyfVVoADACHENAwCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAnSAMABgAFkAgQCDASeAmYBOgAA',
        json => {verbose => 0, compact => 1, use_epoch => 1, boolean_values => [0, 1],},
      );


      my $got      = $consent->TO_JSON();
      my $expected = _fixture_range_compact(created => 1587946020, last_updated => 1587946020,);

      is_deeply $got, $expected, "must return the json hashref";

      done_testing;
    };

    done_testing;
  };

  subtest "should convert data to json using default (non-compact) and 0/1 as booleans" => sub {

    subtest "default non verbose, date as iso 8601" => sub {
      my $consent = GDPR::IAB::TCFv2->Parse(
        'COyfVVoOyfVVoADACHENAwCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAnSAMABgAFkAgQCDASeAmYBOgAA',
        json => {verbose => 0, compact => 0, use_epoch => 0, boolean_values => [0, 1],},
      );

      ok $consent->vendor_consent(23);

      my $got      = $consent->TO_JSON();
      my $expected = _fixture_range_default();

      is_deeply $got, $expected, "must return the json hashref";

      done_testing;
    };

    subtest "default verbose, date as iso 8601" => sub {
      my $consent = GDPR::IAB::TCFv2->Parse(
        'COyfVVoOyfVVoADACHENAwCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAnSAMABgAFkAgQCDASeAmYBOgAA',
        json => {verbose => 1, compact => 0, use_epoch => 0, boolean_values => [0, 1],},
      );

      ok $consent->vendor_consent(23);

      my $got      = $consent->TO_JSON();
      my $expected = _fixture_range_verbose();

      is_deeply $got, $expected, "must return the json hashref";

      done_testing;
    };

    done_testing;
  };

  subtest "publisher section" => sub {
    subtest "publisher section without publisher_tc" => sub {
      my $consent = GDPR::IAB::TCFv2->Parse('COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA',
        json => {verbose => 0, compact => 1, use_epoch => 0, boolean_values => [0, 1],},);

      my $got      = $consent->TO_JSON;
      my $expected = {"publisher" => {"restrictions" => {"7" => {"32" => 1}}}};

      is_deeply $got->{publisher}, $expected->{publisher}, "must return the same publisher restriction section";

      done_testing;
    };
    subtest "publisher section with publisher_tc" => sub {
      subtest "without custom purposes" => sub {
        my $consent = GDPR::IAB::TCFv2->Parse(
          'COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA.argAC0gAAAAAAAAAAAA',
          json => {verbose => 0, compact => 1, use_epoch => 0, boolean_values => [0, 1],},
        );

        my $got      = $consent->TO_JSON;
        my $expected = {
          "publisher" => {
            "consents"             => [2, 4, 6, 8, 9, 10],
            "legitimate_interests" => [2, 4, 5, 7, 10],
            "custom_purposes"      => {"consents" => [], "legitimate_interests" => [],},
            "restrictions"         => {"7"        => {"32" => 1}}
          }
        };

        is_deeply $got->{publisher}, $expected->{publisher}, "must return the same publisher restriction section";

        done_testing;
      };

      subtest "with custom purposes" => sub {
        my $consent = GDPR::IAB::TCFv2->Parse(
          'COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA.YAAAAAAAAXA',
          json => {verbose => 0, compact => 1, use_epoch => 0, boolean_values => [0, 1],},
        );

        my $got      = $consent->TO_JSON;
        my $expected = {
          "publisher" => {
            "consents"             => [],
            "legitimate_interests" => [],
            "custom_purposes"      => {"consents" => [1, 2], "legitimate_interests" => [1],},
            "restrictions"         => {"7"        => {"32" => 1}}
          }

t/03-json-range.t  view on Meta::CPAN


      done_testing;
    };

    done_testing;
  };

  subtest "TO_JSON method should return the same hashref " => sub {
    my $consent = GDPR::IAB::TCFv2->Parse(
      'COyfVVoOyfVVoADACHENAwCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAnSAMABgAFkAgQCDASeAmYBOgAA',
      json => {verbose => 0, compact => 1, use_epoch => 0, boolean_values => [0, 1],},
    );


    my $got1 = $consent->TO_JSON();
    my $got2 = $consent->TO_JSON();

    is_deeply $got1, $got2, "must return the same hashref";

    done_testing;
  };

t/07-golden.t  view on Meta::CPAN

  # Important for memory-tight smokers (e.g. OmniOS/Solaris on threaded perl).
  subtest "String $count" => sub {
    if ($entry->{expect_failure}) {
      throws_ok { GDPR::IAB::TCFv2->Parse($tc_string); }
      qr/\Q$entry->{error_match}\E/, "should fail as expected";
      return;
    }

    my $consent;
    lives_ok {
      $consent = GDPR::IAB::TCFv2->Parse($tc_string, json => {boolean_values => [JSON::PP::false, JSON::PP::true]});
    }
    "parsed successfully";

    return unless $consent;

    my $tests = $entry->{tests};

    is_deeply_with_diag($consent->TO_JSON, $tests->{to_json}, "TO_JSON match");

    is($consent->version,         $tests->{metadata}->{version},       "version match");

t/08-golden-validator.t  view on Meta::CPAN


  # Parse-failure entries don't carry a validator section.
  next if $entry->{expect_failure};

  my $expected = $entry->{tests}{validator};
  next unless $expected;    # forward compat with pre-Phase-2 corpora

  $checked_count++;

  my $consent = eval {
    GDPR::IAB::TCFv2->Parse($entry->{tc_string}, json => {boolean_values => [JSON::PP::false, JSON::PP::true]});
  };
  if (my $err = $@) {
    fail("String $count: parse failed unexpectedly: $err");
    next;
  }

  for my $entry_v (@validators) {
    my $name        = $entry_v->{name};
    my $expected_sc = $expected->{$name};

t/generate_golden.pl  view on Meta::CPAN


open my $ifh, '<', $input_file or die "Could not open $input_file: $!";
my $ofh  = IO::Compress::Gzip->new($output_file, Level => 9) or die "Could not open $output_file: $GzipError";
my $json = JSON::PP->new->canonical->utf8;

while (my $line = <$ifh>) {
  chomp $line;
  next unless $line;

  eval {
    my $consent = GDPR::IAB::TCFv2->Parse($line, json => {boolean_values => [JSON::PP::false, JSON::PP::true]});

    my $data = {
      tc_string      => $line,
      expect_failure => JSON::PP::false,
      tests          => {
        to_json  => $consent->TO_JSON,
        metadata =>
          {version => $consent->version, cmp_id => $consent->cmp_id, created_epoch => scalar($consent->created),},
        sampling => {
          purpose_1_consent  => $consent->is_purpose_consent_allowed(1) ? JSON::PP::true : JSON::PP::false,



( run in 0.952 second using v1.01-cache-2.11-cpan-bbcb1afb8fc )