Section 17.7. Utility Modules


17.7. Utility Modules

This section presents the source code of the utility modules imported and used by the page scripts shown earlier. As installed, all of these modules live in the same directory as the CGI scripts, to make imports simplethey are found in the current working directory. There aren't any new screenshots to see here because these are utilities, not CGI scripts. Moreover, these modules aren't all that useful to study in isolation and are included here primarily to be referenced as you go through the CGI scripts' code. See earlier in this chapter for additional details not repeated here.

17.7.1. External Components and Configuration

When running PyMailCGI out of its own directory in the book examples distribution tree, it relies on a number of external modules that are potentially located elsewhere. Because all of these are accessible from the PP3E package root, they can be imported with dotted-path names as usual, relative to the root. In case this setup ever changes, though, the module in Example 17-10 encapsulates the location of all external dependencies; if they ever move, this is the only file that must be changed.

Example 17-10. PP3E\Internet\Web\PyMailCgi\externs.py

 ############################################################################# # Isolate all imports of modules that live outside of the PyMailCgi # directory, so that their location must only be changed here if moved; # we use a custom version of mailconfig.py here: a pymailgui2 subset ############################################################################# #from  PP3E.Internet.Email.PyMailGui import mailconfig import mailconfig from  PP3E.Internet.Email import mailtools  # PP3E/.. must be on your PYTHONPATH 

This module simply preimports all external names needed by PyMailCGI into its own namespace. See Chapter 15 for the content of the mailconfig module; as a reference, Example 17-11 lists part of its content again. This version of PyMailCGI has its own copy of this module, to allow it to differ from PyMailGUI. See Chapter 14 for the mailtools package modules' source code.

Example 17-11. PP3E\Internet\Email\mailconfig.py

 ############################################################################### # user configuration settings for various email programs (PyMailCGI version); # email scripts get their server names and other email config options from # this module: change me to reflect your machine names, sig, and preferences; ############################################################################### #------------------------------------------------------------------------------ # (required for load, delete) POP3 email server machine, user #------------------------------------------------------------------------------ popservername  = 'pop.earthlink.net'       # or starship.python.net, 'localhost' popusername    = 'pp3e'                    # password fetched or asked when run #------------------------------------------------------------------------------ # (required for send) SMTP email server machine name # see Python smtpd module for a SMTP server class to run locally # note: your ISP may require that you be directly connected to their system: # I can email through Earthlink on dial up, but cannot via Comcast cable #------------------------------------------------------------------------------ smtpservername = 'smtp.comcast.net'     # or 'smtp.mindspring.com', 'localhost' #------------------------------------------------------------------------------ # (optional) personal information used by PyMailGUI to fill in edit forms; # if not set, does not fill in initial form values; # sig  -- can be a triple-quoted block, ignored if empty string; # addr -- used for initial value of "From" field if not empty, # no longer tries to guess From for replies--varying success; #------------------------------------------------------------------------------ myaddress   = 'pp3e@earthlink.net' mysignature = '--Mark Lutz  (http://www.rmi.net/~lutz)' #------------------------------------------------------------------------------ # (optional) local file where sent messages are saved; # PyMailGUI 'Open' button allows this file to be opened and viewed #------------------------------------------------------------------------------ sentmailfile   = r'.\sentmail.txt'             # . means in current working dir 

17.7.2. POP Mail Interface

The loadmail utility module in Example 17-12 depends on external files and encapsulates access to mail on the remote POP server machine. It currently exports one function, loadmailhdrs, which returns a list of the header text (only) of all mail in the specified POP account; callers are unaware of whether this mail is fetched over the Net, lives in memory, or is loaded from a persistent storage medium on the CGI server machine. That is by designloadmail changes won't impact its clients. It is mostly a hook for future expansion.

Example 17-12. PP3E\Internet\Web\PyMailCgi\loadmail.py

 ################################################################### # mail list loader; future--change me to save mail list between # CGI script runs, to avoid reloading all mail each time; this # won't impact clients that use the interfaces here if done well; # for now, to keep this simple, reloads all mail for each list page; # 2.0: we now only load message headers (via TOP), not full msg, # but still fetch all hdrs for each index list--in-memory caches # don't work in a stateless CGI script, and require a real db; ################################################################### from commonhtml import runsilent         # suppress print's (no verbose flag) from externs    import mailtools         # shared with PyMailGUI # load all mail from number 1 up # this may trigger an exception import sys def progress(*args):  # not used     sys.stderr.write(str(args) + '\n') def loadmailhdrs(mailserver, mailuser, mailpswd):     fetcher = mailtools.SilentMailFetcher(mailserver, mailuser, mailpswd)     hdrs, sizes, full = fetcher.downloadAllHeaders( )     # get list of hdr text     return hdrs 

