5
0
mirror of https://github.com/cwinfo/envayasms.git synced 2024-09-18 21:39:34 +00:00

PHP server library with example implementation; add 'check now' button on menu

This commit is contained in:
Jesse Young 2011-09-12 16:53:38 -07:00
parent d5b973411b
commit cb8d95ebd5
16 changed files with 442 additions and 29 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "server/php/example/httpserver"]
path = server/php/example/httpserver
url = git://github.com/youngj/httpserver.git

View File

@ -6,4 +6,7 @@
<item android:id="@+id/test"
android:icon="@drawable/icon"
android:title="@string/test" />
<item android:id="@+id/check_now"
android:icon="@drawable/icon"
android:title="@string/check_now" />
</menu>

View File

@ -3,4 +3,5 @@
<string name="app_name">KalSMS 2</string>
<string name="settings">Settings</string>
<string name="test">Test</string>
<string name="check_now">Check Now</string>
</resources>

177
server/php/KalSMS.php Executable file
View File

@ -0,0 +1,177 @@
<?php
/*
* PHP server library for KalSMS
*
* For example usage see example/www/index.php
*/
class KalSMS
{
const ACTION_INCOMING = 'incoming';
const ACTION_OUTGOING = 'outgoing';
const ACTION_SEND_STATUS = 'send_status';
const ACTION_TEST = 'test';
const STATUS_QUEUED = 1;
const STATUS_FAILED = 2;
const STATUS_SENT = 3;
static function new_from_request()
{
$version = @$_SERVER['HTTP_X_KALSMS_VERSION'];
return new KalSMS();
}
static function escape($val)
{
return htmlspecialchars($val, ENT_QUOTES, 'UTF-8');
}
function get_request_action()
{
switch (@$_POST['action'])
{
case static::ACTION_INCOMING:
return new KalSMS_Action_Incoming($this);
case static::ACTION_OUTGOING:
return new KalSMS_Action_Outgoing($this);
case static::ACTION_SEND_STATUS:
return new KalSMS_Action_SendStatus($this);
case static::ACTION_TEST:
return new KalSMS_Action_Test($this);
default:
return new KalSMS_Action($this);
}
}
function get_request_phone_number()
{
return @$_SERVER['HTTP_X_KALSMS_PHONENUMBER'];
}
function is_validated_request($correct_password)
{
$signature = @$_SERVER['HTTP_X_KALSMS_SIGNATURE'];
if (!$signature)
{
return false;
}
$is_secure = (!empty($_SERVER['HTTPS']) AND filter_var($_SERVER['HTTPS'], FILTER_VALIDATE_BOOLEAN));
$protocol = $is_secure ? 'https' : 'http';
$full_url = $protocol . "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$correct_signature = $this->compute_signature($full_url, $_POST, $correct_password);
//error_log("Correct signature: '$correct_signature'");
return $signature === $correct_signature;
}
function compute_signature($url, $data, $password)
{
ksort($data);
$input = $url;
foreach($data as $key => $value)
$input .= ",$key=$value";
$input .= ",$password";
//error_log("Signed data: '$input'");
return base64_encode(sha1($input, true));
}
}
class KalSMS_OutgoingMessage
{
public $id = '';
public $to;
public $message;
}
class KalSMS_Action
{
public $type;
public $kalsms;
function __construct($kalsms)
{
$this->kalsms = $kalsms;
}
}
class KalSMS_Action_Test extends KalSMS_Action
{
function __construct($kalsms)
{
parent::__construct($kalsms);
$this->type = KalSMS::ACTION_TEST;
}
}
class KalSMS_Action_Incoming extends KalSMS_Action
{
public $from;
public $message;
function __construct($kalsms)
{
parent::__construct($kalsms);
$this->type = KalSMS::ACTION_INCOMING;
$this->from = $_POST['from'];
$this->message = $_POST['message'];
}
function get_response_xml($messages)
{
ob_start();
echo "<?xml version='1.0' encoding='UTF-8'?>\n";
echo "<Response>";
foreach ($messages as $message)
{
echo "<Sms id='".KalSMS::escape($message->id)."'>".KalSMS::escape($message->message)."</Sms>";
}
echo "</Response>";
return ob_get_clean();
}
}
class KalSMS_Action_Outgoing extends KalSMS_Action
{
function __construct($kalsms)
{
parent::__construct($kalsms);
$this->type = KalSMS::ACTION_OUTGOING;
}
function get_response_xml($messages)
{
ob_start();
echo "<?xml version='1.0' encoding='UTF-8'?>\n";
echo "<Messages>";
foreach ($messages as $message)
{
echo "<Sms id='".KalSMS::escape($message->id)."' to='".KalSMS::escape($message->to)."'>".
KalSMS::escape($message->message)."</Sms>";
}
echo "</Messages>";
return ob_get_clean();
}
}
class KalSMS_Action_SendStatus extends KalSMS_Action
{
public $status;
public $id;
function __construct($type)
{
$this->type = KalSMS::ACTION_SEND_STATUS;
$this->status = (int)$_POST['status'];
$this->id = $_POST['id'];
}
}

