From 89f19004cfb76995b4380b36efffbb97d3846f3f Mon Sep 17 00:00:00 2001 From: pdontthink Date: Fri, 3 Apr 2009 08:31:17 +0000 Subject: [PATCH] Encode outgoing attachments that have lines longer than allowed per RFC. Otherwise, they can be corrupted when artificially (forced) folding - unfolding typically produces an extra space at the fold in most MUAs. This fixes #2226470 and #1473714. Thanks to Kelly Fallon. git-svn-id: https://svn.code.sf.net/p/squirrelmail/code/trunk/squirrelmail@13512 7612ce4b-ef26-0410-bec9-ea0150e637f0 --- class/deliver/Deliver.class.php | 76 ++++++++++++++++++++++++++++----- functions/files.php | 30 +++++++++++++ 2 files changed, 95 insertions(+), 11 deletions(-) diff --git a/class/deliver/Deliver.class.php b/class/deliver/Deliver.class.php index 80f62976..db9e3556 100644 --- a/class/deliver/Deliver.class.php +++ b/class/deliver/Deliver.class.php @@ -282,17 +282,52 @@ class Deliver { global $username, $attachment_dir; $hashed_attachment_dir = getHashedDir($username, $attachment_dir); $filename = $message->att_local_name; + + // inspect attached file for lines longer than allowed by RFC, + // in which case we'll be using base64 encoding (so we can split + // the lines up without corrupting them) instead of 8bit unencoded... + // (see RFC 2822/2.1.1) + // + // using 990 because someone somewhere is folding lines at + // 990 instead of 998 and I'm too lazy to find who it is + // + $file_has_long_lines = file_has_long_lines($hashed_attachment_dir + . '/' . $filename, 990); + $file = fopen ($hashed_attachment_dir . '/' . $filename, 'rb'); - while ($body_part = fgets($file, 4096)) { - // remove NUL characters - $body_part = str_replace("\0",'',$body_part); - $length += $this->clean_crlf($body_part); - if ($stream) { - $this->preWriteToStream($body_part); - $this->writeToStream($stream, $body_part); + + // long lines were found, need to use base64 encoding + // + if ($file_has_long_lines) { + while ($tmp = fread($file, 570)) { + $body_part = chunk_split(base64_encode($tmp)); + // Up to 4.3.10 chunk_split always appends a newline, + // while in 4.3.11 it doesn't if the string to split + // is shorter than the chunk length. + if( substr($body_part, -1 , 1 ) != "\n" ) + $body_part .= "\n"; + $length += $this->clean_crlf($body_part); + if ($stream) { + $this->writeToStream($stream, $body_part); + } } - $last = $body_part; } + + // no excessively long lines - normal 8bit + // + else { + while ($body_part = fgets($file, 4096)) { + // remove NUL characters + $body_part = str_replace("\0",'',$body_part); + $length += $this->clean_crlf($body_part); + if ($stream) { + $this->preWriteToStream($body_part); + $this->writeToStream($stream, $body_part); + } + $last = $body_part; + } + } + fclose($file); } break; @@ -461,10 +496,29 @@ class Deliver { if ($mime_header->encoding) { $header[] = 'Content-Transfer-Encoding: ' . $mime_header->encoding . $rn; } else { - if ($mime_header->type0 == 'text' || $mime_header->type0 == 'message') { - $header[] = 'Content-Transfer-Encoding: 8bit' . $rn; - } else if ($mime_header->type0 == 'multipart' || $mime_header->type0 == 'alternative') { + + // inspect attached file for lines longer than allowed by RFC, + // in which case we'll be using base64 encoding (so we can split + // the lines up without corrupting them) instead of 8bit unencoded... + // (see RFC 2822/2.1.1) + // + if (!empty($message->att_local_name)) { // is this redundant? I have no idea + global $username, $attachment_dir; + $hashed_attachment_dir = getHashedDir($username, $attachment_dir); + $filename = $hashed_attachment_dir . '/' . $message->att_local_name; + + // using 990 because someone somewhere is folding lines at + // 990 instead of 998 and I'm too lazy to find who it is + // + $file_has_long_lines = file_has_long_lines($filename, 990); + } else + $file_has_long_lines = FALSE; + + if ($mime_header->type0 == 'multipart' || $mime_header->type0 == 'alternative') { /* no-op; no encoding needed */ + } else if (($mime_header->type0 == 'text' || $mime_header->type0 == 'message') + && !$file_has_long_lines) { + $header[] = 'Content-Transfer-Encoding: 8bit' . $rn; } else { $header[] = 'Content-Transfer-Encoding: base64' . $rn; } diff --git a/functions/files.php b/functions/files.php index 0a297eaa..69d3ddfd 100644 --- a/functions/files.php +++ b/functions/files.php @@ -263,3 +263,33 @@ function list_files($directory_path, $extensions='', $return_filenames_only=TRUE } +/** + * Determine if there are lines in a file longer than a given length + * + * @param string $filename The full file path of the file to inspect + * @param int $max_length If any lines in the file are GREATER THAN + * this number, this function returns TRUE. + * + * @return boolean TRUE as explained above, otherwise, (no long lines + * found) FALSE is returned. + * + */ +function file_has_long_lines($filename, $max_length) { + + $FILE = @fopen($filename, 'rb'); + + if ($FILE) { + while (!feof($FILE)) { + $buffer = fgets($FILE, 4096); + if (strlen($buffer) > $max_length) { + fclose($FILE); + return TRUE; + } + } + fclose($FILE); + } + + return FALSE; +} + + -- 2.25.1