Author Archive

Wednesday, August 06th, 2008

So I was writing this tool to create a bunch of SQL statements from a data dump. Simple enough, right. And as always when you generate SQL statements, you have to make sure that the data doesn’t interfere with the SQL syntax by escaping the single quotes (and generally any binary data, but I didn’t have that). Any database gem/module/library has that built-in, of course, but I didn’t want to use that. So I said [Note: this doesn't work. Read on for the solution.]

def quote (str)
  str.gsub('\\','\\\\').gsub('\'','\\\'')
end

I read this as “replace all backslashes with double backslashes, and then replace all single quotes with a backslash and a single quote”. I added a simple test for it (yay TestUnit!):

def setup
  @m = Migrate.new
end

def test_quote
  assert_equal("I\\'m home", @m.quote("I'm home"))
end

But imagine my surprise when I got

  1) Failure:
test_quote(TestMigrate) [migration/test_migrate.rb:29]:
< "I\\'m home"> expected but was
< "Im homem home">.

Ooookay. What’s wrong here? Have I misunderstood the rules for escaping the escape sequence? It’s supposed to be easier with single quotes, but maybe I got it wrong. So I tried with double quotes:

def quote (str)
  str.gsub("\\","\\\\").gsub("'","\\'")
end

Surely this would work? Nope, it gives the exact same error. Time to look up gsub in the manual:

str.gsub(pattern, replacement) => new_str
str.gsub(pattern) {|match| block } => new_str

[…] If a string is used as the replacement, special variables from the match (such as $& and $1) cannot be substituted into it, as substitution into the string occurs before the pattern match starts. However, the sequences \1, \2, and so on [my emphasis] may be used to interpolate successive groups in the match.

“And so on”? Oh, so obviously \' (escaped \\' in the string literal) is the replacement string equivalent of $', which means everything afther the match (as all regexp hackers know). So I need to escape the backslash for regexp engine too:

def quote (str)
  str.gsub("\\","\\\\").gsub("'","\\\\'")
end

OK, the tests pass. But the code looks wrong. Four backslashes can’t work for both cases, can they? Let’s add a test case:

def test_quote
  assert_equal("I\\'m home", @m.quote("I'm home"))
  assert_equal("S\\\\N", @m.quote("S\\N"))
end

Nope, that fails. So we need this:

def quote (str)
  str.gsub("\\","\\\\\\\\").gsub("'","\\\\'")
end

Eight backslashes. Yes, the test passes, but is it worth it? Is it understandable? I don’t want comments to explain my code. Comments are good to provide a raison d’être for something, but not to explain its looks. Let’s switch to the other form of gsub:

