implementado fastroute para controle e segurança de rotas

This commit is contained in:
Renan Bernordi 2025-01-24 00:19:52 -03:00
parent 4cd475c8ef
commit bc06a7cbc9
12 changed files with 570 additions and 539 deletions

View file

@ -1,133 +0,0 @@
<?php
/**
* URL Analysis API
* API para análise de URLs
*
* This file implements a REST endpoint that receives URLs via GET
* and returns processed results in JSON format.
*
* Este arquivo implementa um endpoint REST que recebe URLs via GET
* e retorna resultados processados em formato JSON.
*
* Features / Funcionalidades:
* - URL validation / Validação de URLs
* - Content analysis / Análise de conteúdo
* - Error handling / Tratamento de erros
* - CORS support / Suporte a CORS
*/
require_once 'config.php';
require_once 'inc/URLAnalyzer.php';
require_once 'inc/Language.php';
// Initialize language system with default language
// Inicializa o sistema de idiomas com o idioma padrão
Language::init(LANGUAGE);
// Set content type as JSON
// Define o tipo de conteúdo como JSON
header('Content-Type: application/json');
// Enable CORS (Cross-Origin Resource Sharing)
// Habilita CORS (Cross-Origin Resource Sharing)
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET');
// Get request URL from path
// Obtém a URL da requisição a partir do path
$path = $_SERVER['REQUEST_URI'];
$prefix = '/api/';
if (strpos($path, $prefix) === 0) {
$url = urldecode(substr($path, strlen($prefix)));
/**
* Function to send standardized JSON response
* Função para enviar resposta JSON padronizada
*
* @param array $data Data to be sent in response / Dados a serem enviados na resposta
* @param int $statusCode HTTP status code / Código de status HTTP
*/
function sendResponse($data, $statusCode = 200)
{
http_response_code($statusCode);
$response = [
'status' => $statusCode
];
if (isset($data['error'])) {
$response['error'] = $data['error'];
} else if (isset($data['url'])) {
$response['url'] = $data['url'];
}
echo json_encode($response);
exit;
}
// Basic URL validation
// Validação básica da URL
if (!$url || !filter_var($url, FILTER_VALIDATE_URL)) {
sendResponse([
'error' => [
'type' => URLAnalyzer::ERROR_INVALID_URL,
'message' => Language::getMessage('INVALID_URL')['message']
]
], 400);
}
try {
// Instantiate URL analyzer
// Instancia o analisador de URLs
$analyzer = new URLAnalyzer();
// Try to analyze the provided URL
// Tenta analisar a URL fornecida
$analyzer->analyze($url);
// If analysis is successful, return the processed URL
// Se a análise for bem-sucedida, retorna a URL processada
sendResponse([
'url' => SITE_URL . '/p/' . $url
], 200);
} catch (URLAnalyzerException $e) {
// Get error details from the exception
// Obtém detalhes do erro da exceção
$errorType = $e->getErrorType();
$additionalInfo = $e->getAdditionalInfo();
// Add error header for better client-side handling
// Adiciona header de erro para melhor tratamento no cliente
header('X-Error-Type: ' . $errorType);
if ($additionalInfo) {
header('X-Error-Info: ' . $additionalInfo);
}
sendResponse([
'error' => [
'type' => $errorType,
'message' => $e->getMessage(),
'details' => $additionalInfo ?: null
]
], $e->getCode());
} catch (Exception $e) {
// Handle any other unexpected errors
// Trata quaisquer outros erros inesperados
sendResponse([
'error' => [
'type' => URLAnalyzer::ERROR_GENERIC_ERROR,
'message' => Language::getMessage('GENERIC_ERROR')['message']
]
], 500);
}
} else {
// Return 404 error for endpoints not found
// Retorna erro 404 para endpoints não encontrados
sendResponse([
'error' => [
'type' => URLAnalyzer::ERROR_NOT_FOUND,
'message' => Language::getMessage('NOT_FOUND')['message']
]
], 404);
}

View file

@ -4,11 +4,13 @@
"aws/aws-sdk-php": "^3.0",
"php-curl-class/php-curl-class": "^11.0",
"php-webdriver/webdriver": "^1.15",
"monolog/monolog": "^3.8.1"
"monolog/monolog": "^3.8.1",
"nikic/fast-route": "^1.3"
},
"autoload": {
"psr-4": {
"Inc\\": "inc/"
"Inc\\": "inc/",
"App\\": "src/"
}
}
}

View file

