/home/altere25/public_html
Edit: /home/altere25/public_html/email-forwarder.php (10503B)
$parsedEmail['from'],
'subject' => $parsedEmail['subject'],
'attachmentCount' => count($parsedEmail['attachments'])
]));
}
// Add metadata
$parsedEmail['metadata'] = [
'script_version' => '4.4',
'script_path' => __FILE__,
'source' => 'cPanel_piping_script',
'parsed_at' => date('c')
];
// Send to webhook
$result = sendToWebhook($parsedEmail);
if (DEBUG_MODE) {
error_log("✅ Webhook response: " . $result);
}
// Success response for cPanel
http_response_code(200);
echo "Email processed successfully";
} catch (Exception $e) {
error_log("❌ Error processing email: " . $e->getMessage());
http_response_code(500);
echo "Error: " . $e->getMessage();
}
/**
* Parse MIME email and extract all parts
*/
function parseMimeEmail($rawEmail) {
$headers = [];
$body = '';
$htmlBody = '';
$attachments = [];
$boundary = '';
// Split into lines
$lines = explode("\n", $rawEmail);
// Parse headers
$i = 0;
$currentHeader = '';
while ($i < count($lines)) {
$line = rtrim($lines[$i], "\r\n");
// Empty line marks end of headers
if (trim($line) === '') {
$i++;
break;
}
// Continuation of previous header (starts with whitespace)
if (preg_match('/^[\s\t]/', $line) && $currentHeader) {
$headers[$currentHeader] .= ' ' . trim($line);
}
// New header
elseif (preg_match('/^([^:]+):\s*(.*)$/', $line, $matches)) {
$currentHeader = $matches[1];
$headers[$currentHeader] = $matches[2];
}
$i++;
}
// Extract boundary from Content-Type header
if (isset($headers['Content-Type'])) {
if (preg_match('/boundary="?([^"\s;]+)"?/i', $headers['Content-Type'], $matches)) {
$boundary = $matches[1];
}
}
// Get remaining content (body)
$bodyContent = implode("\n", array_slice($lines, $i));
// Parse multipart email
if ($boundary) {
$parts = parseMultipartBody($bodyContent, $boundary);
foreach ($parts as $part) {
$partHeaders = $part['headers'];
$partBody = $part['body'];
$contentType = isset($partHeaders['Content-Type']) ? $partHeaders['Content-Type'] : 'text/plain';
$contentDisposition = isset($partHeaders['Content-Disposition']) ? $partHeaders['Content-Disposition'] : '';
$contentTransferEncoding = isset($partHeaders['Content-Transfer-Encoding']) ? strtolower($partHeaders['Content-Transfer-Encoding']) : '';
// Check if this is an attachment
if (strpos($contentDisposition, 'attachment') !== false || strpos($contentDisposition, 'filename') !== false) {
// Extract filename
$filename = 'unknown';
if (preg_match('/filename="?([^";]+)"?/i', $contentDisposition, $matches)) {
$filename = trim($matches[1]);
} elseif (preg_match('/name="?([^";]+)"?/i', $contentType, $matches)) {
$filename = trim($matches[1]);
}
// Decode content based on encoding
$decodedContent = decodeContent($partBody, $contentTransferEncoding);
// Get just the content type without parameters
$mimeType = preg_match('/^([^;]+)/', $contentType, $matches) ? trim($matches[1]) : 'application/octet-stream';
$attachments[] = [
'filename' => $filename,
'contentType' => $mimeType,
'size' => strlen($decodedContent),
'content' => base64_encode($decodedContent)
];
}
// Text body
elseif (strpos($contentType, 'text/plain') !== false) {
$body = decodeContent($partBody, $contentTransferEncoding);
}
// HTML body
elseif (strpos($contentType, 'text/html') !== false) {
$htmlBody = decodeContent($partBody, $contentTransferEncoding);
}
}
} else {
// Simple email without multipart
$contentTransferEncoding = isset($headers['Content-Transfer-Encoding']) ? strtolower($headers['Content-Transfer-Encoding']) : '';
$body = decodeContent($bodyContent, $contentTransferEncoding);
}
// Build normalized email data
return [
'email' => isset($headers['To']) ? $headers['To'] : '',
'to' => isset($headers['To']) ? $headers['To'] : '',
'from' => isset($headers['From']) ? $headers['From'] : '',
'subject' => isset($headers['Subject']) ? decodeHeader($headers['Subject']) : 'No Subject',
'plain' => $body,
'html' => $htmlBody,
'attachments' => $attachments,
'headers' => $headers,
'message_id' => isset($headers['Message-ID']) ? $headers['Message-ID'] : '',
'in_reply_to' => isset($headers['In-Reply-To']) ? $headers['In-Reply-To'] : '',
'references' => isset($headers['References']) ? $headers['References'] : '',
'timestamp' => isset($headers['Date']) ? $headers['Date'] : date('c'),
'script_path' => __FILE__ // This triggers our custom format detector
];
}
/**
* Parse multipart body into individual parts
*/
function parseMultipartBody($body, $boundary) {
$parts = [];
// Split by boundary
$sections = preg_split('/--' . preg_quote($boundary, '/') . '(--)?\r?\n/', $body);
foreach ($sections as $section) {
$section = trim($section);
if (empty($section)) continue;
// Split section into headers and body
$sectionLines = explode("\n", $section);
$sectionHeaders = [];
$sectionBodyStart = 0;
// Parse section headers
$currentHeader = '';
for ($i = 0; $i < count($sectionLines); $i++) {
$line = rtrim($sectionLines[$i], "\r\n");
// Empty line marks end of headers
if (trim($line) === '') {
$sectionBodyStart = $i + 1;
break;
}
// Continuation of previous header
if (preg_match('/^[\s\t]/', $line) && $currentHeader) {
$sectionHeaders[$currentHeader] .= ' ' . trim($line);
}
// New header
elseif (preg_match('/^([^:]+):\s*(.*)$/', $line, $matches)) {
$currentHeader = $matches[1];
$sectionHeaders[$currentHeader] = $matches[2];
}
}
// Get section body
$sectionBody = implode("\n", array_slice($sectionLines, $sectionBodyStart));
// Check if this part has nested multipart
if (isset($sectionHeaders['Content-Type']) && preg_match('/boundary="?([^"\s;]+)"?/i', $sectionHeaders['Content-Type'], $matches)) {
$nestedBoundary = $matches[1];
$nestedParts = parseMultipartBody($sectionBody, $nestedBoundary);
$parts = array_merge($parts, $nestedParts);
} else {
$parts[] = [
'headers' => $sectionHeaders,
'body' => $sectionBody
];
}
}
return $parts;
}
/**
* Decode content based on transfer encoding
*/
function decodeContent($content, $encoding) {
$content = trim($content);
switch ($encoding) {
case 'base64':
return base64_decode($content);
case 'quoted-printable':
return quoted_printable_decode($content);
case '7bit':
case '8bit':
case 'binary':
default:
return $content;
}
}
/**
* Decode MIME encoded headers (e.g., =?UTF-8?B?...?=)
*/
function decodeHeader($header) {
if (function_exists('iconv_mime_decode')) {
return iconv_mime_decode($header, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, 'UTF-8');
}
return $header;
}
/**
* Send parsed email data to webhook
*/
function sendToWebhook($emailData) {
$ch = curl_init(WEBHOOK_URL);
$jsonData = json_encode($emailData);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $jsonData,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Content-Length: ' . strlen($jsonData)
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => true
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
throw new Exception("Webhook request failed: " . $error);
}
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("Webhook returned HTTP " . $httpCode . ": " . $response);
}
return $response;
}