Daniel Rench

Web application development : Servers : Networks : E-Mail : DNS : Databases : Programming for hire

previous : contact : linkedin : code : links : pictures : facebook : twitter : next

Transmutable (?) Objects in Perl and Ruby

I have no idea if "transmutable" is the right word to describe this concept but I'm using it anyway.

In Perl, I sometimes find it useful to "transmute" objects. For example:

sub Grape::new {
	my $class = shift;
	bless {}, $class;
}

sub Grape::dehydrate {
	my $self = shift;
	bless $self, 'Raisin';
}

sub Raisin::new {
	my $class = shift;
	bless {}, $class;
}

my $x = Grape->new();
print ref($x), "\n"; # prints "Grape"
$x->dehydrate();     # it's not a Grape anymore
print ref($x), "\n"; # prints "Raisin"
$x->dehydrate();     # Raisin doesn't have a dehydrate() method, so it dies

To do this in Ruby, I first tried something like this:

class Grape
	def dehydrate
		self = Raisin.new
	end
end

But Ruby complains "Can't change the value of self". I ended up trying something with Ruby's version of AUTOLOAD, method_missing:

class Grape
	def initialize
		@me = Hidden::Grape.new
	end
	def method_missing(method, *args)
		r = @me.method(method).call(*args)
		@me = r if /^Hidden::/.match(r.class.to_s)
		return r
	end
	def class
		@me.class.to_s.sub(/^Hidden::/, '')
	end
	def methods
		@me.methods
	end
end

module Hidden
	class Grape
		def dehydrate
			Raisin.new
		end
	end
	class Raisin
	end
end

x = Grape.new
puts(x.class) # prints "Grape"
x.dehydrate
puts(x.class) # prints "Raisin"
x.dehydrate   # NameError: undefined method `dehydrate' for class `Hidden::Raisin'

Various things seem wrong about it (especially overriding class() and methods()), but it pretty much does what I want.

11/15/2006 update: I no longer think the above seems "wrong". If anything it's probably considered insufficiently weird in Ruby circles.

The Cookie Hack: using mod_perl to make IE7 think you're not using Basic Auth (even though you are)