@ -16,10 +16,10 @@
* - Selenium extraction support when enabled by domain / Suporte a extração via Selenium quando habilitado por domínio
*/
require_once 'Rules.php';
require_once 'Cache.php';
require_once 'Logger.php';
require_once 'Language.php';
require_once __DIR__ . '/Rules.php';
require_once __DIR__ . '/Cache.php';
require_once __DIR__ . '/Logger.php';
require_once __DIR__ . '/Language.php';
use Curl\Curl;
use Facebook\WebDriver\Remote\DesiredCapabilities;
@ -253,14 +253,14 @@ class URLAnalyzer
$host = preg_replace('/^www\./', '', $host);
if (in_array($host, BLOCKED_DOMAINS)) {
Logger::getInstance()->log($cleanUrl, 'BLOCKED_DOMAIN');
Logger::getInstance()->logUrl($cleanUrl, 'BLOCKED_DOMAIN');
$this->throwError(self::ERROR_BLOCKED_DOMAIN);
}
// Check URL status code before proceeding
$redirectInfo = $this->checkStatus($cleanUrl);
if ($redirectInfo['httpCode'] !== 200) {
Logger::getInstance()->log($cleanUrl, 'INVALID_STATUS_CODE', "HTTP {$redirectInfo['httpCode']}");
Logger::getInstance()->logUrl($cleanUrl, 'INVALID_STATUS_CODE', "HTTP {$redirectInfo['httpCode']}");
if ($redirectInfo['httpCode'] === 404) {
$this->throwError(self::ERROR_NOT_FOUND);
} else {
@ -296,7 +296,7 @@ class URLAnalyzer
return $processedContent;
}
} catch (Exception $e) {
Logger::getInstance()->log($cleanUrl, strtoupper($fetchStrategy) . '_ERROR', $e->getMessage());
Logger::getInstance()->logUrl($cleanUrl, strtoupper($fetchStrategy) . '_ERROR', $e->getMessage());
throw $e;
}
}
@ -326,7 +326,7 @@ class URLAnalyzer
}
// If we get here, all strategies failed
Logger::getInstance()->log($cleanUrl, 'GENERAL_FETCH_ERROR');
Logger::getInstance()->logUrl($cleanUrl, 'GENERAL_FETCH_ERROR');
if ($lastError) {
$message = $lastError->getMessage();
if (strpos($message, 'DNS') !== false) {

View file

@ -1,214 +1,17 @@
<?php
/**
* Página principal do sistema / Main system page
* Arquivo de entrada da aplicação
* Application entry point
*
* Este arquivo implementa a interface web principal, incluindo:
* - Formulário para análise de URLs
* - Tratamento de mensagens de erro/sucesso
* - Documentação da API
* - Interface para bookmarklet
* - Links para serviços alternativos
*
* This file implements the main web interface, including:
* - URL analysis form
* - Error/success message handling
* - API documentation
* - Bookmarklet interface
* - Links to alternative services
* Este arquivo inicializa o sistema de roteamento e despacha as requisições
* para os manipuladores apropriados usando FastRoute.
*
* This file initializes the routing system and dispatches requests
* to appropriate handlers using FastRoute.
*/
require_once 'config.php';
require_once 'inc/Cache.php';
require_once 'inc/Language.php';
require_once __DIR__ . '/vendor/autoload.php';
// Inicialização das traduções / Initialize translations
Language::init(LANGUAGE);
// Inicialização de variáveis / Variable initialization
$message = '';
$message_type = '';
$url = '';
// Processa mensagens de erro/alerta da query string / Process error/warning messages from query string
if (isset($_GET['message'])) {
$message_key = $_GET['message'];
$messageData = Language::getMessage($message_key);
$message = $messageData['message'];
$message_type = $messageData['type'];
}
// Processa submissão do formulário / Process form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['url'])) {
$url = filter_var($_POST['url'], FILTER_SANITIZE_URL);
if (filter_var($url, FILTER_VALIDATE_URL)) {
header('Location: ' . SITE_URL . '/p/' . urlencode($url));
exit;
} else {
$messageData = Language::getMessage('INVALID_URL');
$message = $messageData['message'];
$message_type = $messageData['type'];
}
}
// Inicializa o cache para contagem / Initialize cache for counting
$cache = new Cache();
$cache_folder = $cache->getCacheFileCount();
?>
<!DOCTYPE html>
<html lang="<?php echo Language::getCurrentLanguage(); ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title><?php echo SITE_NAME; ?></title>
<link rel="icon" href="<?php echo SITE_URL; ?>/assets/svg/marreta.svg" type="image/svg+xml">
<meta name="theme-color" content="#2563eb">
<link rel="manifest" href="<?php echo SITE_URL; ?>/manifest.json">
<!-- PWA meta tags -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="application-name" content="<?php echo SITE_NAME; ?>">
<!-- Open Graph meta tags -->
<meta property="og:type" content="website" />
<meta property="og:url" content="<?php echo SITE_URL; ?>" />
<meta property="og:title" content="<?php echo SITE_NAME; ?>" />
<meta property="og:description" content="<?php echo htmlspecialchars(SITE_DESCRIPTION); ?>" />
<meta property="og:image" content="<?php echo SITE_URL; ?>/assets/opengraph.png" />
<script src="https://cdn.tailwindcss.com/3.4.15"></script>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8 max-w-4xl">
<!-- Cabeçalho da página / Page header -->
<div class="text-center mb-8">
<h1 class="text-4xl font-bold text-gray-800 mb-4">
<img src="assets/svg/marreta.svg" class="inline-block w-12 h-12 mb-2" alt="Marreta">
<?php echo SITE_NAME; ?>
</h1>
<p class="text-gray-600 text-lg"><?php echo SITE_DESCRIPTION; ?></p>
<p class="text-gray-600 text-lg">
<span class="font-bold text-blue-600">
<?php echo number_format($cache_folder, 0, ',', '.'); ?>
</span>
<span><?php echo Language::get('walls_destroyed'); ?></span>
</p>
</div>
<!-- Formulário principal de análise de URLs / Main URL analysis form -->
<div class="bg-white rounded-xl shadow-lg p-8 mb-8">
<form id="urlForm" method="POST" onsubmit="return validateForm()" class="space-y-6">
<div class="relative">
<div class="flex items-stretch">
<span class="inline-flex items-center px-5 rounded-l-lg border border-r-0 border-gray-300 bg-gray-50 text-gray-500">
<img src="assets/svg/link.svg" class="w-6 h-6" alt="Link">
</span>
<input type="url"
name="url"
id="url"
class="flex-1 block w-full rounded-none rounded-r-lg text-lg py-4 border border-l-0 border-gray-300 bg-gray-50 focus:border-blue-500 focus:ring-blue-500 shadow-sm bg-gray-50"
placeholder="<?php echo Language::get('url_placeholder'); ?>"
value="<?php echo htmlspecialchars($url); ?>"
required
pattern="https?://.+"
title="<?php echo Language::getMessage('INVALID_URL')['message']; ?>">
</div>
<button type="submit"
class="mt-4 w-full inline-flex justify-center items-center px-6 py-4 border border-transparent text-lg font-medium rounded-lg text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200">
<img src="assets/svg/search.svg" class="w-6 h-6 mr-3" alt="Search">
<?php echo Language::get('analyze_button'); ?>
</button>
</div>
</form>
<!-- Aviso sobre bloqueadores de anúncios / Ad blocker warning -->
<div class="mt-4 text-sm text-gray-600 flex items-start">
<p><?php echo str_replace('{site_name}', SITE_NAME, Language::get('adblocker_warning')); ?></p>
</div>
<!-- Área de mensagens de erro/alerta / Error/warning message area -->
<?php if ($message): ?>
<div class="mt-6 <?php echo $message_type === 'error' ? 'bg-red-50 border-red-400' : 'bg-yellow-50 border-yellow-400'; ?> border-l-4 p-4 rounded-r">
<div class="flex">
<div class="flex-shrink-0">
<?php if ($message_type === 'error'): ?>
<img src="assets/svg/error.svg" class="w-6 h-6" alt="Error">
<?php else: ?>
<img src="assets/svg/warning.svg" class="w-6 h-6" alt="Warning">
<?php endif; ?>
</div>
<div class="ml-3">
<p class="text-base <?php echo $message_type === 'error' ? 'text-red-700' : 'text-yellow-700'; ?>">
<?php echo htmlspecialchars($message); ?>
</p>
</div>
</div>
</div>
<?php endif; ?>
</div>
<!-- Adicionar como aplicativo / Add as app (mobile only) -->
<div class="bg-white rounded-xl shadow-lg p-8 mt-8 mb-8 md:hidden">
<h2 class="text-xl font-semibold text-gray-800 mb-6 flex items-center">
<img src="assets/svg/marreta.svg" class="w-6 h-6 mr-3" alt="App">
<?php echo Language::get('add_as_app'); ?>
</h2>
<div class="space-y-4">
<p class="text-gray-600">
<?php echo str_replace('{site_name}', SITE_NAME, Language::get('add_as_app_description')); ?>
</p>
<div class="bg-gray-50 rounded-lg p-4">
<ol class="list-decimal list-inside space-y-2 text-gray-700">
<li><?php echo Language::get('add_as_app_step1'); ?></li>
<li><?php echo Language::get('add_as_app_step2'); ?></li>
<li><?php echo Language::get('add_as_app_step3'); ?></li>
<li><?php echo str_replace('{site_name}', SITE_NAME, Language::get('add_as_app_step4')); ?></li>
</ol>
</div>
</div>
</div>
<!-- Seção de Bookmarklet / Bookmarklet section (desktop only) -->
<div class="bg-white rounded-xl shadow-lg p-8 mt-8 mb-8 hidden md:block">
<h2 class="text-xl font-semibold text-gray-800 mb-6 flex items-center">
<img src="assets/svg/bookmark.svg" class="w-6 h-6 mr-3" alt="Favoritos">
<?php echo Language::get('bookmarklet_title'); ?>
</h2>
<div class="space-y-4">
<p class="text-gray-600">
<?php echo str_replace('{site_name}', SITE_NAME, Language::get('bookmarklet_description')); ?>
</p>
<div class="flex justify-center">
<a href="javascript:(function(){let currentUrl=window.location.href;window.location.href='<?php echo SITE_URL; ?>/p/'+encodeURIComponent(currentUrl);})()"
class="inline-flex items-center px-6 py-3 border-2 border-blue-500 font-medium rounded-lg text-blue-600 bg-white hover:bg-blue-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200 cursor-move"
onclick="return false;">
<img src="assets/svg/marreta.svg" class="w-5 h-5 mr-2" alt="Marreta">
<?php echo str_replace('{site_name}', SITE_NAME, Language::get('open_in')); ?>
</a>
</div>
</div>
</div>
<div class="text-gray-600 text-center text-sm">
<p>
<?php echo Language::get('open_source_description'); ?>
</p>
<p class="mt-2">
<a href="https://github.com/manualdousuario/marreta/wiki/API-Rest" target="_blank" class="underline">API Rest</a> <a href="https://github.com/manualdousuario/marreta/" target="_blank" class="underline">Github</a>
</p>
</div>
</div>
<!-- Scripts JavaScript -->
<script>
<?php
$js_file = 'assets/js/scripts.js';
if (file_exists($js_file)) {
echo file_get_contents($js_file);
}
?>
</script>
</body>
</html>
$router = new App\Router();
$router->dispatch();

View file

@ -1,95 +0,0 @@
<?php
/**
* URL Processor
* Processador de URLs
*
* This file is responsible for:
* - Receiving URLs through the /p/ path
* - Validating URL format
* - Processing content using URLAnalyzer
* - Displaying processed content or redirecting in case of error
*
* Este arquivo é responsável por:
* - Receber URLs através do path /p/
* - Validar o formato da URL
* - Processar o conteúdo usando o URLAnalyzer
* - Exibir o conteúdo processado ou redirecionar em caso de erro
*
* Usage example / Exemplo de uso:
* /p/https://exemplo.com
*/
require_once 'config.php';
require_once 'inc/URLAnalyzer.php';
// Extract URL from request path
// Extrai a URL do path da requisição
$path = $_SERVER['REQUEST_URI'];
$prefix = '/p/';
if (strpos($path, $prefix) === 0) {
// Remove prefix and decode URL
// Remove o prefixo e decodifica a URL
$url = urldecode(substr($path, strlen($prefix)));
// Validate URL format
// Valida o formato da URL
if (filter_var($url, FILTER_VALIDATE_URL)) {
$analyzer = new URLAnalyzer();
try {
// Check for redirects
// Verifica se há redirecionamentos
$redirectInfo = $analyzer->checkStatus($url);
// If there's a redirect and the final URL is different
// Se houver redirecionamento e a URL final for diferente
if ($redirectInfo['hasRedirect'] && $redirectInfo['finalUrl'] !== $url) {
// Redirect to final URL
// Redireciona para a URL final
header('Location: ' . SITE_URL . '/p/' . urlencode($redirectInfo['finalUrl']));
exit;
}
// If there's no redirect or if already at final URL
// Se não houver redirecionamento ou se já estiver na URL final
// Try to analyze and process the URL
// Tenta analisar e processar a URL
$content = $analyzer->analyze($url);
// Display processed content
// Exibe o conteúdo processado
echo $content;
exit;
} catch (URLAnalyzerException $e) {
// Get error type and additional info from exception
// Obtém o tipo de erro e informações adicionais da exceção
$errorType = $e->getErrorType();
$additionalInfo = $e->getAdditionalInfo();
// Handle blocked domain with redirect URL
// Trata domínio bloqueado com URL de redirecionamento
if ($errorType === URLAnalyzer::ERROR_BLOCKED_DOMAIN && $additionalInfo) {
header('Location: ' . trim($additionalInfo) . '?message=' . $errorType);
exit;
}
// Redirect to home page with error message
// Redireciona para a página inicial com mensagem de erro
header('Location: /?message=' . $errorType);
exit;
} catch (Exception $e) {
// Handle any other unexpected errors
// Trata quaisquer outros erros inesperados
header('Location: /?message=' . URLAnalyzer::ERROR_GENERIC_ERROR);
exit;
}
} else {
// Invalid URL / URL inválida
header('Location: /?message=' . URLAnalyzer::ERROR_INVALID_URL);
exit;
}
} else {
// Invalid path / Path inválido
header('Location: /?message=' . URLAnalyzer::ERROR_NOT_FOUND);
exit;
}

View file

@ -1,81 +0,0 @@
<?php
/**
* PWA Share Target Handler
*
* This script handles the PWA (Progressive Web App) share target functionality.
* It receives a URL parameter via GET request and performs a 301 permanent
* redirect to the /p/{URL} endpoint. If no URL is provided, redirects to
* the homepage.
*
* Security measures:
* - URL sanitization to prevent XSS attacks
* - URL encoding to ensure proper parameter handling
*
* Este script gerencia a funcionalidade de compartilhamento do PWA (Progressive Web App).
* Ele recebe um parâmetro URL via requisição GET e realiza um redirecionamento
* permanente 301 para o endpoint /p/{URL}. Se nenhuma URL for fornecida,
* redireciona para a página inicial.
*
* Medidas de segurança:
* - Sanitização da URL para prevenir ataques XSS
* - Codificação da URL para garantir o correto tratamento dos parâmetros
*/
require_once 'config.php';
// Get URL and text parameters from GET request
// Obtém os parâmetros URL e text da requisição GET
$url = $_GET['url'] ?? '';
$text = $_GET['text'] ?? '';
/**
* Validates if a given URL is valid
* Valida se uma URL fornecida é válida
*
* @param string $url URL to validate / URL para validar
* @return bool Returns true if URL is valid, false otherwise / Retorna true se a URL for válida, false caso contrário
*/
function isValidUrl($url) {
// First sanitize the URL
// Primeiro sanitiza a URL
$sanitized_url = filter_var($url, FILTER_SANITIZE_URL);
// Then validate it
// Então valida
return filter_var($sanitized_url, FILTER_VALIDATE_URL) !== false;
}
// Check URL parameter first
// Verifica primeiro o parâmetro URL
if (!empty($url) && isValidUrl($url)) {
$redirect_url = $url;
}
// If URL is not valid, check text parameter
// Se a URL não é válida, verifica o parâmetro text
elseif (!empty($text) && isValidUrl($text)) {
$redirect_url = $text;
}
// If text is not a URL but contains content, try to extract URL from it
// Se o texto não é uma URL mas contém conteúdo, tenta extrair URL dele
elseif (!empty($text)) {
if (preg_match('/https?:\/\/[^\s]+/', $text, $matches)) {
$redirect_url = $matches[0];
}
}
// If we have a valid URL, redirect to it
// Se temos uma URL válida, redireciona para ela
if (isset($redirect_url)) {
// Sanitize URL to prevent XSS
// Sanitiza a URL para prevenir XSS
$redirect_url = htmlspecialchars($redirect_url, ENT_QUOTES, 'UTF-8');
header('HTTP/1.1 301 Moved Permanently');
header('Location: /p/' . urlencode($redirect_url));
exit;
}
// If no valid URL found in either parameter, redirect to homepage
// Se nenhuma URL válida foi encontrada em nenhum dos parâmetros, redireciona para a página inicial
header('Location: /');
exit;

179
app/src/Router.php Normal file
View file

@ -0,0 +1,179 @@
<?php
namespace App;
require_once __DIR__ . '/../vendor/autoload.php';
use FastRoute;
/**
* Classe Router - Gerenciador de rotas da aplicação
* Router Class - Application route manager
*
* Esta classe implementa o sistema de roteamento usando FastRoute para:
* - Gerenciar todas as rotas da aplicação
* - Processar requisições HTTP
* - Direcionar para os manipuladores apropriados
*
* This class implements the routing system using FastRoute to:
* - Manage all application routes
* - Process HTTP requests
* - Direct to appropriate handlers
*/
class Router
{
/**
* Instância do dispatcher do FastRoute
* FastRoute dispatcher instance
*/
private $dispatcher;
/**
* Construtor - Inicializa as rotas da aplicação
* Constructor - Initializes application routes
*/
public function __construct()
{
$this->dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
// Rota principal - página inicial
// Main route - home page
$r->addRoute('GET', '/', function() {
// Inicialização das variáveis para a view principal
// Initialize variables for the main view
require_once __DIR__ . '/../config.php';
require_once __DIR__ . '/../inc/Cache.php';
require_once __DIR__ . '/../inc/Language.php';
\Language::init(LANGUAGE);
$message = '';
$message_type = '';
$url = '';
// Processa mensagens da query string
// Process query string messages
if (isset($_GET['message'])) {
$message_key = $_GET['message'];
$messageData = \Language::getMessage($message_key);
$message = $messageData['message'];
$message_type = $messageData['type'];
}
// Processa submissão do formulário
// Process form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['url'])) {
$url = filter_var($_POST['url'], FILTER_SANITIZE_URL);
if (filter_var($url, FILTER_VALIDATE_URL)) {
header('Location: ' . SITE_URL . '/p/' . urlencode($url));
exit;
} else {
$messageData = \Language::getMessage('INVALID_URL');
$message = $messageData['message'];
$message_type = $messageData['type'];
}
}
// Inicializa o cache para contagem
// Initialize cache for counting
$cache = new \Cache();
$cache_folder = $cache->getCacheFileCount();
require __DIR__ . '/views/home.php';
});
// Rota da API - inclui api.php existente
// API route - includes existing api.php
$r->addRoute('GET', '/api/{url:.+}', function($vars) {
$_GET['url'] = $vars['url'];
require __DIR__ . '/api.php';
});
// Rota da API sem parâmetros - redireciona para raiz
// API route without parameters - redirects to root
$r->addRoute('GET', '/api[/]', function() {
header('Location: /');
exit;
});
// Rota de processamento - inclui p.php existente
// Processing route - includes existing p.php
$r->addRoute('GET', '/p/{url:.+}', function($vars) {
$_GET['url'] = $vars['url'];
require __DIR__ . '/p.php';
});
// Rota de processamento com query parameter ou sem parâmetros
// Processing route with query parameter or without parameters
$r->addRoute('GET', '/p[/]', function() {
if (isset($_GET['url']) || isset($_GET['text'])) {
$url = isset($_GET['url']) ? $_GET['url'] : '';
$text = isset($_GET['text']) ? $_GET['text'] : '';
// Check which parameter is a valid URL
if (filter_var($url, FILTER_VALIDATE_URL)) {
header('Location: /p/' . urlencode($url));
exit;
} elseif (filter_var($text, FILTER_VALIDATE_URL)) {
header('Location: /p/' . urlencode($text));
exit;
} else {
header('Location: /?message=INVALID_URL');
exit;
}
}
header('Location: /');
exit;
});
// Rota do manifesto PWA - inclui manifest.php existente
// PWA manifest route - includes existing manifest.php
$r->addRoute('GET', '/manifest.json', function() {
require __DIR__ . '/views/manifest.php';
});
});
}
/**
* Despacha a requisição para a rota apropriada
* Dispatches the request to the appropriate route
*/
public function dispatch()
{
$httpMethod = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];
// Remove a query string mas mantém para processamento
// Strip query string but keep for processing
$queryString = '';
if (false !== $pos = strpos($uri, '?')) {
$queryString = substr($uri, $pos);
$uri = substr($uri, 0, $pos);
}
$uri = rawurldecode($uri);
// Parse query string parameters
if ($queryString) {
parse_str(substr($queryString, 1), $_GET);
}
$routeInfo = $this->dispatcher->dispatch($httpMethod, $uri);
switch ($routeInfo[0]) {
case FastRoute\Dispatcher::NOT_FOUND:
require_once __DIR__ . '/../config.php';
header('Location: ' . SITE_URL);
exit;
case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
header("HTTP/1.0 405 Method Not Allowed");
echo '405 Method Not Allowed';
break;
case FastRoute\Dispatcher::FOUND:
$handler = $routeInfo[1];
$vars = $routeInfo[2];
call_user_func($handler, $vars);
break;
}
}
}

