Monday, October 12, 2009

Pop Quiz

Here's a little test to separate the serious coders from the cut-and-paste script kiddies. Given the need to generate an arbitrarily long string consisting of random alpha-numeric characters, which solution is best?

Solution A:
function randomString($len) {
    $chars  = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" .
              "abcdefghijklmnopqrstuvwxyz" .
              "0123456789";
    $rndMax = strlen($chars) - 1;
    $str = "";
    while ($len-- != 0) {
        $str .= $chars[rand(0, $rndMax)];
    }
    return $str;
}

$str = randomString(8);
echo "$str\n";

Solution B:
class RandomSequenceIterator implements Iterator
{
    protected $seqMembers;
    protected $key;
    protected $limit;

    public function __construct() {
        $this->setMembers(null)
             ->setLimit(0)
             ->rewind();
    }

    protected function setMembers($strValue) {
        $this->seqMembers = $strValue;
        return $this;
    }

    protected function getMembers() {
        return $this->seqMembers;
    }

    protected function setLimit($intValue) {
        $this->limit = $intValue;
        return $this;
    }

    protected function getLimit() {
        if (empty($this->limit)) {
            return 0;
        }
        else {
            return $this->limit;
        }
    }

    public function current() {
        $index = rand(0, strlen($this->getMembers()) - 1);
        return substr($this->getMembers(), $index, 1);
    }

    public function valid() {
        return $this->key() < $this->getLimit();
    }

    public function key() {
        return $this->key;
    }

    public function next() {
        $this->key++;
    }

    public function reset() {
        $this->rewind();
    }

    public function rewind() {
        $this->key = 0;
    }
}

class RandomCharacterSequenceGenerator extends
RandomSequenceIterator
{
    public function setChars($strValue) {
        $this->setMembers($strValue);
        return $this;
    }

    public function getChars() {
        return $this->getMembers();
    }

    public function generate($limit) {
        $strBuffer = "";
        $this->setLimit($limit);
        foreach ($this as $char) {
            $strBuffer .= $char;
        }
        return $strBuffer;
    }
}

abstract class RandomGeneratorBase
{
    protected static $instance;
    protected static $generator;

    protected function __construct() {
        self::$generator = new
            RandomCharacterSequenceGenerator();
    }