25
server/php/README.txt Executable file
View File

@ -0,0 +1,25 @@
PHP server library for KalSMS, with example implementation
The KalSMS.php library is intended to be used as part of a PHP application
running on an HTTP server that receives incoming SMS messages from, and sends
outgoing SMS messages to an Android phone running KalSMS.
To run the example implementation, the example/www/ directory should be made available
via a web server running PHP (e.g. Apache). You can also use the included standalone
PHP web server, by running the following commands:
git submodule init
php server.php
example/config.php contains the list of phone numbers and passwords for phones running KalSMS.
On a phone running KalSMS, go to Menu -> Settings and enter:
* Server URL: The URL to example/www/index.php.
If you're using server.php, this will be http://<your_ip_address>:8002/
* Your phone number: One of the phone numbers listed in example/config.php
* Password: The corresponding password in example/config.php
To send an outgoing SMS, use
php example/send_sms.php
See KalSMS.php and example/www/index.php

9
server/php/example/config.php Executable file
View File

@ -0,0 +1,9 @@
<?php
$PASSWORDS = array(
'16505551212' => 'rosebud',
'16505551213' => 's3krit',
);
$PHONE_NUMBERS = array_keys($PASSWORDS);
$OUTGOING_DIR_NAME = __DIR__."/outgoing_sms";

@ -0,0 +1 @@
Subproject commit 95a38ab08f14ac54dc64b6f6c008d7f69af8a8f4

41
server/php/example/send_sms.php Executable file
View File

@ -0,0 +1,41 @@
<?php
// invoke from command line
require_once __DIR__."/config.php";
$arg_len = sizeof($argv);
if ($arg_len == 4)
{
$from = $argv[1];
$to = $argv[2];
$message = $argv[3];
}
else if ($arg_len == 3)
{
$from = $PHONE_NUMBERS[0];
$to = $argv[1];
$message = $argv[2];
}
else
{
error_log("Usage: php send_sms.php [<from>] <to> \"<message>\"");
error_log("Examples: ");
error_log(" php send_sms.php 16505551212 16504449876 \"hello world\"");
error_log(" php send_sms.php 16504449876 \"hello world\"");
die;
}
$id = uniqid("");
$filename = "$OUTGOING_DIR_NAME/$id.json";
file_put_contents($filename, json_encode(array(
'from' => $from,
'to' => $to,
'message' => $message,
'id' => $id
)));
echo "Message $id added to outgoing queue\n";

46
server/php/example/server.php Executable file
View File

@ -0,0 +1,46 @@
<?php
/*
* Example standalone HTTP server that routes all .php URIs to PHP files under ./example_www,
* and routes all other URIs to static files under ./example_www.
*
* index.php is used as the directory index.
*
* Just run it on the command line like "php example_server.php".
*/
require_once __DIR__ . '/httpserver/httpserver.php';
class ExampleServer extends HTTPServer
{
function __construct()
{
parent::__construct(array(
'port' => 8002,
));
}
function route_request($request)
{
$uri = $request->uri;
$doc_root = __DIR__ . '/www';
if (preg_match('#/$#', $uri))
{
$uri .= "index.php";
}
if (preg_match('#\.php$#', $uri))
{
return $this->get_php_response($request, "$doc_root$uri");
}
else
{
return $this->get_static_response($request, "$doc_root$uri");
}
}
}
$server = new ExampleServer();
$server->run_forever();

View File

@ -0,0 +1,6 @@
<?php
require_once __DIR__."/config.php";
require_once __DIR__."/lib.php";
var_dump(get_outgoing_sms("16505551212"));

View File