You may have heard that the forthcoming MSIE 7 web browser made some "enhancements" for Basic Authentication (warning or optionally disallowing it unless used over SSL). You could buy a certificate or try converting to digest auth or you could implement a cookie hack. It goes something like this (assuming you're running Apache 1.3 with mod_perl):

  • Create an ErrorDocument 401 with an external redirect to an HTML page with a login form. Normally it's a bad idea to put external redirects on your 401 documents (because it turns them into 302s [or is it 301s?]). Anyway, it's still a bad idea but that's OK because this whole thing is a bad idea.
  • Create a "login form" page that looks something like this:

    <script>
    window.onload = function () {
      var F = document.forms['login'];
      F.onsubmit = function () {
        document.cookie =
         "auth=" + btoa(F['username'].value + ':' + F['password'].value);
        location.href = 'http://example.net/URL/THAT/REQUIRES/LOGIN';
        return false; // probably not necessary
       };
     };
    </script>
    
    <form name="login">
     <input type="text" name="username">
     <input type="password" name="password">
     <input type="submit" value="log in">
    </form>
    

    The above uses the little-known btoa() function. It encodes to base64. Too bad MSIE doesn't support it. You'll need to find or write your own base64 encoder. But then you've just screwed all the browsers that aren't running javascript. So then you write yourself some server-side CGI/PHP/ASP/JSP code that sets the cookie instead. Whatever you do, the result is a cookie called "auth" with a value that should look just like the right side of a typical "Authorization: Basic" HTTP header.

  • Here's the fun hack part. Write a mod_perl HeaderParserHandler that does something like this:

    use Apache::Constants 'DECLINED';
    
    sub handler {
    	my $r = shift;
    	my $h = $r->headers_in();
    	return DECLINED if $h->{Authorization};
    	if (my $c = $h->{Cookie}) {
    		for (split /\s*,\s*/, $c) {
    			if (m{\bauth=([a-zA-Z\d\+/]+)}) {
    				$h->{Authorization} = "Basic $1";
    				last;
    			}
    		}
    	}
    	DECLINED;
    }
    

    Now when Apache sees one of our "auth" cookies coming in, we make it think that you actually sent an "Authorization" header. If it's correct, it lets you in. If it's wrong, you get a 401 and get redirected back to your login page.

It's just as "secure" as always (that is, not at all) but it does have some advantages over basic auth:

  1. Delete the cookie, and you're logged out. No need for a browser shutdown as in basic
  2. Add an expiration date to the cookie, and your login "session" can survive a browser shutdown
  3. Set the domain of the cookie to .example.com and suddenly you don't have to maintain separate logins for http://www.example.com/ and http://example.com/ perhaps even https://www.example.com/

If you really want to run with this, you could work around the insecurity by encrypting the the username and password on the client side and expanding the mod_perl handler to decrypt before setting up the fake auth header. You'll have to figure out how to pass the key securely.

August 2006 Chicago-area Community Calendar

The Minutemen and their back catalog

Seeing We Jam Econo reminds me that the Minutemen need a reissue program. Here's how it should go:

Front cover of the My First Bells cassette
  1. A double CD reissue of the previously cassette-only My First Bells

    Though the original cassette sounds terrible, this compilation of their releases from 1980 to 1983 (mostly) in order, works as an album much better than any of the Post-Mersh CDs that came later. Side (or in this case, disc) 2 is especially good, starting with "Bob Dylan Wrote Propaganda Songs" and ending with "Little Man With a Gun in His Hand". Both songs' impact is blunted coming halfway through their respective CD compilations.

    If you have the three Post-Mersh CDs and download some stray compilation tracks, you can nearly recreate My First Bells in your own home (you'll still be missing "Clocks" from the Chunks compilation). Try it and you too will consider this to be the finest Minutemen collection, with the possible exception of...

  2. A (double, if necessary) CD reissue of the complete, original Double Nickels on the Dime

    All of the previous CD editions of this album are missing songs. Again, let the corndogs help you reproduce the experience (though you will still be missing most of the side ending and beginning "car jams").

  3. 1985

    Put both of their "commercial" records from 1985 together: Project: Mersh and 3-Way Tie (For Last), in that order on a single CD.

  4. A double "deluxe" CD reissue of Ballot Result

    This would include all the tracks from the original double LP, the cassette bonus tracks, and then pile on all the other odds & ends (possibly expanding to 3 discs): The Politics of Time LP, the Tour-Spiel EP, the Georgeless EP, and more Reactionaries material.

All four will come with full-color reproductions of all the original jacket art, obligatory Byron Coley liner notes, and won't be on SST.

"Vim" is a synonym for "excitement"

  1. To get matchit working with the FreeBSD port of vim:
    % echo "filetype plugin on" >> ~/.vimrc
    

    You might think you would need to include something like "runtime plugins/matchit.vim" but you would be wrong, as usual.

  2. To get all your apps that use the GNU readline library to use vi-style command line editing:
    % echo "set editing-mode vi" >> ~/.inputrc
    
  3. Zsh users already know about:
    % echo "bindkey -v" >> ~/.zshrc
    

Javascript "namespaces"

It's not a new concept, but it's a useful and underused one. If you're not familiar, it goes like this:

You choose a single identifier (let's call it "X" though you would of course pick something less generic), define it as an object (or "hash" or "associative array" if that's your bag) and define all your functions, variables, and objects as properties (or "keys" if that's how you swing) of "X":

var X = {
		a: "just a string",
		b: function (x) { return true },
		c: [ 'zero', 'one', 'two', 'three' ],
		d: { eins: 1, zwei: 2, drei: 3 }
	};

Now you can pull in scripts from all over and you only need to worry about one name conflict ("X" in this case; again this is why you will pick something more distinctive).

You also get a simple "import" technique as a side-effect. I used it on a recent project. I defined the "X" object in the main window, but I also needed to have code run inside a child iframe. All I had to do was to place this as the first line of code in the iframe:

var X = parent.X;

You could do something similar for a popup window ("window.opener.X" probably).

Another thing that this namespace concept lets you do is use the rarely used "with" statement, if you're into that kind of thing:

with(X) { alert(d.zwei); }

But don't do that unless you want to anger people like Mr. JSON

Another Perl dispatch table technique

This guy brings up dispatch tables, as discussed in the book Higher-Order Perl. Here's his example:

$dispatch_table = {
    VERBOSITY => sub { $VERBOSITY = shift } ,
    LOGFILE => \&config_LOGFILE,
    CONFIGFILE => \&config_CONFIG_LOGFILE,
    LOCAL_CONFIG => \&config_LOCALCONFIG
};

It's an improvement over lots of if/elses but it still has problems (I've run into code like this before so I'm not just picking on this example). Since there's no consistency between the name of the hash keys and functions, it makes things unnecessarily complicated. How about:

$dispatch_table = {
	VERBOSITY => \&config_VERBOSITY,
	LOGFILE => \&config_LOGFILE,
	CONFIGFILE => \&config_CONFIGFILE,
	LOCAL_CONFIG => \&config_LOCAL_CONFIG,
};

But it still can be better. Avoid name consistency issues by giving the functions no names at all:

my $dispatch_table = {};
$dispatch_table->{VERBOSITY} = sub { ... };
$dispatch_table->{LOGFILE} = sub { ... };
$dispatch_table->{CONFIGFILE} = sub { ... };
$dispatch_table->{LOCAL_CONFIG} = sub { ... };

I tried another technique in a recent project, though I don't know if it's necessarily an improvement, just different:

sub Action::do_this { ... };
sub Action::do_that { ... };
sub Action::do_another_thing { ... };

my $action = bless \%args, 'Action';
if (my $f = $action->can($some_operation)) {
	$f->();
} else {
	die "action '$some_operation' not implemented";
}

iPod wire repair hint: put fire on it

"We" had an incident (actually a bizarre gardening accident) yesterday that resulted in a cut iPod earbud wire.

I'd hoped to fix it with a quick wire strip/solder/tape job but the wires inside (4 in total) were covered in some non-conductive material in lieu of the strippable plastic casings I expected. I tried scraping it off but it was tedious and didn't seem to be working.

The eventual solution: separate the wires, then burn the ends with a candle flame. Solder, tape, and continue "rocking".

Antony & the Johnsons: Smells Pretty Good

I checked out their acclaimed "I Am A Bird Now" from my local public library recently. If I were blind and deaf this would definitely be among my picks for best album of 2005; its packaging smells great, like a freshly opened indie vinyl record from the mid-to-late 1980s ("Jacket Made In Canada" redux; it is on Secretly Canadian).

Can't say I dig the music though, Lou Reed endorsement or not (suspect anyway, considering his approval of Pearl Jam).

Javascript strftime

<script type="text/javascript" src="http://dren.ch/js/strftime.js"></script>
<script type="text/javascript">
 var d = new Date();
 alert(d.strftime('Today is %A %B %e, day %j of the year %Y'));
</script>

Please refer to the FreeBSD strftime documentation if you are not familiar with the format codes.

"Known issues":

  • I made no attempts at i18n or l10n
  • Z and z worked under spidermonkey in a kludgy way and broke under IE and Safari, so they're commented out for now
  • Still missing %U, %V, and %W, and I also made no attempt to implement %E* and %O*
  • As a bonus of sorts, Number objects get a new pad() method (read the code if you're interested)
  • Overall it's only "lightly tested"
  • Somebody already did this
January 25, 2008 update: I never specified a license for http://dren.ch/js/strftime.js; it is public domain.

older things