Deploy Ladill Link / deploy (push) Successful in 1m1s
Proxy transfer paths directly to transfer.ladill.com and stop following ladl.link redirects server-side so recipient links resolve. Co-authored-by: Cursor <cursoragent@cursor.com>
155 lines
4.9 KiB
PHP
155 lines
4.9 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Link;
|
|
|
|
use App\Support\LadillLink;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
/**
|
|
* Proxies ladl.link ecosystem paths to the platform QR resolver (/q/* on ladill.com)
|
|
* or directly to satellite apps (e.g. transfer.ladill.com). The browser stays on
|
|
* ladl.link; resolution happens server-side without redirect loops.
|
|
*/
|
|
class LinkPlatformProxy
|
|
{
|
|
public const INTERNAL_HEADER = 'X-Ladill-Internal';
|
|
|
|
public function forward(Request $request, string $slug, ?string $path = null): Response
|
|
{
|
|
$normalizedPath = $path !== null ? ltrim($path, '/') : '';
|
|
|
|
if ($normalizedPath === 'transfer' || str_starts_with($normalizedPath, 'transfer/')) {
|
|
return $this->fetch(
|
|
$request,
|
|
rtrim($this->transferBaseUrl(), '/').'/q/'.$slug.'/'.$normalizedPath,
|
|
);
|
|
}
|
|
|
|
$platform = rtrim((string) config('app.platform_url', 'https://ladill.com'), '/');
|
|
$target = $platform.'/q/'.$slug;
|
|
if ($normalizedPath !== '') {
|
|
$target .= '/'.$normalizedPath;
|
|
}
|
|
|
|
$response = $this->fetch($request, $target);
|
|
|
|
if ($response->isRedirection()) {
|
|
$location = $response->headers->get('Location');
|
|
if (is_string($location)) {
|
|
if ($followUp = $this->followLadillLinkRedirect($request, $slug, $location)) {
|
|
return $followUp;
|
|
}
|
|
|
|
$response->headers->set('Location', $this->rewriteLocation($location, $slug));
|
|
}
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
private function fetch(Request $request, string $target): Response
|
|
{
|
|
if ($qs = $request->getQueryString()) {
|
|
$target .= (str_contains($target, '?') ? '&' : '?').$qs;
|
|
}
|
|
|
|
$method = strtoupper($request->method());
|
|
$client = Http::withOptions(['allow_redirects' => false])
|
|
->timeout(15)
|
|
->withHeaders($this->forwardHeaders($request));
|
|
|
|
$pending = match ($method) {
|
|
'POST' => $client->asForm()->post($target, $request->post()),
|
|
'PATCH' => $client->patch($target, $request->all()),
|
|
'DELETE' => $client->delete($target),
|
|
default => $client->get($target),
|
|
};
|
|
|
|
$upstream = $pending->toPsrResponse();
|
|
|
|
return new Response(
|
|
(string) $upstream->getBody(),
|
|
$upstream->getStatusCode(),
|
|
$this->sanitizeHeaders($upstream->getHeaders()),
|
|
);
|
|
}
|
|
|
|
private function followLadillLinkRedirect(Request $request, string $slug, string $location): ?Response
|
|
{
|
|
$parsed = parse_url($location);
|
|
if (! is_array($parsed)) {
|
|
return null;
|
|
}
|
|
|
|
$host = strtolower((string) ($parsed['host'] ?? ''));
|
|
$linkHost = strtolower((string) parse_url(LadillLink::baseUrl(), PHP_URL_HOST));
|
|
if ($host !== $linkHost) {
|
|
return null;
|
|
}
|
|
|
|
$path = ltrim((string) ($parsed['path'] ?? ''), '/');
|
|
$slugPrefix = $slug.'/';
|
|
if (! str_starts_with($path, $slugPrefix)) {
|
|
return null;
|
|
}
|
|
|
|
return $this->forward($request, $slug, substr($path, strlen($slugPrefix)));
|
|
}
|
|
|
|
private function transferBaseUrl(): string
|
|
{
|
|
$domain = (string) config('app.transfer_domain', 'transfer.ladill.com');
|
|
|
|
return 'https://'.$domain;
|
|
}
|
|
|
|
/** @return array<string, string> */
|
|
private function forwardHeaders(Request $request): array
|
|
{
|
|
return array_filter([
|
|
'Accept' => $request->header('Accept'),
|
|
'Accept-Language' => $request->header('Accept-Language'),
|
|
'User-Agent' => $request->header('User-Agent'),
|
|
'Referer' => $request->header('Referer'),
|
|
self::INTERNAL_HEADER => '1',
|
|
]);
|
|
}
|
|
|
|
/** @param array<string, array<int, string>> $headers */
|
|
private function sanitizeHeaders(array $headers): array
|
|
{
|
|
$flat = [];
|
|
foreach ($headers as $name => $values) {
|
|
if (strtolower($name) === 'transfer-encoding') {
|
|
continue;
|
|
}
|
|
$flat[$name] = $values[0] ?? '';
|
|
}
|
|
|
|
return $flat;
|
|
}
|
|
|
|
private function rewriteLocation(string $location, string $slug): string
|
|
{
|
|
$platform = rtrim((string) config('app.platform_url', 'https://ladill.com'), '/');
|
|
$prefix = $platform.'/q/'.$slug;
|
|
|
|
if (str_starts_with($location, $prefix)) {
|
|
$suffix = substr($location, strlen($prefix));
|
|
|
|
return LadillLink::url($slug).$suffix;
|
|
}
|
|
|
|
$transfer = rtrim($this->transferBaseUrl(), '/').'/q/'.$slug;
|
|
if (str_starts_with($location, $transfer)) {
|
|
$suffix = substr($location, strlen($transfer));
|
|
|
|
return LadillLink::url($slug).$suffix;
|
|
}
|
|
|
|
return $location;
|
|
}
|
|
}
|