The issue you're encountering is related to the PHP iconv() function throwing notices about "incomplete multibyte character" or "illegal character" in the input string, specifically in the decode method of your Imap class (line 416). This occurs when processing email headers or content with problematic encodings, often due to non-standard or corrupted character sequences. Additionally, you mentioned that attachment filenames appear in a format like _utf-8_B_0JPQsNGA0LDQvdGC0LjQudC90L7QtSDQotGA0LXQudGB0..., indicating issues with MIME header decoding.
Below, I'll outline the root causes of these issues and provide actionable solutions to track and fix the warnings, as well as address the filename decoding problem.
Root Causes
- iconv() Notices:
- The iconv() function in the decode method (line 416) fails when it encounters invalid or incomplete multibyte sequences in the input string (e.g., email subject or attachment names).
- This is common when emails use non-standard encodings, corrupted data, or encodings not properly supported by iconv().
- The decode method uses imap_mime_header_decode() to split the header into parts, but it doesn't handle cases where the charset is invalid or unsupported by iconv().
- Filename Decoding Issues:
- The attachment filenames (e.g., _utf-8_B_0JPQsNGA0LDQvdGC0LjQudC90L7QtSDQotGA0LXQudGB0...) suggest that the MIME header decoding is failing, likely because the filename is encoded in a format like =?UTF-8?B?...?=, but the decoding logic isn't properly handling it.
- The setFileName and saveToDirectory methods rely on the decode method, which may produce invalid or unreadable filenames if the input is malformed.
- Lack of Robust Error Handling:
- The decode method attempts a fallback with fallbackDecode, but it may not always resolve the issue, especially for complex or mixed encodings.
- The class doesn't log or handle these errors in a way that makes debugging straightforward.
Solutions
To address the iconv() notices and the filename decoding issues, we need to:
- Improve the decode method to handle problematic encodings gracefully.
- Enhance filename sanitization and decoding for attachments.
- Add logging to track problematic inputs for debugging.
- Test and validate the changes to ensure robustness.
Below are the specific steps and code modifications:
1. Improve the decode Method
The decode method is where the iconv() notices occur. We'll modify it to:
- Validate the charset before passing it to iconv().
- Use mb_convert_encoding() as a fallback if iconv() fails.
- Log problematic inputs for debugging.
Here's the updated decode method:
private function decode($text) {
if (empty($text)) {
return '';
}
$result = '';
$decoded_parts = imap_mime_header_decode($text);
if ($decoded_parts === false) {
// Log the error and fallback
$this->errors[] = "Failed to decode MIME header: " . $text;
return $this->fallbackDecode($text);
}
foreach ($decoded_parts as $word) {
$charset = ($word->charset === 'default') ? 'ascii' : $word->charset;
$normalizedCharset = $this->normalizeCharsetEncodings($charset);
if ($normalizedCharset && in_array($normalizedCharset, mb_list_encodings())) {
try {
$converted = iconv($normalizedCharset, 'UTF-8//IGNORE', $word->text);
if ($converted === false) {
throw new Exception("iconv failed for charset: $normalizedCharset");
}
$result .= $converted;
} catch (Exception $e) {
$this->errors[] = "iconv error for charset '$normalizedCharset' on text: " . $word->text;
$result .= $this->fallbackDecode($word->text);
}
} else {
// Invalid or unsupported charset, use fallback
$this->errors[] = "Unsupported charset '$charset' in text: " . $word->text;
$result .= $this->fallbackDecode($word->text);
}
}
if (empty($result)) {
$this->errors[] = "Empty result after decoding text: " . $text;
$result = $this->fallbackDecode($text);
}
return $result;
}Key Changes:
- Added validation of the charset using normalizeCharsetEncodings and mb_list_encodings().
- Wrapped iconv() in a try-catch block to catch errors and use //IGNORE to skip invalid characters.
- Log errors to the $this->errors array for debugging.
- Fallback to fallbackDecode if iconv() fails or the charset is invalid.
2. Enhance Filename Handling
The filenames like _utf-8_B_0JPQsNGA0LDQvdGC0LjQudC90L7QtSDQotGA0LXQudGB0... indicate that the MIME-encoded filename isn't being decoded properly. We'll improve the setFileName and saveToDirectory methods to handle this.
Update the setFileName method to sanitize and decode filenames properly:
private function setFileName($text) {
$decoded = $this->decode($text);
// Sanitize the filename to remove invalid characters
$cleanName = preg_replace('/[^\p{L}\p{N}\s\-_\.]/u', '_', $decoded);
$cleanName = preg_replace('/\s+/', '_', $cleanName); // Replace multiple spaces with underscore
$cleanName = mb_substr($cleanName, 0, 100); // Limit length
$this->filename = empty($cleanName) ? uniqid('file_') . '.dat' : $cleanName;
}Update the saveToDirectory method to ensure proper filename handling:
private function saveToDirectory($uid, $partIdentifier) {
$array = array();
$array['part'] = $partIdentifier;
$array['file'] = empty($this->filename) ? 'noname_' . uniqid() . '.eml' : $this->filename;
$array['encoding'] = $this->encoding;
$array['path'] = date('Y-m-d'); // Store in a date-based directory
// Ensure the filename is URL-decoded and cleaned
$array['file'] = urldecode($array['file']);
$array['file'] = preg_replace('/[^\p{L}\p{N}\s\-_\.]/u', '_', $array['file']);
return $array;
}Key Changes:
- Decode the filename using the improved decode method.
- Sanitize the filename to remove invalid characters and limit length.
- Use urldecode() to handle cases where the filename contains encoded characters (e.g., from MIME headers).
- Fallback to a unique filename if decoding fails.
3. Improve getFiles Method for Attachment Saving
The getFiles method saves attachments to the filesystem. Ensure it uses the sanitized filename and logs any issues:
public function getFiles($r) {
$this->attachments_dir = rtrim($r['attachments_dir'], '/');
$pullPath = $this->attachments_dir . '/' . $r['path'] . '/' . $r['file'];
// Log the attempt to save the file
$this->errors[] = "Attempting to save attachment: $pullPath";
if (file_exists($pullPath)) {
$this->errors[] = "File already exists: $pullPath";
return false;
}
if (!is_dir($this->attachments_dir)) {
$this->errors[] = "Directory not found: $this->attachments_dir";
return false;
}
if (!is_writable($this->attachments_dir)) {
$this->errors[] = "Directory not writable: $this->attachments_dir";
return false;
}
if (!is_dir($this->attachments_dir . '/' . $r['path'])) {
if (!mkdir($this->attachments_dir . '/' . $r['path'], 0755, true)) {
$this->errors[] = "Failed to create directory: " . $this->attachments_dir . '/' . $r['path'];
return false;
}
}
// Prevent saving dangerous file types
if (preg_match('/\.(php|cgi|exe|dll|mobileconfig)$/i', $r['file'])) {
$this->errors[] = "Blocked dangerous file type: " . $r['file'];
return array("status" => "error", "message" => "Blocked file type", "file" => $r['file']);
}
$filePointer = fopen($pullPath, 'w');
if ($filePointer === false) {
$this->errors[] = "Failed to open file for writing: $pullPath";
return false;
}
switch ($r['encoding']) {
case 3: // base64
$streamFilter = stream_filter_append($filePointer, 'convert.base64-decode', STREAM_FILTER_WRITE);
break;
case 4: // quoted-printable
$streamFilter = stream_filter_append($filePointer, 'convert.quoted-printable-decode', STREAM_FILTER_WRITE);
break;
default:
$streamFilter = null;
}
$success = imap_savebody($this->imapStream, $filePointer, $r['uid'], $r['part'], FT_UID);
if ($streamFilter) {
stream_filter_remove($streamFilter);
}
fclose($filePointer);
if (!$success) {
$this->errors[] = "Failed to save attachment: $pullPath";
return array("status" => "error", "message" => "Failed to save attachment", "file" => $r['file']);
}
return array("status" => "success", "path" => $r['path'], "file" => $r['file']);
}Key Changes:
- Added detailed error logging for each step (directory creation, file writing, etc.).
- Ensured the filename is sanitized and safe.
- Improved error handling for file operations.
4. Enhance Error Logging
To track warnings effectively, ensure the __destruct method logs errors to a file or another logging system:
public function __destruct() {
if (!empty($this->errors)) {
$logFile = __DIR__ . '/../../logs/imap_errors.log';
$logMessage = "[" . date('Y-m-d H:i:s') . "] IMAP Errors:\n";
foreach ($this->errors as $error) {
$logMessage .= $error . "\n";
}
file_put_contents($logFile, $logMessage, FILE_APPEND);
}
if ($this->imapStream) {
imap_close($this->imapStream);
}
}Key Changes:
- Save errors to a log file (imap_errors.log) for debugging.
- Include timestamps and detailed error messages.
- Ensure the IMAP connection is closed properly.
5. Test and Validate
- Test with Problematic Emails: Use emails with known problematic encodings (e.g., Windows-1251, KOI8-R, or malformed headers) to verify that the notices are gone and filenames are decoded correctly.
- Check Filenames: Ensure attachment filenames are readable and properly saved (e.g., no _utf-8_B_... prefixes).
- Monitor Logs: Review the imap_errors.log file to identify any remaining issues with specific emails or charsets.
Additional Recommendations
- Update PHP and Extensions:
- Ensure you're using a recent version of PHP (e.g., 7.4 or later) and that the iconv and mbstring extensions are properly configured.
- Some older PHP versions have bugs in iconv() that can cause issues with certain encodings.
- Validate Input Data:
- Add checks in the processStructure method to log the raw input data when decoding fails, so you can inspect problematic emails.
- Use a Library for MIME Parsing:
- Consider using a library like Zend\Mail or PHP-IMAP for more robust MIME parsing and decoding, as they handle edge cases better than PHP's built-in imap functions.
- Handle Specific Encodings:
- For Russian emails, prioritize encodings like Windows-1251 and KOI8-R in the detectCharset method, as shown in your existing detectRussianEncoding method.
Example Output
After applying these changes, attachment filenames should be properly decoded (e.g., document.pdf instead of _utf-8_B_...), and iconv() notices should be eliminated or logged for debugging. The log file (imap_errors.log) will contain entries like:
[2025-10-24 09:12:11] IMAP Errors:
Unsupported charset 'invalid-charset' in text: =?invalid-charset?B?...
Attempting to save attachment: attachments/2025-10-24/document.pdfThis allows you to track which emails are causing issues and why.
Testing the Fix
To verify the solution:
- Send test emails with Russian text, different encodings (e.g., Windows-1251, KOI8-R, UTF-8), and attachments with complex names.
- Check the attachments directory for correctly named files.
- Monitor the imap_errors.log file for any new errors.
- Ensure no PHP notices appear in the error log.
If you encounter specific cases where the issue persists, share the problematic email headers or content (sanitized if necessary), and I can help refine the solution further.
Let me know if you need additional clarification or assistance with testing!