Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

A CGI Framework in Python (Web Techniques, Feb 1998)


"A CGI Framework in Python"
by  A. M. Kuchling
Web Techniques,  February 1998

Web Techniques grants permission to use these listings and code for private or
commercial use provided that credit to Web Techniques and the author is
maintained within the comments of the source. For questions, contact
[email protected].       

This file consists of five listings that accompany the article " A CGI Framework in Python ", 
published in the February 1998 issue of Web Techniques:


LISTING ONE

#!/usr/bin/python
# showpage.cgi : The top-level driver for our development framework

import os, sys

# Ensure that a path has been provided
if not os.environ.has_key('PATH_INFO'):
    print "Content-type: text/html\n"
    print "<P>No path information provided for showpage.cgi."
    sys.exit(0)

# Determine the root of the server's document tree
try:
    doc_root = os.environ['DOCUMENT_ROOT']
except KeyError: 
    doc_root = '/www/'

# os.path.join combines a path and a filename and returns
# the resulting filename.
filename = os.environ['PATH_INFO']
filename = os.path.join(doc_root, filename[1:] )

# Set up the 
import cgi
headers = {'Content-type':'text/html'}
webvars = cgi.FieldStorage()

namespace = {
             'headers': headers,
             'webvars': webvars,
             'environ': os.environ
            }

import StringIO
real_stdout = sys.stdout
sys.stdout = StringIO.StringIO()

try:
    execfile(filename, namespace)
except:
    # The page's code raised some sort of exception.  
    # Write information about the crash to a file, and output
    # an error page
    real_stdout.write("""Content-type: text/html

<P>The site is having technical difficulties with this page.
An error has been logged, and the problem will be fixed as soon 
as possible.  Sorry!""")

    import traceback, time
    sys.stderr.write('Error while executing script %s\n' % (filename) )
    traceback.print_exc(file=sys.stderr)
    sys.exit(0)

else:
    for header, value in headers.items():
        real_stdout.write("%s: %s\n" % (header, value) )
    real_stdout.write('\n')
    real_stdout.write( sys.stdout.getvalue() )

LISTING TWO



#  User registration module

Error = "User.Error exception"

# The following function will open a GDBM database and return the
# database object; a module-level private variable is used so that 
# the file will only be opened once.

_gdbmDB = None                          
def _openDB():
    global _gdbmDB
    if _gdbmDB == None: 
        import gdbm
        _gdbmDB = gdbm.open('userDatabase', 'c', 0600)
    return _gdbmDB
class User:
    "The fundamental class that represents a user"

    # The constructor for a User object; if more fields were 
    # added, they should be initialized here.
    def __init__(self, userID="", password=""):
        self.userID, self.password = userID, password

    
    def getCookie(self):
        "Produce the string required to set a cookie containing a login token"
        token = makeToken(self.userID)
        return 'userID=%s; path=/' % (token,)

    def save(self):
        "Save the object's current state into the database"
        import pickle
        db = _openDB()
        db[ self.userID ] = pickle.dumps(self)


# This is some super-secret information, hashed together 
# with the userID to generate a (hopefully) unforgeable 
# token.  It's some random bytes from /dev/urandom on my Linux box.
_secretInfo = "\016\327\132\154\215\256\373\023\362\132\047\023"
               
def makeToken(userID):
    "Given a user ID, compute the hash and return the resulting token"
    import md5, base64, string
    digest = md5.new(_secretInfo + userID + _secretInfo).digest()

    # The token will contain the user ID and the digest, separated by /
    token = userID + '/' + digest

    # To avoid illegal characters in the digest, the token is base-64 encoded.
    token = base64.encodestring(token)
    # base64.encodestring adds a newline to its result, so we have to 
    # strip it off
    return string.strip(token)

def checkToken(token):
    """Given a user ID and a token, verify that the digest portion
    matches the user ID.  Returns false if it fails, and the user ID
    if it succeeds."""
    import md5, base64, string

    # Undo the base-64 encoding
    token = base64.decodestring(token+'\n')

    slash = string.find(token, '/')
    if slash == -1:
        return ""   # Malformed authentication token

    # Check that the digest matches.
    userID = token[:slash]
    digest = md5.new(_secretInfo + userID + _secretInfo).digest()
    if digest!=token[slash+1:]:
        return ""  # Digest portion of token doesn't match

    # The token checks out.
    return userID
    