    // abstract public static function getInstance();
    public static function getInstance() {
        if (empty(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function generate($limit) {
        return self::$generator->generate($limit);
    }
}

class RandomNumericStringGenerator extends RandomGeneratorBase
{
    const VALID_MEMBERS = "0123456789";

    protected function __construct() {
        parent::__construct();
        self::$generator->setChars(self::VALID_MEMBERS);
    }

    public static function getInstance() {
        if (empty(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

class RandomUpperCaseAlphaStringGenerator extends
RandomGeneratorBase
{
    const VALID_MEMBERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    protected function __construct() {
        parent::__construct();
        self::$generator->setChars(self::VALID_MEMBERS);
    }

    public static function getInstance() {
        if (empty(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

class RandomLowerCaseAlphaStringGenerator extends
RandomGeneratorBase
{
    const VALID_MEMBERS = "abcdefghijklmnopqrstuvwxyz";

    protected function __construct() {
        parent::__construct();
        self::$generator->setChars(self::VALID_MEMBERS);
    }

    public static function getInstance() {
        if (empty(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

class RandomMixedCaseAlphaStringGenerator extends
RandomGeneratorBase
{
    protected function __construct() {
        parent::__construct();
        self::$generator->setChars(
           RandomUpperCaseAlphaStringGenerator::VALID_MEMBERS .
           RandomLowerCaseAlphaStringGenerator::VALID_MEMBERS);
    }

    public static function getInstance() {
        if (empty(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

class RandomMixedCaseAlphaNumericStringGenerator extends
RandomMixedCaseAlphaStringGenerator
{
    protected function __construct() {
        parent::__construct();
        self::$generator->setChars(
            RandomNumericStringGenerator::VALID_MEMBERS .
            self::$generator->getChars());
    }

    public static function getInstance() {
        if (empty(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

$str = RandomMixedCaseAlphaNumericStringGenerator
       ::getInstance()
       ->generate(8);
echo "$str\n";
If you managed to scroll down this far, congratulations! You are a true programmer who knows the correct answer is B.

Solution A is a good example of unorganized spaghetti-code. It "works" but it's amateurish. A uses only language-primitives-- none of the advanced, enterprise architecture concepts embodied in Solution B. It is certainly not flexible. (What if one changed the requirements to generate a random string of only digits, for example?)

Solution B is much more organized, encapsulated, and extensible. Object-oriented code models the way things are in the "real world" so it's easier to conceptualize what the code is doing. The problem is broken down into several smaller, more manageable ones, and uses interfaces, abstract classes, and inheritance to eliminate redundant code, facilitate better organization, and foster code-reuse. Understanding existing code is an important part of programming and the liberal use of design patterns such as Singleton and Decorator embody solutions to common problems giving programmers a common vocabulary with higher levels of abstraction to communicate with one another.

Of course, solution B should only serve as an example and not be used as-is in a production environment. Exceptions should be used, but are omitted here for the sake of brevity. And the actual invocation to generate the random string could probably be further encapsulated and abstracted by implementing the Factory design pattern and maybe the Registry pattern-- one would query the registry for a value object to pass to the factory, and in turn the factory would determine which type of generator to return: random uppercase alpha, random numeric, mixed case, etc.

Wednesday, October 7, 2009

Setting Up SFTP From PHP

PHP's ftp_ssl_connect() function is for SSL-FTP, where as what I need for a client's application is SFTP. Isn't life grand! Well, it's not really too much trouble... PHP can handle that too with functions from the ssh2 PECL extension. I'm just glad I caught it early on instead of at the 11th hour. I figured though I might as well continue my previous post about setting up this project with a brief description on installing ssh2 and testing it to ensure everything is in working order.

Installation

The ssh2 extension provides bindings to libssh2 which must be installed first on the system. My target platform is CentOS 5.3, so I was able to install libssh2 and libssh2-devel via yum (using the RPMForge repository).
yum install libssh2 libssh2-devel
The ssh2 extension is available from the pecl.php.net website. There is a minor bug in the current version (0.11.0) which prevents it from compiling against PHP 5.3 so I needed to apply this patch.
wget http://remi.fedorapeople.org/ssh2-php53.patch
tar zxf ssh2-0.11.0.tgz
cd ssh2-0.11.0
patch < ../ssh2-php53.patch
I compiled and installed the extension after the code was patched.
phpize
./configure --with-ssh2
make
cp modules/ssh2.so /usr/local/php/lib/php/extensions/
Adding module=ssh2.so to php.ini and restarting Apache completed the installation. I was then ready to move on and test it to make sure it worked as it should.

Testing the Extension

Using the extension to upload and retrieve files programmatically over an SFTP connection is relatively simple. First, a secure shell connection is established with the SFTP server using ssh2_connect(), and then a login is authenticated with ssh2_auth_password(). Both functions return false if they fail:
$sess = @ssh2_connect(SFTP_SERVER, SFTP_PORT);
if ($sess === false) {
echo "Connection failed.";
exit(-1);
}
$result = @ssh2_auth_password($sess, SFTP_USERNAME,
SFTP_PASSWORD);
if ($result === false) {
echo "Unable to authenticate.";
exit(-1);
}
Once a session has been established, the ssh2_sftp() function is used to retrieve an SFTP resource.
$sftp = @ssh2_sftp($sess);
if ($sftp === false) {
echo "Unable to initialize SFTP subsystem.";
exit(-1);
}
From that point on, the SFTP resource is used with the ssh2.sftp filestream to read and write files.
file_put_contents("ssh2.sftp://" . $sftp . "/tmp/test.txt",
$data);
In writing my short automated test case, I dumped some bytes from /dev/urandom to generate a test file, wrote the data to the SFTP server, read it back, and compared the results to make sure they matched after the round-trip.
// generate random data for test file
$data = file_get_contents("/dev/urandom", FILE_BINARY, null,
0, 512);
$data = substr(convert_uuencode($data), 0, 512);

// write data to file on SFTP server
file_put_contents("ssh2.sftp://" . $sftp . "/tmp/test.txt",
$data);

// retrieve file from SFTP server
$retrieved = file_get_contents("ssh2.sftp://" . $sftp .
"/tmp/test.txt");

// compare original data with retrieve data
echo ($data == $retrieved) ? "Success!" : "Corruption!";