Stripping received email headers in PHP - php

I have a PHP script which has the source of an email. I aim to split the headers into variables $To and $From
The issue comes when trying to split the to and from strings up. What I need the script to do is take
From: John <john#somesite.com>
To: Susy <susy#mysite.com>, Steven <steven#somesite.com>, Mary <mary#mysite.com>
and return only the from address and the to addresses which are on my site. I.e.
$From = 'john#somesite.com';
$To = array('susy#mysite.com', 'mary#mysite.com');
So the code needs to turn a string of email addresses into an array and then filter out the ones from other sites. It's the first part that is proving difficult because of the different ways an email address can be listed in a header.

Edit
As you've now specified that you have the headers as a string but you actually need to parse the addresses from it, there is no need to reinvent the wheel:
imap_rfc822_parse_headersDocs
imap_rfc822_parse_adrlistDocs
These two functions will do the job for you, the last one will give you an array with objects that have the email addresses pre-parsed, so you can easily take decisions based on the host.
It was not specifically clear to me what your actual problem is from your question.
As long as you are concerned about filtering a string containing one email address (cast it to array) or an array containing one or multiple addresses:
To filter the existing array of email-addresses you can use a simple array mapping function that will set any email that is not matching your site's host to FALSE and then filter the array copy Demo:
$addresses = array(
'mary#mysite.com',
'mary#othersite.com',
);
$myhost = 'mysite.com';
$filtered = array_map(function($email) use ($myhost) {
$host = '#'.$myhost;
$is = substr($email, -strlen($host)) === $host;
return $is ? $email : FALSE;
}, $addresses);
$filtered = array_filter($filtered);
print_r($filtered);
This codes makes the assumption that you have the email addresses already gathered. You have not specified how you parse the headers already in your question, so it's actually unknown with which data you are dealing, so I opted to start from the end of your problem. Let us know if you have more information available.

<?php
$k= "......Subject: Write the program any of your favorite language whenever if you feel
you are free
From: Vinay Kumar <vinaykumarjg#gmail.com>
To: msnjsk#gmail.com, mithunsatish#gmail.com,Susy <susy#mysite.com>, Steven <steven#somesite.com>, Mary <mary#mysite.com>
Content-Type: multipart/alternative; boundary=bcaec53964ec5eed2604acd0e09a
--bcaec53964ec5eed2604acd0e09a
Content-Type: text/plain; charset=ISO-8859-1
.......";
if(preg_match('/From:(?P<text>.+)\r\n/', $k, $matches1))
{
if(preg_match('/(?P<from>([a-z0-9])(([-a-z0-9._])*([a-z0-9]))*\#([a-z0-9])' .'(([a-z0-9-])*([a-z0-9]))+' . '(\.([a-z0-9])([-a-z0-9_-])?([a-z0-9])+)+)/', $matches1['text'],$sender ))
{
print_r($sender['from']);
}
}
if(preg_match('/To:(?P<text>.+)\r\n/', $k, $matches2))
{
if(preg_match_all('/(?P<to>([a-z0-9])(([-a-z0-9._])*([a-z0-9]))*\#([a-z0-9])' .
'(([a-z0-9-])*([a-z0-9]))+' . '(\.([a-z0-9])([-a-z0-9_-])?([a-z0-9])+)+)/', $matches2['text'], $reciever))
{
if(isset($reciever['to']))
{
print_r($reciever['to']);
}
}
}
to get the subject:
if(preg_match('/Subject:(?P<subject>.+)\r\n/', $k, $subject))
{
print_r($subject['subject']);
}

preg_match_all("/<([^><]+)/", $headers, $matches);
print_r($matches[1]);
Output:
Array
(
[0] => john#somesite.com
[1] => susy#mysite.com
[2] => steven#somesite.com
[3] => mary#mysite.com
)
The first one is always From email address.
Live demo

Related

Quoted-printable Email Subject lines not getting decoded by email clients

