Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 63 additions & 1 deletion modules/imap/hm-ews.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ public function get_capability() {
return '';
}

public function supports_dsn() {
// For EWS, "receipt-like" behavior should use EWS flags instead:
// - IsDeliveryReceiptRequested (already used in send_message)
// - IsReadReceiptRequested (can be added similarly if needed)
// TODO: If we decide to expose receipt options in the UI for EWS profiles,
// map those UI controls to the EWS flags above and keep DSN disabled here
// so that SMTP-specific DSN UI remains hidden for EWS connections.
return false;
}

public function get_folders($folder = null, $only_subscribed = false, $unsubscribed_folders = [], $with_input = false) {
$result = [];
if (empty($folder)) {
Expand Down Expand Up @@ -267,6 +277,8 @@ public function send_message($from, $recipients, $message, $delivery_receipt = f
$msg = new Type\MessageType();
$msg->setFrom($from);
$msg->setToRecipients($recipients);

$message = $this->use_iso_if_safe($message);
$mimeContent = Type\MimeContentType::buildFromArray([
'CharacterSet' => 'UTF-8',
'_' => base64_encode($message)
Expand All @@ -285,6 +297,44 @@ public function send_message($from, $recipients, $message, $delivery_receipt = f
}
}

/**
* Convert UTF-8 to ISO-8859-1 if the content is safe to do so.
* This prevents Exchange from converting quoted-printable to base64 encoding.
*
* @param string $message The MIME message from Hm_MIME_Msg
* @return string Modified message with ISO-8859-1 where safe
*/
private function use_iso_if_safe($message) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole function seems very hacky and low-level. Why do you need to convert a universally available encoding like UTF-8 to something that doesn't even support more than a few hundreds of characters?
If you experience a specific edge case with Exchange, let's discuss what can be done but this conversion certainly looks very suspicious to do on the client side.

// Use regex to find and replace each text part's charset + body
return preg_replace_callback(
'/Content-Type:\s*text\/[^;]+;\s*charset="?UTF-8"?[^\r\n]*\r\n' .
'Content-Transfer-Encoding:\s*quoted-printable\r\n\r\n' .
'(.*?)(?=\r\n--|\r\n\r\n--|\z)/s',
function($match) {
// Decode quoted-printable body
$decoded = quoted_printable_decode($match[1]);

// This will fail for characters not in ISO-8859-1 (e.g., €, emojis, Cyrillic).
$converted = @iconv('UTF-8', 'ISO-8859-1', $decoded);
if ($converted === false) {
return $match[0]; // Keep UTF-8 (contains €, emojis, Cyrillic, etc.)
}

$backToUtf8 = @iconv('ISO-8859-1', 'UTF-8', $converted);
if ($backToUtf8 === false || $decoded !== $backToUtf8) {
return $match[0]; // Not exact match, keep UTF-8
}

// Safe to convert: replace charset and re-encode
$newHeader = preg_replace('/charset="?UTF-8"?/i', 'charset="iso-8859-1"', $match[0]);
$newBody = quoted_printable_encode($converted);

return str_replace($match[1], $newBody, $newHeader);
},
$message
);
}

public function store_message($folder, $message, $seen = true, $draft = false) {
try {

Expand Down Expand Up @@ -1040,9 +1090,21 @@ protected function parse_mime_part($part, &$struct, $part_num) {
$struct[$part_num]['md5'] = '';
$struct[$part_num]['disposition'] = $part->getContentDisposition();

if ($filename = $part->getFilename()) {
$filename = $part->getFilename();
if (! $filename) {
$filename = $part->getHeaderParameter('Content-Type', 'name');
}
if (! $filename) {
$filename = $part->getHeaderParameter('Content-Disposition', 'filename');
}
if ($filename) {
$struct[$part_num]['file_attributes'] = ['filename' => $filename];

if (! isset($struct[$part_num]['attributes']) || ! is_array($struct[$part_num]['attributes'])) {
$struct[$part_num]['attributes'] = [];
}
$struct[$part_num]['attributes']['name'] = $filename;

if ($part->getContentDisposition() == 'attachment') {
$struct[$part_num]['file_attributes']['attachment'] = true;
}
Expand Down
Loading