119
app/src/api.php Normal file
View file

@ -0,0 +1,119 @@
<?php
/**
* URL Analysis API
* API para análise de URLs
*
* This file implements a REST endpoint that receives URLs via GET
* and returns processed results in JSON format.
*
* Este arquivo implementa um endpoint REST que recebe URLs via GET
* e retorna resultados processados em formato JSON.
*
* Features / Funcionalidades:
* - URL validation / Validação de URLs
* - Content analysis / Análise de conteúdo
* - Error handling / Tratamento de erros
* - CORS support / Suporte a CORS
*/
require_once __DIR__ . '/../config.php';
require_once __DIR__ . '/../inc/URLAnalyzer.php';
require_once __DIR__ . '/../inc/Language.php';
// Initialize language system with default language
// Inicializa o sistema de idiomas com o idioma padrão
Language::init(LANGUAGE);
// Set content type as JSON
// Define o tipo de conteúdo como JSON
header('Content-Type: application/json');
// Enable CORS (Cross-Origin Resource Sharing)
// Habilita CORS (Cross-Origin Resource Sharing)
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET');
/**
* Function to send standardized JSON response
* Função para enviar resposta JSON padronizada
*
* @param array $data Data to be sent in response / Dados a serem enviados na resposta
* @param int $statusCode HTTP status code / Código de status HTTP
*/
function sendResponse($data, $statusCode = 200)
{
http_response_code($statusCode);
$response = [
'status' => $statusCode
];
if (isset($data['error'])) {
$response['error'] = $data['error'];
} else if (isset($data['url'])) {
$response['url'] = $data['url'];
}
echo json_encode($response);
exit;
}
// Get URL from Router
// Obtém a URL do Router
$url = isset($_GET['url']) ? urldecode($_GET['url']) : '';
// Basic URL validation
// Validação básica da URL
if (!$url || !filter_var($url, FILTER_VALIDATE_URL)) {
sendResponse([
'error' => [
'type' => URLAnalyzer::ERROR_INVALID_URL,
'message' => Language::getMessage('INVALID_URL')['message']
]
], 400);
}
try {
// Instantiate URL analyzer
// Instancia o analisador de URLs
$analyzer = new URLAnalyzer();
// Try to analyze the provided URL
// Tenta analisar a URL fornecida
$analyzer->analyze($url);
// If analysis is successful, return the processed URL
// Se a análise for bem-sucedida, retorna a URL processada
sendResponse([
'url' => SITE_URL . '/p/' . $url
], 200);
} catch (URLAnalyzerException $e) {
// Get error details from the exception
// Obtém detalhes do erro da exceção
$errorType = $e->getErrorType();
$additionalInfo = $e->getAdditionalInfo();
// Add error header for better client-side handling
// Adiciona header de erro para melhor tratamento no cliente
header('X-Error-Type: ' . $errorType);
if ($additionalInfo) {
header('X-Error-Info: ' . $additionalInfo);
}
sendResponse([
'error' => [
'type' => $errorType,
'message' => $e->getMessage(),
'details' => $additionalInfo ?: null
]
], $e->getCode());
} catch (Exception $e) {
// Handle any other unexpected errors
// Trata quaisquer outros erros inesperados
sendResponse([
'error' => [
'type' => URLAnalyzer::ERROR_GENERIC_ERROR,
'message' => Language::getMessage('GENERIC_ERROR')['message']
]
], 500);
}

