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]))

Reply via email to