It's not much to look atjust an interface and calls to other modules. The mailtools.SilentMailFetcher class (reused here from Chapter 14) uses the Python poplib module to fetch mail over sockets. The silent class prevents mailtools print statements from going to the HTML reply stream (although any exceptions are allowed to propagate there normally).

In this version, loadmail loads just the header text portions of all incoming email to generate the selection list page. However, it still reloads headers every time you refetch the selection list page. As mentioned earlier, this scheme is better than the prior version, but it can still be slow if you have lots of email sitting on your server. Server-side database techniques, combined with a scheme for invalidating message lists on deletions and new receipts, might alleviate some of this bottleneck. Because the interface exported by loadmail would likely not need to change to introduce a caching mechanism, clients of this module would still work.

17.7.3. POP Password Encryption

We discussed PyMailCGI's security protocols in the abstract earlier in this chapter. Here, we look at their concrete implementation. PyMailCGI passes user and password state information from page to page using hidden form fields and URL query parameters embedded in HTML reply pages. We studied these techniques in the prior chapter. Such data is transmitted as simple text over network socketswithin the HTML reply stream from the server, and as parameters in the request from the client. As such, it is subject to security issues.

This isn't a concern if you are running a local web server on your machine, as all our examples do. The data is being shipped back and forth between two programs running on your computer, and it is not accessible to the outside world. If you want to install PyMailCGI on a remote web server machine, though, this can be an issue. Because this data is sensitive, we'd ideally like some way to hide it in transit and prevent it from being viewed in server logs.

In the second edition of this book, we developed a custom encryption module using the standard library's rotor encryption module. This module was used to encrypt data inserted into the server's reply stream, and then to later decrypt it when it was returned as a parameter from the client. Unfortunately, in Python 2.4, the rotor module is no longer available in the standard library; it was withdrawn due to security concerns. This seems a somewhat extreme measure (rotor was adequate for simpler applications), but rotor is no longer a usable solution in recent releases.

There are a variety of general approaches to encrypting information transferred back and forth between client and server. Unfortunately again, none is easily implemented for this chapter's example, none is universally applicable, and most involve tools or techniques that are well beyond the scope and size constraints of this text. The sections that follow contain a brief rundown of some of the common techniques in this domain.

17.7.3.1. Manual data encryption: rotor (defunct)

In principle, CGI scripts can manually encrypt any sensitive data they insert into reply streams, as PyMailCGI did in this book's second edition. With the removal of the rotor module, though, Python 2.4's standard library has no encryption tools for this task. Moreover, using the original rotor module's code is not advisable from a maintenance perspective and would not be straightforward, since it was coded in the C language (it's not a simple matter of copying a .py file from a prior release). Unless you are using an older version of Python, rotor is not a real option.

Mostly for historical interest and comparison today, this module was used as follows. It was based on an Enigma-style encryption scheme: we make a new rotor object with a key (and optionally, a rotor count) and call methods to encrypt and decrypt:

 >>> import rotor >>> r = rotor.newrotor('pymailcgi')        # (key, [,numrotors]) >>> r.encrypt('abc123')                    # may return nonprintable chars ' \323an\021\224' >>> x = r.encrypt('spam123')               # result is same len as input >>> x '* _\344\011pY' >>> len(x) 7 >>> r.decrypt(x) 'spam123' 

Notice that the same rotor object can encrypt multiple strings, that the result may contain nonprintable characters (printed as \ascii escape codes when displayed), and that the result is always the same length as the original string. Most important, a string encrypted with rotor can be decrypted in a different process (e.g., in a later CGI script) if we re-create the rotor object:

 >>> import rotor >>> r = rotor.newrotor('pymailcgi')        # can be decrypted in new process >>> r.decrypt('* _\344\011pY')             # use "\ascii" escapes for two chars 'spam123' 

