facebook / facebook/capi-param-builder
PHP: getEtldPlusOne() resolves apex hosts under multi-label eTLDs to the public suffix, so _fbp/_fbc are never stored
- Dominant language
- JavaScript
- Stars
- 33
- Forks
- 9
- PR merge metrics
- No merged PRs in 30d
Description
### Summary
For an apex host under a multi-label eTLD — `example.co.uk`, `example.com.au`, `example.co.jp` — `ParamBuilder` 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
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:
```php
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:
```php
$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:
```php
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`.
Contributor guide
Research direction
Start at ParamBuilder::getEtldPlusOne() and run the supplied reproduction with example.co.uk, www.example.co.uk, and example.com to trace the domain-list branch. Done means apex hosts under multi-label eTLDs return the full host as the cookie domain, while the www and single-label cases retain their expected domains.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 72/100