I've written a WordPress plugin which sends out new post notifications. There is a setting to convert subject lines from html entites to quoted-printable so they'll display in UTF-8 on any email client. A few weeks ago I started getting reports that the quoted-printable subject line was being kept as-is instead of being decoded.
Sample Subject header:
Subject: =?UTF-8?Q?[Pranamanasyoga]=20Foro=20Pranamanasyoga=20:=20estr?= =?UTF-8?Q?=C3=A9s=20y=20resilencia?=
I cannot replicate it locally and have not been able to find any common denominators between reporters.
The code that generates the quoted-printable line is this:
<?php
$enc = iconv_get_encoding( 'internal_encoding' ); // this is UTF-8
$preferences = ['input-charset' => $enc, 'output-charset' => "UTF-8", 'scheme' => 'Q' ];
$filtered_subject = '[Pranamanasyoga] Foro Pranamanasyoga : estrés y resilencia';
$encoded = iconv_mime_encode( 'Subject', html_entity_decode( $filtered_subject ), $preferences );
$encoded = substr( $encoded, strlen( 'Subject: ' ) );
If I try decoding it, it works fine:
$decoded = iconv_mime_decode($encoded, 0, "UTF-8");
var_dump(['encoded' => $encoded, 'decoded' => $decoded])."\n";
Result:
array(2) {
["encoded"]=>
string(102) "=?UTF-8?Q?[Pranamanasyoga]=20Foro=20Pranamanasyoga=20:=20estr?=
=?UTF-8?Q?=C3=A9s=20y=20resilencia?="
["decoded"]=>
string(59) "[Pranamanasyoga] Foro Pranamanasyoga : estrés y resilencia"
}
One thing I noticed, but think is not related is that my code actually adds a newline before the second =?UTF-8?Q? piece and the email subject header does not have it. Decoding the strings with- and without the newline works the same.
Does anyone have ideas/suggestions on what may be causing the email clients (Gmail included) to display the string as-is, instead of decoding it to UTF-8?
P.S. While writing this I saw a suggestion to use mb_encode_mimeheader() in a different thread. It seems to work well with iconv_mime_decode() in my test code, but the output string is indeed different from the original one:
[Pranamanasyoga] Foro Pranamanasyoga : =?UTF-8?Q?estr=C3=A9s=20y=20resile?=
=?UTF-8?Q?ncia?=
Could it be that email clients would prefer this format over the original one?

Swiftmailer rejecting valid email addresses if and only if submitted in array