Our secret module by default simply used rotor to encrypt and did no additional encoding of its own. It relies on URL encoding when the password is embedded in a URL parameter and on HTML escaping when the password is embedded in hidden form fields. For URLs, the following sorts of calls occur:

 >>> from secret import encode, decode >>> x = encode('abc$#<>&+')                 # CGI scripts do this >>> x ' \323a\016\317\326\023\0163' >>> import urllib                           # urllib.urlencode does this >>> y = urllib.quote_plus(x) >>> y '+%d3a%0e%cf%d6%13%0e3' >>> a = urllib.unquote_plus(y)              # cgi.FieldStorage does this >>> a ' \323a\016\317\326\023\0163' >>> decode(a)                               # CGI scripts do this 'abc$#<>&+' 

Although rotor itself is not a widely viable option today, these same techniques can be used with other encryption schemes.

17.7.3.2. Manual data encryption: PyCrypto

A variety of encryption tools are available in the third-party public domain, including the popular Python Cryptography Toolkit, also known as PyCrypto. This package adds built-in modules for private and public key algorithms such as AES, DES, IDEA, and RSA encryption, provides a Python module for reading and decrypting PGP files, and much more. Here is an example of using AES encryption, run after installing PyCrypto on my machine with a Windows self-installer:

 >>> from Crypto.Cipher import AES >>> AES.block_size 16 >>> mykey   = 'pymailcgi'.ljust(16, '-')      # key must be 16, 24, or 32 bytes >>> mykey 'pymailcgi-------' >>> >>> password   = 'Already got one.'           # length must be multiple of 16 >>> aesobj1    = AES.new(mykey, AES.MODE_ECB) >>> cyphertext = aesobj1.encrypt(password) >>> cyphertext '\xfez\x95\xb7\x07_"\xd4\xb6\xe3r\x07g~X]' >>> >>> aesobj2  = AES.new(mykey, AES.MODE_ECB) >>> aesobj2.decrypt(cyphertext) 'Already got one.' 

This interface is similar to that of the original rotor module, but it uses better encryption algorithms. AES is a popular private key encryption algorithm. It requires a fixed length key and a data string to have a length that is a multiple of 16 bytes.

Unfortunately, this is not part of standard Python, may be subject to U.S. export controls in binary form at this writing, and is too large and complex a topic for us to address in this text. This makes it less than universally applicable; at the least, shipping its binary installer with this book's examples package may require legal expertise. And since data encryption is a core requirement of PyMailCGI, this seems too strong an external dependency.

Still, if you are able to install and learn PyCrypto, this can be a powerful solution. For more details, see PyCrypto on the Web; at this writing, its web site lives at http://www.amk.ca/python/code/crypto.

17.7.3.3. Secure HTTP transmissions

Provided you are using a server that supports secure HTTP, you can simply write HTML and delegate the encryption to the web server and browser. As long as both ends of the transmission support this protocol, it is probably the ultimate encrypting solution. In fact, it is used by most e-commerce sites on the Web today.

Secure HTTP (HTTPS) is designated in URLs by using the protocol name https:// rather than http://. Under HTTPS, data is still sent with the usual HTTP protocol, but it is encrypted with SSL. HTTPS is supported by most web browsers and can be configured in most web servers, including Apache and the webserver.py script that we are running locally in this chapter. If SSL support is compiled into your Python, Python sockets support it, and the client-side modules urllib and urllib2 we met in Chapter 16 support HTTPS.