88
app/src/p.php Normal file
View file

@ -0,0 +1,88 @@
<?php
/**
* URL Processor
* Processador de URLs
*
* This file is responsible for:
* - Receiving URLs through the /p/ path
* - Validating URL format
* - Processing content using URLAnalyzer
* - Displaying processed content or redirecting in case of error
*
* Este arquivo é responsável por:
* - Receber URLs através do path /p/
* - Validar o formato da URL
* - Processar o conteúdo usando o URLAnalyzer
* - Exibir o conteúdo processado ou redirecionar em caso de erro
*
* Usage examples / Exemplos de uso:
* /p/https://exemplo.com
* /p/?url=https://exemplo.com
*/
require_once __DIR__ . '/../config.php';
require_once __DIR__ . '/../inc/URLAnalyzer.php';
// Get URL from either path parameter or query string
// Obtém a URL do parâmetro de path ou query string
$url = '';
if (isset($_GET['url'])) {
$url = urldecode($_GET['url']);
}
// Validate URL format
// Valida o formato da URL
if (filter_var($url, FILTER_VALIDATE_URL)) {
$analyzer = new URLAnalyzer();
try {
// Check for redirects
// Verifica se há redirecionamentos
$redirectInfo = $analyzer->checkStatus($url);
// If there's a redirect and the final URL is different
// Se houver redirecionamento e a URL final for diferente
if ($redirectInfo['hasRedirect'] && $redirectInfo['finalUrl'] !== $url) {
// Redirect to final URL
// Redireciona para a URL final
header('Location: ' . SITE_URL . '/p/' . urlencode($redirectInfo['finalUrl']));
exit;
}
// If there's no redirect or if already at final URL
// Se não houver redirecionamento ou se já estiver na URL final
// Try to analyze and process the URL
// Tenta analisar e processar a URL
$content = $analyzer->analyze($url);
// Display processed content
// Exibe o conteúdo processado
echo $content;
exit;
} catch (URLAnalyzerException $e) {
// Get error type and additional info from exception
// Obtém o tipo de erro e informações adicionais da exceção
$errorType = $e->getErrorType();
$additionalInfo = $e->getAdditionalInfo();
// Handle blocked domain with redirect URL
// Trata domínio bloqueado com URL de redirecionamento
if ($errorType === URLAnalyzer::ERROR_BLOCKED_DOMAIN && $additionalInfo) {
header('Location: ' . trim($additionalInfo) . '?message=' . $errorType);
exit;
}
// Redirect to home page with error message
// Redireciona para a página inicial com mensagem de erro
header('Location: /?message=' . $errorType);
exit;
} catch (Exception $e) {
// Handle any other unexpected errors
// Trata quaisquer outros erros inesperados
header('Location: /?message=' . URLAnalyzer::ERROR_GENERIC_ERROR);
exit;
}
} else {
// Invalid URL / URL inválida
header('Location: /?message=' . URLAnalyzer::ERROR_INVALID_URL);
exit;
}

157
app/src/views/home.php Normal file
View file

@ -0,0 +1,157 @@
<!DOCTYPE html>
<html lang="<?php echo Language::getCurrentLanguage(); ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title><?php echo SITE_NAME; ?></title>
<link rel="icon" href="<?php echo SITE_URL; ?>/assets/svg/marreta.svg" type="image/svg+xml">
<meta name="theme-color" content="#2563eb">
<link rel="manifest" href="<?php echo SITE_URL; ?>/manifest.json">
<!-- PWA meta tags -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="application-name" content="<?php echo SITE_NAME; ?>">
<!-- Open Graph meta tags -->
<meta property="og:type" content="website" />
<meta property="og:url" content="<?php echo SITE_URL; ?>" />
<meta property="og:title" content="<?php echo SITE_NAME; ?>" />
<meta property="og:description" content="<?php echo htmlspecialchars(SITE_DESCRIPTION); ?>" />
<meta property="og:image" content="<?php echo SITE_URL; ?>/assets/opengraph.png" />
<script src="https://cdn.tailwindcss.com/3.4.15"></script>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8 max-w-4xl">
<!-- Cabeçalho da página / Page header -->
<div class="text-center mb-8">
<h1 class="text-4xl font-bold text-gray-800 mb-4">
<img src="assets/svg/marreta.svg" class="inline-block w-12 h-12 mb-2" alt="Marreta">
<?php echo SITE_NAME; ?>
</h1>
<p class="text-gray-600 text-lg"><?php echo SITE_DESCRIPTION; ?></p>
<p class="text-gray-600 text-lg">
<span class="font-bold text-blue-600">
<?php echo number_format($cache_folder, 0, ',', '.'); ?>
</span>
<span><?php echo Language::get('walls_destroyed'); ?></span>
</p>
</div>
<!-- Formulário principal de análise de URLs / Main URL analysis form -->
<div class="bg-white rounded-xl shadow-lg p-8 mb-8">
<form id="urlForm" method="POST" onsubmit="return validateForm()" class="space-y-6">
<div class="relative">
<div class="flex items-stretch">
<span class="inline-flex items-center px-5 rounded-l-lg border border-r-0 border-gray-300 bg-gray-50 text-gray-500">
<img src="assets/svg/link.svg" class="w-6 h-6" alt="Link">
</span>
<input type="url"
name="url"
id="url"
class="flex-1 block w-full rounded-none rounded-r-lg text-lg py-4 border border-l-0 border-gray-300 bg-gray-50 focus:border-blue-500 focus:ring-blue-500 shadow-sm bg-gray-50"
placeholder="<?php echo Language::get('url_placeholder'); ?>"
value="<?php echo htmlspecialchars($url); ?>"
required
pattern="https?://.+"
title="<?php echo Language::getMessage('INVALID_URL')['message']; ?>">
</div>
<button type="submit"
class="mt-4 w-full inline-flex justify-center items-center px-6 py-4 border border-transparent text-lg font-medium rounded-lg text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200">
<img src="assets/svg/search.svg" class="w-6 h-6 mr-3" alt="Search">
<?php echo Language::get('analyze_button'); ?>
</button>
</div>
</form>
<!-- Aviso sobre bloqueadores de anúncios / Ad blocker warning -->
<div class="mt-4 text-sm text-gray-600 flex items-start">
<p><?php echo str_replace('{site_name}', SITE_NAME, Language::get('adblocker_warning')); ?></p>
</div>
<!-- Área de mensagens de erro/alerta / Error/warning message area -->
<?php if ($message): ?>
<div class="mt-6 <?php echo $message_type === 'error' ? 'bg-red-50 border-red-400' : 'bg-yellow-50 border-yellow-400'; ?> border-l-4 p-4 rounded-r">
<div class="flex">
<div class="flex-shrink-0">
<?php if ($message_type === 'error'): ?>
<img src="assets/svg/error.svg" class="w-6 h-6" alt="Error">
<?php else: ?>
<img src="assets/svg/warning.svg" class="w-6 h-6" alt="Warning">
<?php endif; ?>
</div>
<div class="ml-3">
<p class="text-base <?php echo $message_type === 'error' ? 'text-red-700' : 'text-yellow-700'; ?>">
<?php echo htmlspecialchars($message); ?>
</p>
</div>
</div>
</div>
<?php endif; ?>
</div>
<!-- Adicionar como aplicativo / Add as app (mobile only) -->
<div class="bg-white rounded-xl shadow-lg p-8 mt-8 mb-8 md:hidden">
<h2 class="text-xl font-semibold text-gray-800 mb-6 flex items-center">
<img src="assets/svg/marreta.svg" class="w-6 h-6 mr-3" alt="App">
<?php echo Language::get('add_as_app'); ?>
</h2>
<div class="space-y-4">
<p class="text-gray-600">
<?php echo str_replace('{site_name}', SITE_NAME, Language::get('add_as_app_description')); ?>
</p>
<div class="bg-gray-50 rounded-lg p-4">
<ol class="list-decimal list-inside space-y-2 text-gray-700">
<li><?php echo Language::get('add_as_app_step1'); ?></li>
<li><?php echo Language::get('add_as_app_step2'); ?></li>
<li><?php echo Language::get('add_as_app_step3'); ?></li>
<li><?php echo str_replace('{site_name}', SITE_NAME, Language::get('add_as_app_step4')); ?></li>
</ol>
</div>
</div>
</div>
<!-- Seção de Bookmarklet / Bookmarklet section (desktop only) -->
<div class="bg-white rounded-xl shadow-lg p-8 mt-8 mb-8 hidden md:block">
<h2 class="text-xl font-semibold text-gray-800 mb-6 flex items-center">
<img src="assets/svg/bookmark.svg" class="w-6 h-6 mr-3" alt="Favoritos">
<?php echo Language::get('bookmarklet_title'); ?>
</h2>
<div class="space-y-4">
<p class="text-gray-600">
<?php echo str_replace('{site_name}', SITE_NAME, Language::get('bookmarklet_description')); ?>
</p>
<div class="flex justify-center">
<a href="javascript:(function(){let currentUrl=window.location.href;window.location.href='<?php echo SITE_URL; ?>/p/'+encodeURIComponent(currentUrl);})()"
class="inline-flex items-center px-6 py-3 border-2 border-blue-500 font-medium rounded-lg text-blue-600 bg-white hover:bg-blue-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200 cursor-move"
onclick="return false;">
<img src="assets/svg/marreta.svg" class="w-5 h-5 mr-2" alt="Marreta">
<?php echo str_replace('{site_name}', SITE_NAME, Language::get('open_in')); ?>
</a>
</div>
</div>
</div>
<div class="text-gray-600 text-center text-sm">
<p>
<?php echo Language::get('open_source_description'); ?>
</p>
<p class="mt-2">
<a href="https://github.com/manualdousuario/marreta/wiki/API-Rest" target="_blank" class="underline">API Rest</a> <a href="https://github.com/manualdousuario/marreta/" target="_blank" class="underline">Github</a>
</p>
</div>
</div>
<!-- Scripts JavaScript -->
<script>
<?php
$js_file = 'assets/js/scripts.js';
if (file_exists($js_file)) {
echo file_get_contents($js_file);
}
?>
</script>
</body>
</html>

View file

@ -9,8 +9,8 @@
* Ele define o comportamento da aplicação quando instalada em um dispositivo e sua aparência.
*/
require_once 'config.php';
require_once 'inc/Language.php';
require_once __DIR__ . '/../../config.php';
require_once __DIR__ . '/../../inc/Language.php';
header('Content-Type: application/json');

View file

@ -48,18 +48,10 @@ server {
return 403;
}
# All requests go through index.php for FastRoute routing
# Todas as requisições passam pelo index.php para roteamento FastRoute
location / {
try_files $uri $uri/ $uri/index.php?$args;
}
location ~ ^/(api|p)/ {
try_files $uri $uri/ /$1.php;
}
# Serve manifest.json from manifest.php
# Serve manifest.json a partir do manifest.php
location = /manifest.json {
rewrite ^ /manifest.php last;
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {