(Freshly updated after a few fixes. Read on, source code included)
I've spent a couple of hours implementing the Nokia Image Upload API. I had already read through the protocol specs and they seemed straightforward enough, so it was quite a surprise to see my 7650 trying to post to an ASP (/login/uploaderLogin.asp) when the entire protocol document goes to great pains to be URL-independent.
Nothing to it, though. I just put up an .htaccess that reads:
<Files login>
AcceptPathInfo on
SetOutputFilter PHP
SetInputFilter PHP
LimitRequestBody 65535
</Files>
This invokes login as a PHP script, forces the URL path to be sent to the script as REQUEST_URI and sets a hard limit (Apache 2-specific, I think) on the upload size. No sweat. And the rest of the protocol is pretty straightforward - you GET or POST a specific URL, and you get plaintext with the info you want.
Then came the "late night hacking" bit. I'm dog tired, but here are a few notes: You login by POSTing four fields: Username, Password, Language ("EN"), and a protocol Version ("1.0"), getting this as a reply:
0 Version=1.0 SId=somesessionid RSURL=http://hostname/remoteStorageCapabilities
You can optionally return your image server capabilities right away, but I decided to implement things step-by-step. The next transaction the 7650 performs (after an unexplicable delay, since it seems to spend quite some time brooding) is a GET to the remoteStorageCapabilities URL, and it expects something like this:
0 CreateDirURL=http://hostname/createDir UploadURL=http://hostname/upload DirListURL=http://hostname/dirList
Then it tries to get a directory listing by GETting dirList and, no matter what I send back (the format is pretty straightforward), my 7650 complained of not being able to contact the server - that turned out to be due to the lack of a "Content-Length" header. After googling around for a bit after noon, I found a thread where some of this was being discussed, and proceeded to get "upload" working.
There is, however, a PHP bug that prevents me from getting this to work on my box, so I've decided to hold off for a bit until I can shift my mental gear box from scripting to RPM building and get 4.3.2 compiled on my development server.
For the curious, here is my code - a "harness" class, designed to test all of this before doing a proper implementation. It is a gross hack that maps URL bits straight to class methods, spitting out stuff to the error_log as it goes along. It uses the PHP session mechanism as "storage", so the 7650 creates "directories" and "files" on a session-based hash table that is persistent across requests (I was feeling too lazy to go off and implement a full-blown storage mechanism, and letting a development script write to your filesystem is a BAD idea any way you put it...).
Mind you, I can read serialize() output like other people read plaintext (so the debug output may need some work for non-PHP geeks), and I think in terms of hash tables and eval() (a Perlism that stuck with me). This code works entirely off /login/anythingGoesHere, since I couldn't bother setting up separate scripts, and I may have gotten something definetly wrong (It was started at around 2:30 AM, after all...). YMMV.
ini_set( "log_errors", "1" );
ini_set( "display_errors", "0" );
define( "SERVER_HOSTNAME", "change.me" );
class NokiaUploader {
function NokiaUploader() {
$aURLMap = array( "/uploaderLogin/" => "login",
"/RS/" => "remoteStorage",
"/CreateDir/" => "createDir",
"/Upload/" => "upload",
"/DirList/" => "dirList",
"/DirContents/" => "_default",
"/GetItem/" => "_default",
"/DeleteDir/" => "_default" );
$szEnd = basename( $_SERVER["REQUEST_URI"] );
error_log( serialize( $_SERVER ) );
error_log( serialize( $_POST ) );
foreach( $aURLMap as $szKey => $szMethod ) {
if( preg_match( $szKey, $szEnd ) ) {
if( $_SERVER["QUERY_STRING"] ) {
session_id( $_SERVER["QUERY_STRING"] ); // bind to the ID we generated upon login
session_start(); // retrieve session context
}
$szResponse = eval( "return \$this->" . $szMethod . "();" );
break;
}
}
error_log( serialize( $_SESSION ) );
$this->reply( $szResponse );
}
function login() {
$aURLs = array( "RS" );
$szBaseURL = "http://" . SERVER_HOSTNAME . dirname($_SERVER["PHP_SELF"]) . "/";
if( $this->authenticate() ) {
$szResponse = "0\r\nVersion=1.00\r\n";
$szResponse .="SId=" . $this->getsessionid() . "\r\n";
foreach( $aURLs as $szURL ) {
$szResponse .= $szURL . "URL=" . $szBaseURL . $szURL . "\r\n";
}
}
else {
$szResponse = "2\r\n";
}
return $szResponse;
} // login
function remoteStorage() {
$aURLs = array( "CreateDir", "Upload", "DirList" ); // mandatory
//$aURLs = array( "CreateDir", "Upload", "DirList", "DirContents", "GetItem", "DeleteDir" );
$szBaseURL = "http://" . SERVER_HOSTNAME . dirname($_SERVER["PHP_SELF"]) . "/";
$szResponse = "0\r\n";
foreach( $aURLs as $szURL ) {
$szResponse .= $szURL . "URL=" . $szBaseURL . $szURL . "\r\n";
}
return $szResponse;
} // remoteStorage
function authenticate() {
return true; // assume $_POST["Username"] , etc. was OK
} // authenticate
function getsessionid() {
$szSessionID = md5( $_POST["Username"] . $_POST["Password"] );
return $szSessionID;
} // getsessionid
function dirList() {
return $this->unserialize(); // do this someplace else for now
} // dirList
function createDir() {
$aStorage = $_SESSION["RS"];
$szID = md5( microtime() );
$aStorage[$szID] = array( "_name" => $_POST["DirName"],
"_desc" => $_POST["DirDesc"],
"_parent" => $_POST["Pid"] );
$szResponse = "0\r\nDid=$szID\r\n";
$_SESSION["RS"] = $aStorage;
return $szResponse;
} // createDir
function upload() {
// just say OK and see the data in the $_POST dump for now
// $_FILES holds the uploaded file data, if any
$szResponse = "0\r\nFree=" . 1024*1024 . "\r\n" . "Id=" . md5( microtime() );
return $szResponse;
} // upload
function _default() {
error_log( $_SERVER["PHP_SELF"] );
}
function unserialize() {
$aStorage = $_SESSION["RS"];
if( empty( $aStorage ) )
return "0\r\n0\r\n";
$szResponse = "0\r\n";
$szResponse .= count($aStorage) . "\r\n2\r\n";
foreach( $aStorage as $szDirectoryID => $aDirectory ) {
$szResponse .= "DId=" . $szDirectoryID . "\r\nDirName=" . $aDirectory["_name"] . "\r\n";
}
return $szResponse;
} // unserialize
function reply( $szResponse ) {
//header( "Content-Type: text-plain; charset=utf-8" );
$szResponse = utf8_encode( $szResponse );
header( "Content-Length: " . strlen( $szResponse ) );
echo $szResponse;
error_log( $szResponse );
} // reply
}
$oUploader = new NokiaUploader();
Feel free to send me any thoughts on this by e-mail (rui dot carmo at a domain called accao.net), but I'm not going to pursue this much further for a few days (maybe weeks), unless I can get PHP 4.3.2 compiled sooner.
Update: PHP 4.3.2 fixed the file upload issue I had - the code works, and might be the simplest available PHP example for dealing with Series 60 image uploads.