Unfortunately, enabling secure HTTP in a web server requires more configuration and background knowledge than we can cover here, and it requires installing tools outside the standard Python release. If you want to explore this issue further, at this writing a coding recipe is available on the ActiveState Programmer Network (ASPN) web site (http://aspn.activestate.com/ASPN), which demonstrates how to set up a simple Python HTTPS server that supports SSL secure communications. It extends the BaseHTTPServer standard library module our webserver.py script uses, to support the SSL protocol. However, it requires the OpenSSL and pyOpenSSL third-party packages, generation of an SSL certificate, and subclasses of the standard library's server classes.

For more details on HTTPS, search the Web. For more on the ASPN coding recipe, try a web search on "Python secure HTTP" to locate it. It is not impossible that some of the HTTPS extensions for Python's standard web server classes may make their way into the Python standard library in the future.

17.7.3.4. Secure cookies

It's possible to replace the form fields and query parameter PyMailCGI currently generates, with client-side cookies marked as secure. Such cookies are automatically encrypted when sent. Unfortunately again, marking a cookie as secure simply means that it can be transmitted only if the communications channel with the host is secure. It does not provide any additional encryption. Because of this, this option really just begs the question; it still requires an HTTPS server.

17.7.3.5. The secret.py module

As you can probably tell, web security is a larger topic than we have time to address here. Because of that, the secret.py module in Example 17-13 finesses the issue, by trying a variety of approaches in turn:

  • If you are able to fetch and install the third-party PyCrypto system described earlier, the module will use that package's AES tools to manually encrypt password data when transmitted together with a username.

  • If not, it will try rotor next (if you happen to be using a prior version of Python, or otherwise install rotor in the version of Python that you're using).

  • And finally, it falls back on a very simplistic default character code shuffling scheme, which you can replace with one of your own if you install this program on the Internet at large.

Function definitions nested in if statements are used to generate the selected encryption scheme's functions. See Example 17-13 for more details.

Example 17-13. PP3E\Internet\Web\PyMailCgi\secret.py

 ############################################################################### # PyMailCGI encodes the POP password whenever it is sent to/from client over # the Net with a username, as hidden text fields or explicit URL params; uses # encode/decode functions in this module to encrypt the pswd--upload your own # version of this module to use a different encryption mechanism or key; pymail # doesn't save the password on the server, and doesn't echo pswd as typed, # but this isn't 100% safe--this module file itself might be vulnerable; ############################################################################### import sys, time dayofweek = time.localtime(time.time( ))[6]    # for custom schemes forceReadablePassword = False ############################################################################### # string encoding schemes ############################################################################### if not forceReadablePassword:     ###########################################################     # don't do anything by default: the urllib.quote or     # cgi.escape calls in commonhtml.py will escape the     # password as needed to embed in URL or HTML; the     # cgi module undoes escapes automatically for us;     ###########################################################     def stringify(old):   return old     def unstringify(old): return old else:     ###########################################################     # convert encoded string to/from a string of digit chars,     # to avoid problems with some special/nonprintable chars,     # but still leave the result semi-readable (but encrypted);     # some browsers had problems with escaped ampersands, etc.;     ###########################################################     separator = '-'     def stringify(old):         new = ''         for char in old:             ascii = str(ord(char))             new   = new + separator + ascii       # '-ascii-ascii-ascii'         return new     def unstringify(old):         new = ''         for ascii in old.split(separator)[1:]:             new = new + chr(int(ascii))         return new ############################################################################### # encryption schemes: try PyCrypto, then rotor, then simple/custom scheme ############################################################################### useCrypto = useRotor = True try:    import Crypto except:     useCrypto = False     try:         import rotor     except:         useRotor = False if useCrypto:     #######################################################     # use third-party pycrypto package's AES algorithm     # assumes pswd has no '\0' on the right: used to pad     # change the private key here if you install this     #######################################################     sys.stderr.write('using PyCrypto\n')     from Crypto.Cipher import AES     mykey = 'pymailcgi2'.ljust(16, '-')       # key must be 16, 24, or 32 bytes     def do_encode(pswd):         over = len(pswd) % 16         if over: pswd += '\0' * (16-over)     # pad: len must be multiple of 16         aesobj = AES.new(mykey, AES.MODE_ECB)         return aesobj.encrypt(pswd)     def do_decode(pswd):         aesobj = AES.new(mykey, AES.MODE_ECB)         pswd   = aesobj.decrypt(pswd)         return pswd.rstrip('\0') elif useRotor:     #######################################################     # use the standard lib's rotor module to encode pswd     # this does a better job of encryption than code above     # unfortunately, it is no longer available in Py 2.4     #######################################################     sys.stderr.write('using rotor\n')     import rotor     mykey = 'pymailcgi2'     def do_encode(pswd):         robj = rotor.newrotor(mykey)              # use enigma encryption         return robj.encrypt(pswd)     def do_decode(pswd):         robj = rotor.newrotor(mykey)         return robj.decrypt(pswd) else:     #######################################################     # use our own custom scheme as a last resort     # shuffle characters in some reversible fashion     # caveat: very simple -- replace with one of your own     #######################################################     sys.stderr.write('using simple\n')     adder = 1     def do_encode(pswd):         pswd = 'b' + pswd + '46'         res = ''         for char in pswd:             res = res + chr(ord(char) + adder)    # inc each ASCII code         return str(res)     def do_decode(pswd):         pswd = pswd[1:-3]         res = ''         for char in pswd:             res = res + chr(ord(char) - adder)         return res ############################################################################### # top-level entry points ############################################################################### def encode(pswd):     return stringify(do_encode(pswd))       # encrypt plus string encode def decode(pswd):     return do_decode(unstringify(pswd)) 

In addition to encryption, this module also implements an encoding method for already encrypted strings, which transforms them to and from printable characters. By default, the encoding functions do nothing, and the system relies on straight URL or HTML encoding of the encrypted string. An optional encoding scheme translates the encrypted string to a string of ASCII code digits separated by dashes. Either encoding method makes nonprintable characters in the encrypted string printable.

To illustrate, let's test this module's tools interactively. For this test, we set forceReadablePassword to TRue. The top-level entry points encode and decode into printable characters:

 >>> from secret import * using PyCrypto >>> data = encode('spam@123+') >>> data '-47-248-2-170-107-242-175-18-227-249-53-130-14-140-163-107' >>> decode(data) 'spam@123+' 

But there are actually two steps to thisencryption and printable encoding:

 >>> raw = do_encode('spam@123+') >>> raw '/\xf8\x02\xaak\xf2\xaf\x12\xe3\xf95\x82\x0e\x8c\xa3k' >>> text = stringify(raw) >>> text '-47-248-2-170-107-242-175-18-227-249-53-130-14-140-163-107' >>> len(raw), len(text) (16, 58) 

Here's what the encoding looks like without the extra printable encoding:

 >>> raw = do_encode('spam@123+') >>> raw '/\xf8\x02\xaak\xf2\xaf\x12\xe3\xf95\x82\x0e\x8c\xa3k' >>> do_decode(raw) 'spam@123+' 

17.7.3.6. Rolling your own encryptor

As is, PyMailCGI avoids ever passing the POP account username and password across the Net together in a single transaction, unless the password is encrypted according to the module secret.py on the server. This module can be different everywhere PyMailCGI is installed, and it can be uploaded anew in the futureencrypted passwords aren't persistent and live only for the duration of one mail-processing interaction session. Provided you don't publish your encryption code or its private keys, your data will be as secure as the custom encryption module you provide on your own server.

If you wish to use this system on the general Internet, you'll want to tailor this code. Ideally, you'll install PyCrypto and change the private key string. Barring that, replace Example 17-13 with a custom encryption coding scheme of your own or deploy one of the general techniques mentioned earlier such as an HTTPS-capable web server. In any event, this software makes no guarantees; the security of your password is ultimately up to you to ensure. For additional information on security tools and techniques, search the Web and consult books geared exclusively toward web-programming techniques.

Because the encryption schemes used by PyMailCGI are reversible, it is possible to reconstruct my email account's password if you happen to see its encrypted form in a screenshot, unless the private key listed in secret.py was different when the tests shown were run. To sidestep this issue, the email account used in all of this book's examples is temporary and will be deleted by the time you read these words. Please use an email account of your own to test-drive the system.


17.7.4. Common Utilities Module

The file commonhtml.py, shown in Example 17-14, is the Grand Central Station of this applicationits code is used and reused by just about every other file in the system. Most of it is self-explanatory, and we've already met most of its core idea earlier, in conjunction with the CGI scripts that use it.

I haven't talked about its debugging support, though. Notice that this module assigns sys.stderr to sys.stdout, in an attempt to force the text of Python error messages to show up in the client's browser (remember, uncaught exceptions print details to sys.stderr). That works sometimes in PyMailCGI, but not alwaysthe error text shows up in a web page only if a page_header call has already printed a response preamble. If you want to see all error messages, make sure you call page_header (or print Content-type: lines manually) before any other processing. This module also defines functions that dump raw CGI environment information to the browser (dumpstatepage), and that wrap calls to functions that print status messages so that their output isn't added to the HTML stream (runsilent).

I'll leave the discovery of any remaining magic in the code in Example 17-14 up to you, the reader. You are hereby admonished to go forth and read, refer, and reuse.

Example 17-14. PP3E\Internet\Web\PyMailCgi\commonhtml.py

 #!/usr/bin/python ############################################################################## # generate standard page header, list, and footer HTML; isolates HTML # generation-related details in this file; text printed here goes over a # socket to the client, to create parts of a new web page in the web browser; # uses one print per line, instead of string blocks; uses urllib to escape # parms in URL links auto from a dict, but cgi.escape to put them in HTML # hidden fields; some of the tools here are useful outside pymailcgi; could # also return HTML generated here instead of  printing it, so it could be # included in other pages; could also structure as a single CGI script that # gets and tests a next action name as a hidden form field; caveat: this # system works, but was largely written during a two-hour layover at the # Chicago O'Hare airport: there is room for improvement and optimization; ############################################################################## import cgi, urllib, sys, os sys.stderr = sys.stdout           # show error messages in browser from externs import mailconfig    # from a package somewhere on server # my cgi address root #urlroot = 'http://starship.python.net/~lutz/PyMailCgi/' #urlroot = 'http://localhost:8000/cgi-bin/' urlroot  = ''  # use minimal, relative paths def pageheader(app='PyMailCGI', color='#FFFFFF', kind='main', info=''):     print 'Content-type: text/html\n'     print '<html><head><title>%s: %s page (PP3E)</title></head>' % (app, kind)     print '<body bgcolor="%s"><h1>%s %s</h1><hr>' % (color, app, (info or kind)) def pagefooter(root='pymailcgi.html'):     print '</p><hr><a href="http://www.python.org">'     print '<img src="/books/2/726/1/html/2/../PythonPoweredSmall.gif" '     print 'align=left alt="[Python Logo]" border=0 hspace=15></a>'     print '<a href="../%s">Back to root page</a>' % root     print '</body></html>' def formatlink(cgiurl, parmdict):     """     make "%url?key=val&key=val" query link from a dictionary;     escapes str( ) of all key and val with %xx, changes ' ' to +     note that URL escapes are different from HTML (cgi.escape)     """     parmtext = urllib.urlencode(parmdict)           # calls urllib.quote_plus     return '%s?%s' % (cgiurl, parmtext)             # urllib does all the work def pagelistsimple(linklist):                       # show simple ordered list     print '<ol>'     for (text, cgiurl, parmdict) in linklist:         link = formatlink(cgiurl, parmdict)         text = cgi.escape(text)         print '<li><a href="%s">\n    %s</a>' % (link, text)     print '</ol>' def pagelisttable(linklist):                        # show list in a table     print '<p><table border>'                       # escape text to be safe     count = 1     for (text, cgiurl, parmdict) in linklist:         link = formatlink(cgiurl, parmdict)         text = cgi.escape(text)         print '<tr><th><a href="%s">View</a> %d<td>\n %s' % (link, count, text)         count = count+1     print '</table>' def listpage(linkslist, kind='selection list'):     pageheader(kind=kind)     pagelisttable(linkslist)         # [('text', 'cgiurl', {'parm':'value'})]     pagefooter( ) def messagearea(headers, text, extra=''):     print '<table border cellpadding=3>'     for hdr in ('From', 'To', 'Cc', 'Subject'):         val = headers.get(hdr, '?')         val = cgi.escape(val, quote=1)         print '<tr><th align=right>%s:' % hdr         print '    <td><input type=text '         print '    name=%s value="%s" %s size=60>' % (hdr, val, extra)     print '<tr><th align=right>Text:'     print '<td><textarea name=text cols=80 rows=10 %s>' % extra     print '%s\n</textarea></table>' % (cgi.escape(text) or '?')   # if has </>s def viewattachmentlinks(partnames):     """     create hyperlinks to locally saved part/attachment files     when clicked, user's web browser will handle opening     assumes just one user, only valid while viewing 1 msg     """     print '<hr><table border cellpadding=3><tr><th>Parts:'     for filename in partnames:         basename = os.path.basename(filename)         filename  = filename.replace('\\', '/') # Windows hack         print '<td><a href=../%s>%s</a>' % (filename, basename)     print '</table><hr>' def viewpage(msgnum, headers, text, form, parts=[]):     """     on View + select (generated link click)     very subtle thing: at this point, pswd was URL encoded in the     link, and then unencoded by CGI input parser; it's being embedded     in HTML here, so we use cgi.escape; this usually sends nonprintable     chars in the hidden field's HTML, but works on ie and ns anyhow:     in url:  ?user=lutz&mnum=3&pswd=%8cg%c2P%1e%f0%5b%c5J%1c%f3&...     in html: <input type=hidden name=pswd value="...nonprintables..">     could urllib.quote the html field here too, but must urllib.unquote     in next script (which precludes passing the inputs in a URL instead     of the form); can also fall back on numeric string fmt in secret.py     """     pageheader(kind='View')     user, pswd, site = map(cgi.escape, getstandardpopfields(form))     print '<form method=post action="%sonViewPageAction.py">' % urlroot     print '<input type=hidden name=mnum value="%s">' % msgnum     print '<input type=hidden name=user value="%s">' % user     # from page|url     print '<input type=hidden name=site value="%s">' % site     # for deletes     print '<input type=hidden name=pswd value="%s">' % pswd     # pswd encoded     messagearea(headers, text, 'readonly')     if parts: viewattachmentlinks(parts)     # onViewPageAction.quotetext needs date passed in page     print '<input type=hidden name=Date value="%s">' % headers.get('Date','?')     print '<table><tr><th align=right>Action:'     print '<td><select name=action>'     print '    <option>Reply<option>Forward<option>Delete</select>'     print '<input type=submit value="Next">'     print '</table></form>'                      # no 'reset' needed here     pagefooter( ) def sendattachmentwidgets(maxattach=3):     print '<p><b>Attach:</b><br>'     for i in range(1, maxattach+1):         print '<input size=80 type=file name=attach%d><br>' % i     print '</p>' def editpage(kind, headers={}, text=''):     # on Send, View+select+Reply, View+select+Fwd     pageheader(kind=kind)     print '<p><form enctype="multipart/form-data" method=post',     print 'action="%sonEditPageSend.py">' % urlroot     if mailconfig.mysignature:         text = '\n%s\n%s' % (mailconfig.mysignature, text)     messagearea(headers, text)     sendattachmentwidgets( )     print '<input type=submit value="Send">'     print '<input type=reset  value="Reset">'     print '</form>'     pagefooter( ) def errorpage(message, stacktrace=True):     pageheader(kind='Error')                        # was sys.exc_type/exc_value     exc_type, exc_value, exc_tb = sys.exc_info( )     print '<h2>Error Description</h2><p>', message     print '<h2>Python Exception</h2><p>',  cgi.escape(str(exc_type))     print '<h2>Exception details</h2><p>', cgi.escape(str(exc_value))     if stacktrace:         print '<h2>Exception traceback</h2><p><pre>'         import traceback         traceback.print_tb(exc_tb, None, sys.stdout)         print '</pre>'     pagefooter( ) def confirmationpage(kind):     pageheader(kind='Confirmation')     print '<h2>%s operation was successful</h2>' % kind     print '<p>Press the link below to return to the main page.</p>'     pagefooter( ) def getfield(form, field, default=''):     # emulate dictionary get method     return (form.has_key(field) and form[field].value) or default def getstandardpopfields(form):     """     fields can arrive missing or '' or with a real value     hardcoded in a URL; default to mailconfig settings     """     return (getfield(form, 'user', mailconfig.popusername),             getfield(form, 'pswd', '?'),             getfield(form, 'site', mailconfig.popservername)) def getstandardsmtpfields(form):     return  getfield(form, 'site', mailconfig.smtpservername) def runsilent(func, args):     """     run a function without writing stdout     ex: suppress print's in imported tools     else they go to the client/browser     """     class Silent:         def write(self, line): pass     save_stdout = sys.stdout     sys.stdout  = Silent( )                        # send print to dummy object     try:                                           # which has a write method         result = func(*args)                       # try to return func result     finally:                                       # but always restore stdout         sys.stdout = save_stdout     return result def dumpstatepage(exhaustive=0):     """     for debugging: call me at top of a CGI to     generate a new page with CGI state details     """     if exhaustive:         cgi.test( )                       # show page with form, environ, etc.     else:         pageheader(kind='state dump')         form = cgi.FieldStorage( )        # show just form fields names/values         cgi.print_form(form)         pagefooter( )     sys.exit( ) def selftest(showastable=False):                    # make phony web page     links = [                                       # [(text, url, {parms})]         ('text1', urlroot + 'page1.cgi', {'a':1}),         ('text2', urlroot + 'page1.cgi', {'a':2, 'b':'3'}),         ('text3', urlroot + 'page2.cgi', {'x':'a b', 'y':'a<b&c', 'z':'?'}),         ('te<>4', urlroot + 'page2.cgi', {'<x>':'', 'y':'<a>', 'z':None})]     pageheader(kind='View')     if showastable:         pagelisttable(links)     else:         pagelistsimple(links)     pagefooter( ) if _ _name_ _ == '_ _main_ _':                          # when run, not imported     selftest(len(sys.argv) > 1)                      # HTML goes to stdout 




Programming Python
Programming Python
ISBN: 0596009259
EAN: 2147483647
Year: 2004
Pages: 270
Authors: Mark Lutz

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net