def quote (str)
  str.gsub(/\\|'/) { |c| "\\#{c}" }
end

“If you see a backslash or a single quote, replace it with a backslash and whatever you saw.” That’s what I wanted to say anyway.

Good. But I wrote this in Markdown, so now I have to generate the HTML and the go through it and make sure that I restore whatever backslashes Markdown ate. (It turns out it didn’t eat any. TextMate has a Markdown Preview function that ate a lot of backslashes, but when I said “Convert to HTML” it didn’t eat any at all. Go figure.)

Category: Ruby  | Tags: , , ,  | 9 Comments
Wednesday, August 06th, 2008

It’s time for the yearly survey for web professionals:

But that’s hardly a note to self.  Looks like I’m turning this into a blog.

Category: The daily grind  | Comments off
Sunday, July 27th, 2008

Installing the mysql gem for Ruby is almost always a mess, as witnessed by the large number of hits one get when googling for “mysql gem leopard”.  To make the gem work with your platform, you need to ensure that the ‘make’ command run internally by ‘gem’ has the right ARCHFLAGS, which most of the web pages will tell you about.  So

sudo env ARCHFLAGS='-arch i386' gem install mysql

should handle it, more or less.  But since Leopard (the client version) does not have mysql, you will have had to install that on your own (I installed the MySQL binary package for 10.4, from dev.mysql.com, because there was no 10.5 package available the day after I Leopard was released), and ‘gem’ isn’t good at guessing where it is.  So I need to say

sudo env ARCHFLAGS='-arch i386' gem install mysql -- --with-mysql-dir=/usr/local/mysql

But for some reason that is not quite enough either.  I get this error:

/Library/Ruby/Gems/1.8/gems/mysql-2.7/lib/mysql.bundle:
dlopen(/Library/Ruby/Gems/1.8/gems/mysql-2.7/lib/mysql.bundle, 9):
Library not loaded: /usr/local/mysql/lib/mysql/libmysqlclient.15.dylib (LoadError)

  Referenced from: /Library/Ruby/Gems/1.8/gems/mysql-2.7/lib/mysql.bundle

  Reason: image not found - /Library/Ruby/Gems/1.8/gems/mysql-2.7/lib/mysql.bundle
      from /Library/Ruby/Site/1.8/rubygems/custom_require.rb:32:in `require'
      from my_script.rb:6

Hey, look at that.  It actually tries to load the lib from `/usr/local/mysql/lib/mysql` rather than `/usr/local/mysql/lib`.  Why?  I don’t know.  I couldn’t be bothered to dive into the code, so I went for the quick workaround:

sudo ln -s . /usr/local/mysql/lib/mysql

Silly, and perhaps not the cleanest way to do it, but it works for me.

Friday, July 25th, 2008

I acquired this server in December 2006 to host a few Rails projects and one of the first things I did was to disable Apache and install Nginx.  Once I decided to use WordPress for this blog, I needed PHP, and while I could have tried to set up PHP for Nginx, I decided to go the easy route and use the familiar LAMP combo.  I have after all used Apache in various guises ever since it was the Cern httpd back in 1993, so it should be no match for me.

Not so.  But to make a long story short and save myself some time next time around, here are the mistakes I made:

Private DNS

My wardrobe server (on my home network) hosts a number of public-facing web servers; little things that I set up for friends before I started doing web work professionally.  So, for instance www.norbusam.org points to my fixed home IP, and all traffic on port 80 is forwarded to my server.  The WGR614 that I use for NAT and routing is not smart enough to let me use the public interface from the private part of the network, i.e. if I sit at my desk at home and enter www.norbusam.org into my web browser, I won’t get anywhere.  That would have annoyed me if I hadn’t liked the solution so much: I set up a DNS daemon on the home server and let it trick all home computers to use the internal interface (192.168.x.y) for norbusam.org and others.  Easy as pie.

This came back to bite me for hours on end last night.  notetoself.vrensk.com was pointing at my home server, so while I was hacking the Apache settings on this server and reloading my browser furiously, all I got was my home server.  And since both run Apache, I never got suspicious—it just looked like my changes didn’t take.  Ouch.

SELinux

This server is running Security Enhanced Linux out of the box.  I asked for a vanilla install of FC6, and apparently SELinux is switched on by default.  I decided to keep it on when I got the server since I saw it as a learning opportunity.  It caused me some problems when I set up Postfix when the server was new, but I sorted it out and haven’t thought about it since then.

This, too, came back to bite me for a couple of hours.  It turns out that the default setting of SE for Apache are somewhere between frugal and paranoid.  There are various security bits to be set or cleared, and the default setting assumes that I only want to publish pages from a root-blessed directory and that I don’t want to run any scripts, use a database or have httpd talk to any other servers out there.  Rather than go through all these settings one by one and reverse them (and risk disabling something that was actually permissive to start with) I decided to turn off SELinux for Apache:

# setsebool -P httpd_disable_trans 1

Followed by a restart (not reload) of Apache.  I’m indebted to an article at Begin Linux for this solution.  I can’t recommend reading the actual article though, as it is just a long recapitulation of man pages and other documentation without a trace of explanation or even a try to put things in context.

Oh well.  Next time I will make sure to double check my IPs and disable SELinux at least while setting up a system.

Friday, July 25th, 2008

While this is a public blog, its primary audience is not the meandering surfers of the inter-tubes, but myself.  I need someplace to store various findings and HOWTOs that I write for myself.  I assume that they will prove useful for others too, from time to time, but that is mostly a side-effect.

That being said, I might produce the occasional rant or Instruction For Your Betterment or something else intended for public consumption.  I will let you know.

Category: Meta  | Comments off