# The Nokia Image Upload Protocol

(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
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" );

$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 );
}

$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

// 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;

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 );
\$oUploader = new NokiaUploader();