This program had a couple of those forehead-slapping moments. In early November, I'd moved my web pages from www.dnaco.net to www.canonical.org, but www.dnaco.net was still getting hits --- one of my pages referred explicitly to its imagemap.cgi program, because it had a server-side imagemap in it. Forehead slap number one.
Well, I figured maybe I should install imagemap.cgi on www.canonical.org. But after searching for a while, I found a lot more about security holes in imagemap.cgi (Rob McCool wrote it, so it was full of buffer overflows) than about where to download it. So I figured I'd write my own, in Python. So I took an afternoon and wrote it and got it working and installed it. Then I thought I'd see if I could find documentation on the file format. And I discovered that Apache, which is what we use on www.canonical.org, ships with a module that has the functionality of imagemap.cgi. Forehead slap number two. I was insufficiently lazy and did hours of work for no good reason --- someone else had already done them! (For people who are mystified by the references to forehead slapping: people from the US sometimes slap their foreheads when they learn something that surprises them, especially when it demonstrates how stupid they have been before.) So I added another interface to this program, one to convert server-side imagemaps to client-side HTML imagemaps. (Someone probably already did that, too; I didn't look) I used this to improve the web page. So here's pymagemap.py, which works as a CGI script if you run it from the command line: #!/usr/bin/env python # CGI script for imagemaps, to replace the old C imagemap program from NCSA. # Uses PATH_TRANSLATED to get the map file. Parses '#' comment lines, # 'default url' lines, and 'rect url x,y x2,y2' lines to come up with # an image map; then it fetches the x,y QUERY_STRING and generates a redirect. import os, sys, string, re, string def startswith(haystack, prefix): length = len(prefix) return haystack[:length] == prefix def complain(str): print "Content-type: text/plain\n\n" + str + "\n" sys.exit(-1) def getcoords(source, str): coords = string.split(str, ',') if len(coords) != 2: complain("%s: weird coords %s in %s" % (sys.argv[0], str, source)) return map(int, coords) def distsq(x1, y1, x2, y2): dx = x1 - x2 dy = y1 - y2 return dx*dx + dy*dy blanklinere = re.compile('\s*$') class Map: def __init__(self, filename): f = open(filename) self.rects = [] for line in f.readlines(): if startswith(line, '#') or blanklinere.match(line): continue fields = string.split(line) if fields[0] == 'default': self.default = fields[1] elif fields[0] == 'rect': url, corner1, corner2 = fields[1:] x1, y1 = getcoords("%s line %s" % (filename, line), corner1) x2, y2 = getcoords("%s line %s" % (filename, line), corner2) # let them be any two corners, not just upper left and lower # right, in that order if x1 > x2: x1, x2 = x2, x1 if y1 > y2: y1, y2 = y2, y1 self.rects.append((url, x1, y1, x2, y2)) else: # don't want to send this to the web browser # it might reveal secret file contents sys.stderr.write("%s: Can't understand line %s in mapfile %s" % (sys.argv[0], line, filename)) complain("Bad mapfile --- see server error log for details\n") def geturl(self, x, y): # possible shapes possibles = [] # find the rectangles the coords are in for rect in self.rects: url, x1, y1, x2, y2 = rect if x1 < x and x < x2 and y1 < y and y < y2: possibles.append((url, (x1 + x2)/2, (y1 + y2)/2)) # of all the shapes the coords are in, return the one whose center # is closest mindist = None besturl = self.default for shape in possibles: url, centerx, centery = shape dist = distsq(x, y, centerx, centery) if mindist is None or mindist > dist: mindist = dist besturl = url return besturl def html(self, name): def htmlrect(rect): url, x1, y1, x2, y2 = rect return (' <area shape="rect" coords="%d,%d,%d,%d" href="%s" />\n' % (x1, y1, x2, y2, url)) return ('<map name="%s">\n' % name + string.join(map(htmlrect, self.rects), '') + "</map>\n") def main(): # default to no query string qs = os.environ.get('QUERY_STRING', '') # can't do that with PATH_TRANSLATED if os.environ.has_key('PATH_TRANSLATED'): mapfile = os.environ['PATH_TRANSLATED'] else: complain("No PATH_TRANSLATED provided.\n" + "You need to put something after %s in the URL.\n" % sys.argv[0]) map = Map(mapfile) if qs == '': url = map.default else: coords = getcoords("user input", qs) url = map.geturl(coords[0], coords[1]) print "Location: %s\n\n" % url if __name__ == '__main__': main() Here's pymagemap.cgi, which is useful if your web server likes CGI scripts to have filenames that end in .cgi: #!/usr/bin/env python import pymagemap pymagemap.main() And here's imagemap2html: #!/usr/bin/env python import sys, pymagemap map = pymagemap.Map(sys.argv[1]) sys.stdout.write(map.html(sys.argv[2]))