Clickatell arabic text getting garbled - php

I ma trying to send arabic text from the clickatell sms provider using php, but all i have been able to send yet is garbled text. The text I am using for testing is "غثس هفس ".
I have tried encoding the message text to Windows-1256,1250 and ISO-8859-6 formats using iconv but everytimeit just sends garbled texts.
Can anyone please give me some pointers to what I am missing?
Thanks in advance

Ok, this is for whoever comes on this path later. Found the solution here
Retaining code from Esailija's answer
<?php
$text = "غثس هفس خن";
$arr = unpack('H*hex', iconv('UTF-8', 'UCS-2BE', $text));
$message = strtoupper($arr['hex']);
$username = '';
$password = '';
$API_ID = '';
$to = '';
$url = "http://api.clickatell.com/http/auth?user=$username&password=$password&api_id=$API_ID";
$ret = file($url);
$sess = explode(":",$ret[0]);
if ($sess[0] == "OK") {
$sess_id = trim($sess[1]);
$url = "http://api.clickatell.com/http/sendmsg?session_id=$sess_id&to=$to&text=$message&unicode=1";
$ret = file($url);
$send = explode(":",$ret[0]);
if ($send[0] == "ID") {
echo "success - message ID: ". $send[1];
} else {
echo "send message failed";
}
} else {
echo "Authentication failure: ". $ret[0];
}

Based on this, the default is GSM but you can also choose UCS2.
So:
<?php
//Make sure the PHP source file is physically
//saved in utf-8
$text = rawurlencode(iconv( "UTF-8", "UCS-2", "غثس هفس "));
$url = "http://api.clickatell.com/http/auth?user=username&password=password&api_id=API_ID&encoding=UCS2";
$ret = file($url);
$sess = explode(":",$ret[0]);
if ($sess[0] == "OK") {
$sess_id = trim($sess[1]);
$url = "http://api.clickatell.com/http/sendmsg?session_id=$sess_id&to=$to&text=$text&encoding=UCS2";
$ret = file($url);
$send = explode(":",$ret[0]);
if ($send[0] == "ID") {
echo "successnmessage ID: ". $send[1];
} else {
echo "send message failed";
}
} else {
echo "Authentication failure: ". $ret[0];
}

I rewrote ClickATell code for Vtiger 6 so that it can accommodate UTF-8 Extended characters. I use Turkish. I use the below code for conversion. I hope you can port.
/**
* Function to handle UTF-8 Check and conversion
* #author Nuri Unver
*
*/
public function smstxtcode($data){
$mb_hex = '';
$utf = 0;
for($i = 0 ; $i<mb_strlen($data,'UTF-8') ; $i++){
$c = mb_substr($data,$i,1,'UTF-8');
$o = unpack('N',mb_convert_encoding($c,'UCS-4BE','UTF-8'));
$hx = sprintf('%04X',$o[1]);
$utf += intval(substr($hx,0,2));
$mb_hex .= $hx;
}
if ($utf>0)
{
$return=$mb_hex;
$utf=1;
}
else
{
$return=utf8_decode($data);
$utf=0;
}
return array($utf,$return);
}
The you call this function with your message. The response you get it is an array saying wheter to send unicode or normal text depending on the message and also the text to be sent. If there are no extended characters, it just sends it as plain text with unicode=0 in order to save characters. If message contains extended characters, it converts the message to hexcode and sends it as unicode.
This code just does the calculations. You need to implement your own code to port it to your system. For demonstration this is the code I use for Vtiger to extract data and send the message:
/**
* Function to handle SMS Send operation
* #param <String> $message
* #param <Mixed> $toNumbers One or Array of numbers
*/
public function send($message, $toNumbers) {
if(!is_array($toNumbers)) {
$toNumbers = array($toNumbers);
}
$params = $this->prepareParameters();
$smsarray = $this->smstxtcode($message);
$params['text'] = $smsarray[1];
$params['unicode'] = $smsarray[0];
$params['to'] = implode(',', $toNumbers);
$serviceURL = $this->getServiceURL(self::SERVICE_SEND);

Short answer: you need to do two things:
a) Convert your Arabic message:
$data = iconv("UTF-8", "UCS-2BE", $data);
$data = bin2hex($data);
Use the resulting string as your message text
b) When sending via the HTTP API, include the unicode parameter:
&unicode=1

