Skip to content
Permalink
Browse files

Mailer errors: Show error location.

  • Loading branch information
kohler committed Feb 9, 2020
1 parent de1dde1 commit 0229982f58dff15e72d08c5af8a920d6eb9135d8
Showing with 98 additions and 32 deletions.
  1. +92 −28 lib/mailer.php
  2. +3 −2 mail.php
  3. +3 −2 src/settingvalues.php
@@ -626,11 +626,12 @@ function make_preparation($template, $rest = []) {
if (($hdr = $mimetext->encode_email_header($field . ": ", $text))) {
$prep->headers[$lcfield] = $hdr . $eol;
} else {
$prep->errors[$lcfield] = $text;
$prep->errors[$lcfield] = $mimetext->unparse_error();
error_log("mailer error on $lcfield: $text");
$prep->sendable = false;
if (!get($rest, "no_error_quit"))
Conf::msg_error("$field destination “<samp>" . htmlspecialchars($text) . "</samp>” isn't a valid email list.");
if (!get($rest, "no_error_quit")) {
Conf::msg_error("Malformed $field field:" . $prep->errors[$lcfield]);
}
}
}
}
@@ -678,6 +679,9 @@ function warnings() {

class MimeText {
public $in;
public $errorpos;
public $errorlen;
public $errortext;
public $out;
public $linelen;

@@ -687,6 +691,9 @@ function reset($header, $str) {
} else {
$this->in = $str;
}
$this->errorpos = false;
$this->errorlen = false;
$this->errortext = false;
$this->out = $header;
$this->linelen = strlen($header);
}
@@ -771,40 +778,79 @@ private function append($str, $utf8) {

function encode_email_header($header, $str) {
$this->reset($header, $str);
if (strpos($this->in, chr(0xE2)) !== false) {
$this->in = str_replace(["“", "”"], ["\"", "\""], $this->in);
}
$str = $this->in;
$inlen = strlen($str);

// separate $str into emails, quote each separately
while (true) {
$str = preg_replace('/\A[,;\s]+/', "", $str);
if ($str === "") {
return $this->out;
}

// try three types of match in turn:
// 1. name <email> [RFC 822]
$match = preg_match("/\\A[,;\\s]*((?:(?:\"(?:[^\"\\\\]|\\\\.)*\"|[^\\s\\000-\\037()[\\]<>@,;:\\\\\".]+)\\s*?)*)\\s*<\\s*(.*?)\\s*>\\s*(.*)\\z/s", $str, $m);
// 2. name including periods but no quotes <email> (canonicalize)
if (!$match) {
$match = preg_match("/\\A[,;\\s]*((?:[^\\s\\000-\\037()[\\]<>@,;:\\\\\"]+\\s*?)*)\\s*<\\s*(.*?)\\s*>\\s*(.*)\\z/s", $str, $m);
if ($match)
$m[1] = "\"$m[1]\"";
}
// 3. bare email
if (!$match) {
$match = preg_match("/\\A[,;\\s]*()<?\\s*([^\\s\\000-\\037()[\\]<>,;:\\\\\"]+)\\s*>?\\s*(.*)\\z/s", $str, $m);
}
// otherwise, fail
if (!$match) {
break;
// 2. name including periods and “\'” but no quotes <email>
if (preg_match('/\A((?:(?:"(?:[^"\\\\]|\\\\.)*"|[^\s\000-\037()[\\]<>@,;:\\\\".]+)\s*?)*)\s*<\s*(.*?)(\s*>\s*)(.*)\z/s', $str, $m)
|| preg_match('/\A((?:[^\000-\037()[\\]<>@,;:\\\\"]|\\\\\')+?)\s*<\s*(.*?)(\s*>\s*)(.*)\z/s', $str, $m)) {
$emailpos = $inlen - strlen($m[4]) - strlen($m[3]) - strlen($m[2]);
$name = $m[1];
$email = $m[2];
$str = $m[4];
} else if (preg_match('/\A<\s*([^\s\000-\037()[\\]<>,;:\\\\"]+)(\s*>?\s*)(.*)\z/s', $str, $m)) {
$emailpos = $inlen - strlen($m[3]) - strlen($m[2]) - strlen($m[1]);
$name = "";
$email = $m[1];
$str = $m[3];
} else if (preg_match('/\A(none|hidden|[^\s\000-\037()[\\]<>,;:\\\\"]+@[^\s\000-\037()[\\]<>,;:\\\\"]+)(\s*)(.*)\z/s', $str, $m)) {
$emailpos = $inlen - strlen($m[3]) - strlen($m[2]) - strlen($m[1]);
$name = "";
$email = $m[1];
$str = $m[3];
} else {
$this->errorpos = $inlen - strlen($str);
if (preg_match('/[\s<>@]/', $str)) {
$this->errortext = "Invalid destination (possible quoting problem).";
} else {
$this->errortext = "Invalid email address.";
}
return false;
}

list($name, $email, $str) = array($m[1], $m[2], $m[3]);
if (strpos($email, "@") !== false && !validate_email($email)) {
// Validate email
if (!validate_email($email)
&& $email !== "none"
&& $email !== "hidden") {
if (strpos($email, "@") === false) {
error_log("mailer is going to bail out on something it didn't previously $email");
}
$this->errorpos = $emailpos;
$this->errorlen = strlen($email);
$this->errortext = "Invalid email address.";
return false;
}
if ($str !== "" && $str[0] !== "," && $str[0] !== ";") {
return false;

// Validate rest of string
if ($str !== ""
&& $str[0] !== ","
&& $str[0] !== ";") {
if ($this->errorpos === false) {
$this->errorpos = $inlen - strlen($str);
$this->errortext = "Destinations should be separated with commas.";
}
if (!preg_match('/\A<?\s*([^\s>]*)/', $str, $m)
|| !validate_email($m[1])) {
return false;
}
}

// Append email
if ($email === "none" || $email === "hidden") {
continue;
}

if ($this->out !== $header) {
$this->out .= ", ";
$this->linelen += 2;
@@ -838,12 +884,6 @@ function encode_email_header($header, $str) {
$this->append(" <$email>", false);
}
}

if (preg_match('/\A[\s,;]*\z/', $str)) {
return $this->out;
} else {
return false;
}
}

function encode_header($header, $str) {
@@ -852,6 +892,30 @@ function encode_header($header, $str) {
return $this->out;
}

function unparse_error() {
if ($this->errorpos !== false) {
$str = str_replace("\t", " ", $this->in);
$t = '<pre>';
if ($this->errorlen) {
$t .= htmlspecialchars(substr($str, 0, $this->errorpos))
. '<span class="is-error">'
. htmlspecialchars(substr($str, $this->errorpos, $this->errorlen))
. '</span>'
. htmlspecialchars(substr($str, $this->errorpos + $this->errorlen));
$arrow = str_repeat("↑", $this->errorlen);
} else {
$t .= htmlspecialchars($str);
$arrow = "↑";
}
$space = str_repeat(" ", $this->errorpos);
return $t . "\n"
. $space . '<span class="is-error">' . $arrow . '</span>' . "\n"
. $space . '<span class="is-error">' . htmlspecialchars($this->errortext) . '</span></pre>';
} else {
return false;
}
}

static function chr_hexdec_callback($m) {
return chr(hexdec($m[1]));
}
@@ -489,9 +489,10 @@ private function run() {
foreach ($prep->errors as $lcfield => $hline) {
$reqfield = ($lcfield == "reply-to" ? "replyto" : $lcfield);
Ht::error_at($reqfield);
$emsg = Mailer::$email_fields[$lcfield] . " destination isn’t a valid email list: <blockquote><samp>" . htmlspecialchars($hline) . "</samp></blockquote> Make sure email address are separated by commas; put names in \"quotes\" and email addresses in &lt;angle brackets&gt;.";
if (!isset($preperrors[$emsg]))
$emsg = "Malformed " . Mailer::$email_fields[$lcfield] . " field: " . $hline . " Put names in \"double quotes\" and email addresses in &lt;angle brackets&gt;, and separate destinations with commas.";
if (!isset($preperrors[$emsg])) {
Conf::msg_error($emsg);
}
$preperrors[$emsg] = true;
}
} else if ($this->process_prep($prep, $last_prep, $row)) {
@@ -1211,11 +1211,12 @@ function parse_value(Si $si) {
return $v;
$err = $tagger->error_html;
} else if ($si->type === "emailheader") {
$v = (new MimeText)->encode_email_header("", $v);
$mt = new MimeText;
$v = $mt->encode_email_header("", $v);
if ($v !== false) {
return ($v == "" ? "" : MimeText::decode_header($v));
}
$err = "Should be an email header.";
$err = "Malformed destination list: " . $mt->unparse_error();
} else if ($si->type === "emailstring") {
$v = trim($v);
if ($v === "" && $si->optional)

0 comments on commit 0229982

Please sign in to comment.
You can’t perform that action at this time.