diff --git a/WpMailCatcher.php b/WpMailCatcher.php index 2a1ef4e..874f15e 100644 --- a/WpMailCatcher.php +++ b/WpMailCatcher.php @@ -6,7 +6,7 @@ Domain Path: /languages Description: Logging your mail will stop you from ever losing your emails again! This fast, lightweight plugin (under 140kb in size!) is also useful for debugging or backing up your messages. Author: James Ward -Version: 2.1.2 +Version: 2.1.3 Author URI: https://jamesward.io Donate link: https://paypal.me/jamesmward */ diff --git a/build/grunt/package.json b/build/grunt/package.json index f7d9753..85cb2a8 100644 --- a/build/grunt/package.json +++ b/build/grunt/package.json @@ -1,6 +1,6 @@ { "name": "WpMailCatcher", - "version": "2.1.1", + "version": "2.1.3", "lang_po_directory": "../../languages", "build_directory": "./..", "dist_directory": "../../assets", diff --git a/readme.txt b/readme.txt index d23ca4a..faa219a 100644 --- a/readme.txt +++ b/readme.txt @@ -2,9 +2,9 @@ Contributors: Wardee Tags: mail logging, email log, email logger, logging, email logging, mail, crm Requires at least: 4.7 -Tested up to: 6.2.2 +Tested up to: 6.2.3 Requires PHP: 7.4 -Stable tag: 2.1.2 +Stable tag: 2.1.3 License: GNU General Public License v3.0 License URI: https://raw.githubusercontent.com/JWardee/wp-mail-catcher/master/LICENSE Donate link: https://paypal.me/jamesmward @@ -94,6 +94,11 @@ Great! Please leave a note in our (GitHub tracker) == Changelog == += 2.1.3 = + +- Fix: Improved HTML email detection +- Fix: Improved XSS filtering + = 2.1.2 = - Fix: Escaping no longer mangles exports diff --git a/src/GeneralHelper.php b/src/GeneralHelper.php index 5af9712..eb29b34 100644 --- a/src/GeneralHelper.php +++ b/src/GeneralHelper.php @@ -27,7 +27,7 @@ class GeneralHelper public static $namespacePrefix; public static $reviewLink; public static $actionNameSpace; - public static $htmlEmailHeader = 'content-type: text/html;'; + public static $htmlEmailHeader = 'content-type: text/html'; public static function setSettings() { @@ -132,7 +132,7 @@ public static function labelToSlug($label) return strtolower($label); } - public static function sanitiseForQuery($value) + public static function sanitiseForDbQuery($value) { switch (gettype($value)) { case ('array'): @@ -148,14 +148,16 @@ public static function sanitiseForQuery($value) return $value; } - public static function sanitiseHtmlspecialchars($input): string + private static function getAllowedTags() { - return htmlspecialchars( - $input, - ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, - null, - false - ); + $tags = wp_kses_allowed_html('post'); + $tags['style'] = []; + return $tags; + } + + public static function filterHtml($value) + { + return wp_kses($value, self::getAllowedTags()); } public static function getAttachmentIdsFromUrl($urls) @@ -166,7 +168,7 @@ public static function getAttachmentIdsFromUrl($urls) global $wpdb; - $urls = self::sanitiseForQuery($urls); + $urls = self::sanitiseForDbQuery($urls); $sql = "SELECT DISTINCT post_id FROM " . $wpdb->prefix . "postmeta diff --git a/src/Loggers/BuddyPress.php b/src/Loggers/BuddyPress.php index f2a8aa7..f9dbe4d 100644 --- a/src/Loggers/BuddyPress.php +++ b/src/Loggers/BuddyPress.php @@ -44,9 +44,9 @@ protected function getTransformedMailArgs(object $bpMail): array return [ 'time' => time(), - 'email_to' => GeneralHelper::arrayToString($tos), - 'subject' => $bpMail->get_subject(), - 'message' => $this->sanitiseInput($bpMail->get_content()), + 'email_to' => GeneralHelper::filterHtml(GeneralHelper::arrayToString($tos)), + 'subject' => GeneralHelper::filterHtml($bpMail->get_subject()), + 'message' => GeneralHelper::filterHtml($bpMail->get_content()), 'backtrace_segment' => json_encode($this->getBacktrace('bp_send_email')), 'status' => 1, 'attachments' => '',//json_encode($this->getAttachmentLocations($args['attachments'])), diff --git a/src/Loggers/LogHelper.php b/src/Loggers/LogHelper.php index 08625b2..e6d1e90 100644 --- a/src/Loggers/LogHelper.php +++ b/src/Loggers/LogHelper.php @@ -142,25 +142,6 @@ protected function getAttachmentLocations($attachments): array return $result; } - protected function sanitiseInput($input): string - { - return htmlspecialchars( - $input, - ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, - null, - false - ); - } - - protected function sanitiseAndRemoveScripts($input): string - { - return preg_replace( - '#(.*?)#is', - '', - GeneralHelper::sanitiseHtmlspecialchars($input) - ); - } - /** * Get the details of the method that originally triggered wp_mail * diff --git a/src/Loggers/WpMail.php b/src/Loggers/WpMail.php index a0c2738..ba95643 100644 --- a/src/Loggers/WpMail.php +++ b/src/Loggers/WpMail.php @@ -42,9 +42,9 @@ protected function getTransformedMailArgs(array $args): array { return [ 'time' => time(), - 'email_to' => $this->sanitiseInput(GeneralHelper::arrayToString($args['to'])), - 'subject' => $this->sanitiseInput($args['subject']), - 'message' => $this->sanitiseAndRemoveScripts($args['message']), + 'email_to' => GeneralHelper::filterHtml(GeneralHelper::arrayToString($args['to'])), + 'subject' => GeneralHelper::filterHtml($args['subject']), + 'message' => GeneralHelper::filterHtml($args['message']), 'backtrace_segment' => json_encode($this->getBacktrace()), 'status' => 1, 'attachments' => json_encode($this->getAttachmentLocations($args['attachments'])), diff --git a/src/MailAdminTable.php b/src/MailAdminTable.php index 95797da..3651ac6 100644 --- a/src/MailAdminTable.php +++ b/src/MailAdminTable.php @@ -31,6 +31,18 @@ public static function getInstance() return self::$instance; } + private function runHtmlSpecialChars($value) + { + $value = GeneralHelper::filterHtml($value); + + return htmlspecialchars( + $value, + ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, + null, + false + ); + } + function column_default($item, $column_name) { switch ($column_name) { @@ -58,7 +70,8 @@ function column_subject($item) ); $subjectDecoded = base64_decode($subjectEncoded); - $subjectDecoded = GeneralHelper::sanitiseHtmlspecialchars($subjectDecoded); + $subjectDecoded = $this->runHtmlSpecialChars($subjectDecoded); + return ' (?) ' . $subjectDecoded . ' @@ -74,14 +87,15 @@ function column_subject($item) $subjectDecoded = quoted_printable_decode($subjectEncoded); $subjectDecoded = base64_decode($subjectEncoded); - $subjectDecoded = GeneralHelper::sanitiseHtmlspecialchars($subjectDecoded); + $subjectDecoded = $this->runHtmlSpecialChars($subjectDecoded); + return ' (?) ' . $subjectDecoded . ' '; } - return GeneralHelper::sanitiseHtmlspecialchars($subject); + return $this->runHtmlSpecialChars($subject); } function column_time($item): string @@ -125,7 +139,9 @@ function column_email_to($item): string 'view' => '' . __('View', 'WpMailCatcher') . '', ]; - return sprintf('%1$s %2$s', GeneralHelper::sanitiseHtmlspecialchars($item['email_to']), $this->row_actions($actions)); + $emailTo = $this->runHtmlSpecialChars($item['email_to']); + + return sprintf('%1$s %2$s', $emailTo, $this->row_actions($actions)); } function column_status($item): string diff --git a/src/Models/Logs.php b/src/Models/Logs.php index 933c9c7..7eddf65 100644 --- a/src/Models/Logs.php +++ b/src/Models/Logs.php @@ -84,7 +84,7 @@ public static function get(array $args = []) /** * Sanitise each value in the array */ - array_walk_recursive($args, 'WpMailCatcher\GeneralHelper::sanitiseForQuery'); + array_walk_recursive($args, 'WpMailCatcher\GeneralHelper::sanitiseForDbQuery'); $sql = "SELECT " . implode(',', $columnsToSelect) . " FROM " . $wpdb->prefix . GeneralHelper::$tableName . " "; @@ -180,23 +180,11 @@ private static function dbResultTransform($results, $args = []) // Otherwise resort to the original method } elseif (isset($result['additional_headers'])) { $result['is_html'] = GeneralHelper::doesArrayContainSubString( - $result['additional_headers'], - GeneralHelper::$htmlEmailHeader + str_replace(' ', '', $result['additional_headers']), + str_replace(' ', '', GeneralHelper::$htmlEmailHeader) ); } - if (isset($result['message'])) { - $result['message'] = stripslashes(htmlspecialchars_decode($result['message'])); - } - - if (isset($result['subject'])) { - $result['subject'] = stripslashes(htmlspecialchars_decode($result['subject'])); - } - - if (isset($result['email_to'])) { - $result['email_to'] = stripslashes(htmlspecialchars_decode($result['email_to'])); - } - if (!empty($result['attachments'])) { $result['attachments'] = json_decode($result['attachments'], true); @@ -238,7 +226,7 @@ public static function delete($ids) global $wpdb; $ids = GeneralHelper::arrayToString($ids); - $ids = GeneralHelper::sanitiseForQuery($ids); + $ids = GeneralHelper::sanitiseForDbQuery($ids); $wpdb->query("DELETE FROM " . $wpdb->prefix . GeneralHelper::$tableName . " WHERE id IN(" . $ids . ")"); diff --git a/src/Models/Mail.php b/src/Models/Mail.php index 5b594d7..198ebba 100644 --- a/src/Models/Mail.php +++ b/src/Models/Mail.php @@ -21,6 +21,14 @@ public static function resend($ids) add_filter('wp_mail_content_type', $updateContentType, self::$contentTypeFilterPriority); + if (isset($log['message'])) { + $log['message'] = GeneralHelper::filterHtml($log['message']); + } + + if (isset($log['subject'])) { + $log['subject'] = GeneralHelper::filterHtml($log['subject']); + } + wp_mail( $log['email_to'], $log['subject'], @@ -54,6 +62,14 @@ public static function export($ids, $forceBrowserDownload = true) return in_array($key, GeneralHelper::$csvExportLegalColumns); }, ARRAY_FILTER_USE_KEY); + if (isset($log['message'])) { + $log['message'] = GeneralHelper::filterHtml($log['message']); + } + + if (isset($log['subject'])) { + $log['subject'] = GeneralHelper::filterHtml($log['subject']); + } + if (isset($log['attachments']) && !empty($log['attachments']) && is_array($log['attachments'])) { $log['attachments'] = array_column($log['attachments'], 'url'); $log['attachments'] = GeneralHelper::arrayToString( diff --git a/src/Views/HtmlMessage.php b/src/Views/HtmlMessage.php index dc6a439..6bec584 100644 --- a/src/Views/HtmlMessage.php +++ b/src/Views/HtmlMessage.php @@ -1,3 +1,5 @@ alert("Hello");'; - $escapedSubject = GeneralHelper::sanitiseHtmlspecialchars($subjectBase); - $subject = $mailTable->column_subject(['subject' => $subjectBase]); - - $this->assertEquals($subject, $escapedSubject); - } }