(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.