Read Programming Python Online

Authors: Mark Lutz

Tags: #COMPUTERS / Programming Languages / Python

Programming Python (169 page)

BOOK: Programming Python
3.56Mb size Format: txt, pdf, ePub
ads
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 or obfuscated 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 16-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. As this system is a prototype at large,
security is just one of a handful of limitations which would have to
be more fully addressed in a robust production-grade
version.

Note

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.

Common Utilities Module

Finally, the file
commonhtml.py
in
Example 16-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
). A
version 3.0 addition also attempts to work around the fact that built-in
print calls can fail in Python 3.1 for some types of Unicode text (e.g.,
non-ASCII character sets in Internationalized headers), by forcing
binary mode and bytes for the output stream (
print
).

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

Example 16-14. PP4E\Internet\Web\PyMailCgi\cgi-bin\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 params in URL links auto from a
dict, but cgi.escape to put them in HTML hidden fields; some tools here may be
useful outside pymailcgi; could also return the 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 much room for improvement and optimization;
##################################################################################
"""
import cgi, urllib.parse, sys, os
# 3.0: Python 3.1 has issues printing some decoded str as text to stdout
import builtins
bstdout = open(sys.stdout.fileno(), 'wb')
def print(*args, end='\n'):
try:
builtins.print(*args, end=end)
sys.stdout.flush()
except:
for arg in args:
bstdout.write(str(arg).encode('utf-8'))
if end: bstdout.write(end.encode('utf-8'))
bstdout.flush()
sys.stderr = sys.stdout # show error messages in browser
from externs import mailconfig # from a package somewhere on server
from externs import mailtools # need parser for header decoding
parser = mailtools.MailParser() # one per process in this module
# 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('%s: %s page (PP4E)' % (app, kind))
print('%s %s
' % (color, app, (info or kind)))
def pagefooter(root='pymailcgi.html'):
print('


')
print('print('align=left alt="[Python Logo]" border=0 hspace=15>
')
print('Back to root page' % root)
print('')
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.parse.urlencode(parmdict) # calls parse.quote_plus
return '%s?%s' % (cgiurl, parmtext) # urllib does all the work
def pagelistsimple(linklist): # show simple ordered list
print('
    ')
    for (text, cgiurl, parmdict) in linklist:
    link = formatlink(cgiurl, parmdict)
    text = cgi.escape(text)
    print('
  1. \n %s' % (link, text))
    print('
')
def pagelisttable(linklist): # show list in a table
print('

') # escape text to be safe
for (text, cgiurl, parmdict) in linklist:
link = formatlink(cgiurl, parmdict)
text = cgi.escape(text)
print('
View\n %s' % (link, text))
print('
')
def listpage(linkslist, kind='selection list'):
pageheader(kind=kind)
pagelisttable(linkslist) # [('text', 'cgiurl', {'parm':'value'})]
pagefooter()
def messagearea(headers, text, extra=''): # extra for readonly
addrhdrs = ('From', 'To', 'Cc', 'Bcc') # decode names only
print('')
for hdr in ('From', 'To', 'Cc', 'Subject'):
rawhdr = headers.get(hdr, '?')
if hdr not in addrhdrs:
dechdr = parser.decodeHeader(rawhdr) # 3.0: decode for display
else: # encoded on sends
dechdr = parser.decodeAddrHeader(rawhdr) # email names only
val = cgi.escape(dechdr, quote=1)
print('
%s:' % hdr)
print('
print(' name=%s value="%s" %s size=60>' % (hdr, val, extra))
print('
Text:')
print('
' % (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('
Parts:')
for filename in partnames:
basename = os.path.basename(filename)
filename = filename.replace('\\', '/') # Windows hack
print('
%s' % (filename, basename))
print('

')
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:
could urllib.parse.quote html field here too, but must urllib.parse.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 = list(map(cgi.escape, getstandardpopfields(form)))
print('
' % urlroot)
print('' % msgnum)
print('' % user) # from page|url
print('' % site) # for deletes
print('' % pswd) # pswd encoded
messagearea(headers, text, 'readonly')
if parts: viewattachmentlinks(parts)
# onViewPageAction.quotetext needs date passed in page
print('' % headers.get('Date','?'))
print('
Action:')
print('
')
print('')
print('
') # no 'reset' needed here
pagefooter()
def sendattachmentwidgets(maxattach=3):
print('

Attach:
')
for i in range(1, maxattach+1):
print('
' % i)
print('

')
def editpage(kind, headers={}, text=''):
# on Send, View+select+Reply, View+select+Fwd
pageheader(kind=kind)
print('

print('action="%sonEditPageSend.py">' % urlroot)
if mailconfig.mysignature:
text = '\n%s\n%s' % (mailconfig.mysignature, text)
messagearea(headers, text)
sendattachmentwidgets()
print('')
print('')
print('
')
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('Error Description

', message)
print('Python Exception

', cgi.escape(str(exc_type)))
print('Exception details

', cgi.escape(str(exc_value)))
if stacktrace:
print('Exception traceback

')
import traceback
traceback.print_tb(exc_tb, None, sys.stdout)
print('
')
pagefooter()
def confirmationpage(kind):
pageheader(kind='Confirmation')
print('%s operation was successful' % kind)
print('

Press the link below to return to the main page.

')
pagefooter()
def getfield(form, field, default=''):
# emulate dictionary get method
return (field in form 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('te<>4', urlroot + 'page2.cgi', {'':'', 'y':'', '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

Other books

Showdown by William W. Johnstone
VEGAS follows you home by Sadie Grubor
The First Ladies by Feather Schwartz Foster
GOODBYE to YESTERDAY by WANDA E. BRUNSTETTER
The Hollows by Kim Harrison
Gail Eastwood by An Unlikely Hero
The Skeleton Room by Kate Ellis
The Brigadier's Daughter by Catherine March
A Love of My Own by E. Lynn Harris