Related

Sending Encrypted Email S/MIME with PHP

I have been looking a lot online but I didn't find an answer, is it possible to send encrypted emails S/MIME using PHP? if it is, how? (im using cakephp 2.x)
Thank you very much in advance
I managed to find a solution to this using PHPMailer, It applies to regular PHP as well. It will sign and encrypt the email, I couldn't find a way to do both with PHPMailer (sign and encrypt) only sign so I added some code to class.phpmailer.php. It stills need to add some error handling in case of an encryption error but so far works good.
for CakePHP 2.x:
Download PHPMailer and add it to your Vendors folder (project_name/app/vendor)
Add this line at the beginning of your function:
App::import('Vendor','PHPMailer/PHPMailerAutoload');
From here its the same for PHP or CakePHP:
$mail = new PHPMailer();
$mail->setFrom('from_who#email', 'Intranet');
//Set who the message is to be sent to
$mail->addAddress('to_who#email', 'Ricardo V');
//Set the subject line
$mail->Subject = 'PHPMailer signing test';
//Replace the plain text body with one created manually
$mail->Body = "some encrypted text...";
//Attach an image file
$mail->addAttachment('D:/path_to_file/test.pdf');
$mail->sign(
'app/webroot/cert/cert.crt', //The location of your certificate file
'app/webroot/cert/private.key', //The location of your private key
file
'password', //The password you protected your private key with (not
//the Import Password! may be empty but parameter must not be omitted!)
'app/webroot/cert/certchain.pem', //the certificate chain.
'1', //Encrypt the email as well, (1 = encrypt, 0 = dont encrypt)
'app/webroot/cert/rvarilias.crt'//The location of public certificate
//to encrypt the email with.
);
if (!$mail->send()) {
echo "Mailer Error: " . $mail->ErrorInfo;
} else {
echo "Message sent!";
}
Then we need to make some changes to class.phpmailer.php
replace the lines from 2368 to 2390 with:
$sign = #openssl_pkcs7_sign(
$file,
$signed,
'file://' . realpath($this->sign_cert_file),
array('file://' . realpath($this->sign_key_file),
$this->sign_key_pass),
null,
PKCS7_DETACHED,
$this->sign_extracerts_file
);
if ($this->encrypt_file == 1) {
$encrypted = tempnam(sys_get_temp_dir(), 'encrypted');
$encrypt = #openssl_pkcs7_encrypt(
$signed,
$encrypted,
file_get_contents($this->encrypt_cert_file),
null,
0,
1
);
if ($encrypted) {
#unlink($file);
$body = file_get_contents($encrypted);
#unlink($signed);
#unlink($encrypted);
//The message returned by openssl contains both headers
and body, so need to split them up
$parts = explode("\n\n", $body, 2);
$this->MIMEHeader .= $parts[0] . $this->LE . $this->LE;
$body = $parts[1];
} else {
#unlink($file);
#unlink($signed);
#unlink($encrypted);
throw new phpmailerException($this->lang('signing') .
openssl_error_string());
}
} else {
if ($signed) {
#unlink($file);
$body = file_get_contents($signed);
#unlink($signed);
//The message returned by openssl contains both headers
and body, so need to split them up
$parts = explode("\n\n", $body, 2);
$this->MIMEHeader .= $parts[0] . $this->LE . $this->LE;
$body = $parts[1];
} else {
#unlink($file);
#unlink($signed);
throw new phpmailerException($this->lang('signing') .
openssl_error_string());
}
}
}
then look for:
public function sign($cert_filename, $key_filename, $key_pass,
$extracerts_filename = '')
{
$this->sign_cert_file = $cert_filename;
$this->sign_key_file = $key_filename;
$this->sign_key_pass = $key_pass;
$this->sign_extracerts_file = $extracerts_filename;
}
and change it for:
public function sign($cert_filename, $key_filename, $key_pass,
$extracerts_filename = '', $and_encrypt ='0', $encrypt_cert = '')
{
$this->sign_cert_file = $cert_filename;
$this->sign_key_file = $key_filename;
$this->sign_key_pass = $key_pass;
$this->sign_extracerts_file = $extracerts_filename;
$this->encrypt_file = $and_encrypt;
$this->encrypt_cert_file = $encrypt_cert;
}
look for:
protected $sign_extracerts_file = '';
and add these lines after it:
protected $encrypt_cert = '';
protected $and_encrypt = '';
With these changes to phpmailer you can send a signed email or a signed and encrypted email. It works with attachments too.
I hope it is help ful to somebody.
*for regular php just don't add the line:
App::import('Vendor','PHPMailer/PHPMailerAutoload');

Filtering "=2E" from e-mails

We retrieve and parse e-mails via PHP5-imap, and when parsing e-mails, we noticed that some of the e-mails contain special characters which are not filtered out automatically (ex: =2E or = before a line break)
What is the best way to filter these characters out ?
We're using this section of code to get the mail body:
$structure = imap_fetchstructure(static::$_connection, $msg_id);
$unparsed = imap_fetchbody(static::$_connection, $msg_id, $i + 1);
if ($structure->parts[$i]->encoding == 3) { // 3 = BASE64
$parsed = base64_decode($unparsed);
}
elseif ($structure->parts[$i]->encoding == 4) { // 4 = QUOTED-PRINTABLE
$parsed = quoted_printable_decode($unparsed);
}
else {
$parsed = $unparsed;
}

imap_search() for unseen messages does not returning anything

I am currently trying to get the UNSEEN/UNREAD messages from my server. Currently, I have this:
$openmail = imap_open($dns, $email, $password) or die("Cannot Connect " . imap_last_error());
if ($openmail) {
/* grab emails */
$emails = imap_search($openmail, 'UNSEEN');
if ($emails) {
//For every e-mail.
$tot = imap_num_msg($openmail);
for ($i = $tot; $i > 0; $i--) {
$structure = imap_fetchstructure($openmail, $i);
if (isset($structure->parts) && is_array($structure->parts) && isset($structure->parts[1])) {
$part = $structure->parts[1];
$message = imap_fetchbody($openmail, $i, 2, FT_PEEK);
if ($part->encoding == 3) {
$message = imap_base64($message);
} else if ($part->encoding == 1) {
$message = imap_8bit($message);
} else {
$message = imap_qprint($message);
}
}
$header = imap_header($openmail, $i);
$from = imap_utf8($header->fromaddress);
$subject = $header->Subject;
$subject = substr($subject, 0, 150);
$date = $header->Date;
}
/* Print out the Unseen messages in here! */
} else {
/* No unseen messages */
echo "No unseen";
}
}
I've tried sending multiply emails to my mailserver, refreshed the page with the above script. But I keep getting the "No unseen".
I've tried to output the $emails but it's empty. It can't find anything.
If I try to just get ALL the messages (no unseen filter), I can see the emails I've sent to the mailbox, although, they're all marked as read.
Your code $message = imap_fetchbody($openmail, $i, 2, FT_PEEK); uses hardcoded part offsets, i.e. it assumes that a message is always multipart/alternative with text/plain and text/html. That's a very wrong assumption.
You have to look at the output of the BODYSTRUCTURE to see what the MIME structure of your mail is.
With that out of the way, be sure that none of your commands use the BODY fetch operation; you have to use BODY.PEEK. I have no idea how this is accessible within the PHP c-client bindings.

mb_strlen() & strlen() don't return correct values from an Ajax call to PHP

