connect($host, $port, $username, $password, $encryption); $this->disconnect(); return ['success' => true, 'message' => 'Connessione e autenticazione IMAP riuscite con successo.']; } catch (\Throwable $e) { return ['success' => false, 'message' => 'Errore IMAP: ' . $e->getMessage()]; } } public function connect(string $host, int $port, string $username, string $password, string $encryption = 'ssl'): void { $prefix = strtolower($encryption) === 'ssl' ? 'ssl://' : ''; $target = $prefix . $host; $timeout = 15; $context = stream_context_create([ 'ssl' => [ 'verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true, ], ]); $this->socket = @stream_socket_client( $target . ':' . $port, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $context ); if (! $this->socket) { throw new Exception("Impossibile connettersi al server IMAP {$host}:{$port} ({$errstr})"); } stream_set_timeout($this->socket, $timeout); // Leggi il banner iniziale $greeting = $this->readLine(); if (! str_starts_with($greeting, '* OK')) { throw new Exception("Risposta iniziale inattesa dal server IMAP: {$greeting}"); } // Login $userEscaped = addcslashes($username, '\\"'); $passEscaped = addcslashes($password, '\\"'); $res = $this->sendCommand("LOGIN \"{$userEscaped}\" \"{$passEscaped}\""); if (! $res['ok']) { throw new Exception("Autenticazione IMAP fallita per l'utente {$username}: " . ($res['response'] ?? '')); } } public function selectFolder(string $folder = 'INBOX'): array { $folderEscaped = addcslashes($folder, '\\"'); $res = $this->sendCommand("SELECT \"{$folderEscaped}\""); if (! $res['ok']) { throw new Exception("Impossibile aprire la cartella IMAP '{$folder}'"); } $exists = 0; foreach ($res['lines'] as $line) { if (preg_match('/^\*\s+(\d+)\s+EXISTS/i', $line, $m)) { $exists = (int) $m[1]; } } return ['ok' => true, 'exists' => $exists]; } /** * @return array */ public function search(string $criteria = 'ALL'): array { $res = $this->sendCommand("SEARCH {$criteria}"); if (! $res['ok']) { return []; } $ids = []; foreach ($res['lines'] as $line) { if (str_starts_with($line, '* SEARCH')) { $parts = explode(' ', trim(substr($line, 8))); foreach ($parts as $p) { if (is_numeric($p) && (int) $p > 0) { $ids[] = (int) $p; } } } } return $ids; } public function fetchRawEml(int $msgId): string { $tag = $this->nextTag(); $cmd = "{$tag} FETCH {$msgId} (BODY.PEEK[])\r\n"; fwrite($this->socket, $cmd); $firstLine = $this->readLine(); $expectedLength = null; if (preg_match('/\{(\d+)\}$/', trim($firstLine), $m)) { $expectedLength = (int) $m[1]; } $rawEml = ''; if ($expectedLength !== null && $expectedLength > 0) { $bytesRead = 0; while ($bytesRead < $expectedLength && ! feof($this->socket)) { $chunk = fread($this->socket, min(8192, $expectedLength - $bytesRead)); if ($chunk === false || $chunk === '') { break; } $rawEml .= $chunk; $bytesRead += strlen($chunk); } } // Leggi fino al tag di completamento while (! feof($this->socket)) { $line = $this->readLine(); if (str_starts_with($line, $tag . ' OK')) { break; } if (str_starts_with($line, $tag . ' NO') || str_starts_with($line, $tag . ' BAD')) { break; } } return $rawEml; } public function disconnect(): void { if ($this->socket) { try { $this->sendCommand('LOGOUT'); } catch (\Throwable) { // ignore } @fclose($this->socket); $this->socket = null; } } private function nextTag(): string { $this->tagIndex++; return 'A' . str_pad((string) $this->tagIndex, 4, '0', STR_PAD_LEFT); } private function sendCommand(string $command): array { if (! $this->socket) { throw new Exception("Socket IMAP non connesso."); } $tag = $this->nextTag(); $payload = "{$tag} {$command}\r\n"; fwrite($this->socket, $payload); $lines = []; $ok = false; $response = ''; while (! feof($this->socket)) { $line = $this->readLine(); $lines[] = $line; if (str_starts_with($line, $tag . ' OK')) { $ok = true; $response = $line; break; } if (str_starts_with($line, $tag . ' NO') || str_starts_with($line, $tag . ' BAD')) { $ok = false; $response = $line; break; } } return [ 'ok' => $ok, 'response' => $response, 'lines' => $lines, ]; } private function readLine(): string { $line = fgets($this->socket); return $line !== false ? trim($line, "\r\n") : ''; } }