49 lines
1.2 KiB
PHP
49 lines
1.2 KiB
PHP
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
use App\Models\PageVisit;
|
|
use Illuminate\Http\Request;
|
|
|
|
/**
|
|
* Visitor IP behind Cloudflare.
|
|
*
|
|
* Prefer CF-Connecting-IP (Cloudflare overwrites it). Fall back to
|
|
* $request->ip() when the request did not come through CDN.
|
|
* Do not trust JSON/query `ip` or a client-prefixed X-Forwarded-For hop.
|
|
*/
|
|
final class VisitorIp
|
|
{
|
|
public static function fromRequest(Request $request): string
|
|
{
|
|
foreach (['CF-Connecting-IP', 'True-Client-IP'] as $header) {
|
|
$ip = self::normalize($request->headers->get($header));
|
|
if ($ip !== '') {
|
|
return $ip;
|
|
}
|
|
}
|
|
|
|
$fallback = self::normalize((string) $request->ip());
|
|
|
|
return $fallback !== '' ? $fallback : substr((string) $request->ip(), 0, 64);
|
|
}
|
|
|
|
public static function normalize(?string $raw): string
|
|
{
|
|
$raw = trim((string) $raw);
|
|
if ($raw === '') {
|
|
return '';
|
|
}
|
|
if (str_contains($raw, ',')) {
|
|
$raw = trim(explode(',', $raw, 2)[0]);
|
|
}
|
|
if (filter_var($raw, FILTER_VALIDATE_IP) === false) {
|
|
return '';
|
|
}
|
|
|
|
$norm = PageVisit::normalizeIp($raw);
|
|
|
|
return $norm !== '' ? substr($norm, 0, 64) : '';
|
|
}
|
|
}
|