Skip to content

PHP: getEtldPlusOne() resolves apex hosts under multi-label eTLDs to the public suffix, so _fbp/_fbc are never stored #40

Description

@virajsoni06

Summary

For an apex host under a multi-label eTLD — example.co.uk, example.com.au, example.co.jpParamBuilder resolves the eTLD+1 to the public suffix itself (co.uk) and emits _fbp / _fbc with domain=co.uk. Browsers reject cookies scoped to a public suffix, so the cookies are never stored: every request mints a new browser id and CAPI receives no stable _fbp.

Single-label eTLDs (example.com) are unaffected, and the same host with a www. prefix is resolved correctly, which is probably why this has gone unnoticed.

Reproduction

<?php
require 'vendor/autoload.php';

foreach (['example.co.uk', 'www.example.co.uk', 'example.com'] as $host) {
    $pb = new FacebookAds\ParamBuilder(array('https://' . $host));
    $pb->processRequest($host, array(), array(), null);
    foreach ($pb->getCookiesToSet() as $c) {
        printf("host=%-22s %s domain=%s value=%s\n", $host, $c->name, $c->domain, $c->value);
    }
}

Output with facebook/capi-param-builder-php 1.3.1 (and current main):

host=example.co.uk         _fbp domain=co.uk                value=fb.1.…
host=www.example.co.uk     _fbp domain=example.co.uk        value=fb.2.…
host=example.com           _fbp domain=example.com          value=fb.1.…

Expected for the first row: domain=example.co.uk.

Cause

In getEtldPlusOne(), the domain-list branch confirms the candidate is a suffix of the host and then requires the preceding character to be a dot:

if ($host[$lastOccurrence - 1] === '.') {
    return $domain_candidate;
}

When the candidate is the host, $lastOccurrence is 0, so this reads $host[-1] — which since PHP 7.1 is the last character of the string, not a boundary check. It is 'k' for example.co.uk, the candidate is discarded, and control falls through to:

$slice = explode(".", $host);
if (count($slice) > 2) {
    return substr($host, strpos($host, '.') + 1);
}

which strips the first label and yields co.uk. For a two-label host the fallback returns the host, which is why .com sites look fine.

Suggested fix

Accept the exact match in the domain-list branch:

if ($lastOccurrence === 0 || $host[$lastOccurrence - 1] === '.') {
    return $domain_candidate;
}

That makes the configured domain list authoritative, as intended. The label-stripping fallback is still wrong for multi-label suffixes when no list is supplied, so it may be worth refusing to return a candidate that consists solely of a known public suffix.

Impact

facebook-for-woocommerce (3.7.6, latest) constructs the builder as new \FacebookAds\ParamBuilder( array( get_site_url() ) ) and passes $cookie->domain straight to setcookie(), so every WooCommerce store on a .co.uk-style apex domain is currently unable to persist _fbp or _fbc. It also hands the same domain to the browser in the consent-release path. Verified on a live store: set-cookie: _fbp=…; path=/; domain=co.uk.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions