| Server IP : 80.211.21.168 / Your IP : 216.73.217.143 Web Server : Apache System : Linux alex-webdesign.it 3.10.0-1160.119.1.el7.tuxcare.els22.x86_64 #1 SMP Mon Aug 18 06:07:12 UTC 2025 x86_64 User : admin_japan ( 10003) PHP Version : 8.2.32 Disable Function : opcache_get_status MySQL : OFF | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /var/www/vhosts/cerchijapan.it/ai4dealers.com/ |
Upload File : |
<?php
session_start();
ini_set('display_errors','1');
error_reporting(E_ALL);
/** =========================
* CONFIG BASE + API KEY
* ========================= */
$API_URL = "https://api.openai.com/v1/chat/completions";
$MODEL = "gpt-5-mini-2025-08-07";
$TEMP = 1;
// ⚠️ API key hardcoded SOLO per test (RUOTALA DOPO)
$HARDCODED_API_KEY = "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";
// Limiti fetch URL
const FETCH_TIMEOUT_SEC = 10;
const FETCH_MAX_REDIRECTS = 3;
const FETCH_MAX_BYTES = 1572864; // ≈1.5MB
const TOOL_TEXT_LIMIT = 120000; // ≈120k chars verso il modello
/** Estensioni minime */
foreach (['curl','json','mbstring','openssl'] as $ext) {
if (!extension_loaded($ext)) {
$_SESSION['flash_error'] = "Estensione PHP mancante: {$ext}. Attivala nel php.ini.";
}
}
/** Gestione API key: form -> sessione -> env var -> hardcoded */
if (isset($_POST['api_key'])) {
$_SESSION['api_key'] = trim((string)$_POST['api_key']);
}
$API_KEY = $_SESSION['api_key'] ?? getenv('OPENAI_API_KEY') ?? $HARDCODED_API_KEY;
/** Reset chat */
if (isset($_POST['reset'])) {
unset($_SESSION['messages'], $_SESSION['last_api_raw'], $_SESSION['flash_error']);
header("Location: ".$_SERVER['PHP_SELF']);
exit;
}
/** Inizializza conversazione (system) */
if (!isset($_SESSION['messages'])) {
$_SESSION['messages'] = [[
"role" => "system",
"content" =>
"Se l'utente invia un URL, usa il tool fetch_url per leggerlo prima di rispondere. ".
"Se il fetch fallisce, spiega brevemente che rispondi senza leggere il link."
]];
}
/** Tool dichiarato (ORA implementato lato server) */
$tools = [[
"type" => "function",
"function" => [
"name" => "fetch_url",
"description" => "Scarica una pagina web e restituisce testo pulito per il modello.",
"parameters" => [
"type" => "object",
"properties" => [
"url" => ["type"=>"string", "description"=>"URL http/https da leggere"]
],
"required" => ["url"]
]
]
]];
/** =========================
* UTIL: maschera e debug
* ========================= */
function mask($s) {
if (!$s) return '—';
$n = strlen($s);
if ($n <= 8) return str_repeat('•', $n);
return substr($s, 0, 4) . str_repeat('•', $n - 8) . substr($s, -4);
}
function save_last_api_raw($code, $body) {
$_SESSION['last_api_raw'] = "HTTP {$code}\n".(string)$body;
}
/** =========================
* OPENAI CALL
* ========================= */
function call_openai(array $payload, string $API_URL, string $API_KEY): array {
if (!$API_KEY) throw new RuntimeException("API key mancante.");
$ch = curl_init($API_URL);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer {$API_KEY}",
"Content-Type: application/json"
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
]);
$resp = curl_exec($ch);
if ($resp === false) throw new RuntimeException("cURL error: ".curl_error($ch));
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
save_last_api_raw($code, $resp);
if ($code < 200 || $code >= 300) {
$snippet = mb_substr($resp ?? '', 0, 1500);
throw new RuntimeException("HTTP {$code} dalla OpenAI API. Body: {$snippet}");
}
$data = json_decode($resp, true);
if (!is_array($data)) throw new RuntimeException("Risposta non valida dall'API (JSON decode fallito).");
return $data;
}
/** =========================
* FETCH URL (con protezioni)
* ========================= */
function is_public_ip(string $ip): bool {
return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false;
}
function resolve_host_ips(string $host): array {
$ips = [];
// IPv4
$a = @gethostbynamel($host);
if (is_array($a)) $ips = array_merge($ips, $a);
// IPv6
$aaaa = @dns_get_record($host, DNS_AAAA);
if (is_array($aaaa)) {
foreach ($aaaa as $r) if (!empty($r['ipv6'])) $ips[] = $r['ipv6'];
}
return array_values(array_unique($ips));
}
function normalize_url(string $url): string {
$url = trim($url);
// Assicuriamoci che ci sia schema
if (!preg_match('~^https?://~i', $url)) {
throw new InvalidArgumentException("URL non valido (manca schema http/https).");
}
$parts = parse_url($url);
if (!$parts || empty($parts['host'])) throw new InvalidArgumentException("URL non valido.");
$scheme = strtolower($parts['scheme'] ?? '');
if (!in_array($scheme, ['http','https'], true)) throw new InvalidArgumentException("Schema non consentito.");
return $url;
}
function assert_safe_host(string $host): void {
// Se host è già un IP, controlla subito
if (filter_var($host, FILTER_VALIDATE_IP)) {
if (!is_public_ip($host)) throw new RuntimeException("IP non pubblico bloccato.");
return;
}
// Altrimenti risolvi DNS e verifica tutti gli IP
$ips = resolve_host_ips($host);
if (!$ips) throw new RuntimeException("Impossibile risolvere DNS per {$host}.");
foreach ($ips as $ip) {
if (!is_public_ip($ip)) throw new RuntimeException("Risolto IP non pubblico/riservato. Bloccato.");
}
}
function parse_headers_from_string(string $rawHeaders): array {
$headers = [];
$lines = preg_split("/\r\n|\n|\r/", $rawHeaders);
foreach ($lines as $line) {
if (strpos($line, ':') !== false) {
[$k,$v] = explode(':', $line, 2);
$headers[strtolower(trim($k))] = trim($v);
}
}
return $headers;
}
function abs_url(string $base, string $rel): string {
// semplice risoluzione Location relativa
if (preg_match('~^https?://~i', $rel)) return $rel;
$bp = parse_url($base);
if (!$bp) return $rel;
$scheme = $bp['scheme'] ?? 'https';
$host = $bp['host'] ?? '';
$port = isset($bp['port']) ? ":{$bp['port']}" : '';
if (strpos($rel, '/') === 0) {
return "{$scheme}://{$host}{$port}{$rel}";
}
$path = $bp['path'] ?? '/';
$path = preg_replace('~/[^/]*$~', '/', $path);
return "{$scheme}://{$host}{$port}{$path}{$rel}";
}
function fetch_once(string $url): array {
$ch = curl_init($url);
$headers = [
"User-Agent: WhatsApp-GPT/1.0 (+chat_web.php)",
"Accept: text/html,application/json,text/plain;q=0.9,*/*;q=0.8",
"Accept-Language: it-IT,it;q=0.9,en;q=0.8",
"Range: bytes=0-".(FETCH_MAX_BYTES-1),
];
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_FOLLOWLOCATION => false, // gestiamo noi i redirect
CURLOPT_TIMEOUT => FETCH_TIMEOUT_SEC,
CURLOPT_CONNECTTIMEOUT => 6,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
$resp = curl_exec($ch);
if ($resp === false) {
throw new RuntimeException("Fetch cURL error: ".curl_error($ch));
}
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$rawHeaders = substr($resp, 0, $headerSize);
$body = substr($resp, $headerSize);
$hdrs = parse_headers_from_string($rawHeaders);
$ct = $hdrs['content-type'] ?? '';
return [
'status' => $status,
'headers' => $hdrs,
'body' => $body,
'content_type' => $ct,
'url' => $url,
];
}
function fetch_with_redirects(string $url, int $maxRedirects = FETCH_MAX_REDIRECTS): array {
$visited = 0;
$current = $url;
while (true) {
$norm = normalize_url($current);
$parts = parse_url($norm);
assert_safe_host($parts['host']);
$r = fetch_once($norm);
if (in_array($r['status'], [301,302,303,307,308], true) && isset($r['headers']['location'])) {
if ($visited >= $maxRedirects) {
throw new RuntimeException("Troppi redirect.");
}
$next = abs_url($norm, $r['headers']['location']);
// ricontrolla sicurezza su host target al prossimo giro
$visited++;
$current = $next;
continue;
}
return $r;
}
}
function html_to_text_basic(string $html): string {
// rimuovi script/style/noscript/svg/canvas
$html = preg_replace('~<(script|style|noscript|svg|canvas)[^>]*>.*?</\1>~is', ' ', $html);
// togli tag
$text = strip_tags($html);
// normalizza spazi
$text = html_entity_decode($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$text = preg_replace('/[ \t]+/', ' ', $text);
$text = preg_replace('/\R{2,}/', "\n\n", $text);
return trim($text);
}
function pick_mime(string $ct): string {
if (!$ct) return 'application/octet-stream';
if (strpos($ct, ';') !== false) $ct = trim(strtolower(strtok($ct,';')));
return strtolower($ct);
}
function convert_body_to_text(string $body, string $contentType): string {
$mime = pick_mime($contentType);
if (strpos($mime, 'text/html') === 0) {
return html_to_text_basic($body);
}
if (strpos($mime, 'text/') === 0) {
return trim($body);
}
if (strpos($mime, 'application/json') === 0) {
// prova a prettificare
$arr = json_decode($body, true);
if (is_array($arr)) return substr(json_encode($arr, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE), 0, TOOL_TEXT_LIMIT);
return substr($body, 0, TOOL_TEXT_LIMIT);
}
// PDF e altri: non processiamo qui
return "[fetch_url] Tipo contenuto non gestito: {$mime}. Dimensione ricevuta: ".strlen($body)." bytes.";
}
function fetch_url_text_secure(string $url): string {
try {
$res = fetch_with_redirects($url);
$text = convert_body_to_text($res['body'], $res['content_type']);
// tronca per il modello
return mb_substr($text, 0, TOOL_TEXT_LIMIT);
} catch (Throwable $e) {
return "[fetch_url][ERRORE] ".$e->getMessage();
}
}
/** =========================
* HANDLER POST CHAT
* ========================= */
$hadError = false;
if (isset($_POST['user_message']) && trim((string)$_POST['user_message']) !== '') {
$user = trim((string)$_POST['user_message']);
$_SESSION['messages'][] = ["role" => "user", "content" => $user];
try {
// 1) Prima chiamata
$req = [
"model" => $MODEL,
"messages" => $_SESSION['messages'],
"tools" => $tools,
"tool_choice" => "auto",
"temperature" => $TEMP
];
$res = call_openai($req, $API_URL, $API_KEY);
$choice = $res["choices"][0] ?? null;
$assistantMsg = $choice["message"] ?? ["role" => "assistant", "content" => ""];
$_SESSION['messages'][] = $assistantMsg;
// 2) Eseguiamo le eventuali tool-calls fetch_url
$toolCalls = $assistantMsg["tool_calls"] ?? [];
if (is_array($toolCalls) && count($toolCalls) > 0) {
foreach ($toolCalls as $tc) {
$callId = $tc["id"] ?? "";
$fname = $tc["function"]["name"] ?? "";
$args = json_decode($tc["function"]["arguments"] ?? "{}", true);
if ($fname === "fetch_url" && is_array($args) && !empty($args["url"])) {
$toolContent = fetch_url_text_secure((string)$args["url"]);
} else {
$toolContent = "[fetch_url][ERRORE] Argomenti non validi o funzione sconosciuta.";
}
$_SESSION['messages'][] = [
"role" => "tool",
"tool_call_id" => $callId,
"content" => $toolContent
];
}
// 2a) Seconda chiamata con output del tool
$req2 = [
"model" => $MODEL,
"messages" => $_SESSION['messages'],
"tools" => $tools,
"tool_choice" => "auto",
"temperature" => $TEMP
];
$res2 = call_openai($req2, $API_URL, $API_KEY);
$choice2 = $res2["choices"][0] ?? null;
$assistantMsg2 = $choice2["message"] ?? ["role" => "assistant", "content" => "(nessuna risposta)"];
$_SESSION['messages'][] = $assistantMsg2;
}
// redirect solo se tutto ok
header("Location: " . $_SERVER['PHP_SELF'] . (isset($_GET['debug']) ? '?debug=1' : ''));
exit;
} catch (Throwable $e) {
$hadError = true;
$_SESSION['flash_error'] = $e->getMessage();
// niente redirect: mostriamo errore in pagina
}
}
/** =========================
* VIEW
* ========================= */
$flashError = $_SESSION['flash_error'] ?? null;
unset($_SESSION['flash_error']);
$API_IN_USE = $API_KEY ?: '—';
$debugOn = isset($_GET['debug']);
$lastRaw = $debugOn ? ($_SESSION['last_api_raw'] ?? "—") : null;
?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mini Chat GPT — Chat Completions + fetch_url</title>
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; margin: 0; background:#f6f7f9; }
header { background:#0f172a; color:#fff; padding:14px 16px; }
main { max-width: 900px; margin: 0 auto; padding: 16px; }
.bar { background:#fff; border:1px solid #e5e7eb; padding:12px; border-radius:10px; margin-bottom:16px; display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
.bar input[type="password"] { width: 320px; max-width: 80vw; padding:8px; border:1px solid #e5e7eb; border-radius:8px; }
.chat { background:#fff; border:1px solid #e5e7eb; border-radius: 12px; padding: 12px; min-height: 50vh; }
.msg { margin: 10px 0; }
.msg.user { text-align: right; }
.bubble { display:inline-block; padding:10px 12px; border-radius:12px; max-width: 80%; white-space: pre-wrap; }
.user .bubble { background:#dcfce7; border:1px solid #bbf7d0; }
.assistant .bubble { background:#eef2ff; border:1px solid #c7d2fe; }
.tool .bubble { background:#ffe4e6; border:1px solid #fecdd3; font-size: 0.95em; }
form.send { display:flex; gap:10px; margin-top:12px; }
form.send textarea { flex:1; padding:10px; border-radius:10px; border:1px solid #e5e7eb; min-height:60px; }
form.send button { padding:10px 14px; border-radius:10px; border:0; background:#0ea5e9; color:#fff; cursor:pointer; }
.small { color:#64748b; font-size:12px; }
.danger { color:#b91c1c; }
.error { background:#fee2e2; border:1px solid #fecaca; color:#991b1b; padding:8px 10px; border-radius:10px; margin-bottom:12px; }
pre.debug { background:#0b1020; color:#d1e7ff; padding:12px; border-radius:10px; overflow:auto; max-height:300px; }
a.btn { background:#111827; color:#fff; padding:6px 10px; border-radius:8px; text-decoration:none; }
</style>
</head>
<body>
<header>
<strong>Mini Chat GPT</strong> — Chat Completions + tool <code>fetch_url</code> (attivo)
</header>
<main>
<?php if ($flashError): ?>
<div class="error"><?= htmlspecialchars($flashError, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></div>
<?php endif; ?>
<form class="bar" method="post" action="">
<label>API key (opzionale override):
<input type="password" name="api_key" placeholder="sk-..." value="" autocomplete="off">
</label>
<button type="submit">Salva</button>
<span class="small">In uso: <?= htmlspecialchars(mask($API_IN_USE), ENT_QUOTES, 'UTF-8') ?></span>
<button name="reset" value="1" style="margin-left:auto; background:#ef4444;color:#fff;padding:8px 12px;border-radius:8px;border:0;cursor:pointer">Reset chat</button>
<?php if (!$debugOn): ?>
<a class="btn" href="<?= htmlspecialchars($_SERVER['PHP_SELF'].'?debug=1', ENT_QUOTES) ?>">Debug: ON</a>
<?php else: ?>
<a class="btn" href="<?= htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES) ?>">Debug: OFF</a>
<?php endif; ?>
</form>
<div class="chat">
<?php
foreach (($_SESSION['messages'] ?? []) as $m) {
if (!in_array($m['role'], ['user','assistant','tool'], true)) continue;
$cls = htmlspecialchars($m['role'], ENT_QUOTES, 'UTF-8');
$content = $m['content'] ?? '';
echo '<div class="msg '.$cls.'"><div class="bubble">'.nl2br(htmlspecialchars($content, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')).'</div></div>';
}
?>
<form class="send" method="post" action="">
<textarea name="user_message" placeholder="Scrivi un messaggio... (incolla un link per farlo leggere al modello)"></textarea>
<button type="submit">Invia</button>
</form>
<div class="small" style="margin-top:8px">
Modello: <code><?= htmlspecialchars($MODEL, ENT_QUOTES, 'UTF-8') ?></code> — Temperature: <?= $TEMP ?> — Endpoint: <code>/v1/chat/completions</code>
</div>
</div>
<?php if ($debugOn): ?>
<h3>Debug ultima risposta API</h3>
<pre class="debug"><?= htmlspecialchars($lastRaw ?? "—", ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></pre>
<p class="small">
Suggerimento: se vedi <em>invalid_model</em> o simili, verifica che il nome del modello sia abilitato per Chat Completions.
</p>
<?php endif; ?>
<p class="small danger" style="margin-top:10px">
⚠️ Sicurezza: key hardcoded. Non pubblicare questo file; ruota la chiave a fine test. Il fetch blocca IP privati/loopback e segue max <?= FETCH_MAX_REDIRECTS ?> redirect con ri-validazione ostile.
</p>
</main>
</body>
</html>