Apple Push Notifications With Foreign Accent Characters Not Receiving - php

I'm sending push notifications and when the message contains foreign characters (Turkish in my case) like İ, ş, ç, ğ... The message does not arrive to devices.
Here's my code:
$message = 'THİS is push';
$passphrase = 'mypass';
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'MyPemFile.pem');
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
// Open a connection to the APNS server
$fp = stream_socket_client(
'ssl://gateway.push.apple.com:2195', $err,
$errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
if (!$fp)
exit("Failed to connect: $err $errstr" . PHP_EOL);
echo 'Connected to Apple service. ' . PHP_EOL;
// Encode the payload as JSON
$body['aps'] = array(
'alert' => $message,
'sound' => 'default'
);
$payload = json_encode($body);
$result = 'Start'.PHP_EOL;
$tokenArray = array('mytoken');
foreach ($tokenArray as $item)
{
// Build the binary notification
$msg = chr(0) . pack('n', 32) . pack('H*', $item) . pack('n', strlen($payload)) . $payload;
// Send it to the server
$result = fwrite($fp, $msg, strlen($msg));
if (!$result)
echo 'Failed message'.PHP_EOL;
else
echo 'Successful message'.PHP_EOL;
}
// Close the connection to the server
fclose($fp);
I have tried encoding $message variable with utf8_encode() but the message received as "THÝS is push". And other ways like iconv() didn't work for me, some of them cropped Turkish characters, some didn't receive at all.
I also have
header('content-type: text/html; charset: utf-8');
and
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
in my page. I don't think the problem appears while I set the value but maybe with pack() function.
Any ideas to solve this without replacing characters with English?

All I had to do was replacing the Turkish characters with following script:
function tr_to_utf($text) {
$text = trim($text);
$search = array('Ü','Ş','Ğ','Ç','İ','Ö','ü','ş','ğ','ç','ı','ö');
$replace = array('Ãœ','Å','Ğ','Ç','Ä°','Ö','ü','ÅŸ','ÄŸ','ç','ı','ö');
$new_text = str_replace($search,$replace,$text);
return $new_text;
}
Now it is working with no problems.
This is the source.

The "n" parameter means, that you pack as unsigned short (always 16 bit, big endian byte order). I`m not sure how Apple CPU hardware handle the instructions and how convert them, but is different from the PC for sure. Try to switch the byte order, use "v" unsigned short (always 16 bit, little endian byte order).

Related

APNS fails to deliver message after inactivity

I have created a client-server solution for APNS in PHP.
The "server" is a continously running script that waits for connections and copies the content to APNS. This means it keeps the connection to APNS open all the time. I use fwrite and stream context as such. This is the part that accepts incoming connections:
if ($client = #stream_socket_accept($this->socket, -1)) {
$this->writeLine("Received connection.");
$this->inputBuffer = stream_get_contents($client);
}
It runs in a while-loop, so that next iteration copies the contents of inputBuffer to APNS as such:
$writtenBytes = #fwrite($this->connectionAPNS, $this->inputBuffer, strlen($this->inputBuffer));
I then check for bytes written like:
if ($writtenBytes > 0) {
$this->writeLine("Copied $writtenBytes bytes to APNS.");
return true;
}
Now, the problem arises when the connection has been sitting idle for some time. The first push notification WILL return the number of bytes written to APNS - such as "Copied 157 bytes to APNS.", indicating that the fwrite works, but the message is not delivered.
The next notification then returns 0 bytes written, reconnects automatically (part of my script) and writes the bytes that failed - then it works.
Why do I get bytes written returned if the connection actually failed to write or if APNS did not accept them?
I use the enhanced notification format and I check for errors returned, which seem to be none.
I use this code for that, which I found on a blog, where I pass the connection handle to the function after writing to APNS:
function checkAppleErrorResponse($fp)
{
$apple_error_response = fread($fp, 6);
if ($apple_error_response) {
error_response = unpack('Ccommand/Cstatus_code/Nidentifier', $apple_error_response);
if ($error_response['status_code'] == '0') {
$error_response['status_code'] = '0-No errors encountered';
} else {
// Here is some fancy if/else error handling, but I never end in this else , so I excluded it.
}
}
}
The 'client' part that keeps the connection APNS open looks like this, and is called whenever writtenBytes returns 0 (error) or when the script first loads:
function connectToAPNS()
{
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', $this->certpath);
stream_context_set_option($ctx, 'ssl', 'passphrase', $this->passphrase);
// Open a connection to the APNS server
$error = "";
$errno = 0;
$this->connectionAPNS = stream_socket_client(
'ssl://gateway.push.apple.com:2195',
$errno,
$error,
60,
STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT,
$ctx
);
if ($this->connectionAPNS) {
stream_set_blocking($this->connectionAPNS, 0);
$this->writeLine("Connected to APNS!");
return true;
}
}
Is your app a release or a debug version? In each case you have to communicate with a different web-service and use different certificates.
In sandbox/debug mode communicate with ssl://gateway.sandbox.push.apple.com:2195
In production/relase mode communicate with ssl://gateway.push.apple.com:2195
The notification will be silently dropped, if you communicate with the wrong service.

text written from php client to c server only appears on dosconnect

I have an ssl server (test) in c - and a client written in php. the server works fine when a client written in c connects, but when the php client (virtually identical to the c client) sends, the message appears on the server only after buffer overflow or disconnect.
why a c program and not a php program would work is a mystery to me.
I've tried buffer control, unblocking, adding nulls to the send string.
php 5.4 centos linux 2.6
i can add code if appropriate - it's quite vanilla. but i thought there might be a difference between the way php handles stream traffic and c. my confidence in my code.
thanks for any thoughts.
#!/usr/local/bin/php
<?php
date_default_timezone_set('America/New_York');
$host = 'localhost';
$port = 9255;
$timeout = 5.0;
function gen_cert($pem_file,$pem_passphrase)
{
// Certificate data:
$dn = array(
"countryName" => "US",
"stateOrProvinceName" => "Texas",
"localityName" => "Houston",
"organizationName" => "<org name>",
"organizationalUnitName" => "Tech",
"commonName" => "kensie2",
"emailAddress" => "<email>"
);
// Generate certificate
$privkey = openssl_pkey_new();
$cert = openssl_csr_new($dn, $privkey);
$cert = openssl_csr_sign($cert, null, $privkey, 365);
// Generate PEM file
$pem = array();
openssl_x509_export($cert, $pem[0]);
openssl_pkey_export($privkey, $pem[1], $pem_passphrase);
openssl_pkey_export($privkey, $pem[1], $pem_passphrase);
$pem = implode($pem);
// Save PEM file
// $pemfile = $filename;
file_put_contents($pem_file, $pem);
chmod($pem_file,0400);
}
$pem_passphrase = 'foozot';
$pem_file = 'test3_client.pem';
if(!file_exists($pem_file))
gen_cert($pem_file,$pem_passphrase);
$context = stream_context_create();
// local_cert must be in PEM format
stream_context_set_option($context, 'tls', 'local_cert', $pem_file);
// Pass Phrase (password) of private key
stream_context_set_option($context, 'tls', 'passphrase', $pem_passphrase);
stream_context_set_option($context, 'tls', 'allow_self_signed', true);
stream_context_set_option($context, 'tls', 'verify_peer', true);
$fp = stream_socket_client('tls://'.$host.':'.$port, $errno, $errstr, $timeout,
STREAM_CLIENT_CONNECT, $context);
if($fp)
{
$buf = fread($fp,8192);
fwrite(STDOUT,"from server: $buf");
// tried this - no difference
// stream_set_write_buffer($fp,0);
// tried this - no difference
// stream_set_blocking($fp,0);
$ary = stream_get_meta_data($fp);
fwrite(STDERR,"meta=" . print_r($ary,true) . "\n");
while(1)
{
$buf = fgets(STDIN);
fwrite(STDOUT,"buf=$buf\n");
if(strstr($buf,"\n") === null)
$buf .= "\n\0";
$er = fwrite($fp, $buf,strlen($buf) + 1);
fwrite(STDERR,posix_strerror(posix_get_last_error()) . "\n");
}
}
else
{
echo "ERROR: $errno - $errstr<br />\n";
}
?>
I'musing code from Professional Linux Network Programming - Chapter 8 - server.c
By Nathan Yocom, plnp#yocom.org modified to prefork. it uses BIO for io.
as a model for the c code. i don't think i can post it without some copyright violation.
it does work. far more tested than my code.

PHP Push notification service, feedback is empty,how can i to do

The push works just fine, the problem is that the feedback is empty. I need to delete the tokens that have expired or have an invalid status.This is what I write test code,this a problem?]
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
$fp = stream_socket_client('ssl://feedback.sandbox.push.apple.com:2196', $error, $errorString, 60, STREAM_CLIENT_CONNECT, $ctx);
if (!$fp) {
return;
}
while ($devcon = fread($fp,38)) {
$arr = unpack("H*", $devcon);
$rawhex = trim(implode("", $arr));
$feedbackTime = hexdec(substr($rawhex, 0, 8));
$feedbackDate = date('Y-m-d H:i', $feedbackTime);
$feedbackLen = hexdec(substr($rawhex, 8, 4));
$feedbackDeviceToken = substr($rawhex, 12, 64);
}
fclose($fp);
Feedback services don't always return data. You will get data only if there is need to report failed deliveries.
See the documentation here.
Not sure if this is true but I have noticed feedback services don't work with the sandbox environment (at least I haven't received a response any time I checked with sandbox)

push notification in ios

I am new in a php and write a code for push notification in codeigniter but I got these erros.
Here is my model..
function sendmessage($appid, $deviceid, $status, $message)
{
$deviceToken = '0f744707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bbad78';
$message = 'My first push notification!';
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
//stream_context_set_option($ctx, 'ssl', 'passphrase', 'essar#123');
$fp = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
if (!$fp){
exit("Failed to connect: $err $errstr" . PHP_EOL);
}
else {
print "Connection OK/n";
}
echo 'Connected to APNS' . PHP_EOL;
$body['aps'] = array(
'alert' => $message,
'sound' => 'default'
);
$payload = json_encode($body);
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
$result = fwrite($fp, $msg, strlen($msg));
if (!$result)
echo 'Message not delivered' . PHP_EOL;
else
echo 'Message successfully delivered' . PHP_EOL;
fclose($fp);
$data = array(
'message' => $this->message . 'add',
'appid' => $this->appid,
'deviceid' => $this->deviceid,
'status' => $status
);
$this->sendmessage($data);
Error message:
Message: stream_socket_client(): SSL operation failed with code 1. OpenSSL Error messages: error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure Message: stream_socket_client(): Failed to enable crypto Message: stream_socket_client(): unable to connect to ssl://gateway.sandbox.push.apple.com:2195 (Unknown error)
Once you created the provisional certificates and push notification for both development and distribution. Follow the steps for Generate Push Notification
In order to use the certificates you generated, you need to create a PEM file that stores both, your Apple Push Notification Service SSL Certificate and your Private Key. You can create the PEM file from a terminal.
Navigate to the directory that contains the certificates and key you generated earlier and execute the following steps. The file names here reflect the names of the certificates that were generated as part of this lesson. You have to update the syntax according the names you gave your certificates.
First create the application certificate PEM file. You can do this by double clicking on the aps_developer_identity.cer certificate file, then opening the Keychain Assistant and exporting the certificate as ap12 file and then converting it to a PEM file, in the same fashion as the PushNotificationApp.p12 is converted to a PEM file. Alternatively you can use a single command line that converts the aps_developer_identity.cer certificate file directly to a PEM file. Here we are opting for the single command line option, as follows:
openssl x509 -inform der -outform pem -in aps_developer_identity.cer -out PushNotificationAppCertificate.pem
Now create the application key PEM file as follows. You need to enter the import password and PEM pass phrase:
openssl pkcs12 -in PushNotificationApp.p12 -out PushNotificationAppKey.pem -nocerts
Enter Import Password:
MAC verified OK
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
Now concatenate the two files:
cat PushNotificationAppCertificate.pem PushNotificationAppKey.pem > PushNotificationAppCertificateKey.pem
Open a Mac terminal and execute the following line from the directory that contains the certificates you generated:
openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert PushNotificationAppCertificate.pem -key PushNotificationAppKey.pem
You are then asked to enter the pass phrase for the key you submitted:
Enter pass phrase for PushNotificationAppKey.pem:
If everything worked, then the server should send you a lot of information that may look something like the following:
CONNECTED(00000003)
depth=1 /C=US/O=Entrust, Inc./OU=www.entrust.net/rpa is incorporated by reference/OU=(c) 2009 Entrust, Inc./CN=Entrust Certification Authority - L1C
verify error:num=20:unable to get local issuer certificate verify return:0
...
Key-Arg : None
Start Time: 1326899631 Timeout : 300 (sec) Verify return code: 0 (ok)
At the end of this, you can enter some text and then select the return key. We entered the text "**Hello World**".
**Hello World
closed**
This completes the communication with the server and verifies that our certificates work.
I have been having the same issue as you mentioned here. This took some time and head scratching to find...
The solution that I found that corrected the handshake error was to download the entrust certificate, and include this in the stream context using the code below:
$entrustCert = '<full path to cert>/entrust_2048_ca.cer';
stream_context_set_option($ctx, 'ssl', 'cafile', entrustCert);
There does seem to intermittent connection issues with the sandbox APN service. I occasionally get errors returned like:
Warning: stream_socket_client(): SSL: Connection reset by peer
Warning: stream_socket_client(): Failed to enable crypto
I hope this is a time saver for someone!
function send_iOS_notifications($device_tokens, $notification_content)
{
$this->load->library('apn');
$this->apn->payloadMethod = 'enhance'; // you can turn on this method for debuggin purpose
$this->apn->connectToPush();
//$badge_count = $this->set_and_get_ios_active_notifications('',$device_token);
// adding custom variables to the notification
$this->apn->setData($notification_content);
$message = $notification_content['message'];
if ((strpos($message, 'http://') !== false) || (strpos($message, 'https://') !== false)) {
$message = "Image";
}
//$send_result = $this->apn->sendMessage($device_token, 'Test notif #1 (TIME:'.date('H:i:s').')', /*badge*/ 2, /*sound*/ 'default' );
$send_result = $this->apn->sendMessage($device_tokens, $message, /*badge*/ '', /*sound*/ 'default' );
if($send_result){
log_message('debug','Sending successful');
$result['status'] = true;
$result['message'] = 'Sending successful';
}
else{
log_message('error',$this->apn->error);
$result['status'] = true;
$result['message'] = $this->apn->error;
}
$this->apn->disconnectPush();
}

PHP technique to query the APNs Feedback Server

Can someone clarify what the APNs (Apple Push Notification) wants as far as how you query it?
The docs say it starts sending as soon as the connection is made. Does this mean that I don't do an fread() on it?
Here's my current code to try and read it. I did NOT put the fread() in a loop as I do not know what response indicates "no more records to read" and I didn't want an infinite loop on my server.
<?php
$apnsCert = 'HOHRO-prod.pem';
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', $apnsCert);
stream_context_set_option($streamContext, 'ssl', 'verify_peer', false);
$apns = stream_socket_client('ssl://feedback.push.apple.com:2196', $error, $errorString, 60, STREAM_CLIENT_CONNECT, $streamContext);
echo 'error=' . $error;
echo 'errorString=' . $errorString;
$result = fread($apns, 38);
echo 'result=' . $result;
fclose($apns);
?>
So far all I am getting is a null reply. There are no errors so it is connecting.
I don't know if the null reply means no data is there, or my fread() is the wrong way to do it.
Thanks
Here's a big gotcha which confused me when I first tried connecting: the APNS feedback servers only return the device tokens that have "expired" since your last feedback request. Which means most of the time you'll get a NULL response unless you're already dealing with a high volume of users of your app.
So make sure you store the expired device tokens to disk or db, because after your feedback query they're gone for good. This makes testing a pain to say the least!
Here's a complete function to fetch the device tokens from the APNS feedback servers (many thanks to the answers above for helping me put it all together):
function send_feedback_request() {
//connect to the APNS feedback servers
//make sure you're using the right dev/production server & cert combo!
$stream_context = stream_context_create();
stream_context_set_option($stream_context, 'ssl', 'local_cert', '/path/to/my/cert.pem');
$apns = stream_socket_client('ssl://feedback.push.apple.com:2196', $errcode, $errstr, 60, STREAM_CLIENT_CONNECT, $stream_context);
if(!$apns) {
echo "ERROR $errcode: $errstr\n";
return;
}
$feedback_tokens = array();
//and read the data on the connection:
while(!feof($apns)) {
$data = fread($apns, 38);
if(strlen($data)) {
$feedback_tokens[] = unpack("N1timestamp/n1length/H*devtoken", $data);
}
}
fclose($apns);
return $feedback_tokens;
}
If all is well, the return values from this function will look something like this (via print_r()):
Array
(
Array
(
[timestamp] => 1266604759
[length] => 32
[devtoken] => abc1234..............etcetc
),
Array
(
[timestamp] => 1266604922
[length] => 32
[devtoken] => def56789..............etcetc
),
)
That code looks right however you need to loop and check for end of stream in order to read all the device codes.
while (!feof($apns)) {
$devcon = fread($apns, 38);
}
However my problem is the actual unpacking of the data. Does anyone know how to unpack the binary data which you've just read to get the actual device ID (as string) along with the timestamp etc?
I got the solution from apple forum and it is for development. Try this for production also.
"Well, as dumb as it sounds, I found a solution:
Create a dummy app id in the program portal, enable development push notifications on it
Create and download the associated provisioning profile
Create a new xcode project, and invoke the registerForRemoteNotificationTypes method on start.
Install the dummy app on your device. At this point, you should have two DEVELOPMENT apps running on your device: the original app and the dummy app. Both should be registered to receive push notifications.
Uninstall the original app, and try to send a push notification to that app.
Invoke the feedback service, and you should receive data back."
This finally worked for me.
$arr = unpack("H*", $devconts);
$rawhex = trim(implode("", $arr));
$feedbackTime = hexdec(substr($rawhex, 0, 8));
$feedbackDate = date('Y-m-d H:i', $feedbackTime);
$feedbackLen = hexdec(substr($rawhex, 8, 4));
$feedbackDeviceToken = substr($rawhex, 12, 64);
And then you simply check for the device token against the timestamp!
Just started using this library - works great for me!
https://github.com/mac-cain13/notificato

Categories