I'm pulling users' phone numbers from an s2member database and appending the wireless carrier domain name required to send SMS texts via email (Swift Mailer). So to create the array of email addresses I use the following (the function cellmap below just replaces the users' wireless carrier with the correct domain to append):
$matches = array();
if (is_array ($users) && count ($users) > 0) {
foreach ($users as $user) {
$user = new WP_User ($user->ID);
$matches[] = "'" . get_user_field("cell_no", $user->ID) . cellmap(get_user_field("cell_carrier",$user->ID)) . "'";
}
}
The resulting array looks like this, for an example of 4 matched users:
Array
(
[0] => '1234567891#vtext.com'
[1] => '3216549871#vtext.com'
[2] => '9876543211#vtext.com'
[3] => '6543219877#vtext.com'
)
Then I implode to create a string to use as the "To" field for Swift Mailer:
$cell_list = implode(", ", $matches);
which results in this (numbers are made up):
'1234567891#vtext.com', '3216549871#vtext.com', '9876543211#vtext.com', '6543219877#vtext.com'
Now I pass that to Swift Mailer like so:
$outgoing_message = Swift_Message::newInstance('')
->setFrom(array('no-reply#mydomain.com' => 'Mydomain'))
->setTo($cell_list)
->setBody($message);
// Send the message
$result = $mailer->send($outgoing_message);
And I get this error (ignore the phone numbers: they're made up, but correspond to the right ones in practice):
PHP Fatal error: Uncaught exception 'Swift_RfcComplianceException' with message 'Address in mailbox given ['123456789#vtext.com', '987654321#vtext.com', '5555551212#vtext.com', '321654987#vtext.com'] does not comply with RFC 2822, 3.6.2.'
I can successfully send any one of these emails individually via Swift Mailer, but when they appear as an array I always get the above error. I have tried applying trim to the individual addresses and the entire resulting string. A print_r of the array does not show any non-printable characters. I have tried various combinations of ->setTo($cell_list), ->setTo(array('$cell_list')) and anything else that might work, to no avail. I have tried replacing comma with semicolon in the list, and removing the single quotes around each address. As far as I can tell, the string of email addresses is in the exact format as shown in the Swift Mailer documentation. For the life of me I can't figure this out.
According to Swiftmailer documentation, setTo takes either a single email address or an array of email addresses as parameter.
Your code:
$cell_list = implode(", ", $matches);
$outgoing_message = Swift_Message::newInstance('')
->setFrom(array('no-reply#mydomain.com' => 'Mydomain'))
->setTo($cell_list)
->setBody($message);
Using implode() puts all the email addresses in a single string of text.
I suggest not imploding $matches:
$outgoing_message = Swift_Message::newInstance('')
->setFrom(array('no-reply#mydomain.com' => 'Mydomain'))
->setTo($matches)
->setBody($message);

how to handle this: Get all rows with the same email address, operate on ids, email them once

Honestly, I do not even know how to ask this question since I also do not have any plan on where to start.
Say I have the following in mySQL:
id | URL | email |
---|------------|---------|
1 | google.com | a#a.com |
2 | bing.com | a#a.com |
3 | yahoo.com | b#a.com |
My original plan is:
do something on each URL (analyze it), afterwards send an email corresponding to such URL.
that makes:
analyze URL 1, send an email to a#a.com
analyze URL 2, send an email to a#a.com
analyze URL 3, send an email to b#a.com
why should I still do such spamming act when, I can just send an email to a#a.com will all the analysis.
I am trying to be less-spammer as possible, that is instead of , my refined plan then is:
SELECT email FROM table
--> a#a.com, a#a.com, b#a.com
Trim the output such that there will not be duplicate email address
--> a#a.com, b#a.com
Put them in an array or itenary
For each element in array SELECT * FROM table WHERE array()
For each output(the elements), do the analysis
Put the analysis in an array
So say, all analysis is complete
Email the analysis to the parent email.
Am I doing it right?
Is my plan efficient?
Not memory consuming?
Any better plan to handle this?
Fetch all, group by mail, send later. Useful to separate logics.
// Fetch all
$users = $db->fetch('SELECT * FROM table');
// Group by mail
$mails = array();
forach ( $users as $user ) {
$mail = ... analyze stuff with $user->url
$mails[$user->mail][] = $mail;
}
// Send
foreach ( $mails as $address => $content ) {
$subject = count($content) . ' urls analyzed';
$body = implode("\n\n", $content);
mail($address, $subject, $body);
}
Explanation:
The -> are how you pick a property of an object. Most database classes return rows as objects, not associative arrays. (I think most, maybe not.) So if $users is an array of objects, $user will be an object and its mail would be in $user->mail. If they're assoc arrays, $user will be an array and its mail will be in $user['mail']. Both have their advantages.
The 'group by mail' part creates many arrays in $mails, grouped by mail. The fetch order (from the db) doesn't matter, because PHP stacks the results per mail, so $mails would be something like:
array(
'a#a.com' => array(
0 => 'Analytics stuff here...',
1 => 'Analytics stuff here...',
),
'b#a.com' => array(
0 => 'Analytics stuff here...',
),
)
and that's a very neat, compact array with everything you need to send mail. And you could pass it onto another layer of your app. The mail-send-layer, or the html-template-layer, or whatever.
Use ORDER BY to have the entries grouped by emails like so
SELECT * FROM table ORDER BY email
Then inside of each entry loop
$currEmail = $row['email'];
if ($prevEmail != $currEmail) {
// send $mail to $prevMail
$mail = ''; // reset the contents of the e-mail to nothing
}
$mail .= '
// analysis of whatever you are doing
';
$prevEmail = $currEmail;
Don't forget to send the contents of $mail after the loop finishes as well! Since the mail sending condition is triggered on the next row, the loop will finish with a non-empty $mail every time.
This fetches everything with just one query, allows you to "build" your analysis without spamming the same e-mail address with each analysis item, and saves memory, database queries, and processing time by not using arrays or multiple queries.

Drupal 7 drupal_mail stripping out HTML?

I have a custom module that I'm trying to generate an HTML email from using the drupal_mail function (D7). Mail is coming through, and even shows text/html, however something somewhere appears to be stripping out the HTMl before it gets to an inbox.
First, in a function I'm building my title/body/other vars and sending to a custom function:
$body = "We thought you'd like to know that ".$fullname." has marked your project as completed.
<br /><br />
Please visit the link at <a href='http://".$_SERVER['HTTP_HOST']."/survey/customer/".$customer[0]->unique_id."'>http://".$_SERVER['HTTP_HOST']."/survey/customer/".$customer[0]->unique_id."</a> to take the survey.";
$returnMail = latch_send_mail('pro_realized',$customer[0]->customer_email,$user->mail,$title,$body);
Then I have the latch_mail latch_send_email functions:
function latch_mail($key, &$message, $params) {
$headers = array(
'MIME-Version' => '1.0',
'Content-Type' => 'text/html; charset=UTF-8; format=flowed',
'Content-Transfer-Encoding' => '8Bit',
'X-Mailer' => 'Drupal'
);
foreach ($headers as $key => $value) {
$message['headers'][$key] = $value;
}
$message['body'][] = $params['body'];
$message['subject'] = $params['subject'];
}
and
function latch_send_mail($key,$to,$from,$title,$body,$headers='') {
$params['body']=$body;
$params['subject'] = t($title);
return drupal_mail('latch', $key, $to, language_default(), $params, $from,TRUE);
}
I would expect the emails to come through with my a tags and br tags, but it comes through like this:
We thought you'd like to know that John Doe has marked your project as completed. Please visit the link at http://latch.local/survey/customer/34c91b8883cd70b32c65feb7adf9c393 [1] to take the survey. [1] http://latch.local/survey/customer/34c91b8883cd70b32c65feb7adf9c393
Somehow it's taking my links and turning them into footnotes while removing the br tags completely.
Any help you can provide would be appreciated. Thanks!
Out of the box, Drupal can't send HTML email. In order for Drupal to support HTML email you need the HTML Mail module. http://drupal.org/project/htmlmail Once you have that all HTML should be sent as such.
Here's an alternative method with a complete explenation. First of all, install and enable the Mime Mail module. You can read the README.txt for complete instructions on how to use it. I'll give you the short version.
You need to enable Mime Mail for your module. You can do this using hook_enable or hook_update_N in example.install:
function example_enable() {
mailsystem_set(array(
'example_examplekey' => 'MimeMailSystem',
));
}
When you go to admin/config/system/mailsystem you will see that a new entry has been added for your module:
Example module (examplekey key) class
MimeMailSystem
Now you don't need to specificy any text/html headers anymore, Mime Mail takes care of this. So you don't need this:
$headers['Content-Type'] = ...
If you want, you can add $message['plaintext'] to your mail for a non-HTML alternative, but this is not required.
That's it!

OpenID check_authentication not working

I'm trying to get a check_authentication response working, but so far, all consumers reject it and say that my server denied check_authentication.
This is the GET and POST data that my server file receives:
$_GET:
Array
(
[mode] => profile
[username] => hachque
[domain] => roket-enterprises.com
)
$_POST:
Array
(
[openid_assoc_handle] => {HMAC-SHA1}{4b00d7b2}{vo1FEQ==}
[openid_identity] => http://www.roket-enterprises.com/openaccount/openid:hachque
[openid_mode] => check_authentication
[openid_response_nonce] => 2009-11-16T04:40:18Zrrz8R4
[openid_return_to] => http://openiddirectory.com:80/openidauth/id/c/finish_auth.php?nonce=adCevd6T
[openid_sig] => SgFE5iT9IGd5EftkrZ72mgCHiLk=
[openid_signed] => assoc_handle,identity,mode,response_nonce,return_to,signed,sreg.email,sreg.fullname,sreg.nickname
[openid_sreg_email] => jrhodes#roket-enterprises.com
[openid_sreg_fullname] => James Rhodes
[openid_sreg_nickname] => jrhodes
)
This is the header reponse that I am outputting (contains POST data as it was explained to me on IRC that sending the key-values as headers shouldn't be done to the consumer server EDIT: Come to think of it, it doesn't make much sense RESPONDING with POST data. Maybe some here can explain the whole process of check_authentication clearly).
Content-Type: text/plain;
Content-Length: 675;
openid.mode=id_res&openid.assoc_handle=%7BHMAC-SHA1%7D%7B4b00d7b2%7D%7Bvo1FEQ%3D%3D%7D&openid.identity=http%3A%2F%2Fwww.roket-enterprises.com%2Fopenaccount%2Fopenid%3Ahachque&openid.response_nonce=2009-11-16T04%3A40%3A18Zrrz8R4&openid.return_to=http%3A%2F%2Fopeniddirectory.com%3A80%2Fopenidauth%2Fid%2Fc%2Ffinish_auth.php%3Fnonce%3DadCevd6T&openid.signed=assoc_handle%2Cidentity%2Cmode%2Cresponse_nonce%2Creturn_to%2Csigned%2Csreg.email%2Csreg.fullname%2Csreg.nickname&openid.sreg_email=jrhodes%40roket-enterprises.com&openid.sreg_fullname=James+Rhodes&openid.sreg_nickname=jrhodes&openid.sig=MGVhMmQ1Mzg4ZWFlMWY1OWVlYjlmZmY0Njc3OTc5YWIzMjM3NGFjMQ%3D%3D&openid.is_valid=true;
This is the PHP code that my file is using to handle check_authentication (remember that PHP turns all . characters into _ for $_GET and $_POST variables since they aren't valid character in PHP array keys):
// Retrieve the OpenID information from the $_REQUEST data
// I'm not sure whether it's possible that this data might
// come in on the $_GET parameter instead of $_POST, so that's
// what it uses $_REQUEST.
$assoc_handle = $_REQUEST['openid_assoc_handle'];
$sig = $_REQUEST['openid_sig'];
$signed = $_REQUEST['openid_signed'];
// The method for returning data is via the headers outputted
// by the webserver. Create an array that stores the headers
// to be returned.
$keys = array(
'openid.mode' => 'id_res',
'openid.assoc_handle' => $_REQUEST['openid_assoc_handle'],
'openid.identity' => $_REQUEST['openid_identity'],
'openid.response_nonce' => $_REQUEST['openid_response_nonce'],
'openid.return_to' => $_REQUEST['openid_return_to'],
'openid.signed' => $_REQUEST['openid_signed'],
'openid.sreg_email' => $_REQUEST['openid_sreg_email'],
'openid.sreg_fullname' => $_REQUEST['openid_sreg_fullname'],
'openid.sreg_nickname' => $_REQUEST['openid_sreg_nickname']
//'openid_mode' => 'id_res'
);
// The server may request that we invalidate the user's session
// via $_REQUEST['openid_invalidate_handle']. In this case we
// will clear the session data (you may need to change this
// depending on how you implement the session). After doing so
// we continue and tell the server we did via a variable
if (strlen($_REQUEST['openid_invalidate_handle']) > 0)
{
// Reset the session
session_unset();
session_name('openid_server');
session_start();
// Set the header we need to return
$keys['openid.invalidate_handle'] = $_REQUEST['openid_invalidate_handle'];
}
// We need to validate the signature now. This constructs a token_contents
// for signing the data. The signing key is returned as openid.sig
// and is generated with base64(HMAC(secret(assoc_handle), token_contents)
$token_contents = '';
foreach (explode(',', $signed) as $param) {
$post = preg_replace('/\./', '_', $param);
$token_contents .= sprintf("%s:%s\n", $param, $_REQUEST['openid_' . $post]);
}
// Generate our openid.sig and add it to the list of keys to
// return.
$keys['openid.sig'] = base64_encode(hash_hmac('sha1',$token_contents,$assoc_handle));
// Add the data that we are sharing (via SReg) to the headers.
// For now this is fixed data (see action_authorization.php).
//$keys["sreg.fullname"] = 'James Rhodes';
//$keys["sreg.nickname"] = 'jrhodes';
//$keys["sreg.email"] = 'jrhodes#roket-enterprises.com';
// Just accept the request for now..
// phpMyID does some kind of secret-shared-key thing
// here to determine whether it is valid. I'm not
// quite sure how that process works yet, so we are just
// going to say go ahead.
$keys["openid.is_valid"] = "true";
// We need to format the $keys array into POST format
$keys_post = "";
$keys_post_first = true;
foreach ($keys as $name => $value)
{
if ($keys_post_first)
$keys_post_first = false;
else
$keys_post .= "&";
$keys_post .= urlencode($name) . "=" . urlencode($value);
}
// Now output the POST data
header('Content-Type: application/x-www-form-urlencoded');
header('Content-Length: ' . strlen($keys_post));
header($keys_post);
Can anyone help me with my problem? I've been trying to get this working for months and I can't get a straight answer on how this stage of OpenID authentication is meant to work.
First of all, although PHP transforms periods to underscores in parameter names, be sure you're sending periods and not underscores.
Secondly, your check_authentication response should only have three parameters, but you have six. Check the spec and fix up your response and see if that helps.
Andrew Arnott,you're wrong!
documentation from openid.net:
11.4.2.1. Request Parameters
openid.mode
Value: "check_authentication"
Exact copies of all fields from the authentication response, except for "openid.mode".
may be more than three fields!
I had a similar issue. In my case, the client (relying party) failed to resolve the name of the OpenId provider to the correct ip. Although this is unlikely to be the case, please check name resolution on your relying server.

Categories