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 simple—they 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.
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. PP3EInternetWebPyMailCgiexterns.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. PP3EInternetEmailmailconfig.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 = '[email protected]' 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
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
design—loadmail
changes won’t
impact its clients. It is mostly a hook for future expansion.
Example 17-12. PP3EInternetWebPyMailCgiloadmail.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) + ' ') 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 at—just 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.
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 sockets—within 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.
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 21224' >>>x = r.encrypt('spam123')
# result is same len as input >>>x
'* _344 11pY' >>>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 11pY')
# 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 16317326 23 163' >>>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 16317326 23 163' >>>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.
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
'xfezx95xb7x07_"xd4xb6xe3rx07g~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.
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.
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.
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. PP3EInternetWebPyMailCgisecret.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 ' ' on the right: used to pad # change the private key here if you install this ####################################################### sys.stderr.write('using PyCrypto ') 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 += ' ' * (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(' ') 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 ') 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 ') 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 this—encryption and printable encoding:
>>>raw = do_encode('spam@123+')
>>>raw
'/xf8x02xaakxf2xafx12xe3xf95x82x0ex8cxa3k' >>>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
'/xf8x02xaakxf2xafx12xe3xf95x82x0ex8cxa3k' >>>do_decode(raw)
'spam@123+'
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 future—encrypted 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.
The file commonhtml.py, shown in Example 17-14, is the Grand Central Station of this application—its 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 always—the 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. PP3EInternetWebPyMailCgicommonhtml.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 ' 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="../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"> %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> %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 </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 = ' %s %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
3.15.14.98