def createUser(userID, password, headers):
    """Create a new user with the given ID and password.  
    Raises User.Error if the name's already taken, or if 
    the password fails some simple checks.  A cookie is 
    handed out containing the authentication token."""

    import regex
    db = _openDB()
    if db.has_key(userID):
        raise Error, "There's already a user by that name."
    elif password == "":
        raise Error, "You can't leave the password empty."
    elif password == userID:
        raise Error, "It's a bad idea to use your ID as your password."
    elif regex.match('^[A-Za-z_0-9]+$', userID) == -1:
        raise Error, "User IDs can only contain letters, numbers, and underscores."
    
    # It looks like it's OK to create the user, so create a new User 
    # object and call its save() method, telling the object to store itself
    # in the database.
    userObj = User(userID, password)
    userObj.save()

    token = makeToken(userID)
    headers['Set-Cookie'] = userObj.getCookie()
    return userObj
def loginUser(userID, password, headers):
    """Log in a user; this requires checking the password, and 
    handing out a cookie containing the authentication token."""

    db = _openDB()
    import pickle
    if not db.has_key(userID):
        raise Error, "There's either no such user, or the password is incorrect."
    userObj = pickle.loads( db[userID] )
    if password != userObj.password:
        raise Error, "There's either no such user, or the password is incorrect."

    # It's OK, so drop a cookie on the user
    headers['Set-Cookie'] = userObj.getCookie()
    return userObj

def getCurrentUser(environ):
    """Read the cookie to determine the user's ID, and returns their
    User object.  Returns None if no ID could be determined."""    
    try:
        cookie = environ['HTTP_COOKIE']
    except KeyError:
        return None                     # No cookie present

    # There's a complete cookie module available for Python, but we'll
    # just use a regex to get the cookie's value.  The following
    # regular expression looks kind of ugly because the regex module
    # uses an Emacs-like regex syntax. In Python 1.5, the new re
    # module will provide regular expressions that support almost all
    # the features of Perl's regexes.

    import regex
    IDpat = regex.compile('userID[ \t]*=[ \t]*\([^ \t;]*\)')
    if IDpat.search(cookie) == -1:
        # No match for the regex could be found
        return None

    # Get the token, and verify that it's correct.  If it is, 
    token = IDpat.group(1)
    userID = checkToken(token)
    if userID: 
        db = _openDB()
        import pickle
        if not db.has_key(userID):
            return None
        return pickle.loads( db[userID] )

    

LISTING THREE


# register.py : Create a new user object

import User
if not (webvars.has_key('userID') and webvars.has_key('password') ):
    print "<P>You must fill in both the user ID and the password fields."
else:
    userID, password = webvars['userID'].value, webvars['password'].value
    try:
        userObj = User.createUser(userID, password, headers)
    except User.Error, message:
        print "<P>", message
    else:
        print "<P>Thank you for joining."



LISTING FOUR


# login.py: Log in as an existing user.  This file is almost identical
#           to register.py, except that it calls User.loginUser() and 
#           not User.createUser(), and the success message is different.

import User
if not (webvars.has_key('userID') and webvars.has_key('password') ):
    print "<P>You must fill in both the user ID and the password fields."
else:
    userID, password = webvars['userID'].value, webvars['password'].value
    try:
        userObj = User.loginUser(userID, password, headers)
    except User.Error, message:
        print "<P>", message
    else:
        print "<P>Welcome back, %s!" % (userObj.userID,)



LISTING FIVE


# info.py : A sample page that demonstrates how to find out 
#           who the current user is.

import User
userObj = User.getCurrentUser(environ)

if userObj != None:
    print "<P>This page would be customized for user '%s'." % (userObj.userID,)
else:
    print """<P>You're not logged in as a registered user, so you'd see
			this page without any customizations."""




Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.