How can I add a check in the PHP for the length of the $username passed. The site is UTF-8 but I believe Javascript is using a different encoding. You can see in the comments where I tried different things in the PHP and they don't work.
What I tried and didn't work:
Changing Ajax (javascript) to pass variables by UTF-8 and not javascript encoding
strlen, mb_strlen in the PHP - both return incorrect values
MORE INFO
My Ajax sends a username to my PHP, which checks the SQL DB and returns available or not. I decided to try and do some extra checking in the PHP before checking the DB (like mb_strlen($username). mb_internal_encoding("UTF-8"); is also set.
I was going to try and send the Ajax request in UTF-8 but didnt see a way to do that.
is UPPER being used correctly in the MySQL? - for UTF-8 stuff?
PHP BELOW ***********
// Only checks for the username being valid or not and returns 'taken' or 'available'
require_once('../defines/mainDefines.php'); // Connection variables
require_once('commonMethods.php');
require_once('sessionInit.php'); // start session, check for HTTP redid to HHHTPs
sleep(2); // Looks cool watching the spinner
$username = $_POST['username'];
//if (mb_strlen($username) < MIN_USERNAME_SIZE) echo 'invalid_too_short';
//if (mb_strlen($username, 'UTF-8') < 10) { echo ('invalid_too_short'); exit; }
//die ('!1!' . $username . '!2!' . mb_strlen($username) . '!3!' . strlen($username) . '!4!');
$dbc = mysqli_connect(DB_HOST, DB_READER, DB_READER_PASSWORD, DB_NAME) or die(DB_CONNECT_ERROR . DB_HOST . '--QueryDB--checkName.php');
$stmt = mysqli_stmt_init($dbc);
$query = "SELECT username FROM pcsuser WHERE UPPER(username) = UPPER(?)";
if (!mysqli_stmt_prepare($stmt, $query)) {
die('SEL:mysqli_prepare failed somehow:' . $query . '--QueryDB--checkName.php');
}
if (!mysqli_stmt_bind_param($stmt, 's', $username)) {
die('mysqli_stmt_bind_param failed somehow --checkName.php');
}
if (!mysqli_stmt_execute($stmt)) {
die('mysqli_stmt_execute failed somehow' . '--checkName.php');
}
mysqli_stmt_store_result($stmt);
$num_rows = mysqli_stmt_num_rows($stmt);
mysqli_stmt_bind_result($stmt, $row);
echo ($num_rows >= 1) ? 'taken' : 'available';
mysqli_stmt_close($stmt);
mysqli_close($dbc);
AJAX CODE BELOW
function CheckUsername(sNameToCheck) {
document.getElementById("field_username").className = "validated";
registerRequest = CreateRequest();
if (registerRequest === null)
alert("Unable to create AJAX request");
else {
var url= "https://www.perrycs.com/php/checkName.php";
var requestData = "username=" + escape(sNameToCheck); // data to send
registerRequest.onreadystatechange = ShowUsernameStatus;
registerRequest.open("POST", url, true);
registerRequest.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
registerRequest.send(requestData);
}
}
function ShowUsernameStatus() {
var img_sad = "graphics/signup/smiley-sad006.gif";
var img_smile = "graphics/signup/smiley-happy088.gif";
var img_checking = "graphics/signup/bluespinner.gif";
if (request.readyState === 4) {
if (request.status === 200) {
var txtUsername = document.getElementById('txt_username');
var fieldUsername = document.getElementById('field_username');
var imgUsername = document.getElementById('img_username');
var error = true;
var response = request.responseText;
switch (response) {
case "available":
txtUsername.innerHTML = "NAME AVAILABLE!";
error = false;
break;
case "taken":
txtUsername.innerHTML = "NAME TAKEN!";
break;
case "invalid_too_short":
txtUsername.innerHTML = "TOO SHORT!";
break;
default:
txtUsername.innerHTML = "AJAX ERROR!";
break;
} // matches switch
if (error) {
imgUsername.src = img_sad;
fieldUsername.className = 'error';
} else {
imgUsername.src = img_smile;
fieldUsername.className = 'validated';
}
} // matches ===200
} // matches ===4
}
TESTING RESULTS
This is what I get back when I DIE in the PHP and echo out as in the following (before and after making the Ajax change below [adding in UTF-8 to the request]...
PHP SNIPPIT
die ('!1!' . $username . '!2!' . mb_strlen($username) . '!3!' . strlen($username) . '!4!');
TEST DATA
Username: David Perry
!1!David Perry!2!11!3!11!4!
Username: ܦ"~÷Û♦
!1!ܦ\"~��%u2666!2!9!3!13!4!
The first one works. The second one should work but it looks like the encoding is weird (understandable).
7 visible characters for the 2nd one. mb_strlen shows 9, strlen shows 13.
After reading Joeri Sebrechts solution and link they gave me I looked up Ajax request parameters and someone had the following...
AJAX SNIPPIT (changed from original code)
registerRequest.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");
(I added in the charset=UTF-8 from an example I saw on a article).
UPDATE: Nov 27, 9:11pm EST
Ok, after much reading I believe I am encoding my JS wrong. I was using escape... as follows...
var requestData = "username=" + escape(sNameToCheck);
After looking at this website...
http://www.the-art-of-web.com/javascript/escape/
it helped me understand more of what's going on with each function and how they encode and decode. I should be able to do this...
var requestData = "username=" + encodeURIComponent(sNameToCheck);
in JS and in PHP I should be able to do this...
$username = rawurldecode($_POST['username']);
Doing that still gives me 8 characters for my weird example above instead of 7. It's close, but am I doing something wrong? If I cursor through the text on the screen it's 7 characters. Any ideas to help me understand this better?
FIXED/SOLVED!!!
Ok, thank you for your tips that lead me in the right direction to make this work. My changes were as follows.
In the AJAX -- i used to have escape(sNameToCheck); --
var requestData = "username=" + encodeURIComponent(sNameToCheck);
In the PHP *-- I used to have $username = $_POST['username']; --*
$username = rawurldecode($_POST['username']);
if (get_magic_quotes_gpc()) $username = stripslashes($username);
I really hate magic_quotes... it's caused me about 50+ hours of frustration over form data in total because I forgot about it. As long as it works. I'm happy!
So, now the mb_strlen works and I can easily add this back in...
if (mb_strlen($username) < MIN_USERNAME_SIZE) { echo 'invalid_too_short'; exit; }
Works great!
PHP is a byte processor, it is not charset-aware. That has a number of tricky consequences.
Strlen() returns the length in bytes, not the length in characters. This is because php's "string" type is actually an array of bytes. Utf8 uses more than one byte per character for the 'special characters'. Therefore strlen() will only give you the right answer for a narrow subset of text (= plain english text).
Mb_strlen() treats the string as actual characters, but assumes it's in the encoding specified via mbstring.internal_encoding, because the string itself is just an array of bytes and does not have metadata specifying its character set. If you are working with utf8 data and set internal_encoding to utf8 it will give you the right answer. If your data is not utf8 it will give you the wrong answer.
Mysql will receive a stream of bytes from php, and will parse it based on the database session's character set, which you set via the SET NAMES directive. Everytime you connect to the database you must inform it what encoding your php strings are in.
The browser receives a stream of bytes from php, and will parse it based on the content-type charset http header, which you control via php.ini default_charset. The ajax call will submit in the same encoding as the page it runs from.
Summarized, you can find advice on the following page on how to ensure all your data is treated as utf8. Follow it and your problem should resolve itself.
http://malevolent.com/weblog/archive/2007/03/12/unicode-utf8-php-mysql/
From a quick glance, you can clean this up:
if (request.status == 200) {
if (request.responseText == "available") {
document.getElementById("txt_username").innerHTML = "NAME AVAILABLE!";
document.images['img_username'].src=img_smile;
document.getElementById("continue").disabled = false;
document.getElementById("field_username").className = 'validated';
} else if (request.responseText == "taken") {
document.getElementById("txt_username").innerHTML = "NAME TAKEN!";
document.images['img_username'].src=img_sad;
document.getElementById("field_username").className = 'error';
} else if (request.responseText == "invalid_too_short") {
document.getElementById("txt_username").innerHTML = "TOO SHORT!";
document.images['img_username'].src=img_sad;
document.getElementById("field_username").className = 'error';
} else {
document.getElementById("txt_username").innerHTML = "AJAX ERROR!";
document.images['img_username'].src=img_sad;
document.getElementById("field_username").className = 'error';
}
}
to:
// I prefer triple equals
// Read more at http://javascript.crockford.com/style2.html
if (request.status === 200) {
// use variables!
var txtUsername = document.getElementById('txt_username');
var fieldUsername = document.getElementById('field_username');
var imgUsername = document.getElementById('img_username');
var response = request.responseText;
var error = true;
// you can do a switch statement here too, if you prefer
if (response === "available") {
txtUsername.innerHTML = "NAME AVAILABLE!";
document.getElementById("continue").disabled = false;
error = false;
} else if (response === "taken") {
txtUsername.innerHTML = "NAME TAKEN!";
} else if (response === "invalid_too_short") {
txtUsername.innerHTML = "TOO SHORT!";
} else {
txtUsername.innerHTML = "AJAX ERROR!";
}
// refactor error actions
if (error) {
imgUsername.src = img_sad;
fieldUsername.className = 'error';
} else {
imgUsername.src = img_smile;
fieldUsername.className = 'validated';
}
}

Using PHP's IMAP library triggers Kaspersky's Antivirus

I just started today working with PHP's IMAP library, and while imap_fetchbody or imap_body are called, it is triggering my Kaspersky antivirus. The viruses are Trojan.Win32.Agent.dmyq and Trojan.Win32.FraudPack.aoda. I am running this off a local development machine with XAMPP and Kaspersky AV.
Now, I am sure there are viruses there since there is spam in the box (who doesn't need a some viagra or vicodin these days?). And I know that since the raw body includes attachments and different mime-types, bad stuff can be in the body.
So my question is: are there any risks using these libraries?
I am assuming that the IMAP functions are retrieving the body, caching it to disk/memory and the AV scanning it sees the data.
Is that correct? Are there any known security concerns using this library (I couldn't find any)? Does it clean up cached message parts perfectly or might viral files be sitting somewhere?
Is there a better way to get plain text out of the body than this? Right now I am using the following code (credit to Kevin Steffer):
function get_mime_type(&$structure) {
$primary_mime_type = array("TEXT", "MULTIPART","MESSAGE", "APPLICATION", "AUDIO","IMAGE", "VIDEO", "OTHER");
if($structure->subtype) {
return $primary_mime_type[(int) $structure->type] . '/' .$structure->subtype;
}
return "TEXT/PLAIN";
}
function get_part($stream, $msg_number, $mime_type, $structure = false, $part_number = false) {
if(!$structure) {
$structure = imap_fetchstructure($stream, $msg_number);
}
if($structure) {
if($mime_type == get_mime_type($structure)) {
if(!$part_number) {
$part_number = "1";
}
$text = imap_fetchbody($stream, $msg_number, $part_number);
if($structure->encoding == 3) {
return imap_base64($text);
} else if($structure->encoding == 4) {
return imap_qprint($text);
} else {
return $text;
}
}
if($structure->type == 1) /* multipart */ {
while(list($index, $sub_structure) = each($structure->parts)) {
if($part_number) {
$prefix = $part_number . '.';
}
$data = get_part($stream, $msg_number, $mime_type, $sub_structure,$prefix . ($index + 1));
if($data) {
return $data;
}
} // END OF WHILE
} // END OF MULTIPART
} // END OF STRUTURE
return false;
} // END OF FUNCTION
$connection = imap_open($server, $login, $password);
$count = imap_num_msg($connection);
for($i = 1; $i <= $count; $i++) {
$header = imap_headerinfo($connection, $i);
$from = $header->fromaddress;
$to = $header->toaddress;
$subject = $header->subject;
$date = $header->date;
$body = get_part($connection, $i, "TEXT/PLAIN");
}
Your guess seems accurate. IMAP itself is fine. What you do with the contents is what's dangerous.
What's dangerous about virus e-mails is that users might open a .exe attachment or something, so bad attachments and potentially evil HTML are what's being checked. As long as your code handling attachments doesn't tell the user to open them and this is just automatic processing or whatever, you're good to go. If you're planning on outputting HTML contents, be sure to use something like HTML Purifier.
The AV is detecting these signatures as they pass through the networking stack, most likely. You should be able to tell the source of the detection from the messages Kaspersky is giving you.

Categories