diff --git a/app/Http/Middleware/BotDetector.php b/app/Http/Middleware/BotDetector.php index 2a2d9b3..649dfe3 100644 --- a/app/Http/Middleware/BotDetector.php +++ b/app/Http/Middleware/BotDetector.php @@ -61,20 +61,37 @@ class BotDetector 'postman', 'insomnia', 'httpie', ]; - // Bu path'ler için sadece log tut, engelleme yapma + // Bu path'ler için sadece log tut, engelleme ve genel rate limit yapma private const SKIP_PATHS = [ - '/up', '/api/', '/sitemap', + '/up', + '/api/', + '/sitemap', + '/stream/', + '/vtt-proxy', + '/track/', + '/calls/', + '/episode/', + '/comments', + '/media/', + '/assets/', + '/build/', + '/vendor/', ]; public function handle(Request $request, Closure $next) { $ip = $request->ip(); $ua = strtolower($request->userAgent() ?? ''); - $path = $request->path(); + $path = '/' . ltrim($request->path(), '/'); + + // index.php önekini normalize et (/index.php/stream/seg -> /stream/seg) + if (str_starts_with($path, '/index.php/')) { + $path = '/' . substr($path, 11); + } // Skip paths foreach (self::SKIP_PATHS as $skip) { - if (str_starts_with('/' . $path, $skip)) { + if (str_starts_with($path, $skip)) { return $next($request); } } @@ -86,9 +103,9 @@ class BotDetector } } - // Manuel engelli IP kontrolü + // Manuel engelli IP kontrolü (Sadece yöneticinin manuel engellediği IP'ler) if ($this->isBlockedIp($ip)) { - $this->logBot($ip, $request->userAgent(), '/' . $path, $request->method(), 'ip_blocked', 'blocked_ip'); + $this->logBot($ip, $request->userAgent(), $path, $request->method(), 'ip_blocked', 'blocked_ip'); return response('Erişim engellendi.', 403); } @@ -96,14 +113,14 @@ class BotDetector if (empty($ua)) { $request->attributes->set('is_bot', true); $request->attributes->set('bot_type', 'noua'); - $this->logBot($ip, '', '/' . $path, $request->method(), 'allowed', 'no_ua'); + $this->logBot($ip, '', $path, $request->method(), 'allowed', 'no_ua'); return $next($request); } // Kötü bot mu? foreach (self::BAD_BOTS as $pattern) { if (str_contains($ua, $pattern)) { - $this->logBot($ip, $request->userAgent(), '/' . $path, $request->method(), 'blocked', $pattern); + $this->logBot($ip, $request->userAgent(), $path, $request->method(), 'blocked', $pattern); return response('', 403); } } @@ -113,8 +130,8 @@ class BotDetector if (str_contains($ua, $pattern)) { $request->attributes->set('is_bot', true); $request->attributes->set('bot_type', 'good'); - // İyi botlar için çok agresif rate limit (dakikada 60) - if ($this->isRateLimited($ip, 60, 'good_bot')) { + // İyi botlar için rate limit (dakikada 120) + if ($this->isRateLimited($ip, 120, 'good_bot')) { return response('', 429); } return $next($request); @@ -126,28 +143,24 @@ class BotDetector if (str_contains($ua, $pattern)) { $request->attributes->set('is_bot', true); $request->attributes->set('bot_type', 'generic'); - if ($this->isRateLimited($ip, 10, 'generic')) { - $this->logBot($ip, $request->userAgent(), '/' . $path, $request->method(), 'rate_limited', $pattern); - // 30+ istek → otomatik engelle + if ($this->isRateLimited($ip, 20, 'generic')) { + $this->logBot($ip, $request->userAgent(), $path, $request->method(), 'rate_limited', $pattern); + // 50+ istek → otomatik engelle $count = Cache::get("bot_count_{$ip}", 0); - if ($count > 30) { - $this->autoBlock($ip, 'Otomatik: dakikada 30+ generic bot isteği'); + if ($count > 50) { + $this->autoBlock($ip, 'Otomatik: dakikada 50+ generic bot isteği'); } return response('', 429); } - $this->logBot($ip, $request->userAgent(), '/' . $path, $request->method(), 'allowed', $pattern); + $this->logBot($ip, $request->userAgent(), $path, $request->method(), 'allowed', $pattern); return $next($request); } } - // Normal kullanıcı — genel rate limit (dakikada 120 istek) - if ($this->isRateLimited($ip, 120, 'human')) { - $this->logBot($ip, $request->userAgent(), '/' . $path, $request->method(), 'rate_limited', 'human_flood'); - $count = Cache::get("bot_count_{$ip}", 0); - if ($count > 200) { - $this->autoBlock($ip, 'Otomatik: dakikada 200+ istek flood'); - } - return response('', 429); + // Normal kullanıcı — genel sayfa istek limiti (dakikada 300 sayfa isteği) + if ($this->isRateLimited($ip, 300, 'human')) { + $this->logBot($ip, $request->userAgent(), $path, $request->method(), 'rate_limited', 'human_flood'); + return response('Çok fazla istek gönderdiniz. Lütfen biraz bekleyin.', 429); } $request->attributes->set('is_bot', false); @@ -156,10 +169,11 @@ class BotDetector private function isBlockedIp(string $ip): bool { - return Cache::remember("blocked_ip_{$ip}", 300, function () use ($ip) { + return Cache::remember("blocked_ip_{$ip}", 30, function () use ($ip) { try { return DB::table('blocked_ips') ->where('ip', $ip) + ->where('auto_blocked', 0) ->where(function ($q) { $q->whereNull('expires_at')->orWhere('expires_at', '>', now()); }) @@ -216,3 +230,4 @@ class BotDetector } catch (\Exception) {} } } + diff --git a/routes/web.php b/routes/web.php index 6c61c41..0c48854 100644 --- a/routes/web.php +++ b/routes/web.php @@ -510,6 +510,52 @@ Route::get("/stream/proxy", function (\Illuminate\Http\Request $request) { $customRef = base64_encode($referer); } $origin = rtrim($referer, '/'); + $path = strtolower(parse_url($url, PHP_URL_PATH) ?? ""); + $UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"; + + // Doğrudan MP4 / video dosyası istendiyse belleğe yüklemeden stream et (PHP memory_limit çökmesini önler) + $isDirectMedia = str_ends_with($path, ".mp4") + || str_ends_with($path, ".m4v") + || str_ends_with($path, ".webm") + || str_ends_with($path, ".mkv"); + + if ($isDirectMedia) { + $streamHeaders = [ + "Referer: {$referer}", + "User-Agent: {$UA}", + "Accept: */*", + ]; + if ($range = $request->header('Range')) { + $streamHeaders[] = "Range: {$range}"; + } + + return response()->stream(function () use ($url, $streamHeaders) { + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_MAXREDIRS => 5, + CURLOPT_TIMEOUT => 300, + CURLOPT_CONNECTTIMEOUT => 10, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_HTTPHEADER => $streamHeaders, + CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { + echo $chunk; + if (ob_get_level() > 0) { + ob_flush(); + } + flush(); + return strlen($chunk); + }, + ]); + curl_exec($ch); + curl_close($ch); + }, 200, [ + "Content-Type" => "video/mp4", + "Access-Control-Allow-Origin" => "*", + "Cache-Control" => "public, max-age=86400", + ]); + } // Tek istek yürüten yardımcı — farklı header setleriyle tekrar denemek için. // Redirect'ler elle takip edilir ki her adım SSRF kontrolünden geçsin. @@ -546,8 +592,6 @@ Route::get("/stream/proxy", function (\Illuminate\Http\Request $request) { } }; - $UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"; - // 1) Referer-only (gerçek oynatıcı davranışı — Origin göndermez, hotlink koruması daha toleranslı) [$content, $httpCode, $contentType, $curlErrStr] = $fetchUpstream($url, [ "Referer: {$referer}", @@ -578,7 +622,6 @@ Route::get("/stream/proxy", function (\Illuminate\Http\Request $request) { abort(502); } - $path = strtolower(parse_url($url, PHP_URL_PATH) ?? ""); $isM3u8 = str_ends_with($path, ".m3u8") || str_contains($contentType, "mpegurl") || str_starts_with(trim((string)$content), "#EXTM3U");