@ -0,0 +1,91 @@
<?php
require_once dirname(__DIR__)."/config.php";
require_once dirname(dirname(__DIR__))."/KalSMS.php";
ini_set('display_errors','0');
// this example implementation uses the filesystem to store outgoing SMS messages,
// but presumably a production implementation would use another storage method
$kalsms = KalSMS::new_from_request();
$phone_number = $kalsms->get_request_phone_number();
$password = @$PASSWORDS[$phone_number];
if (!isset($password) || !$kalsms->is_validated_request($password))
{
header("HTTP/1.1 403 Forbidden");
error_log("Invalid request signature");
echo "Invalid request signature";
return;
}
$action = $kalsms->get_request_action();
switch ($action->type)
{
case KalSMS::ACTION_INCOMING:
error_log("Received SMS from {$action->from}");
$reply = new KalSMS_OutgoingMessage();
$reply->message = "You said: {$action->message}";
error_log("Sending reply: {$reply->message}");
header("Content-Type: text/xml");
echo $action->get_response_xml(array($reply));
return;
case KalSMS::ACTION_OUTGOING:
$messages = array();
$dir = opendir($OUTGOING_DIR_NAME);
while ($file = readdir($dir))
{
if (preg_match('#\.json$#', $file))
{
$data = json_decode(file_get_contents("$OUTGOING_DIR_NAME/$file"), true);
if ($data && @$data['from'] == $phone_number)
{
$sms = new KalSMS_OutgoingMessage();
$sms->id = $data['id'];
$sms->to = $data['to'];
$sms->from = $data['from'];
$sms->message = $data['message'];
$messages[] = $sms;
}
}
}
closedir($dir);
header("Content-Type: text/xml");
echo $action->get_response_xml($messages);
return;
case KalSMS::ACTION_SEND_STATUS:
$id = $action->id;
// delete file with matching id
if (preg_match('#^\w+$#', $id) && unlink("$OUTGOING_DIR_NAME/$id.json"))
{
echo "OK";
}
else
{
header("HTTP/1.1 404 Not Found");
echo "invalid id";
}
return;
case KalSMS::ACTION_TEST:
echo "OK";
return;
default:
header("HTTP/1.1 404 Not Found");
echo "Invalid action";
return;
}

View File

@ -16,8 +16,10 @@ import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.telephony.SmsManager;
import android.util.Log;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
@ -71,6 +73,22 @@ public class App {
context.sendBroadcast(broadcast);
}
public void checkOutgoingMessages()
{
String serverUrl = getServerUrl();
if (serverUrl.length() > 0)
{
log("Checking for outgoing messages");
new PollerTask().execute(
new BasicNameValuePair("action", App.ACTION_OUTGOING)
);
}
else
{
log("Can't check outgoing messages; server URL not set");
}
}
public void setOutgoingMessageAlarm()
{
AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
@ -212,4 +230,20 @@ public class App {
log("Sending " +sms.getLogName() + " to " + sms.getTo());
smgr.sendTextMessage(sms.getTo(), null, sms.getMessage(), sentIntent, null);
}
private class PollerTask extends HttpTask {
public PollerTask()
{
super(app);
}
@Override
protected void handleResponse(HttpResponse response) throws Exception {
for (OutgoingSmsMessage reply : parseResponseXML(response)) {
app.sendSMS(reply);
}
}
}
}

View File

@ -84,6 +84,7 @@ public class HttpTask extends AsyncTask<BasicNameValuePair, Void, HttpResponse>
String signature = this.getSignature(url, params);
post.setHeader("X-Kalsms-Version", "2");
post.setHeader("X-Kalsms-PhoneNumber", app.getPhoneNumber());
post.setHeader("X-Kalsms-Signature", signature);

View File

@ -53,7 +53,6 @@ public class IncomingMessageForwarder extends BroadcastReceiver {
String serverUrl = app.getServerUrl();
String message = sms.getMessageBody();
String sender = sms.getOriginatingAddress();
String recipient = app.getPhoneNumber();
app.log("Received SMS from " + sender);
@ -64,7 +63,6 @@ public class IncomingMessageForwarder extends BroadcastReceiver {
new ForwarderTask(sms).execute(
new BasicNameValuePair("from", sender),
new BasicNameValuePair("to", recipient),
new BasicNameValuePair("message", message),
new BasicNameValuePair("action", App.ACTION_INCOMING)
);

View File

@ -133,6 +133,9 @@ public class Main extends Activity {
case R.id.settings:
startActivity(new Intent(this, Prefs.class));
return true;
case R.id.check_now:
app.checkOutgoingMessages();
return true;
case R.id.test:
app.log("Testing server connection...");
new TestTask().execute(

View File

@ -3,40 +3,14 @@ package org.envaya.kalsms;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.apache.http.HttpResponse;
import org.apache.http.message.BasicNameValuePair;
public class OutgoingMessagePoller extends BroadcastReceiver {
private App app;
private class PollerTask extends HttpTask {
public PollerTask()
{
super(app);
}
@Override
protected void handleResponse(HttpResponse response) throws Exception {
for (OutgoingSmsMessage reply : parseResponseXML(response)) {
app.sendSMS(reply);
}
}
}
@Override
public void onReceive(Context context, Intent intent) {
app = App.getInstance(context);
String serverUrl = app.getServerUrl();
if (serverUrl.length() > 0)
{
app.log("Checking for outgoing messages");
new PollerTask().execute(
new BasicNameValuePair("from", app.getPhoneNumber()),
new BasicNameValuePair("action", App.ACTION_OUTGOING)
);
}
app.checkOutgoingMessages();
}
}